jira-universal-server
v0.1.5
Published
Jira MCP server over stdio only (stdin/stdout JSON-RPC). Not HTTP — use command/args in Cursor & Claude.
Maintainers
Readme
jira-universal-server
stdio only — not HTTP. This program does not listen on a port. It speaks MCP by reading stdin and writing stdout (JSON-RPC). You must configure clients with
command+args+env(or globaljira-universal-server). If you still have aurllikehttp://localhost:3000/mcpin Cursor or Claude, remove it; that was for an older HTTP transport and will not work with this package.
Model Context Protocol server for Jira Cloud and Jira Data Center / Server. It talks to Jira over REST using your instance base URL (no hard‑coded cloud assumptions) and exposes tools for issues, search, projects, users, and Agile boards/sprints.
Transport: StdioServerTransport from @modelcontextprotocol/sdk — standard input/output only. Hosts (Cursor, Claude Desktop, Claude Code, etc.) spawn this process and pipe MCP over stdio.
Running npm start in a terminal will look idle: it is waiting for MCP on stdin, which only the host provides. Use the client config below instead of opening a browser or curling a URL.
Installation
npm install -g jira-universal-serverThe CLI on your PATH is jira-universal-server (see package.json bin).
Or use from a checkout:
npm install
npm run build
npm startQuick start
Pass Jira settings via environment variables (client env block, or a .env next to the working directory when using dotenv — the process loads .env from the current working directory).
Jira Cloud (basic auth — API token)
JIRA_BASE_URL=https://yourcompany.atlassian.net
JIRA_AUTH_TYPE=basic
[email protected]
JIRA_API_TOKEN=your_atlassian_api_token
JIRA_API_VERSION=3Jira Cloud (PAT)
JIRA_BASE_URL=https://yourcompany.atlassian.net
JIRA_AUTH_TYPE=pat
JIRA_PAT=your_personal_access_tokenJira Data Center (basic)
JIRA_BASE_URL=https://jira.yourcompany.com
JIRA_AUTH_TYPE=basic
[email protected]
JIRA_API_TOKEN=your_password_or_token_as_configured
JIRA_API_VERSION=2Use API version 2 on many Data Center deployments; try 3 if your platform supports it.
Jira Data Center (PAT)
JIRA_BASE_URL=https://jira.yourcompany.com
JIRA_AUTH_TYPE=pat
JIRA_PAT=your_personal_access_token
JIRA_API_VERSION=2See .env.example for a template.
Client configuration (stdio)
Claude Desktop
Config path is platform-dependent, e.g. macOS: ~/Library/Application Support/Claude/claude_desktop_config.json.
{
"mcpServers": {
"jira": {
"command": "npx",
"args": ["-y", "jira-universal-server"],
"env": {
"JIRA_BASE_URL": "https://yourcompany.atlassian.net",
"JIRA_AUTH_TYPE": "basic",
"JIRA_EMAIL": "[email protected]",
"JIRA_API_TOKEN": "your_api_token"
}
}
}
}If the package is installed globally:
{
"mcpServers": {
"jira": {
"command": "jira-universal-server",
"args": [],
"env": {
"JIRA_BASE_URL": "https://yourcompany.atlassian.net",
"JIRA_AUTH_TYPE": "basic",
"JIRA_EMAIL": "[email protected]",
"JIRA_API_TOKEN": "your_api_token"
}
}
}
}Cursor (.cursor/mcp.json in project or ~/.cursor/mcp.json)
{
"mcpServers": {
"jira": {
"command": "npx",
"args": ["-y", "jira-universal-server"],
"env": {
"JIRA_BASE_URL": "https://yourcompany.atlassian.net",
"JIRA_AUTH_TYPE": "basic",
"JIRA_EMAIL": "[email protected]",
"JIRA_API_TOKEN": "your_api_token"
}
}
}
}Local build (absolute path)
{
"mcpServers": {
"jira": {
"command": "node",
"args": ["/absolute/path/to/jira-universal-server/dist/index.js"],
"env": {
"JIRA_BASE_URL": "https://yourcompany.atlassian.net",
"JIRA_AUTH_TYPE": "basic",
"JIRA_EMAIL": "[email protected]",
"JIRA_API_TOKEN": "your_api_token"
}
}
}
}Restart the host app after editing MCP config.
Auth guide
| Strategy | Header | When to use |
|----------|--------|-------------|
| basic | Authorization: Basic base64(email:token) | Cloud: Atlassian account email + API token. Data Center: Often the same pattern if your admin documents it; some sites use username + password or token — follow your Jira docs. |
| pat | Authorization: Bearer <PAT> | Cloud and Data Center when PATs are enabled. Create in Jira under profile/security or admin PAT settings (product‑dependent). |
| oauth2 | Bearer (planned) | Stub only in this release; use basic or pat. |
basic is the usual choice for Cloud API tokens. pat avoids embedding the email and matches setups that only issue bearer tokens.
REST API version
Only the core REST path /rest/api/{version} is negotiable (supported 2 and 3). Agile is always /rest/agile/1.0.
With stdio transport, the Jira REST API version is taken from:
- Environment variable
JIRA_API_VERSION(2or3) - If unset, default
3
(resolveApiVersionFromSources in code still supports additional sources for reuse; the running stdio server uses env + default.)
When to use v2 vs v3
- Jira Cloud today is typically v3 (richer payloads, ADF for some text fields).
- Data Center often still documents v2 for the platform REST API; use v2 if v3 returns 404 or errors.
Environment variables
| Name | Required | Default | Description |
|------|----------|---------|-------------|
| JIRA_BASE_URL | Yes | — | Instance root, e.g. https://company.atlassian.net or https://jira.company.com (no trailing slash required). |
| JIRA_AUTH_TYPE | No | basic | basic, pat, or oauth2 (oauth2 stub). |
| JIRA_EMAIL | If basic | — | Account email (basic). |
| JIRA_API_TOKEN | If basic | — | API token or password as required by your server (basic). |
| JIRA_PAT | If pat | — | Personal access token (pat). |
| JIRA_OAUTH_ACCESS_TOKEN | No | — | Reserved for future OAuth2 support. |
| JIRA_API_VERSION | No | 3 | Jira REST API version (2 or 3). |
Rebuild and quick test
npm run buildSmoke test (process should stay alive waiting for MCP messages on stdin; invalid JSON may error — that is expected):
echo '{}' | node dist/index.jsPrefer validating by connecting Cursor or Claude Desktop with the config above and running a tool such as list_projects.
Custom fields
Jira stores custom data under keys like customfield_12345. This server:
- Requests
*allfields from Jira by default forget_issue,search_issues,get_my_issues, andget_sprint_issues. On many Jira Data Center / Server instances,*navigablereturns standard fields but omitscustomfield_*in the JSON even though you can browse the issue in the UI —*allis the reliable way to get custom field values over REST. - Adds a
customFieldsobject on each flattened issue with everycustomfield_*entry, values lightly normalized (users, selects, ADF docs, arrays).
If payloads are too large, pass fields: *navigable on get_issue or search_issues.fields: ["*navigable"] only after you have confirmed your Jira actually includes your custom fields in that response.
MCP tools
Responses are JSON text in the MCP content field, with flattened objects (not raw Jira payloads). Jira 401 / 403 are turned into clear messages referencing your configured auth type.
Issues
| Tool | Inputs | Example output (shape) |
|------|--------|-------------------------|
| get_issue | issueKey, fields? (*all default) | Core fields + customFields. Use *navigable only if you need a smaller response and your Jira still returns customs there. |
| create_issue | projectKey, summary, issueType, description? | { id, key, self } |
| update_issue | issueKey, fields (map) | { ok: true, issueKey } |
| delete_issue | issueKey | { ok: true, issueKey } |
| get_transitions | issueKey | { issueKey, transitions: [{ id, name, toStatus }] } |
| transition_issue | issueKey, transitionId | { ok: true, issueKey, transitionId } |
| add_comment | issueKey, body | { id, author, body, created, … } |
| get_comments | issueKey | { issueKey, comments: [...] } |
| assign_issue | issueKey, accountId | { ok: true, issueKey, accountId } |
Search
| Tool | Inputs | Example output |
|------|--------|----------------|
| search_issues | jql, fields? (default [*all]), maxResults?, startAt? | { total, startAt, maxResults, issues: [...] } — each issue includes customFields for customfield_* keys |
| get_my_issues | (none) | Same as search with fixed JQL for current user’s unresolved work |
Projects
| Tool | Inputs | Example output |
|------|--------|----------------|
| list_projects | (none) | { projects: [{ id, key, name, … }], count } |
| get_project | projectKey | Project summary object |
| get_project_statuses | projectKey | { projectKey, issueTypes: [{ issueTypeName, statuses }] } |
Users
| Tool | Inputs | Example output |
|------|--------|----------------|
| get_current_user | (none) | { accountId, displayName, email?, … } |
| find_users | query | { query, users: [...], count } |
Boards / Sprints (Agile 1.0)
| Tool | Inputs | Example output |
|------|--------|----------------|
| list_boards | projectKey? | { boards: [{ id, name, type }], count } or agile‑unavailable message |
| list_sprints | boardId, state? | { boardId, sprints: [...], count } or agile‑unavailable message |
| get_sprint_issues | sprintId | { sprintId, issues: [...], total, … } — issues use *all fields + customFields; or agile‑unavailable message |
On 404 from Agile endpoints, tools return a clear message that Jira Software / Agile REST may not be installed or enabled (common on some Data Center setups).
Data Center notes
- Prefer
JIRA_API_VERSION=2unless your version documents REST v3. - Agile tools require the Jira Software application and REST Agile endpoints. If the plugin is missing or URLs differ, you may see the graceful “agile unavailable” style response instead of a hard crash.
Example JQL (search_issues)
project = PROJ AND status = "In Progress"
assignee = currentUser() AND sprint in openSprints()
created >= -7d ORDER BY created DESC
type = Bug AND priority = High
text ~ "regression" AND updated >= -30dDevelopment
npm run dev # tsx src/index.ts
npm run build
npm startLicense
MIT
