@exactpdf/mcp 0.2.10 → 0.2.12
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 +14 -1
- package/dist/run.d.ts +4 -1
- package/dist/run.js +104 -5
- package/package.json +3 -3
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -8,9 +8,11 @@ Use it when an agent needs to inspect, merge, split, rotate, compress, extract,
|
|
|
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
|
|
|
11
|
+
Developer quickstart: [https://exactpdf.com/developers](https://exactpdf.com/developers)
|
|
12
|
+
|
|
11
13
|
## Setup
|
|
12
14
|
|
|
13
|
-
1. Create an API key at [exactpdf.com](https://exactpdf.com)
|
|
15
|
+
1. Create an API key at [max.exactpdf.com/account](https://max.exactpdf.com/account). New accounts get **20 free test credits**. Buy one-time credit packs only after the workflow works; no subscription is required for API/MCP.
|
|
14
16
|
2. Export:
|
|
15
17
|
|
|
16
18
|
```bash
|
|
@@ -60,16 +62,26 @@ Safe first run:
|
|
|
60
62
|
|
|
61
63
|
Document outputs are saved to `EXACTPDF_API_OUTPUT_DIR` or your OS temp directory. Paid document tools consume credits only on successful output. Async speech jobs are metered by generated minutes.
|
|
62
64
|
|
|
65
|
+
Credit model:
|
|
66
|
+
|
|
67
|
+
- Free probes: checklist, account, PDF info, voice preview, speech cost estimate, and job polling.
|
|
68
|
+
- New accounts: 20 free test credits for API/MCP workflows.
|
|
69
|
+
- Standard document tools: 1 credit only after a successful output.
|
|
70
|
+
- Long audio jobs: metered by generated minutes.
|
|
71
|
+
- One-time packs: available in Max Account when you need more; no subscription required for API/MCP.
|
|
72
|
+
|
|
63
73
|
| Tool | Endpoint | Credits |
|
|
64
74
|
|------|----------|--------:|
|
|
65
75
|
| `exactpdf_first_run_checklist` | local guidance | 0 |
|
|
66
76
|
| `exactpdf_account` | GET /api/v1/account | 0 |
|
|
67
77
|
| `exactpdf_pdf_info` | POST /api/v1/pdf-info | 0 |
|
|
68
78
|
| `exactpdf_merge_pdfs` | POST /api/v1/merge | 1 |
|
|
79
|
+
| `exactpdf_merge_and_compress_pdfs` | POST /api/v1/merge-compress | 1 |
|
|
69
80
|
| `exactpdf_split_pdf` | POST /api/v1/split | 1 |
|
|
70
81
|
| `exactpdf_rotate_pdf` | POST /api/v1/rotate | 1 |
|
|
71
82
|
| `exactpdf_compress_pdf` | POST /api/v1/compress | 1 |
|
|
72
83
|
| `exactpdf_images_to_pdf` | POST /api/v1/images-to-pdf | 1 |
|
|
84
|
+
| `exactpdf_images_to_pdf_and_compress` | POST /api/v1/images-to-pdf-compress | 1 |
|
|
73
85
|
| `exactpdf_extract_text` | POST /api/v1/extract-text | 1 |
|
|
74
86
|
| `exactpdf_pdf_structured_markdown` | POST /api/v1/pdf-structured-markdown | 1 |
|
|
75
87
|
| `exactpdf_estimate_speech_cost` | local estimate | 0 |
|
|
@@ -149,6 +161,7 @@ The package must exist on the public npm registry before the MCP Registry can ve
|
|
|
149
161
|
|
|
150
162
|
## Docs
|
|
151
163
|
|
|
164
|
+
- [https://exactpdf.com/developers](https://exactpdf.com/developers)
|
|
152
165
|
- [https://exactpdf.com/docs/api](https://exactpdf.com/docs/api)
|
|
153
166
|
|
|
154
167
|
## License
|
package/dist/run.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* ExactPDF MCP — stdio server for Cursor / Claude Desktop / Codex.
|
|
4
|
-
* Requires EXACTPDF_API_KEY (sk_live_…) from exactpdf.com
|
|
4
|
+
* Requires EXACTPDF_API_KEY (sk_live_…) from max.exactpdf.com/account.
|
|
5
|
+
* New accounts get free test credits; one-time credit packs are available
|
|
6
|
+
* after the first workflow works.
|
|
5
7
|
*
|
|
8
|
+
* @see https://exactpdf.com/developers
|
|
6
9
|
* @see https://exactpdf.com/docs/api
|
|
7
10
|
*/
|
|
8
11
|
export {};
|
package/dist/run.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* ExactPDF MCP — stdio server for Cursor / Claude Desktop / Codex.
|
|
4
|
-
* Requires EXACTPDF_API_KEY (sk_live_…) from exactpdf.com
|
|
4
|
+
* Requires EXACTPDF_API_KEY (sk_live_…) from max.exactpdf.com/account.
|
|
5
|
+
* New accounts get free test credits; one-time credit packs are available
|
|
6
|
+
* after the first workflow works.
|
|
5
7
|
*
|
|
8
|
+
* @see https://exactpdf.com/developers
|
|
6
9
|
* @see https://exactpdf.com/docs/api
|
|
7
10
|
*/
|
|
8
11
|
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
@@ -20,12 +23,12 @@ function outputDir() {
|
|
|
20
23
|
function requireKey() {
|
|
21
24
|
const k = process.env.EXACTPDF_API_KEY?.trim();
|
|
22
25
|
if (!k) {
|
|
23
|
-
throw new Error('EXACTPDF_API_KEY is not set. Create a key at exactpdf.com
|
|
26
|
+
throw new Error('EXACTPDF_API_KEY is not set. Create a key with free test credits at https://max.exactpdf.com/account, then export EXACTPDF_API_KEY=sk_live_…. Quickstart: https://exactpdf.com/developers');
|
|
24
27
|
}
|
|
25
28
|
return k;
|
|
26
29
|
}
|
|
27
|
-
const server = new McpServer({ name: 'exactpdf', version: '0.2.
|
|
28
|
-
instructions: 'ExactPDF turns local PDFs into production outputs for agents: inspect metadata, merge/split/rotate/compress, extract text/structured Markdown, create voice previews, and queue async PDF speech/audiobook/translation/presentation narration jobs. Start with exactpdf_first_run_checklist, then exactpdf_account and exactpdf_pdf_info before paid work. Free tools: first-run checklist, account, pdf-info, voice-preview, speech cost estimate. Standard document tools cost 1 credit on success. Async speech jobs are metered by generated minutes. Use absolute local file paths; binary outputs are saved to EXACTPDF_API_OUTPUT_DIR or the OS temp directory. For async jobs, submit first, then poll exactpdf_get_speech_job, then download with exactpdf_download_audio when succeeded.',
|
|
30
|
+
const server = new McpServer({ name: 'exactpdf', version: '0.2.12' }, {
|
|
31
|
+
instructions: 'ExactPDF turns local PDFs into production outputs for agents: inspect metadata, merge/split/rotate/compress, create portal-ready merge/images workflows, extract text/structured Markdown, create voice previews, and queue async PDF speech/audiobook/translation/presentation narration jobs. 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, speech cost estimate. Standard document tools cost 1 credit on success, including launch portal-ready workflows. Async speech jobs are metered by generated minutes. Use absolute local file paths; binary outputs are saved to EXACTPDF_API_OUTPUT_DIR or the OS temp directory. For async jobs, submit first, then poll exactpdf_get_speech_job, then download with exactpdf_download_audio when succeeded.',
|
|
29
32
|
});
|
|
30
33
|
async function saveBinaryFromResponse(res, prefix, fallbackExt) {
|
|
31
34
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
@@ -47,6 +50,16 @@ function extensionFromContentType(contentType, fallback) {
|
|
|
47
50
|
return 'pdf';
|
|
48
51
|
return fallback;
|
|
49
52
|
}
|
|
53
|
+
function targetStatus(res) {
|
|
54
|
+
const target = res.headers.get('x-target-bytes');
|
|
55
|
+
if (!target)
|
|
56
|
+
return 'No target_bytes requested.';
|
|
57
|
+
const reached = res.headers.get('x-target-reached') ?? 'unknown';
|
|
58
|
+
const original = res.headers.get('x-original-bytes') ?? '?';
|
|
59
|
+
const output = res.headers.get('x-output-bytes') ?? '?';
|
|
60
|
+
const mode = res.headers.get('x-compression-mode') ?? 'best_effort';
|
|
61
|
+
return `Target bytes: ${target}\nTarget reached: ${reached}\nOriginal bytes: ${original}\nOutput bytes: ${output}\nCompression mode: ${mode}`;
|
|
62
|
+
}
|
|
50
63
|
server.registerTool('exactpdf_first_run_checklist', {
|
|
51
64
|
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.',
|
|
52
65
|
inputSchema: z.object({}),
|
|
@@ -58,7 +71,8 @@ server.registerTool('exactpdf_first_run_checklist', {
|
|
|
58
71
|
ok: true,
|
|
59
72
|
setup: {
|
|
60
73
|
required_env: 'EXACTPDF_API_KEY=sk_live_...',
|
|
61
|
-
create_key_url:
|
|
74
|
+
create_key_url: 'https://max.exactpdf.com/account',
|
|
75
|
+
developer_quickstart: `${BASE}/developers`,
|
|
62
76
|
optional_output_dir: 'EXACTPDF_API_OUTPUT_DIR=/absolute/output/folder',
|
|
63
77
|
docs: `${BASE}/docs/api`,
|
|
64
78
|
openapi: `${BASE}/openapi.json`,
|
|
@@ -71,7 +85,10 @@ server.registerTool('exactpdf_first_run_checklist', {
|
|
|
71
85
|
],
|
|
72
86
|
credit_model: {
|
|
73
87
|
free: ['exactpdf_first_run_checklist', 'exactpdf_account', 'exactpdf_pdf_info', 'exactpdf_voice_preview', 'exactpdf_estimate_speech_cost', 'job polling'],
|
|
88
|
+
starter_trial: 'New accounts get 20 free test credits for API/MCP workflows.',
|
|
74
89
|
standard_document_tools: '1 credit only after successful output',
|
|
90
|
+
credit_packs: 'Buy one-time credit packs when the workflow works; subscription is not required for API/MCP.',
|
|
91
|
+
launch_portal_ready_workflows: '1 credit on successful output: exactpdf_merge_and_compress_pdfs, exactpdf_images_to_pdf_and_compress',
|
|
75
92
|
speech_audiobook_presentation: '1 credit per generated minute',
|
|
76
93
|
translate_and_speak: '3 credits per generated minute',
|
|
77
94
|
},
|
|
@@ -101,6 +118,44 @@ server.registerTool('exactpdf_account', {
|
|
|
101
118
|
content: [{ type: 'text', text: `HTTP ${res.status}\n${text}` }],
|
|
102
119
|
};
|
|
103
120
|
});
|
|
121
|
+
server.registerTool('exactpdf_merge_and_compress_pdfs', {
|
|
122
|
+
description: 'Portal-ready workflow: merge 2-20 local PDFs in order, then best-effort repack/compress the final PDF for an upload limit. Costs 1 launch credit on success. Returns measured output size and whether target_bytes was reached; exact target compression for image-heavy PDFs may require Max/browser workflows.',
|
|
123
|
+
inputSchema: z.object({
|
|
124
|
+
paths: z.array(z.string()).min(2).describe('Absolute paths to PDF files on this machine, in merge order.'),
|
|
125
|
+
target_bytes: z.number().int().positive().optional().describe('Optional upload limit, e.g. 1048576 for 1MB or 2097152 for 2MB.'),
|
|
126
|
+
}),
|
|
127
|
+
}, async ({ paths, target_bytes }) => {
|
|
128
|
+
const key = requireKey();
|
|
129
|
+
const form = new FormData();
|
|
130
|
+
for (const p of paths) {
|
|
131
|
+
const buf = await readFile(p);
|
|
132
|
+
form.append('file', new Blob([buf], { type: 'application/pdf' }), basename(p));
|
|
133
|
+
}
|
|
134
|
+
if (typeof target_bytes === 'number')
|
|
135
|
+
form.append('target_bytes', String(target_bytes));
|
|
136
|
+
const res = await fetch(`${BASE}/api/v1/merge-compress`, {
|
|
137
|
+
method: 'POST',
|
|
138
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
139
|
+
body: form,
|
|
140
|
+
});
|
|
141
|
+
const credits = res.headers.get('x-credits-remaining') ?? '?';
|
|
142
|
+
if (!res.ok) {
|
|
143
|
+
const errBody = await res.text();
|
|
144
|
+
return {
|
|
145
|
+
content: [{ type: 'text', text: `merge-compress failed HTTP ${res.status}. Credits: ${credits}\n${errBody.slice(0, 8000)}` }],
|
|
146
|
+
isError: true,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const outPath = await saveBinaryFromResponse(res, 'exactpdf-merged-compressed', 'pdf');
|
|
150
|
+
return {
|
|
151
|
+
content: [
|
|
152
|
+
{
|
|
153
|
+
type: 'text',
|
|
154
|
+
text: `Merged and best-effort compressed ${paths.length} PDFs → ${outPath}\nCredits remaining: ${credits}\n${targetStatus(res)}`,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
});
|
|
104
159
|
server.registerTool('exactpdf_merge_pdfs', {
|
|
105
160
|
description: 'Merge 2-20 local PDF files into one PDF in the exact order provided. Costs 1 credit only on successful output. Saves the merged PDF locally and returns its path.',
|
|
106
161
|
inputSchema: z.object({
|
|
@@ -289,6 +344,50 @@ server.registerTool('exactpdf_compress_pdf', {
|
|
|
289
344
|
],
|
|
290
345
|
};
|
|
291
346
|
});
|
|
347
|
+
server.registerTool('exactpdf_images_to_pdf_and_compress', {
|
|
348
|
+
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.',
|
|
349
|
+
inputSchema: z.object({
|
|
350
|
+
paths: z.array(z.string()).min(1).describe('Absolute paths to PNG or JPEG files, in PDF order.'),
|
|
351
|
+
target_bytes: z.number().int().positive().optional().describe('Optional upload limit, e.g. 524288 for 500KB or 1048576 for 1MB.'),
|
|
352
|
+
}),
|
|
353
|
+
}, async ({ paths, target_bytes }) => {
|
|
354
|
+
const key = requireKey();
|
|
355
|
+
const form = new FormData();
|
|
356
|
+
for (const p of paths) {
|
|
357
|
+
const buf = await readFile(p);
|
|
358
|
+
const lower = p.toLowerCase();
|
|
359
|
+
const type = lower.endsWith('.png')
|
|
360
|
+
? 'image/png'
|
|
361
|
+
: lower.endsWith('.jpg') || lower.endsWith('.jpeg')
|
|
362
|
+
? 'image/jpeg'
|
|
363
|
+
: 'application/octet-stream';
|
|
364
|
+
form.append('file', new Blob([buf], { type }), basename(p));
|
|
365
|
+
}
|
|
366
|
+
if (typeof target_bytes === 'number')
|
|
367
|
+
form.append('target_bytes', String(target_bytes));
|
|
368
|
+
const res = await fetch(`${BASE}/api/v1/images-to-pdf-compress`, {
|
|
369
|
+
method: 'POST',
|
|
370
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
371
|
+
body: form,
|
|
372
|
+
});
|
|
373
|
+
const credits = res.headers.get('x-credits-remaining') ?? '?';
|
|
374
|
+
if (!res.ok) {
|
|
375
|
+
const errBody = await res.text();
|
|
376
|
+
return {
|
|
377
|
+
content: [{ type: 'text', text: `images-to-pdf-compress failed HTTP ${res.status}. Credits: ${credits}\n${errBody.slice(0, 8000)}` }],
|
|
378
|
+
isError: true,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
const outPath = await saveBinaryFromResponse(res, 'exactpdf-images-compressed', 'pdf');
|
|
382
|
+
return {
|
|
383
|
+
content: [
|
|
384
|
+
{
|
|
385
|
+
type: 'text',
|
|
386
|
+
text: `Built and best-effort compressed PDF from ${paths.length} images → ${outPath}\nCredits remaining: ${credits}\n${targetStatus(res)}`,
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
};
|
|
390
|
+
});
|
|
292
391
|
server.registerTool('exactpdf_images_to_pdf', {
|
|
293
392
|
description: 'Convert local PNG/JPEG images into one ordered PDF. Costs 1 credit on success. Saves the resulting PDF locally and returns its path.',
|
|
294
393
|
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 — PDF
|
|
3
|
+
"version": "0.2.12",
|
|
4
|
+
"description": "MCP server for ExactPDF — agent PDF workflows, structured Markdown/RAG, merge/compress, voice previews, async audio jobs, and API credits.",
|
|
5
5
|
"mcpName": "com.exactpdf/mcp",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/run.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"publishConfig": {
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
|
-
"homepage": "https://exactpdf.com/
|
|
22
|
+
"homepage": "https://exactpdf.com/developers",
|
|
23
23
|
"bugs": {
|
|
24
24
|
"url": "https://exactpdf.com/docs/api",
|
|
25
25
|
"email": "support@exactpdf.com"
|
package/server.json
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"name": "com.exactpdf/mcp",
|
|
4
4
|
"title": "ExactPDF",
|
|
5
5
|
"description": "Agent-facing PDF API: merge, split, rotate, compress, images, metadata, text, Markdown, voice previews, async speech/audiobook, multilingual speech, and presentation narration jobs.",
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.12",
|
|
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.12",
|
|
13
13
|
"transport": {
|
|
14
14
|
"type": "stdio"
|
|
15
15
|
}
|