| Filename | Latest commit message | Latest commit date |
|---|---|---|
* fix(examples): use npm reference for ext-apps dependency Replace local path '../..' with '^1.0.0' npm reference in all example packages. The local path was accidentally published to npm, causing npm install to hang when installing server-* packages. Bump versions to 1.0.1 for all public packages to fix the published versions. Fixes npm install hanging issue in dependent projects like example-remote-server. Note: npm workspaces automatically resolves the dependency to the local package during development, so no overrides are needed. * fix: update zod to 4.x in transcript-server and fix update-lock:docker script - Update transcript-server to use zod ^4.1.13 for compatibility - Fix update-lock:docker script to properly clean workspace node_modules - Regenerate package-lock.json with clean npmjs.org URLs |
||
| .. | ||
| src | ||
| .gitignore | ||
| grid-cell.png | ||
| main.ts | ||
| mcp-app.html | ||
| package.json | ||
| pdf-icon.svg | ||
| README.md | ||
| screenshot.png | ||
| server.ts | ||
| tsconfig.json | ||
| tsconfig.server.json | ||
| vite.config.ts | ||
PDF Server
An interactive PDF viewer using PDF.js. Supports local files and remote URLs from academic sources (arxiv, biorxiv, zenodo, etc).
MCP Client Configuration
Add to your MCP client configuration (stdio transport):
{
"mcpServers": {
"pdf": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-pdf",
"--stdio"
]
}
}
}
Local Development
To test local modifications, use this configuration (replace ~/code/ext-apps with your clone path):
{
"mcpServers": {
"pdf": {
"command": "bash",
"args": [
"-c",
"cd ~/code/ext-apps/examples/pdf-server && npm run build >&2 && node dist/index.js --stdio"
]
}
}
}
What This Example Demonstrates
1. Chunked Data Through Size-Limited Tool Calls
On some host platforms, tool calls have size limits, so large PDFs cannot be sent in a single response. This example streams PDFs in chunks using HTTP Range requests:
Server side (server.ts):
// Returns chunks with pagination metadata
{
(bytes, offset, byteCount, totalBytes, hasMore);
}
Client side (mcp-app.ts):
// Load in chunks with progress
while (hasMore) {
const chunk = await app.callServerTool("read_pdf_bytes", { url, offset });
chunks.push(base64ToBytes(chunk.bytes));
offset += chunk.byteCount;
hasMore = chunk.hasMore;
updateProgress(offset, chunk.totalBytes);
}
2. Model Context Updates
The viewer keeps the model informed about what the user is seeing:
app.updateModelContext({
content: [
{
type: "text",
text: `PDF viewer | "${title}" | Current Page: ${page}/${total}\n\nPage content:\n${pageText}`,
},
],
});
This enables the model to answer questions about the current page or selected text.
3. Display Modes: Fullscreen vs Inline
- Inline mode: App requests height changes to fit content
- Fullscreen mode: App fills the screen with internal scrolling
// Request fullscreen
app.requestDisplayMode({ mode: "fullscreen" });
// Listen for mode changes
app.ondisplaymodechange = (mode) => {
if (mode === "fullscreen") enableScrolling();
else disableScrolling();
};
4. External Links (openLink)
The viewer demonstrates opening external links (e.g., to the original arxiv page):
titleEl.onclick = () => app.openLink(sourceUrl);
5. View Persistence
Page position is saved per-view using viewUUID and localStorage.
6. Dark Mode / Theming
The viewer syncs with the host's theme using CSS light-dark() and the SDK's theming APIs:
app.onhostcontextchanged = (ctx) => {
if (ctx.theme) applyDocumentTheme(ctx.theme);
if (ctx.styles?.variables) applyHostStyleVariables(ctx.styles.variables);
};
Usage
# Default: loads a sample arxiv paper
bun examples/pdf-server/main.ts
# Load local files (converted to file:// URLs)
bun examples/pdf-server/main.ts ./docs/paper.pdf /path/to/thesis.pdf
# Load from URLs
bun examples/pdf-server/main.ts https://arxiv.org/pdf/2401.00001.pdf
# Mix local and remote
bun examples/pdf-server/main.ts ./local.pdf https://arxiv.org/pdf/2401.00001.pdf
# stdio mode for MCP clients
bun examples/pdf-server/main.ts --stdio ./papers/
Allowed Sources
- Local files: Must be passed as CLI arguments
- Remote URLs: arxiv.org, biorxiv.org, medrxiv.org, chemrxiv.org, zenodo.org, osf.io, hal.science, ssrn.com, and more
Tools
| Tool | Visibility | Purpose |
|---|---|---|
list_pdfs |
Model | List available local files and origins |
display_pdf |
Model + UI | Display interactive viewer |
read_pdf_bytes |
App only | Stream PDF data in chunks |
Architecture
server.ts # MCP server + tools
main.ts # CLI entry point
src/
└── mcp-app.ts # Interactive viewer UI (PDF.js)
Key Patterns Shown
| Pattern | Implementation |
|---|---|
| App-only tools | _meta: { ui: { visibility: ["app"] } } |
| Chunked responses | hasMore + offset pagination |
| Model context | app.updateModelContext() |
| Display modes | app.requestDisplayMode() |
| External links | app.openLink() |
| View persistence | viewUUID + localStorage |
| Theming | applyDocumentTheme() + CSS light-dark() |
Dependencies
pdfjs-dist: PDF rendering (frontend only)@modelcontextprotocol/ext-apps: MCP Apps SDK
