@modelcontextprotocol/server-pdf 0.4.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,26 +2,59 @@
2
2
 
3
3
  ![Screenshot](screenshot.png)
4
4
 
5
- A simple interactive PDF viewer that uses [PDF.js](https://mozilla.github.io/pdf.js/). Launch it w/ a few PDF files and/or URLs as CLI args (+ support loading any additional pdf from arxiv.org).
5
+ An interactive PDF viewer using [PDF.js](https://mozilla.github.io/pdf.js/). Supports local files and remote URLs from academic sources (arxiv, biorxiv, zenodo, etc).
6
+
7
+ ## MCP Client Configuration
8
+
9
+ Add to your MCP client configuration (stdio transport):
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "pdf": {
15
+ "command": "npx",
16
+ "args": [
17
+ "-y",
18
+ "--silent",
19
+ "--registry=https://registry.npmjs.org/",
20
+ "@modelcontextprotocol/server-pdf",
21
+ "--stdio"
22
+ ]
23
+ }
24
+ }
25
+ }
26
+ ```
27
+
28
+ ### Local Development
29
+
30
+ To test local modifications, use this configuration (replace `~/code/ext-apps` with your clone path):
31
+
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "pdf": {
36
+ "command": "bash",
37
+ "args": [
38
+ "-c",
39
+ "cd ~/code/ext-apps/examples/pdf-server && npm run build >&2 && node dist/index.js --stdio"
40
+ ]
41
+ }
42
+ }
43
+ }
44
+ ```
6
45
 
7
46
  ## What This Example Demonstrates
8
47
 
9
48
  ### 1. Chunked Data Through Size-Limited Tool Calls
10
49
 
11
- 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:
50
+ 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:
12
51
 
13
- **Server side** (`pdf-loader.ts`):
52
+ **Server side** (`server.ts`):
14
53
 
15
54
  ```typescript
16
55
  // Returns chunks with pagination metadata
17
- async function loadPdfBytesChunk(entry, offset, byteCount) {
18
- return {
19
- bytes: base64Chunk,
20
- offset,
21
- byteCount,
22
- totalBytes,
23
- hasMore: offset + byteCount < totalBytes,
24
- };
56
+ {
57
+ (bytes, offset, byteCount, totalBytes, hasMore);
25
58
  }
26
59
  ```
27
60
 
@@ -30,7 +63,7 @@ async function loadPdfBytesChunk(entry, offset, byteCount) {
30
63
  ```typescript
31
64
  // Load in chunks with progress
32
65
  while (hasMore) {
33
- const chunk = await app.callServerTool("read_pdf_bytes", { pdfId, offset });
66
+ const chunk = await app.callServerTool("read_pdf_bytes", { url, offset });
34
67
  chunks.push(base64ToBytes(chunk.bytes));
35
68
  offset += chunk.byteCount;
36
69
  hasMore = chunk.hasMore;
@@ -44,13 +77,12 @@ The viewer keeps the model informed about what the user is seeing:
44
77
 
45
78
  ```typescript
46
79
  app.updateModelContext({
47
- structuredContent: {
48
- title: pdfTitle,
49
- currentPage,
50
- totalPages,
51
- pageText: pageText.slice(0, 5000),
52
- selection: selectedText ? { text, start, end } : undefined,
53
- },
80
+ content: [
81
+ {
82
+ type: "text",
83
+ text: `PDF viewer | "${title}" | Current Page: ${page}/${total}\n\nPage content:\n${pageText}`,
84
+ },
85
+ ],
54
86
  });
55
87
  ```
56
88
 
@@ -80,58 +112,75 @@ The viewer demonstrates opening external links (e.g., to the original arxiv page
80
112
  titleEl.onclick = () => app.openLink(sourceUrl);
81
113
  ```
82
114
 
115
+ ### 5. View Persistence
116
+
117
+ Page position is saved per-view using `viewUUID` and localStorage.
118
+
119
+ ### 6. Dark Mode / Theming
120
+
121
+ The viewer syncs with the host's theme using CSS `light-dark()` and the SDK's theming APIs:
122
+
123
+ ```typescript
124
+ app.onhostcontextchanged = (ctx) => {
125
+ if (ctx.theme) applyDocumentTheme(ctx.theme);
126
+ if (ctx.styles?.variables) applyHostStyleVariables(ctx.styles.variables);
127
+ };
128
+ ```
129
+
83
130
  ## Usage
84
131
 
85
132
  ```bash
86
133
  # Default: loads a sample arxiv paper
87
- bun examples/pdf-server/server.ts
134
+ bun examples/pdf-server/main.ts
88
135
 
89
136
  # Load local files (converted to file:// URLs)
90
- bun examples/pdf-server/server.ts ./docs/paper.pdf /path/to/thesis.pdf
137
+ bun examples/pdf-server/main.ts ./docs/paper.pdf /path/to/thesis.pdf
91
138
 
92
139
  # Load from URLs
93
- bun examples/pdf-server/server.ts https://arxiv.org/pdf/2401.00001.pdf
140
+ bun examples/pdf-server/main.ts https://arxiv.org/pdf/2401.00001.pdf
94
141
 
95
142
  # Mix local and remote
96
- bun examples/pdf-server/server.ts ./local.pdf https://arxiv.org/pdf/2401.00001.pdf
143
+ bun examples/pdf-server/main.ts ./local.pdf https://arxiv.org/pdf/2401.00001.pdf
97
144
 
98
145
  # stdio mode for MCP clients
99
- bun examples/pdf-server/server.ts --stdio ./papers/
146
+ bun examples/pdf-server/main.ts --stdio ./papers/
100
147
  ```
101
148
 
102
- **Security**: Dynamic URLs (via `view_pdf` tool) are restricted to arxiv.org. Local files must be in the initial list.
149
+ ## Allowed Sources
150
+
151
+ - **Local files**: Must be passed as CLI arguments
152
+ - **Remote URLs**: arxiv.org, biorxiv.org, medrxiv.org, chemrxiv.org, zenodo.org, osf.io, hal.science, ssrn.com, and more
103
153
 
104
154
  ## Tools
105
155
 
106
- | Tool | Visibility | Purpose |
107
- | ---------------- | ---------- | ---------------------------------- |
108
- | `list_pdfs` | Model | List indexed PDFs |
109
- | `display_pdf` | Model + UI | Display interactive viewer in chat |
110
- | `read_pdf_bytes` | App only | Chunked binary loading |
156
+ | Tool | Visibility | Purpose |
157
+ | ---------------- | ---------- | -------------------------------------- |
158
+ | `list_pdfs` | Model | List available local files and origins |
159
+ | `display_pdf` | Model + UI | Display interactive viewer |
160
+ | `read_pdf_bytes` | App only | Stream PDF data in chunks |
111
161
 
112
162
  ## Architecture
113
163
 
114
164
  ```
115
- server.ts # MCP server (233 lines)
116
- ├── src/
117
- │ ├── types.ts # Zod schemas (75 lines)
118
- │ ├── pdf-indexer.ts # URL-based indexing (44 lines)
119
- │ ├── pdf-loader.ts # Chunked loading (171 lines)
120
- │ └── mcp-app.ts # Interactive viewer UI
165
+ server.ts # MCP server + tools
166
+ main.ts # CLI entry point
167
+ src/
168
+ └── mcp-app.ts # Interactive viewer UI (PDF.js)
121
169
  ```
122
170
 
123
171
  ## Key Patterns Shown
124
172
 
125
- | Pattern | Implementation |
126
- | ----------------- | ---------------------------------------- |
127
- | App-only tools | `_meta: { ui: { visibility: ["app"] } }` |
128
- | Chunked responses | `hasMore` + `offset` pagination |
129
- | Model context | `app.updateModelContext()` |
130
- | Display modes | `app.requestDisplayMode()` |
131
- | External links | `app.openLink()` |
132
- | Size negotiation | `app.sendSizeChanged()` |
173
+ | Pattern | Implementation |
174
+ | ----------------- | ------------------------------------------- |
175
+ | App-only tools | `_meta: { ui: { visibility: ["app"] } }` |
176
+ | Chunked responses | `hasMore` + `offset` pagination |
177
+ | Model context | `app.updateModelContext()` |
178
+ | Display modes | `app.requestDisplayMode()` |
179
+ | External links | `app.openLink()` |
180
+ | View persistence | `viewUUID` + localStorage |
181
+ | Theming | `applyDocumentTheme()` + CSS `light-dark()` |
133
182
 
134
183
  ## Dependencies
135
184
 
136
- - `pdfjs-dist`: PDF rendering
185
+ - `pdfjs-dist`: PDF rendering (frontend only)
137
186
  - `@modelcontextprotocol/ext-apps`: MCP Apps SDK