MCP server
@simplysf/simply-atlassian-mcp is a Model Context Protocol
server with one tool per CLI command. It calls the same library the CLI is built on, in-process,
so the agent gets the same credential handling, dryRun previews, read-only guard, and error
messages described elsewhere in these guides, and the same raw JSON the CLI prints with --json.
Using it takes four steps: install the server, tell your MCP client how to launch it, check the connection, then ask the agent for what you want in plain language. This page walks through each, then covers letting the agent write, the full tool list, results and errors, and what to check when something does not work.
Install
Section titled “Install”The server needs Node.js 22 or later.
npm install -g @simplysf/simply-atlassian-mcpsimply-atlassian-mcp --help--help prints the options and every tool the server can register, so it doubles as a check that
the install worked. It prints to stderr, because stdout is reserved for the protocol stream.
You can skip the global install and have the client fetch the package on demand with
npx -y @simplysf/simply-atlassian-mcp as the command; the client configurations below show both
forms.
Connection settings
Section titled “Connection settings”The server reads the same variables as the CLI, described in Credentials:
JIRA_URL=https://your-site.atlassian.netJIRA_USERNAME=you@example.comJIRA_API_TOKEN=...
CONFLUENCE_URL=https://your-site.atlassian.netCONFLUENCE_USERNAME=you@example.comCONFLUENCE_API_TOKEN=...Put them in a .env file and pass it with --env-file, or set them in the env block of the
client’s server entry. A variable already in the environment wins over the file, as it does for
the CLI. Settings are resolved when a tool is called rather than at startup, so a server given only
the JIRA_* variables still serves every Jira tool; only the Confluence tools report a missing
setting.
Use an absolute path for the env file. MCP clients launch the server from a working directory of
their own, so ~ and relative paths may not resolve. On Windows, write the path with forward
slashes or doubled backslashes inside JSON: "C:/Users/me/atlassian.env".
Configure a client
Section titled “Configure a client”With simply atlassian mcp install
Section titled “With simply atlassian mcp install”One command writes the entry for you. Name each client with --client:
simply atlassian mcp install --client claude-code --client cursor --env-file ~/atlassian.envClient ids: claude-code, claude-desktop, cursor, vscode, windsurf, gemini, codex.
Running it without --client lists them, and marks the ones found on this machine.
- Only the entry changes. In a JSON file, everything else, comments included, is left as it
was, and the previous file is kept as
<file>.bak. Claude Code and Codex are configured through their ownclaude mcpandcodex mcpcommands. If one isn’t onPATH, you get the exact command to run yourself. - Credentials are never written.
--env-fileis resolved to an absolute path, checked to exist, and passed to the server. Without it, the entry starts with no settings, because a GUI client doesn’t see your shell’s environment. - Server flags are passed through.
--allow-writesand--xrayreach the server.--allow-writesalso names the entrysimply-atlassian-write, so it can sit beside the read-only one (see Let the agent write).--nameoverrides the name. --scope projectwrites the repository’s own file in the current directory instead:.mcp.jsonfor Claude Code,.cursor/mcp.json,.vscode/mcp.jsonor.gemini/settings.json. The entry then runssimplyfromPATH, so it works for everyone who clones the repository.- At user scope the entry starts this Node binary and the
simplyCLI’s own script. That works for GUI clients that don’t see yourPATH. If a Node upgrade moves the binary, run the command again with--force.--launcher simplyor--launcher npxchoose the other forms. - Running it twice is safe. An identical entry is left alone. An entry that differs is reported
as a conflict (exit 2) and is replaced only with
--force.--dry-runshows what would be written, and--jsonreturns the per-client results. - Your own settings in an entry are kept. The command owns only how the server is launched
(
command,args,type). Anenvblock you added, and settings such as Gemini’strustortimeout, survive a--forcereplace, and an entry that differs only in them counts as identical. Previews showenvnames with their values as<kept>.
Restart Claude Desktop or Windsurf afterwards. In Claude Code or Gemini CLI, run /mcp. The
sections below show what the command writes, for editing by hand or for any other client.
Claude Desktop
Section titled “Claude Desktop”Edit claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/; Windows:
%APPDATA%\Claude\) and restart Claude Desktop:
{ "mcpServers": { "simply-atlassian": { "command": "simply", "args": ["atlassian", "mcp", "--env-file", "/home/me/atlassian.env"] } }}The same entry with the settings inline and no global install:
{ "mcpServers": { "simply-atlassian": { "command": "npx", "args": ["atlassian", "mcp"], "env": { "JIRA_URL": "https://your-site.atlassian.net", "JIRA_USERNAME": "you@example.com", "JIRA_API_TOKEN": "..." } } }}Claude Code
Section titled “Claude Code”claude mcp add simply-atlassian -- simply-atlassian-mcp --env-file ~/atlassian.envAdd --scope project to write the entry to a .mcp.json at the repository root instead, which
can be committed so a team shares the server definition. Keep tokens out of a committed file by
pointing at an env file each person holds locally. Inside a session, /mcp lists the configured
servers and whether each one connected.
Cursor
Section titled “Cursor”Cursor reads .cursor/mcp.json in the project, or ~/.cursor/mcp.json for every project, with
the same mcpServers shape as Claude Desktop.
Gemini CLI
Section titled “Gemini CLI”Gemini CLI reads ~/.gemini/settings.json for every project, or .gemini/settings.json inside a
project, with the same mcpServers shape as Claude Desktop:
{ "mcpServers": { "simply-atlassian": { "command": "simply", "args": ["atlassian", "mcp", "--env-file", "/home/me/atlassian.env"] } }}gemini mcp add writes the same entry. --scope user puts it in the global file; the default
project scope writes .gemini/settings.json, and -e sets one variable on the entry:
gemini mcp add --scope user \ -e JIRA_URL=https://your-site.atlassian.net \ -e JIRA_USERNAME=you@example.com \ -e JIRA_API_TOKEN=... \ simply-atlassian simply-atlassian-mcpFlags meant for the server itself, --env-file and --allow-writes, are easier to add by editing
settings.json, because gemini mcp add reads a leading-dash argument as one of its own options.
Values in env expand $NAME from the environment Gemini started in, which keeps the token out of
the settings file without an env file:
{ "mcpServers": { "simply-atlassian": { "command": "npx", "args": ["atlassian", "mcp"], "env": { "JIRA_URL": "https://your-site.atlassian.net", "JIRA_USERNAME": "you@example.com", "JIRA_API_TOKEN": "$JIRA_API_TOKEN" } } }}Two other fields on an entry matter here. trust: true bypasses the per-call confirmation for
every tool on that server, so leave it off on any entry started with --allow-writes and let a
person approve each write. includeTools and excludeTools narrow what the model is shown, which
is a second way to express the split in Let the agent write: a write entry
can register the write tools while holding back the destructive ones.
{ "mcpServers": { "simply-atlassian-write": { "command": "simply", "args": ["atlassian", "mcp", "--allow-writes", "--env-file", "/home/me/atlassian-write.env"], "excludeTools": ["jira_issue_delete", "jira_issue_comment_delete", "confluence_page_delete"], "trust": false } }}Inside a session, /mcp lists the configured servers, whether each connected, and the tools it
registered.
Gemini Code Assist
Section titled “Gemini Code Assist”Agent mode in the VS Code extension reads that same ~/.gemini/settings.json, so an entry added
for Gemini CLI is already in place. In IntelliJ the file is mcp.json in the IDE’s configuration
directory, holding the same mcpServers object.
VS Code
Section titled “VS Code”VS Code reads .vscode/mcp.json, with a servers key and an explicit transport type:
{ "servers": { "simply-atlassian": { "type": "stdio", "command": "simply", "args": ["atlassian", "mcp", "--env-file", "/home/me/atlassian.env"] } }}Any other client
Section titled “Any other client”The server speaks MCP over stdio, so any client that can launch a local command works: give it the
command, its arguments, and optionally an environment, as above. It announces itself as
simply-atlassian with the package version during the handshake and supplies instructions that
summarise the tool conventions for the host’s model.
Check the connection
Section titled “Check the connection”Ask the agent which Jira account it is connected as. It should call jira_whoami, whose
description tells the model to use it first for exactly this purpose, and report back the account
the credentials resolve to. If it does, the server is running and the credentials work; the
troubleshooting table covers the cases where it does not.
Ask for what you want
Section titled “Ask for what you want”You do not call tools yourself. Describe the outcome, and the agent chooses the tools and fills in their inputs from the descriptions the server publishes. Some examples of what a request turns into:
| You ask | The agent calls |
|---|---|
| “What’s open in PROJ and assigned to me?” | jira_issue_search with jql such as project = PROJ AND assignee = currentUser() AND statusCategory != Done |
| “Summarise PROJ-42 and its comments.” | jira_issue_view, then jira_issue_comment_list |
| “Who changed the priority on PROJ-42, and when?” | jira_issue_history with field: "priority" |
| “What does the log attached to PROJ-42 say?” | jira_issue_attachment_list, then jira_issue_attachment_download with the log’s id |
| “What’s in the current sprint on the Platform board?” | jira_board_list to find the board id, jira_sprint_list for the active sprint, then jira_sprint_issues |
| “Find our release-notes pages in the DOCS space.” | confluence_page_search with a CQL query |
| “Who last edited the deployment runbook page?” | confluence_page_history with limit: 1 |
| “Give me a link to PROJ-42.” | jira_open, which returns the URL rather than launching a browser |
Each tool’s inputs are the matching command’s arguments and flags in camel case, with arrays where the CLI takes a repeatable or comma-separated flag; see the Command Reference for what each does. The first request above, for instance, becomes a call like:
{ "jql": "project = PROJ AND assignee = currentUser() AND statusCategory != Done", "fields": ["summary", "status", "priority"], "limit": 50}and returns the same envelope the CLI prints under --json:
{ "issues": [...], "pages": 1, "complete": true }A few things make the agent’s job easier:
- Name the fields you care about.
fieldson the issue tools matters as much here as in the CLI: raw issue payloads are large, and an agent pays for every token it reads. “Show me the summary and status of…” lets it ask for only those. - Name boards and sprints; the agent resolves the numbers. The sprint tools take numeric ids,
which the agent looks up with
jira_board_listandjira_sprint_list. - Transitions go by name. “Move it to Done” works; when unsure what the workflow allows, the
agent can call
jira_issue_transitionsfirst. - Mention people by name or email.
mentionsresolves them to account ids; an ambiguous name comes back as an error listing the candidates, so the agent can ask you which one. - Paste Confluence URLs. Every page input accepts a page id or a page URL.
opentools return URLs. A server cannot launch a browser, sojira_openandconfluence_openhand back the address for you to follow.
Attachments
Section titled “Attachments”jira_issue_attachment_download reads one file attached to an issue. What the agent gets depends
on the file:
| The file | What happens |
|---|---|
| UTF-8 text up to 1 MiB (logs, JSON, CSV, XML, YAML) | Returned to the agent, after a JSON block describing the file |
| A PNG, JPEG, GIF or WebP image up to 5 MiB | Returned to the agent as an image |
Anything else, or the agent passes save: true | Your client asks you for a folder to save it in, as described below |
When the server needs a folder, your MCP client shows a prompt naming the file, its size and the
issue. Type the absolute path of an existing folder, or decline. The agent cannot choose the folder:
the tool has no path input, because the file’s contents come from whoever uploaded it, and a
ticket’s text can steer an agent. Tick Save later attachments here without asking to reuse the
folder until the server stops. A file is never overwritten: if its name is taken, it is saved as
name (id).ext.
Clients that cannot show prompts (they do not support MCP elicitation) get the file’s details and
a reason instead of a prompt. Either way, every result carries a browserUrl: open it in a browser
where you are signed in to Jira to download the file yourself. The link carries no credential of
its own. For a large or bulk download, the CLI’s atlassian jira issue attachment download streams
any number of files of any size.
Text that comes back is the file exactly as uploaded. Like ticket text, treat it as data rather than as instructions to the agent; Write safety explains why that matters.
Let the agent write
Section titled “Let the agent write”Started without options, the server registers only the read tools. Start it with --allow-writes
to also register the tools that create, update, transition, comment, link, and delete. Even then,
ATLASSIAN_READ_ONLY in the environment refuses every write, exactly as it does for the CLI.
That mirrors the two-credential-file arrangement in Write safety: give the
agent’s everyday server a read-scoped token, and configure a second server entry with
--allow-writes and a write-capable token that is enabled only when a person means to let the
agent write. In a mcpServers configuration the pair looks like this:
{ "mcpServers": { "simply-atlassian": { "command": "simply", "args": ["atlassian", "mcp", "--env-file", "/home/me/atlassian.env"] }, "simply-atlassian-write": { "command": "simply", "args": ["atlassian", "mcp", "--allow-writes", "--env-file", "/home/me/atlassian-write.env"] } }}A read-scoped token is still the only layer that binds; the server-side default just keeps write
tools out of the agent’s normal loop entirely. Most clients also ask you to approve each tool call,
and the tools carry MCP annotations (readOnlyHint, destructiveHint) so a host can tell a read
from a delete when it asks. jira_issue_attachment_download is registered with the reads, because
it changes nothing in Jira, but is not marked read-only, because it can save a file to a folder you
chose.
With writes allowed:
-
Ask for a preview first. Every write tool accepts
dryRun: true, which returns the request that would be sent without sending it. “Show me what you’d send to move PROJ-42 to Done with a comment, then do it” produces a call like the one below, which you can check before the agent repeats it withoutdryRun.{ "issue": "PROJ-42", "transition": "Done", "comment": "Verified in staging.", "dryRun": true } -
Irreversible deletes need consent.
jira_issue_deleteandjira_issue_comment_deleterequireconfirm: true, and so doesconfluence_page_deletewithpurge: true. A call without it is answered with aconfirm-requirederror before anything is looked up, and the message names the input to add, so the agent will come back and ask you. Trashing a page needs noconfirm, because it is reversible. -
Confluence bodies can be Markdown. The page and comment tools take a body as
text(plain prose), raw storage-formatbody, ormarkdown(converted to storage format), andconfluence_page_updatetakesappendto add to a page instead of replacing it. “Add a Decisions section to this page with these three bullets” becomesconfluence_page_updatewithmarkdownandappend: true. -
A failed write may still have landed. A write is never retried after a
5xx, a dropped connection, or a timeout, because the instance may already have applied it; the error message saysThe change may have been applied; check before retrying.An agent that sees it should look for the issue, comment, or page change before calling the tool again, or it may create a duplicate. See Write safety.
| Read tools | Write tools (--allow-writes) |
|---|---|
jira_whoami | jira_issue_create |
jira_user_search | jira_issue_update |
jira_user_view | jira_issue_transition |
jira_open | jira_issue_delete (needs confirm) |
jira_issue_search | jira_issue_comment_add |
jira_issue_view | jira_issue_comment_edit |
jira_issue_history | jira_issue_comment_delete (needs confirm) |
jira_issue_attachment_list | jira_issue_link_create |
jira_issue_attachment_download | jira_issue_link_delete |
jira_issue_transitions | jira_sprint_add |
jira_issue_comment_list | confluence_page_create |
jira_issue_link_list | confluence_page_update |
jira_issue_link_types | confluence_page_delete (needs confirm with purge) |
jira_board_list | confluence_page_comment_add |
jira_sprint_list | |
jira_sprint_issues | |
confluence_open | |
confluence_page_get | |
confluence_page_search | |
confluence_page_children | |
confluence_page_history | |
confluence_page_comment_list |
Xray tools (--xray)
Section titled “Xray tools (--xray)”For Jira Server/Data Center with Xray installed, start the server with
--xray to also register seven read tools: jira_xray_fields, jira_xray_test_get,
jira_xray_test_list, jira_xray_test_export, jira_xray_plan_list, jira_xray_set_list, and
jira_xray_path_list. They are off by default because a client shows its model every registered
tool and most instances have no Xray, and Xray Server/Data Center needs no settings of its own that
the server could detect it by.
With --allow-writes as well, five write tools join them: jira_xray_test_create,
jira_xray_test_update, jira_xray_test_step_add, jira_xray_test_step_update, and
jira_xray_test_step_delete. Each returns { record, notes }, the test read back, or the plan under
dryRun. jira_xray_test_step_delete needs confirm: true, and so does a jira_xray_test_update
whose plan deletes a step. They were built from Xray’s published documentation and have not been run
against a live instance. Xray credentials have no read-only scope, so ATLASSIAN_READ_ONLY is the only
guard against a write.
They differ from the other tools in three ways, all described in the Xray guide:
- They return assembled data rather than a raw payload:
jira_xray_test_getreturns{ record, notes }andjira_xray_test_exportreturns{ records, total?, complete, notes }, with export records, and the list tools return{ rows, total?, complete }, rows keyed by field name rather than Xray’scustomfield_…ids. - Their
fieldsinput adds to the default fields instead of replacing them. jira_xray_test_exportreturns 100 tests unlesslimitasks for more, and at most 500, because a host holds the whole result in its context. For larger exports, use the CLI’sjira xray test export --format jsonl.
Results and errors
Section titled “Results and errors”A successful call returns what the CLI prints with --json, verbatim. A failure returns an error
result whose text is a JSON object with a stable code:
code | Meaning |
|---|---|
config | Missing or contradictory settings, a refused write, bad input. |
auth | The instance rejected the credentials; carries the HTTP status. |
error | Any other failure, including an API error with its HTTP status and sanitised body. |
confirm-required | A destructive tool was called without confirm: true. |
The first three map one-to-one onto the CLI’s exit codes 2, 3, and 1, described in
Scripts and agents, and carry the same name and message, scrubbed of
credentials and control characters the same way. The agent sees these errors and can act on them,
so a wrong key or a missing setting usually ends in the agent telling you what went wrong rather
than in silence.
Troubleshooting
Section titled “Troubleshooting”Run simply-atlassian-mcp --help at a terminal first. If it prints the tool list, the package is
installed and working, and the problem is in how the client launches it or what it passes.
| Symptom | Cause and fix |
|---|---|
| The client reports the server failed to start | Desktop apps do not load your shell profile, so the command may not be on their PATH. Use the full path to the executable (it lives in the directory npm prefix -g reports, under bin/ on macOS and Linux), or the npx form above. |
The client’s log says Env file ... not found. | The only startup failure. The path is wrong or relative; make it absolute. The server exits with code 2. |
Every tool returns code: "config" naming JIRA_URL | The settings are not reaching the server: the env file is not being read, or the variables were set in a shell the client did not inherit. Pass --env-file or an env block explicitly. |
Confluence tools fail with code: "config" while Jira works | Only the JIRA_* variables are set. Add the CONFLUENCE_* ones; each product’s settings are resolved separately. |
code: "auth" with status 401 | The instance rejected the credentials. Cloud needs JIRA_USERNAME plus JIRA_API_TOKEN; Server/Data Center needs JIRA_PERSONAL_TOKEN alone. Set one style, not both. |
code: "auth" with status 403 on a write | The token is read-scoped, which is the intended outcome for the everyday server. Use the write-capable server entry for this change. |
| The agent says it has no tool to create, update, or delete | The server was started without --allow-writes. That is the default; see Let the agent write. |
The agent has no jira_xray_* tools | The server was started without --xray. Add it to the server’s args; see Xray tools. |
A write returns code: "config" mentioning ATLASSIAN_READ_ONLY | The variable is set in the environment or the env file. Unset it, or point the write server at a credential file meant for writing. |
Embed in your own process
Section titled “Embed in your own process”The stdio binary is the usual way to run the server, but the package also exports what it is built from, so a host process can serve the same tools over a transport of its choosing:
import { createServer, startServer } from '@simplysf/simply-atlassian-mcp';
// Serve over stdio, exactly as the binary does.await startServer({ allowWrites: false, envFile: '/home/me/atlassian.env' });
// Or build the server and attach a transport yourself.const server = createServer({ allowWrites: true, env: process.env });await server.connect(transport);createServer returns the MCP SDK’s McpServer with the tools registered and nothing connected.
TOOLS is the full catalogue, selectTools(allowWrites, xray) is the subset a given configuration
registers, and invokeTool and mapError run one tool and map a failure onto the error object
above, for tests and hosts that need to drive the server without a transport.
Options
Section titled “Options”simply-atlassian-mcp [--allow-writes] [--xray] [--env-file <path>]--help prints the options and the full tool list. Because stdout is the protocol stream, it
prints to stderr.