pdfops-mcp 0.3.0 → 0.3.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 +4 -0
- package/dist/index.js +73 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,4 +59,8 @@ Locally, absolute paths keep working exactly as before and remain the recommende
|
|
|
59
59
|
|
|
60
60
|
API docs: [pdfops.dev/docs](https://pdfops.dev/docs) · OpenAPI: [pdfops.dev/openapi.json](https://pdfops.dev/openapi.json) · Typed client: [`pdfops-sdk`](https://www.npmjs.com/package/pdfops-sdk) · Questions: hello@pdfops.dev
|
|
61
61
|
|
|
62
|
+
## Privacy Policy
|
|
63
|
+
|
|
64
|
+
This server runs on your machine and sends only what a tool call needs to the PDFops API (`https://pdfops.dev`): the PDF bytes you point it at, the field values you supply, and your API key if you set one. PDFops processes the request in memory and returns the result; it does not store your documents. Anonymous usage is metered per IP and per client tag (`mcp`) for quota and attribution only. Nothing is shared with third parties. The full policy, including retention and contact details, is at <https://pdfops.dev/privacy>. Questions: hello@pdfops.dev.
|
|
65
|
+
|
|
62
66
|
MIT © PDFops
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
import { writeFile } from 'node:fs/promises';
|
|
18
18
|
import { createRequire } from 'node:module';
|
|
19
19
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
20
|
+
// Tool annotations (title + readOnlyHint/destructiveHint) are mandatory for the
|
|
21
|
+
// Claude Connectors Directory and help every client show what a tool does.
|
|
20
22
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
21
23
|
import { z } from 'zod';
|
|
22
24
|
import { PdfOps, PdfOpsError } from 'pdfops-sdk';
|
|
@@ -46,7 +48,12 @@ const emit = async (bytes, name, summary, output_path) => {
|
|
|
46
48
|
await writeFile(output_path, bytes);
|
|
47
49
|
return pdfResult(bytes, name, summary, output_path);
|
|
48
50
|
};
|
|
49
|
-
server.
|
|
51
|
+
server.registerTool('pdf_inspect', {
|
|
52
|
+
title: 'Inspect PDF form fields',
|
|
53
|
+
description: 'List a PDF\'s AcroForm form fields — names, types, options, current values, per-field maxLength where declared — plus a paste-ready fillTemplate object for pdf_fill and a hasXFA flag (hybrid AcroForm/XFA inputs lose their XFA layer when filled). A PDF with no form returns count 0. Call this FIRST when filling an unfamiliar PDF: you cannot fill fields whose names you do not know, and values longer than a field\'s maxLength are rejected.',
|
|
54
|
+
inputSchema: { pdf_path: z.string().describe(SOURCE_DOC) },
|
|
55
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
56
|
+
}, async ({ pdf_path }) => {
|
|
50
57
|
try {
|
|
51
58
|
const result = await client.inspect(await resolveSource(pdf_path));
|
|
52
59
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
@@ -55,16 +62,21 @@ server.tool('pdf_inspect', 'List a PDF\'s AcroForm form fields — names, types,
|
|
|
55
62
|
return fail(e);
|
|
56
63
|
}
|
|
57
64
|
});
|
|
58
|
-
server.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.describe(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
.optional()
|
|
67
|
-
|
|
65
|
+
server.registerTool('pdf_fill', {
|
|
66
|
+
title: 'Fill PDF form',
|
|
67
|
+
description: 'Fill AcroForm form fields in a PDF and save or return the result. Field names must exist in the PDF (use pdf_inspect first). All values are strings; checkboxes take "true"/"false"; dropdown/radio/optionlist values must be one of the field\'s options; text values must respect the field\'s maxLength from pdf_inspect. Encrypted PDFs are rejected with decrypt advice (common for government blanks with an empty user password).',
|
|
68
|
+
inputSchema: {
|
|
69
|
+
pdf_path: z.string().describe(`Template ${SOURCE_DOC}`),
|
|
70
|
+
fields: z
|
|
71
|
+
.record(z.string())
|
|
72
|
+
.describe('Field name → string value (from pdf_inspect\'s fillTemplate)'),
|
|
73
|
+
output_path: z.string().optional().describe(OUTPUT_DOC),
|
|
74
|
+
flatten: z
|
|
75
|
+
.boolean()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe('Bake values into page content and drop the AcroForm so fields are no longer editable'),
|
|
78
|
+
},
|
|
79
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
68
80
|
}, async ({ pdf_path, fields, output_path, flatten }) => {
|
|
69
81
|
try {
|
|
70
82
|
const bytes = await client.fillForm(await resolveSource(pdf_path), fields, { flatten });
|
|
@@ -74,9 +86,14 @@ server.tool('pdf_fill', 'Fill AcroForm form fields in a PDF and save or return t
|
|
|
74
86
|
return fail(e);
|
|
75
87
|
}
|
|
76
88
|
});
|
|
77
|
-
server.
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
server.registerTool('pdf_merge', {
|
|
90
|
+
title: 'Merge PDFs',
|
|
91
|
+
description: 'Merge two or more PDFs into one, in the order given, and save or return the result.',
|
|
92
|
+
inputSchema: {
|
|
93
|
+
pdf_paths: z.array(z.string()).min(2).describe(`In order, each a ${SOURCE_DOC}`),
|
|
94
|
+
output_path: z.string().optional().describe(OUTPUT_DOC),
|
|
95
|
+
},
|
|
96
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
80
97
|
}, async ({ pdf_paths, output_path }) => {
|
|
81
98
|
try {
|
|
82
99
|
const inputs = await Promise.all(pdf_paths.map((p) => resolveSource(p)));
|
|
@@ -87,37 +104,42 @@ server.tool('pdf_merge', 'Merge two or more PDFs into one, in the order given, a
|
|
|
87
104
|
return fail(e);
|
|
88
105
|
}
|
|
89
106
|
});
|
|
90
|
-
server.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
.optional()
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
107
|
+
server.registerTool('pdf_invoice', {
|
|
108
|
+
title: 'Generate invoice PDF',
|
|
109
|
+
description: 'Generate a complete, professionally laid-out invoice PDF from structured data — no template needed. Deterministic: the same input produces byte-identical output (safe to re-run). Note: without a paid PDFops key the output carries a small "Generated with pdfops.dev" footer line.',
|
|
110
|
+
inputSchema: {
|
|
111
|
+
invoice: z
|
|
112
|
+
.object({
|
|
113
|
+
from: z.union([
|
|
114
|
+
z.string(),
|
|
115
|
+
z.object({ name: z.string(), lines: z.array(z.string()).optional() }),
|
|
116
|
+
]),
|
|
117
|
+
to: z.union([
|
|
118
|
+
z.string(),
|
|
119
|
+
z.object({ name: z.string(), lines: z.array(z.string()).optional() }),
|
|
120
|
+
]),
|
|
121
|
+
items: z
|
|
122
|
+
.array(z.object({
|
|
123
|
+
description: z.string(),
|
|
124
|
+
quantity: z.number().positive().optional(),
|
|
125
|
+
unit_price: z.number().nonnegative(),
|
|
126
|
+
}))
|
|
127
|
+
.min(1)
|
|
128
|
+
.max(100),
|
|
129
|
+
invoice_number: z.string().optional(),
|
|
130
|
+
date: z
|
|
131
|
+
.string()
|
|
132
|
+
.optional()
|
|
133
|
+
.describe('Shown on the invoice; also pins metadata for determinism'),
|
|
134
|
+
due: z.string().optional(),
|
|
135
|
+
currency: z.string().regex(/^[A-Z]{3}$/).optional(),
|
|
136
|
+
tax_rate: z.number().min(0).max(100).optional(),
|
|
137
|
+
notes: z.string().max(1000).optional(),
|
|
138
|
+
})
|
|
139
|
+
.describe('Invoice data'),
|
|
140
|
+
output_path: z.string().optional().describe(OUTPUT_DOC),
|
|
141
|
+
},
|
|
142
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
121
143
|
}, async ({ invoice, output_path }) => {
|
|
122
144
|
try {
|
|
123
145
|
const bytes = await client.invoice(invoice);
|
|
@@ -128,7 +150,12 @@ server.tool('pdf_invoice', 'Generate a complete, professionally laid-out invoice
|
|
|
128
150
|
return fail(e);
|
|
129
151
|
}
|
|
130
152
|
});
|
|
131
|
-
server.
|
|
153
|
+
server.registerTool('pdfops_usage', {
|
|
154
|
+
title: 'Check PDFops quota',
|
|
155
|
+
description: 'Check the current PDFops API quota for the configured key: tier, limit, used, remaining, reset date. Requires PDFOPS_API_KEY.',
|
|
156
|
+
inputSchema: {},
|
|
157
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
158
|
+
}, async () => {
|
|
132
159
|
try {
|
|
133
160
|
const usage = await client.usage();
|
|
134
161
|
return { content: [{ type: 'text', text: JSON.stringify(usage, null, 2) }] };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pdfops-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
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": [
|