pdfpipe-mcp-server 0.1.0 → 0.3.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/dist/index.js +121 -24
- package/package.json +38 -32
package/dist/index.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* MCP server for PDFPipe (https://pdfpipe.xyz).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Tools:
|
|
6
|
+
* pdfpipe_generate_pdf — render HTML or URL to a PDF file
|
|
7
|
+
* pdfpipe_list_documents — list archived PDFs (requires store: true on generate)
|
|
8
|
+
* pdfpipe_get_document — retrieve an archived PDF by document ID
|
|
9
9
|
*
|
|
10
10
|
* Auth: set PDFPIPE_API_KEY. Override the host with PDFPIPE_BASE_URL.
|
|
11
11
|
*/
|
|
@@ -47,6 +47,37 @@ const GeneratePdfInput = z
|
|
|
47
47
|
.string()
|
|
48
48
|
.default("1cm")
|
|
49
49
|
.describe("Uniform page margin as a CSS length, e.g. '1cm', '0', '0.5in'. Default '1cm'."),
|
|
50
|
+
store: z
|
|
51
|
+
.boolean()
|
|
52
|
+
.default(false)
|
|
53
|
+
.describe("If true, persist the PDF for later retrieval via pdfpipe_get_document. " +
|
|
54
|
+
"Returns document_id, document_url, and document_expires. " +
|
|
55
|
+
"Retention by plan: hobby 1 day, starter 30 days, growth 1 year, scale 2 years."),
|
|
56
|
+
filename: z
|
|
57
|
+
.string()
|
|
58
|
+
.max(200)
|
|
59
|
+
.optional()
|
|
60
|
+
.describe("Filename to associate with the stored document. Only used when store is true."),
|
|
61
|
+
header_html: z
|
|
62
|
+
.string()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("HTML rendered as a running page header. Class names .pageNumber, .totalPages, .date, .title, .url are substituted by the renderer on each page."),
|
|
65
|
+
footer_html: z
|
|
66
|
+
.string()
|
|
67
|
+
.optional()
|
|
68
|
+
.describe("HTML rendered as a running page footer. Same class substitution as header_html."),
|
|
69
|
+
tabular_nums: z
|
|
70
|
+
.boolean()
|
|
71
|
+
.default(false)
|
|
72
|
+
.describe("Force consistent digit widths via font-variant-numeric: tabular-nums. Fixes column misalignment in tables and invoices. Default false."),
|
|
73
|
+
deduplicate_images: z
|
|
74
|
+
.boolean()
|
|
75
|
+
.default(false)
|
|
76
|
+
.describe("Merge identical image XObjects in the output PDF. Reduces file size when the same image (e.g. a logo) appears on many pages. Default false."),
|
|
77
|
+
pdf_a: z
|
|
78
|
+
.boolean()
|
|
79
|
+
.default(false)
|
|
80
|
+
.describe("Add PDF/A-1b XMP metadata to the document (best-effort conformance). Default false."),
|
|
50
81
|
})
|
|
51
82
|
.strict();
|
|
52
83
|
const GeneratePdfOutput = z.object({
|
|
@@ -55,6 +86,9 @@ const GeneratePdfOutput = z.object({
|
|
|
55
86
|
plan: z.string().optional(),
|
|
56
87
|
usage: z.number().optional(),
|
|
57
88
|
limit: z.number().optional(),
|
|
89
|
+
document_id: z.string().optional().describe("Archive ID (present when store: true)"),
|
|
90
|
+
document_url: z.string().optional().describe("Retrieval URL (present when store: true)"),
|
|
91
|
+
document_expires: z.string().optional().describe("ISO 8601 expiry timestamp (present when store: true)"),
|
|
58
92
|
});
|
|
59
93
|
function apiKey() {
|
|
60
94
|
const key = process.env.PDFPIPE_API_KEY;
|
|
@@ -110,13 +144,15 @@ function describeError(error) {
|
|
|
110
144
|
}
|
|
111
145
|
const server = new McpServer({
|
|
112
146
|
name: "pdfpipe-mcp-server",
|
|
113
|
-
version: "0.
|
|
147
|
+
version: "0.3.0",
|
|
114
148
|
});
|
|
115
149
|
server.registerTool("pdfpipe_generate_pdf", {
|
|
116
150
|
title: "Generate a PDF with PDFPipe",
|
|
117
151
|
description: `Generate a PDF document from HTML or a public URL using the PDFPipe API, and save it to disk.
|
|
118
152
|
|
|
119
|
-
Use this to produce invoices, receipts, reports, certificates, statements, or any document, from HTML you compose or a web page you point at. The rendering runs server-side in a sandboxed
|
|
153
|
+
Use this to produce invoices, receipts, reports, certificates, statements, or any document, from HTML you compose or a web page you point at. The rendering runs server-side in a sandboxed browser, so the calling agent does not need a browser.
|
|
154
|
+
|
|
155
|
+
Pass store: true to persist the PDF and receive a document_id for later retrieval via pdfpipe_get_document.
|
|
120
156
|
|
|
121
157
|
Args:
|
|
122
158
|
- html (string, optional): Raw HTML to render. Provide html OR url, not both.
|
|
@@ -125,21 +161,15 @@ Args:
|
|
|
125
161
|
- format ('A4'|'A3'|'A5'|'Letter'|'Legal'|'Tabloid', optional): Page size, default 'A4'.
|
|
126
162
|
- landscape (boolean, optional): Landscape orientation, default false.
|
|
127
163
|
- margin (string, optional): CSS length page margin, e.g. '1cm', '0', default '1cm'.
|
|
164
|
+
- header_html (string, optional): HTML for a running page header. .pageNumber, .totalPages, .date classes substituted per page.
|
|
165
|
+
- footer_html (string, optional): HTML for a running page footer. Same substitution as header_html.
|
|
166
|
+
- tabular_nums (boolean, optional): Force consistent digit widths for tables/invoices. Default false.
|
|
167
|
+
- deduplicate_images (boolean, optional): Merge duplicate image XObjects to shrink file size. Default false.
|
|
168
|
+
- pdf_a (boolean, optional): Add PDF/A-1b metadata (best-effort). Default false.
|
|
169
|
+
- store (boolean, optional): Persist the PDF for later retrieval, default false.
|
|
170
|
+
- filename (string, optional): Filename for the stored document (used with store: true).
|
|
128
171
|
|
|
129
|
-
Returns JSON
|
|
130
|
-
{
|
|
131
|
-
"output_path": string, // absolute path the PDF was written to
|
|
132
|
-
"size_bytes": number, // size of the generated PDF
|
|
133
|
-
"plan": string, // the API key's plan (e.g. "hobby")
|
|
134
|
-
"usage": number, // documents used this month after this call
|
|
135
|
-
"limit": number // monthly document limit for the plan
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
Examples:
|
|
139
|
-
- "Make an invoice PDF": html="<h1>Invoice #4012</h1>...", output_path="./invoice-4012.pdf"
|
|
140
|
-
- "Save this web page as PDF": url="https://example.com/report", output_path="./report.pdf"
|
|
141
|
-
|
|
142
|
-
Errors return a message starting with "Error:" explaining the cause (bad key, limit reached, render failure, unreachable API).`,
|
|
172
|
+
Returns JSON with output_path, size_bytes, plan, usage, limit, and optionally document_id/document_url/document_expires when store is true.`,
|
|
143
173
|
inputSchema: GeneratePdfInput.shape,
|
|
144
174
|
outputSchema: GeneratePdfOutput.shape,
|
|
145
175
|
annotations: {
|
|
@@ -163,14 +193,22 @@ Errors return a message starting with "Error:" explaining the cause (bad key, li
|
|
|
163
193
|
};
|
|
164
194
|
}
|
|
165
195
|
try {
|
|
166
|
-
const
|
|
196
|
+
const body = {
|
|
167
197
|
...(params.html ? { html: params.html } : { url: params.url }),
|
|
168
198
|
options: {
|
|
169
199
|
format: params.format,
|
|
170
200
|
landscape: params.landscape,
|
|
171
201
|
margin: params.margin,
|
|
202
|
+
...(params.header_html ? { header_html: params.header_html } : {}),
|
|
203
|
+
...(params.footer_html ? { footer_html: params.footer_html } : {}),
|
|
204
|
+
...(params.tabular_nums ? { tabular_nums: true } : {}),
|
|
205
|
+
...(params.deduplicate_images ? { deduplicate_images: true } : {}),
|
|
206
|
+
...(params.pdf_a ? { pdf_a: true } : {}),
|
|
172
207
|
},
|
|
173
|
-
|
|
208
|
+
...(params.store ? { store: true } : {}),
|
|
209
|
+
...(params.filename ? { filename: params.filename } : {}),
|
|
210
|
+
};
|
|
211
|
+
const response = await axios.post(`${BASE_URL}/v1/pdf`, body, {
|
|
174
212
|
responseType: "arraybuffer",
|
|
175
213
|
timeout: REQUEST_TIMEOUT_MS,
|
|
176
214
|
headers: {
|
|
@@ -190,12 +228,16 @@ Errors return a message starting with "Error:" explaining the cause (bad key, li
|
|
|
190
228
|
const n = Number(v);
|
|
191
229
|
return Number.isFinite(n) ? n : undefined;
|
|
192
230
|
};
|
|
231
|
+
const str = (v) => (typeof v === "string" && v ? v : undefined);
|
|
193
232
|
const output = {
|
|
194
233
|
output_path: absPath,
|
|
195
234
|
size_bytes: buffer.length,
|
|
196
|
-
plan: h["x-pdfpipe-plan"]
|
|
235
|
+
plan: str(h["x-pdfpipe-plan"]),
|
|
197
236
|
usage: num(h["x-pdfpipe-usage"]),
|
|
198
237
|
limit: num(h["x-pdfpipe-limit"]),
|
|
238
|
+
document_id: str(h["x-pdfpipe-document-id"]),
|
|
239
|
+
document_url: str(h["x-pdfpipe-document-url"]),
|
|
240
|
+
document_expires: str(h["x-pdfpipe-document-expires"]),
|
|
199
241
|
};
|
|
200
242
|
return {
|
|
201
243
|
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
@@ -209,6 +251,62 @@ Errors return a message starting with "Error:" explaining the cause (bad key, li
|
|
|
209
251
|
};
|
|
210
252
|
}
|
|
211
253
|
});
|
|
254
|
+
server.registerTool("pdfpipe_list_documents", {
|
|
255
|
+
title: "List archived PDFs",
|
|
256
|
+
description: "List PDFs previously stored with store: true. Returns up to 100 documents per page, newest first. Use the next_before cursor for pagination.",
|
|
257
|
+
inputSchema: z.object({
|
|
258
|
+
limit: z.number().int().min(1).max(100).default(100).describe("Max documents to return (1-100)."),
|
|
259
|
+
before: z.string().optional().describe("ISO 8601 cursor: return documents created before this timestamp. Use next_before from a previous response."),
|
|
260
|
+
}).shape,
|
|
261
|
+
annotations: { readOnlyHint: true },
|
|
262
|
+
}, async (params) => {
|
|
263
|
+
try {
|
|
264
|
+
const qs = new URLSearchParams({ limit: String(params.limit) });
|
|
265
|
+
if (params.before) qs.set("before", params.before);
|
|
266
|
+
const res = await axios.get(`${BASE_URL}/v1/documents?${qs}`, {
|
|
267
|
+
timeout: REQUEST_TIMEOUT_MS,
|
|
268
|
+
headers: { Authorization: `Bearer ${apiKey()}` },
|
|
269
|
+
});
|
|
270
|
+
return {
|
|
271
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
272
|
+
structuredContent: res.data,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
return { isError: true, content: [{ type: "text", text: describeError(error) }] };
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
server.registerTool("pdfpipe_get_document", {
|
|
280
|
+
title: "Retrieve an archived PDF",
|
|
281
|
+
description: "Download a previously archived PDF by its document_id and save it to disk.",
|
|
282
|
+
inputSchema: z.object({
|
|
283
|
+
document_id: z.string().min(1).describe("The document_id returned by pdfpipe_generate_pdf when store was true."),
|
|
284
|
+
output_path: z.string().min(1).describe("Filesystem path to write the PDF to. Parent dirs are created."),
|
|
285
|
+
}).shape,
|
|
286
|
+
annotations: { readOnlyHint: false },
|
|
287
|
+
}, async (params) => {
|
|
288
|
+
try {
|
|
289
|
+
const res = await axios.get(`${BASE_URL}/v1/documents/${encodeURIComponent(params.document_id)}`, {
|
|
290
|
+
responseType: "arraybuffer",
|
|
291
|
+
timeout: REQUEST_TIMEOUT_MS,
|
|
292
|
+
headers: { Authorization: `Bearer ${apiKey()}`, Accept: "application/pdf" },
|
|
293
|
+
});
|
|
294
|
+
const buffer = Buffer.from(res.data);
|
|
295
|
+
const absPath = isAbsolute(params.output_path)
|
|
296
|
+
? params.output_path
|
|
297
|
+
: resolve(process.cwd(), params.output_path);
|
|
298
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
299
|
+
writeFileSync(absPath, buffer);
|
|
300
|
+
const output = { output_path: absPath, size_bytes: buffer.length };
|
|
301
|
+
return {
|
|
302
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
303
|
+
structuredContent: output,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
return { isError: true, content: [{ type: "text", text: describeError(error) }] };
|
|
308
|
+
}
|
|
309
|
+
});
|
|
212
310
|
async function main() {
|
|
213
311
|
// Fail fast with a clear message if the key is missing.
|
|
214
312
|
if (!process.env.PDFPIPE_API_KEY) {
|
|
@@ -223,4 +321,3 @@ main().catch((error) => {
|
|
|
223
321
|
console.error("Fatal:", error);
|
|
224
322
|
process.exit(1);
|
|
225
323
|
});
|
|
226
|
-
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,32 +1,38 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pdfpipe-mcp-server",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for PDFPipe. Generate PDF documents (invoices, reports, certificates) from HTML or a URL in one tool call.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"pdfpipe-mcp-server": "dist/index.js"
|
|
9
|
-
},
|
|
10
|
-
"files": ["dist"],
|
|
11
|
-
"author": "Johin Johny <johinjohny144@gmail.com>",
|
|
12
|
-
"license": "MIT",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
},
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "pdfpipe-mcp-server",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "MCP server for PDFPipe. Generate PDF documents (invoices, reports, certificates) from HTML or a URL in one tool call.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pdfpipe-mcp-server": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": ["dist"],
|
|
11
|
+
"author": "Johin Johny <johinjohny144@gmail.com>",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"homepage": "https://pdfpipe.xyz",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/Johin2/pdfpipe-libraries.git",
|
|
17
|
+
"directory": "mcp"
|
|
18
|
+
},
|
|
19
|
+
"keywords": ["mcp", "pdf", "pdf-generation", "html-to-pdf", "pdfpipe", "ai-agents"],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"start": "node dist/index.js",
|
|
23
|
+
"dev": "tsx watch src/index.ts"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.6.1",
|
|
30
|
+
"axios": "^1.7.9",
|
|
31
|
+
"zod": "^4.4.3"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^25.9.3",
|
|
35
|
+
"tsx": "^4.19.2",
|
|
36
|
+
"typescript": "^5.7.2"
|
|
37
|
+
}
|
|
38
|
+
}
|