Skip to content

Commit 6771319

Browse files
committed
fix(pdf-server): MCPB bundle missing runtime deps + display_name with spaces
`mcpb pack` zips whatever is on disk; in this monorepo all runtime deps are hoisted to root node_modules, so the published .mcpb shipped without pdfjs-dist/ajv/express/etc. and crashed on import in Claude Desktop. build-mcpb.mjs stages dist/ + manifest into a clean temp dir, runs a non-workspace `npm install --omit=dev --omit=optional` (the new DOMMatrix polyfill makes @napi-rs/canvas's ~130MB of native binaries unnecessary), syncs manifest.version to package.json, then packs. Result: 12.3MB / 38.8MB unpacked, full MCP handshake + tools/list pass. Also rename display_name "PDF (By Anthropic)" → "pdf-viewer" — spaces in display_name break MCP App UI rendering for MCPBs in Claude Desktop. CI now smoke-tests the extracted bundle starts (`dist/index.js --stdio` prints "Ready") so a deps-less bundle can't ship again.
1 parent 13c8c79 commit 6771319

6 files changed

Lines changed: 73 additions & 10 deletions

File tree

.github/workflows/ci.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,15 @@ jobs:
103103

104104
- name: Build MCPB bundle (pdf-server)
105105
if: runner.os == 'Linux' && matrix.name == 'Linux x64'
106-
run: npx -y @anthropic-ai/mcpb pack
106+
run: node build-mcpb.mjs
107+
working-directory: examples/pdf-server
108+
109+
- name: Smoke-test MCPB bundle starts
110+
if: runner.os == 'Linux' && matrix.name == 'Linux x64'
111+
run: |
112+
unzip -q pdf-server.mcpb -d .mcpb-smoke
113+
node .mcpb-smoke/dist/index.js --stdio < /dev/null 2>&1 | tee /tmp/mcpb-smoke.log
114+
grep -q "Ready" /tmp/mcpb-smoke.log
107115
working-directory: examples/pdf-server
108116

109117
e2e:

.github/workflows/npm-publish.yml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,8 @@ jobs:
159159
cache: npm
160160
- run: npm ci
161161

162-
- name: Build pdf-server
163-
run: npm run build --workspace examples/pdf-server
164-
165-
- name: Pack MCPB bundle
166-
run: npx -y @anthropic-ai/mcpb pack
167-
working-directory: examples/pdf-server
162+
- name: Build pdf-server + MCPB bundle
163+
run: npm run build:mcpb --workspace examples/pdf-server
168164

169165
- name: Upload MCPB to release
170166
run: gh release upload "${{ github.event.release.tag_name }}" examples/pdf-server/*.mcpb --clobber

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ __pycache__/
1515
*.pyc
1616

1717
# MCPB bundles (built artifacts)
18-
*.mcpb
18+
*.mcpb
19+
.mcpb-stage/
20+
.mcpb-smoke/

examples/pdf-server/build-mcpb.mjs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Build a self-contained .mcpb bundle for pdf-server.
4+
*
5+
* `mcpb pack` zips whatever is on disk; in this monorepo all runtime deps
6+
* are hoisted to the root node_modules, so packing in-place produces a
7+
* bundle with no pdfjs-dist/ajv/etc. that crashes in Claude Desktop.
8+
*
9+
* This script stages dist/ + manifest into a clean temp dir, runs a fresh
10+
* non-workspace `npm install --omit=dev --omit=optional` (the polyfill in
11+
* dist/pdfjs-polyfill.js makes @napi-rs/canvas's ~130MB of native binaries
12+
* unnecessary), syncs the manifest version to package.json, then packs.
13+
*/
14+
15+
import {
16+
cpSync,
17+
rmSync,
18+
mkdirSync,
19+
readFileSync,
20+
writeFileSync,
21+
} from "node:fs";
22+
import { execSync } from "node:child_process";
23+
import { fileURLToPath } from "node:url";
24+
import path from "node:path";
25+
26+
const here = path.dirname(fileURLToPath(import.meta.url));
27+
const stage = path.join(here, ".mcpb-stage");
28+
const out = path.join(here, "pdf-server.mcpb");
29+
30+
const pkg = JSON.parse(readFileSync(path.join(here, "package.json"), "utf8"));
31+
const manifest = JSON.parse(
32+
readFileSync(path.join(here, "manifest.json"), "utf8"),
33+
);
34+
manifest.version = pkg.version;
35+
36+
rmSync(stage, { recursive: true, force: true });
37+
mkdirSync(stage);
38+
39+
for (const f of ["dist", "icon.png", "README.md", ".mcpbignore"]) {
40+
cpSync(path.join(here, f), path.join(stage, f), { recursive: true });
41+
}
42+
writeFileSync(
43+
path.join(stage, "manifest.json"),
44+
JSON.stringify(manifest, null, 2),
45+
);
46+
writeFileSync(path.join(stage, "package.json"), JSON.stringify(pkg, null, 2));
47+
48+
const run = (cmd) => execSync(cmd, { cwd: stage, stdio: "inherit" });
49+
run(
50+
"npm install --omit=dev --omit=optional --no-audit --no-fund --no-package-lock " +
51+
"--registry=https://registry.npmjs.org/",
52+
);
53+
run(`npx -y @anthropic-ai/mcpb pack . ${JSON.stringify(out)}`);
54+
55+
rmSync(stage, { recursive: true, force: true });
56+
console.log(`\n✅ ${path.relative(process.cwd(), out)}`);

examples/pdf-server/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": "0.3",
33
"name": "@modelcontextprotocol/server-pdf",
4-
"display_name": "PDF (By Anthropic)",
4+
"display_name": "pdf-viewer",
55
"version": "1.0.1",
66
"description": "Read and interact with PDF files using an interactive viewer with search, navigation, and text extraction",
77
"author": {

examples/pdf-server/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
"start": "cross-env NODE_ENV=development npm run build && npm run serve",
2222
"start:stdio": "cross-env NODE_ENV=development npm run build 1>&2 && npm run serve:stdio",
2323
"dev": "cross-env NODE_ENV=development concurrently \"npm run watch\" \"npm run serve\"",
24-
"prepublishOnly": "npm run build"
24+
"prepublishOnly": "npm run build",
25+
"build:mcpb": "npm run build && node build-mcpb.mjs"
2526
},
2627
"dependencies": {
2728
"@modelcontextprotocol/ext-apps": "^1.0.0",

0 commit comments

Comments
 (0)