@stubbedev/atlassian-mcp 0.4.5 → 0.5.1
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 +96 -42
- package/bin/cli.mjs +59 -0
- package/package.json +17 -30
- package/scripts/download.mjs +86 -0
- package/scripts/postinstall.mjs +15 -0
- package/dist/attachment.js +0 -350
- package/dist/bitbucket.js +0 -1340
- package/dist/config.js +0 -62
- package/dist/context.js +0 -162
- package/dist/git.js +0 -227
- package/dist/index.js +0 -1055
- package/dist/jira.js +0 -979
- package/dist/video.js +0 -211
package/README.md
CHANGED
|
@@ -38,10 +38,10 @@ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for **s
|
|
|
38
38
|
|
|
39
39
|
| Tool | Description |
|
|
40
40
|
|---|---|
|
|
41
|
-
| `bitbucket_search` | Discover resources: `pull_requests` (default), `repos`, or `
|
|
41
|
+
| `bitbucket_search` | Discover resources: `pull_requests` (default), `repos`, `branches`, or `users` via `resource` param; `mine=true` for your inbox |
|
|
42
42
|
| `bitbucket_get_pr` | Full PR details: metadata, commits, comments, blockers, build status, optional diff, and any attachments referenced from the description or comments |
|
|
43
43
|
| `bitbucket_get_attachment` | Fetch a repo attachment by ID. Same decoding pipeline as `jira_get_attachment` (images, videos, animated images, audio, PDFs). Oversized or non-renderable attachments are auto-saved to a temp file and the path is returned; `saveTo` streams the original to disk |
|
|
44
|
-
| `bitbucket_mutate` | Create/update a PR, or perform lifecycle actions: `approve`, `unapprove`, `merge`, `decline` |
|
|
44
|
+
| `bitbucket_mutate` | Create/update a PR, or perform lifecycle actions: `approve`, `unapprove`, `needs_work`, `merge`, `decline` |
|
|
45
45
|
| `bitbucket_comment` | Add, update, or delete a PR comment; for code changes use `suggestion` so Bitbucket shows Apply suggestion (no trailing text after a suggestion block) |
|
|
46
46
|
| `bitbucket_get_file` | Raw file content from Bitbucket at a branch, tag, or commit |
|
|
47
47
|
| `bitbucket_pr_tasks` | Manage PR tasks (checklist items): `list`, `create`, `resolve`, `reopen`, `delete` |
|
|
@@ -114,7 +114,7 @@ BITBUCKET_URL=https://bitbucket.example.com
|
|
|
114
114
|
BITBUCKET_ACCESS_TOKEN=your-bitbucket-personal-access-token
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
-
Config is resolved in this order: `--config <path>` CLI arg → `ATLASSIAN_MCP_CONFIG` env var → `~/.atlassian-mcp.json` → `.atlassian-mcp.json` in cwd → environment variables.
|
|
117
|
+
Config is resolved in this order: `--config <path>` CLI arg → `ATLASSIAN_MCP_CONFIG` env var → `~/.atlassian-mcp.json` → `$XDG_CONFIG_HOME/atlassian-mcp/config.json` (default `~/.config/atlassian-mcp/config.json`) → `.atlassian-mcp.json` in cwd → environment variables.
|
|
118
118
|
|
|
119
119
|
### 2. Connect to your AI tool
|
|
120
120
|
|
|
@@ -236,17 +236,65 @@ Then restart your MCP client.
|
|
|
236
236
|
|
|
237
237
|
---
|
|
238
238
|
|
|
239
|
-
###
|
|
239
|
+
### Install without npm
|
|
240
240
|
|
|
241
|
-
|
|
241
|
+
The server is a single static Go binary. The `npx` path above downloads the prebuilt
|
|
242
|
+
binary for your platform on first run; these alternatives skip Node entirely:
|
|
242
243
|
|
|
243
244
|
```bash
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
# Go toolchain — installs to $GOBIN / $GOPATH/bin
|
|
246
|
+
go install github.com/stubbedev/atlassian-mcp@latest
|
|
247
|
+
|
|
248
|
+
# Nix flake
|
|
249
|
+
nix run github:stubbedev/atlassian-mcp -- --config ~/.atlassian-mcp.json
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Then point your MCP client's `command` at the resulting `atlassian-mcp` binary
|
|
253
|
+
instead of `npx`. On these paths `ffmpeg`/`ffprobe` must be available on `PATH`
|
|
254
|
+
(or set `ATLASSIAN_MCP_FFMPEG_PATH` / `ATLASSIAN_MCP_FFPROBE_PATH`); the npm
|
|
255
|
+
wrapper bundles them automatically.
|
|
256
|
+
|
|
257
|
+
### Running as an HTTP server (shared / behind a proxy)
|
|
258
|
+
|
|
259
|
+
By default the server speaks MCP over **stdio** (one process per client, launched
|
|
260
|
+
by your editor). It can instead run as a long-lived **Streamable HTTP** server that
|
|
261
|
+
many clients share — useful behind a reverse proxy:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
atlassian-mcp --http # binds 127.0.0.1:7337
|
|
265
|
+
atlassian-mcp --http 127.0.0.1:9000 # custom address
|
|
266
|
+
ATLASSIAN_MCP_HTTP=1 atlassian-mcp # same, via env
|
|
247
267
|
```
|
|
248
268
|
|
|
249
|
-
|
|
269
|
+
- Single endpoint `POST /mcp` (JSON-RPC) plus an optional `GET /mcp` SSE stream that
|
|
270
|
+
carries server→client requests (`roots/list`, elicitation). The server is **stateful**:
|
|
271
|
+
`initialize` mints a session and returns an `Mcp-Session-Id` header, which the client
|
|
272
|
+
**must** echo on every subsequent request and on the SSE stream. Requests with a
|
|
273
|
+
missing/unknown/expired session id get **HTTP 404** so the client re-initializes
|
|
274
|
+
(standard MCP-client behaviour). Each connected client/worktree is an isolated session.
|
|
275
|
+
- **Auth:** on a loopback bind no token is needed. Binding a non-loopback address
|
|
276
|
+
**requires** `ATLASSIAN_MCP_HTTP_TOKEN` (sent by clients as `Authorization: Bearer …`);
|
|
277
|
+
the server refuses to start otherwise. Terminate TLS at your proxy.
|
|
278
|
+
- **`GET /healthz`** is an unauthenticated liveness probe (returns `ok`) for proxies/load
|
|
279
|
+
balancers. Idle sessions are evicted after 1h.
|
|
280
|
+
|
|
281
|
+
**Repo context comes from the client, not the server's working directory.** Tools that
|
|
282
|
+
need a repo (the `git_*` tools, `get_dev_context`, `start_work`, `complete_work`, and
|
|
283
|
+
Bitbucket project/repo auto-detection) resolve it in this order: an explicit `repoPath`
|
|
284
|
+
argument → the client's **MCP workspace roots** (the server asks via `roots/list`, caches
|
|
285
|
+
per session, and refreshes on `notifications/roots/list_changed`) → the process cwd (stdio
|
|
286
|
+
only). So one shared HTTP server handles many worktrees: each client's own workspace drives
|
|
287
|
+
its calls. When a session exposes **several** roots (multiple worktrees), a tool with no
|
|
288
|
+
`repoPath` uses the first git-repo root; pass `repoPath` (an absolute path, or a worktree
|
|
289
|
+
name/basename that matches one of the roots) to target a specific worktree. For Bitbucket,
|
|
290
|
+
passing `projectKey`+`repoSlug` explicitly skips repo detection entirely. The repos must be
|
|
291
|
+
reachable on the server's host (the git tools run `git` locally).
|
|
292
|
+
|
|
293
|
+
Client config for an already-running HTTP server (Claude Code example):
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
claude mcp add --transport http atlassian http://127.0.0.1:7337/mcp
|
|
297
|
+
```
|
|
250
298
|
|
|
251
299
|
### Attachment decoding pipeline
|
|
252
300
|
|
|
@@ -254,28 +302,37 @@ The attachment tools (`jira_get_attachment`, `bitbucket_get_attachment`) decode
|
|
|
254
302
|
|
|
255
303
|
| Input | What gets returned | How |
|
|
256
304
|
| --- | --- | --- |
|
|
257
|
-
| Static images (PNG/JPEG/WebP/
|
|
258
|
-
| Animated images (GIF/APNG/animated WebP) | N sampled frames as image content blocks | `ffmpeg
|
|
259
|
-
| Video (mp4/webm/mov/…) | N sampled frames as image content blocks | `ffmpeg
|
|
305
|
+
| Static images (PNG/JPEG/WebP/BMP/TIFF/GIF/SVG…) | Resized image content blocks | native Go (`imaging`, long edge ≤ `maxDimension`, default 1568; EXIF auto-orient; PNG for alpha, else JPEG) |
|
|
306
|
+
| Animated images (GIF/APNG/animated WebP) | N sampled frames as image content blocks | `ffmpeg` + native Go re-encode (default 6 frames @ 768 px) |
|
|
307
|
+
| Video (mp4/webm/mov/…) | N sampled frames as image content blocks | `ffmpeg`/`ffprobe`. Uniform or scene-change sampling. Re-call with `start`, `end`, `frames`, `mode`, `sceneThreshold` to zoom in |
|
|
260
308
|
| Audio (mp3/wav/ogg/…) | MCP audio content block | passthrough |
|
|
261
|
-
| PDFs | Extracted text — or rasterized pages if text is empty (scanned PDFs) | `
|
|
309
|
+
| PDFs | Extracted text — or rasterized pages if text is empty (scanned PDFs) | native Go text extraction (`ledongthuc/pdf`); rasterization shells to `pdftoppm`/`mutool` if present, else the original is saved to disk |
|
|
262
310
|
| Text-like (json/xml/yaml/…) | Text content block | passthrough |
|
|
263
|
-
| Everything else (or oversized) | Auto-saved to a temp file; path is returned | `os.
|
|
311
|
+
| Everything else (or oversized) | Auto-saved to a temp file; path is returned | `os.TempDir()` with `atlmcp-` prefix |
|
|
264
312
|
|
|
265
313
|
Auto-saved files are periodically pruned by TTL and total-size quota — see *Environment overrides* below.
|
|
266
314
|
|
|
267
|
-
###
|
|
315
|
+
### External tools (optional)
|
|
268
316
|
|
|
269
|
-
|
|
270
|
-
-
|
|
271
|
-
|
|
317
|
+
Image and PDF-text decoding are pure Go and need nothing extra. The two pipelines that have no
|
|
318
|
+
pure-Go implementation shell out to external binaries:
|
|
319
|
+
|
|
320
|
+
- **`ffmpeg` + `ffprobe`** — video and animated-image frame sampling. The npm wrapper bundles
|
|
321
|
+
[`ffmpeg-static`](https://www.npmjs.com/package/ffmpeg-static) /
|
|
322
|
+
[`ffprobe-static`](https://www.npmjs.com/package/ffprobe-static) and injects their paths, so the
|
|
323
|
+
npx install path is zero-config. On the `go install` / Nix paths, install `ffmpeg` (it provides
|
|
324
|
+
`ffprobe`) or set the env vars below.
|
|
325
|
+
- **`pdftoppm` (poppler) or `mutool` (MuPDF)** — only needed to rasterize *scanned* PDFs that have no
|
|
326
|
+
extractable text. If neither is on `PATH`, such PDFs are saved to disk instead.
|
|
272
327
|
|
|
273
328
|
### Environment overrides
|
|
274
329
|
|
|
275
330
|
| Variable | Purpose | Default |
|
|
276
331
|
| --- | --- | --- |
|
|
277
|
-
| `
|
|
278
|
-
| `
|
|
332
|
+
| `ATLASSIAN_MCP_HTTP` | Run as a Streamable HTTP server instead of stdio. `1`/`true` → `127.0.0.1:7337`; or set an explicit `host:port`. Same as `--http`. | unset (stdio) |
|
|
333
|
+
| `ATLASSIAN_MCP_HTTP_TOKEN` | Bearer token for HTTP mode. Optional on loopback binds; **required** on non-loopback binds. | unset |
|
|
334
|
+
| `ATLASSIAN_MCP_FFMPEG_PATH` | Path to `ffmpeg` binary. | npm: bundled `ffmpeg-static`; otherwise `ffmpeg` on `PATH` |
|
|
335
|
+
| `ATLASSIAN_MCP_FFPROBE_PATH` | Path to `ffprobe` binary. | npm: bundled `ffprobe-static`; otherwise `ffprobe` on `PATH` |
|
|
279
336
|
| `ATLASSIAN_MCP_TMP_TTL_DAYS` | Auto-saved attachments older than this are pruned. | `7` |
|
|
280
337
|
| `ATLASSIAN_MCP_TMP_MAX_BYTES` | Total-size quota for auto-saved attachments in `os.tmpdir()`. When exceeded, oldest are evicted. | `1073741824` (1 GB) |
|
|
281
338
|
|
|
@@ -287,23 +344,20 @@ This package is published to npm as `@stubbedev/atlassian-mcp`.
|
|
|
287
344
|
|
|
288
345
|
Use semantic versioning for releases. Breaking tool-surface changes should bump the minor version while `<1.0.0` (for example `0.0.x` -> `0.1.0`).
|
|
289
346
|
|
|
290
|
-
|
|
347
|
+
On a pushed `v*` tag, `.github/workflows/publish.yml` cross-compiles the Go binary for 14
|
|
348
|
+
OS/arch targets, attaches them to a GitHub release, and publishes the npm wrapper (which
|
|
349
|
+
downloads the matching binary on install).
|
|
291
350
|
|
|
292
351
|
Release flow:
|
|
293
352
|
|
|
294
353
|
```bash
|
|
295
|
-
# choose one: patch | minor | major
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
# bumps package.json + package-lock.json,
|
|
299
|
-
# creates a version commit, and creates a git tag (for example v0.1.17)
|
|
300
|
-
npm version "$increment"
|
|
301
|
-
|
|
302
|
-
# push commit and tag to GitHub
|
|
354
|
+
# choose one: patch | minor | major (also: npm run release:patch / :minor / :major)
|
|
355
|
+
npm version patch # bumps package.json, commits, tags vX.Y.Z
|
|
303
356
|
git push origin HEAD --follow-tags
|
|
304
357
|
```
|
|
305
358
|
|
|
306
|
-
|
|
359
|
+
`flake.nix` reads its version from `package.json`, so the Nix package tracks the same bump
|
|
360
|
+
automatically. GitHub Actions builds + publishes from the pushed tag.
|
|
307
361
|
|
|
308
362
|
- The workflow is configured for npm Trusted Publisher (OIDC), so no `NPM_TOKEN` secret is required
|
|
309
363
|
|
|
@@ -351,22 +405,22 @@ Paste the token as the `token` value under `bitbucket` in your config file.
|
|
|
351
405
|
|
|
352
406
|
## Development
|
|
353
407
|
|
|
408
|
+
The server is a single Go module at the repo root (no `src/` tree).
|
|
409
|
+
|
|
354
410
|
```bash
|
|
355
|
-
#
|
|
356
|
-
|
|
411
|
+
# Build the binary
|
|
412
|
+
go build -o atlassian-mcp .
|
|
413
|
+
|
|
414
|
+
# Run it
|
|
415
|
+
./atlassian-mcp --config /path/to/config.json
|
|
357
416
|
|
|
358
|
-
#
|
|
359
|
-
|
|
417
|
+
# Vet + unit tests
|
|
418
|
+
go vet ./...
|
|
419
|
+
go test ./...
|
|
360
420
|
|
|
361
421
|
# Test the tool list
|
|
362
|
-
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' |
|
|
422
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | ./atlassian-mcp
|
|
363
423
|
|
|
364
|
-
# Quick release smoke check
|
|
424
|
+
# Quick release smoke check (build + tools/list validation)
|
|
365
425
|
npm run smoke
|
|
366
426
|
```
|
|
367
|
-
|
|
368
|
-
To use a specific config file:
|
|
369
|
-
|
|
370
|
-
```bash
|
|
371
|
-
node dist/index.js --config /path/to/config.json
|
|
372
|
-
```
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// npm/npx launcher. Ensures the prebuilt Go binary for this platform is present
|
|
3
|
+
// (npx @latest guarantees the newest version), then hands stdio to it directly.
|
|
4
|
+
//
|
|
5
|
+
// It also injects the bundled ffmpeg-static / ffprobe-static binaries via env so
|
|
6
|
+
// video and animated-image attachments work with zero extra setup on the npm
|
|
7
|
+
// install path. Users who install the Go binary directly (go install / Nix)
|
|
8
|
+
// supply ffmpeg/ffprobe on PATH, or set ATLASSIAN_MCP_FFMPEG_PATH / _FFPROBE_PATH.
|
|
9
|
+
//
|
|
10
|
+
// Note on overhead: with stdio 'inherit' the Go binary reads/writes the real
|
|
11
|
+
// stdin/stdout, so this launcher adds ZERO per-message latency — it only relays
|
|
12
|
+
// signals and the exit code.
|
|
13
|
+
import { spawn } from 'node:child_process';
|
|
14
|
+
import { ensureBinary } from '../scripts/download.mjs';
|
|
15
|
+
|
|
16
|
+
let bin;
|
|
17
|
+
try {
|
|
18
|
+
bin = await ensureBinary();
|
|
19
|
+
} catch (err) {
|
|
20
|
+
console.error(`[atlassian-mcp] ${err.message}`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Resolve bundled ffmpeg/ffprobe and expose them to the Go binary unless the
|
|
25
|
+
// user already pinned a path. Best-effort: missing modules are non-fatal.
|
|
26
|
+
const env = { ...process.env };
|
|
27
|
+
async function resolveBundled(envKey, importer) {
|
|
28
|
+
if (env[envKey]) return;
|
|
29
|
+
try {
|
|
30
|
+
const p = await importer();
|
|
31
|
+
if (p) env[envKey] = p;
|
|
32
|
+
} catch { /* dependency absent — fall back to PATH lookup in the binary */ }
|
|
33
|
+
}
|
|
34
|
+
await resolveBundled('ATLASSIAN_MCP_FFMPEG_PATH', async () => {
|
|
35
|
+
const m = await import('ffmpeg-static');
|
|
36
|
+
return m.default ?? m;
|
|
37
|
+
});
|
|
38
|
+
await resolveBundled('ATLASSIAN_MCP_FFPROBE_PATH', async () => {
|
|
39
|
+
const m = await import('ffprobe-static');
|
|
40
|
+
return (m.default ?? m)?.path;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const child = spawn(bin, process.argv.slice(2), { stdio: 'inherit', env });
|
|
44
|
+
|
|
45
|
+
// Forward termination signals so the Go binary shuts down cleanly with its host.
|
|
46
|
+
for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGQUIT']) {
|
|
47
|
+
process.on(sig, () => {
|
|
48
|
+
if (!child.killed) child.kill(sig);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
child.on('exit', (code, signal) => {
|
|
53
|
+
if (signal) process.kill(process.pid, signal);
|
|
54
|
+
else process.exit(code ?? 0);
|
|
55
|
+
});
|
|
56
|
+
child.on('error', (err) => {
|
|
57
|
+
console.error(`[atlassian-mcp] failed to start binary: ${err.message}`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
});
|
package/package.json
CHANGED
|
@@ -1,45 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stubbedev/atlassian-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for self-hosted Jira and Bitbucket",
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "MCP server for self-hosted Jira and Bitbucket (Go, distributed as a prebuilt binary)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"
|
|
7
|
+
"bin": {
|
|
8
|
+
"atlassian-mcp": "bin/cli.mjs"
|
|
9
|
+
},
|
|
8
10
|
"files": [
|
|
9
|
-
"
|
|
11
|
+
"bin/cli.mjs",
|
|
12
|
+
"scripts/download.mjs",
|
|
13
|
+
"scripts/postinstall.mjs",
|
|
10
14
|
"atlassian-mcp.schema.json",
|
|
11
15
|
"README.md"
|
|
12
16
|
],
|
|
13
|
-
"bin": {
|
|
14
|
-
"atlassian-mcp": "dist/index.js"
|
|
15
|
-
},
|
|
16
17
|
"scripts": {
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"release:
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"smoke:validate": "printf '%s\\n' '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}}' | node dist/index.js | 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)}})\"",
|
|
26
|
-
"prerelease": "npm run build && npm run smoke:validate && ncu"
|
|
18
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
19
|
+
"build": "go build -o atlassian-mcp .",
|
|
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)}})\"",
|
|
22
|
+
"preversion": "go vet ./... && npm run test && npm run smoke",
|
|
23
|
+
"release:patch": "npm version patch && git push origin HEAD --follow-tags",
|
|
24
|
+
"release:minor": "npm version minor && git push origin HEAD --follow-tags",
|
|
25
|
+
"release:major": "npm version major && git push origin HEAD --follow-tags"
|
|
27
26
|
},
|
|
28
27
|
"dependencies": {
|
|
29
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
30
|
-
"@napi-rs/canvas": "^0.1.100",
|
|
31
|
-
"dotenv": "^17.4.2",
|
|
32
28
|
"ffmpeg-static": "^5.3.0",
|
|
33
|
-
"ffprobe-static": "^3.1.0"
|
|
34
|
-
"pdfjs-dist": "^5.7.284",
|
|
35
|
-
"sharp": "^0.35.2",
|
|
36
|
-
"unpdf": "^1.6.2"
|
|
37
|
-
},
|
|
38
|
-
"devDependencies": {
|
|
39
|
-
"@types/node": "^25.9.4",
|
|
40
|
-
"npm-check-updates": "^21.0.3",
|
|
41
|
-
"tsx": "^4.22.4",
|
|
42
|
-
"typescript": "^6.0.3"
|
|
29
|
+
"ffprobe-static": "^3.1.0"
|
|
43
30
|
},
|
|
44
31
|
"engines": {
|
|
45
32
|
"node": ">=18.0.0"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Resolves (and, if needed, downloads) the prebuilt Go binary that matches the
|
|
2
|
+
// current platform. Shared by the postinstall script and the CLI launcher so a
|
|
3
|
+
// failed install (e.g. offline) self-heals on first run.
|
|
4
|
+
import { createWriteStream } from 'node:fs';
|
|
5
|
+
import { chmod, mkdir, rm, stat, rename } from 'node:fs/promises';
|
|
6
|
+
import { Readable } from 'node:stream';
|
|
7
|
+
import { pipeline } from 'node:stream/promises';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
|
|
12
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const root = join(here, '..');
|
|
14
|
+
const binDir = join(root, 'bin');
|
|
15
|
+
|
|
16
|
+
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8'));
|
|
17
|
+
const REPO = 'stubbedev/atlassian-mcp';
|
|
18
|
+
|
|
19
|
+
// Map Node's platform/arch onto the Go release asset naming. Anything not
|
|
20
|
+
// listed falls back to the "go install" hint in target().
|
|
21
|
+
const PLATFORMS = {
|
|
22
|
+
'linux:x64': { os: 'linux', arch: 'amd64', ext: '' },
|
|
23
|
+
'linux:arm64': { os: 'linux', arch: 'arm64', ext: '' },
|
|
24
|
+
'linux:arm': { os: 'linux', arch: 'arm', ext: '' },
|
|
25
|
+
'linux:ia32': { os: 'linux', arch: '386', ext: '' },
|
|
26
|
+
'linux:ppc64': { os: 'linux', arch: 'ppc64le', ext: '' },
|
|
27
|
+
'linux:s390x': { os: 'linux', arch: 's390x', ext: '' },
|
|
28
|
+
'linux:riscv64': { os: 'linux', arch: 'riscv64', ext: '' },
|
|
29
|
+
'darwin:x64': { os: 'darwin', arch: 'amd64', ext: '' },
|
|
30
|
+
'darwin:arm64': { os: 'darwin', arch: 'arm64', ext: '' },
|
|
31
|
+
'win32:x64': { os: 'windows', arch: 'amd64', ext: '.exe' },
|
|
32
|
+
'win32:arm64': { os: 'windows', arch: 'arm64', ext: '.exe' },
|
|
33
|
+
'win32:ia32': { os: 'windows', arch: '386', ext: '.exe' },
|
|
34
|
+
'freebsd:x64': { os: 'freebsd', arch: 'amd64', ext: '' },
|
|
35
|
+
'freebsd:arm64': { os: 'freebsd', arch: 'arm64', ext: '' },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function target() {
|
|
39
|
+
const key = `${process.platform}:${process.arch}`;
|
|
40
|
+
const t = PLATFORMS[key];
|
|
41
|
+
if (!t) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Unsupported platform ${key}. Build from source with: go install github.com/${REPO}@latest`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return t;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function binaryPath() {
|
|
50
|
+
const { ext } = target();
|
|
51
|
+
return join(binDir, `atlassian-mcp-native${ext}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function assetUrl() {
|
|
55
|
+
const { os, arch, ext } = target();
|
|
56
|
+
return `https://github.com/${REPO}/releases/download/v${pkg.version}/atlassian-mcp_${os}_${arch}${ext}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function exists(path) {
|
|
60
|
+
try {
|
|
61
|
+
const s = await stat(path);
|
|
62
|
+
return s.isFile() && s.size > 0;
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ensureBinary returns the path to the platform binary, downloading it from the
|
|
69
|
+
// matching GitHub release if it is not already present.
|
|
70
|
+
export async function ensureBinary() {
|
|
71
|
+
const dest = binaryPath();
|
|
72
|
+
if (await exists(dest)) return dest;
|
|
73
|
+
|
|
74
|
+
await mkdir(binDir, { recursive: true });
|
|
75
|
+
const url = assetUrl();
|
|
76
|
+
const res = await fetch(url, { redirect: 'follow' });
|
|
77
|
+
if (!res.ok || !res.body) {
|
|
78
|
+
throw new Error(`Failed to download ${url}: HTTP ${res.status}`);
|
|
79
|
+
}
|
|
80
|
+
const tmp = `${dest}.download`;
|
|
81
|
+
await pipeline(Readable.fromWeb(res.body), createWriteStream(tmp));
|
|
82
|
+
await chmod(tmp, 0o755).catch(() => {});
|
|
83
|
+
await rm(dest, { force: true });
|
|
84
|
+
await rename(tmp, dest);
|
|
85
|
+
return dest;
|
|
86
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Best-effort download of the prebuilt binary at install time. Failures are not
|
|
2
|
+
// fatal — the CLI launcher retries the download on first run — so installs in
|
|
3
|
+
// offline/CI environments still succeed.
|
|
4
|
+
import { ensureBinary } from './download.mjs';
|
|
5
|
+
|
|
6
|
+
if (process.env.ATLASSIAN_MCP_SKIP_DOWNLOAD === '1') {
|
|
7
|
+
process.exit(0);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
await ensureBinary();
|
|
12
|
+
} catch (err) {
|
|
13
|
+
console.error(`[atlassian-mcp] postinstall: ${err.message}`);
|
|
14
|
+
console.error('[atlassian-mcp] The binary will be fetched on first run instead.');
|
|
15
|
+
}
|