@exactpdf/mcp 0.2.12 → 0.2.13

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 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, narrate, translate, or export PDFs without writing fragile PDF plumbing.
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.
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. Async speech jobs are metered by generated minutes.
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
- - Long audio jobs: metered by generated minutes.
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 |
@@ -86,14 +88,14 @@ Credit model:
86
88
  | `exactpdf_pdf_structured_markdown` | POST /api/v1/pdf-structured-markdown | 1 |
87
89
  | `exactpdf_estimate_speech_cost` | local estimate | 0 |
88
90
  | `exactpdf_voice_preview` | POST /api/v1/voice-preview | 0 |
89
- | `exactpdf_pdf_to_speech` | POST /api/v1/pdf-to-speech | 1/min |
90
- | `exactpdf_pdf_to_audiobook` | POST /api/v1/pdf-to-audiobook | 1/min |
91
- | `exactpdf_generate_audiobook` | POST /api/v1/generate-audiobook | 1/min |
92
- | `exactpdf_translate_and_speak` | POST /api/v1/translate-and-speak | 3/min |
93
- | `exactpdf_presentation_narration` | POST /api/v1/presentation-narration | 1/min |
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 |
91
+ | `exactpdf_pdf_to_speech` | POST /api/v1/pdf-to-speech | disabled / hardening |
92
+ | `exactpdf_pdf_to_audiobook` | POST /api/v1/pdf-to-audiobook | disabled / hardening |
93
+ | `exactpdf_generate_audiobook` | POST /api/v1/generate-audiobook | disabled / hardening |
94
+ | `exactpdf_translate_and_speak` | POST /api/v1/translate-and-speak | disabled / hardening |
95
+ | `exactpdf_presentation_narration` | POST /api/v1/presentation-narration | disabled / hardening |
96
+ | `exactpdf_job_status` | GET /api/v1/jobs/:id | 0 / legacy polling |
97
+ | `exactpdf_get_speech_job` | GET /api/v1/speech-jobs/:id | 0 / legacy polling |
98
+ | `exactpdf_download_audio` | GET /api/v1/speech-jobs/:id/download | 0 / legacy download |
97
99
 
98
100
  ## Tool behavior
99
101
 
@@ -103,48 +105,27 @@ Credit model:
103
105
  | PDF operations | Absolute local PDF paths | Saved PDF/ZIP path plus remaining credits |
104
106
  | Text / Markdown | Absolute local PDF path | JSON text/Markdown, page count, structured content |
105
107
  | Voice preview | Short text, voice style/language | Saved MP3/WAV preview, no credit charge |
106
- | Speech jobs | PDF path, page range, style, callback URL | `job_id`, status URL, pricing estimate |
107
- | Job polling/download | `job_id` | progress/errors/result URL or saved audio/ZIP |
108
+ | Audio planning | Short text, character count, rough PDF size | Voice preview or estimated minutes/credits |
109
+ | Legacy job polling/download | `job_id` | progress/errors/result URL or saved audio/ZIP for jobs created before the hardening gate |
108
110
 
109
- Async audiobook flow:
111
+ Background audio status:
110
112
 
111
113
  ```text
112
- 1. exactpdf_estimate_speech_cost(path="/abs/book.pdf", mode="audiobook")
114
+ 1. exactpdf_estimate_speech_cost(path="/abs/book.pdf", mode="audiobook") # planning only
113
115
  2. exactpdf_voice_preview(text="Read the first paragraph in an audiobook tone", voice_style="audiobook")
114
- 3. exactpdf_generate_audiobook(path="/abs/book.pdf", voice_style="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
116
+ 3. Do not call exactpdf_generate_audiobook / exactpdf_pdf_to_speech for paid workloads until ExactPDF announces worker smoke has passed.
136
117
  ```
137
118
 
138
- The default ZIP includes one MP3 per PDF page plus `manifest.json` with page numbers and duration seconds.
119
+ 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
120
 
140
121
  ## Reliability rules for agent builders
141
122
 
142
123
  - Always call `exactpdf_account` once before paid tools.
143
124
  - Use absolute file paths. Relative paths are client-dependent and may fail.
144
125
  - Use `exactpdf_pdf_info` before large jobs to confirm the file is readable.
145
- - Use `exactpdf_estimate_speech_cost` before speech/audiobook/translation jobs.
146
- - Use `exactpdf_voice_preview` before full narration when quality matters.
147
- - Async jobs are not downloads. Submit, poll, then download after `succeeded`.
126
+ - Use `exactpdf_estimate_speech_cost` for audio planning only until worker smoke passes.
127
+ - Use `exactpdf_voice_preview` before making any narration-quality decision.
128
+ - Do not promise PDF-to-MP3/audiobook output through MCP until background audio is re-enabled.
148
129
  - If a tool returns `isError`, show the HTTP status and response body to the user. The API returns structured error details.
149
130
  - Keep API keys out of prompts, logs, screenshots, and commits.
150
131
 
package/dist/run.js CHANGED
@@ -27,8 +27,9 @@ function requireKey() {
27
27
  }
28
28
  return k;
29
29
  }
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.',
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 server = new McpServer({ name: 'exactpdf', version: '0.2.13' }, {
32
+ instructions: 'ExactPDF turns local PDFs into production outputs for agents: inspect metadata, merge/split/rotate/compress, create portal-ready merge/images workflows, extract text, 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. 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
33
  });
33
34
  async function saveBinaryFromResponse(res, prefix, fallbackExt) {
34
35
  const buf = Buffer.from(await res.arrayBuffer());
@@ -80,8 +81,8 @@ server.registerTool('exactpdf_first_run_checklist', {
80
81
  safe_first_run: [
81
82
  'Call exactpdf_account to verify the key and credit balance.',
82
83
  '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 long narration.',
84
- 'Call exactpdf_estimate_speech_cost before audiobook, speech, translation, or presentation narration.',
84
+ 'Call exactpdf_voice_preview with a short text sample before any narration-quality decision.',
85
+ 'Call exactpdf_estimate_speech_cost only for planning; background audio submission is disabled until worker smoke passes.',
85
86
  ],
86
87
  credit_model: {
87
88
  free: ['exactpdf_first_run_checklist', 'exactpdf_account', 'exactpdf_pdf_info', 'exactpdf_voice_preview', 'exactpdf_estimate_speech_cost', 'job polling'],
@@ -89,13 +90,13 @@ server.registerTool('exactpdf_first_run_checklist', {
89
90
  standard_document_tools: '1 credit only after successful output',
90
91
  credit_packs: 'Buy one-time credit packs when the workflow works; subscription is not required for API/MCP.',
91
92
  launch_portal_ready_workflows: '1 credit on successful output: exactpdf_merge_and_compress_pdfs, exactpdf_images_to_pdf_and_compress',
92
- speech_audiobook_presentation: '1 credit per generated minute',
93
- translate_and_speak: '3 credits per generated minute',
93
+ speech_audiobook_presentation: 'Planning only while background audio jobs are disabled for production hardening.',
94
+ translate_and_speak: 'Planning only while background audio jobs are disabled for production hardening.',
94
95
  },
95
96
  reliability_rules: [
96
97
  'Use absolute local file paths; relative paths are client-dependent.',
97
98
  'Never paste API keys into prompts, screenshots, commits, or logs.',
98
- 'Async jobs return a job_id first; poll until succeeded, then download.',
99
+ 'Background audio tools intentionally return a hardening/disabled message until worker smoke passes.',
99
100
  'If a tool returns isError, show the HTTP status and response body to the user.',
100
101
  ],
101
102
  }, null, 2),
@@ -535,7 +536,7 @@ server.registerTool('exactpdf_pdf_structured_markdown', {
535
536
  };
536
537
  });
537
538
  server.registerTool('exactpdf_pdf_to_audiobook', {
538
- description: 'Queue an async PDF-to-audiobook job with chapter-aware narration. Metered by generated minutes. Returns job_id/status_url; poll with exactpdf_get_speech_job and download when succeeded.',
539
+ 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.',
539
540
  inputSchema: z.object({
540
541
  path: z.string().describe('Absolute path to a PDF file'),
541
542
  output_format: z.enum(['mp3', 'wav', 'zip']).optional().describe('Audio export format. Default: mp3.'),
@@ -554,58 +555,14 @@ server.registerTool('exactpdf_pdf_to_audiobook', {
554
555
  webhook_secret: z.string().min(12).max(256).optional().describe('Optional webhook HMAC signing secret.'),
555
556
  }),
556
557
  }, async ({ path, output_format, voice_style, voice_id, language, speed, page_range, normalize_pauses, preserve_chapters, pronunciation_fixes, callback_url, webhook_secret, }) => {
557
- const key = requireKey();
558
- const form = new FormData();
559
- form.append('file', new Blob([await readFile(path)], { type: 'application/pdf' }), basename(path));
560
- if (output_format)
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`, {
583
- method: 'POST',
584
- headers: {
585
- Authorization: `Bearer ${key}`,
586
- Accept: 'application/json',
587
- },
588
- body: form,
589
- });
590
- const raw = await res.text();
591
- if (!res.ok) {
592
- return {
593
- content: [
594
- {
595
- type: 'text',
596
- text: `pdf-to-audiobook failed HTTP ${res.status}\n${raw.slice(0, 8000)}`,
597
- },
598
- ],
599
- isError: true,
600
- };
601
- }
602
558
  return {
603
559
  content: [
604
560
  {
605
561
  type: 'text',
606
- text: `Audiobook job submitted HTTP ${res.status}\n${raw.slice(0, 120_000)}`,
562
+ text: `pdf-to-audiobook disabled: ${BACKGROUND_AUDIO_HARDENING_MESSAGE}`,
607
563
  },
608
564
  ],
565
+ isError: true,
609
566
  };
610
567
  });
611
568
  server.registerTool('exactpdf_job_status', {
@@ -661,7 +618,7 @@ server.registerTool('exactpdf_job_status', {
661
618
  };
662
619
  });
663
620
  server.registerTool('exactpdf_translate_and_speak', {
664
- description: 'Queue async translate-and-speak: translate PDF content, preserve structure, then generate native-accent audio. Costs more than standard speech; start with hi/es/fr. Poll with exactpdf_get_speech_job.',
621
+ description: 'Planning-only placeholder while translate-and-speak workers are hardened. Do not use for paid workloads; use estimates for planning only.',
665
622
  inputSchema: z.object({
666
623
  path: z.string().describe('Absolute path to a PDF file'),
667
624
  target_language: z.string().describe('Target language code, e.g. hi, es, fr, mr, de, ja, ar.'),
@@ -679,59 +636,18 @@ server.registerTool('exactpdf_translate_and_speak', {
679
636
  webhook_secret: z.string().min(12).max(256).optional().describe('Optional webhook HMAC signing secret.'),
680
637
  }),
681
638
  }, 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
639
  return {
725
640
  content: [
726
641
  {
727
642
  type: 'text',
728
- text: `Multilingual speech job submitted HTTP ${res.status}\n${raw.slice(0, 120_000)}`,
643
+ text: `translate-and-speak disabled: ${BACKGROUND_AUDIO_HARDENING_MESSAGE}`,
729
644
  },
730
645
  ],
646
+ isError: true,
731
647
  };
732
648
  });
733
649
  server.registerTool('exactpdf_presentation_narration', {
734
- description: 'Queue audio-first presentation narration for a PDF deck. Each page becomes a narrated slide track in a ZIP with manifest timings. Metered by generated minutes.',
650
+ description: 'Planning-only placeholder while presentation narration workers are hardened. Do not use for paid workloads; use estimates for planning only.',
735
651
  inputSchema: z.object({
736
652
  path: z.string().describe('Absolute path to a PDF presentation file'),
737
653
  output_format: z.enum(['mp3', 'wav', 'zip']).optional().describe('Default: zip for per-slide tracks and manifest.'),
@@ -745,48 +661,14 @@ server.registerTool('exactpdf_presentation_narration', {
745
661
  webhook_secret: z.string().min(12).max(256).optional().describe('Optional webhook HMAC signing secret.'),
746
662
  }),
747
663
  }, 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
664
  return {
784
665
  content: [
785
666
  {
786
667
  type: 'text',
787
- text: `Presentation narration job submitted HTTP ${res.status}\n${raw.slice(0, 120_000)}`,
668
+ text: `presentation-narration disabled: ${BACKGROUND_AUDIO_HARDENING_MESSAGE}`,
788
669
  },
789
670
  ],
671
+ isError: true,
790
672
  };
791
673
  });
792
674
  const speechJobSchema = z.object({
@@ -807,48 +689,9 @@ const speechJobSchema = z.object({
807
689
  webhook_secret: z.string().min(12).max(256).optional().describe('Optional webhook HMAC signing secret.'),
808
690
  });
809
691
  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
692
  return {
851
- content: [{ type: 'text', text: `${label} job submitted HTTP ${res.status}\n${raw.slice(0, 120_000)}` }],
693
+ content: [{ type: 'text', text: `${label} disabled: ${BACKGROUND_AUDIO_HARDENING_MESSAGE}` }],
694
+ isError: true,
852
695
  };
853
696
  }
854
697
  async function pollSpeechJob(jobId, download, downloadEndpoint = false) {
@@ -912,7 +755,7 @@ async function pollSpeechJob(jobId, download, downloadEndpoint = false) {
912
755
  };
913
756
  }
914
757
  server.registerTool('exactpdf_estimate_speech_cost', {
915
- description: 'Estimate generated minutes and credits before submitting a paid PDF speech/audiobook/translation job. Local-only rough estimate; free and safe to call repeatedly.',
758
+ 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
759
  inputSchema: z.object({
917
760
  path: z.string().optional().describe('Absolute path to a PDF file. Used for a rough size-based duration estimate.'),
918
761
  characters: z.number().int().positive().optional().describe('Known narration character count, if already extracted.'),
@@ -947,7 +790,7 @@ server.registerTool('exactpdf_estimate_speech_cost', {
947
790
  current_credit_cost: credits,
948
791
  target_language_applied: selectedMode === 'translate' ? (target_language ?? null) : null,
949
792
  ignored_target_language: selectedMode !== 'translate' && target_language ? target_language : null,
950
- note: 'ExactPDF charges async speech jobs by estimated generated minutes: 1 credit/minute for standard speech/audiobook/presentation, 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.',
793
+ 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
794
  },
952
795
  }, null, 2),
953
796
  },
@@ -1006,22 +849,22 @@ server.registerTool('exactpdf_voice_preview', {
1006
849
  };
1007
850
  });
1008
851
  server.registerTool('exactpdf_pdf_to_speech', {
1009
- description: 'Queue async PDF-to-speech for reports, study notes, or accessibility audio. Metered by generated minutes. Returns job_id; poll exactpdf_get_speech_job.',
852
+ 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
853
  inputSchema: speechJobSchema,
1011
854
  }, async (args) => submitSpeechJob('/api/v1/pdf-to-speech', args, 'pdf-to-speech'));
1012
855
  server.registerTool('exactpdf_generate_audiobook', {
1013
- description: 'Queue async audiobook generation with voice style, page range, chapters, pause normalization, and optional webhook. Metered by generated minutes.',
856
+ 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
857
  inputSchema: speechJobSchema,
1015
858
  }, async (args) => submitSpeechJob('/api/v1/generate-audiobook', args, 'generate-audiobook'));
1016
859
  server.registerTool('exactpdf_get_speech_job', {
1017
- description: 'Poll a speech/audiobook/translation/presentation job. Free. Returns status, progress, error details, and result_url when succeeded; set download=true to save result_url locally.',
860
+ description: 'Poll a legacy speech/audiobook/translation/presentation job. Background audio submission is currently disabled for paid workloads.',
1018
861
  inputSchema: z.object({
1019
862
  job_id: z.string().describe('ExactPDF speech job id.'),
1020
863
  download: z.boolean().optional().describe('When true, save result_url to EXACTPDF_API_OUTPUT_DIR if job succeeded.'),
1021
864
  }),
1022
865
  }, async ({ job_id, download }) => pollSpeechJob(job_id, Boolean(download)));
1023
866
  server.registerTool('exactpdf_download_audio', {
1024
- description: 'Download a succeeded speech/audiobook job and save the audio/ZIP locally. Free. Use only after exactpdf_get_speech_job reports succeeded.',
867
+ 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
868
  inputSchema: z.object({
1026
869
  job_id: z.string().describe('ExactPDF speech job id.'),
1027
870
  }),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@exactpdf/mcp",
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.",
3
+ "version": "0.2.13",
4
+ "description": "MCP server for ExactPDF — agent PDF workflows, structured Markdown/RAG, merge/split/compress, 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,19 @@
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
+ "rag",
52
+ "images-to-pdf",
53
+ "merge-pdf",
54
+ "split-pdf"
46
55
  ],
47
56
  "author": "ExactPDF",
48
57
  "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, async speech/audiobook, multilingual speech, and presentation narration jobs.",
6
- "version": "0.2.12",
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. Background audio jobs are hardening-only until worker smoke passes.",
6
+ "version": "0.2.13",
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",
12
+ "version": "0.2.13",
13
13
  "transport": {
14
14
  "type": "stdio"
15
15
  }