jc-mcp
Token-efficient Jira and Confluence MCP server for reading, searching, and modifying issues and pages with lean responses.
Read Jira issues individually or batch up to 50, including descriptions, comments, attachments, parent/reporter, changelog, and links.
Search Jira with JQL; inspect workflow transitions; get the current user and look up users by email.
Create/edit issues, transition status, add comments, add issues to active sprints, convert issue types, and manage issue links.
Create, list, and release Jira versions.
Resolve temporary Jira attachment download URLs without returning file bytes.
Read, search, create, and update Confluence pages; resolve space keys to IDs.
Optionally require user confirmation before write operations via
JCM_CONFIRM_WRITES=true.Run over stdio or shared HTTP transport for multiple MCP clients.
Provides tools for interacting with Confluence, enabling reading and creating pages, updating pages, and searching content.
Provides tools for interacting with Jira, enabling management of issues (create, read, update, transition, comment, search) and release versions.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@jc-mcp find issues with high priority assigned to me"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
jc-mcp
Token-efficient Jira & Confluence MCP server. Returns lean, flat responses that minimize token consumption while preserving the information LLM agents need.
Quick Start
Get an API token from id.atlassian.com
Add the server to your MCP client:
claude mcp add --scope user jc-mcp \
-e ATLASSIAN_URL=https://yoursite.atlassian.net \
-e ATLASSIAN_EMAIL=you@example.com \
-e ATLASSIAN_TOKEN=your-api-token \
-- npx -y @maphnet/jc-mcpThe --scope user flag makes the server available across all your projects. Omit it to add only for the current project. CloudId is auto-discovered from your site URL.
Related MCP server: Jira Cloud MCP Server
Updating
npx caches packages per package spec — a cache entry created for @maphnet/jc-mcp (no version) is not refreshed by running npx @maphnet/jc-mcp@latest; that creates a separate entry. To actually update a server configured as npx -y @maphnet/jc-mcp, clear the stale entry:
# find and remove the cached copy used by the un-versioned spec
for d in ~/.npm/_npx/*/node_modules/@maphnet/jc-mcp; do
echo "$d: $(node -p "require('$d/package.json').version")"
done
rm -rf ~/.npm/_npx/<hash-of-old-version>Alternatively, configure the server as npx -y @maphnet/jc-mcp@latest so every start resolves the newest release.
If you installed globally (npm i -g @maphnet/jc-mcp, command jc-mcp), update with:
npm i -g @maphnet/jc-mcp@latestThen restart your MCP client (Claude Code, Cursor, etc.) to pick up the new version.
Tools
Tool | Description | Returns |
| Read a Jira issue | Flat fields (incl. |
| Batch-read up to 50 issues in one call (one JQL search, plus one GET per key the search omits). |
|
| Field-change history, oldest first, optionally after |
|
| Resolve a temporary attachment download URL on demand |
|
| Available workflow transitions |
|
| Update issue fields and/or apply |
|
| Change workflow state |
|
| Add an issue to its project's active scrum sprint (no-op if already in one or no scrum board; returns candidates if ambiguous; pass |
|
| Create a new issue |
|
| Add a comment (plain text) |
|
| JQL search |
|
| Get the authenticated user |
|
| Find a user by email |
|
| Create a release version |
|
| List project versions |
|
| Mark a version as released |
|
| Link two issues: issueKey → targetIssueKey (A blocks B) |
|
| Delete an issue link by ID |
|
| List links on an issue |
|
| Change type of one or more issues, keeping keys (e.g. Tasks → Sub-tasks under a Story); uses Jira bulk move |
|
| Read a Confluence page | Title + markdown body + |
| Resolve a Confluence space key to its numeric ID |
|
| Create a Confluence page |
|
| Update a Confluence page |
|
| CQL search |
|
To create a page next to an existing one, pass its spaceId (and parentId) from jcm_getArticle to jcm_createArticle. Otherwise resolve a space key (e.g. spaceKey from jcm_searchArticles) via jcm_lookupSpace. Space names are not unique — don't use them to pick a space.
Jira attachments
Call jcm_getIssue to discover attachments: [{id, filename, mimeType, size, content}] (an empty array when none exist). content is Jira's authenticated content URL; issue reads do not resolve temporary links.
Pass the selected id to jcm_getAttachmentDownloadLink as attachmentId. The server uses its existing Jira credentials and returns a temporary URL plus the filename. Its Jira account needs permission to view the issue and attachment, including applicable issue security restrictions. Download the URL with a local HTTP client without a Jira Authorization header; file bytes stay outside the model context. For example, in a shell, after assigning the returned values to download_url and a local output_path:
curl --fail --location --proto '=https' --proto-redir '=https' --output "$output_path" "$download_url"Treat the returned URL as a secret: anyone holding it may be able to download the file. Avoid public logs and tickets. URLs expire; call the tool again to obtain a fresh one. No fixed lifetime or permanent public access is promised.
The resolver supports HTTPS redirects to api.media.atlassian.com/file/{id}/binary. Missing or unsupported redirects, direct file responses, and authentication/permission/not-found failures produce errors. It does not host or proxy attachments. A live private Jira Cloud check on 2026-10-16 observed a 303 redirect; downloading its URL without Jira credentials matched the authenticated file byte for byte. One token carried an expiry about ten minutes after the request; that observation is not a lifetime guarantee.
Watching a ticket
Poll with jcm_searchIssues using key = PROJ-1 AND updated > "<updatedJql>", where updatedJql comes from the previous jcm_getIssue or jcm_searchIssues result. It is the issue's updated time floored to the minute in the Jira user's profile timezone, which is the timezone JQL uses. It is null if the timezone lookup failed (retry later). Because of the minute floor, the query re-matches the issue itself and anything changed within that minute. Matching is at-least-once, so compare raw updated values or dedupe by id. The profile timezone is re-read hourly. The no-miss guarantee does not hold during the repeated hour when daylight saving ends: two instants format to the same local minute and JQL may resolve it to the later one. During that hour, also compare raw updated values, or use a lookback of one hour.
On a match, read new comments with jcm_getIssue. Filter them by comment id, created, or updated, and skip your own using authorAccountId from jcm_getCurrentUser. Read field changes with jcm_getIssueChangelog(issueKey, since). Entries are returned oldest first, and since is strict (created > since). When truncated is true, call again with since set to the last returned created.
Why This Server?
Standard Atlassian MCP servers return raw API responses — deeply nested JSON with metadata LLM agents don't need. jc-mcp flattens and trims every response, cutting ~92% of tokens across typical operations:
Operation | jc-mcp | Standard MCP | Reduction |
Get issue | ~460 tokens | ~1,144 tokens | 60% |
Search (5 issues) | ~105 tokens | ~6,204 tokens | 98% |
Get transitions | ~17 tokens | ~313 tokens | 95% |
Flat fields:
status: "In Progress"instead of nestedstatus.name+statusCategory+selflinks +iconUrlLean search results:
{key, summary, status, type, parent, assignee, updated, updatedJql}per issue (parentis{key, summary, type}ornull) — no descriptions, avatars, or project metadataMinimal writes:
{ok: true, key: "PROJ-1"}— no re-fetch
Less tokens = faster responses, lower cost, more room in the context window.
Write Confirmation (Elicitation Gate)
Set JCM_CONFIRM_WRITES=true to require user confirmation before every write operation. When enabled, the following tools present a confirmation form (via MCP elicitation) before executing:
jcm_createIssue— create a Jira issuejcm_editIssue— update issue fields or apply incrementalupdateopsjcm_transitionIssue— change workflow statejcm_addToActiveSprint— add an issue to the active sprintjcm_addComment— add a commentjcm_createArticle— create a Confluence pagejcm_updateArticle— update a Confluence page
The confirmation shows a title and summary of the pending write. The user can:
Accept — the write proceeds normally
Decline or Cancel — the write is skipped and the tool returns
{ok: false, message: "...declined by user."}Timeout (client-dependent) — treated as decline
Without the flag (or with any value other than "true"), all writes proceed immediately — existing behaviour is unchanged.
MCP client requirements
The client must support the elicitation/create capability (MCP spec 2025-06-18+). In Hermes, confirmations appear as approve/deny buttons in Slack. In Claude Code, they appear as inline prompts.
claude mcp add --scope user jc-mcp \
-e JCM_CONFIRM_WRITES=true \
-e ATLASSIAN_URL=https://yoursite.atlassian.net \
-e ATLASSIAN_EMAIL=you@example.com \
-e ATLASSIAN_TOKEN=your-api-token \
-- npx -y @maphnet/jc-mcpConfiguration
Variable | Required | Description |
| Yes | Site URL, e.g. |
| Yes | Atlassian account email |
| Yes | API token |
| No | Auto-discovered if omitted |
| No | Set to |
HTTP transport (shared multi-client server)
By default jc-mcp speaks MCP over stdio, one process per client session. Set JCM_TRANSPORT=http to run one long-lived process per credential set that many clients (Claude Code, Codex, …) share over MCP Streamable HTTP. Every client session gets its own MCP session (isolated responses and write-confirmation prompts). All sessions share one Atlassian client.
Variable | Default | Description |
|
|
|
|
| Bind address. Keep it on loopback (see Security) |
|
| Listen port. Use a distinct port per credential set |
|
| Endpoint path |
|
| Sessions with no open stream and no activity for this long are closed. Clients that disconnect cleanly ( |
Security: the HTTP endpoint has no authentication. Any local process that can reach the port acts with the configured Atlassian credentials. It is meant for trusted, single-user hosts on loopback only. Requests whose Host header is not 127.0.0.1:<port>, localhost:<port> or [::1]:<port> (or exactly <host>:<port> for a non-loopback bind) get 403 (DNS-rebinding protection); the Origin header, when present, is checked the same way. POST requests must send Content-Type: application/json (415 otherwise), and the request body is capped at 4 MiB (413, checked via Content-Length and enforced during the read). Wildcard binds such as 0.0.0.0 or :: are rejected at startup, including spellings Node normalizes to a wildcard (e.g. 0, 0.0); binding to a specific non-loopback IP is still allowed, but it remains unauthenticated — use at your own risk. Under WSL with mirrored networking, Windows-side processes and rootless Docker containers (via the slirp gateway) can also reach loopback, which falls inside this "local = trusted" model.
systemd user services (one per credential set)
Use one template unit, ~/.config/systemd/user/jc-mcp@.service:
[Unit]
Description=jc-mcp HTTP server (%i)
After=network-online.target
[Service]
Type=simple
EnvironmentFile=%h/.config/mcp/secrets/%i-jc-mcp.env
EnvironmentFile=%h/.config/jc-mcp/%i.env
Environment=JCM_TRANSPORT=http
# systemd does not load nvm: use the absolute output of `command -v node`
ExecStart=/home/USER/.nvm/versions/node/v24.0.0/bin/node /home/USER/dev/jc-mcp/dist/index.js
Restart=on-failure
RestartSec=2
[Install]
WantedBy=default.targetPer-instance port files, ~/.config/jc-mcp/<set>.env:
# ~/.config/jc-mcp/maphnet.env
JCM_HTTP_PORT=3101
# ~/.config/jc-mcp/publius.env
JCM_HTTP_PORT=3102
# ~/.config/jc-mcp/lektra.env
JCM_HTTP_PORT=3103EnvironmentFile accepts plain KEY=VALUE lines only. If a secrets file uses export KEY=VALUE, remove the export prefixes.
systemctl --user daemon-reload
systemctl --user enable --now jc-mcp@maphnet jc-mcp@publius jc-mcp@lektra
journalctl --user -u jc-mcp@maphnet -f # expect: jc-mcp server running via http at http://127.0.0.1:3101/mcpAfter upgrading jc-mcp (npm run build), run systemctl --user restart 'jc-mcp@*'.
Client configuration
Claude Code (project .mcp.json):
{
"mcpServers": {
"maphnet-jc-mcp": { "type": "http", "url": "http://127.0.0.1:3101/mcp" }
}
}Or from the CLI: claude mcp add --transport http maphnet-jc-mcp http://127.0.0.1:3101/mcp.
Codex (~/.codex/config.toml or project .codex/config.toml):
[mcp_servers.maphnet-jc-mcp]
url = "http://127.0.0.1:3101/mcp"Older Codex releases also need experimental_use_rmcp_client = true at the top level of config.toml.
Migrating from stdio group entries
Start the systemd instances above and confirm each one logs its URL.
In each of
~/.config/mcp/groups/maphnet.json,publius.jsonandlektra.json, replace the jc-mcp stdio entry (command/args/env sourcing) with an HTTP entry:{ "type": "http", "url": "http://127.0.0.1:<port>/mcp" }(ports 3101/3102/3103).Re-apply the groups in each project:
mcp-group rm <group> <dir>thenmcp-group add <group> <dir>.Update Codex
config.tomlentries as shown above, and re-enable the jc-mcp entries that were disabled for memory reasons.Restart running Claude Code and Codex sessions. MCP server config is read at session start, so existing sessions keep their old stdio processes until restarted.
Verify in a fresh session: the client lists the jc-mcp tools, and a read-only call such as
jcm_getIssueon a known key succeeds.
Memory: stdio vs HTTP
Date measured: 2026-10-12, Node v24.19.0. Procedure: start the server with fake credentials, connect N SDK clients and run tools/list on each, then read ps -o rss= -p <pid>.
Mode | Processes | Sessions | Measured RSS |
stdio ( | 1 per session | 1 | 78.2 MB |
stdio, 6 sessions | 6 | 6 | 469.2 MB (6 × above) |
HTTP, idle | 1 | 0 | 90.7 MB |
HTTP, 6 sessions | 1 | 6 | 92.9 MB |
The previous npx-launched stdio setup used 1.3-1.7 GB combined RSS across sessions, including the npm wrapper processes.
License
MIT
Available Tools
25 toolsjcm_addCommentAdd Comment to Jira IssueA
Add a comment to a Jira issue. Body accepts markdown (headings, tables, code fences, lists). Returns {ok, commentId}.
Inputs: body (required), issueKey (required).
jcm_addIssueLinkAdd Jira Issue LinkA
Create a link between two Jira issues. issueKey is the source and gets the outward verb (e.g. "blocks", "duplicates", "clones"); targetIssueKey gets the inward verb. Returns {ok, issueKey, targetKey, linkType}.
Inputs: issueKey (required), linkType (required), targetIssueKey (required).
jcm_addToActiveSprintAdd Issue to Active SprintAIdempotent
Add a Jira issue to its project's active scrum sprint. No-op if the issue is already in an active sprint or the project has no scrum board. If the board or sprint is ambiguous, returns {ok:false, reason, candidates:[{id,name}]}: for reason multiple-scrum-boards retry with boardId; no-active-sprint and multiple-active-sprints need manual handling in Jira. Returns {ok, key, sprintId, sprintName, boardId} or {ok, skipped}.
Inputs: issueKey (required), boardId.
jcm_convertIssueTypeConvert Jira Issue TypeADestructiveIdempotent
Convert one or more Jira issues (same project) to another issue type, keeping their keys. To make tasks sub-tasks, provide parentKey. To make sub-tasks standalone, omit parentKey. Uses Jira's bulk move and waits up to 30s. Returns {ok, keys, issueType, parentKey}, or {ok:false, taskId, status, failed?} if the move failed or is still running.
Inputs: issueKeys (required), issueTypeName (required), parentKey.
jcm_createArticleCreate Confluence ArticleB
Create a new Confluence page. Returns {id, title, url}.
Inputs: body (required), title (required), spaceId (required), parentId.
jcm_createIssueCreate Jira IssueA
Create a new Jira issue. Returns {key, url}. For sub-tasks, pass parentKey.
Inputs: summary (required), issueType (required), projectKey (required), parentKey, +1 more.
jcm_createVersionCreate Jira VersionB
Create a new version (release) in a Jira project. Returns {id, name}.
Inputs: name (required), projectKey (required), description, releaseDate.
jcm_editIssueEdit Jira IssueADestructiveIdempotent
Update a Jira issue. Pass 'fields' as {fieldName: value} to set values (description accepts markdown), and/or 'update' for incremental ops like adding a fix version: update={fixVersions: [{add: {name: '1.2.0'}}]}. 'update' values are passed verbatim, not markdown-converted. At least one is required. Returns {ok, key}.
Inputs: issueKey (required), fields, update.
jcm_getArticleGet Confluence ArticleARead-onlyIdempotent
Fetch a Confluence page by ID. Returns {id, title, body (markdown), status, version, spaceId, parentId?}.
Inputs: pageId (required).
jcm_getAttachmentDownloadLinkGet Jira Attachment Download LinkARead-onlyIdempotent
Resolve an attachment ID from jcm_getIssue to {attachmentId, filename, url, temporary: true}. Requires the server's Jira account to have permission to view the attachment. Download the sensitive temporary URL without Jira credentials using a local download tool; request a fresh URL after expiry. File bytes are not returned. Supports Jira Cloud's Atlassian media redirects.
Inputs: attachmentId (required).
jcm_getCurrentUserGet Current UserARead-onlyIdempotent
Returns the authenticated Atlassian user's account info: accountId, displayName, email, active.
Inputs: no named parameters.
jcm_getIssueGet Jira IssueARead-onlyIdempotent
Fetch a Jira issue by key. Returns lean fields: key, summary, description (markdown), status, type, parent ({key, summary, type} or null), priority, assignee, reporter ({displayName, accountId} or null), labels, updated (ISO), updatedJql (updated floored to the minute in your Jira timezone, for JQL updated > "..." polling; at-least-once, dedupe; null if unavailable), comments (id, author, authorAccountId, body, created, updated), attachments (id, filename, mimeType, size, content URL). Attachment content URLs require Jira authentication.
Inputs: issueKey (required).
jcm_getIssueChangelogGet Jira Issue ChangelogARead-onlyIdempotent
Get a Jira issue's field-change history, oldest first. Returns {entries: [{id, authorAccountId, author, created, items: [{field, from, to}]}], truncated}. since keeps entries created strictly after it. Capped at maxResults oldest matches (entries sharing the last timestamp stay together); if truncated, call again with since = last returned created.
Inputs: issueKey (required), since, maxResults.
jcm_getIssueLinksGet Jira Issue LinksARead-onlyIdempotent
Get all links for a Jira issue. Returns flat array of [{id, type, inwardIssue?, outwardIssue?}].
Inputs: issueKey (required).
jcm_getIssuesGet Jira Issues (batch)ARead-onlyIdempotent
Batch-read up to 50 Jira issues in one call (one JQL search, plus one GET per key the search omits). Returns an array in request order, one entry per unique key: {key, summary, type, status, parent ({key, summary, type} or null), description (markdown or null), comments: [{author, body, created}], commentsTotal?}. fields selects description and/or comments (default both). Descriptions are capped at maxDescriptionChars (default 600), comment bodies at maxCommentChars (default 300); cut text ends with "… [truncated, N chars total]". Only the 3 most recent comments are returned (oldest first); commentsTotal appears when more exist. A key that is missing or not visible to you returns {key, error} instead of being dropped. A moved/renamed key returns the issue under the requested key plus movedTo (current key). Use jcm_getIssue for full detail on one issue.
Inputs: issueKeys (required), fields, maxCommentChars, maxDescriptionChars.
jcm_getTransitionsGet Issue TransitionsARead-onlyIdempotent
Get available workflow transitions for a Jira issue. Returns [{id, name}].
Inputs: issueKey (required).
jcm_listVersionsList Jira VersionsARead-onlyIdempotent
List all versions in a Jira project. Returns [{id, name, released, releaseDate}].
Inputs: projectKey (required).
jcm_lookupSpaceLookup Confluence SpaceARead-onlyIdempotent
Resolve a Confluence space key to its numeric ID for use with jcm_createArticle. Returns {id, key, name}.
Inputs: spaceKey (required).
jcm_lookupUserLookup UserARead-onlyIdempotent
Look up a Jira user by email address. Returns [{accountId, displayName, email, active}].
Inputs: email (required).
jcm_releaseVersionRelease Jira VersionADestructiveIdempotent
Mark a Jira version as released. Returns {ok, id, name}.
Inputs: versionId (required), releaseDate.
jcm_removeIssueLinkRemove Jira Issue LinkADestructiveIdempotent
Delete an issue link by its ID. Use jcm_getIssueLinks to find link IDs. Returns {ok, linkId}.
Inputs: linkId (required).
jcm_searchArticlesSearch Confluence ArticlesARead-onlyIdempotent
Search Confluence pages using CQL. Returns [{id, title, space, spaceKey, lastModified}].
Inputs: cql (required), maxResults.
jcm_searchIssuesSearch Jira IssuesARead-onlyIdempotent
Search Jira issues using JQL. Returns {total, issues: [{key, summary, status, type, parent, assignee, updated, updatedJql}]}. parent is {key, summary, type} or null. updatedJql is the issue's updated time floored to the minute in your Jira timezone; poll with key = X AND updated > "<updatedJql>" (at-least-once, dedupe). Zero results are validated: invalid JQL values (unknown issue type, missing parent, etc.) return an error instead of an empty list.
Inputs: jql (required), maxResults.
jcm_transitionIssueTransition Jira IssueADestructive
Move a Jira issue to a new workflow state. Use jcm_getTransitions first to get valid transition IDs. Returns {ok, key, transitionId}.
Inputs: issueKey (required), transitionId (required).
jcm_updateArticleUpdate Confluence ArticleADestructiveIdempotent
Update a Confluence page. Pass current version number (auto-incremented). Returns {ok, id, version}.
Inputs: body (required), title (required), pageId (required), version (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 25 tools
Most tools target a distinct resource and action, and descriptions clarify overlapping areas such as jcm_getIssues, jcm_getIssue, and jcm_searchIssues. Some update-style tools (jcm_editIssue, jcm_transitionIssue, jcm_convertIssueType, jcm_addToActiveSprint) require careful selection, but the set is generally clear.
All tools consistently use the jcm_ prefix followed by lowerCamelCase verb-first names. The verbs vary by operation (get, search, create, update, add, remove), but the overall convention is predictable throughout.
25 tools is on the heavy side, but it covers two broad domains, Jira and Confluence, with several substantial sub-areas. Each tool appears purposeful, though the surface is larger than a typical focused MCP server.
The server covers core Jira issue, comment, link, version, sprint, transition, user, and Confluence page workflows quite thoroughly. Some gaps remain, such as deleting issues/pages, editing or deleting comments, and broader board/sprint management, but core workflows are largely supported.
Maintenance
Related MCP Connectors
Official Fibery MCP server: query and update your workspace from any AI client via OAuth.
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
Free, no-key MCP server: token estimates, JSON Schema conversion, MCP configs, tool search.
MCP server for secureFlows: token-free URL builders and integration-linting tools for AI agents.
Related MCP Servers
- AlicenseBqualityDmaintenance🚀 Token-efficient MCP server for Confluence. Reduces LLM costs by 76% via Markdown conversion. Supports listing, searching, and fetching pages.33MIT
- AlicenseNot gradedqualityBmaintenanceMCP server for interacting with Jira Cloud instances. Enables issue management, JQL queries, project and sprint management, and batch operations via natural language interfaces.71 npm4MIT
- AlicenseBqualityCmaintenanceLightweight MCP server for Jira, Confluence, and Bitbucket — read, create, and update from your AI IDE.2016 PyPIMIT
- AlicenseAqualityDmaintenanceMinimal MCP server for Jira with configurable tools to reduce token usage, starting from ~150 tokens for basic issue retrieval.1MIT