@exactpdf/mcp 0.2.12 → 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 +35 -44
- package/dist/run.js +153 -214
- package/package.json +16 -3
- 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,
|
|
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
|
|
|
@@ -57,17 +57,19 @@ Safe first run:
|
|
|
57
57
|
2. exactpdf_account
|
|
58
58
|
3. exactpdf_pdf_info(path="/absolute/path/document.pdf")
|
|
59
59
|
4. exactpdf_voice_preview(text="Short sample...", voice_style="professional")
|
|
60
|
-
5. exactpdf_estimate_speech_cost(path="/absolute/path/document.pdf", mode="audiobook")
|
|
60
|
+
5. exactpdf_estimate_speech_cost(path="/absolute/path/document.pdf", mode="audiobook") # planning only while background audio is hardened
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
Document outputs are saved to `EXACTPDF_API_OUTPUT_DIR` or your OS temp directory. Paid document tools consume credits only on successful output.
|
|
63
|
+
Document outputs are saved to `EXACTPDF_API_OUTPUT_DIR` or your OS temp directory. Paid document tools consume credits only on successful output.
|
|
64
|
+
|
|
65
|
+
Background PDF speech, audiobook, translate-and-speak, and presentation narration submissions are currently disabled for paid workloads while ExactPDF finishes dedicated worker hardening and queue-completion smoke. `exactpdf_voice_preview` remains available for short free samples, and `exactpdf_estimate_speech_cost` is planning-only until the API is re-enabled.
|
|
64
66
|
|
|
65
67
|
Credit model:
|
|
66
68
|
|
|
67
69
|
- Free probes: checklist, account, PDF info, voice preview, speech cost estimate, and job polling.
|
|
68
70
|
- New accounts: 20 free test credits for API/MCP workflows.
|
|
69
71
|
- Standard document tools: 1 credit only after a successful output.
|
|
70
|
-
-
|
|
72
|
+
- Background audio jobs: planning-only until dedicated worker smoke passes.
|
|
71
73
|
- One-time packs: available in Max Account when you need more; no subscription required for API/MCP.
|
|
72
74
|
|
|
73
75
|
| Tool | Endpoint | Credits |
|
|
@@ -80,71 +82,60 @@ Credit model:
|
|
|
80
82
|
| `exactpdf_split_pdf` | POST /api/v1/split | 1 |
|
|
81
83
|
| `exactpdf_rotate_pdf` | POST /api/v1/rotate | 1 |
|
|
82
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 |
|
|
83
91
|
| `exactpdf_images_to_pdf` | POST /api/v1/images-to-pdf | 1 |
|
|
84
92
|
| `exactpdf_images_to_pdf_and_compress` | POST /api/v1/images-to-pdf-compress | 1 |
|
|
85
93
|
| `exactpdf_extract_text` | POST /api/v1/extract-text | 1 |
|
|
94
|
+
| `exactpdf_pdf_to_json` | POST /api/v1/pdf-to-json | 1 |
|
|
86
95
|
| `exactpdf_pdf_structured_markdown` | POST /api/v1/pdf-structured-markdown | 1 |
|
|
96
|
+
| `exactpdf_ocr_pdf` | planned /api/v1/ocr | backend required |
|
|
87
97
|
| `exactpdf_estimate_speech_cost` | local estimate | 0 |
|
|
88
98
|
| `exactpdf_voice_preview` | POST /api/v1/voice-preview | 0 |
|
|
89
|
-
| `exactpdf_pdf_to_speech` | POST /api/v1/pdf-to-speech |
|
|
90
|
-
| `exactpdf_pdf_to_audiobook` | POST /api/v1/pdf-to-audiobook |
|
|
91
|
-
| `exactpdf_generate_audiobook` | POST /api/v1/generate-audiobook |
|
|
92
|
-
| `exactpdf_translate_and_speak` | POST /api/v1/translate-and-speak |
|
|
93
|
-
| `exactpdf_presentation_narration` | POST /api/v1/presentation-narration |
|
|
94
|
-
| `exactpdf_job_status` | GET /api/v1/jobs/:id | 0 |
|
|
95
|
-
| `exactpdf_get_speech_job` | GET /api/v1/speech-jobs/:id | 0 |
|
|
96
|
-
| `exactpdf_download_audio` | GET /api/v1/speech-jobs/:id/download | 0 |
|
|
99
|
+
| `exactpdf_pdf_to_speech` | POST /api/v1/pdf-to-speech | disabled / hardening |
|
|
100
|
+
| `exactpdf_pdf_to_audiobook` | POST /api/v1/pdf-to-audiobook | disabled / hardening |
|
|
101
|
+
| `exactpdf_generate_audiobook` | POST /api/v1/generate-audiobook | disabled / hardening |
|
|
102
|
+
| `exactpdf_translate_and_speak` | POST /api/v1/translate-and-speak | disabled / hardening |
|
|
103
|
+
| `exactpdf_presentation_narration` | POST /api/v1/presentation-narration | disabled / hardening |
|
|
104
|
+
| `exactpdf_job_status` | GET /api/v1/jobs/:id | 0 / legacy polling |
|
|
105
|
+
| `exactpdf_get_speech_job` | GET /api/v1/speech-jobs/:id | 0 / legacy polling |
|
|
106
|
+
| `exactpdf_download_audio` | GET /api/v1/speech-jobs/:id/download | 0 / legacy download |
|
|
97
107
|
|
|
98
108
|
## Tool behavior
|
|
99
109
|
|
|
100
110
|
| Tool family | What the agent provides | What ExactPDF returns |
|
|
101
111
|
| --- | --- | --- |
|
|
102
112
|
| Account / inspect | API key, local PDF path | JSON balance, page count, metadata |
|
|
103
|
-
| PDF operations | Absolute local PDF paths | Saved PDF/ZIP path plus remaining credits |
|
|
104
|
-
|
|
|
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 |
|
|
105
117
|
| Voice preview | Short text, voice style/language | Saved MP3/WAV preview, no credit charge |
|
|
106
|
-
|
|
|
107
|
-
|
|
|
118
|
+
| Audio planning | Short text, character count, rough PDF size | Voice preview or estimated minutes/credits |
|
|
119
|
+
| Legacy job polling/download | `job_id` | progress/errors/result URL or saved audio/ZIP for jobs created before the hardening gate |
|
|
108
120
|
|
|
109
|
-
|
|
121
|
+
Background audio status:
|
|
110
122
|
|
|
111
123
|
```text
|
|
112
|
-
1. exactpdf_estimate_speech_cost(path="/abs/book.pdf", mode="audiobook")
|
|
124
|
+
1. exactpdf_estimate_speech_cost(path="/abs/book.pdf", mode="audiobook") # planning only
|
|
113
125
|
2. exactpdf_voice_preview(text="Read the first paragraph in an audiobook tone", voice_style="audiobook")
|
|
114
|
-
3. exactpdf_generate_audiobook
|
|
115
|
-
4. exactpdf_get_speech_job(job_id="...", download=false)
|
|
116
|
-
5. exactpdf_download_audio(job_id="...") after status is succeeded
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
`exactpdf_pdf_to_speech`, `exactpdf_pdf_to_audiobook`, and `exactpdf_generate_audiobook` accept optional `callback_url` and `webhook_secret`. ExactPDF signs webhook bodies with `x-exactpdf-signature: sha256=<hmac>` over `timestamp.body`.
|
|
120
|
-
|
|
121
|
-
Multilingual speech flow:
|
|
122
|
-
|
|
123
|
-
```text
|
|
124
|
-
1. exactpdf_translate_and_speak(path="/abs/training.pdf", target_language="hi")
|
|
125
|
-
2. exactpdf_job_status(job_id="...", download=false)
|
|
126
|
-
3. exactpdf_job_status(job_id="...", download=true) after status is succeeded
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
`segmentation_mode="pages"` is useful for presentation narration because each PDF page becomes a trackable audio section.
|
|
130
|
-
|
|
131
|
-
Presentation narration flow:
|
|
132
|
-
|
|
133
|
-
```text
|
|
134
|
-
1. exactpdf_presentation_narration(path="/abs/deck.pdf")
|
|
135
|
-
2. exactpdf_job_status(job_id="...", download=true) after status is succeeded
|
|
126
|
+
3. Do not call exactpdf_generate_audiobook / exactpdf_pdf_to_speech for paid workloads until ExactPDF announces worker smoke has passed.
|
|
136
127
|
```
|
|
137
128
|
|
|
138
|
-
The
|
|
129
|
+
The disabled background audio tools intentionally return an MCP error message before submitting work. This protects users from paying for a path that is still being hardened.
|
|
139
130
|
|
|
140
131
|
## Reliability rules for agent builders
|
|
141
132
|
|
|
142
133
|
- Always call `exactpdf_account` once before paid tools.
|
|
143
134
|
- Use absolute file paths. Relative paths are client-dependent and may fail.
|
|
144
135
|
- Use `exactpdf_pdf_info` before large jobs to confirm the file is readable.
|
|
145
|
-
- Use `exactpdf_estimate_speech_cost`
|
|
146
|
-
- Use `exactpdf_voice_preview` before
|
|
147
|
-
-
|
|
136
|
+
- Use `exactpdf_estimate_speech_cost` for audio planning only until worker smoke passes.
|
|
137
|
+
- Use `exactpdf_voice_preview` before making any narration-quality decision.
|
|
138
|
+
- Do not promise PDF-to-MP3/audiobook output through MCP until background audio is re-enabled.
|
|
148
139
|
- If a tool returns `isError`, show the HTTP status and response body to the user. The API returns structured error details.
|
|
149
140
|
- Keep API keys out of prompts, logs, screenshots, and commits.
|
|
150
141
|
|
package/dist/run.js
CHANGED
|
@@ -27,8 +27,16 @@ function requireKey() {
|
|
|
27
27
|
}
|
|
28
28
|
return k;
|
|
29
29
|
}
|
|
30
|
-
const
|
|
31
|
-
|
|
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 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.',
|
|
32
40
|
});
|
|
33
41
|
async function saveBinaryFromResponse(res, prefix, fallbackExt) {
|
|
34
42
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
@@ -60,6 +68,40 @@ function targetStatus(res) {
|
|
|
60
68
|
const mode = res.headers.get('x-compression-mode') ?? 'best_effort';
|
|
61
69
|
return `Target bytes: ${target}\nTarget reached: ${reached}\nOriginal bytes: ${original}\nOutput bytes: ${output}\nCompression mode: ${mode}`;
|
|
62
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
|
+
}
|
|
63
105
|
server.registerTool('exactpdf_first_run_checklist', {
|
|
64
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.',
|
|
65
107
|
inputSchema: z.object({}),
|
|
@@ -80,8 +122,8 @@ server.registerTool('exactpdf_first_run_checklist', {
|
|
|
80
122
|
safe_first_run: [
|
|
81
123
|
'Call exactpdf_account to verify the key and credit balance.',
|
|
82
124
|
'Call exactpdf_pdf_info with a small absolute PDF path before extraction or conversion.',
|
|
83
|
-
'Call exactpdf_voice_preview with a short text sample before
|
|
84
|
-
'Call exactpdf_estimate_speech_cost
|
|
125
|
+
'Call exactpdf_voice_preview with a short text sample before any narration-quality decision.',
|
|
126
|
+
'Call exactpdf_estimate_speech_cost only for planning; background audio submission is disabled until worker smoke passes.',
|
|
85
127
|
],
|
|
86
128
|
credit_model: {
|
|
87
129
|
free: ['exactpdf_first_run_checklist', 'exactpdf_account', 'exactpdf_pdf_info', 'exactpdf_voice_preview', 'exactpdf_estimate_speech_cost', 'job polling'],
|
|
@@ -89,13 +131,13 @@ server.registerTool('exactpdf_first_run_checklist', {
|
|
|
89
131
|
standard_document_tools: '1 credit only after successful output',
|
|
90
132
|
credit_packs: 'Buy one-time credit packs when the workflow works; subscription is not required for API/MCP.',
|
|
91
133
|
launch_portal_ready_workflows: '1 credit on successful output: exactpdf_merge_and_compress_pdfs, exactpdf_images_to_pdf_and_compress',
|
|
92
|
-
speech_audiobook_presentation: '
|
|
93
|
-
translate_and_speak: '
|
|
134
|
+
speech_audiobook_presentation: 'Planning only while background audio jobs are disabled for production hardening.',
|
|
135
|
+
translate_and_speak: 'Planning only while background audio jobs are disabled for production hardening.',
|
|
94
136
|
},
|
|
95
137
|
reliability_rules: [
|
|
96
138
|
'Use absolute local file paths; relative paths are client-dependent.',
|
|
97
139
|
'Never paste API keys into prompts, screenshots, commits, or logs.',
|
|
98
|
-
'
|
|
140
|
+
'Background audio tools intentionally return a hardening/disabled message until worker smoke passes.',
|
|
99
141
|
'If a tool returns isError, show the HTTP status and response body to the user.',
|
|
100
142
|
],
|
|
101
143
|
}, null, 2),
|
|
@@ -304,46 +346,67 @@ server.registerTool('exactpdf_rotate_pdf', {
|
|
|
304
346
|
};
|
|
305
347
|
});
|
|
306
348
|
server.registerTool('exactpdf_compress_pdf', {
|
|
307
|
-
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.',
|
|
308
350
|
inputSchema: z.object({
|
|
309
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.'),
|
|
310
353
|
}),
|
|
311
|
-
}, async ({ path }) =>
|
|
312
|
-
|
|
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
|
-
|
|
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
|
+
}));
|
|
347
410
|
server.registerTool('exactpdf_images_to_pdf_and_compress', {
|
|
348
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.',
|
|
349
412
|
inputSchema: z.object({
|
|
@@ -534,52 +597,16 @@ server.registerTool('exactpdf_pdf_structured_markdown', {
|
|
|
534
597
|
content: [{ type: 'text', text: `Credits remaining: ${credits}\n${raw.slice(0, 120_000)}` }],
|
|
535
598
|
};
|
|
536
599
|
});
|
|
537
|
-
server.registerTool('
|
|
538
|
-
description: '
|
|
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.',
|
|
539
602
|
inputSchema: z.object({
|
|
540
603
|
path: z.string().describe('Absolute path to a PDF file'),
|
|
541
|
-
output_format: z.enum(['mp3', 'wav', 'zip']).optional().describe('Audio export format. Default: mp3.'),
|
|
542
|
-
voice_style: z
|
|
543
|
-
.enum(['professional', 'audiobook', 'educational', 'presenter', 'conversational'])
|
|
544
|
-
.optional()
|
|
545
|
-
.describe('Narration style. Default: audiobook.'),
|
|
546
|
-
voice_id: z.string().optional().describe('Optional provider voice id, if your account supports it.'),
|
|
547
|
-
language: z.string().optional().describe('Language hint such as en, hi, es, fr.'),
|
|
548
|
-
speed: z.number().min(0.5).max(2).optional().describe('Speech speed multiplier, 0.5-2.0.'),
|
|
549
|
-
page_range: z.string().optional().describe('Optional page range, e.g. "1-10" or "2,4-7".'),
|
|
550
|
-
normalize_pauses: z.boolean().optional().describe('Normalize pauses between extracted sections.'),
|
|
551
|
-
preserve_chapters: z.boolean().optional().describe('Keep detected headings as audiobook chapters.'),
|
|
552
|
-
pronunciation_fixes: z.string().optional().describe('JSON object of pronunciation replacements.'),
|
|
553
|
-
callback_url: z.string().url().optional().describe('Optional https webhook URL for job completion.'),
|
|
554
|
-
webhook_secret: z.string().min(12).max(256).optional().describe('Optional webhook HMAC signing secret.'),
|
|
555
604
|
}),
|
|
556
|
-
}, async ({ path
|
|
605
|
+
}, async ({ path }) => {
|
|
557
606
|
const key = requireKey();
|
|
558
607
|
const form = new FormData();
|
|
559
608
|
form.append('file', new Blob([await readFile(path)], { type: 'application/pdf' }), basename(path));
|
|
560
|
-
|
|
561
|
-
form.append('output_format', output_format);
|
|
562
|
-
if (voice_style)
|
|
563
|
-
form.append('voice_style', voice_style);
|
|
564
|
-
if (voice_id)
|
|
565
|
-
form.append('voice_id', voice_id);
|
|
566
|
-
if (language)
|
|
567
|
-
form.append('language', language);
|
|
568
|
-
if (typeof speed === 'number')
|
|
569
|
-
form.append('speed', String(speed));
|
|
570
|
-
if (page_range)
|
|
571
|
-
form.append('page_range', page_range);
|
|
572
|
-
if (typeof normalize_pauses === 'boolean')
|
|
573
|
-
form.append('normalize_pauses', String(normalize_pauses));
|
|
574
|
-
if (typeof preserve_chapters === 'boolean')
|
|
575
|
-
form.append('preserve_chapters', String(preserve_chapters));
|
|
576
|
-
if (pronunciation_fixes)
|
|
577
|
-
form.append('pronunciation_fixes', pronunciation_fixes);
|
|
578
|
-
if (callback_url)
|
|
579
|
-
form.append('callback_url', callback_url);
|
|
580
|
-
if (webhook_secret)
|
|
581
|
-
form.append('webhook_secret', webhook_secret);
|
|
582
|
-
const res = await fetch(`${BASE}/api/v1/pdf-to-audiobook`, {
|
|
609
|
+
const res = await fetch(`${BASE}/api/v1/pdf-to-json`, {
|
|
583
610
|
method: 'POST',
|
|
584
611
|
headers: {
|
|
585
612
|
Authorization: `Bearer ${key}`,
|
|
@@ -587,25 +614,51 @@ server.registerTool('exactpdf_pdf_to_audiobook', {
|
|
|
587
614
|
},
|
|
588
615
|
body: form,
|
|
589
616
|
});
|
|
617
|
+
const credits = res.headers.get('x-credits-remaining') ?? '?';
|
|
590
618
|
const raw = await res.text();
|
|
591
619
|
if (!res.ok) {
|
|
592
620
|
return {
|
|
593
621
|
content: [
|
|
594
622
|
{
|
|
595
623
|
type: 'text',
|
|
596
|
-
text: `pdf-to-
|
|
624
|
+
text: `pdf-to-json failed HTTP ${res.status}. Credits: ${credits}\n${raw.slice(0, 8000)}`,
|
|
597
625
|
},
|
|
598
626
|
],
|
|
599
627
|
isError: true,
|
|
600
628
|
};
|
|
601
629
|
}
|
|
630
|
+
return {
|
|
631
|
+
content: [{ type: 'text', text: `Credits remaining: ${credits}\n${raw.slice(0, 120_000)}` }],
|
|
632
|
+
};
|
|
633
|
+
});
|
|
634
|
+
server.registerTool('exactpdf_pdf_to_audiobook', {
|
|
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.',
|
|
636
|
+
inputSchema: z.object({
|
|
637
|
+
path: z.string().describe('Absolute path to a PDF file'),
|
|
638
|
+
output_format: z.enum(['mp3', 'wav', 'zip']).optional().describe('Audio export format. Default: mp3.'),
|
|
639
|
+
voice_style: z
|
|
640
|
+
.enum(['professional', 'audiobook', 'educational', 'presenter', 'conversational'])
|
|
641
|
+
.optional()
|
|
642
|
+
.describe('Narration style. Default: audiobook.'),
|
|
643
|
+
voice_id: z.string().optional().describe('Optional provider voice id, if your account supports it.'),
|
|
644
|
+
language: z.string().optional().describe('Language hint such as en, hi, es, fr.'),
|
|
645
|
+
speed: z.number().min(0.5).max(2).optional().describe('Speech speed multiplier, 0.5-2.0.'),
|
|
646
|
+
page_range: z.string().optional().describe('Optional page range, e.g. "1-10" or "2,4-7".'),
|
|
647
|
+
normalize_pauses: z.boolean().optional().describe('Normalize pauses between extracted sections.'),
|
|
648
|
+
preserve_chapters: z.boolean().optional().describe('Keep detected headings as audiobook chapters.'),
|
|
649
|
+
pronunciation_fixes: z.string().optional().describe('JSON object of pronunciation replacements.'),
|
|
650
|
+
callback_url: z.string().url().optional().describe('Optional https webhook URL for job completion.'),
|
|
651
|
+
webhook_secret: z.string().min(12).max(256).optional().describe('Optional webhook HMAC signing secret.'),
|
|
652
|
+
}),
|
|
653
|
+
}, async ({ path, output_format, voice_style, voice_id, language, speed, page_range, normalize_pauses, preserve_chapters, pronunciation_fixes, callback_url, webhook_secret, }) => {
|
|
602
654
|
return {
|
|
603
655
|
content: [
|
|
604
656
|
{
|
|
605
657
|
type: 'text',
|
|
606
|
-
text: `
|
|
658
|
+
text: `pdf-to-audiobook disabled: ${BACKGROUND_AUDIO_HARDENING_MESSAGE}`,
|
|
607
659
|
},
|
|
608
660
|
],
|
|
661
|
+
isError: true,
|
|
609
662
|
};
|
|
610
663
|
});
|
|
611
664
|
server.registerTool('exactpdf_job_status', {
|
|
@@ -661,7 +714,7 @@ server.registerTool('exactpdf_job_status', {
|
|
|
661
714
|
};
|
|
662
715
|
});
|
|
663
716
|
server.registerTool('exactpdf_translate_and_speak', {
|
|
664
|
-
description: '
|
|
717
|
+
description: 'Planning-only placeholder while translate-and-speak workers are hardened. Do not use for paid workloads; use estimates for planning only.',
|
|
665
718
|
inputSchema: z.object({
|
|
666
719
|
path: z.string().describe('Absolute path to a PDF file'),
|
|
667
720
|
target_language: z.string().describe('Target language code, e.g. hi, es, fr, mr, de, ja, ar.'),
|
|
@@ -679,59 +732,18 @@ server.registerTool('exactpdf_translate_and_speak', {
|
|
|
679
732
|
webhook_secret: z.string().min(12).max(256).optional().describe('Optional webhook HMAC signing secret.'),
|
|
680
733
|
}),
|
|
681
734
|
}, async ({ path, target_language, source_language, output_format, voice_style, speed, page_range, segmentation_mode, translation_glossary, callback_url, webhook_secret, }) => {
|
|
682
|
-
const key = requireKey();
|
|
683
|
-
const form = new FormData();
|
|
684
|
-
form.append('file', new Blob([await readFile(path)], { type: 'application/pdf' }), basename(path));
|
|
685
|
-
form.append('target_language', target_language);
|
|
686
|
-
if (source_language)
|
|
687
|
-
form.append('source_language', source_language);
|
|
688
|
-
if (output_format)
|
|
689
|
-
form.append('output_format', output_format);
|
|
690
|
-
if (voice_style)
|
|
691
|
-
form.append('voice_style', voice_style);
|
|
692
|
-
if (typeof speed === 'number')
|
|
693
|
-
form.append('speed', String(speed));
|
|
694
|
-
if (page_range)
|
|
695
|
-
form.append('page_range', page_range);
|
|
696
|
-
if (segmentation_mode)
|
|
697
|
-
form.append('segmentation_mode', segmentation_mode);
|
|
698
|
-
if (translation_glossary)
|
|
699
|
-
form.append('translation_glossary', translation_glossary);
|
|
700
|
-
if (callback_url)
|
|
701
|
-
form.append('callback_url', callback_url);
|
|
702
|
-
if (webhook_secret)
|
|
703
|
-
form.append('webhook_secret', webhook_secret);
|
|
704
|
-
const res = await fetch(`${BASE}/api/v1/translate-and-speak`, {
|
|
705
|
-
method: 'POST',
|
|
706
|
-
headers: {
|
|
707
|
-
Authorization: `Bearer ${key}`,
|
|
708
|
-
Accept: 'application/json',
|
|
709
|
-
},
|
|
710
|
-
body: form,
|
|
711
|
-
});
|
|
712
|
-
const raw = await res.text();
|
|
713
|
-
if (!res.ok) {
|
|
714
|
-
return {
|
|
715
|
-
content: [
|
|
716
|
-
{
|
|
717
|
-
type: 'text',
|
|
718
|
-
text: `translate-and-speak failed HTTP ${res.status}\n${raw.slice(0, 8000)}`,
|
|
719
|
-
},
|
|
720
|
-
],
|
|
721
|
-
isError: true,
|
|
722
|
-
};
|
|
723
|
-
}
|
|
724
735
|
return {
|
|
725
736
|
content: [
|
|
726
737
|
{
|
|
727
738
|
type: 'text',
|
|
728
|
-
text: `
|
|
739
|
+
text: `translate-and-speak disabled: ${BACKGROUND_AUDIO_HARDENING_MESSAGE}`,
|
|
729
740
|
},
|
|
730
741
|
],
|
|
742
|
+
isError: true,
|
|
731
743
|
};
|
|
732
744
|
});
|
|
733
745
|
server.registerTool('exactpdf_presentation_narration', {
|
|
734
|
-
description: '
|
|
746
|
+
description: 'Planning-only placeholder while presentation narration workers are hardened. Do not use for paid workloads; use estimates for planning only.',
|
|
735
747
|
inputSchema: z.object({
|
|
736
748
|
path: z.string().describe('Absolute path to a PDF presentation file'),
|
|
737
749
|
output_format: z.enum(['mp3', 'wav', 'zip']).optional().describe('Default: zip for per-slide tracks and manifest.'),
|
|
@@ -745,48 +757,14 @@ server.registerTool('exactpdf_presentation_narration', {
|
|
|
745
757
|
webhook_secret: z.string().min(12).max(256).optional().describe('Optional webhook HMAC signing secret.'),
|
|
746
758
|
}),
|
|
747
759
|
}, async ({ path, output_format, voice_style, speed, page_range, callback_url, webhook_secret }) => {
|
|
748
|
-
const key = requireKey();
|
|
749
|
-
const form = new FormData();
|
|
750
|
-
form.append('file', new Blob([await readFile(path)], { type: 'application/pdf' }), basename(path));
|
|
751
|
-
if (output_format)
|
|
752
|
-
form.append('output_format', output_format);
|
|
753
|
-
if (voice_style)
|
|
754
|
-
form.append('voice_style', voice_style);
|
|
755
|
-
if (typeof speed === 'number')
|
|
756
|
-
form.append('speed', String(speed));
|
|
757
|
-
if (page_range)
|
|
758
|
-
form.append('page_range', page_range);
|
|
759
|
-
if (callback_url)
|
|
760
|
-
form.append('callback_url', callback_url);
|
|
761
|
-
if (webhook_secret)
|
|
762
|
-
form.append('webhook_secret', webhook_secret);
|
|
763
|
-
const res = await fetch(`${BASE}/api/v1/presentation-narration`, {
|
|
764
|
-
method: 'POST',
|
|
765
|
-
headers: {
|
|
766
|
-
Authorization: `Bearer ${key}`,
|
|
767
|
-
Accept: 'application/json',
|
|
768
|
-
},
|
|
769
|
-
body: form,
|
|
770
|
-
});
|
|
771
|
-
const raw = await res.text();
|
|
772
|
-
if (!res.ok) {
|
|
773
|
-
return {
|
|
774
|
-
content: [
|
|
775
|
-
{
|
|
776
|
-
type: 'text',
|
|
777
|
-
text: `presentation-narration failed HTTP ${res.status}\n${raw.slice(0, 8000)}`,
|
|
778
|
-
},
|
|
779
|
-
],
|
|
780
|
-
isError: true,
|
|
781
|
-
};
|
|
782
|
-
}
|
|
783
760
|
return {
|
|
784
761
|
content: [
|
|
785
762
|
{
|
|
786
763
|
type: 'text',
|
|
787
|
-
text: `
|
|
764
|
+
text: `presentation-narration disabled: ${BACKGROUND_AUDIO_HARDENING_MESSAGE}`,
|
|
788
765
|
},
|
|
789
766
|
],
|
|
767
|
+
isError: true,
|
|
790
768
|
};
|
|
791
769
|
});
|
|
792
770
|
const speechJobSchema = z.object({
|
|
@@ -807,48 +785,9 @@ const speechJobSchema = z.object({
|
|
|
807
785
|
webhook_secret: z.string().min(12).max(256).optional().describe('Optional webhook HMAC signing secret.'),
|
|
808
786
|
});
|
|
809
787
|
async function submitSpeechJob(endpoint, args, label) {
|
|
810
|
-
const key = requireKey();
|
|
811
|
-
const form = new FormData();
|
|
812
|
-
form.append('file', new Blob([await readFile(args.path)], { type: 'application/pdf' }), basename(args.path));
|
|
813
|
-
if (args.output_format)
|
|
814
|
-
form.append('output_format', args.output_format);
|
|
815
|
-
if (args.voice_style)
|
|
816
|
-
form.append('voice_style', args.voice_style);
|
|
817
|
-
if (args.voice_id)
|
|
818
|
-
form.append('voice_id', args.voice_id);
|
|
819
|
-
if (args.language)
|
|
820
|
-
form.append('language', args.language);
|
|
821
|
-
if (typeof args.speed === 'number')
|
|
822
|
-
form.append('speed', String(args.speed));
|
|
823
|
-
if (args.page_range)
|
|
824
|
-
form.append('page_range', args.page_range);
|
|
825
|
-
if (typeof args.normalize_pauses === 'boolean')
|
|
826
|
-
form.append('normalize_pauses', String(args.normalize_pauses));
|
|
827
|
-
if (typeof args.preserve_chapters === 'boolean')
|
|
828
|
-
form.append('preserve_chapters', String(args.preserve_chapters));
|
|
829
|
-
if (args.pronunciation_fixes)
|
|
830
|
-
form.append('pronunciation_fixes', args.pronunciation_fixes);
|
|
831
|
-
if (args.callback_url)
|
|
832
|
-
form.append('callback_url', args.callback_url);
|
|
833
|
-
if (args.webhook_secret)
|
|
834
|
-
form.append('webhook_secret', args.webhook_secret);
|
|
835
|
-
const res = await fetch(`${BASE}${endpoint}`, {
|
|
836
|
-
method: 'POST',
|
|
837
|
-
headers: {
|
|
838
|
-
Authorization: `Bearer ${key}`,
|
|
839
|
-
Accept: 'application/json',
|
|
840
|
-
},
|
|
841
|
-
body: form,
|
|
842
|
-
});
|
|
843
|
-
const raw = await res.text();
|
|
844
|
-
if (!res.ok) {
|
|
845
|
-
return {
|
|
846
|
-
content: [{ type: 'text', text: `${label} failed HTTP ${res.status}\n${raw.slice(0, 8000)}` }],
|
|
847
|
-
isError: true,
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
788
|
return {
|
|
851
|
-
content: [{ type: 'text', text: `${label}
|
|
789
|
+
content: [{ type: 'text', text: `${label} disabled: ${BACKGROUND_AUDIO_HARDENING_MESSAGE}` }],
|
|
790
|
+
isError: true,
|
|
852
791
|
};
|
|
853
792
|
}
|
|
854
793
|
async function pollSpeechJob(jobId, download, downloadEndpoint = false) {
|
|
@@ -912,7 +851,7 @@ async function pollSpeechJob(jobId, download, downloadEndpoint = false) {
|
|
|
912
851
|
};
|
|
913
852
|
}
|
|
914
853
|
server.registerTool('exactpdf_estimate_speech_cost', {
|
|
915
|
-
description: 'Estimate generated minutes and credits
|
|
854
|
+
description: 'Estimate generated minutes and credits for planning only while background PDF speech/audiobook/translation jobs are hardened. Local-only rough estimate; free and safe to call repeatedly.',
|
|
916
855
|
inputSchema: z.object({
|
|
917
856
|
path: z.string().optional().describe('Absolute path to a PDF file. Used for a rough size-based duration estimate.'),
|
|
918
857
|
characters: z.number().int().positive().optional().describe('Known narration character count, if already extracted.'),
|
|
@@ -947,7 +886,7 @@ server.registerTool('exactpdf_estimate_speech_cost', {
|
|
|
947
886
|
current_credit_cost: credits,
|
|
948
887
|
target_language_applied: selectedMode === 'translate' ? (target_language ?? null) : null,
|
|
949
888
|
ignored_target_language: selectedMode !== 'translate' && target_language ? target_language : null,
|
|
950
|
-
note: '
|
|
889
|
+
note: 'Planning-only while background audio jobs are disabled for production hardening. Intended pricing after re-enable is 1 credit/minute for standard speech/audiobook/presentation and 3 credits/minute for translate-and-speak. A target language only changes pricing when mode is translate. Path-based estimates are rough until the API extracts PDF text.',
|
|
951
890
|
},
|
|
952
891
|
}, null, 2),
|
|
953
892
|
},
|
|
@@ -1006,22 +945,22 @@ server.registerTool('exactpdf_voice_preview', {
|
|
|
1006
945
|
};
|
|
1007
946
|
});
|
|
1008
947
|
server.registerTool('exactpdf_pdf_to_speech', {
|
|
1009
|
-
description: '
|
|
948
|
+
description: 'Planning-only placeholder while PDF-to-speech workers are hardened. Do not use for paid workloads; use exactpdf_voice_preview and exactpdf_estimate_speech_cost instead.',
|
|
1010
949
|
inputSchema: speechJobSchema,
|
|
1011
950
|
}, async (args) => submitSpeechJob('/api/v1/pdf-to-speech', args, 'pdf-to-speech'));
|
|
1012
951
|
server.registerTool('exactpdf_generate_audiobook', {
|
|
1013
|
-
description: '
|
|
952
|
+
description: 'Planning-only placeholder while audiobook workers are hardened. Do not use for paid workloads; use exactpdf_voice_preview and exactpdf_estimate_speech_cost instead.',
|
|
1014
953
|
inputSchema: speechJobSchema,
|
|
1015
954
|
}, async (args) => submitSpeechJob('/api/v1/generate-audiobook', args, 'generate-audiobook'));
|
|
1016
955
|
server.registerTool('exactpdf_get_speech_job', {
|
|
1017
|
-
description: 'Poll a speech/audiobook/translation/presentation job.
|
|
956
|
+
description: 'Poll a legacy speech/audiobook/translation/presentation job. Background audio submission is currently disabled for paid workloads.',
|
|
1018
957
|
inputSchema: z.object({
|
|
1019
958
|
job_id: z.string().describe('ExactPDF speech job id.'),
|
|
1020
959
|
download: z.boolean().optional().describe('When true, save result_url to EXACTPDF_API_OUTPUT_DIR if job succeeded.'),
|
|
1021
960
|
}),
|
|
1022
961
|
}, async ({ job_id, download }) => pollSpeechJob(job_id, Boolean(download)));
|
|
1023
962
|
server.registerTool('exactpdf_download_audio', {
|
|
1024
|
-
description: 'Download a succeeded speech/audiobook job and save the audio/ZIP locally.
|
|
963
|
+
description: 'Download a legacy succeeded speech/audiobook job and save the audio/ZIP locally. New background audio submission is currently disabled for paid workloads.',
|
|
1025
964
|
inputSchema: z.object({
|
|
1026
965
|
job_id: z.string().describe('ExactPDF speech job id.'),
|
|
1027
966
|
}),
|
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/
|
|
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",
|
|
@@ -39,10 +39,23 @@
|
|
|
39
39
|
"pdf",
|
|
40
40
|
"exactpdf",
|
|
41
41
|
"cursor",
|
|
42
|
+
"cursor-mcp",
|
|
42
43
|
"claude",
|
|
43
44
|
"claude-desktop",
|
|
45
|
+
"codex",
|
|
44
46
|
"headless-pdf",
|
|
45
|
-
"pdf-api"
|
|
47
|
+
"pdf-api",
|
|
48
|
+
"pdf-mcp",
|
|
49
|
+
"pdf-compression",
|
|
50
|
+
"pdf-to-markdown",
|
|
51
|
+
"pdf-to-json",
|
|
52
|
+
"pdf-to-images",
|
|
53
|
+
"target-size-compression",
|
|
54
|
+
"rag",
|
|
55
|
+
"ocr",
|
|
56
|
+
"images-to-pdf",
|
|
57
|
+
"merge-pdf",
|
|
58
|
+
"split-pdf"
|
|
46
59
|
],
|
|
47
60
|
"author": "ExactPDF",
|
|
48
61
|
"license": "MIT",
|
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, metadata, text, Markdown, voice previews,
|
|
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
|
}
|