pdfops-mcp 0.3.2 → 0.3.4
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 +29 -1
- package/dist/index.js +18 -3
- package/dist/source.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ Beside a local agent, tools operate on file paths, so PDF bytes never transit th
|
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
claude mcp add pdfops -- npx -y pdfops-mcp
|
|
13
|
+
# with a key:
|
|
14
|
+
claude mcp add pdfops -e PDFOPS_API_KEY=pdfops_live_… -- npx -y pdfops-mcp
|
|
13
15
|
```
|
|
14
16
|
|
|
15
17
|
**Claude Desktop** (`claude_desktop_config.json`) / **Cursor** (`.cursor/mcp.json`)
|
|
@@ -26,6 +28,23 @@ claude mcp add pdfops -- npx -y pdfops-mcp
|
|
|
26
28
|
}
|
|
27
29
|
```
|
|
28
30
|
|
|
31
|
+
**VS Code** (`.vscode/mcp.json`, note the `servers` key and `type`)
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"servers": {
|
|
36
|
+
"pdfops": {
|
|
37
|
+
"type": "stdio",
|
|
38
|
+
"command": "npx",
|
|
39
|
+
"args": ["-y", "pdfops-mcp"],
|
|
40
|
+
"env": { "PDFOPS_API_KEY": "pdfops_live_…" }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Any other stdio client (Windsurf, Cline, Zed, your own agent): command `npx`, args `-y pdfops-mcp`. Per-client walkthrough and a real tool-call transcript: [pdfops.dev/mcp](https://pdfops.dev/mcp).
|
|
47
|
+
|
|
29
48
|
`PDFOPS_API_KEY` is optional — without it you get the keyless trial (100 requests/IP/month). A free key (250/month, no card) takes one field at [pdfops.dev/pricing](https://pdfops.dev/pricing).
|
|
30
49
|
|
|
31
50
|
## Tools
|
|
@@ -42,11 +61,20 @@ claude mcp add pdfops -- npx -y pdfops-mcp
|
|
|
42
61
|
|
|
43
62
|
A hosted MCP runtime executes this server on a machine where your agent's file paths do not exist. Nothing changes in the config — pass sources the server can reach and skip `output_path`:
|
|
44
63
|
|
|
45
|
-
- **Inputs** (`pdf_path`, `pdf_paths`): an `https://` URL the server can fetch (≤
|
|
64
|
+
- **Inputs** (`pdf_path`, `pdf_paths`): an `https://` URL the server can fetch (≤4 MB per PDF, ≤4.5 MB per request — the API's limits), or a `data:application/pdf;base64,…` URI for small files.
|
|
46
65
|
- **Outputs**: omit `output_path` and `pdf_fill` / `pdf_merge` / `pdf_invoice` return the PDF inline as an embedded `application/pdf` resource (`pdfops://filled.pdf`, …) that the client saves. With `output_path` set, the file is written where the *server* runs.
|
|
47
66
|
|
|
48
67
|
Locally, absolute paths keep working exactly as before and remain the recommended form — bytes stay off the model context.
|
|
49
68
|
|
|
69
|
+
## Example prompts
|
|
70
|
+
|
|
71
|
+
- *"What fields does ~/forms/fw9.pdf have?"* → `pdf_inspect`
|
|
72
|
+
- *"Fill ~/forms/fw9.pdf for Ada Lovelace, 12 Analytical Way, London; save it flattened as ~/out/w9-ada.pdf"* → `pdf_inspect`, then `pdf_fill` with `flatten: true`
|
|
73
|
+
- *"Fill the onboarding form once per row of contractors.csv into ~/out/"* → one `pdf_inspect`, then `pdf_fill` per row
|
|
74
|
+
- *"Merge ~/out/w9-ada.pdf, ~/docs/nda.pdf and ~/docs/cover.pdf into one packet, cover first"* → `pdf_merge`
|
|
75
|
+
- *"Invoice Globex for 3 days of consulting at $650, 8.5% tax, due in 30 days"* → `pdf_invoice`
|
|
76
|
+
- *"How many PDFops requests do I have left this month?"* → `pdfops_usage`
|
|
77
|
+
|
|
50
78
|
## Example agent flow
|
|
51
79
|
|
|
52
80
|
> "Fill the W-9 template at ~/docs/w9.pdf for Ada Lovelace and merge it with ~/docs/cover.pdf"
|
package/dist/index.js
CHANGED
|
@@ -26,12 +26,25 @@ import { describeSource, pdfResult, resolveSource } from './source.js';
|
|
|
26
26
|
// Version comes from package.json so the string MCP clients display can no
|
|
27
27
|
// longer drift from the published one (0.2.0 shipped reporting 0.1.1).
|
|
28
28
|
const { version } = createRequire(import.meta.url)('../package.json');
|
|
29
|
+
const server = new McpServer({ name: 'pdfops', version });
|
|
30
|
+
// Source attribution: every API call carries this server's version and the
|
|
31
|
+
// MCP host app ("claude-ai/0.1.0", "cursor-vscode/1.0.0", …) taken from the
|
|
32
|
+
// initialize handshake's clientInfo. Added via a fetch wrapper rather than
|
|
33
|
+
// SDK options because clientInfo only exists after connect, and so this works
|
|
34
|
+
// against the already-published pdfops-sdk 0.4.0.
|
|
29
35
|
const client = new PdfOps({
|
|
30
36
|
apiKey: process.env.PDFOPS_API_KEY,
|
|
31
37
|
baseUrl: process.env.PDFOPS_BASE_URL,
|
|
32
38
|
clientTag: 'mcp',
|
|
39
|
+
fetch: (input, init) => {
|
|
40
|
+
const headers = new Headers(init?.headers);
|
|
41
|
+
headers.set('X-Pdfops-Client-Version', version);
|
|
42
|
+
const host = server.server.getClientVersion();
|
|
43
|
+
if (host)
|
|
44
|
+
headers.set('X-Pdfops-Client-Host', `${host.name}/${host.version}`);
|
|
45
|
+
return fetch(input, { ...init, headers });
|
|
46
|
+
},
|
|
33
47
|
});
|
|
34
|
-
const server = new McpServer({ name: 'pdfops', version });
|
|
35
48
|
const SOURCE_DOC = 'PDF source: an absolute file path, an https:// URL, or a data:application/pdf;base64,… URI. Use a URL or data URI when this server runs remotely (Smithery, hosted gateways) where local paths do not exist.';
|
|
36
49
|
const OUTPUT_DOC = 'Absolute path to write the result. Omit when running remotely: the PDF is then returned inline as an application/pdf resource for the client to save.';
|
|
37
50
|
const errText = (e) => e instanceof PdfOpsError
|
|
@@ -69,10 +82,11 @@ server.registerTool('pdf_inspect', {
|
|
|
69
82
|
outputSchema: {
|
|
70
83
|
count: z.number().int().describe('Number of form fields found; 0 when the PDF has no fillable form'),
|
|
71
84
|
hasXFA: z.boolean().describe('True for hybrid AcroForm/XFA documents, whose XFA layer is dropped when filled'),
|
|
85
|
+
truncated: z.boolean().describe('True when the form exceeded an API enumeration cap; fields is then a prefix, not the full list'),
|
|
72
86
|
fields: z
|
|
73
87
|
.array(z.object({
|
|
74
88
|
name: z.string(),
|
|
75
|
-
type: z.string().describe('text, checkbox, radio, optionlist, or unsupported'),
|
|
89
|
+
type: z.string().describe('text, checkbox, dropdown, radio, optionlist, or unsupported'),
|
|
76
90
|
readOnly: z.boolean(),
|
|
77
91
|
value: z.string().optional(),
|
|
78
92
|
options: z.array(z.string()).optional().describe('Permitted values for radio/dropdown/optionlist fields'),
|
|
@@ -162,8 +176,9 @@ server.registerTool('pdf_invoice', {
|
|
|
162
176
|
invoice_number: z.string().optional(),
|
|
163
177
|
date: z
|
|
164
178
|
.string()
|
|
179
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
165
180
|
.optional()
|
|
166
|
-
.describe('Shown on the invoice; also pins metadata for determinism'),
|
|
181
|
+
.describe('Calendar date in YYYY-MM-DD form (other formats are rejected with invalid_date). Shown on the invoice; also pins metadata for determinism'),
|
|
167
182
|
due: z.string().optional(),
|
|
168
183
|
currency: z.string().regex(/^[A-Z]{3}$/).optional(),
|
|
169
184
|
tax_rate: z.number().min(0).max(100).optional(),
|
package/dist/source.js
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
// Outputs mirror it: with `output_path` the PDF is written to disk; without it
|
|
15
15
|
// the PDF comes back as an embedded application/pdf resource the client saves.
|
|
16
16
|
import { readFile } from 'node:fs/promises';
|
|
17
|
-
/** Upper bound on a fetched
|
|
18
|
-
export const MAX_INPUT_BYTES =
|
|
17
|
+
/** Upper bound on a fetched PDF: the API's per-PDF cap, so oversize fails here with a clear message. */
|
|
18
|
+
export const MAX_INPUT_BYTES = 4 * 1024 * 1024;
|
|
19
19
|
export const classifySource = (s) => {
|
|
20
20
|
if (/^data:/i.test(s))
|
|
21
21
|
return 'data';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pdfops-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"mcpName": "dev.pdfops/pdfops-mcp",
|
|
5
5
|
"description": "MCP server for the PDFops API — give AI agents deterministic PDF tools: inspect AcroForm fields, fill forms, merge PDFs, and generate invoices. Works with Claude Code, Claude Desktop, Cursor, and any MCP client.",
|
|
6
6
|
"keywords": [
|