1. Conduid
  2. Search
  3. Netra Confluence MCP
MCP server · Search

Netra Confluence MCP

This tool lets your AI assistant (Claude, GitHub Copilot, or any MCP-compatible client) read and edit Confluence pages for you. You describe what to change in plain language, and the tool will do it for you.

34Low

Scored a month ago · breakdown

About Netra Confluence MCP

Netra Confluence MCP is an MCP server published by sunishbharat in the Search category: this tool lets your AI assistant (Claude, GitHub Copilot, or any MCP-compatible client) read and edit Confluence pages for you. You describe what to change in plain language, and the tool will do it for you.

The repository has 1 stars and 0 forks, with the last commit 14 days ago. Recent commit activity is the strongest signal that reported issues will get attention.

Install

From source
git clone https://github.com/sunishbharat/netra-confluence-mcp

This server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.

Tools (5)

clone_release_reportClones a page, updates all version tokens and macro IDs

Parameters not published.

create_page_from_adfCreates a new page from an ADF body

Parameters not published.

inspect_page_jqlLists all Jira macro JQL queries on a page with location paths

Parameters not published.

update_page_macrosReplaces values in macro parameters on any page

Parameters not published.

update_release_versionLike above, also updates date node timestamps

Parameters not published.

Ask AI

✦

Ask AI about Netra Confluence MCP

Powered by Claude · Grounded in docs

I know everything about Netra Confluence MCP. Ask me about installation, configuration, usage, or troubleshooting.

Security checks

Static checks derived from the repository 5 days ago. Heuristics on the source, not an audit.

  • ✓README presentThe repository ships documentation.
  • ✓License declaredUsable under a stated license.
  • ✓Tests presentA test suite exists in the repository.
  • ✓Dependencies pinnedA lockfile pins dependency versions.
  • ✓No dynamic code executionSource scan found no eval, dynamic Function, or shell execution.

1 check has not been run yet.

Releases

v1.0.1v1.0.1
v1.0.0v1.0.0

README

Netra MCP Server - Confluence Write Tool

This tool lets your AI assistant (Claude, GitHub Copilot, or any MCP-compatible client) read and edit Confluence pages for you. You describe what to change in plain language, and the AI calls the right tool.

What it can do:

  • Show you every JQL query on a Confluence page, extracted cleanly with its location
  • Replace version tokens or JQL values across an entire page in one shot
  • Preview every change before anything is written (dry-run is on by default)
  • Clone a release report page to a new version with all macros updated

Before you start

You need:

  • Python 3.12 or newer - check with python --version
  • uv (Python package manager) - install from docs.astral.sh/uv
  • A Confluence Cloud account
  • A Confluence API token - create one at: Atlassian account -> Security -> API tokens -> Create API token

Setup (one time)

1. Clone the repository

git clone <repo-url>
cd netra-confluence-mcp

2. Install dependencies

uv sync

3. Create your configuration file

cp .env.example .env

Open .env and fill in your details:

CONFLUENCE_BASE_URL=https://your-org.atlassian.net
CONFLUENCE_API_TOKEN=your-api-token-here
CONFLUENCE_USER_EMAIL=your-email@example.com
CONFLUENCE_SITE_URL=https://your-org.atlassian.net
LOG_LEVEL=INFO

CONFLUENCE_BASE_URL and CONFLUENCE_SITE_URL are usually the same value.

4. Test that it starts

uv run python server.py

You should see the server start without errors. Press Ctrl+C to stop it - your AI client will start it automatically when needed.


Connect your AI client

Claude Desktop

  1. Open your Claude Desktop config file:

    • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
    • Windows: %APPDATA%\Claude\claude_desktop_config.json
  2. Add this block (adjust the cwd path to where you cloned the repo):

{
  "mcpServers": {
    "netra-confluence-writer": {
      "command": "uv",
      "args": ["run", "python", "server.py"],
      "cwd": "/absolute/path/to/netra-confluence-mcp"
    }
  }
}
  1. Restart Claude Desktop. The Confluence tools will appear in the tool list.

Claude Desktop over HTTP (mcp-remote bridge)

When the server runs in HTTP mode (Docker container, remote host, or SERVER_TRANSPORT=http uv run python server.py) instead of as a stdio child process, use mcp-remote as a stdio-to-HTTP bridge. Claude Desktop's JSON config only spawns stdio children, so the bridge translates between Claude Desktop and the running HTTP endpoint.

Install mcp-remote once:

npm install -g mcp-remote

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "netra-confluence-writer": {
      "command": "mcp-remote",
      "args": ["http://127.0.0.1:8765/mcp"]
    }
  }
}

Prefer no global install? Use npx instead:

{
  "mcpServers": {
    "netra-confluence-writer": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://127.0.0.1:8765/mcp"]
    }
  }
}

The Confluence tools appear in the tool list once the bridge connects to the running server. The server must already be listening at the URL in args (see Production transport (http)).

VS Code with GitHub Copilot

  1. Open your VS Code user settings (Ctrl+Shift+P -> "Open User Settings JSON")

  2. Add:

{
  "mcp": {
    "servers": {
      "netra-confluence-writer": {
        "command": "uv",
        "args": ["run", "python", "server.py"],
        "cwd": "/absolute/path/to/netra-confluence-mcp"
      }
    }
  }
}
  1. Reload VS Code. The tools are available in Copilot Chat when you click the tools icon.

VS Code with GitHub Copilot - shared server on CF

When the server is deployed on Cloud Foundry (see Docker and cloud deployment), Copilot connects to it over http and sends your own Atlassian credentials as headers. Nothing runs locally.

  1. Open the MCP configuration: Cmd+Shift+P / Ctrl+Shift+P -> "MCP: Open User Configuration". The file is mcp.json:

    • macOS: ~/Library/Application Support/Code/User/mcp.json
    • Windows: %APPDATA%\Code\User\mcp.json
    • Or per project: .vscode/mcp.json in the workspace
  2. Add the server (replace the URL with your CF route, and the email and tokens with your own):

{
  "servers": {
    "netra-confluence-jira-mcp": {
      "url": "https://<your-cf-route>/mcp",
      "type": "http",
      "headers": {
        "X-Confluence-User-Email": "you@example.com",
        "X-Confluence-Api-Token": "<your Atlassian API token>",
        "X-Jira-User-Email": "you@example.com",
        "X-Jira-Api-Token": "<your Atlassian API token>"
      }
    }
  },
  "inputs": []
}
  1. Start the server from Command Palette -> "MCP: List Servers" -> netra-confluence-jira-mcp -> Start (or Restart after editing). The Confluence and Jira tools appear in Copilot Agent mode under the tools icon.

Option: same token for Confluence and Jira (two headers). An Atlassian API token works for Confluence and Jira on the same site. If both pairs above would hold the same email and token, drop the X-Jira-* lines; the Jira tools then use the X-Confluence-* pair:

{
  "servers": {
    "netra-confluence-jira-mcp": {
      "url": "https://<your-cf-route>/mcp",
      "type": "http",
      "headers": {
        "X-Confluence-User-Email": "you@example.com",
        "X-Confluence-Api-Token": "<your Atlassian API token>"
      }
    }
  },
  "inputs": []
}

Remove both X-Jira-* lines or neither. With only one of them present, Jira calls fail rather than mixing a Jira email with the Confluence token. Use the four-header version only when Jira needs a different token.

Notes:

  • The URL must end in /mcp. The root URL of the route is not the MCP endpoint.
  • Keep the token out of shared files. This file holds a live API token in plain text. Never commit a .vscode/mcp.json that contains one. To have VS Code ask for the token once and keep it in its encrypted secret storage instead, use an input:
{
  "inputs": [
    {"type": "promptString", "id": "atl-token", "description": "Atlassian API token", "password": true}
  ],
  "servers": {
    "netra-confluence-jira-mcp": {
      "url": "https://<your-cf-route>/mcp",
      "type": "http",
      "headers": {
        "X-Confluence-User-Email": "you@example.com",
        "X-Confluence-Api-Token": "${input:atl-token}"
      }
    }
  }
}
  • Updating the token: edit the value in mcp.json and restart the server from "MCP: List Servers". With an input, clear the stored value (Command Palette, type "MCP") and VS Code asks for the new token on the next start.
  • Credential errors: a tool response of {"status": "ERROR", "error": "missing per-user ... credentials"} means the headers did not reach the server; check the header names and that the server was restarted after editing.

Other MCP clients

Use uv run python server.py as the server command with the project directory as the working directory. By default the server uses stdio transport (a child process spawned per client) - the standard transport for local MCP clients.


Production transport (http)

For cloud or shared deployment the server runs standalone as a web service instead of being spawned as a child process - one server process serves many clients over HTTP. Every tool call here is a single, independent read-transform-write against Confluence (there is no clarification loop or multi-turn session), so the server runs stateless: no session store, no Valkey, nothing to keep in memory between requests.

Start it in HTTP mode:

SERVER_TRANSPORT=http uv run python server.py

(Or set SERVER_TRANSPORT=http in .env instead.) main() in server.py reads the settings and passes host, port, and stateless_http=True directly to FastMCP's run() for this transport, starting uvicorn bound to 127.0.0.1:8765 by default. The /mcp path is FastMCP's default endpoint for the streamable HTTP protocol:

http://127.0.0.1:8765/mcp

To change the bind address or port, set SERVER_HOST / SERVER_PORT before starting (e.g. host 0.0.0.0 to accept connections from other machines). A /health endpoint is also exposed for load balancer / CF health checks:

curl http://127.0.0.1:8765/health   # {"status":"ok"}

Connect with the MCP Inspector (npx @modelcontextprotocol/inspector, transport "Streamable HTTP", URL http://127.0.0.1:8765/mcp), register it with Claude Code:

claude mcp add --transport http netra-confluence http://127.0.0.1:8765/mcp

or use mcp-remote as a stdio-to-HTTP bridge from Claude Desktop (see Claude Desktop over HTTP).

No endpoint auth yet. There is no API-key gate on the /mcp endpoint itself (see Phase 4 in docs/netra-mcp-confluence-write-phased-design.md). Keep the default loopback bind (127.0.0.1) unless you are on a trusted network or sitting behind an auth-terminating proxy - see "Team access on http (multi-user credential passthrough)" below for why this matters even after per-user credentials are wired up.

Team access on http (multi-user credential passthrough)

On http transport the server holds no Confluence identity of its own - CONFLUENCE_API_TOKEN / CONFLUENCE_USER_EMAIL must be left unset in the server's environment. Every tool call instead carries the caller's own identity as request headers:

X-Confluence-User-Email: alice@example.com
X-Confluence-Api-Token:  <alice's own API token>

The server builds a fresh Confluence client from those headers per call. Two teammates hitting the same shared server therefore write as themselves - Confluence page history shows "Alice" and "Bob", not a shared service account - and each person is bound by their own Confluence permissions. A request with no headers, or a blank header, gets {"status": "ERROR", "error": "missing per-user Confluence credentials"} - there is no fallback to a shared identity.

Each team member's Claude Desktop config carries their own headers via mcp-remote --header:

{
  "mcpServers": {
    "netra-confluence-writer": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote", "https://netra-confluence.<your-domain>/mcp",
        "--header", "X-Confluence-User-Email: alice@example.com",
        "--header", "X-Confluence-Api-Token: ${CONFLUENCE_API_TOKEN}"
      ],
      "env": { "CONFLUENCE_API_TOKEN": "<alice's own token>" }
    }
  }
}

(The env indirection keeps the raw token out of the JSON config body itself. Clients with native HTTP-header support can configure the two headers directly instead of going through mcp-remote.)

Requirements for running this in production, not just locally:

  • TLS end to end. These headers carry a live API token; the route must be HTTPS-only. A plain-HTTP bind is only acceptable on 127.0.0.1.
  • The endpoint still needs its own gate. Header passthrough authenticates the Confluence call, not the MCP endpoint - anyone who can reach /mcp and supply any valid Atlassian token can use the server as an open Confluence proxy. Keep the route internal-only, or put an API-key/mTLS gate in front of it at the router, until OAuth 2.0 (Tier 2, see docs/netra-mcp-confluence-write-phased-design.md section 16.4) lands.
  • Don't log the headers. X-Confluence-Api-Token must never end up in request logs. A structlog processor redacts any log field whose key matches token/password/api_key/authorization (case-insensitive) to [REDACTED] as a backstop, but no code should pass credential values to a log call in the first place.

Jira tools on http. The Jira tools resolve credentials from the same request: X-Jira-User-Email / X-Jira-Api-Token if present, otherwise the X-Confluence-* pair (one Atlassian API token works for both products on the same site). Add the Jira pair only if you want Jira calls to use a different token:

"--header", "X-Jira-User-Email: alice@example.com",
"--header", "X-Jira-Api-Token: ${JIRA_API_TOKEN}"

The two pairs are never mixed, and env credentials are never used on http.

If you don't need a shared server at all, per-user stdio (each teammate runs their own local process with their own .env - see Setup) already gives native per-user attribution today with zero extra configuration.


Finding your Confluence page ID

The page ID is in the URL when you open a Confluence page:

https://your-org.atlassian.net/wiki/spaces/MYSPACE/pages/1234567890/Page+Title
                                                          ^^^^^^^^^^
                                                          this is your page ID

Recipe: list all JQL queries on a page

Before you decide what to change, you can ask the AI to just show you what is on the page. This is read-only - nothing is written.

"Show me all the JQL queries on Confluence page 34334."

The AI calls inspect_page_jql("34334") and returns:

{
  "status": "INSPECTION",
  "page_id": "34334",
  "title": "R1.0 Release Report ProjectX",
  "jira_macro_count": 7,
  "jql_queries": [
    {
      "macro_id": "abc-123-...",
      "location_path": "table[0]/row[1]/cell[0]",
      "jql": "project = MYPROJ AND \"release status app1\" in (V1.0, V2.0) AND fixVersion = R1.0",
      "columns": "type,key,summary,status",
      "server": "MyJira",
      "max_issues": "20"
    },
    {
      "macro_id": "def-456-...",
      "location_path": "table[1]/row[0]/cell[0]",
      "jql": "project = MYPROJ AND label = app1_R1.0_Baseline AND fixVersion = R1.0",
      "columns": "type,key,summary",
      "server": "MyJira",
      "max_issues": "20"
    }
  ],
  "unique_strings": ["MYPROJ", "R1.0", "V1.0", "V2.0", "app1_R1.0", "app1_R1.0_Baseline"]
}

Key fields in the response:

  • status: "INSPECTION" - always present; confirms this is the read-only path. No write happened.
  • jira_macro_count - how many Jira macros the page has. If this is 0, the page has no Jira macros and there is nothing to replace.
  • jql_queries[].jql - the exact JQL string stored in each macro. Copy this verbatim if you plan to write a replacement rule - whitespace and quoting must match exactly.
  • jql_queries[].location_path - human-readable ADF path so you know which macro you are looking at (table[0]/row[1]/cell[0]).
  • jql_queries[].macro_id - the macro's UUID. Preserved on update, regenerated on clone.
  • unique_strings - deduplicated token candidates already split on JQL syntax (whitespace, operators, quotes). Useful as a starting point to spot candidates, but not a direct replacement rule - it is a list of candidates, not the full substrings you would replace.

When to use this recipe:

  • Before any update, to see what release tokens, project names, or version labels appear on the page.
  • After an update, to verify the change landed where you expected.
  • When you are handed an unfamiliar page and need to know what is on it before deciding what to do.

You can also pass a full Confluence URL instead of a bare page ID - the AI will extract the ID:

"Show me the JQL on https://your-org.atlassian.net/wiki/spaces/ENG/pages/34334/R1.0-Report"

Controlling the output format

The tool always returns the full JSON shape (with status, page_id, title, jira_macro_count, jql_queries, and unique_strings). The agent has the full response in its context but can choose to show you only the JQL strings. So:

Your prompt What the agent shows you
"Show me all the JQL queries on page 34334." The full JSON dump (default behavior, what we documented in the recipe above)
"List just the JQL strings on page 34334." A plain list of only the jql field values
"Show me the JQL queries on page 34334 as a bullet list." One bullet per JQL, no metadata
"Show me only the JQLs and nothing else from page 34334." Same as above, with explicit instruction to suppress the rest
"Give me a markdown table of the JQL queries on page 34334." A formatted table - the agent can decide which columns to include

A competent MCP client (Claude, Rovo) will honor all of these. The user always sees what they asked for.


Worked example: change version V10.0 to V20.0

Say you have a release report page at page ID 2811939182 and every JQL query on it references V10.0. Here is how to update the whole page in three steps.

Step 1 - Inspect the page

Ask your AI:

"What JQL queries are on Confluence page 2811939182?"

The AI calls inspect_page_jql and returns something like:

{
  "status": "INSPECTION",
  "page_id": "2811939182",
  "title": "V10.0 Release Report",
  "jira_macro_count": 12,
  "jql_queries": [
    {
      "macro_id": "abc-001",
      "location_path": "table[0]/row[1]/cell[2]",
      "jql": "project = MYPROJ AND fixVersion = ver_V10.0 AND status != Done",
      "columns": "key,summary,status"
    }
  ],
  "unique_strings": ["Done", "MYPROJ", "ver_V10.0", "ver_V10.0_Baseline", "V10.0"]
}

Look at unique_strings - these are the values you can replace. You can see ver_V10.0_Baseline, ver_V10.0, and V10.0 all need to change.

Step 2 - Preview the changes (dry run)

Ask your AI:

"Preview replacing V10.0 with V20.0 on page 2811939182. Include label variants."

The AI calls update_release_version in dry-run mode:

{
  "status": "DRY_RUN",
  "current_title": "V10.0 Release Report",
  "new_title": "V20.0 Release Report",
  "current_version": 14,
  "total_changes": 47,
  "change_summary": "jql: 36 changes\ntext: 8 changes\ntitle: 3 changes",
  "change_log": [...],
  "message": "Preview only. Call again with dry_run=False to apply."
}

Nothing was written yet. You can see exactly what would change.

Step 3 - Apply the changes

If the preview looks correct, ask your AI:

"Apply it."

The AI calls the same tool with dry_run=False:

{
  "status": "UPDATED",
  "page_id": "2811939182",
  "title": "V20.0 Release Report",
  "version": 15,
  "url": "https://your-org.atlassian.net/wiki/spaces/MYSPACE/pages/2811939182",
  "total_changes": 47
}

Your Confluence page is now updated. Open the URL to verify.


Other things you can do

Replace a specific JQL value only

If you only want to change values inside JQL queries and not text or title:

"Replace OLD_VALUE with NEW_VALUE in JQL queries only on page 99887766."

This uses scope="jql" which is precise - it will not touch column header names or other macro parameters even if they contain the same string.

Clone a page to a new release

"Clone page 2811939182 from version V10.0 to V20.0 with delivery date 2026-09-15. Create it as a draft."

The AI calls clone_release_report. The clone gets:

  • All version tokens updated
  • All Jira macro UUIDs regenerated (so the clone has no ID collisions with the original)
  • All date nodes updated to the new delivery date

Create a page from scratch

"Create a new Confluence page in space MYSPACE titled 'Project Plan' using this ADF body: {...}"

Clone a Jira template ticket (one or many)

"Create a clone of the Jira template ticket PROJ-124 and set the summary to ProjectX Variant-A Payment Service."

"Clone PROJ-124 once for each of these services: Payment, Billing, Ledger. Summary format: ProjectX Variant-A Service."

The AI expands the summary pattern into one exact summary per issue and calls clone_jira_issue. The dry run lists every issue it would create, which fields are copied from the template, and any summary that already exists in the project (those are skipped). Say "apply it" to create them. Each clone:

  • Copies every template field the target project accepts (custom fields, labels, components, priority, description), but not assignee, reporter, attachments, links, or parent
  • Gets an "is cloned by" link from the template
  • Is attributed to you in Jira (you are the reporter)

Up to 50 issues per call. If some fail, the status is PARTIAL and each item says what went wrong. Re-running the same request only creates the missing ones.

Create or edit Jira issues

"Create 3 Tasks in project PROJ with summaries A, B, and C and label 'release-2'."

"Change the priority of PROJ-412 to High and add the label 'urgent'."

create_jira_issue works like the clone tool without a template. edit_jira_issue shows a before/after for every field it would change before writing.

Create Jira issues from an input file

Keep the ticket details in a file in your workspace and let the AI read it. The server never reads the file itself (on a shared http deployment it has no access to your machine): the AI client reads the file and passes its contents to create_jira_issue, or to clone_jira_issue if the file names a template.

In VS Code Copilot Agent mode, attach the file with #file: (or drag it into the chat):

"Create Jira tickets from #file:tickets/hadoop-rollout.md. Use the project, issue type, and fields given in the file. Show me the dry run first."

Check the preview, then say "create them". Any format the AI can read works (Markdown, YAML, JSON, CSV). Markdown with one section per ticket is the easiest to write by hand:

Project: PROJ
Issue type: Task

## Ticket: [Hadoop][vari-12] abc1 ingestion retries
Priority: High
Labels: hadoop, release-2
Components: Backend
Service Name: Payment

Description:
The ingestion job for abc1 fails on retries.

Add idempotent writes and an alert when retries exceed 3.

## Ticket: [Hadoop][vari-12] abc2 ingestion retries
Priority: Medium
Labels: hadoop
Service Name: Billing

Description:
Same fix for abc2.

Field names in the file, such as "Service Name", are matched to Jira fields by their display name. To create tickets from a template instead, put Clone template: PROJ-1285 at the top of the file and list one summary per ticket.

Things to know:

  • Up to 50 tickets per call. For a larger file, ask the AI to create them in batches of 50.
  • Re-runs are safe. Tickets whose summary already exists in the project are skipped, so after fixing a failed item you can run the whole file again.
  • Field values are converted by the AI. A value like Priority: High must become Jira's API shape. A wrongly shaped value is not caught by the dry run; that ticket comes back FAILED with Jira's error message, the rest are created (PARTIAL), and you fix the file and re-run.
  • Descriptions are plain text. Blank lines become paragraphs and line breaks are kept, but Markdown bullets, bold, and headings are not converted to Jira formatting.

Read, search, and analyse Jira issues

"Show me PROJ-1285."

"Read PROJ-1285 including comments and give me feedback: is the description clear, are the acceptance criteria testable, and what is missing before a developer can start?"

"Find all issues in PROJ with Hadoop in the summary and show their status and Service Name."

get_jira_issue returns one issue in readable form: description as plain text, custom fields by display name (for example "Acceptance Criteria"), parent, sub-tasks, linked issues, and attachment file names. Ask for comments to get the 20 most recent. search_jira_issues runs a JQL query and returns up to 100 issues with key, summary, status, assignee, priority, and any extra fields you name. Both are read-only. The AI does the analysis itself; it cannot read attachment contents, and its feedback stays in the chat.


How it works

flowchart TD
    subgraph Client["Your AI (Claude / Copilot)"]
        A[Your message]
    end

    subgraph Server["Netra MCP Server"]
        subgraph Tools["MCP Tools"]
            T0[inspect_page_jql]
            T1[update_page_macros]
            T2[update_release_version]
            T3[clone_release_report]
            T4[create_page_from_adf]
        end
        subgraph Engine["ADF Engine"]
            R[Find and replace]
            V[Validate before write]
        end
    end

    subgraph Confluence["Confluence Cloud"]
        API[REST API]
    end

    A --> T0 & T1 & T2 & T3 & T4
    T1 & T2 & T3 --> R --> V
    T0 & V --> API

The server reads pages as ADF (Atlassian Document Format - the native JSON format Confluence uses internally). It finds JQL values at their exact JSON path, replaces them, validates the result, and writes back. No regex is used at any point.


Tool reference

Tool What it does Writes?
inspect_page_jql Lists all Jira macro JQL queries on a page with location paths Never
update_page_macros Replaces values in macro parameters on any page Yes, if dry_run=False
update_release_version Like above, also updates date node timestamps Yes, if dry_run=False
clone_release_report Clones a page, updates all version tokens and macro IDs Yes, if dry_run=False
create_page_from_adf Creates a new page from an ADF body Yes, if dry_run=False
clone_jira_issue Clones a Jira template issue into 1-50 new issues Yes, if dry_run=False
create_jira_issue Creates 1-50 new Jira issues without a template Yes, if dry_run=False
edit_jira_issue Edits fields on one Jira issue Yes, if dry_run=False
get_jira_issue Reads one Jira issue, optionally with its recent comments Never
search_jira_issues Finds Jira issues with a JQL query (up to 100) Never

All write tools default to dry_run=True. You must explicitly say "apply it" or pass dry_run=False to make any change.

Response status values

Status Meaning
INSPECTION Read-only result (inspect_page_jql, get_jira_issue, search_jira_issues)
DRY_RUN Preview only; nothing written; change_log present
UPDATED Page updated; version and url present
CREATED New page created; page_id and url present
NO_CHANGES No occurrences of the search term were found
VALIDATION_FAILED ADF structure check failed; write blocked; errors list present
VERSION_CONFLICT Someone else edited the page at the same time; retry
PARTIAL Bulk Jira call where some issues were created and some failed; results has one entry per item
ERROR API or network error; error field has details

Troubleshooting

"Missing required fields" or 401 error Check your .env file. Make sure CONFLUENCE_USER_EMAIL matches the email on your Atlassian account, and CONFLUENCE_API_TOKEN is a valid token (not your password).

"Page not found" (404) Double-check the page ID from the URL. The ID is the number in .../pages/1234567890/....

"Permission denied" (403) Your account needs at least View permission on the page to inspect it, and Edit permission to update it.

The server is not appearing in my AI client Make sure the cwd path in your client config is the absolute path to the folder containing server.py. On Windows use forward slashes or escape backslashes.

"Version conflict" after applying Someone else saved the page between your inspect and your apply. Just repeat the operation - the server re-reads the latest version before writing.


Docker and cloud deployment

The server ships as a self-contained, stateless Docker image - no Valkey, no external datastore. Full runbook (CF first deploy, blue-green updates, env var reference): docs/docker_cf_deployment.md.

Local dev with docker-compose

# From repo root (never from docker/)
docker-compose up
curl http://localhost:8765/health   # {"status":"ok"}
curl http://localhost:8765/mcp      # MCP endpoint

Build the image locally (single arch, fast)

Requires Docker Desktop 4.x+ with buildx (shipped by default).

VERSION=dev bash scripts/docker-build-local.sh
docker run -p 8765:8765 --env-file .env -e SERVER_TRANSPORT=http -e SERVER_HOST=0.0.0.0 \
    ghcr.io/sunishbharat/netra-confluence-mcp:dev

The -e flags matter: --env-file overrides the image's defaults, and a local .env with SERVER_HOST=127.0.0.1 would make the server unreachable through the published port. Run the build script from Git Bash on Windows.

Build and push multi-platform (amd64 + arm64) and deploy to CF

docker buildx create --name multiarch --driver docker-container --use   # one-time
docker buildx inspect --bootstrap

export REGISTRY=ghcr.io/sunishbharat
./scripts/cf-deploy.sh   # builds both platforms, pushes to GHCR, then cf push

Set the Atlassian site URLs once (they survive cf push/cf restage). The Confluence and Jira tools both use them:

cf set-env netra-confluence-mcp CONFLUENCE_BASE_URL https://your-org.atlassian.net
cf set-env netra-confluence-mcp CONFLUENCE_SITE_URL https://your-org.atlassian.net
cf restage netra-confluence-mcp

Do not set CONFLUENCE_API_TOKEN, CONFLUENCE_USER_EMAIL, JIRA_USER_EMAIL, or JIRA_API_TOKEN on CF. On http they are ignored: each user's MCP client sends their own credentials as headers (see VS Code with GitHub Copilot - shared server on CF).

CI builds and pushes a multi-arch image to GHCR automatically on every v*.*.* tag push (.github/workflows/docker.yml).


Running tests

Unit tests (no Confluence connection required):

uv run python -m pytest

Integration tests against a real Confluence page:

CONFLUENCE_TEST_PAGE_ID=<page-id> uv run python -m pytest -m integration

Testing the Jira tools

The Jira tools (clone_jira_issue, create_jira_issue, edit_jira_issue, get_jira_issue, search_jira_issues) can be tested in three layers: unit tests with no Jira connection, direct tool calls through the MCP Inspector, and end-to-end prompts from your AI client. Do the layers in that order.

Use a sandbox Jira project. Every call with dry_run=False creates or edits real issues. Never point the apply steps below at a production project.

1. Unit tests (no Jira connection)

All Jira HTTP calls are mocked, so these run anywhere:

uv run python -m pytest tests/test_jira_*.py
File What it covers
tests/test_jira_client.py Auth header, HTTP status mapping, 429 retry honoring Retry-After, no retry of a POST/PUT whose response was lost
tests/test_jira_api.py Each REST call, bulk-create partial-failure parsing, createmeta pagination, exact-match duplicate search
tests/test_jira_fields.py Field copying and normalization, override key resolution, required-field check, JQL escaping, plain text to ADF, input validation
tests/test_jira_shared.py Credential resolution on stdio and http, CREATED / PARTIAL / ERROR / NO_CHANGES mapping, skip-existing, link failures
tests/test_jira_tools.py The three write tools: dry run never writes, the payload sent on apply, validation blocks writes
tests/test_jira_read_tools.py Readable formatting, get_jira_issue with and without comments, search_jira_issues columns, extra fields, truncation, errors

With coverage:

uv run python -m pytest tests/test_jira_*.py --cov=netra_jira --cov-report=term-missing

2. Prepare a sandbox project

You need:

  • A Jira project you can freely create issues in (for example SANDBOX)
  • A template issue in it that looks like a real template: a few labels, a component, a priority, a description, and ideally one required custom field
  • Jira permissions on that project: Browse projects, Create issues, Edit issues, and Link issues

Credentials: on stdio the Jira tools use JIRA_USER_EMAIL / JIRA_API_TOKEN from .env if both are set, otherwise your CONFLUENCE_USER_EMAIL / CONFLUENCE_API_TOKEN. One Atlassian API token works for both products, so usually nothing extra is needed.

3. Direct tool calls with the MCP Inspector

The Inspector calls a tool with exact arguments, with no AI in between, so you can test each case deterministically.

npx @modelcontextprotocol/inspector uv run python server.py

Open the URL it prints, go to Tools, pick a tool, fill in the arguments, and run it. Work through these cases in order, replacing SANDBOX-1 with your template key:

# Tool and arguments Expected result
1 clone_jira_issue, template_issue_key="SANDBOX-1", items=[{"summary": "Test ProjectX Variant-A Payment Service"}] DRY_RUN, to_create: 1, copied_fields lists the template's labels, components, priority, description, and custom fields
2 Same as 1 with dry_run=false CREATED, one issue_key and url
3 Same as 2 again NO_CHANGES, the item shows SKIPPED_EXISTS with the key from step 2. No duplicate is created
4 items with three summaries (...Payment..., ...Billing..., ...Ledger...), dry_run=false CREATED for the two new ones, SKIPPED_EXISTS for Payment
5 One item with "field_overrides": {"labels": ["override-test"]}, dry run DRY_RUN, that item lists "overrides": ["labels"]
6 One item with "field_overrides": {"No Such Field": 1} VALIDATION_FAILED naming the unknown field. Nothing is created
7 Two items, one with an invalid value for a select field (for example {"priority": {"name": "NotAPriority"}}), dry_run=false PARTIAL: one item CREATED, the other FAILED with Jira's error message
8 target_project_key set to a second sandbox project, dry run DRY_RUN, components and versions appear in dropped_fields
9 create_jira_issue, project_key="SANDBOX", issue_type="Task", items=[{"summary": "Test create", "description": "line one\n\nline two"}], dry_run=false CREATED; the description shows two paragraphs in Jira
10 edit_jira_issue, issue_key from step 9, summary="Test create (edited)", field_overrides={"labels": ["edited"]} DRY_RUN with a before / after entry for each field
11 Same as 10 with dry_run=false UPDATED, fields_updated: ["labels", "summary"]
12 Same as 11 again NO_CHANGES
13 clone_jira_issue with template_issue_key="SANDBOX-99999" ERROR with a not-found message
14 get_jira_issue, issue_key="SANDBOX-1", include_comments=true INSPECTION; custom fields appear under their display names, and comments lists the newest first
15 search_jira_issues, jql="project = SANDBOX AND summary ~ \"Test\"", fields=["labels"] INSPECTION; one row per issue created above, each with a Labels column
16 search_jira_issues, jql="nope = 1" ERROR with Jira's JQL error message

4. Check the results in Jira

Open the issues created in steps 2, 4 and 7 and confirm:

  • Fields: labels, components, priority, description, and custom fields match the template, and the summary is the one you passed
  • Not copied: assignee is empty (or the project default), and reporter is you, not the template's reporter
  • Clone link: the new issue shows "clones SANDBOX-1" and the template shows "is cloned by" for each clone. If the direction is reversed, swap inwardIssue and outwardIssue in netra_jira/api.py:create_clone_link
  • Partial failure (step 7): the issue Jira reported as created is the one whose summary matches the CREATED item, not the failed one

Also try one summary with special characters (for example Test - Variant[A]: Payment+), create it, then run the same call again. The second run must show SKIPPED_EXISTS. If it creates a duplicate, the duplicate search does not handle that character.

5. End-to-end from your AI client

With the server connected to Claude Desktop or Copilot (see Connect your AI client), try the real prompts:

"Clone the template ticket SANDBOX-1 and set the summary to ProjectX Variant-A Payment Service."

"Clone SANDBOX-1 once for each of these services: Payment, Billing, Ledger. Summary format: ProjectX Variant-B Service."

Check that the AI:

  1. Expands the summary pattern into exact summaries and shows you the dry-run preview before creating anything
  2. Only applies after you say "apply it"
  3. Reports each created issue key, and any SKIPPED_EXISTS or FAILED items, rather than a single success message

6. Testing over http (per-user headers)

Start the server in http mode, then connect the Inspector with Transport "Streamable HTTP" and URL http://127.0.0.1:8765/mcp:

SERVER_TRANSPORT=http uv run python server.py

In the Inspector's connection settings, add the headers X-Confluence-User-Email / X-Confluence-Api-Token (or X-Jira-User-Email / X-Jira-Api-Token) and repeat case 1. Then confirm:

  • With no headers, every Jira tool returns {"status": "ERROR", "error": "missing per-user Jira credentials"}, even if credentials are set in the server's .env
  • With only X-Jira-User-Email and no X-Jira-Api-Token, the call fails rather than mixing the Jira email with the Confluence token

7. Clean up

The tools never delete issues. Remove the test issues in Jira yourself, for example by searching project = SANDBOX AND summary ~ "Test" and using Bulk change -> Delete issues.

Jira troubleshooting

Symptom Likely cause
VALIDATION_FAILED with missing required [...] The target project requires a field the template does not have. Add it with field_overrides
VALIDATION_FAILED with issue type '...' not available The target project does not have the template's issue type
ERROR with Permission denied (HTTP 403) Your account lacks Create, Edit, or Browse permission on the project
Issue created but the item has link_error Your account lacks Link issues permission, or the Cloners link type is disabled on the site
ERROR with Rate limited by Jira (HTTP 429) Atlassian's rate limit was still in force after 3 attempts. Wait a minute and re-run; existing issues are skipped
ERROR with Network error on apply The response was lost and the request was not retried, because issues may already exist. Re-run the dry run to see which were created

Development commands

uv sync                                                    # install all deps
uv run ruff check .                                        # lint
uv run ruff format --check .                               # format check
uv run mypy --strict .                                     # type check
uv run python -m pytest --cov=confluence --cov=netra_jira --cov-report=term-missing

README fetched from the repository 5 days ago. The original is authoritative.

Questions

About Netra Confluence MCP

How do I install Netra Confluence MCP?

No package has been published for this server, so install it from source: clone the repository and follow its README.

Is Netra Confluence MCP safe to use with an AI agent?

Its trust score is 34 out of 100 (low). It passes 5 of the 5 static security checks run so far. It has no ConduID identity yet, so agent calls to it are not receipted.

Is Netra Confluence MCP still maintained?

Yes — the last commit was 14 days ago. The repository has 1 stars and 0 forks.