@stubbedev/atlassian-mcp 0.5.15 → 0.5.17

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.
package/README.md CHANGED
@@ -28,7 +28,7 @@ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for **s
28
28
  |---|---|
29
29
  | `jira_search` | Discover resources: `issues`, `projects`, `issue_types`, `boards`, `sprints`, `board_overview`, `versions`, `components`, `fields`, or `users` via `resource` param |
30
30
  | `jira_get` | Full details for one issue: summary, description, status, sprint, transitions, comments, and attachment list |
31
- | `jira_mutate` | Create, update, transition, comment (`commentAction`: `add` / `update` / `delete`), upload local files as attachments, link, add to sprint, log work, change issue type, set any custom field by name (`create.customFields` / `update.customFields`), or manage a fix version (`version.action`: `create` / `update` / `release` / `archive` / `delete`) — several in one call. Markdown in any text field is converted to Jira wiki markup |
31
+ | `jira_mutate` | Create, update, transition, comment (`commentAction`: `add` / `update` / `delete`), upload attachments from a local path, URL, or `data:` URI, link, add to sprint, log work, change issue type, set any custom field by name (`create.customFields` / `update.customFields`), or manage a fix version (`version.action`: `create` / `update` / `release` / `archive` / `delete`) — several in one call. Markdown in any text field is converted to Jira wiki markup |
32
32
 
33
33
  ### Bitbucket
34
34
 
@@ -36,8 +36,8 @@ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for **s
36
36
  |---|---|
37
37
  | `bitbucket_search` | Discover resources: `pull_requests` (default), `repos`, `branches`, or `users` via `resource` param; `mine=true` for your inbox, narrowed with `role=author` / `reviewer` / `participant` |
38
38
  | `bitbucket_get_pr` | Full PR details: metadata, commits, comments, blockers, build status, optional diff, and any attachments referenced from the description or comments |
39
- | `bitbucket_mutate` | Create/update a PR, or perform lifecycle actions: `approve`, `unapprove`, `needs_work`, `merge`, `decline`. Reviewer names are verified against Bitbucket, and an update that would drop existing reviewers needs `update.replaceReviewers=true`. `create.attachments` / `update.attachments` upload local files (screenshots, logs) to the repo and reference them from the description |
40
- | `bitbucket_comment` | Add, update, or delete a PR comment; for code changes use `suggestion` so Bitbucket shows Apply suggestion. Enforced here: one reply per thread, no new top-level comment on your own PR (`asAuthor=true` to override), `#123` references rewritten as links. `pending=true` posts an unpublished draft-review comment. `attachments` uploads local files and references them from the comment |
39
+ | `bitbucket_mutate` | Create/update a PR, or perform lifecycle actions: `approve`, `unapprove`, `needs_work`, `merge`, `decline`. Reviewer names are verified against Bitbucket, and an update that would drop existing reviewers needs `update.replaceReviewers=true`. `create.attachments` / `update.attachments` upload files (local path, URL, or `data:` URI) to the repo and reference them from the description |
40
+ | `bitbucket_comment` | Add, update, or delete a PR comment; for code changes use `suggestion` so Bitbucket shows Apply suggestion. Enforced here: one reply per thread, no new top-level comment on your own PR (`asAuthor=true` to override), `#123` references rewritten as links. `pending=true` posts an unpublished draft-review comment. `attachments` uploads files (local path, URL, or `data:` URI) and references them from the comment |
41
41
  | `bitbucket_get_file` | Raw file content at a branch, tag, or commit — or pass `prId` to read the PR source branch. Every response names the path and ref it came from, and pages via `maxChars`/`charOffset` |
42
42
  | `bitbucket_pr_tasks` | Manage PR tasks (checklist items): `list`, `create`, `resolve`, `reopen`, `delete` |
43
43
 
@@ -46,6 +46,7 @@ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for **s
46
46
  | Tool | Description |
47
47
  |---|---|
48
48
  | `get_attachment` | Fetch an attachment by ID from Jira (`source=jira`, IDs from `jira_get`) or Bitbucket (`source=bitbucket`, IDs from `bitbucket_get_pr`). Images, videos, animated images (GIF/APNG/animated WebP), audio, and PDFs are decoded inline so the model can see/hear them; text/JSON inline. Oversized or non-renderable attachments are auto-saved to a temp file and the path is returned. `saveTo=/absolute/path` streams the original to disk |
49
+ | `attach_files` | Open an upload panel in the chat so the user can pick, drag or **paste** files onto a Jira issue or Bitbucket PR. The panel runs in the host (MCP Apps) and uploads directly, so it works where this server cannot read the filesystem — notably sandboxed Claude Desktop extensions. Degrades to a text answer on hosts without MCP Apps |
49
50
 
50
51
  ### Resources
51
52
 
@@ -117,6 +118,12 @@ cannot get them wrong, and they need no prompting:
117
118
  so reading the wrong branch is visible rather than silent.
118
119
  - **Tool annotations** (`readOnlyHint`, `destructiveHint`, `idempotentHint`) are
119
120
  published for every tool, so hosts can gate confirmation on metadata.
121
+ - **AI-written text is attributed.** Every Jira ticket description, Jira comment
122
+ (including worklog comments) and Bitbucket PR description / PR comment posted
123
+ through this server ends with a bold `[AI]` line, added after markdown→wiki
124
+ conversion so it never lands inside a code block, and skipped rather than
125
+ mangled when the text ends inside an open code fence. Disable with
126
+ `"markAIText": false` in the config file or `ATLASSIAN_MCP_MARK_AI_TEXT=false`.
120
127
 
121
128
  ## Setup
122
129
 
@@ -522,6 +529,74 @@ Client config for an already-running HTTP server (Claude Code example):
522
529
  claude mcp add --transport http atlassian http://127.0.0.1:7337/mcp
523
530
  ```
524
531
 
532
+ ### Attachment upload sources
533
+
534
+ Every `attachments` argument — `jira_mutate`, `bitbucket_mutate` (`create`/`update`) and
535
+ `bitbucket_comment` — takes *sources*, not only paths. Each entry is one of:
536
+
537
+ | Source | Example | Notes |
538
+ | --- | --- | --- |
539
+ | Local file path | `/tmp/shot.png`, `file:///tmp/shot.png` | Requires the server to share a filesystem with the file |
540
+ | `http(s)` URL | `https://example.com/diagram.png` | Downloaded by the server. Filename comes from `Content-Disposition`, else the URL's last path segment, with an extension filled in from `Content-Type` |
541
+ | `data:` URI | `data:text/plain;name=run.log;base64,Ym9vbQ==` | Bytes inlined in the argument. `;name=` titles the file; without it the name is derived from the media type |
542
+
543
+ A URL whose host matches the configured Jira or Bitbucket instance is fetched **with your token**,
544
+ so an attachment that already lives on one service can be re-attached to the other by URL. Tokens
545
+ are never sent to any other host. Downloads are capped at 250 MB.
546
+
547
+ For Bitbucket, a source that already appears in the description or comment text is replaced in place
548
+ by the attachment markup — that works for URLs too, so writing `![the widget](https://…/shot.png)`
549
+ and passing the same URL in `attachments` keeps your caption and swaps in the uploaded file.
550
+
551
+ **Sandboxed hosts.** Claude Desktop runs extensions in an OS sandbox that blocks arbitrary
552
+ filesystem paths but still allows outbound HTTPS, so local paths generally fail there while URL and
553
+ `data:` sources work. The error for an unreadable path says so. This also applies to chaining with
554
+ another MCP server: a screenshot another server saved to *its* temp directory is not reachable
555
+ from this one under a sandbox, though it is when both run unsandboxed (as in Claude Code).
556
+
557
+ > Files a user attaches to the chat itself still cannot be uploaded from these arguments: MCP has
558
+ > no mechanism for passing conversation attachments to a server, and the model cannot re-emit image
559
+ > bytes it was shown. Use [`attach_files`](#the-upload-panel-mcp-apps) — the user re-picks or pastes
560
+ > the file into a panel that uploads it directly — or give this server a URL or a readable path.
561
+
562
+ ### The upload panel (MCP Apps)
563
+
564
+ `attach_files` opens a file picker **inside the chat**. It exists because neither of the
565
+ other routes works everywhere:
566
+
567
+ - a local path needs this server to share a filesystem with the file, which a sandboxed
568
+ host (Claude Desktop extensions) does not allow;
569
+ - a file the user drops into the conversation never reaches an MCP server at all — the
570
+ protocol has no client-to-server file transfer, and the model cannot re-emit bytes it
571
+ was shown. [SEP-2631](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/2631)
572
+ would add one; it is still a draft.
573
+
574
+ [MCP Apps](https://github.com/modelcontextprotocol/ext-apps) routes around both. The tool
575
+ declares a `ui://` resource, the host renders it as an iframe, and that iframe is an
576
+ ordinary browser: its file picker, drop target and paste handler sit outside the server's
577
+ sandbox. It hands the bytes back as `data:` URIs through `tools/call`, which is the same
578
+ source format the `attachments` arguments take.
579
+
580
+ ```
581
+ attach_files { issueKey: "KON-123" } → panel renders in the chat
582
+ user picks / drops / pastes → FileReader
583
+ panel calls attach_files { files: [...] } → data: URI → upload
584
+ ```
585
+
586
+ The model passes only the target (`issueKey`, or `prId` with an optional `commentId`); it
587
+ never fills in `files` and never sees the file contents. Files are capped at 12 MB each,
588
+ since the bytes ride the JSON-RPC channel as base64 — anything larger should go up by URL.
589
+
590
+ Support is negotiated: hosts advertise MCP Apps through the `io.modelcontextprotocol/ui`
591
+ extension at `initialize`. Where it is absent — **Claude Code today** — `attach_files`
592
+ returns a plain instruction to use a path or URL instead, and every other tool behaves
593
+ exactly as before.
594
+
595
+ The panel is one self-contained HTML document, because the host serves it under
596
+ `default-src 'none'`: no CDN, no second resource, no build step. The MCP Apps browser SDK
597
+ is vendored into `ui/vendor/ext-apps.js` by `scripts/vendor-ext-apps.sh` (re-run it to
598
+ bump the version) and spliced into the page when the resource is read.
599
+
525
600
  ### Attachment decoding pipeline
526
601
 
527
602
  The `get_attachment` tool decodes binary attachments into model-readable content before returning them:
@@ -559,6 +634,7 @@ pure-Go implementation shell out to external binaries:
559
634
  | `ATLASSIAN_MCP_HTTP_TOKEN` | Bearer token for HTTP mode. Optional on loopback binds; **required** on non-loopback binds. | unset |
560
635
  | `ATLASSIAN_MCP_REPO_ROOT` | Default workspace root(s) for the git/PR tools, comma-separated. `file://` URIs, absolute paths, `~/…` and Windows drive paths all work. Needed by clients that expose no MCP roots (desktop apps). Overridden by a `repoPath` argument or a root header. | unset |
561
636
  | `ATLASSIAN_MCP_GIT_PATH` | Path to the `git` executable. Only needed when `git` is off the host app's `PATH`; the server already probes the usual install locations. | `git` on `PATH` |
637
+ | `ATLASSIAN_MCP_MARK_AI_TEXT` | Set `false` to stop appending the bold `[AI]` attribution line to Jira/Bitbucket text this server posts. | `true` |
562
638
  | `ATLASSIAN_MCP_FFMPEG_PATH` | Path to `ffmpeg` binary. | npm: bundled `ffmpeg-static`; otherwise `ffmpeg` on `PATH` |
563
639
  | `ATLASSIAN_MCP_FFPROBE_PATH` | Path to `ffprobe` binary. | npm: bundled `ffprobe-static`; otherwise `ffprobe` on `PATH` |
564
640
  | `ATLASSIAN_MCP_TMP_TTL_DAYS` | Auto-saved attachments older than this are pruned. | `7` |
@@ -42,6 +42,11 @@
42
42
  "description": "Personal Access Token (PAT) for Bitbucket Server authentication"
43
43
  }
44
44
  }
45
+ },
46
+ "markAIText": {
47
+ "type": "boolean",
48
+ "default": true,
49
+ "description": "Append a bold [AI] attribution line to Jira ticket descriptions/comments and Bitbucket PR descriptions/comments posted through this server, so readers can tell AI writing from yours. Default true."
45
50
  }
46
51
  }
47
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stubbedev/atlassian-mcp",
3
- "version": "0.5.15",
3
+ "version": "0.5.17",
4
4
  "description": "MCP server for self-hosted Jira and Bitbucket (Go, distributed as a prebuilt binary)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -18,7 +18,7 @@
18
18
  "postinstall": "node scripts/postinstall.mjs",
19
19
  "build": "go build -o atlassian-mcp .",
20
20
  "test": "go test ./...",
21
- "smoke": "go build -o atlassian-mcp . && printf '%s\\n' '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}}' | ATLASSIAN_MCP_CONFIG=/nonexistent JIRA_URL=https://github.com JIRA_ACCESS_TOKEN=smoke BITBUCKET_URL=https://github.com BITBUCKET_ACCESS_TOKEN=smoke ./atlassian-mcp | node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const r=JSON.parse(d.trim());if(r.error)throw new Error('server error: '+r.error.message);const t=r.result?.tools;if(!Array.isArray(t)||!t.length)throw new Error('no tools in response');const bad=t.filter(x=>!x.name||!x.inputSchema);if(bad.length)throw new Error('malformed tools: '+bad.map(x=>x.name??'(unnamed)').join(', '));console.log('smoke OK — '+t.length+' tool(s): '+t.map(x=>x.name).join(', '))}catch(e){console.error('smoke FAIL:',e.message);process.exit(1)}})\"",
21
+ "smoke": "go build -o atlassian-mcp . && { printf '%s\\n' '{\"jsonrpc\":\"2.0\",\"id\":0,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{},\"clientInfo\":{\"name\":\"npm-smoke\",\"version\":\"0\"}}}' '{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}' '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}}'; sleep 3; } | ATLASSIAN_MCP_CONFIG=/nonexistent JIRA_URL=https://github.com JIRA_ACCESS_TOKEN=smoke BITBUCKET_URL=https://github.com BITBUCKET_ACCESS_TOKEN=smoke ./atlassian-mcp | node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const rs=d.trim().split('\\n').filter(Boolean).map(l=>JSON.parse(l));const err=rs.find(r=>r.error);if(err)throw new Error('server error: '+err.error.message);const t=rs.find(r=>r.id===1)?.result?.tools;if(!Array.isArray(t)||!t.length)throw new Error('no tools in response');const bad=t.filter(x=>!x.name||!x.inputSchema);if(bad.length)throw new Error('malformed tools: '+bad.map(x=>x.name??'(unnamed)').join(', '));console.log('smoke OK — '+t.length+' tool(s): '+t.map(x=>x.name).join(', '))}catch(e){console.error('smoke FAIL:',e.message);process.exit(1)}})\"",
22
22
  "preversion": "go vet ./... && npm run test && npm run smoke",
23
23
  "release:patch": "npm version patch && git push origin HEAD --follow-tags",
24
24
  "release:minor": "npm version minor && git push origin HEAD --follow-tags",