ext-apps/examples/pdf-server
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Olivier Chafik 5bbf277882
chore: bump ext-apps to 1.1.0 (#485)
* chore: bump ext-apps to 1.1.0

Features:
- registerAppResource now returns the registered resource (#370)
- Accept UIResourceMeta in both resources/list and resources/read (#410)
- callServerTool throws helpful error when called with a string (#449)
- Add double-connect guard to prevent protocol message handling errors (#450)

Bug fixes:
- Only ignore messages that lack jsonrpc 2.0 in message-transport (#448)
- Align basic-host dark mode styles (#438)

Spec:
- Change Host <> Sandbox communication protocol to SHOULD (#435)
- Clarify UIResourceMeta in both list and read responses (#410)

* fix(debug-server): add missing npm package config (main, bin, types, exports)

Align debug-server package.json with all other server examples:
- main: dist/server.js (was server.ts)
- types: dist/server.d.ts
- bin: mcp-server-debug -> dist/index.js
- exports with types
- files: only dist (remove source server.ts)
2026-02-22 01:18:47 +00:00
..
src feat(pdf-server): lazy range-based loading + eager background preloading (#480) 2026-02-21 16:33:48 -08:00
.gitignore feat: compile example servers to JavaScript for npm consumption (#259) 2026-01-14 23:46:17 +00:00
grid-cell.png examples: add PDF viewer w/ chunked data loading, full-screen, model context updates, private tool (#267) 2026-01-14 14:25:24 +00:00
main.ts Refactor server startup functions 2026-01-24 09:27:54 -06:00
mcp-app.html feat(pdf-server): lazy range-based loading + eager background preloading (#480) 2026-02-21 16:33:48 -08:00
package.json chore: bump ext-apps to 1.1.0 (#485) 2026-02-22 01:18:47 +00:00
pdf-icon.svg Adding a simple (bad) pdf icon for directory (#330) 2026-01-23 16:04:46 +00:00
README.md feat(app): throw helpful error when callServerTool is called with a string (#449) 2026-02-13 21:05:21 +00:00
screenshot.png examples: add PDF viewer w/ chunked data loading, full-screen, model context updates, private tool (#267) 2026-01-14 14:25:24 +00:00
server.test.ts feat(pdf-server): lazy range-based loading + eager background preloading (#480) 2026-02-21 16:33:48 -08:00
server.ts feat(pdf-server): lazy range-based loading + eager background preloading (#480) 2026-02-21 16:33:48 -08:00
tsconfig.json feat(pdf-server): lazy range-based loading + eager background preloading (#480) 2026-02-21 16:33:48 -08:00
tsconfig.server.json feat: compile example servers to JavaScript for npm consumption (#259) 2026-01-14 23:46:17 +00:00
vite.config.ts examples: add PDF viewer w/ chunked data loading, full-screen, model context updates, private tool (#267) 2026-01-14 14:25:24 +00:00

PDF Server

Screenshot

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({
    name: "read_pdf_bytes",
    arguments: { 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();
};

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