jira-mcp-ai 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +139 -0
  2. package/LICENSE +21 -0
  3. package/README.md +247 -0
  4. package/bin/jira-mcp-ai.cjs +53 -0
  5. package/build/api/adf.d.ts +97 -0
  6. package/build/api/adf.js +1034 -0
  7. package/build/api/agile.d.ts +300 -0
  8. package/build/api/agile.js +695 -0
  9. package/build/api/attachments.d.ts +190 -0
  10. package/build/api/attachments.js +394 -0
  11. package/build/api/collab.d.ts +382 -0
  12. package/build/api/collab.js +852 -0
  13. package/build/api/filters.d.ts +113 -0
  14. package/build/api/filters.js +292 -0
  15. package/build/api/issues.d.ts +717 -0
  16. package/build/api/issues.js +1456 -0
  17. package/build/api/meta.d.ts +344 -0
  18. package/build/api/meta.js +547 -0
  19. package/build/api/search.d.ts +173 -0
  20. package/build/api/search.js +366 -0
  21. package/build/api/shared.d.ts +195 -0
  22. package/build/api/shared.js +324 -0
  23. package/build/api/users.d.ts +148 -0
  24. package/build/api/users.js +262 -0
  25. package/build/cli/doctor.d.ts +134 -0
  26. package/build/cli/doctor.js +1042 -0
  27. package/build/core/clock.d.ts +14 -0
  28. package/build/core/clock.js +75 -0
  29. package/build/core/config.d.ts +75 -0
  30. package/build/core/config.js +160 -0
  31. package/build/core/credentials.d.ts +84 -0
  32. package/build/core/credentials.js +115 -0
  33. package/build/core/env-lock.d.ts +78 -0
  34. package/build/core/env-lock.js +340 -0
  35. package/build/core/errors.d.ts +127 -0
  36. package/build/core/errors.js +340 -0
  37. package/build/core/host.d.ts +70 -0
  38. package/build/core/host.js +168 -0
  39. package/build/core/http-util.d.ts +160 -0
  40. package/build/core/http-util.js +533 -0
  41. package/build/core/http.d.ts +54 -0
  42. package/build/core/http.js +849 -0
  43. package/build/core/journal.d.ts +30 -0
  44. package/build/core/journal.js +143 -0
  45. package/build/core/log.d.ts +52 -0
  46. package/build/core/log.js +133 -0
  47. package/build/core/redact.d.ts +75 -0
  48. package/build/core/redact.js +307 -0
  49. package/build/core/rng.d.ts +8 -0
  50. package/build/core/rng.js +20 -0
  51. package/build/core/settings.d.ts +109 -0
  52. package/build/core/settings.js +496 -0
  53. package/build/core/telemetry.d.ts +33 -0
  54. package/build/core/telemetry.js +60 -0
  55. package/build/core/types.d.ts +566 -0
  56. package/build/core/types.js +167 -0
  57. package/build/index.d.ts +46 -0
  58. package/build/index.js +385 -0
  59. package/build/mcp/define.d.ts +76 -0
  60. package/build/mcp/define.js +212 -0
  61. package/build/mcp/errors.d.ts +42 -0
  62. package/build/mcp/errors.js +93 -0
  63. package/build/mcp/recent-writes.d.ts +68 -0
  64. package/build/mcp/recent-writes.js +136 -0
  65. package/build/mcp/registry.d.ts +76 -0
  66. package/build/mcp/registry.js +380 -0
  67. package/build/mcp/result.d.ts +80 -0
  68. package/build/mcp/result.js +411 -0
  69. package/build/mcp/server.d.ts +125 -0
  70. package/build/mcp/server.js +320 -0
  71. package/build/mcp/taint.d.ts +55 -0
  72. package/build/mcp/taint.js +126 -0
  73. package/build/mcp/tool-helpers.d.ts +131 -0
  74. package/build/mcp/tool-helpers.js +158 -0
  75. package/build/mcp/transport.d.ts +55 -0
  76. package/build/mcp/transport.js +146 -0
  77. package/build/mcp/types.d.ts +332 -0
  78. package/build/mcp/types.js +74 -0
  79. package/build/mcp/write-mode.d.ts +143 -0
  80. package/build/mcp/write-mode.js +426 -0
  81. package/build/tools/agile.d.ts +138 -0
  82. package/build/tools/agile.js +503 -0
  83. package/build/tools/attachments.d.ts +63 -0
  84. package/build/tools/attachments.js +497 -0
  85. package/build/tools/collab.d.ts +173 -0
  86. package/build/tools/collab.js +615 -0
  87. package/build/tools/core.d.ts +143 -0
  88. package/build/tools/core.js +129 -0
  89. package/build/tools/index.d.ts +62 -0
  90. package/build/tools/index.js +193 -0
  91. package/build/tools/issues-delete.d.ts +95 -0
  92. package/build/tools/issues-delete.js +319 -0
  93. package/build/tools/issues-write.d.ts +104 -0
  94. package/build/tools/issues-write.js +645 -0
  95. package/build/tools/issues.d.ts +41 -0
  96. package/build/tools/issues.js +312 -0
  97. package/build/tools/meta.d.ts +136 -0
  98. package/build/tools/meta.js +354 -0
  99. package/build/tools/search.d.ts +121 -0
  100. package/build/tools/search.js +413 -0
  101. package/build/tools/users.d.ts +51 -0
  102. package/build/tools/users.js +173 -0
  103. package/package.json +93 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,139 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
6
+ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ Because registrations pin an exact version (see README), this file is the thing a
9
+ user reads before bumping the pin. Entries describe what changes for **them** —
10
+ new or renamed tools, changed tool input/output shapes, changed defaults, changed
11
+ env var names — not internal refactors.
12
+
13
+ ## [0.9.0] — 2026-08-17
14
+
15
+ The first release. Everything below is "added" relative to nothing, so this
16
+ entry is the whole surface rather than a diff — later entries will be diffs
17
+ against it.
18
+
19
+ It is `0.9.0` and not `1.0.0` deliberately. The surface is complete and the
20
+ gate is green, but this build has never been run against a real Atlassian
21
+ tenant — every test in it answers a fake. A pre-1.0 number is the honest way to
22
+ say so, and it keeps the semver promise unspent until a live run has been made:
23
+ under 1.0.0 the same discovery would have to arrive as a breaking major.
24
+
25
+ ### Added
26
+
27
+ - **52 tools in 10 packages**, registered as a set you choose with
28
+ `JIRA_TOOL_PACKAGES`:
29
+ - `core` (2) — `jira_capabilities`, `jira_get_myself`.
30
+ - `search` (4) — JQL search and approximate count, plus the saved-filter
31
+ reads `jira_list_filters` / `jira_get_filter`. Neither filter tool runs the
32
+ stored JQL; `jira_search` is the only tool that executes one.
33
+ - `issues` (5) — issue, comments, transitions, changelog and worklog reads.
34
+ - `issues-write` (8) — create, update, transition, add comment, edit comment,
35
+ assign, worklog, link.
36
+ - `issues-delete` (3) — delete issue, comment and worklog, behind the
37
+ irreversible tier below. Not in the `reader` profile and in no read-only
38
+ selection; `JIRA_PACKAGES_DENY=issues-delete` removes the surface entirely.
39
+ - `attachments` (3) — list attachment metadata, download a file to disk,
40
+ upload a file from disk.
41
+ - `collab` (12) — watchers, votes, components, versions and project roles:
42
+ the surface around an issue rather than inside it. Nothing in this package
43
+ deletes anything — "remove watcher" and "remove vote" are links their `add`
44
+ twin restores exactly.
45
+ - `meta` (6) — projects, fields, create-meta, statuses and link types.
46
+ - `users` (1) — `jira_search_users`.
47
+ - `agile` (8) — boards, sprints, sprint issues, move to sprint and to
48
+ backlog, plus the sprint lifecycle (create, start, close).
49
+ - **Markdown on the way in and on the way out.** `jira_get_issue` and
50
+ `jira_get_comments` take `format: "text" | "markdown"`, and every rich-text
51
+ input on the 7 write tools that has one (`description`, `body`, `comment`)
52
+ takes the same option. The default is `text` and its output is byte-identical
53
+ to having no option at all, so nothing you already call changes. The dialect
54
+ is deliberately narrow — headings, lists, fenced code, inline code, bold,
55
+ italic, links — and anything outside it degrades exactly the way plain-text
56
+ flattening always did. Two safety rules travel with it: a mention renders
57
+ one-way (`@name` out, literal text back in), and only `http(s):` and
58
+ `mailto:` links keep their href.
59
+ - **A plan/apply gate on all 25 write tools.** `JIRA_WRITE_MODE` defaults to
60
+ `plan`, where a write tool describes the change and returns a single-use
61
+ `plan_id` instead of performing it. Executing takes all three:
62
+ `JIRA_WRITE_MODE=apply`, an explicit `apply: true`, and that `plan_id` echoed
63
+ back with identical arguments. Ids die with the server process, and unsafe
64
+ writes are never replayed after an ambiguous failure.
65
+ - **An irreversible tier for the three deletes**, which the normal gate does
66
+ not cover. On top of plan → apply they need `JIRA_ALLOW_IRREVERSIBLE=true`:
67
+ a blanket `JIRA_WRITE_MODE=apply` is never enough, because the variable is
68
+ set by the person who starts the server and the model cannot fill it in.
69
+ Planning a delete always works, even on a server that will never permit the
70
+ apply, and a refusal costs you nothing — no request is made and the
71
+ `plan_id` is not consumed, so flipping the variable and restarting does not
72
+ mean re-planning. The plan carries a `before` snapshot of what the apply
73
+ would destroy (for an issue: its summary, status and subtask keys; for a
74
+ comment or worklog: author, timestamps and an excerpt of the text), and a
75
+ successful apply echoes that same snapshot back — Jira answers a delete with
76
+ an empty 204, so this is the only receipt there is.
77
+ - **Attachments, with the bytes on disk instead of in the conversation.**
78
+ `JIRA_MEDIA_DIR` is the one directory the server reads from and writes to.
79
+ Unset, the download and upload tools refuse with a configuration error and
80
+ make no Jira call at all; metadata listing needs no directory and keeps
81
+ working. A download sanitizes the Jira-supplied filename (it is tenant-authored
82
+ text, and `../../etc/passwd` lands as `passwd` inside your media directory),
83
+ never overwrites — a collision gets a suffixed name and the result tells you
84
+ it was renamed — and writes the file `0600`; the untouched original name is
85
+ still reported. An upload takes a plain file name inside that same directory
86
+ and **refuses** paths, `..` and subdirectories rather than rewriting them. Both
87
+ directions are size-capped, and an upload that fails ambiguously is never
88
+ retried for you.
89
+ - **Tenant text arrives branded.** Any result that can carry free text written
90
+ inside Jira — issue and comment bodies, changelogs, worklogs, filter
91
+ descriptions, attachment filenames, delete `before` snapshots — is marked
92
+ `_untrusted` with a note that its content is data, never instructions.
93
+ - **`jira-mcp-ai doctor`** — checks the configuration and probes the site, with
94
+ `--version` and `--help` alongside it. Its report goes to stdout only on these
95
+ non-server paths; under the server, stdout is the MCP protocol and every
96
+ diagnostic goes to stderr.
97
+ - **Credential profiles** — `JIRA_PROFILE_<NAME>_SITE` / `_EMAIL` /
98
+ `_API_TOKEN`, selected with `JIRA_ACTIVE_PROFILE`. `JIRA_LOCK_PROFILE`
99
+ defaults to `true`, so a single tool call cannot switch tenant unless you
100
+ unlock it deliberately.
101
+ - **Package trimming** — `JIRA_PACKAGES_DENY` wins over the selection (`core` is
102
+ force-re-added, so the server is never left with no tools), and
103
+ `JIRA_PACKAGES_READONLY` drops just the write-tier tools out of a package it
104
+ otherwise keeps.
105
+ - **Budgets and loop guards** — `JIRA_REQUEST_TIMEOUT_MS` (30000),
106
+ `JIRA_CALL_BUDGET_MS` (120000), `JIRA_HOST_CONCURRENCY` (4),
107
+ `JIRA_RETRY_ATTEMPTS` (3), `JIRA_MAX_RESULT_CHARS` (25000) and
108
+ `JIRA_MAX_PAGES` (20). Results are truncated to a stated budget rather than
109
+ returned whole.
110
+ - **Host allowlisting** — outbound requests go to the configured site and to
111
+ `JIRA_ALLOWED_HOSTS` entries only, matched exactly or by anchored regex. No
112
+ suffix matching. Redirects are not followed, with one deliberate exception:
113
+ an attachment download follows exactly one hop to the signed media URL Jira
114
+ answers with. That hop must be `https:` and not a private or link-local
115
+ address, and it carries no credentials — your API token is never sent to it.
116
+ - **Secret redaction and an optional write journal** — the API token is
117
+ registered with the redactor and never logged; `JIRA_JOURNAL_PATH` records
118
+ every write tool call as JSONL.
119
+ - **API token expiry warnings** — `JIRA_TOKEN_EXPIRES` makes startup and
120
+ `doctor` warn 30 days out.
121
+ - **Transport: stdio.** `JIRA_TRANSPORT`, `JIRA_HTTP_PORT` and `JIRA_HTTP_TOKEN`
122
+ are still read and validated, but the HTTP transport is deferred past 1.0:
123
+ starting with `JIRA_TRANSPORT=http` fails immediately with a message saying
124
+ so, rather than silently falling back to stdio.
125
+ - Distribution manifests: `server.json` for the MCP registry, `.claude-plugin/`
126
+ for Claude Code, and a release workflow that publishes with provenance — over
127
+ GitHub OIDC once a trusted publisher can be registered, which for a package's
128
+ own first version is not yet possible.
129
+ - Repository furniture: build, lint, format, coverage and CI configuration; the
130
+ `npm run check` gate; the docs consistency linter (`scripts/docs-lint.mjs`)
131
+ and the tarball-content assertion (`scripts/check-tarball.mjs`).
132
+
133
+ ### Notes
134
+
135
+ - Jira Cloud REST v3 only, so issue and comment bodies are ADF. Users are
136
+ addressed by `accountId`; there is no name or email lookup shortcut.
137
+ - Search runs on `/rest/api/3/search/jql` with `nextPageToken` paging. The
138
+ legacy `/rest/api/3/search` endpoint was removed by Atlassian on 2025-08-01
139
+ and is not called.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ivan Baev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,247 @@
1
+ # jira-mcp-ai
2
+
3
+ An [MCP](https://modelcontextprotocol.io) server for **Jira Cloud**: it gives an
4
+ MCP-capable agent (Claude Code, Claude Desktop, or any other client) tools to
5
+ search, read and — behind an explicit gate — write Jira issues, using your own
6
+ Atlassian account and API token.
7
+
8
+ Two things shape the design:
9
+
10
+ - **Writes are gated.** Write tools default to describing what they *would* do
11
+ instead of doing it. Executing requires both an opt-in configuration and an
12
+ explicit flag on the call. See [`docs/THREAT-MODEL.md`](docs/THREAT-MODEL.md).
13
+ - **Jira content is untrusted input.** Issue text arrives from whoever filed the
14
+ ticket. It is labelled as data, never merged into the agent's instructions.
15
+
16
+ > **Status: implemented.** The specification in [`docs/`](docs/README.md) is
17
+ > normative and the code ships with it; drift is a bug. Nothing is published to
18
+ > npm yet, so the registration example below will not resolve until the first
19
+ > release.
20
+
21
+ ## Requirements
22
+
23
+ - Node.js ≥ 22 (env files are read with `process.loadEnvFile()`, not dotenv)
24
+ - A Jira Cloud site and an Atlassian API token
25
+
26
+ ## Registration
27
+
28
+ Put this in `.mcp.json` (project scope), or paste the inner `"jira"` object
29
+ into `claude mcp add-json jira '<object>'` — `claude mcp add` takes CLI
30
+ arguments, not JSON.
31
+
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "jira": {
36
+ "command": "npx",
37
+ "args": ["-y", "jira-mcp-ai@0.9.0"],
38
+ "env": {
39
+ "JIRA_SITE": "mycompany",
40
+ "JIRA_EMAIL": "me@example.com",
41
+ "JIRA_API_TOKEN": "<api-token>",
42
+ "JIRA_WRITE_MODE": "plan"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ The version is pinned on purpose: an unpinned `npx -y` re-resolves to whatever is
50
+ newest at spawn time, so a fresh publish could start running new code inside an
51
+ agent session with no review step. Bump the pin once you have read the changelog.
52
+
53
+ Before wiring the server into a client, run
54
+ `npx -y jira-mcp-ai@0.9.0 doctor` with the same environment variables set: it
55
+ runs the configuration and credential probes from a plain terminal and prints a
56
+ report — the fastest way to learn whether the site, email and token actually
57
+ work. `--help` and `--version` are also available.
58
+
59
+ **If the server does not appear, it is almost always PATH.** Claude Desktop
60
+ launches MCP servers from a minimal environment that does not include your
61
+ shell's PATH, so a `node`/`npx` installed by nvm, Homebrew or fnm is invisible to
62
+ it and the launch fails inside the client, before this server runs — you get the
63
+ client's generic "server failed" message and nothing on this server's stderr,
64
+ because there was no process. Fix it by giving an absolute path:
65
+ `"command": "/usr/local/bin/npx"` (`which npx` prints yours). Claude Code, run
66
+ from a terminal, inherits your PATH and is not affected.
67
+
68
+ Every diagnostic this server writes goes to **stderr**, never stdout — stdout is
69
+ the MCP protocol. Claude Code keeps it in `~/.claude/logs/`; Claude Desktop in
70
+ `~/Library/Logs/Claude/mcp*.log` (macOS) or `%APPDATA%\Claude\logs\` (Windows).
71
+ That is where the startup report and any `JIRA_*` configuration error will be.
72
+
73
+ ## Configuration
74
+
75
+ Every setting is an environment variable with the `JIRA_` prefix. The full table
76
+ — names, defaults, required-ness, and where credentials may live — is in
77
+ [`docs/CONFIGURATION.md`](docs/CONFIGURATION.md); [`.env.example`](.env.example)
78
+ is a fill-in-the-blanks copy.
79
+
80
+ ## Tools
81
+
82
+ <!-- GENERATED:TOOLS:START -->
83
+
84
+ <!-- Generated by scripts/generate-readme.mjs from the tool manifest.
85
+ Do not edit by hand — run `npm run readme`. -->
86
+
87
+ 52 tools in 10 packages, 25 of them write tools
88
+ behind the plan/apply gate. `JIRA_TOOL_PACKAGES`, `JIRA_PACKAGES_DENY` and
89
+ `JIRA_PACKAGES_READONLY` decide which of them a session actually sees; the
90
+ `core` package is always registered. Full input and output shapes are in
91
+ [`docs/TOOLS.md`](docs/TOOLS.md).
92
+
93
+ ### Core — `core`
94
+
95
+ Server self-description and credential check — always registered, even when every other package is denied.
96
+
97
+ | Tool | Title | Access | What it does |
98
+ | --- | --- | --- | --- |
99
+ | `jira_capabilities` | Describe this server | read-only | Describe this server without calling Jira: the packages and tools that are registered, the site, the active profile, the write mode (plan vs apply) and the per-call limits. |
100
+ | `jira_get_myself` | Get authenticated user | read-only | Verify the configured credentials and return the account they belong to: accountId, displayName, active, accountType, timeZone and locale. |
101
+
102
+ ### Search — `search`
103
+
104
+ JQL search over issues, approximate result counts, and the saved filters that store reusable JQL — one page per call.
105
+
106
+ | Tool | Title | Access | What it does |
107
+ | --- | --- | --- | --- |
108
+ | `jira_search` | Search issues | read-only | Run JQL and return ONE page of issues plus data.nextPageToken — pass it back to read the next page. There is no total; use jira_count. |
109
+ | `jira_count` | Count issues | read-only | Count the issues a JQL matches without fetching any of them. |
110
+ | `jira_list_filters` | List saved filters | read-only | Finds saved filters — named, stored JQL — by name substring or owner, and returns each one with its jql. |
111
+ | `jira_get_filter` | Get saved filter | read-only | Reads one saved filter by numeric id (from jira_list_filters): name, description, owner, the JQL it stores and whether you favourited it. |
112
+
113
+ ### Issues (read) — `issues`
114
+
115
+ One issue and its comments, available transitions, change history and worklogs — reads only; the matching writes live in issues-write.
116
+
117
+ | Tool | Title | Access | What it does |
118
+ | --- | --- | --- | --- |
119
+ | `jira_get_issue` | Get issue | read-only | Read one Jira issue by key or id. Name the fields you need — omitting fields returns Jira's whole navigable set and burns the result budget. |
120
+ | `jira_get_comments` | Get comments | read-only | List the comments on an issue, newest first by default (orderBy -created; Jira's own default is oldest first). |
121
+ | `jira_get_transitions` | Get transitions | read-only | List the workflow transitions available from this issue's CURRENT status: id, name and target status. |
122
+ | `jira_get_changelog` | Get changelog | read-only | Read an issue's change history — field, from → to, author, created. |
123
+ | `jira_get_worklogs` | Get worklogs | read-only | List the work logged on an issue: timeSpentSeconds, timeSpent, started, author and the flattened comment. Sum timeSpentSeconds rather than parsing timeSpent strings. |
124
+
125
+ ### Issues (write) — `issues-write`
126
+
127
+ Issue changes: creation, fields, workflow transitions, comments and comment edits, assignee, worklogs and links — every tool is plan-gated.
128
+
129
+ | Tool | Title | Access | What it does |
130
+ | --- | --- | --- | --- |
131
+ | `jira_create_issue` | Create issue | write · standard | Create one issue. project and issueType are instance-specific — resolve them with jira_list_projects and jira_get_create_meta, which also names the custom fields this project requires. |
132
+ | `jira_update_issue` | Update issue | write · standard | Update fields on one issue. REPLACE semantics: description (text or ADF) replaces the WHOLE rich-text field, so tables and panels in the old value are lost — never "append" a paragraph this way. |
133
+ | `jira_transition_issue` | Transition issue | write · standard | Move one issue through its workflow. |
134
+ | `jira_add_comment` | Add comment | write · standard | Add a comment to one issue. body takes plain text (converted to ADF) or a raw ADF document; format: "markdown" parses a string body as the markdown subset. |
135
+ | `jira_update_comment` | Update comment | write · standard | Edit one existing comment. |
136
+ | `jira_assign_issue` | Assign issue | write · standard | Set or clear the assignee of one issue. Pass exactly one of accountId (assign) or unassign: true (clear) — both together is rejected as ambiguous, neither is rejected as intentless. |
137
+ | `jira_add_worklog` | Add worklog | write · standard | Log work against one issue. Pass exactly one of timeSpentSeconds (preferred) or timeSpent ("2h 30m"). |
138
+ | `jira_link_issues` | Link issues | write · standard | Link two issues. linkType is the link type NAME ("Blocks", "Relates"), and those names are instance-specific — read them from jira_list_link_types and spell them exactly. |
139
+
140
+ ### Issues (delete) — `issues-delete`
141
+
142
+ Irreversible deletions: an issue, a comment, a worklog entry. Every tool is plan-gated AND needs JIRA_ALLOW_IRREVERSIBLE; every plan shows what would be destroyed.
143
+
144
+ | Tool | Title | Access | What it does |
145
+ | --- | --- | --- | --- |
146
+ | `jira_delete_issue` | Delete issue | write · irreversible | Permanently delete one issue. IRREVERSIBLE: Jira has no undo and no trash for this, the issue and its comments, worklogs and attachments are gone. |
147
+ | `jira_delete_comment` | Delete comment | write · irreversible | Permanently delete one comment from an issue. IRREVERSIBLE: the comment is not recoverable and the deletion is not recorded in the issue changelog. |
148
+ | `jira_delete_worklog` | Delete worklog | write · irreversible | Permanently delete one worklog entry from an issue. IRREVERSIBLE: the logged time is gone and Jira gives it back to the remaining estimate (its default adjustment). |
149
+
150
+ ### Attachments — `attachments`
151
+
152
+ Files on issues: list what is attached, download one into the server's media directory, and attach a file from it. The two byte-moving tools need JIRA_MEDIA_DIR and never touch anything outside it.
153
+
154
+ | Tool | Title | Access | What it does |
155
+ | --- | --- | --- | --- |
156
+ | `jira_list_attachments` | List attachments | read-only | Lists the files attached to one issue: id, filename, size in bytes, mime type, author and creation time. Metadata only — no bytes are transferred and no local directory is needed. |
157
+ | `jira_download_attachment` | Download attachment | read-only | Downloads one attachment INTO THIS SERVER'S media directory and returns the local path — the bytes never pass through the conversation. |
158
+ | `jira_upload_attachment` | Upload attachment | write · standard | Attaches a file from this server's media directory to an issue. |
159
+
160
+ ### Watchers, votes & project setup — `collab`
161
+
162
+ The surface around an issue: who watches it, who voted for it, and the components and versions a project files work under — including cutting a release. Reversible writes only; nothing here deletes anything.
163
+
164
+ | Tool | Title | Access | What it does |
165
+ | --- | --- | --- | --- |
166
+ | `jira_list_watchers` | List watchers | read-only | Lists the accounts watching an issue, with the watch count and whether this server's own account is among them. |
167
+ | `jira_add_watcher` | Add watcher | write · standard | Makes an account watch an issue, so Jira notifies it of every change. Adding an account that already watches changes nothing. |
168
+ | `jira_remove_watcher` | Remove watcher | write · standard | Stops an account watching an issue — it no longer gets notifications. Nothing is deleted: the watch is a link, jira_add_watcher puts it back with the same accountId, and no issue content changes. |
169
+ | `jira_add_vote` | Vote for issue | write · standard | Casts THIS SERVER'S OWN vote for an issue. There is no way to vote on behalf of another account — the endpoint takes no accountId — so a request to record someone else's vote cannot be honoured. |
170
+ | `jira_remove_vote` | Withdraw vote | write · standard | Withdraws THIS SERVER'S OWN vote from an issue; other people's votes are untouched and unreachable. Nothing is deleted beyond the vote itself, and jira_add_vote casts it again. |
171
+ | `jira_list_components` | List components | read-only | Lists a project's components — the sub-areas an issue's `components` field points at — with their id, name, description, lead and default assignee rule. |
172
+ | `jira_create_component` | Create component | write · standard | Creates a component in a project — a sub-area issues can be filed under. Takes the project KEY (jira_create_version takes a numeric id instead; that asymmetry is Jira's). |
173
+ | `jira_update_component` | Update component | write · standard | Changes a component. This is a PARTIAL update, unlike jira_update_issue: only the fields you pass are changed and everything you omit keeps its current value. |
174
+ | `jira_list_versions` | List versions | read-only | Lists a project's versions (releases) — the values an issue's fixVersions and affectedVersions fields point at — with their id, name, dates and whether they are released or archived. |
175
+ | `jira_create_version` | Create version | write · standard | Creates a version (a release) in a project — a value issues can then use in fixVersions. Takes the NUMERIC projectId, not the key (jira_create_component takes a key; the asymmetry is Jira's). |
176
+ | `jira_update_version` | Update version | write · standard | Changes a version — this is how a release is cut (released: true) and how it is un-cut (released: false). |
177
+ | `jira_list_project_roles` | List project roles | read-only | Lists a project's roles (Administrators, Developers, …) with their ids, and — when you pass a roleId — the accounts and groups in that one role. |
178
+
179
+ ### Metadata & discovery — `meta`
180
+
181
+ Projects, fields, create metadata, statuses and link types — the reads that turn names into the ids every other package needs.
182
+
183
+ | Tool | Title | Access | What it does |
184
+ | --- | --- | --- | --- |
185
+ | `jira_list_projects` | List projects | read-only | Lists the Jira projects you can see — id, key, name, project type and lead. This is how a project NAME becomes the KEY every other tool wants. |
186
+ | `jira_get_project` | Get project | read-only | Reads one project in detail: description, lead, issue types, components and versions — what you need before creating an issue, because issue type ids and component/version names are per-project. |
187
+ | `jira_list_fields` | List fields | read-only | THE discovery tool for field ids: every field with id, name, schema type and the custom flag, so "Story Points" resolves to customfield_10016 and back. |
188
+ | `jira_get_create_meta` | Get create metadata | read-only | Reads what jira_create_issue accepts for a project. |
189
+ | `jira_list_statuses` | List statuses | read-only | Lists workflow statuses — id, name, category and scope — so JQL like status = "In Review" names a status that really exists on this site. |
190
+ | `jira_list_link_types` | List issue link types | read-only | Lists the issue link types configured on this site with their inward and outward phrases (for example "blocks" / "is blocked by"). |
191
+
192
+ ### User lookup — `users`
193
+
194
+ Finding people by name or email — the one path from a human name to the accountId every other tool requires.
195
+
196
+ | Tool | Title | Access | What it does |
197
+ | --- | --- | --- | --- |
198
+ | `jira_search_users` | Search users | read-only | Finds Jira users by display name or email and returns their accountId — the id every other tool takes, since Cloud has no usernames. |
199
+
200
+ ### Boards & sprints — `agile`
201
+
202
+ Jira Software boards, sprints and their issues, the two moves (into a sprint, out to the backlog) and the sprint lifecycle — the only tools that speak the Agile API rather than the platform one.
203
+
204
+ | Tool | Title | Access | What it does |
205
+ | --- | --- | --- | --- |
206
+ | `jira_list_boards` | List boards | read-only | Lists the Jira Software boards you can see — id, name, type and the project each belongs to. The board id is what jira_list_sprints takes, so this is the first call of any sprint workflow. |
207
+ | `jira_list_sprints` | List sprints | read-only | Lists a board's sprints with their id, name, state, goal and dates. Filter with state ("active" for the sprint in flight, "future" for the ones planned). |
208
+ | `jira_get_sprint_issues` | Get sprint issues | read-only | Lists the issues in one sprint, flattened exactly like jira_search: rich text as plain text, users as accountId + displayName. |
209
+ | `jira_move_to_sprint` | Move issues to sprint | write · standard | Moves up to 50 issues into a sprint — the only way to set a sprint, which is not an editable field on jira_update_issue. |
210
+ | `jira_move_to_backlog` | Move issues to backlog | write · standard | Sends up to 50 issues back to the backlog — Jira defines it as "remove the future and active sprints from these issues", so it is the inverse of jira_move_to_sprint and the only way to clear a sprint… |
211
+ | `jira_create_sprint` | Create sprint | write · standard | Creates a sprint on a Scrum board and returns its id. The sprint is created in the "future" state — this does NOT start it, jira_start_sprint does, and only a started sprint is the work in flight. |
212
+ | `jira_start_sprint` | Start sprint | write · standard | Starts a sprint: "future" becomes "active", which is what makes its issues the work in flight and what every board report measures from. |
213
+ | `jira_close_sprint` | Close sprint | write · standard | Completes the active sprint. |
214
+
215
+ <!-- GENERATED:TOOLS:END -->
216
+
217
+ ## Data handling
218
+
219
+ What leaves your machine, what is written to disk, what is redacted from logs,
220
+ and what the write gate does and does not promise are documented in
221
+ [`docs/THREAT-MODEL.md`](docs/THREAT-MODEL.md). Credential storage and lifecycle
222
+ are in [`docs/AUTH.md`](docs/AUTH.md).
223
+
224
+ To report a vulnerability, see [`SECURITY.md`](SECURITY.md).
225
+
226
+ ## Development
227
+
228
+ ```sh
229
+ npm install
230
+ npm run check # typecheck, lint, format, build, tarball, test, docs-lint, prod audit
231
+ ```
232
+
233
+ [`CONTRIBUTING.md`](CONTRIBUTING.md) is the contributor entry point: the
234
+ `npm run check` gate, the rules a PR cannot break, and how to point the server
235
+ at a real Jira site without endangering anyone's tenant.
236
+ [`docs/README.md`](docs/README.md) is the index to the specification and
237
+ [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) is the place to start; the test
238
+ taxonomy and the coverage gate are in [`docs/TESTING.md`](docs/TESTING.md).
239
+ Participation is governed by the [Code of Conduct](CODE_OF_CONDUCT.md).
240
+
241
+ ## License
242
+
243
+ [MIT](LICENSE).
244
+
245
+ Jira and Atlassian are trademarks of Atlassian Pty Ltd. This project is an
246
+ independent, unofficial client and is not affiliated with or endorsed by
247
+ Atlassian.
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ // CommonJS launcher whose only job is the Node version guard. It must stay
3
+ // parseable by ancient Node (no arrow functions, no `?.`/`??`, no ESM syntax):
4
+ // the real entry is an ESM graph that old Node fails to *parse*, so a guard
5
+ // living inside it could never run. `import()` is deliberately the one modern
6
+ // construct here — it has been parseable since Node 12, long before the
7
+ // runtimes this guard exists to warn about.
8
+ //
9
+ // Failure sets `process.exitCode` and returns; it never calls `process.exit()`.
10
+ // Every failure here is a diagnostic on stderr, and under an MCP client stderr
11
+ // is a pipe: `process.exit()` tears the process down with the write still in the
12
+ // pipe buffer, which silently truncates the message at 64 KiB (measured). The
13
+ // same rule governs `src/index.ts` (CC-91), and for the same reason — the one
14
+ // sentence explaining why the server did not start is the whole value of these
15
+ // paths, so it must survive.
16
+ 'use strict';
17
+
18
+ var major = parseInt(process.versions.node.split('.')[0], 10);
19
+ if (major < 22) {
20
+ console.error(
21
+ 'jira-mcp-ai requires Node.js >= 22, but this is ' +
22
+ process.versions.node +
23
+ '.\nNode 22 is the floor because the server loads env files with ' +
24
+ 'process.loadEnvFile() instead of dotenv.\nUse a newer runtime, e.g.: nvm install 24 && nvm use 24',
25
+ );
26
+ process.exitCode = 1;
27
+ } else {
28
+ // Importing the entry module is not enough to start it: its self-run guard
29
+ // compares process.argv[1] against its own path, and here argv[1] is *this*
30
+ // shim (ARCHITECTURE.md §Dependency injection boundary). So call the exported
31
+ // entry point explicitly, or the binary exits 0 having done nothing at all.
32
+ import('../build/index.js')
33
+ .then(function (mod) {
34
+ var run = mod && mod.main;
35
+ if (typeof run !== 'function') {
36
+ console.error(
37
+ 'jira-mcp-ai: build/index.js exports no main() entry point. ' +
38
+ 'From a source checkout, run `npm run build` first.',
39
+ );
40
+ process.exitCode = 1;
41
+ return undefined;
42
+ }
43
+ return run();
44
+ })
45
+ .catch(function (err) {
46
+ console.error(
47
+ 'jira-mcp-ai: the compiled server could not be started. From a source ' +
48
+ 'checkout, run `npm run build` first.\n' +
49
+ (err && err.stack ? err.stack : String(err)),
50
+ );
51
+ process.exitCode = 1;
52
+ });
53
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * A minimal ADF node. The real schema is far larger; we read a subset and emit
3
+ * a smaller one, so unknown keys are tolerated rather than modelled.
4
+ */
5
+ export interface AdfNode {
6
+ type: string;
7
+ version?: number;
8
+ text?: string;
9
+ content?: AdfNode[];
10
+ attrs?: Record<string, unknown>;
11
+ [key: string]: unknown;
12
+ }
13
+ /** A whole ADF document. Jira accepts `version: 1` only (JIRA-API.md §ADF). */
14
+ export interface AdfDoc extends AdfNode {
15
+ type: 'doc';
16
+ version: 1;
17
+ content: AdfNode[];
18
+ }
19
+ /**
20
+ * Hard recursion cap. ADF from a human tops out around a dozen levels; anything
21
+ * past this is either a generated document or an attack on the stack, and a
22
+ * `RangeError` from a blown stack would take the whole tool call with it.
23
+ * Reaching the cap emits {@link DEPTH_LIMIT_MARKER} and stops descending, so a
24
+ * deep tree degrades gracefully instead of failing. This also terminates a
25
+ * cyclic tree, which no shape check can rule out on wire data.
26
+ */
27
+ export declare const MAX_NODE_DEPTH = 64;
28
+ /**
29
+ * Indentation cap for nested lists. Past six levels the leading whitespace
30
+ * stops growing: further nesting still renders, it just shares the sixth
31
+ * level's indent. Unbounded indentation eats the result budget (CC-25) for a
32
+ * distinction no reader makes.
33
+ */
34
+ export declare const MAX_LIST_INDENT_DEPTH = 6;
35
+ /** What a subtree past {@link MAX_NODE_DEPTH} collapses to. */
36
+ export declare const DEPTH_LIMIT_MARKER = "[\u2026]";
37
+ /**
38
+ * Flatten an ADF document (or any subtree, or `null`) to readable plain text.
39
+ * Never throws: unknown nodes, malformed attrs, cycles and pathological depth
40
+ * all degrade (CC-06, CC-09). An empty or absent document is `''`, never
41
+ * `"undefined"` (CC-08).
42
+ *
43
+ * Delta over the donor: the result is right-trimmed, so a one-paragraph
44
+ * description is `'hello'` and not `'hello\n'`.
45
+ */
46
+ export declare function adfToText(node: unknown): string;
47
+ /**
48
+ * Flatten an ADF document to the markdown subset instead of plain text. Same
49
+ * guarantees as {@link adfToText} — never throws, same caps, same degradation
50
+ * for everything outside the subset — and the same right-trim, so a
51
+ * one-paragraph description is `'hello'`.
52
+ *
53
+ * This is opt-in per call (`format: 'markdown'`); plain text stays the default
54
+ * so existing clients keep byte-identical output.
55
+ */
56
+ export declare function adfToMarkdown(node: unknown): string;
57
+ /**
58
+ * Replace every ADF document inside an arbitrary JSON value with its rendered
59
+ * string, non-mutatingly. This is how the tool ring re-renders a payload it
60
+ * fetched raw: the *set* of fields that gets rendered is exactly the set
61
+ * {@link isAdfDoc} recognises, which is the same set the plain-text shaping
62
+ * path flattens, so `format: 'markdown'` cannot render a field that
63
+ * `format: 'text'` would have left alone.
64
+ */
65
+ export declare function renderAdfDocs<T>(value: T, render: (node: unknown) => string): T;
66
+ /**
67
+ * Build a minimal ADF document from plain text (CC-10):
68
+ * CRLF is normalised, a blank line starts a new paragraph, a single newline
69
+ * inside a paragraph becomes a `hardBreak`, and leading/trailing blank
70
+ * paragraphs are trimmed. The version is pinned — Jira accepts `1` only.
71
+ *
72
+ * Blank input yields a doc with an empty `content` array, which is how a
73
+ * rich-text field is cleared. Tools that require a non-empty body (a comment)
74
+ * validate their input; that is not this builder's job.
75
+ */
76
+ export declare function adfFromText(text: string): AdfDoc;
77
+ /**
78
+ * Parse the markdown subset into an ADF document. The inverse of
79
+ * {@link adfToMarkdown} for everything the subset covers; anything else — block
80
+ * quotes, tables, images, reference links, setext headings, HTML — degrades to
81
+ * the paragraph text it was written as, never to an exception.
82
+ *
83
+ * CC-10 parity with {@link adfFromText} is deliberate: CRLF is normalised, a
84
+ * blank line starts a new paragraph, a single newline inside a paragraph is a
85
+ * `hardBreak`, and leading/trailing blank paragraphs are trimmed.
86
+ */
87
+ export declare function adfFromMarkdown(text: string): AdfDoc;
88
+ /** Structural guard: is this value an ADF document rather than a scalar field? */
89
+ export declare function isAdfDoc(value: unknown): value is AdfDoc;
90
+ /**
91
+ * Accept either plain text or a caller-supplied ADF document for a rich-text
92
+ * field (TOOLS.md: `description` takes text or raw ADF, and either one replaces
93
+ * the whole field — CC-31). A string is built; a document is normalised to the
94
+ * pinned `{ type: 'doc', version: 1 }` header and otherwise passed through.
95
+ * Anything else is a `validation` error, not a silent coercion.
96
+ */
97
+ export declare function toAdf(body: string | AdfNode): AdfDoc;