@modelcontextprotocol/server-pdf 0.4.1
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 +137 -0
- package/dist/index.js +39384 -0
- package/dist/mcp-app.html +197 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +36037 -0
- package/dist/src/pdf-indexer.d.ts +15 -0
- package/dist/src/pdf-loader.d.ts +5 -0
- package/dist/src/types.d.ts +50 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# PDF Server
|
|
2
|
+
|
|
3
|
+

|
|
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).
|
|
6
|
+
|
|
7
|
+
## What This Example Demonstrates
|
|
8
|
+
|
|
9
|
+
### 1. Chunked Data Through Size-Limited Tool Calls
|
|
10
|
+
|
|
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:
|
|
12
|
+
|
|
13
|
+
**Server side** (`pdf-loader.ts`):
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// 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
|
+
};
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Client side** (`mcp-app.ts`):
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// Load in chunks with progress
|
|
32
|
+
while (hasMore) {
|
|
33
|
+
const chunk = await app.callServerTool("read_pdf_bytes", { pdfId, offset });
|
|
34
|
+
chunks.push(base64ToBytes(chunk.bytes));
|
|
35
|
+
offset += chunk.byteCount;
|
|
36
|
+
hasMore = chunk.hasMore;
|
|
37
|
+
updateProgress(offset, chunk.totalBytes);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Model Context Updates
|
|
42
|
+
|
|
43
|
+
The viewer keeps the model informed about what the user is seeing:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
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
|
+
},
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This enables the model to answer questions about the current page or selected text.
|
|
58
|
+
|
|
59
|
+
### 3. Display Modes: Fullscreen vs Inline
|
|
60
|
+
|
|
61
|
+
- **Inline mode**: App requests height changes to fit content
|
|
62
|
+
- **Fullscreen mode**: App fills the screen with internal scrolling
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Request fullscreen
|
|
66
|
+
app.requestDisplayMode({ mode: "fullscreen" });
|
|
67
|
+
|
|
68
|
+
// Listen for mode changes
|
|
69
|
+
app.ondisplaymodechange = (mode) => {
|
|
70
|
+
if (mode === "fullscreen") enableScrolling();
|
|
71
|
+
else disableScrolling();
|
|
72
|
+
};
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 4. External Links (openLink)
|
|
76
|
+
|
|
77
|
+
The viewer demonstrates opening external links (e.g., to the original arxiv page):
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
titleEl.onclick = () => app.openLink(sourceUrl);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Default: loads a sample arxiv paper
|
|
87
|
+
bun examples/pdf-server/server.ts
|
|
88
|
+
|
|
89
|
+
# Load local files (converted to file:// URLs)
|
|
90
|
+
bun examples/pdf-server/server.ts ./docs/paper.pdf /path/to/thesis.pdf
|
|
91
|
+
|
|
92
|
+
# Load from URLs
|
|
93
|
+
bun examples/pdf-server/server.ts https://arxiv.org/pdf/2401.00001.pdf
|
|
94
|
+
|
|
95
|
+
# Mix local and remote
|
|
96
|
+
bun examples/pdf-server/server.ts ./local.pdf https://arxiv.org/pdf/2401.00001.pdf
|
|
97
|
+
|
|
98
|
+
# stdio mode for MCP clients
|
|
99
|
+
bun examples/pdf-server/server.ts --stdio ./papers/
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Security**: Dynamic URLs (via `view_pdf` tool) are restricted to arxiv.org. Local files must be in the initial list.
|
|
103
|
+
|
|
104
|
+
## Tools
|
|
105
|
+
|
|
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 |
|
|
111
|
+
|
|
112
|
+
## Architecture
|
|
113
|
+
|
|
114
|
+
```
|
|
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
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Key Patterns Shown
|
|
124
|
+
|
|
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()` |
|
|
133
|
+
|
|
134
|
+
## Dependencies
|
|
135
|
+
|
|
136
|
+
- `pdfjs-dist`: PDF rendering
|
|
137
|
+
- `@modelcontextprotocol/ext-apps`: MCP Apps SDK
|