@kolmopdf/mcp-server 1.0.0 → 1.0.2

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.
Files changed (66) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +37 -63
  3. package/dist/index.cjs.map +1 -0
  4. package/{packages/mcp-server/dist → dist}/index.js +0 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +58 -30
  7. package/.claude-plugin/marketplace.json +0 -25
  8. package/.github/ISSUE_TEMPLATE/bug-report.yml +0 -75
  9. package/.github/workflows/ci.yml +0 -98
  10. package/.github/workflows/release.yml +0 -52
  11. package/CHANGELOG.md +0 -12
  12. package/biome.json +0 -33
  13. package/codex-skill/kolmopdf/SKILL.md +0 -108
  14. package/codex-skill/kolmopdf/references/chain-recipes.md +0 -35
  15. package/codex-skill/kolmopdf/references/parameter-glossary.md +0 -72
  16. package/doc/apidocs/Format_Conversion_API_Guide.md +0 -117
  17. package/doc/apidocs/PDF_Layout_Translation_API_Guide.md +0 -138
  18. package/doc/apidocs/PDF_Parsing_API_Guide.md +0 -364
  19. package/doc/plan/DEVELOPMENT.md +0 -896
  20. package/doc/plan/DISTRIBUTION.md +0 -377
  21. package/doc/plan/TESTING_AND_USAGE.md +0 -370
  22. package/packages/mcp-server/LICENSE +0 -21
  23. package/packages/mcp-server/README.md +0 -37
  24. package/packages/mcp-server/dist/index.cjs.map +0 -1
  25. package/packages/mcp-server/dist/index.js.map +0 -1
  26. package/packages/mcp-server/package.json +0 -54
  27. package/packages/mcp-server/src/client.ts +0 -235
  28. package/packages/mcp-server/src/config.ts +0 -62
  29. package/packages/mcp-server/src/context.ts +0 -27
  30. package/packages/mcp-server/src/errors.ts +0 -271
  31. package/packages/mcp-server/src/extract.ts +0 -102
  32. package/packages/mcp-server/src/index.ts +0 -142
  33. package/packages/mcp-server/src/pages.ts +0 -16
  34. package/packages/mcp-server/src/polling.ts +0 -84
  35. package/packages/mcp-server/src/progress.ts +0 -48
  36. package/packages/mcp-server/src/tools/check-balance.ts +0 -33
  37. package/packages/mcp-server/src/tools/convert.ts +0 -130
  38. package/packages/mcp-server/src/tools/estimate-cost.ts +0 -82
  39. package/packages/mcp-server/src/tools/get-task-status.ts +0 -24
  40. package/packages/mcp-server/src/tools/parse-pdf.ts +0 -147
  41. package/packages/mcp-server/src/tools/translate-pdf.ts +0 -110
  42. package/packages/mcp-server/tests/integration/smoke.test.ts +0 -33
  43. package/packages/mcp-server/tests/unit/config.test.ts +0 -49
  44. package/packages/mcp-server/tests/unit/convert.test.ts +0 -28
  45. package/packages/mcp-server/tests/unit/errors.test.ts +0 -112
  46. package/packages/mcp-server/tests/unit/estimate-cost.test.ts +0 -28
  47. package/packages/mcp-server/tests/unit/polling.test.ts +0 -24
  48. package/packages/mcp-server/tsconfig.json +0 -9
  49. package/packages/mcp-server/tsup.config.ts +0 -13
  50. package/packages/mcp-server/vitest.config.ts +0 -13
  51. package/plugins/kolmopdf/.claude-plugin/plugin.json +0 -16
  52. package/plugins/kolmopdf/.mcp.json +0 -11
  53. package/plugins/kolmopdf/README.md +0 -28
  54. package/plugins/kolmopdf/commands/balance.md +0 -6
  55. package/plugins/kolmopdf/commands/convert.md +0 -14
  56. package/plugins/kolmopdf/commands/parse.md +0 -14
  57. package/plugins/kolmopdf/commands/translate.md +0 -14
  58. package/plugins/kolmopdf/skills/kolmopdf/SKILL.md +0 -108
  59. package/plugins/kolmopdf/skills/kolmopdf/references/chain-recipes.md +0 -35
  60. package/plugins/kolmopdf/skills/kolmopdf/references/parameter-glossary.md +0 -72
  61. package/pnpm-workspace.yaml +0 -2
  62. package/smithery.yaml +0 -21
  63. package/tsconfig.base.json +0 -21
  64. /package/{packages/mcp-server/dist → dist}/index.cjs +0 -0
  65. /package/{packages/mcp-server/dist → dist}/index.d.cts +0 -0
  66. /package/{packages/mcp-server/dist → dist}/index.d.ts +0 -0
@@ -1,370 +0,0 @@
1
- # KolmoPDF Claude Plugin — 测试与使用文档
2
-
3
- > 本文件供发布后用户安装、内部团队 QA、和 CI smoke test 共用。
4
- > 所有命令以 macOS / Linux 为基准;Windows 用户改用 PowerShell 等价命令。
5
-
6
- ## 1. 前置条件
7
-
8
- | 项 | 要求 |
9
- |---|---|
10
- | Node.js | ≥ 20 LTS |
11
- | KolmoPDF 账户 | Plus 或 Pro 套餐(Free / Go 用户无 API 权限) |
12
- | API Key | 已在 https://www.kolmopdf.com/api-keys 创建 |
13
- | 积分 | 测试用 ≥ 200(足够跑完整套用例) |
14
- | 网络 | 可访问 `https://www.kolmopdf.com` |
15
-
16
- 环境变量设置:
17
-
18
- ```bash
19
- export KOLMOPDF_API_KEY=sk-xxxxxxxxxxxxxxxx
20
- ```
21
-
22
- ---
23
-
24
- ## 2. 安装命令矩阵
25
-
26
- ### 2.1 Claude Code
27
-
28
- ```bash
29
- # 1. 添加 marketplace
30
- /plugin marketplace add kolmopdf/claude-plugin
31
-
32
- # 2. 安装 plugin
33
- /plugin install kolmopdf@kolmopdf
34
-
35
- # 3. 重启 Claude Code(首次安装时拉取 npm 包)
36
- ```
37
-
38
- 非交互安装(脚本场景):
39
-
40
- ```bash
41
- claude plugin marketplace add kolmopdf/claude-plugin
42
- claude plugin install kolmopdf@kolmopdf
43
- ```
44
-
45
- ### 2.2 Codex CLI
46
-
47
- ```bash
48
- # 1. 克隆 skill 到 Codex 标准路径
49
- mkdir -p ~/.codex/skills
50
- git clone --depth 1 https://github.com/kolmopdf/claude-plugin /tmp/kolmopdf
51
- cp -r /tmp/kolmopdf/codex-skill/kolmopdf ~/.codex/skills/
52
-
53
- # 2. 注册 MCP server(Codex 不自动读取 plugin .mcp.json)
54
- # 编辑 ~/.codex/config.toml,追加:
55
- ```
56
-
57
- ```toml
58
- [mcp_servers.kolmopdf]
59
- command = "npx"
60
- args = ["-y", "@kolmopdf/mcp-server"]
61
- env = { KOLMOPDF_API_KEY = "sk-..." }
62
- ```
63
-
64
- ```bash
65
- # 3. 重启 Codex
66
- ```
67
-
68
- ### 2.3 Cursor
69
-
70
- ```bash
71
- # 1. 安装 skill 到 Cursor 用户 skill 路径
72
- mkdir -p ~/.cursor/skills
73
- cp -r /tmp/kolmopdf/codex-skill/kolmopdf ~/.cursor/skills/
74
-
75
- # 2. MCP server 注册:编辑 ~/.cursor/mcp.json
76
- ```
77
-
78
- ```json
79
- {
80
- "mcpServers": {
81
- "kolmopdf": {
82
- "command": "npx",
83
- "args": ["-y", "@kolmopdf/mcp-server"],
84
- "env": { "KOLMOPDF_API_KEY": "sk-..." }
85
- }
86
- }
87
- }
88
- ```
89
-
90
- ### 2.4 Claude Desktop(手动添加 MCP server)
91
-
92
- 编辑 `~/Library/Application Support/Claude/claude_desktop_config.json`(macOS)或 `%APPDATA%\Claude\claude_desktop_config.json`(Windows):
93
-
94
- ```json
95
- {
96
- "mcpServers": {
97
- "kolmopdf": {
98
- "command": "npx",
99
- "args": ["-y", "@kolmopdf/mcp-server"],
100
- "env": { "KOLMOPDF_API_KEY": "sk-..." }
101
- }
102
- }
103
- }
104
- ```
105
-
106
- > Claude Desktop 不支持 SKILL.md,因此仅获得 MCP 工具能力,无自动触发。
107
-
108
- ---
109
-
110
- ## 3. 安装后验证 Checklist
111
-
112
- | # | 步骤 | 期望 |
113
- |---|---|---|
114
- | 1 | 启动客户端后输入 `/help`(Claude Code)或 `/skills`(Codex) | 看到 `kolmopdf` skill 列出 |
115
- | 2 | 输入 `/kolmopdf:balance` | 输出格式:`KolmoPDF balance: <number> credits (key sk-xxxxxx...xxxx)` |
116
- | 3 | 自然语言询问 "what tools do you have for parsing PDFs?" | Agent 应主动提及 KolmoPDF |
117
- | 4 | 故意把 `KOLMOPDF_API_KEY` 改成空值后重启 | 调用任何工具应返回 `invalid_api_key` 并提示去 `/api-keys` |
118
-
119
- ---
120
-
121
- ## 4. 功能测试用例
122
-
123
- > 准备 5 个真实 PDF 作为 fixtures,存放于 `tests/fixtures/`(不入库,列在 README 中由测试者各自下载)。
124
-
125
- ### 4.1 测试 fixture 清单
126
-
127
- | 文件 | 类型 | 页数 | 关键特征 |
128
- |---|---|---|---|
129
- | `attention-is-all-you-need.pdf` | arxiv 论文 | 15 | 公式密集、多栏、图表 |
130
- | `ieee-spec-sample.pdf` | IEEE 技术标准 | 40 | 跨页表格、嵌套小节 |
131
- | `bilingual-jp-source.pdf` | 日文技术文档 | 8 | 翻译用例 |
132
- | `large-handbook.pdf` | 大型 PDF | 500 | 边界压力测试 |
133
- | `oversized-1gb.pdf` | 文件 > 300MB | — | 文件大小错误用例 |
134
-
135
- ### 4.2 用例 T1: 基础 parse
136
-
137
- **Prompt**: "请帮我把 attention-is-all-you-need.pdf 转成 markdown。"
138
-
139
- | 检查项 | 期望 |
140
- |---|---|
141
- | Agent 是否调用 `kolmopdf_estimate_cost` | ✅ 应在 parse 前调用 |
142
- | Agent 是否调用 `kolmopdf_parse_pdf` | ✅ |
143
- | 返回的 `markdown_path` 是否存在 | ✅ 本地文件可读 |
144
- | Markdown 中公式数量 | ≥ 80% 的可见公式被 `$...$` / `$$...$$` 包裹 |
145
- | 表格 | 至少 1 个表格成功转为 markdown 表格 |
146
- | 多栏阅读顺序 | 段落顺序与原文一致,不串行 |
147
- | 积分扣除 | `points_deducted = pages × 2` |
148
-
149
- ### 4.3 用例 T2: parse + 翻译(中英双语)
150
-
151
- **Prompt**: "Parse this paper into bilingual markdown for me."(指向 `attention-is-all-you-need.pdf`)
152
-
153
- | 检查项 | 期望 |
154
- |---|---|
155
- | `enable_translation=true` | ✅ |
156
- | `output_options` 包含 `bilingual` 或 agent 主动设置 | ✅ |
157
- | 输出 markdown 含中英对照 | ✅ |
158
- | `points_deducted = pages × 3` | ✅ |
159
-
160
- ### 4.4 用例 T3: 版式保留翻译
161
-
162
- **Prompt**: "把 bilingual-jp-source.pdf 翻译成英文,保留原版式。"
163
-
164
- | 检查项 | 期望 |
165
- |---|---|
166
- | Agent 调用 `kolmopdf_translate_pdf`(非 parse) | ✅ |
167
- | `source_language=ja`, `target_language=en` | ✅ |
168
- | 返回的 PDF 文件可正常打开 | ✅ |
169
- | PDF 中文字已替换为英文,图表位置保持 | ✅ |
170
- | `points_deducted = pages × 2` | ✅ |
171
-
172
- ### 4.5 用例 T4: 跨页表格合并
173
-
174
- **Prompt**: "Parse ieee-spec-sample.pdf. Make sure tables that span pages are merged."
175
-
176
- | 检查项 | 期望 |
177
- |---|---|
178
- | `enable_cross_page_merge=true` | ✅ Agent 应主动设置 |
179
- | 跨页表格在输出 markdown 中保持单一表格 | ✅ |
180
-
181
- ### 4.6 用例 T5: 格式转换链式
182
-
183
- **Prompt**: "请把 attention-is-all-you-need.pdf 最终转成 docx 给我。"
184
-
185
- | 检查项 | 期望 |
186
- |---|---|
187
- | Agent 顺序调用 parse → convert | ✅ |
188
- | 中间 markdown 文件存在 | ✅ |
189
- | 最终 `.docx` 文件可在 Word 打开 | ✅ |
190
- | 总扣分 = `pages × 2 + 1` | ✅ |
191
-
192
- ### 4.7 用例 T6: 大文件边界(500 页)
193
-
194
- **Prompt**: "Parse large-handbook.pdf to markdown."
195
-
196
- | 检查项 | 期望 |
197
- |---|---|
198
- | `kolmopdf_estimate_cost` 报出 `pages × 2 = 1000` 积分 | ✅ |
199
- | 若余额不足 → agent 停止并报告 shortfall | ✅ |
200
- | 若余额充足 → 任务完成(端到端可能 5–15 分钟) | ✅ |
201
- | 进度通知 | 客户端有可见进度提示(不被 timeout) |
202
-
203
- ---
204
-
205
- ## 5. 错误场景测试用例
206
-
207
- ### 5.1 E1: 无效 API key
208
-
209
- ```bash
210
- export KOLMOPDF_API_KEY=sk-invalid
211
- # 重启客户端,调用 /kolmopdf:balance
212
- ```
213
-
214
- | 期望 |
215
- |---|
216
- | 返回 `invalid_api_key` |
217
- | 提示用户去 `https://www.kolmopdf.com/api-keys` |
218
-
219
- ### 5.2 E2: 积分不足
220
-
221
- 测试方法:使用一个余额很低的 key(例如 5 积分)尝试 parse 一个 100 页 PDF。
222
-
223
- | 期望 |
224
- |---|
225
- | `kolmopdf_estimate_cost` 返回 `sufficient=false`, `shortfall=195` |
226
- | Agent 应停止而不直接发起 parse |
227
- | 提示用户去 `https://www.kolmopdf.com/subscription` |
228
-
229
- ### 5.3 E3: 文件 > 300MB
230
-
231
- ```bash
232
- # 试图 parse oversized-1gb.pdf
233
- ```
234
-
235
- | 期望 |
236
- |---|
237
- | MCP server 在上传前本地拒绝(`client_local_validation`) |
238
- | 错误信息含 "300MB" |
239
-
240
- ### 5.4 E4: 文件 > 800 页
241
-
242
- | 期望 |
243
- |---|
244
- | MCP server 本地用 pdf-lib 读出 page count > 800 后立即报错 |
245
- | 不发起上传,不扣积分 |
246
-
247
- ### 5.5 E5: 非 PDF 文件
248
-
249
- ```bash
250
- # parse 一个 .docx 或 .txt
251
- ```
252
-
253
- | 期望 |
254
- |---|
255
- | 返回 `parse_file_not_pdf` |
256
- | 不扣积分 |
257
-
258
- ### 5.6 E6: 错误的 target_format
259
-
260
- ```bash
261
- # /kolmopdf:convert sample.md --format excel
262
- ```
263
-
264
- | 期望 |
265
- |---|
266
- | 返回 `convert_target_format_unsupported` |
267
- | 列出合法值 |
268
-
269
- ### 5.7 E7: 服务端处理超时
270
-
271
- 无法人为构造,记录在 issue 模板中(用户上报时收集 task_id + log)。client 侧应:
272
-
273
- | 期望 |
274
- |---|
275
- | Polling 超过 `KOLMOPDF_MAX_POLL_MINUTES` 后抛 `client_polling_timeout` |
276
- | 提示用户用 `kolmopdf_get_task_status` 查询 |
277
-
278
- ### 5.8 E8: 网络中断(重试逻辑)
279
-
280
- 测试方法:用 mitmproxy 或 toxiproxy 模拟随机 5xx。
281
-
282
- | 期望 |
283
- |---|
284
- | 单次 HTTP 失败被指数退避重试 3 次后才抛错 |
285
- | Polling 期间偶发失败不中断 |
286
-
287
- ---
288
-
289
- ## 6. 性能与质量基准(与 MinerU 对标)
290
-
291
- ### 6.1 测试集
292
-
293
- 10 篇 PDF:
294
-
295
- | 类型 | 数量 | 来源 |
296
- |---|---|---|
297
- | arxiv 论文(公式密集) | 4 | arxiv.org,2023–2026 |
298
- | IEEE/ACM 标准 | 2 | IEEE Xplore 公开预览 |
299
- | 中日韩技术文档 | 2 | 多语言场景 |
300
- | 含跨页表格 | 1 | 财报或行业报告 |
301
- | 扫描版 PDF(OCR 路径) | 1 | 复印件 |
302
-
303
- ### 6.2 度量指标
304
-
305
- | 指标 | 度量方法 |
306
- |---|---|
307
- | 端到端耗时 | client 计时(含上传 + 排队 + 处理 + 下载) |
308
- | 公式还原率 | 人工抽检 100 条公式,统计 LaTeX 完全正确比例 |
309
- | 表格还原率 | 人工抽检所有表格,统计结构 + 内容完全正确比例 |
310
- | 多栏阅读顺序错误数 | 人工查阅 markdown 中的串行位置 |
311
- | 翻译可读性(仅 T2/T3 用例) | 1–5 主观打分 × 3 人评 |
312
- | 积分成本 | 实际扣分 |
313
-
314
- ### 6.3 对照组
315
-
316
- | 工具 | 调用方式 |
317
- |---|---|
318
- | KolmoPDF | 本插件 |
319
- | MinerU | `mineru-mcp`(VLM 模式) |
320
- | Mistral OCR | `mcp-pdf2md` |
321
- | Claude Code 内置 Read | 仅可用于 ≤ 20 页 |
322
-
323
- ### 6.4 报告格式
324
-
325
- `tests/benchmark/results-<date>.md` 存放原始计时与抽检数据;`tests/benchmark/summary-<date>.csv` 存放统计结果。
326
-
327
- ---
328
-
329
- ## 7. CI smoke test 规范
330
-
331
- GitHub Actions secret 中注入测试 key 后,nightly 跑:
332
-
333
- | 用例 | 输入 | 验证 |
334
- |---|---|---|
335
- | smoke-parse | 1 页测试 PDF | exit 0, markdown_path 存在 |
336
- | smoke-translate | 1 页中文 PDF → en | exit 0, translated_pdf_path 存在 |
337
- | smoke-convert | hello.md → docx | exit 0, docx 存在 |
338
- | smoke-balance | — | exit 0, points > 0 |
339
-
340
- 每次 smoke 总消耗约 6 积分(2+2+1+0)。月预算 ≈ 200 积分(30 次 × 6)。
341
-
342
- ---
343
-
344
- ## 8. 故障排查
345
-
346
- | 症状 | 排查 |
347
- |---|---|
348
- | `/plugin install` 后 skill 不出现 | 重启客户端;`/plugin marketplace update kolmopdf` |
349
- | 调用工具报 `Cannot find module @kolmopdf/mcp-server` | npm registry 可达性;本地 `npm cache clean --force` 重试 |
350
- | 任务一直 `waiting` 不进入 `processing` | API 端单 key 上限 3 并发;用 `/kolmopdf:balance` 确认 key 有效 |
351
- | 公式渲染异常 | 检查下游 markdown renderer 是否支持 KaTeX;改用 `formula_format=bracket` |
352
- | 中文乱码 | 检查 markdown 文件 BOM;用 `iconv -f utf-8 -t utf-8 file.md` 标准化 |
353
- | ZIP 解压失败 | 检查 `KOLMOPDF_OUTPUT_DIR` 写入权限 |
354
- | 大文件上传中断 | 拉高 `KOLMOPDF_UPLOAD_TIMEOUT_MS`;检查网络稳定性(mainland China 用户可能需要 proxy) |
355
- | `npx -y` 卡住 | 第一次拉包时间较长,~30s 正常;若超过 2 min 检查 npm registry 配置 |
356
-
357
- ---
358
-
359
- ## 9. 用户报告 bug 时收集的最小信息
360
-
361
- | 字段 | 获取方式 |
362
- |---|---|
363
- | 客户端版本 | Claude Code: `claude --version` / Codex: `codex --version` |
364
- | Plugin 版本 | `/plugin list` 或查看 `plugin.json` |
365
- | MCP server 版本 | `npx @kolmopdf/mcp-server --version`(若实现) |
366
- | Node 版本 | `node --version` |
367
- | task_id | 失败时工具返回值中 |
368
- | error_code + message | MCP 工具返回内容 |
369
- | 操作系统 | `uname -a` |
370
- | 网络位置 | 是否大陆/海外(影响 KolmoPDF 上行链路) |
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 KomoAI LLC
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,37 +0,0 @@
1
- # @kolmopdf/mcp-server
2
-
3
- MCP server for [KolmoPDF](https://www.kolmopdf.com) — high-fidelity PDF→Markdown parsing, layout-preserving PDF translation, and Markdown→DOCX/HTML/PDF/LaTeX conversion.
4
-
5
- ## Usage
6
-
7
- ```jsonc
8
- {
9
- "mcpServers": {
10
- "kolmopdf": {
11
- "command": "npx",
12
- "args": ["-y", "@kolmopdf/mcp-server"],
13
- "env": { "KOLMOPDF_API_KEY": "sk-..." }
14
- }
15
- }
16
- }
17
- ```
18
-
19
- ## Environment variables
20
-
21
- | Variable | Required | Default | Description |
22
- | --- | --- | --- | --- |
23
- | `KOLMOPDF_API_KEY` | yes | — | API key from https://www.kolmopdf.com/api-keys |
24
- | `KOLMOPDF_BASE_URL` | no | `https://www.kolmopdf.com` | Override for enterprise/debug |
25
- | `KOLMOPDF_OUTPUT_DIR` | no | `./kolmopdf-output` | Extraction root |
26
- | `KOLMOPDF_POLL_INTERVAL_MS` | no | `2000` | Status poll interval |
27
- | `KOLMOPDF_MAX_POLL_MINUTES` | no | `30` | Max client-side wait |
28
- | `KOLMOPDF_HTTP_TIMEOUT_MS` | no | `60000` | Per-call HTTP timeout |
29
- | `KOLMOPDF_UPLOAD_TIMEOUT_MS` | no | `600000` | Upload timeout |
30
-
31
- ## Tools
32
-
33
- `kolmopdf_parse_pdf`, `kolmopdf_translate_pdf`, `kolmopdf_convert_markdown`, `kolmopdf_estimate_cost`, `kolmopdf_check_balance`, `kolmopdf_get_task_status`.
34
-
35
- ## License
36
-
37
- MIT
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/errors.ts","../src/config.ts","../src/context.ts","../src/tools/check-balance.ts","../src/tools/convert.ts","../src/pages.ts","../src/progress.ts","../src/polling.ts","../src/tools/estimate-cost.ts","../src/tools/get-task-status.ts","../src/tools/parse-pdf.ts","../src/extract.ts","../src/tools/translate-pdf.ts"],"sourcesContent":["/**\r\n * @kolmopdf/mcp-server — stdio MCP server bootstrap (DEVELOPMENT.md §5.2).\r\n *\r\n * - Registers over the stdio transport.\r\n * - Does NOT validate the API key at startup; the key is read lazily so the\r\n * server boots even with no network / no key. The first authenticated tool\r\n * call surfaces a missing key as an MCP error (invalid_api_key).\r\n */\r\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\r\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\r\nimport { KolmoPdfClient } from \"./client.js\";\r\nimport { loadConfig } from \"./config.js\";\r\nimport { type McpSuccessResult, type ToolContext, jsonResult } from \"./context.js\";\r\nimport { KolmoPdfError, toMcpErrorResult } from \"./errors.js\";\r\nimport {\r\n checkBalanceDescription,\r\n checkBalanceHandler,\r\n checkBalanceInputSchema,\r\n checkBalanceName,\r\n} from \"./tools/check-balance.js\";\r\nimport {\r\n convertDescription,\r\n convertHandler,\r\n convertInputSchema,\r\n convertName,\r\n} from \"./tools/convert.js\";\r\nimport {\r\n estimateCostDescription,\r\n estimateCostHandler,\r\n estimateCostInputSchema,\r\n estimateCostName,\r\n} from \"./tools/estimate-cost.js\";\r\nimport {\r\n getTaskStatusDescription,\r\n getTaskStatusHandler,\r\n getTaskStatusInputSchema,\r\n getTaskStatusName,\r\n} from \"./tools/get-task-status.js\";\r\nimport {\r\n parsePdfDescription,\r\n parsePdfHandler,\r\n parsePdfInputSchema,\r\n parsePdfName,\r\n} from \"./tools/parse-pdf.js\";\r\nimport {\r\n translatePdfDescription,\r\n translatePdfHandler,\r\n translatePdfInputSchema,\r\n translatePdfName,\r\n} from \"./tools/translate-pdf.js\";\r\n\r\nconst VERSION = \"1.0.0\";\r\n\r\n/** Build the per-call tool context with a lazily-constructed API client. */\r\nfunction buildContext(): ToolContext {\r\n const config = loadConfig();\r\n return {\r\n config,\r\n getClient(): KolmoPdfClient {\r\n if (!config.apiKey) {\r\n throw new KolmoPdfError(\"invalid_api_key\");\r\n }\r\n return new KolmoPdfClient({\r\n apiKey: config.apiKey,\r\n baseUrl: config.baseUrl,\r\n httpTimeoutMs: config.httpTimeoutMs,\r\n uploadTimeoutMs: config.uploadTimeoutMs,\r\n });\r\n },\r\n };\r\n}\r\n\r\n/** Wrap a typed handler so all thrown errors become MCP error results (§5.13). */\r\nfunction guard<A>(\r\n handler: (args: A, ctx: ToolContext) => Promise<McpSuccessResult>,\r\n): (args: unknown) => Promise<CallToolResult> {\r\n return async (args: unknown) => {\r\n try {\r\n return (await handler(args as A, buildContext())) as CallToolResult;\r\n } catch (err) {\r\n return toMcpErrorResult(err) as CallToolResult;\r\n }\r\n };\r\n}\r\n\r\nexport function createServer(): McpServer {\r\n const server = new McpServer({ name: \"kolmopdf\", version: VERSION });\r\n\r\n server.registerTool(\r\n parsePdfName,\r\n { description: parsePdfDescription, inputSchema: parsePdfInputSchema.shape },\r\n guard(parsePdfHandler),\r\n );\r\n server.registerTool(\r\n translatePdfName,\r\n { description: translatePdfDescription, inputSchema: translatePdfInputSchema.shape },\r\n guard(translatePdfHandler),\r\n );\r\n server.registerTool(\r\n convertName,\r\n { description: convertDescription, inputSchema: convertInputSchema.shape },\r\n guard(convertHandler),\r\n );\r\n server.registerTool(\r\n estimateCostName,\r\n { description: estimateCostDescription, inputSchema: estimateCostInputSchema.shape },\r\n guard(estimateCostHandler),\r\n );\r\n server.registerTool(\r\n checkBalanceName,\r\n { description: checkBalanceDescription, inputSchema: checkBalanceInputSchema.shape },\r\n guard(checkBalanceHandler),\r\n );\r\n server.registerTool(\r\n getTaskStatusName,\r\n { description: getTaskStatusDescription, inputSchema: getTaskStatusInputSchema.shape },\r\n guard(getTaskStatusHandler),\r\n );\r\n\r\n return server;\r\n}\r\n\r\nasync function main(): Promise<void> {\r\n // Surface --version without booting the transport (TESTING_AND_USAGE.md §9).\r\n if (process.argv.includes(\"--version\") || process.argv.includes(\"-v\")) {\r\n process.stdout.write(`${VERSION}\\n`);\r\n return;\r\n }\r\n const server = createServer();\r\n const transport = new StdioServerTransport();\r\n await server.connect(transport);\r\n}\r\n\r\n// `jsonResult` is part of the public surface used by tool handlers (M2+).\r\nexport { jsonResult };\r\n\r\nmain().catch((err) => {\r\n const detail = err instanceof Error ? err.stack : String(err);\r\n process.stderr.write(`[kolmopdf-mcp] fatal: ${detail}\\n`);\r\n process.exit(1);\r\n});\r\n","import type { Writable } from \"node:stream\";\r\nimport { Readable } from \"node:stream\";\r\nimport { pipeline } from \"node:stream/promises\";\r\nimport { KolmoPdfError, errorFromApiBody } from \"./errors.js\";\r\n\r\nexport interface KolmoPdfClientOptions {\r\n apiKey: string;\r\n baseUrl: string;\r\n httpTimeoutMs: number;\r\n uploadTimeoutMs: number;\r\n}\r\n\r\nexport interface ParseForm {\r\n table_mode?: \"markdown\" | \"image\";\r\n formula_format?: \"dollar\" | \"bracket\";\r\n enable_translation?: boolean;\r\n target_language?: string;\r\n output_options?: string[];\r\n images_as_url?: boolean;\r\n skip_rotation_detection?: boolean;\r\n enable_cross_page_merge?: boolean;\r\n}\r\n\r\nexport interface TranslateForm {\r\n source_language?: string;\r\n target_language?: string;\r\n layout_modes?: Array<\"translated_only\" | \"side_by_side\">;\r\n enable_image_translation?: boolean;\r\n enable_table_translation?: boolean;\r\n}\r\n\r\nexport interface ConvertForm {\r\n target_format?: string;\r\n}\r\n\r\nexport type TaskStatus = \"pending\" | \"waiting\" | \"processing\" | \"completed\" | \"failed\";\r\n\r\nexport interface SubmitResult {\r\n task_id: string;\r\n status: TaskStatus | string;\r\n points_deducted: number;\r\n remaining_points: number;\r\n queue_info?: { position: number; ahead_tasks: number };\r\n}\r\n\r\nexport interface StatusResult {\r\n success: boolean;\r\n status: TaskStatus | string;\r\n message?: string;\r\n queue_info?: { position: number; ahead_tasks: number };\r\n error_code?: string;\r\n result?: { task_id: string; download_url: string };\r\n}\r\n\r\nexport interface DownloadMeta {\r\n contentType: string | null;\r\n isZip: boolean;\r\n bytesWritten: number;\r\n}\r\n\r\nexport interface BalanceResult {\r\n success: boolean;\r\n points: number;\r\n api_key: string;\r\n}\r\n\r\nexport type FileInput = Buffer | NodeJS.ReadableStream;\r\n\r\nexport class KolmoPdfClient {\r\n private readonly apiKey: string;\r\n private readonly baseUrl: string;\r\n private readonly httpTimeoutMs: number;\r\n private readonly uploadTimeoutMs: number;\r\n\r\n constructor(opts: KolmoPdfClientOptions) {\r\n this.apiKey = opts.apiKey;\r\n this.baseUrl = opts.baseUrl;\r\n this.httpTimeoutMs = opts.httpTimeoutMs;\r\n this.uploadTimeoutMs = opts.uploadTimeoutMs;\r\n }\r\n\r\n private get apiBase(): string {\r\n return `${this.baseUrl}/api/pdf-to-markdown-proxy`;\r\n }\r\n\r\n private headers(): Record<string, string> {\r\n return { \"X-API-Key\": this.apiKey };\r\n }\r\n\r\n private async jsonRequest(url: string, init: RequestInit): Promise<unknown> {\r\n const res = await fetch(url, init);\r\n const body = (await res.json()) as Record<string, unknown>;\r\n if (!res.ok || body.success === false) {\r\n throw errorFromApiBody(\r\n body as {\r\n error_code?: string;\r\n message?: string;\r\n points_required?: number;\r\n current_points?: number;\r\n },\r\n res.status,\r\n );\r\n }\r\n return body;\r\n }\r\n\r\n private async buildFileForm(file: FileInput, filename: string): Promise<FormData> {\r\n const form = new FormData();\r\n let blob: Blob;\r\n if (Buffer.isBuffer(file)) {\r\n blob = new Blob([file]);\r\n } else {\r\n const chunks: Buffer[] = [];\r\n for await (const chunk of file) {\r\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\r\n }\r\n blob = new Blob([Buffer.concat(chunks)]);\r\n }\r\n form.append(\"file\", blob, filename);\r\n return form;\r\n }\r\n\r\n async parse(file: FileInput, form: ParseForm, filename: string): Promise<SubmitResult> {\r\n const fd = await this.buildFileForm(file, filename);\r\n if (form.table_mode) fd.append(\"table_mode\", form.table_mode);\r\n if (form.formula_format) fd.append(\"formula_format\", form.formula_format);\r\n if (form.enable_translation !== undefined)\r\n fd.append(\"enable_translation\", String(form.enable_translation));\r\n if (form.target_language) fd.append(\"target_language\", form.target_language);\r\n if (form.output_options?.length) fd.append(\"output_options\", form.output_options.join(\",\"));\r\n if (form.images_as_url !== undefined) fd.append(\"images_as_url\", String(form.images_as_url));\r\n if (form.skip_rotation_detection !== undefined)\r\n fd.append(\"skip_rotation_detection\", String(form.skip_rotation_detection));\r\n if (form.enable_cross_page_merge !== undefined)\r\n fd.append(\"enable_cross_page_merge\", String(form.enable_cross_page_merge));\r\n\r\n const body = await this.jsonRequest(`${this.apiBase}/parse`, {\r\n method: \"POST\",\r\n headers: this.headers(),\r\n body: fd,\r\n signal: AbortSignal.timeout(this.uploadTimeoutMs),\r\n });\r\n return body as unknown as SubmitResult;\r\n }\r\n\r\n async translatePdf(\r\n file: FileInput,\r\n form: TranslateForm,\r\n filename: string,\r\n ): Promise<SubmitResult> {\r\n const fd = await this.buildFileForm(file, filename);\r\n if (form.source_language) fd.append(\"sourceLanguage\", form.source_language);\r\n if (form.target_language) fd.append(\"targetLanguage\", form.target_language);\r\n if (form.layout_modes?.length) fd.append(\"layoutModes\", form.layout_modes.join(\",\"));\r\n if (form.enable_image_translation !== undefined)\r\n fd.append(\"enableImageTranslation\", String(form.enable_image_translation));\r\n if (form.enable_table_translation !== undefined)\r\n fd.append(\"enableTableTranslation\", String(form.enable_table_translation));\r\n\r\n const body = await this.jsonRequest(`${this.apiBase}/translate-pdf`, {\r\n method: \"POST\",\r\n headers: this.headers(),\r\n body: fd,\r\n signal: AbortSignal.timeout(this.uploadTimeoutMs),\r\n });\r\n return body as unknown as SubmitResult;\r\n }\r\n\r\n async convert(file: FileInput, form: ConvertForm, filename: string): Promise<SubmitResult> {\r\n const fd = await this.buildFileForm(file, filename);\r\n if (form.target_format) fd.append(\"targetFormat\", form.target_format);\r\n\r\n const body = await this.jsonRequest(`${this.apiBase}/convert`, {\r\n method: \"POST\",\r\n headers: this.headers(),\r\n body: fd,\r\n signal: AbortSignal.timeout(this.uploadTimeoutMs),\r\n });\r\n return body as unknown as SubmitResult;\r\n }\r\n\r\n async getStatus(taskId: string): Promise<StatusResult> {\r\n const body = await this.jsonRequest(`${this.apiBase}/status/${taskId}`, {\r\n method: \"GET\",\r\n headers: this.headers(),\r\n signal: AbortSignal.timeout(this.httpTimeoutMs),\r\n });\r\n return body as unknown as StatusResult;\r\n }\r\n\r\n async download(taskId: string, dest: Writable): Promise<DownloadMeta> {\r\n const res = await fetch(`${this.apiBase}/download/${taskId}`, {\r\n method: \"GET\",\r\n headers: this.headers(),\r\n signal: AbortSignal.timeout(this.uploadTimeoutMs),\r\n });\r\n if (!res.ok) {\r\n throw new KolmoPdfError(\"api_task_error\", {\r\n message: `Download failed with HTTP ${res.status}`,\r\n httpStatus: res.status,\r\n });\r\n }\r\n const contentType = res.headers.get(\"content-type\");\r\n const isZip = contentType?.includes(\"zip\") || contentType?.includes(\"octet-stream\") || false;\r\n const body = res.body;\r\n if (!body) {\r\n throw new KolmoPdfError(\"api_task_error\", { message: \"Empty download response body\" });\r\n }\r\n\r\n const reader = body.getReader();\r\n let bytesWritten = 0;\r\n\r\n async function* generate() {\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n if (done) break;\r\n bytesWritten += value.byteLength;\r\n yield Buffer.from(value);\r\n }\r\n }\r\n\r\n const readable = Readable.from(generate());\r\n await pipeline(readable, dest);\r\n return { contentType, isZip, bytesWritten };\r\n }\r\n\r\n async getBalance(): Promise<BalanceResult> {\r\n const body = await this.jsonRequest(`${this.apiBase}/balance`, {\r\n method: \"GET\",\r\n headers: this.headers(),\r\n signal: AbortSignal.timeout(this.httpTimeoutMs),\r\n });\r\n return body as unknown as BalanceResult;\r\n }\r\n}\r\n","/**\r\n * Unified KolmoPDF error model and MCP error-result formatting.\r\n *\r\n * Implements the error-code mapping table in DEVELOPMENT.md §8 and the\r\n * MCP tool error envelope in §5.13.\r\n */\r\n\r\nexport type ErrorSource = \"api\" | \"client\";\r\n\r\nexport interface ErrorSpec {\r\n /** Default human-readable message. */\r\n message: string;\r\n /** Actionable remediation hint surfaced to the LLM / user. */\r\n remediation: string;\r\n /** Typical HTTP status; null for client-side codes. */\r\n httpStatus: number | null;\r\n source: ErrorSource;\r\n}\r\n\r\n/** Canonical mapping of every error_code we may surface (DEVELOPMENT.md §8). */\r\nexport const ERROR_SPECS: Record<string, ErrorSpec> = {\r\n // --- API codes ---\r\n invalid_api_key: {\r\n message: \"API key is missing or invalid.\",\r\n remediation: \"Create a key at https://www.kolmopdf.com/api-keys (requires Plus/Pro).\",\r\n httpStatus: 401,\r\n source: \"api\",\r\n },\r\n insufficient_points: {\r\n message: \"Not enough credits.\",\r\n remediation: \"Top up at https://www.kolmopdf.com/subscription.\",\r\n httpStatus: 402,\r\n source: \"api\",\r\n },\r\n points_deduction_failed: {\r\n message: \"Credit deduction failed.\",\r\n remediation: \"Retry; if it persists contact support.\",\r\n httpStatus: 402,\r\n source: \"api\",\r\n },\r\n no_file_found: {\r\n message: \"Request missing file field.\",\r\n remediation: \"(internal) MCP server bug, please report.\",\r\n httpStatus: 400,\r\n source: \"api\",\r\n },\r\n parse_file_too_large: {\r\n message: \"PDF exceeds 300MB.\",\r\n remediation: \"Split the PDF locally.\",\r\n httpStatus: 400,\r\n source: \"api\",\r\n },\r\n parse_page_limit_exceeded: {\r\n message: \"PDF exceeds 800 pages.\",\r\n remediation: \"Split the PDF locally.\",\r\n httpStatus: 400,\r\n source: \"api\",\r\n },\r\n parse_file_not_pdf: {\r\n message: \"File is not a valid PDF.\",\r\n remediation: \"Upload a .pdf file.\",\r\n httpStatus: 400,\r\n source: \"api\",\r\n },\r\n translate_pdf_file_too_large: {\r\n message: \"PDF exceeds 300MB.\",\r\n remediation: \"Split the PDF locally.\",\r\n httpStatus: 400,\r\n source: \"api\",\r\n },\r\n translate_pdf_file_not_pdf: {\r\n message: \"File is not a valid PDF.\",\r\n remediation: \"Upload a .pdf file.\",\r\n httpStatus: 400,\r\n source: \"api\",\r\n },\r\n translate_pdf_page_limit_exceeded: {\r\n message: \"PDF exceeds 800 pages.\",\r\n remediation: \"Split the PDF locally.\",\r\n httpStatus: 400,\r\n source: \"api\",\r\n },\r\n convert_file_too_large: {\r\n message: \"File exceeds 300MB.\",\r\n remediation: \"Reduce file size.\",\r\n httpStatus: 400,\r\n source: \"api\",\r\n },\r\n convert_file_type_unsupported: {\r\n message: \"File must be .md / .markdown / .zip.\",\r\n remediation: \"Convert source to markdown first.\",\r\n httpStatus: 400,\r\n source: \"api\",\r\n },\r\n convert_target_format_unsupported: {\r\n message: \"Target format unsupported.\",\r\n remediation: \"Use word/docx/html/pdf/latex/tex.\",\r\n httpStatus: 400,\r\n source: \"api\",\r\n },\r\n file_upload_failed: {\r\n message: \"Upload to storage failed.\",\r\n remediation: \"Check network and retry.\",\r\n httpStatus: 500,\r\n source: \"api\",\r\n },\r\n task_creation_failed: {\r\n message: \"Task creation failed.\",\r\n remediation: \"Retry.\",\r\n httpStatus: 500,\r\n source: \"api\",\r\n },\r\n parse_error: {\r\n message: \"Parsing failed.\",\r\n remediation: \"Retry; if it persists, split and try again.\",\r\n httpStatus: 500,\r\n source: \"api\",\r\n },\r\n parse_file_invalid: {\r\n message: \"PDF is malformed.\",\r\n remediation: \"Re-export the PDF.\",\r\n httpStatus: 500,\r\n source: \"api\",\r\n },\r\n parse_timeout: {\r\n message: \"Server-side timeout.\",\r\n remediation: \"Split into smaller PDFs.\",\r\n httpStatus: 500,\r\n source: \"api\",\r\n },\r\n api_task_error: {\r\n message: \"Generic task error.\",\r\n remediation: \"Retry; if it persists contact support.\",\r\n httpStatus: 500,\r\n source: \"api\",\r\n },\r\n // --- client codes ---\r\n client_polling_timeout: {\r\n message: \"Local polling exceeded KOLMOPDF_MAX_POLL_MINUTES.\",\r\n remediation: \"Task may still be running. Use kolmopdf_get_task_status with task_id.\",\r\n httpStatus: null,\r\n source: \"client\",\r\n },\r\n client_network_error: {\r\n message: \"Network error after retries.\",\r\n remediation: \"Check network.\",\r\n httpStatus: null,\r\n source: \"client\",\r\n },\r\n client_local_validation: {\r\n message: \"Local pre-check failed (page count / file size).\",\r\n remediation: \"See message for the specific limit that was exceeded.\",\r\n httpStatus: null,\r\n source: \"client\",\r\n },\r\n client_extract_failed: {\r\n message: \"ZIP extraction failed.\",\r\n remediation: \"Check disk permissions on output dir.\",\r\n httpStatus: null,\r\n source: \"client\",\r\n },\r\n} as const;\r\n\r\nconst UNKNOWN_SPEC: ErrorSpec = {\r\n message: \"Unknown error.\",\r\n remediation: \"Retry; if it persists contact https://www.kolmopdf.com/contact.\",\r\n httpStatus: null,\r\n source: \"client\",\r\n};\r\n\r\nexport interface KolmoPdfErrorOptions {\r\n /** Override the default message from the spec. */\r\n message?: string;\r\n /** Override the default HTTP status from the spec. */\r\n httpStatus?: number | null;\r\n pointsRequired?: number;\r\n currentPoints?: number;\r\n /** Override the default remediation hint. */\r\n remediation?: string;\r\n}\r\n\r\n/** Structured error thrown across the MCP server; carries a stable error_code. */\r\nexport class KolmoPdfError extends Error {\r\n readonly errorCode: string;\r\n readonly httpStatus: number | null;\r\n readonly remediation: string;\r\n readonly pointsRequired: number | undefined;\r\n readonly currentPoints: number | undefined;\r\n readonly source: ErrorSource;\r\n\r\n constructor(errorCode: string, opts: KolmoPdfErrorOptions = {}) {\r\n const spec = ERROR_SPECS[errorCode] ?? UNKNOWN_SPEC;\r\n super(opts.message ?? spec.message);\r\n this.name = \"KolmoPdfError\";\r\n this.errorCode = errorCode;\r\n this.httpStatus = opts.httpStatus !== undefined ? opts.httpStatus : spec.httpStatus;\r\n this.remediation = opts.remediation ?? spec.remediation;\r\n this.pointsRequired = opts.pointsRequired;\r\n this.currentPoints = opts.currentPoints;\r\n this.source = spec.source;\r\n }\r\n}\r\n\r\n/** Shape of the JSON payload embedded in an MCP error result (DEVELOPMENT.md §5.13). */\r\nexport interface McpErrorPayload {\r\n error_code: string;\r\n message: string;\r\n http_status: number | null;\r\n points_required?: number;\r\n current_points?: number;\r\n remediation: string;\r\n}\r\n\r\n/** MCP tool result envelope for an error (matches MCP SDK `CallToolResult`). */\r\nexport interface McpErrorResult {\r\n isError: true;\r\n content: Array<{ type: \"text\"; text: string }>;\r\n}\r\n\r\n/** Convert a KolmoPdfError (or any error) into the MCP error result envelope. */\r\nexport function toMcpErrorResult(err: unknown): McpErrorResult {\r\n const kerr =\r\n err instanceof KolmoPdfError\r\n ? err\r\n : new KolmoPdfError(\"api_task_error\", {\r\n message: err instanceof Error ? err.message : String(err),\r\n });\r\n\r\n const payload: McpErrorPayload = {\r\n error_code: kerr.errorCode,\r\n message: kerr.message,\r\n http_status: kerr.httpStatus,\r\n remediation: kerr.remediation,\r\n };\r\n if (kerr.pointsRequired !== undefined) payload.points_required = kerr.pointsRequired;\r\n if (kerr.currentPoints !== undefined) payload.current_points = kerr.currentPoints;\r\n\r\n return {\r\n isError: true,\r\n content: [{ type: \"text\", text: JSON.stringify(payload) }],\r\n };\r\n}\r\n\r\n/** Whether an API failure with this code is auto-refunded server-side (§8). */\r\nexport function isAutoRefunded(errorCode: string): boolean {\r\n return (\r\n errorCode === \"task_creation_failed\" ||\r\n errorCode === \"parse_error\" ||\r\n errorCode === \"parse_file_invalid\" ||\r\n errorCode === \"parse_timeout\"\r\n );\r\n}\r\n\r\n/** Map a raw API JSON failure body to a KolmoPdfError. */\r\nexport function errorFromApiBody(\r\n body: {\r\n error_code?: string;\r\n message?: string;\r\n points_required?: number;\r\n current_points?: number;\r\n },\r\n httpStatus?: number,\r\n): KolmoPdfError {\r\n const code = body.error_code ?? \"api_task_error\";\r\n return new KolmoPdfError(code, {\r\n message: body.message,\r\n httpStatus: httpStatus ?? null,\r\n pointsRequired: body.points_required,\r\n currentPoints: body.current_points,\r\n });\r\n}\r\n","/**\r\n * Environment-variable loader for the KolmoPDF MCP server.\r\n *\r\n * Per DEVELOPMENT.md §4 and §5.2, the API key is NOT validated at startup —\r\n * it is read lazily so the server can boot in offline / no-key environments.\r\n * The first authenticated tool call surfaces a missing key as an MCP error.\r\n */\r\n\r\nexport interface KolmoPdfConfig {\r\n /** Resolved at call time; may be undefined until the user sets it. */\r\n apiKey: string | undefined;\r\n baseUrl: string;\r\n outputDir: string;\r\n pollIntervalMs: number;\r\n maxPollMinutes: number;\r\n httpTimeoutMs: number;\r\n uploadTimeoutMs: number;\r\n}\r\n\r\nconst DEFAULTS = {\r\n baseUrl: \"https://www.kolmopdf.com\",\r\n outputDir: \"./kolmopdf-output\",\r\n pollIntervalMs: 2000,\r\n maxPollMinutes: 30,\r\n httpTimeoutMs: 60_000,\r\n uploadTimeoutMs: 600_000,\r\n} as const;\r\n\r\nfunction intFromEnv(value: string | undefined, fallback: number): number {\r\n if (value === undefined || value.trim() === \"\") return fallback;\r\n const parsed = Number.parseInt(value, 10);\r\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\r\n}\r\n\r\nfunction trimTrailingSlash(url: string): string {\r\n return url.replace(/\\/+$/, \"\");\r\n}\r\n\r\n/**\r\n * Build a config object from the current process environment.\r\n * Reads on every call so live env changes are picked up.\r\n */\r\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): KolmoPdfConfig {\r\n const apiKeyRaw = env.KOLMOPDF_API_KEY?.trim();\r\n return {\r\n apiKey: apiKeyRaw && apiKeyRaw.length > 0 ? apiKeyRaw : undefined,\r\n baseUrl: trimTrailingSlash(env.KOLMOPDF_BASE_URL?.trim() || DEFAULTS.baseUrl),\r\n outputDir: env.KOLMOPDF_OUTPUT_DIR?.trim() || DEFAULTS.outputDir,\r\n pollIntervalMs: intFromEnv(env.KOLMOPDF_POLL_INTERVAL_MS, DEFAULTS.pollIntervalMs),\r\n maxPollMinutes: intFromEnv(env.KOLMOPDF_MAX_POLL_MINUTES, DEFAULTS.maxPollMinutes),\r\n httpTimeoutMs: intFromEnv(env.KOLMOPDF_HTTP_TIMEOUT_MS, DEFAULTS.httpTimeoutMs),\r\n uploadTimeoutMs: intFromEnv(env.KOLMOPDF_UPLOAD_TIMEOUT_MS, DEFAULTS.uploadTimeoutMs),\r\n };\r\n}\r\n\r\n/** Mask an API key for display: first 6 + \"***\" + last 4 (DEVELOPMENT.md §5.8). */\r\nexport function maskApiKey(apiKey: string): string {\r\n if (apiKey.length <= 10) return \"***\";\r\n return `${apiKey.slice(0, 6)}***${apiKey.slice(-4)}`;\r\n}\r\n\r\nexport const configDefaults = DEFAULTS;\r\n","/**\r\n * Shared per-call context handed to every tool handler.\r\n */\r\nimport type { KolmoPdfClient } from \"./client.js\";\r\nimport type { KolmoPdfConfig } from \"./config.js\";\r\nimport type { ProgressReporter } from \"./progress.js\";\r\n\r\nexport interface ToolContext {\r\n config: KolmoPdfConfig;\r\n /** Lazily constructed; throws invalid_api_key when the key is absent. */\r\n getClient(): KolmoPdfClient;\r\n progress?: ProgressReporter;\r\n}\r\n\r\n/** Standard MCP success result envelope (subset of the SDK `CallToolResult`). */\r\nexport interface McpSuccessResult {\r\n content: Array<{ type: \"text\"; text: string }>;\r\n structuredContent?: Record<string, unknown>;\r\n}\r\n\r\n/** Wrap a JSON-serializable object into the MCP success envelope. */\r\nexport function jsonResult(data: Record<string, unknown>): McpSuccessResult {\r\n return {\r\n content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }],\r\n structuredContent: data,\r\n };\r\n}\r\n","import { z } from \"zod\";\r\nimport { maskApiKey } from \"../config.js\";\r\nimport type { McpSuccessResult, ToolContext } from \"../context.js\";\r\nimport { jsonResult } from \"../context.js\";\r\n\r\nexport const checkBalanceName = \"kolmopdf_check_balance\";\r\n\r\nexport const checkBalanceDescription =\r\n \"Show the current KolmoPDF credit balance for the configured API key.\";\r\n\r\nexport const checkBalanceInputSchema = z.object({});\r\n\r\nexport type CheckBalanceInput = z.infer<typeof checkBalanceInputSchema>;\r\n\r\nexport interface CheckBalanceOutput {\r\n points: number;\r\n api_key_masked: string;\r\n}\r\n\r\nexport async function checkBalanceHandler(\r\n _args: CheckBalanceInput,\r\n ctx: ToolContext,\r\n): Promise<McpSuccessResult> {\r\n const client = ctx.getClient();\r\n const balance = await client.getBalance();\r\n\r\n const output: CheckBalanceOutput = {\r\n points: balance.points,\r\n api_key_masked: maskApiKey(ctx.config.apiKey || \"\"),\r\n };\r\n\r\n return jsonResult(output as unknown as Record<string, unknown>);\r\n}\r\n","import { createWriteStream, mkdirSync } from \"node:fs\";\r\nimport { readFile } from \"node:fs/promises\";\r\nimport { basename, join, resolve } from \"node:path\";\r\nimport { z } from \"zod\";\r\nimport type { McpSuccessResult, ToolContext } from \"../context.js\";\r\nimport { jsonResult } from \"../context.js\";\r\nimport { KolmoPdfError } from \"../errors.js\";\r\nimport { MAX_FILE_BYTES, readFileSize } from \"../pages.js\";\r\nimport { pollUntilComplete } from \"../polling.js\";\r\n\r\nexport const convertName = \"kolmopdf_convert_markdown\";\r\n\r\nexport const convertDescription =\r\n \"Convert a Markdown file (or a ZIP of markdown + images) to DOCX, HTML, PDF, \" +\r\n \"or LaTeX via KolmoPDF.\";\r\n\r\nexport const convertInputSchema = z.object({\r\n file_path: z\r\n .string()\r\n .describe(\"Path to a .md/.markdown file or .zip containing markdown + images.\"),\r\n target_format: z.enum([\"word\", \"docx\", \"html\", \"pdf\", \"latex\", \"tex\"]).optional().default(\"word\"),\r\n output_subdir: z.string().optional(),\r\n});\r\n\r\nexport type ConvertInput = z.infer<typeof convertInputSchema>;\r\n\r\nexport interface ConvertOutput {\r\n task_id: string;\r\n points_deducted: number;\r\n remaining_points: number;\r\n output: {\r\n output_path: string;\r\n target_format: string;\r\n };\r\n}\r\n\r\nexport function formatToExtension(targetFormat: string): string {\r\n switch (targetFormat) {\r\n case \"word\":\r\n case \"docx\":\r\n return \".docx\";\r\n case \"html\":\r\n return \".html\";\r\n case \"pdf\":\r\n return \".pdf\";\r\n case \"latex\":\r\n case \"tex\":\r\n return \".tex\";\r\n default:\r\n return \".out\";\r\n }\r\n}\r\n\r\nexport function normalizeFormat(targetFormat: string): string {\r\n switch (targetFormat) {\r\n case \"word\":\r\n case \"docx\":\r\n return \"docx\";\r\n case \"latex\":\r\n case \"tex\":\r\n return \"tex\";\r\n default:\r\n return targetFormat;\r\n }\r\n}\r\nexport async function convertHandler(\r\n args: ConvertInput,\r\n ctx: ToolContext,\r\n): Promise<McpSuccessResult> {\r\n const client = ctx.getClient();\r\n const filePath = resolve(args.file_path);\r\n const filename = basename(filePath);\r\n\r\n const fileSize = await readFileSize(filePath);\r\n if (fileSize > MAX_FILE_BYTES) {\r\n throw new KolmoPdfError(\"convert_file_too_large\");\r\n }\r\n\r\n const ext = filePath.toLowerCase();\r\n if (!ext.endsWith(\".md\") && !ext.endsWith(\".markdown\") && !ext.endsWith(\".zip\")) {\r\n throw new KolmoPdfError(\"convert_file_type_unsupported\");\r\n }\r\n\r\n await ctx.progress?.report(\"[uploading] Sending file for conversion...\");\r\n\r\n const fileBuffer = await readFile(filePath);\r\n const submitResult = await client.convert(\r\n fileBuffer,\r\n {\r\n target_format: args.target_format,\r\n },\r\n filename,\r\n );\r\n\r\n const taskId = submitResult.task_id;\r\n await ctx.progress?.report(`[submitted] Task ${taskId} created`);\r\n\r\n await pollUntilComplete({\r\n client,\r\n taskId,\r\n options: {\r\n pollIntervalMs: ctx.config.pollIntervalMs,\r\n maxPollMinutes: ctx.config.maxPollMinutes,\r\n },\r\n progress: ctx.progress,\r\n });\r\n\r\n await ctx.progress?.report(\"[downloading] Fetching converted file...\");\r\n\r\n const subdir = args.output_subdir || taskId;\r\n const outputRoot = resolve(ctx.config.outputDir, subdir);\r\n mkdirSync(outputRoot, { recursive: true });\r\n\r\n const outExt = formatToExtension(args.target_format);\r\n const outputPath = join(outputRoot, `result${outExt}`);\r\n const ws = createWriteStream(outputPath);\r\n await client.download(taskId, ws);\r\n\r\n const output: ConvertOutput = {\r\n task_id: taskId,\r\n points_deducted: submitResult.points_deducted,\r\n remaining_points: submitResult.remaining_points,\r\n output: {\r\n output_path: outputPath,\r\n target_format: normalizeFormat(args.target_format),\r\n },\r\n };\r\n\r\n return jsonResult(output as unknown as Record<string, unknown>);\r\n}\r\n","import { readFile, stat } from \"node:fs/promises\";\r\nimport { PDFDocument } from \"pdf-lib\";\r\n\r\nexport const MAX_PAGES = 800;\r\nexport const MAX_FILE_BYTES = 300 * 1024 * 1024;\r\n\r\nexport async function readPageCount(filePath: string): Promise<number> {\r\n const data = await readFile(filePath);\r\n const doc = await PDFDocument.load(data, { ignoreEncryption: true });\r\n return doc.getPageCount();\r\n}\r\n\r\nexport async function readFileSize(filePath: string): Promise<number> {\r\n const s = await stat(filePath);\r\n return s.size;\r\n}\r\n","/**\r\n * MCP progress-notification helper (DEVELOPMENT.md §5.11).\r\n *\r\n * `progress` must be monotonically increasing; `total` is omitted because the\r\n * API does not provide a precise percentage.\r\n */\r\n\r\n/** Minimal shape of the MCP request context used to emit notifications. */\r\nexport interface ProgressSink {\r\n /** Present only when the client supplied a progressToken in request `_meta`. */\r\n progressToken?: string | number;\r\n notify(notification: {\r\n method: \"notifications/progress\";\r\n params: {\r\n progressToken: string | number;\r\n progress: number;\r\n message?: string;\r\n };\r\n }): Promise<void>;\r\n}\r\n\r\n/** Stateful emitter that guarantees a monotonically increasing counter. */\r\nexport class ProgressReporter {\r\n private counter = 0;\r\n\r\n constructor(private readonly sink: ProgressSink | undefined) {}\r\n\r\n async report(message: string): Promise<void> {\r\n if (!this.sink || this.sink.progressToken === undefined) return;\r\n this.counter += 1;\r\n await this.sink.notify({\r\n method: \"notifications/progress\",\r\n params: {\r\n progressToken: this.sink.progressToken,\r\n progress: this.counter,\r\n message,\r\n },\r\n });\r\n }\r\n}\r\n\r\n/** Build a human-readable status line, e.g. \"[waiting] 3 tasks ahead\". */\r\nexport function humanizeStatus(status: string, aheadTasks?: number): string {\r\n if (status === \"waiting\" && typeof aheadTasks === \"number\") {\r\n return `[waiting] ${aheadTasks} tasks ahead`;\r\n }\r\n return `[${status}]`;\r\n}\r\n","import type { KolmoPdfClient, StatusResult } from \"./client.js\";\r\nimport { KolmoPdfError } from \"./errors.js\";\r\nimport { type ProgressReporter, humanizeStatus } from \"./progress.js\";\r\n\r\nexport interface PollOptions {\r\n pollIntervalMs: number;\r\n maxPollMinutes: number;\r\n}\r\n\r\nexport const TERMINAL_OK = \"completed\";\r\nexport const TERMINAL_FAIL = \"failed\";\r\nexport const IN_FLIGHT_STATUSES = new Set([\"pending\", \"waiting\", \"processing\"]);\r\n\r\nexport const RETRY_POLICY = {\r\n maxAttempts: 3,\r\n baseDelayMs: 1000,\r\n factor: 2,\r\n} as const;\r\n\r\nexport function backoffDelayMs(attempt: number): number {\r\n return RETRY_POLICY.baseDelayMs * RETRY_POLICY.factor ** (attempt - 1);\r\n}\r\n\r\nexport function isRetryable(err: { httpStatus?: number | null; code?: string }): boolean {\r\n const transientCodes = [\"ECONNRESET\", \"ETIMEDOUT\", \"ECONNREFUSED\", \"EAI_AGAIN\"];\r\n if (err.code && transientCodes.includes(err.code)) return true;\r\n if (typeof err.httpStatus === \"number\" && err.httpStatus >= 500) return true;\r\n return false;\r\n}\r\n\r\nexport interface PollContext {\r\n client: KolmoPdfClient;\r\n taskId: string;\r\n options: PollOptions;\r\n progress?: ProgressReporter;\r\n}\r\n\r\nfunction sleep(ms: number): Promise<void> {\r\n return new Promise((resolve) => setTimeout(resolve, ms));\r\n}\r\n\r\nasync function fetchStatusWithRetry(client: KolmoPdfClient, taskId: string): Promise<StatusResult> {\r\n for (let attempt = 1; attempt <= RETRY_POLICY.maxAttempts; attempt++) {\r\n try {\r\n return await client.getStatus(taskId);\r\n } catch (err) {\r\n const retryable =\r\n err instanceof KolmoPdfError\r\n ? isRetryable(err)\r\n : isRetryable({ code: (err as NodeJS.ErrnoException).code });\r\n if (!retryable || attempt === RETRY_POLICY.maxAttempts) throw err;\r\n await sleep(backoffDelayMs(attempt));\r\n }\r\n }\r\n throw new KolmoPdfError(\"client_network_error\");\r\n}\r\n\r\nexport async function pollUntilComplete(ctx: PollContext): Promise<StatusResult> {\r\n const { client, taskId, options, progress } = ctx;\r\n const deadline = Date.now() + options.maxPollMinutes * 60_000;\r\n\r\n while (true) {\r\n if (Date.now() > deadline) {\r\n throw new KolmoPdfError(\"client_polling_timeout\");\r\n }\r\n\r\n const result = await fetchStatusWithRetry(client, taskId);\r\n\r\n if (result.status === TERMINAL_OK) {\r\n await progress?.report(`[completed] Task ${taskId} done`);\r\n return result;\r\n }\r\n\r\n if (result.status === TERMINAL_FAIL) {\r\n throw new KolmoPdfError(result.error_code || \"api_task_error\", {\r\n message: result.message || \"Task failed\",\r\n });\r\n }\r\n\r\n const aheadTasks = result.queue_info?.ahead_tasks;\r\n await progress?.report(humanizeStatus(result.status as string, aheadTasks));\r\n await sleep(options.pollIntervalMs);\r\n }\r\n}\r\n","import { resolve } from \"node:path\";\r\nimport { z } from \"zod\";\r\nimport type { McpSuccessResult, ToolContext } from \"../context.js\";\r\nimport { jsonResult } from \"../context.js\";\r\nimport { readPageCount } from \"../pages.js\";\r\n\r\nexport const estimateCostName = \"kolmopdf_estimate_cost\";\r\n\r\nexport const estimateCostDescription =\r\n \"Estimate the credit cost of a KolmoPDF operation before running it. \" +\r\n \"Reads page count locally and checks the current balance. Does not spend credits.\";\r\n\r\nexport const estimateCostInputSchema = z.object({\r\n file_path: z.string(),\r\n operation: z.enum([\"parse\", \"parse_translate\", \"translate\", \"convert\"]),\r\n options: z\r\n .object({\r\n images_as_url: z.boolean().optional(),\r\n })\r\n .optional(),\r\n});\r\n\r\nexport type EstimateCostInput = z.infer<typeof estimateCostInputSchema>;\r\n\r\nexport type Operation = EstimateCostInput[\"operation\"];\r\n\r\nexport interface EstimateCostOutput {\r\n pages: number | null;\r\n estimated_credits: number;\r\n current_balance: number;\r\n sufficient: boolean;\r\n shortfall: number;\r\n recommendation: string;\r\n}\r\n\r\nexport function estimateCredits(operation: Operation, pages: number): number {\r\n switch (operation) {\r\n case \"parse\":\r\n return pages * 2;\r\n case \"parse_translate\":\r\n return pages * 3;\r\n case \"translate\":\r\n return pages * 2;\r\n case \"convert\":\r\n return 1;\r\n }\r\n}\r\n\r\nexport function buildRecommendation(shortfall: number): string {\r\n return shortfall > 0\r\n ? `Need top-up at https://www.kolmopdf.com/subscription (short by ${shortfall} credits).`\r\n : \"Sufficient\";\r\n}\r\n\r\nexport async function estimateCostHandler(\r\n args: EstimateCostInput,\r\n ctx: ToolContext,\r\n): Promise<McpSuccessResult> {\r\n const client = ctx.getClient();\r\n\r\n let pages: number | null = null;\r\n if (args.operation !== \"convert\") {\r\n const filePath = resolve(args.file_path);\r\n pages = await readPageCount(filePath);\r\n }\r\n\r\n const estimatedCredits = estimateCredits(args.operation, pages ?? 1);\r\n const balance = await client.getBalance();\r\n const currentBalance = balance.points;\r\n const shortfall = Math.max(0, estimatedCredits - currentBalance);\r\n\r\n const output: EstimateCostOutput = {\r\n pages,\r\n estimated_credits: estimatedCredits,\r\n current_balance: currentBalance,\r\n sufficient: shortfall === 0,\r\n shortfall,\r\n recommendation: buildRecommendation(shortfall),\r\n };\r\n\r\n return jsonResult(output as unknown as Record<string, unknown>);\r\n}\r\n","import { z } from \"zod\";\r\nimport type { McpSuccessResult, ToolContext } from \"../context.js\";\r\nimport { jsonResult } from \"../context.js\";\r\n\r\nexport const getTaskStatusName = \"kolmopdf_get_task_status\";\r\n\r\nexport const getTaskStatusDescription =\r\n \"Advanced/debug tool. Returns raw status for a KolmoPDF task. Use only when \" +\r\n \"explicitly asked to inspect a task by ID, or when troubleshooting a stuck task.\";\r\n\r\nexport const getTaskStatusInputSchema = z.object({\r\n task_id: z.string(),\r\n});\r\n\r\nexport type GetTaskStatusInput = z.infer<typeof getTaskStatusInputSchema>;\r\n\r\nexport async function getTaskStatusHandler(\r\n args: GetTaskStatusInput,\r\n ctx: ToolContext,\r\n): Promise<McpSuccessResult> {\r\n const client = ctx.getClient();\r\n const status = await client.getStatus(args.task_id);\r\n return jsonResult(status as unknown as Record<string, unknown>);\r\n}\r\n","import { createWriteStream, mkdirSync } from \"node:fs\";\r\nimport { readFile } from \"node:fs/promises\";\r\nimport { basename, join, resolve } from \"node:path\";\r\nimport { z } from \"zod\";\r\nimport type { McpSuccessResult, ToolContext } from \"../context.js\";\r\nimport { jsonResult } from \"../context.js\";\r\nimport { KolmoPdfError } from \"../errors.js\";\r\nimport { extractZip } from \"../extract.js\";\r\nimport { MAX_FILE_BYTES, MAX_PAGES, readFileSize, readPageCount } from \"../pages.js\";\r\nimport { pollUntilComplete } from \"../polling.js\";\r\n\r\nexport const parsePdfName = \"kolmopdf_parse_pdf\";\r\n\r\nexport const parsePdfDescription =\r\n \"Parse a local PDF into Markdown via KolmoPDF. Handles formulas, tables, \" +\r\n \"multi-column layouts, and code blocks. Optionally translates while parsing.\";\r\n\r\nexport const parsePdfInputSchema = z.object({\r\n file_path: z.string().describe(\"Absolute or cwd-relative path to a local PDF file.\"),\r\n table_mode: z.enum([\"markdown\", \"image\"]).optional(),\r\n formula_format: z.enum([\"dollar\", \"bracket\"]).optional(),\r\n enable_translation: z.boolean().optional(),\r\n target_language: z.enum([\"zh\", \"en\", \"ja\", \"ko\", \"fr\", \"de\", \"es\", \"ru\"]).optional(),\r\n output_options: z.array(z.enum([\"original\", \"translated\", \"bilingual\"])).optional(),\r\n images_as_url: z.boolean().optional(),\r\n skip_rotation_detection: z.boolean().optional(),\r\n enable_cross_page_merge: z.boolean().optional(),\r\n output_subdir: z\r\n .string()\r\n .optional()\r\n .describe(\"Subdirectory name under KOLMOPDF_OUTPUT_DIR. Defaults to <task_id>.\"),\r\n});\r\n\r\nexport type ParsePdfInput = z.infer<typeof parsePdfInputSchema>;\r\n\r\nexport interface ParsePdfOutput {\r\n task_id: string;\r\n pages_parsed: number;\r\n points_deducted: number;\r\n remaining_points: number;\r\n output: {\r\n type: \"zip_extracted\" | \"markdown_file\";\r\n markdown_path: string;\r\n images_dir: string | null;\r\n output_root: string;\r\n };\r\n preview: string;\r\n}\r\n\r\nexport async function parsePdfHandler(\r\n args: ParsePdfInput,\r\n ctx: ToolContext,\r\n): Promise<McpSuccessResult> {\r\n const client = ctx.getClient();\r\n const filePath = resolve(args.file_path);\r\n const filename = basename(filePath);\r\n\r\n const fileSize = await readFileSize(filePath);\r\n if (fileSize > MAX_FILE_BYTES) {\r\n throw new KolmoPdfError(\"parse_file_too_large\");\r\n }\r\n\r\n const pageCount = await readPageCount(filePath);\r\n if (pageCount > MAX_PAGES) {\r\n throw new KolmoPdfError(\"parse_page_limit_exceeded\");\r\n }\r\n\r\n await ctx.progress?.report(\"[uploading] Sending PDF to KolmoPDF...\");\r\n\r\n const fileBuffer = await readFile(filePath);\r\n const submitResult = await client.parse(\r\n fileBuffer,\r\n {\r\n table_mode: args.table_mode,\r\n formula_format: args.formula_format,\r\n enable_translation: args.enable_translation,\r\n target_language: args.target_language,\r\n output_options: args.output_options,\r\n images_as_url: args.images_as_url,\r\n skip_rotation_detection: args.skip_rotation_detection,\r\n enable_cross_page_merge: args.enable_cross_page_merge,\r\n },\r\n filename,\r\n );\r\n\r\n const taskId = submitResult.task_id;\r\n await ctx.progress?.report(`[submitted] Task ${taskId} created`);\r\n\r\n await pollUntilComplete({\r\n client,\r\n taskId,\r\n options: {\r\n pollIntervalMs: ctx.config.pollIntervalMs,\r\n maxPollMinutes: ctx.config.maxPollMinutes,\r\n },\r\n progress: ctx.progress,\r\n });\r\n\r\n await ctx.progress?.report(\"[downloading] Fetching result...\");\r\n\r\n const subdir = args.output_subdir || taskId;\r\n const outputRoot = resolve(ctx.config.outputDir, subdir);\r\n mkdirSync(outputRoot, { recursive: true });\r\n\r\n const isUrlMode = args.images_as_url === true;\r\n\r\n let markdownPath: string;\r\n let imagesDir: string | null = null;\r\n let outputType: \"zip_extracted\" | \"markdown_file\";\r\n\r\n if (isUrlMode) {\r\n markdownPath = join(outputRoot, \"result.md\");\r\n const ws = createWriteStream(markdownPath);\r\n await client.download(taskId, ws);\r\n outputType = \"markdown_file\";\r\n } else {\r\n const zipPath = join(outputRoot, \"result.zip\");\r\n const ws = createWriteStream(zipPath);\r\n await client.download(taskId, ws);\r\n const extracted = await extractZip(zipPath, outputRoot);\r\n markdownPath = extracted.markdownPath || join(outputRoot, \"result.md\");\r\n imagesDir = extracted.imagesDir;\r\n outputType = \"zip_extracted\";\r\n }\r\n\r\n const mdContent = await readFile(markdownPath, \"utf-8\").catch(() => \"\");\r\n const preview = mdContent.slice(0, 500);\r\n\r\n const ptsPerPage = args.enable_translation ? 3 : 2;\r\n const pagesParsed = Math.round(submitResult.points_deducted / ptsPerPage);\r\n\r\n const output: ParsePdfOutput = {\r\n task_id: taskId,\r\n pages_parsed: pagesParsed,\r\n points_deducted: submitResult.points_deducted,\r\n remaining_points: submitResult.remaining_points,\r\n output: {\r\n type: outputType,\r\n markdown_path: markdownPath,\r\n images_dir: imagesDir,\r\n output_root: outputRoot,\r\n },\r\n preview,\r\n };\r\n\r\n return jsonResult(output as unknown as Record<string, unknown>);\r\n}\r\n","import { createWriteStream, mkdirSync } from \"node:fs\";\r\nimport { dirname, join } from \"node:path\";\r\nimport { pipeline } from \"node:stream/promises\";\r\nimport { type Entry, type ZipFile, open as yauzlOpen } from \"yauzl\";\r\n\r\nexport interface ExtractResult {\r\n markdownPath: string | null;\r\n imagesDir: string | null;\r\n outputRoot: string;\r\n files: string[];\r\n}\r\n\r\nexport async function extractZip(zipPath: string, destDir: string): Promise<ExtractResult> {\r\n mkdirSync(destDir, { recursive: true });\r\n\r\n const zipFile = await openZip(zipPath);\r\n const files: string[] = [];\r\n let markdownPath: string | null = null;\r\n let imagesDir: string | null = null;\r\n\r\n for await (const entry of iterEntries(zipFile)) {\r\n const entryPath = join(destDir, entry.fileName);\r\n\r\n if (entry.fileName.endsWith(\"/\")) {\r\n mkdirSync(entryPath, { recursive: true });\r\n if (entry.fileName.includes(\"images\")) {\r\n imagesDir = entryPath;\r\n }\r\n continue;\r\n }\r\n\r\n mkdirSync(dirname(entryPath), { recursive: true });\r\n const readStream = await openReadStream(zipFile, entry);\r\n const writeStream = createWriteStream(entryPath);\r\n await pipeline(readStream, writeStream);\r\n files.push(entryPath);\r\n\r\n if (!markdownPath && /\\.md$/i.test(entry.fileName)) {\r\n markdownPath = entryPath;\r\n }\r\n if (!imagesDir && /images\\//i.test(entry.fileName)) {\r\n const prefix = entry.fileName.split(\"images/\")[0] ?? \"\";\r\n imagesDir = join(destDir, prefix, \"images\");\r\n }\r\n }\r\n\r\n return { markdownPath, imagesDir, outputRoot: destDir, files };\r\n}\r\nfunction openZip(path: string): Promise<ZipFile> {\r\n return new Promise((resolve, reject) => {\r\n yauzlOpen(path, { lazyEntries: true }, (err, zf) => {\r\n if (err || !zf) return reject(err ?? new Error(\"Failed to open zip\"));\r\n resolve(zf);\r\n });\r\n });\r\n}\r\n\r\nasync function* iterEntries(zipFile: ZipFile): AsyncGenerator<Entry> {\r\n let resolve: ((entry: Entry | null) => void) | null = null;\r\n const queue: (Entry | null)[] = [];\r\n\r\n zipFile.on(\"entry\", (entry: Entry) => {\r\n if (resolve) {\r\n const r = resolve;\r\n resolve = null;\r\n r(entry);\r\n } else {\r\n queue.push(entry);\r\n }\r\n });\r\n zipFile.on(\"end\", () => {\r\n if (resolve) {\r\n const r = resolve;\r\n resolve = null;\r\n r(null);\r\n } else {\r\n queue.push(null);\r\n }\r\n });\r\n\r\n zipFile.readEntry();\r\n while (true) {\r\n const entry =\r\n queue.length > 0\r\n ? (queue.shift() as Entry | null)\r\n : await new Promise<Entry | null>((r) => {\r\n resolve = r;\r\n });\r\n if (entry === null) break;\r\n yield entry;\r\n zipFile.readEntry();\r\n }\r\n}\r\n\r\nfunction openReadStream(zipFile: ZipFile, entry: Entry): Promise<NodeJS.ReadableStream> {\r\n return new Promise((resolve, reject) => {\r\n zipFile.openReadStream(entry, (err, stream) => {\r\n if (err || !stream) return reject(err ?? new Error(\"Failed to open entry stream\"));\r\n resolve(stream);\r\n });\r\n });\r\n}\r\n","import { createWriteStream, mkdirSync } from \"node:fs\";\r\nimport { readFile } from \"node:fs/promises\";\r\nimport { basename, join, resolve } from \"node:path\";\r\nimport { z } from \"zod\";\r\nimport type { McpSuccessResult, ToolContext } from \"../context.js\";\r\nimport { jsonResult } from \"../context.js\";\r\nimport { KolmoPdfError } from \"../errors.js\";\r\nimport { MAX_FILE_BYTES, MAX_PAGES, readFileSize, readPageCount } from \"../pages.js\";\r\nimport { pollUntilComplete } from \"../polling.js\";\r\n\r\nexport const translatePdfName = \"kolmopdf_translate_pdf\";\r\n\r\nexport const translatePdfDescription =\r\n \"Translate a PDF while preserving its original layout via KolmoPDF. \" +\r\n \"Produces a translated PDF (optionally side-by-side bilingual).\";\r\n\r\nexport const translatePdfInputSchema = z.object({\r\n file_path: z.string(),\r\n source_language: z.string().optional().default(\"en\"),\r\n target_language: z.string().optional().default(\"zh\"),\r\n layout_modes: z\r\n .array(z.enum([\"translated_only\", \"side_by_side\"]))\r\n .optional()\r\n .default([\"translated_only\"]),\r\n enable_image_translation: z.boolean().optional().default(false),\r\n enable_table_translation: z.boolean().optional().default(false),\r\n output_subdir: z.string().optional(),\r\n});\r\n\r\nexport type TranslatePdfInput = z.infer<typeof translatePdfInputSchema>;\r\n\r\nexport interface TranslatePdfOutput {\r\n task_id: string;\r\n pages_translated: number;\r\n points_deducted: number;\r\n remaining_points: number;\r\n output: {\r\n translated_pdf_path: string;\r\n };\r\n}\r\n\r\nexport async function translatePdfHandler(\r\n args: TranslatePdfInput,\r\n ctx: ToolContext,\r\n): Promise<McpSuccessResult> {\r\n const client = ctx.getClient();\r\n const filePath = resolve(args.file_path);\r\n const filename = basename(filePath);\r\n const fileSize = await readFileSize(filePath);\r\n if (fileSize > MAX_FILE_BYTES) {\r\n throw new KolmoPdfError(\"translate_pdf_file_too_large\");\r\n }\r\n\r\n const pageCount = await readPageCount(filePath);\r\n if (pageCount > MAX_PAGES) {\r\n throw new KolmoPdfError(\"translate_pdf_page_limit_exceeded\");\r\n }\r\n\r\n await ctx.progress?.report(\"[uploading] Sending PDF for translation...\");\r\n\r\n const fileBuffer = await readFile(filePath);\r\n const submitResult = await client.translatePdf(\r\n fileBuffer,\r\n {\r\n source_language: args.source_language,\r\n target_language: args.target_language,\r\n layout_modes: args.layout_modes,\r\n enable_image_translation: args.enable_image_translation,\r\n enable_table_translation: args.enable_table_translation,\r\n },\r\n filename,\r\n );\r\n\r\n const taskId = submitResult.task_id;\r\n await ctx.progress?.report(`[submitted] Task ${taskId} created`);\r\n\r\n await pollUntilComplete({\r\n client,\r\n taskId,\r\n options: {\r\n pollIntervalMs: ctx.config.pollIntervalMs,\r\n maxPollMinutes: ctx.config.maxPollMinutes,\r\n },\r\n progress: ctx.progress,\r\n });\r\n\r\n await ctx.progress?.report(\"[downloading] Fetching translated PDF...\");\r\n\r\n const subdir = args.output_subdir || taskId;\r\n const outputRoot = resolve(ctx.config.outputDir, subdir);\r\n mkdirSync(outputRoot, { recursive: true });\r\n\r\n const pdfPath = join(outputRoot, \"translated.pdf\");\r\n const ws = createWriteStream(pdfPath);\r\n await client.download(taskId, ws);\r\n\r\n const pagesTranslated = Math.round(submitResult.points_deducted / 2);\r\n\r\n const output: TranslatePdfOutput = {\r\n task_id: taskId,\r\n pages_translated: pagesTranslated,\r\n points_deducted: submitResult.points_deducted,\r\n remaining_points: submitResult.remaining_points,\r\n output: {\r\n translated_pdf_path: pdfPath,\r\n },\r\n };\r\n\r\n return jsonResult(output as unknown as Record<string, unknown>);\r\n}\r\n"],"mappings":"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","names":["import_promises","import_zod","import_promises","resolve","import_node_path","import_zod","import_zod","import_node_fs","import_promises","import_node_path","import_zod","import_node_fs","import_node_path","import_promises","resolve","yauzlOpen","import_node_fs","import_promises","import_node_path","import_zod"]}