@stubbedev/atlassian-mcp 0.5.26 → 0.5.27

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
@@ -241,12 +241,13 @@ Clients start the server from their own working directory, so use absolute paths
241
241
 
242
242
  #### Binary cache
243
243
 
244
- npm and Composer download each release's binary once per user, into
245
- `$XDG_CACHE_HOME/atlassian-mcp/<version>/` (by default `~/.cache` on Linux and FreeBSD,
246
- `~/Library/Caches` on macOS, `%LOCALAPPDATA%` on Windows), and hardlink it into every
247
- install, or copy it where the install is on another filesystem. Further projects and git
248
- worktrees on the same version install without downloading anything. Delete old version
249
- directories to reclaim their space; installs keep their own link.
244
+ npm and Composer download each release's binary once per user, verify it against the
245
+ release's `checksums.txt`, and keep it in `$XDG_CACHE_HOME/atlassian-mcp/<version>/` (by
246
+ default `~/.cache` on Linux and FreeBSD, `~/Library/Caches` on macOS, `%LOCALAPPDATA%` on
247
+ Windows), and hardlink it into every install, or copy it where the install is on another
248
+ filesystem. Further projects and git worktrees on the same version install without
249
+ downloading anything. Delete old version directories to reclaim their space; installs keep
250
+ their own link.
250
251
 
251
252
  #### Standalone binary (Go, Nix, release download)
252
253
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stubbedev/atlassian-mcp",
3
- "version": "0.5.26",
3
+ "version": "0.5.27",
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",
@@ -1,9 +1,7 @@
1
- import { randomBytes } from 'node:crypto';
2
- import { createWriteStream, readFileSync } from 'node:fs';
3
- import { chmod, copyFile, link, mkdir, rename, rm, stat } from 'node:fs/promises';
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { chmod, copyFile, link, mkdir, rename, rm, stat, writeFile } from 'node:fs/promises';
4
4
  import { dirname, join } from 'node:path';
5
- import { Readable } from 'node:stream';
6
- import { pipeline } from 'node:stream/promises';
7
5
  import { fileURLToPath } from 'node:url';
8
6
 
9
7
  const NAME = 'atlassian-mcp';
@@ -54,21 +52,7 @@ function asset() {
54
52
  }
55
53
 
56
54
  async function fetchInto(cached) {
57
- const url = `https://github.com/${REPO}/releases/download/v${pkg.version}/${asset()}`;
58
- const res = await fetch(url, { redirect: 'follow' });
59
- if (!res.ok || !res.body) {
60
- throw new Error(`Failed to download ${url}: HTTP ${res.status}`);
61
- }
62
- const tmp = await tmpBeside(cached);
63
- try {
64
- await pipeline(Readable.fromWeb(res.body), createWriteStream(tmp));
65
- if ((await stat(tmp)).size === 0) {
66
- throw new Error(`Failed to download ${url}: empty response`);
67
- }
68
- await commit(tmp, cached);
69
- } finally {
70
- await rm(tmp, { force: true });
71
- }
55
+ await store(await fetchRelease(`v${pkg.version}`, asset()), cached);
72
56
  }
73
57
 
74
58
  function cacheDir() {
@@ -115,3 +99,34 @@ async function tmpBeside(file) {
115
99
  await mkdir(dirname(file), { recursive: true });
116
100
  return `${file}.${randomBytes(6).toString('hex')}.tmp`;
117
101
  }
102
+
103
+ async function fetchRelease(tag, asset) {
104
+ const base = `https://github.com/${REPO}/releases/download/${tag}`;
105
+ const sums = (await get(`${base}/checksums.txt`)).toString('utf8');
106
+ const want = sums
107
+ .split('\n')
108
+ .map((line) => line.trim().split(/\s+\*?/))
109
+ .find(([, file]) => file === asset)?.[0];
110
+ if (!want) throw new Error(`${asset} is not listed in ${tag} checksums.txt`);
111
+ const data = await get(`${base}/${asset}`);
112
+ if (createHash('sha256').update(data).digest('hex') !== want) {
113
+ throw new Error(`Checksum mismatch for ${asset} from ${tag}`);
114
+ }
115
+ return data;
116
+ }
117
+
118
+ async function get(url) {
119
+ const res = await fetch(url, { redirect: 'follow' });
120
+ if (!res.ok) throw new Error(`Failed to download ${url}: HTTP ${res.status}`);
121
+ return Buffer.from(await res.arrayBuffer());
122
+ }
123
+
124
+ async function store(data, cached) {
125
+ const tmp = await tmpBeside(cached);
126
+ try {
127
+ await writeFile(tmp, data);
128
+ await commit(tmp, cached);
129
+ } finally {
130
+ await rm(tmp, { force: true });
131
+ }
132
+ }