@exactpdf/mcp 0.2.13 → 0.2.14
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 +13 -3
- package/dist/run.js +135 -39
- package/package.json +6 -2
- package/server.json +3 -3
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Model Context Protocol (stdio) server for **ExactPDF** — production PDF tools for **Cursor**, **Claude Desktop**, **Codex**, and any MCP client.
|
|
6
6
|
|
|
7
|
-
Use it when an agent needs to inspect, merge, split, rotate, compress, extract text, export structured Markdown for RAG, or build images-to-PDF workflows without writing fragile PDF plumbing.
|
|
7
|
+
Use it when an agent needs to inspect, merge, split, rotate, compress to upload limits, extract text, export PDF JSON, export structured Markdown for RAG, or build images-to-PDF workflows without writing fragile PDF plumbing.
|
|
8
8
|
|
|
9
9
|
**Design principle:** every tool should work on first run, explain credit cost before risky work, return a local output path for files, and expose precise HTTP/API errors when something fails.
|
|
10
10
|
|
|
@@ -82,10 +82,18 @@ Credit model:
|
|
|
82
82
|
| `exactpdf_split_pdf` | POST /api/v1/split | 1 |
|
|
83
83
|
| `exactpdf_rotate_pdf` | POST /api/v1/rotate | 1 |
|
|
84
84
|
| `exactpdf_compress_pdf` | POST /api/v1/compress | 1 |
|
|
85
|
+
| `exactpdf_compress_pdf_to_target` | POST /api/v1/compress + target_bytes | 1 |
|
|
86
|
+
| `exactpdf_compress_pdf_to_100kb` | POST /api/v1/compress + 102400 target_bytes | 1 |
|
|
87
|
+
| `exactpdf_compress_pdf_to_200kb` | POST /api/v1/compress + 204800 target_bytes | 1 |
|
|
88
|
+
| `exactpdf_compress_pdf_to_500kb` | POST /api/v1/compress + 512000 target_bytes | 1 |
|
|
89
|
+
| `exactpdf_compress_pdf_to_1mb` | POST /api/v1/compress + 1048576 target_bytes | 1 |
|
|
90
|
+
| `exactpdf_pdf_to_images` | planned /api/v1/pdf-to-images | backend required |
|
|
85
91
|
| `exactpdf_images_to_pdf` | POST /api/v1/images-to-pdf | 1 |
|
|
86
92
|
| `exactpdf_images_to_pdf_and_compress` | POST /api/v1/images-to-pdf-compress | 1 |
|
|
87
93
|
| `exactpdf_extract_text` | POST /api/v1/extract-text | 1 |
|
|
94
|
+
| `exactpdf_pdf_to_json` | POST /api/v1/pdf-to-json | 1 |
|
|
88
95
|
| `exactpdf_pdf_structured_markdown` | POST /api/v1/pdf-structured-markdown | 1 |
|
|
96
|
+
| `exactpdf_ocr_pdf` | planned /api/v1/ocr | backend required |
|
|
89
97
|
| `exactpdf_estimate_speech_cost` | local estimate | 0 |
|
|
90
98
|
| `exactpdf_voice_preview` | POST /api/v1/voice-preview | 0 |
|
|
91
99
|
| `exactpdf_pdf_to_speech` | POST /api/v1/pdf-to-speech | disabled / hardening |
|
|
@@ -102,8 +110,10 @@ Credit model:
|
|
|
102
110
|
| Tool family | What the agent provides | What ExactPDF returns |
|
|
103
111
|
| --- | --- | --- |
|
|
104
112
|
| Account / inspect | API key, local PDF path | JSON balance, page count, metadata |
|
|
105
|
-
| PDF operations | Absolute local PDF paths | Saved PDF/ZIP path plus remaining credits |
|
|
106
|
-
|
|
|
113
|
+
| PDF operations | Absolute local PDF paths | Saved PDF/ZIP path plus remaining credits and measured size headers |
|
|
114
|
+
| Target-size compression | Absolute local PDF path, target bytes or preset | Saved PDF plus `X-Target-Reached`, original bytes, output bytes |
|
|
115
|
+
| Text / JSON / Markdown | Absolute local PDF path | JSON text/pages/metadata or Markdown, page count, structured content |
|
|
116
|
+
| Backend-required PDF images/OCR | Desired path/options | Clear backend-required message plus live browser tool URL |
|
|
107
117
|
| Voice preview | Short text, voice style/language | Saved MP3/WAV preview, no credit charge |
|
|
108
118
|
| Audio planning | Short text, character count, rough PDF size | Voice preview or estimated minutes/credits |
|
|
109
119
|
| Legacy job polling/download | `job_id` | progress/errors/result URL or saved audio/ZIP for jobs created before the hardening gate |
|
package/dist/run.js
CHANGED
|
@@ -28,8 +28,15 @@ function requireKey() {
|
|
|
28
28
|
return k;
|
|
29
29
|
}
|
|
30
30
|
const BACKGROUND_AUDIO_HARDENING_MESSAGE = 'Background PDF speech, audiobook, translate-and-speak, and presentation narration jobs are currently disabled for paid workloads while ExactPDF finishes dedicated worker hardening and queue-completion smoke. Use exactpdf_voice_preview for short free voice samples and exactpdf_estimate_speech_cost for planning only. Do not promise MP3/audiobook delivery through MCP until the API returns enabled status again.';
|
|
31
|
-
const
|
|
32
|
-
|
|
31
|
+
const BACKEND_REQUIRED_TOOL_MESSAGE = 'This MCP tool is intentionally exposed as backend-required, not live. ExactPDF has the browser workflow today, but the API/MCP endpoint needs a dedicated server renderer/OCR worker before agents can run it as infrastructure. Do not present this as a completed API workflow yet.';
|
|
32
|
+
const TARGET_SIZE_PRESETS = {
|
|
33
|
+
'100kb': 100 * 1024,
|
|
34
|
+
'200kb': 200 * 1024,
|
|
35
|
+
'500kb': 500 * 1024,
|
|
36
|
+
'1mb': 1024 * 1024,
|
|
37
|
+
};
|
|
38
|
+
const server = new McpServer({ name: 'exactpdf', version: '0.2.14' }, {
|
|
39
|
+
instructions: 'ExactPDF turns local PDFs into production outputs for agents: inspect metadata, merge/split/rotate/compress, target-size best-effort compression, create portal-ready merge/images workflows, extract text, export PDF JSON, export structured Markdown for RAG, and create short voice previews. Start with exactpdf_first_run_checklist, then exactpdf_account and exactpdf_pdf_info before paid work. New users can create a key with free test credits at https://max.exactpdf.com/account; developer quickstart: https://exactpdf.com/developers. Free tools: first-run checklist, account, pdf-info, voice-preview, and speech cost estimate. Standard document tools cost 1 credit on success, including launch portal-ready workflows. PDF-to-images and OCR are visible as backend-required placeholders until server renderer/OCR worker endpoints ship. Background PDF speech/audiobook/translation/presentation jobs are in production hardening and must be treated as planning-only until worker smoke passes. Use absolute local file paths; binary outputs are saved to EXACTPDF_API_OUTPUT_DIR or the OS temp directory.',
|
|
33
40
|
});
|
|
34
41
|
async function saveBinaryFromResponse(res, prefix, fallbackExt) {
|
|
35
42
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
@@ -61,6 +68,40 @@ function targetStatus(res) {
|
|
|
61
68
|
const mode = res.headers.get('x-compression-mode') ?? 'best_effort';
|
|
62
69
|
return `Target bytes: ${target}\nTarget reached: ${reached}\nOriginal bytes: ${original}\nOutput bytes: ${output}\nCompression mode: ${mode}`;
|
|
63
70
|
}
|
|
71
|
+
async function compressPdfToTarget(path, target_bytes) {
|
|
72
|
+
const key = requireKey();
|
|
73
|
+
const form = new FormData();
|
|
74
|
+
form.append('file', new Blob([await readFile(path)], { type: 'application/pdf' }), basename(path));
|
|
75
|
+
if (typeof target_bytes === 'number')
|
|
76
|
+
form.append('target_bytes', String(target_bytes));
|
|
77
|
+
const res = await fetch(`${BASE}/api/v1/compress`, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
80
|
+
body: form,
|
|
81
|
+
});
|
|
82
|
+
const credits = res.headers.get('x-credits-remaining') ?? '?';
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
const errBody = await res.text();
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: 'text',
|
|
89
|
+
text: `Compress failed HTTP ${res.status}. Credits: ${credits}\n${errBody.slice(0, 4000)}`,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
isError: true,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const outPath = await saveBinaryFromResponse(res, 'exactpdf-compressed', 'pdf');
|
|
96
|
+
return {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: 'text',
|
|
100
|
+
text: `Compressed → ${outPath}\nCredits remaining: ${credits}\n${targetStatus(res)}`,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
64
105
|
server.registerTool('exactpdf_first_run_checklist', {
|
|
65
106
|
description: 'Return the safest first-run sequence for ExactPDF MCP clients, including API key setup, free probe tools, credit rules, output directory behavior, and docs links. Free; no API key required.',
|
|
66
107
|
inputSchema: z.object({}),
|
|
@@ -305,46 +346,67 @@ server.registerTool('exactpdf_rotate_pdf', {
|
|
|
305
346
|
};
|
|
306
347
|
});
|
|
307
348
|
server.registerTool('exactpdf_compress_pdf', {
|
|
308
|
-
description: 'Compress/repack a local PDF for smaller delivery. Costs 1 credit on success.
|
|
349
|
+
description: 'Compress/repack a local PDF for smaller delivery. Costs 1 credit on success. Optional target_bytes returns measured target headers. Best-effort, not guaranteed exact-size compression.',
|
|
309
350
|
inputSchema: z.object({
|
|
310
351
|
path: z.string().describe('Absolute path to a PDF file'),
|
|
352
|
+
target_bytes: z.number().int().positive().optional().describe('Optional upload limit such as 102400 for 100KB, 204800 for 200KB, 512000 for 500KB, or 1048576 for 1MB.'),
|
|
311
353
|
}),
|
|
312
|
-
}, async ({ path }) =>
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
354
|
+
}, async ({ path, target_bytes }) => compressPdfToTarget(path, target_bytes));
|
|
355
|
+
server.registerTool('exactpdf_compress_pdf_to_target', {
|
|
356
|
+
description: 'Best-effort compress/repack a local PDF toward a target size such as 100KB, 200KB, 500KB, or 1MB. Costs 1 credit on success and reports whether target_bytes was reached.',
|
|
357
|
+
inputSchema: z.object({
|
|
358
|
+
path: z.string().describe('Absolute path to a PDF file'),
|
|
359
|
+
target_bytes: z.number().int().positive().describe('Upload limit in bytes, e.g. 102400 for 100KB or 1048576 for 1MB.'),
|
|
360
|
+
}),
|
|
361
|
+
}, async ({ path, target_bytes }) => compressPdfToTarget(path, target_bytes));
|
|
362
|
+
for (const [label, targetBytes] of Object.entries(TARGET_SIZE_PRESETS)) {
|
|
363
|
+
server.registerTool(`exactpdf_compress_pdf_to_${label}`, {
|
|
364
|
+
description: `Best-effort compress/repack a local PDF toward ${label.toUpperCase()}. Costs 1 credit on success and reports whether the target was reached.`,
|
|
365
|
+
inputSchema: z.object({
|
|
366
|
+
path: z.string().describe('Absolute path to a PDF file'),
|
|
367
|
+
}),
|
|
368
|
+
}, async ({ path }) => compressPdfToTarget(path, targetBytes));
|
|
369
|
+
}
|
|
370
|
+
server.registerTool('exactpdf_pdf_to_images', {
|
|
371
|
+
description: 'Backend-required placeholder for PDF-to-images API/MCP. Use the live browser tool at https://exactpdf.com/tools/pdf-to-images until the server renderer endpoint ships.',
|
|
372
|
+
inputSchema: z.object({
|
|
373
|
+
path: z.string().optional().describe('Absolute path to a PDF file. Not processed yet because the API endpoint is not live.'),
|
|
374
|
+
output_format: z.enum(['jpg', 'png']).optional().describe('Planned output format.'),
|
|
375
|
+
dpi: z.number().int().positive().optional().describe('Planned render DPI.'),
|
|
376
|
+
page_range: z.string().optional().describe('Planned page range, e.g. "1-3".'),
|
|
377
|
+
}),
|
|
378
|
+
}, async () => ({
|
|
379
|
+
content: [{
|
|
380
|
+
type: 'text',
|
|
381
|
+
text: JSON.stringify({
|
|
382
|
+
ok: false,
|
|
383
|
+
status: 'backend_required',
|
|
384
|
+
web_tool: `${BASE}/tools/pdf-to-images`,
|
|
385
|
+
planned_endpoint: `${BASE}/api/v1/pdf-to-images`,
|
|
386
|
+
note: BACKEND_REQUIRED_TOOL_MESSAGE,
|
|
387
|
+
}, null, 2),
|
|
388
|
+
}],
|
|
389
|
+
isError: true,
|
|
390
|
+
}));
|
|
391
|
+
server.registerTool('exactpdf_ocr_pdf', {
|
|
392
|
+
description: 'Backend-required placeholder for OCR API/MCP. Use the live browser OCR page at https://exactpdf.com/tools/ocr-pdf until the server OCR worker endpoint ships.',
|
|
393
|
+
inputSchema: z.object({
|
|
394
|
+
path: z.string().optional().describe('Absolute path to a PDF file. Not processed yet because the API endpoint is not live.'),
|
|
395
|
+
language: z.string().optional().describe('Planned OCR language hint such as eng, hin, spa.'),
|
|
396
|
+
}),
|
|
397
|
+
}, async () => ({
|
|
398
|
+
content: [{
|
|
399
|
+
type: 'text',
|
|
400
|
+
text: JSON.stringify({
|
|
401
|
+
ok: false,
|
|
402
|
+
status: 'backend_required',
|
|
403
|
+
web_tool: `${BASE}/tools/ocr-pdf`,
|
|
404
|
+
planned_endpoint: `${BASE}/api/v1/ocr`,
|
|
405
|
+
note: BACKEND_REQUIRED_TOOL_MESSAGE,
|
|
406
|
+
}, null, 2),
|
|
407
|
+
}],
|
|
408
|
+
isError: true,
|
|
409
|
+
}));
|
|
348
410
|
server.registerTool('exactpdf_images_to_pdf_and_compress', {
|
|
349
411
|
description: 'Portal-ready workflow: convert local PNG/JPEG images into one ordered PDF, then best-effort repack/compress for an upload limit. Costs 1 launch credit on success. Returns measured output size and whether target_bytes was reached.',
|
|
350
412
|
inputSchema: z.object({
|
|
@@ -535,6 +597,40 @@ server.registerTool('exactpdf_pdf_structured_markdown', {
|
|
|
535
597
|
content: [{ type: 'text', text: `Credits remaining: ${credits}\n${raw.slice(0, 120_000)}` }],
|
|
536
598
|
};
|
|
537
599
|
});
|
|
600
|
+
server.registerTool('exactpdf_pdf_to_json', {
|
|
601
|
+
description: 'Extract text-layer PDF structure as JSON: metadata, per-page text, full text, page count, and extraction notes. Costs 1 credit on success. Scanned PDFs need OCR first.',
|
|
602
|
+
inputSchema: z.object({
|
|
603
|
+
path: z.string().describe('Absolute path to a PDF file'),
|
|
604
|
+
}),
|
|
605
|
+
}, async ({ path }) => {
|
|
606
|
+
const key = requireKey();
|
|
607
|
+
const form = new FormData();
|
|
608
|
+
form.append('file', new Blob([await readFile(path)], { type: 'application/pdf' }), basename(path));
|
|
609
|
+
const res = await fetch(`${BASE}/api/v1/pdf-to-json`, {
|
|
610
|
+
method: 'POST',
|
|
611
|
+
headers: {
|
|
612
|
+
Authorization: `Bearer ${key}`,
|
|
613
|
+
Accept: 'application/json',
|
|
614
|
+
},
|
|
615
|
+
body: form,
|
|
616
|
+
});
|
|
617
|
+
const credits = res.headers.get('x-credits-remaining') ?? '?';
|
|
618
|
+
const raw = await res.text();
|
|
619
|
+
if (!res.ok) {
|
|
620
|
+
return {
|
|
621
|
+
content: [
|
|
622
|
+
{
|
|
623
|
+
type: 'text',
|
|
624
|
+
text: `pdf-to-json failed HTTP ${res.status}. Credits: ${credits}\n${raw.slice(0, 8000)}`,
|
|
625
|
+
},
|
|
626
|
+
],
|
|
627
|
+
isError: true,
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
return {
|
|
631
|
+
content: [{ type: 'text', text: `Credits remaining: ${credits}\n${raw.slice(0, 120_000)}` }],
|
|
632
|
+
};
|
|
633
|
+
});
|
|
538
634
|
server.registerTool('exactpdf_pdf_to_audiobook', {
|
|
539
635
|
description: 'Planning-only placeholder while background audio workers are hardened. Do not use for paid workloads; use exactpdf_voice_preview and exactpdf_estimate_speech_cost instead.',
|
|
540
636
|
inputSchema: z.object({
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exactpdf/mcp",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "MCP server for ExactPDF — agent PDF workflows, structured Markdown/RAG, merge/split
|
|
3
|
+
"version": "0.2.14",
|
|
4
|
+
"description": "MCP server for ExactPDF — agent PDF workflows, target-size compression, structured Markdown/RAG, PDF JSON, merge/split, images-to-PDF, voice previews, and API credits.",
|
|
5
5
|
"mcpName": "com.exactpdf/mcp",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/run.js",
|
|
@@ -48,7 +48,11 @@
|
|
|
48
48
|
"pdf-mcp",
|
|
49
49
|
"pdf-compression",
|
|
50
50
|
"pdf-to-markdown",
|
|
51
|
+
"pdf-to-json",
|
|
52
|
+
"pdf-to-images",
|
|
53
|
+
"target-size-compression",
|
|
51
54
|
"rag",
|
|
55
|
+
"ocr",
|
|
52
56
|
"images-to-pdf",
|
|
53
57
|
"merge-pdf",
|
|
54
58
|
"split-pdf"
|
package/server.json
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "com.exactpdf/mcp",
|
|
4
4
|
"title": "ExactPDF",
|
|
5
|
-
"description": "Agent-facing PDF API: merge, split, rotate, compress, images-to-PDF, metadata, text extraction, structured Markdown/RAG, voice previews, and API credit checks.
|
|
6
|
-
"version": "0.2.
|
|
5
|
+
"description": "Agent-facing PDF API: merge, split, rotate, compress, target-size compression, images-to-PDF, metadata, text extraction, PDF JSON, structured Markdown/RAG, voice previews, and API credit checks. PDF-to-images/OCR require backend workers; background audio jobs are hardening-only until worker smoke passes.",
|
|
6
|
+
"version": "0.2.14",
|
|
7
7
|
"websiteUrl": "https://exactpdf.com/docs/api",
|
|
8
8
|
"packages": [
|
|
9
9
|
{
|
|
10
10
|
"registryType": "npm",
|
|
11
11
|
"identifier": "@exactpdf/mcp",
|
|
12
|
-
"version": "0.2.
|
|
12
|
+
"version": "0.2.14",
|
|
13
13
|
"transport": {
|
|
14
14
|
"type": "stdio"
|
|
15
15
|
}
|