Skip to main content
Glama
shamshodisaev

custom-atlassian-mcp

README.md
# 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

A4/5.0

Scored across 12 tools

Disambiguation5/5

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.

Naming Consistency4/5

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.

Tool Count5/5

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.

Completeness4/5

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.

Maintenance

ActivityStale
ResponsivenessNo issues