ext-apps/examples/pdf-server
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jerome 65a62a9fae
feat: compile example servers to JavaScript for npm consumption (#259)
* feat: compile example servers to JavaScript for npm consumption

Add TypeScript compilation for server code so the example packages can be
imported and used by consumers without requiring TypeScript compilation.

Changes:
- Add tsconfig.server.json to each example for compiling server code
- Update package.json exports to point to compiled dist/server/server.js
- Add types field pointing to dist/server/server.d.ts
- Update build scripts to include server compilation step
- Fix .ts import extensions to .js in customer-segmentation-server

This enables importing the createServer function from published packages:
  import { createServer } from '@modelcontextprotocol/server-basic-react';

* fix: add type assertion for Nominatim API response

* chore: regenerate schemas

* refactor: split server examples into library and binary entry points

- server.ts: exports createServer() only (library entry point)
- main.ts: entry point with main() that runs the server (binary)
- package.json: adds bin field for npx support, uses inline bun build
- tsconfig.server.json: emitDeclarationOnly, outputs to dist/

This allows:
1. Importing the server as a library: import { createServer } from '@modelcontextprotocol/server-basic-react'
2. Running with npx: npx @modelcontextprotocol/server-basic-react [--stdio]

Added migration script at scripts/migrate-server-examples.ts

* refactor: migrate remaining server examples to library/binary structure

Apply the same refactoring to all 13 remaining server examples:
- scenario-modeler-server, shadertoy-server, map-server
- cohort-heatmap-server, customer-segmentation-server, video-resource-server
- system-monitor-server, budget-allocator-server, sheet-music-server
- threejs-server, integration-server, transcript-server, wiki-explorer-server

Each now has:
- server.ts: exports createServer() (library)
- main.ts: runs the server (binary with shebang)
- bin field in package.json for npx support

* chore: remove unused server-utils.ts from video-resource-server/src

* update

* fix(build): make Windows-compatible by removing chmod and fixing bun externals

- Remove 'chmod +x dist/index.js' which doesn't exist on Windows
- Add '--external ./server.js' to prevent bun from treating the import as a second entry point

* fix(build): use .ts import for bun compatibility

- Change import from './server.js' to './server.ts' in main.ts files
- Remove --external flag since bun should inline everything
- Fixes Windows build where bun couldn't resolve .js to .ts properly

* fix(build): use --external for Windows bun compatibility

Bun on Windows incorrectly treats imported .ts files as entry points.
Use --external './server.js' to work around this, creating a runtime
dependency between index.js and server.js.

* style: fix prettier formatting in pdf-server/main.ts

* fix(build): remove quotes from --external for Windows cmd.exe compatibility

* fix(build): use glob pattern for --external to work on all platforms

The literal path --external ./server.js was not matching the import.
Using glob pattern --external "./server*" correctly externalizes the server module.

* fix(build): use */server.js pattern for --external

The "./server*" pattern was not matching on Windows.
Using "*/server.js" matches any path ending in /server.js which works cross-platform.

* fix(build): use *server.js pattern without slash for Windows compatibility

* update package-lock

* chore: regenerate package-lock.json for cross-platform compatibility

* fix(build): use ./server.js instead of glob pattern for Windows compatibility

* fix(build): use double quotes for banner to fix Windows cmd.exe compatibility

---------

Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
2026-01-14 23:46:17 +00:00
..
src fix(examples): improve updateModelContext with structured YAML frontmatter (#271) 2026-01-14 22:08:09 +00: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 feat: compile example servers to JavaScript for npm consumption (#259) 2026-01-14 23:46:17 +00:00
mcp-app.html examples: add PDF viewer w/ chunked data loading, full-screen, model context updates, private tool (#267) 2026-01-14 14:25:24 +00:00
package.json feat: compile example servers to JavaScript for npm consumption (#259) 2026-01-14 23:46:17 +00:00
README.md examples: add PDF viewer w/ chunked data loading, full-screen, model context updates, private tool (#267) 2026-01-14 14:25:24 +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.ts feat: compile example servers to JavaScript for npm consumption (#259) 2026-01-14 23:46:17 +00:00
tsconfig.json examples: add PDF viewer w/ chunked data loading, full-screen, model context updates, private tool (#267) 2026-01-14 14:25:24 +00: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

A simple interactive PDF viewer that uses PDF.js. Launch it w/ a few PDF files and/or URLs as CLI args (+ support loading any additional pdf from arxiv.org).

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 shows a possible workaround:

Server side (pdf-loader.ts):

// Returns chunks with pagination metadata
async function loadPdfBytesChunk(entry, offset, byteCount) {
  return {
    bytes: base64Chunk,
    offset,
    byteCount,
    totalBytes,
    hasMore: offset + byteCount < totalBytes,
  };
}

Client side (mcp-app.ts):

// Load in chunks with progress
while (hasMore) {
  const chunk = await app.callServerTool("read_pdf_bytes", { pdfId, 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({
  structuredContent: {
    title: pdfTitle,
    currentPage,
    totalPages,
    pageText: pageText.slice(0, 5000),
    selection: selectedText ? { text, start, end } : undefined,
  },
});

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);

Usage

# Default: loads a sample arxiv paper
bun examples/pdf-server/server.ts

# Load local files (converted to file:// URLs)
bun examples/pdf-server/server.ts ./docs/paper.pdf /path/to/thesis.pdf

# Load from URLs
bun examples/pdf-server/server.ts https://arxiv.org/pdf/2401.00001.pdf

# Mix local and remote
bun examples/pdf-server/server.ts ./local.pdf https://arxiv.org/pdf/2401.00001.pdf

# stdio mode for MCP clients
bun examples/pdf-server/server.ts --stdio ./papers/

Security: Dynamic URLs (via view_pdf tool) are restricted to arxiv.org. Local files must be in the initial list.

Tools

Tool Visibility Purpose
list_pdfs Model List indexed PDFs
display_pdf Model + UI Display interactive viewer in chat
read_pdf_bytes App only Chunked binary loading

Architecture

server.ts           # MCP server (233 lines)
├── src/
│   ├── types.ts        # Zod schemas (75 lines)
│   ├── pdf-indexer.ts  # URL-based indexing (44 lines)
│   ├── pdf-loader.ts   # Chunked loading (171 lines)
│   └── mcp-app.ts      # Interactive viewer UI

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()
Size negotiation app.sendSizeChanged()

Dependencies

  • pdfjs-dist: PDF rendering
  • @modelcontextprotocol/ext-apps: MCP Apps SDK