custom-atlassian-mcp
# custom-atlassian-mcp
A small, self-hosted [Model Context Protocol](https://modelcontextprotocol.io)
server that exposes Jira and Confluence Cloud to any MCP-compatible client
(Claude Code, Claude Desktop, GitHub Copilot Agents, etc.) over stdio.
Built as a lightweight alternative to the (currently unavailable) public
Confluence MCP — one Atlassian site, one API token, no frameworks.
## What you get
**Jira**
- `jira_search` — run a JQL query, get a compact issue list
- `jira_get_issue` — full issue with description and recent comments (ADF → text)
- `jira_add_comment` — append a plain-text comment
- `jira_update_issue` — edit summary, description, assignee, priority, labels
- `jira_create_issue` — create issues, optionally linked to an epic or parent
- `jira_transition_issue` — list transitions or move an issue through workflow
- `jira_list_sprints` — list sprints for a Jira Software board (Agile API)
- `jira_add_issues_to_sprint` — move issues into a sprint
**Confluence**
- `confluence_search` — CQL search across spaces and pages
- `confluence_get_page` — fetch a page as plain text or raw storage-format XHTML
- `confluence_create_page` — create a page under a space (optionally under a parent)
- `confluence_update_page` — update body/title; supports drafts and publishing
## Requirements
- **Node.js 18+** (uses the built-in `fetch`)
- An **Atlassian Cloud** site and an **API token**
(create one at <https://id.atlassian.com/manage-profile/security/api-tokens>)
## Install
```bash
git clone https://github.com/shamshodisaev/custom-atlassian-mcp.git
cd custom-atlassian-mcp
npm install
npm run build
```
`npm run build` compiles TypeScript to `dist/` and marks `dist/index.js`
executable.
## Configure
The server reads three environment variables:
| Variable | Example | Description |
| --- | --- | --- |
| `ATLASSIAN_SITE` | `your-org.atlassian.net` | Your Atlassian Cloud host (no protocol, no trailing slash) |
| `ATLASSIAN_EMAIL` | `you@example.com` | Email of the account that owns the API token |
| `ATLASSIAN_API_TOKEN` | `ATATT3x…` | API token from the Atlassian profile page |
For local runs you can copy `.env.example` to `.env` and fill it in — but the
MCP server itself does **not** load `.env` automatically. Either export the
vars in your shell, launch the server via `node --env-file=.env dist/index.js`,
or pass them through your MCP client's config (see below).
## Wire it into an MCP client
### Claude Code
Add an entry to your Claude Code MCP config (typically `~/.claude.json` under
`mcpServers`, or via `claude mcp add`):
```jsonc
{
"mcpServers": {
"atlassian": {
"command": "node",
"args": ["/absolute/path/to/custom-atlassian-mcp/dist/index.js"],
"env": {
"ATLASSIAN_SITE": "your-org.atlassian.net",
"ATLASSIAN_EMAIL": "you@example.com",
"ATLASSIAN_API_TOKEN": "ATATT3x..."
}
}
}
}
```
Restart Claude Code — the `atlassian` server should appear in `/mcp` and its
tools will be available as `mcp__atlassian__jira_search`, etc.
### Claude Desktop
Add the same block to `~/Library/Application Support/Claude/claude_desktop_config.json`
(macOS) or the equivalent on Windows/Linux.
### Any other MCP client
Any client that can launch an stdio MCP server can use it — point it at
`node /absolute/path/to/dist/index.js` with the three env vars set.
## Run standalone (for smoke testing)
```bash
export ATLASSIAN_SITE=your-org.atlassian.net
export ATLASSIAN_EMAIL=you@example.com
export ATLASSIAN_API_TOKEN=ATATT3x...
npm start
```
The server communicates over stdio, so it will look idle — that's expected.
It's meant to be spawned by an MCP client, not talked to by hand. To exercise
it interactively use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector):
```bash
npx @modelcontextprotocol/inspector node dist/index.js
```
## Tool details
### Jira
<details>
<summary><code>jira_search</code></summary>
Search issues via JQL. Returns a compact summary per issue plus paging info.
**Args**
- `jql` (string, required) — JQL query, e.g. `project = ABC AND status = "In Progress"`
- `maxResults` (number, 1–100, default 25)
- `fields` (string[], optional) — extra field names beyond the default summary set
</details>
<details>
<summary><code>jira_get_issue</code></summary>
Fetch a single issue with description and recent comments (ADF converted to text).
**Args**
- `key` (string, required) — e.g. `ABC-123`
- `includeComments` (boolean, default true)
</details>
<details>
<summary><code>jira_add_comment</code></summary>
Append a plain-text comment. The text is wrapped into a single ADF paragraph.
**Args**
- `key` (string, required)
- `body` (string, required)
</details>
<details>
<summary><code>jira_update_issue</code></summary>
Update editable fields. Only provided fields are changed.
**Args**
- `key` (string, required)
- `summary`, `description`, `priority` (strings, optional)
- `assigneeAccountId` (string, optional; use `"unassigned"` to clear)
- `labels` (string[], optional — replaces existing labels)
</details>
<details>
<summary><code>jira_create_issue</code></summary>
Create a new issue. Description text is converted to ADF.
**Args**
- `projectKey` (string, required), `summary` (string, required)
- `issueType` (string, default `Task`)
- `description`, `assigneeAccountId`, `priority` (strings, optional)
- `labels` (string[], optional)
- `epicKey` (string, optional) — sets Epic Link via `customfield_10014` (company-managed projects)
- `parentKey` (string, optional) — sets `parent` (team-managed projects, sub-tasks)
</details>
<details>
<summary><code>jira_transition_issue</code></summary>
Omit `transition` to list available transitions; supply it (by id or name) to apply one.
**Args**
- `key` (string, required)
- `transition` (string, optional)
</details>
<details>
<summary><code>jira_list_sprints</code></summary>
List sprints for a Jira Software board (Agile API).
**Args**
- `boardId` (string or number, required)
- `state` (`active` | `future` | `closed`, optional)
</details>
<details>
<summary><code>jira_add_issues_to_sprint</code></summary>
Move issues into a sprint (Agile API).
**Args**
- `sprintId` (string or number, required)
- `issueKeys` (string[], required, min 1)
</details>
### Confluence
<details>
<summary><code>confluence_search</code></summary>
Search content with CQL, e.g. `space = ENG AND title ~ "onboarding"`.
**Args**
- `cql` (string, required)
- `limit` (number, 1–50, default 15)
</details>
<details>
<summary><code>confluence_get_page</code></summary>
Fetch a page by id.
**Args**
- `pageId` (string, required)
- `format` (`text` (default) | `storage`) — `text` strips HTML; `storage` returns raw XHTML
</details>
<details>
<summary><code>confluence_create_page</code></summary>
Create a page under a space. The body is treated as Confluence storage-format
XHTML — pass HTML-like markup, not markdown. Plain text is accepted and wrapped
in `<p>`.
**Args**
- `spaceKey` (string, required) — resolved to a spaceId internally
- `title` (string, required)
- `body` (string, required)
- `parentId` (string, optional)
</details>
<details>
<summary><code>confluence_update_page</code></summary>
Update a page's body (and optionally title/status). Drafts stay at version 1
per Confluence's rules; pass `status: "current"` to publish a draft.
**Args**
- `pageId` (string, required)
- `body` (string, required)
- `title` (string, optional)
- `status` (`draft` | `current`, optional)
</details>
## Project layout
```
src/
index.ts # stdio entrypoint, wires the server + tools
client.ts # thin fetch wrapper with Basic auth + typed errors
adf.ts # tiny ADF <-> plain-text converter
tools/
jira.ts # jira_* tool registrations
confluence.ts # confluence_* tool registrations
```
## Development
```bash
npm run dev # tsc --watch
npm run build # compile once, chmod +x dist/index.js
npm start # node dist/index.js (needs env vars set)
```
The MCP server is written against the official
[`@modelcontextprotocol/sdk`](https://www.npmjs.com/package/@modelcontextprotocol/sdk).
Every tool is registered with a Zod schema — argument validation and the tool
manifest come from the same source.
## Security notes
- API tokens are as powerful as your account — treat them like passwords.
- The included `.gitignore` blocks `.env`; keep it that way.
- All requests go directly from the server process to your Atlassian site over
HTTPS with HTTP Basic auth (`email:token` base64-encoded). Nothing is proxied
or logged.
## License
No license granted. This is a personal utility — fork it if you want to use or
modify it.
TDQS
Scored across 12 tools
Each tool targets a distinct resource and action: Jira issues vs sprints vs comments, Confluence pages vs search. No two tools have overlapping purposes, and descriptions clarify even similar verbs like update_issue vs transition_issue.
All tools follow a consistent snake_case pattern with a domain prefix (jira_/confluence_) and verb. Minor deviations: search omits an object (e.g., jira_search vs jira_get_issue) and add_issues uses plural, but the pattern is still predictable.
12 tools is well-scoped for an Atlassian server covering Jira and Confluence. Each tool earns its place, covering search, read, create, update, and specialized actions without bloat.
Core CRUD and lifecycle operations are covered for both Jira (search, get, create, update, transition, comment, sprint management) and Confluence (search, get, create, update). Missing delete operations and project/space listing are minor gaps that agents can work around.