@stubbedev/atlassian-mcp 0.4.4 → 0.5.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.
- package/README.md +52 -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 -1341
- 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,23 @@ 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
|
|
247
250
|
```
|
|
248
251
|
|
|
249
|
-
Then
|
|
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.
|
|
250
256
|
|
|
251
257
|
### Attachment decoding pipeline
|
|
252
258
|
|
|
@@ -254,28 +260,35 @@ The attachment tools (`jira_get_attachment`, `bitbucket_get_attachment`) decode
|
|
|
254
260
|
|
|
255
261
|
| Input | What gets returned | How |
|
|
256
262
|
| --- | --- | --- |
|
|
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
|
|
263
|
+
| 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) |
|
|
264
|
+
| Animated images (GIF/APNG/animated WebP) | N sampled frames as image content blocks | `ffmpeg` + native Go re-encode (default 6 frames @ 768 px) |
|
|
265
|
+
| 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
266
|
| Audio (mp3/wav/ogg/…) | MCP audio content block | passthrough |
|
|
261
|
-
| PDFs | Extracted text — or rasterized pages if text is empty (scanned PDFs) | `
|
|
267
|
+
| 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
268
|
| Text-like (json/xml/yaml/…) | Text content block | passthrough |
|
|
263
|
-
| Everything else (or oversized) | Auto-saved to a temp file; path is returned | `os.
|
|
269
|
+
| Everything else (or oversized) | Auto-saved to a temp file; path is returned | `os.TempDir()` with `atlmcp-` prefix |
|
|
264
270
|
|
|
265
271
|
Auto-saved files are periodically pruned by TTL and total-size quota — see *Environment overrides* below.
|
|
266
272
|
|
|
267
|
-
###
|
|
273
|
+
### External tools (optional)
|
|
274
|
+
|
|
275
|
+
Image and PDF-text decoding are pure Go and need nothing extra. The two pipelines that have no
|
|
276
|
+
pure-Go implementation shell out to external binaries:
|
|
268
277
|
|
|
269
|
-
-
|
|
270
|
-
|
|
271
|
-
|
|
278
|
+
- **`ffmpeg` + `ffprobe`** — video and animated-image frame sampling. The npm wrapper bundles
|
|
279
|
+
[`ffmpeg-static`](https://www.npmjs.com/package/ffmpeg-static) /
|
|
280
|
+
[`ffprobe-static`](https://www.npmjs.com/package/ffprobe-static) and injects their paths, so the
|
|
281
|
+
npx install path is zero-config. On the `go install` / Nix paths, install `ffmpeg` (it provides
|
|
282
|
+
`ffprobe`) or set the env vars below.
|
|
283
|
+
- **`pdftoppm` (poppler) or `mutool` (MuPDF)** — only needed to rasterize *scanned* PDFs that have no
|
|
284
|
+
extractable text. If neither is on `PATH`, such PDFs are saved to disk instead.
|
|
272
285
|
|
|
273
286
|
### Environment overrides
|
|
274
287
|
|
|
275
288
|
| Variable | Purpose | Default |
|
|
276
289
|
| --- | --- | --- |
|
|
277
|
-
| `ATLASSIAN_MCP_FFMPEG_PATH` | Path to `ffmpeg` binary.
|
|
278
|
-
| `ATLASSIAN_MCP_FFPROBE_PATH` | Path to `ffprobe` binary.
|
|
290
|
+
| `ATLASSIAN_MCP_FFMPEG_PATH` | Path to `ffmpeg` binary. | npm: bundled `ffmpeg-static`; otherwise `ffmpeg` on `PATH` |
|
|
291
|
+
| `ATLASSIAN_MCP_FFPROBE_PATH` | Path to `ffprobe` binary. | npm: bundled `ffprobe-static`; otherwise `ffprobe` on `PATH` |
|
|
279
292
|
| `ATLASSIAN_MCP_TMP_TTL_DAYS` | Auto-saved attachments older than this are pruned. | `7` |
|
|
280
293
|
| `ATLASSIAN_MCP_TMP_MAX_BYTES` | Total-size quota for auto-saved attachments in `os.tmpdir()`. When exceeded, oldest are evicted. | `1073741824` (1 GB) |
|
|
281
294
|
|
|
@@ -287,23 +300,20 @@ This package is published to npm as `@stubbedev/atlassian-mcp`.
|
|
|
287
300
|
|
|
288
301
|
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
302
|
|
|
290
|
-
|
|
303
|
+
On a pushed `v*` tag, `.github/workflows/publish.yml` cross-compiles the Go binary for 14
|
|
304
|
+
OS/arch targets, attaches them to a GitHub release, and publishes the npm wrapper (which
|
|
305
|
+
downloads the matching binary on install).
|
|
291
306
|
|
|
292
307
|
Release flow:
|
|
293
308
|
|
|
294
309
|
```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
|
|
310
|
+
# choose one: patch | minor | major (also: npm run release:patch / :minor / :major)
|
|
311
|
+
npm version patch # bumps package.json, commits, tags vX.Y.Z
|
|
303
312
|
git push origin HEAD --follow-tags
|
|
304
313
|
```
|
|
305
314
|
|
|
306
|
-
|
|
315
|
+
`flake.nix` reads its version from `package.json`, so the Nix package tracks the same bump
|
|
316
|
+
automatically. GitHub Actions builds + publishes from the pushed tag.
|
|
307
317
|
|
|
308
318
|
- The workflow is configured for npm Trusted Publisher (OIDC), so no `NPM_TOKEN` secret is required
|
|
309
319
|
|
|
@@ -351,22 +361,22 @@ Paste the token as the `token` value under `bitbucket` in your config file.
|
|
|
351
361
|
|
|
352
362
|
## Development
|
|
353
363
|
|
|
364
|
+
The server is a single Go module at the repo root (no `src/` tree).
|
|
365
|
+
|
|
354
366
|
```bash
|
|
355
|
-
#
|
|
356
|
-
|
|
367
|
+
# Build the binary
|
|
368
|
+
go build -o atlassian-mcp .
|
|
357
369
|
|
|
358
|
-
# Run
|
|
359
|
-
|
|
370
|
+
# Run it
|
|
371
|
+
./atlassian-mcp --config /path/to/config.json
|
|
372
|
+
|
|
373
|
+
# Vet + unit tests
|
|
374
|
+
go vet ./...
|
|
375
|
+
go test ./...
|
|
360
376
|
|
|
361
377
|
# Test the tool list
|
|
362
|
-
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' |
|
|
378
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | ./atlassian-mcp
|
|
363
379
|
|
|
364
|
-
# Quick release smoke check
|
|
380
|
+
# Quick release smoke check (build + tools/list validation)
|
|
365
381
|
npm run smoke
|
|
366
382
|
```
|
|
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.0",
|
|
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
|
+
}
|