npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@modelcontextprotocol/server-pdf

v1.3.1

Published

MCP server for loading and extracting text from PDF files with chunked pagination and interactive viewer

Readme

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

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/

Additional Flags

  • --debug — Enable verbose server-side logging.
  • --enable-interact — Enable the interact tool in HTTP mode (see Deployment). Not needed for stdio.
  • --writeable-uploads-root — Allow saving annotated PDFs back to files under client roots named uploads (Claude Desktop mounts attachments there; writes are refused by default).

Deployment

The interact tool relies on an in-memory command queue (server enqueues → viewer polls). This constrains how the server can be deployed:

  • stdio (Claude Desktop) — interact is always enabled. The server runs as a single long-lived process, so the in-memory queue works.
  • HTTP, single instance — Pass --enable-interact to opt in. Works as long as all requests land on the same process.
  • HTTP, stateless / multi-instanceinteract will not work. Commands enqueued on one instance are invisible to viewers polling another. Leave the flag off; the tool will not be registered.

Security: Client Roots

MCP clients may advertise rootsfile:// URIs pointing to directories on the client's file system. The server uses these to allow access to local files under those directories.

  • Stdio mode (--stdio): Client roots are always enabled — the client is typically on the same machine (e.g. Claude Desktop), so the roots are safe.
  • HTTP mode (default): Client roots are ignored by default — the client may be remote, and its roots would be resolved against the server's filesystem. To opt in, pass --use-client-roots:
# Trust that the HTTP client is local and its roots are safe
bun examples/pdf-server/main.ts --use-client-roots

When roots are ignored the server logs:

[pdf-server] Client roots are ignored (default for remote transports). Pass --use-client-roots to allow the client to expose local directories.

Allowed Sources

  • Local files: Must be passed as CLI arguments (or via client roots when enabled)
  • 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 | | interact¹ | Model | Navigate, annotate, search, extract pages, fill forms | | read_pdf_bytes | App only | Stream PDF data in chunks | | save_pdf | App only | Save annotated PDF back to local file |

¹ stdio only by default; in HTTP mode requires --enable-interact — see Deployment.

Example Prompts

After the model calls display_pdf, it receives the viewUUID and a description of all capabilities. Here are example prompts and follow-ups that exercise annotation features:

Annotating

User: Show me the Attention Is All You Need paper

Model calls display_pdf → viewer opens

User: Highlight the title and add an APPROVED stamp on the first page.

Model calls interact with highlight_text for the title and add_annotations with a stamp

User: Can you annotate this PDF? Mark important sections for me.

Model calls interact with get_text to read content first, then add_annotations with highlights/notes

User: Add a note on page 1 saying "Key contribution" at position (200, 500), and highlight the abstract.

Model calls interact with add_annotations containing a note and either highlight_text or a highlight annotation

Navigation & Search

User: Search for "self-attention" in the paper.

Model calls interact with action search, query "self-attention"

User: Go to page 5.

Model calls interact with action navigate, page 5

Page Extraction

User: Give me the text of pages 1–3.

Model calls interact with action get_text, intervals [{start:1, end:3}]

User: Take a screenshot of the first page.

Model calls interact with action get_screenshot, page 1

Stamps & Form Filling

User: Stamp this document as CONFIDENTIAL on every page.

Model calls interact with add_annotations containing stamp annotations on each page

User: Fill in the "Name" field with "Alice" and "Date" with "2026-02-26".

Model calls interact with action fill_form, fields [{name:"Name", value:"Alice"}, {name:"Date", value:"2026-02-26"}]

Testing

E2E Tests (Playwright)

# Run annotation E2E tests (renders annotations in a real browser)
npx playwright test tests/e2e/pdf-annotations.spec.ts

# Run all PDF server tests
npx playwright test -g "PDF Server"

API Prompt Discovery Tests

These tests verify that Claude can discover and use annotation capabilities by calling the Anthropic Messages API with the tool schemas. They are disabled by default — skipped unless ANTHROPIC_API_KEY is set:

ANTHROPIC_API_KEY=sk-ant-... npx playwright test tests/e2e/pdf-annotations-api.spec.ts

The API tests simulate a conversation where display_pdf has already been called, then send a follow-up user message and verify the model uses annotation actions (or at least the interact tool). Three scenarios are tested:

| Scenario | User prompt | Expected model behavior | | -------------------- | ----------------------------------------------------------------- | ------------------------------------------ | | Direct annotation | "Highlight the title and add an APPROVED stamp" | Uses highlight_text or add_annotations | | Capability discovery | "Can you annotate this PDF?" | Uses interact or mentions annotations | | Specific notes | "Add a note saying 'Key contribution' and highlight the abstract" | Uses interact tool |

Architecture

server.ts                  # MCP server + tools
main.ts                    # CLI entry point
src/
├── mcp-app.ts             # Interactive viewer UI (PDF.js)
├── pdf-annotations.ts     # Annotation types, diff model, PDF import/export
└── pdf-annotations.test.ts # Unit tests for annotation module

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() | | Annotations | DOM overlays synced with proper PDF annotation dicts | | Annotation import | Load existing PDF annotations via PDF.js getAnnotations() | | Diff-based persistence | localStorage stores only additions/removals vs PDF baseline | | Proper PDF export | pdf-lib low-level API creates real /Type /Annot dictionaries | | Save to file | App-only save_pdf tool writes annotated bytes back to disk | | Dirty flag | * prefix on title when unsaved local changes exist | | Command queue | Server enqueues → client polls + processes | | File download | app.downloadFile() for annotated PDF | | Floating panel with anchoring | Magnetic corner-snapping panel for annotation list | | Drag, resize, rotate | Interactive annotation handles with undo/redo | | Keyboard shortcuts | Ctrl+Z/Y (undo/redo), Ctrl+S (save), Ctrl+F (search), ⌘Enter |

Annotation Types

Supported annotation types (synced with PDF.js):

| Type | Properties | PDF Subtype | | --------------- | ------------------------------------------------------------------ | ------------ | | highlight | rects, color?, content? | /Highlight | | underline | rects, color? | /Underline | | strikethrough | rects, color? | /StrikeOut | | note | x, y, content, color? | /Text | | rectangle | x, y, width, height, color?, etc. | /Square | | circle | x, y, width, height, color?, etc. | /Circle | | line | x1, y1, x2, y2, color? | /Line | | freetext | x, y, content, fontSize?, color? | /FreeText | | stamp | x, y, label, color?, rotation? | /Stamp | | image | x, y, width, height, imageData?/imageUrl?, rotation? | /Stamp |

Dependencies

  • pdfjs-dist: PDF rendering and annotation import (frontend only)
  • pdf-lib: Client-side PDF modification — creates proper PDF annotation dictionaries for export
  • @modelcontextprotocol/ext-apps: MCP Apps SDK