pdf-to-markdown-mcp 1.0.8 → 1.1.0
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/.vscode/settings.json +7 -6
- package/README.md +37 -4
- package/TESTING.md +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +22 -24
- package/dist/src/index.js.map +1 -1
- package/dist/src/openaiCompatibleRequest.d.ts +25 -0
- package/dist/src/openaiCompatibleRequest.d.ts.map +1 -0
- package/dist/src/openaiCompatibleRequest.js +53 -0
- package/dist/src/openaiCompatibleRequest.js.map +1 -0
- package/dist/test/demo.d.ts +1 -1
- package/dist/test/demo.d.ts.map +1 -1
- package/dist/test/demo.js +2 -3
- package/dist/test/demo.js.map +1 -1
- package/dist/test/openaiCompatibleRequest.test.d.ts +2 -0
- package/dist/test/openaiCompatibleRequest.test.d.ts.map +1 -0
- package/dist/test/openaiCompatibleRequest.test.js +21 -0
- package/dist/test/openaiCompatibleRequest.test.js.map +1 -0
- package/dist/test/pdfToMarkdown.test.d.ts +1 -1
- package/dist/test/pdfToMarkdown.test.d.ts.map +1 -1
- package/dist/test/pdfToMarkdown.test.js +12 -32
- package/dist/test/pdfToMarkdown.test.js.map +1 -1
- package/package.json +12 -5
- package/src/index.ts +29 -25
- package/src/openaiCompatibleRequest.ts +105 -0
- package/test/demo.ts +4 -3
- package/test/openaiCompatibleRequest.test.ts +47 -0
- package/test/pdfToMarkdown.test.ts +24 -33
package/.vscode/settings.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"chat.instructionsFilesLocations": {
|
|
3
3
|
".github/instructions": true,
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
4
|
+
"~/source/explore/awesome-copilot/instructions/markdown.instructions.md": true,
|
|
5
|
+
"~/source/explore/awesome-copilot/instructions/security-and-owasp.instructions.md": true,
|
|
6
|
+
"~/source/explore/awesome-copilot/instructions/self-explanatory-code-commenting.instructions.md": true,
|
|
7
|
+
"~/source/explore/awesome-copilot/instructions/typescript-mcp-server.instructions.md": true,
|
|
8
|
+
"~/source/explore/awesome-copilot/instructions/typescript-5-es2022.instructions.md": true,
|
|
9
|
+
"~/source/explore/awesome-copilot/instructions/update-docs-on-code-change.instructions.md": true,
|
|
10
|
+
}
|
|
10
11
|
}
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
4
4
|
[](https://nodejs.org/)
|
|
5
5
|
[](https://www.typescriptlang.org/)
|
|
6
|
-
[](https://modelcontextprotocol.io/)
|
|
7
7
|
|
|
8
8
|
A Model Context Protocol (MCP) server that converts PDF pages to markdown format using the Qwen VL vision model.
|
|
9
9
|
|
|
@@ -28,7 +28,11 @@ npm run build
|
|
|
28
28
|
The server needs the following environment variables:
|
|
29
29
|
- `QWEN_API_URL`: The endpoint URL (e.g., `https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions`)
|
|
30
30
|
- `QWEN_API_KEY`: Your authentication key.
|
|
31
|
-
- `QWEN_MODEL`: The specific model name (defaults to `
|
|
31
|
+
- `QWEN_MODEL`: The specific model name (defaults to `Qwen3-VL-235B-A22B-Instruct`).
|
|
32
|
+
- `VLLM_REASONING_PARSER` (optional): When you serve a reasoning model through vLLM and need thinking disabled, set this to `kimi_k2` for Kimi K2.x or `qwen3` for Qwen3. The server will send the matching `chat_template_kwargs` expected by vLLM.
|
|
33
|
+
- `WORKSPACE` (optional): Comma-separated list of absolute directory paths. When set, the server will only process PDF files located within these directories, preventing access to files outside the allowed workspace.
|
|
34
|
+
|
|
35
|
+
The runtime and test scripts automatically load a project-local `.env` file when one is present, so you can usually keep these values in `.env` instead of exporting them in your shell.
|
|
32
36
|
|
|
33
37
|
### 3. Setup with Claude Desktop
|
|
34
38
|
Add this to your Claude Desktop configuration file:
|
|
@@ -45,7 +49,36 @@ Add this to your Claude Desktop configuration file:
|
|
|
45
49
|
"env": {
|
|
46
50
|
"QWEN_API_URL": "https://your-qwen-api-endpoint.com/v1/chat/completions",
|
|
47
51
|
"QWEN_API_KEY": "your-api-key-here",
|
|
48
|
-
"QWEN_MODEL": "
|
|
52
|
+
"QWEN_MODEL": "Qwen3-VL-235B-A22B-Instruct"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### vLLM reasoning models
|
|
60
|
+
|
|
61
|
+
If your OpenAI-compatible endpoint is powered by vLLM and the served model defaults to thinking mode, configure the parser so the server can disable it correctly:
|
|
62
|
+
|
|
63
|
+
- `VLLM_REASONING_PARSER=kimi_k2` sends `chat_template_kwargs: { "thinking": false }`
|
|
64
|
+
- `VLLM_REASONING_PARSER=qwen3` sends `chat_template_kwargs: { "enable_thinking": false }`
|
|
65
|
+
|
|
66
|
+
Example MCP configuration for Kimi K2.x through vLLM:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"pdf-to-markdown": {
|
|
72
|
+
"command": "npx",
|
|
73
|
+
"args": [
|
|
74
|
+
"-y",
|
|
75
|
+
"pdf-to-markdown-mcp"
|
|
76
|
+
],
|
|
77
|
+
"env": {
|
|
78
|
+
"QWEN_API_URL": "http://your-vllm-host:8000/v1/chat/completions",
|
|
79
|
+
"QWEN_API_KEY": "EMPTY",
|
|
80
|
+
"QWEN_MODEL": "moonshotai/Kimi-K2.6-Vision-Instruct",
|
|
81
|
+
"VLLM_REASONING_PARSER": "kimi_k2"
|
|
49
82
|
}
|
|
50
83
|
}
|
|
51
84
|
}
|
|
@@ -76,7 +109,7 @@ Depending on your OS, you may need additional libraries for PDF rendering:
|
|
|
76
109
|
## Troubleshooting
|
|
77
110
|
- **"PDF file not found"**: Ensure the path is absolute and the file is accessible.
|
|
78
111
|
- **"Invalid page number"**: Check that the page number exists in the document.
|
|
79
|
-
- **API Errors**: Verify your `QWEN_API_URL` and `QWEN_API_KEY`.
|
|
112
|
+
- **API Errors**: Verify your `QWEN_API_URL` and `QWEN_API_KEY` in `.env` or your shell environment.
|
|
80
113
|
- **Render Failures**: If conversion fails on Linux/macOS, ensure the **System Dependencies** above are installed.
|
|
81
114
|
|
|
82
115
|
## License
|
package/TESTING.md
CHANGED
|
@@ -17,7 +17,7 @@ This will test:
|
|
|
17
17
|
|
|
18
18
|
### 2. Test Full PDF to Markdown Conversion (Requires API Keys)
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
The test runner automatically loads a project-local `.env` file when one is present. If you prefer not to use `.env`, set up your Qwen API credentials manually:
|
|
21
21
|
|
|
22
22
|
**Windows (PowerShell):**
|
|
23
23
|
```powershell
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC"}
|
package/dist/src/index.js
CHANGED
|
@@ -1,34 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
2
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
5
|
import * as fs from "node:fs/promises";
|
|
6
|
+
import * as path from "node:path";
|
|
5
7
|
import { z } from "zod";
|
|
8
|
+
import { buildOpenAICompatibleRequest } from "./openaiCompatibleRequest.js";
|
|
6
9
|
import { convertPdfPageToImage } from "./pdfConverter.js";
|
|
7
10
|
/**
|
|
8
11
|
* Call Qwen VL API to convert image to markdown
|
|
9
12
|
*/
|
|
10
|
-
async function convertImageToMarkdown(imageBuffer, apiUrl, apiKey, modelName) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
messages: [
|
|
15
|
-
{
|
|
16
|
-
role: "user",
|
|
17
|
-
content: [
|
|
18
|
-
{
|
|
19
|
-
type: "image_url",
|
|
20
|
-
image_url: {
|
|
21
|
-
url: `data:image/png;base64,${base64Image}`,
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
type: "text",
|
|
26
|
-
text: "Please convert this image to markdown format. Extract all text, tables, and structure accurately.",
|
|
27
|
-
},
|
|
28
|
-
],
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
};
|
|
13
|
+
async function convertImageToMarkdown(imageBuffer, apiUrl, apiKey, modelName, vllmReasoningParser) {
|
|
14
|
+
const requestBody = buildOpenAICompatibleRequest(imageBuffer, modelName, {
|
|
15
|
+
vllmReasoningParser,
|
|
16
|
+
});
|
|
32
17
|
const response = await fetch(apiUrl, {
|
|
33
18
|
method: "POST",
|
|
34
19
|
headers: {
|
|
@@ -58,10 +43,11 @@ async function main() {
|
|
|
58
43
|
const apiUrl = process.env.QWEN_API_URL;
|
|
59
44
|
const apiKey = process.env.QWEN_API_KEY;
|
|
60
45
|
const modelName = process.env.QWEN_MODEL || "Qwen3-VL-235B-A22B-Instruct";
|
|
46
|
+
const vllmReasoningParser = process.env.VLLM_REASONING_PARSER;
|
|
61
47
|
if (!apiUrl || !apiKey) {
|
|
62
48
|
throw new Error("Missing required environment variables: QWEN_API_URL and QWEN_API_KEY must be set");
|
|
63
49
|
}
|
|
64
|
-
const se = "1.0.
|
|
50
|
+
const se = "1.0.9";
|
|
65
51
|
const server = new McpServer({
|
|
66
52
|
name: "pdf-to-markdown-mcp",
|
|
67
53
|
version: se,
|
|
@@ -85,6 +71,18 @@ async function main() {
|
|
|
85
71
|
!Number.isInteger(page_number)) {
|
|
86
72
|
throw new Error("page_number must be a positive integer");
|
|
87
73
|
}
|
|
74
|
+
// Validate pdf_path is within an allowed workspace directory
|
|
75
|
+
const workspaceEnv = process.env.WORKSPACE;
|
|
76
|
+
if (workspaceEnv) {
|
|
77
|
+
const allowedDirs = workspaceEnv
|
|
78
|
+
.split(",")
|
|
79
|
+
.map((d) => path.resolve(d.trim()));
|
|
80
|
+
const resolvedPdfPath = path.resolve(pdf_path);
|
|
81
|
+
const isAllowed = allowedDirs.some((dir) => resolvedPdfPath.startsWith(dir + path.sep) || resolvedPdfPath === dir);
|
|
82
|
+
if (!isAllowed) {
|
|
83
|
+
throw new Error(`Access denied: pdf_path must be located within one of the allowed workspace directories`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
88
86
|
// Check if file exists
|
|
89
87
|
try {
|
|
90
88
|
await fs.access(pdf_path);
|
|
@@ -95,7 +93,7 @@ async function main() {
|
|
|
95
93
|
// Convert PDF page to image
|
|
96
94
|
const imageBuffer = await convertPdfPageToImage(pdf_path, page_number);
|
|
97
95
|
// Convert image to markdown using Qwen VL
|
|
98
|
-
const markdown = await convertImageToMarkdown(imageBuffer, apiUrl, apiKey, modelName);
|
|
96
|
+
const markdown = await convertImageToMarkdown(imageBuffer, apiUrl, apiKey, modelName, vllmReasoningParser);
|
|
99
97
|
return {
|
|
100
98
|
content: [
|
|
101
99
|
{
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAc1D;;GAEG;AACH,KAAK,UAAU,sBAAsB,CACnC,WAAmB,EACnB,MAAc,EACd,MAAc,EACd,SAAiB;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,4BAA4B,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAc1D;;GAEG;AACH,KAAK,UAAU,sBAAsB,CACnC,WAAmB,EACnB,MAAc,EACd,MAAc,EACd,SAAiB,EACjB,mBAA4B;IAE5B,MAAM,WAAW,GAAG,4BAA4B,CAAC,WAAW,EAAE,SAAS,EAAE;QACvE,mBAAmB;KACpB,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE;QACnC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,aAAa,EAAE,UAAU,MAAM,EAAE;SAClC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;KAClC,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CACb,uBAAuB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,MAAM,SAAS,EAAE,CAC/E,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAmB,CAAC;IAEzD,6CAA6C;IAC7C,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;QAC1C,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;IAC3C,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;QACxB,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;IAC5B,CAAC;IAED,MAAM,IAAI,KAAK,CACb,mCAAmC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAC5D,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IACxC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IACxC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,6BAA6B,CAAC;IAC1E,MAAM,mBAAmB,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAE9D,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,mFAAmF,CACpF,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,GAAG,OAAO,CAAC;IACnB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,EAAE;KACZ,CAAC,CAAC;IAEH,gBAAgB;IAChB,MAAM,CAAC,YAAY,CACjB,8BAA8B,EAC9B;QACE,KAAK,EAAE,8BAA8B;QACrC,WAAW,EACT,4MAA4M;QAC9M,WAAW,EAAE;YACX,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAC3B,gFAAgF,CACjF;YACD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAC9B,iGAAiG,CAClG;SACF;KACF,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,EAAE;QAClC,IAAI,CAAC;YACH,kBAAkB;YAClB,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBAC9C,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;YACzD,CAAC;YAED,IACE,OAAO,WAAW,KAAK,QAAQ;gBAC/B,WAAW,GAAG,CAAC;gBACf,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,EAC9B,CAAC;gBACD,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;YAC5D,CAAC;YAED,6DAA6D;YAC7D,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;YAC3C,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,WAAW,GAAG,YAAY;qBAC7B,KAAK,CAAC,GAAG,CAAC;qBACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;gBACtC,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAC/C,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CACzC,eAAe,CAAC,UAAU,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,eAAe,KAAK,GAAG,CACtE,CAAC;gBACF,IAAI,CAAC,SAAS,EAAE,CAAC;oBACf,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAC;gBACJ,CAAC;YACH,CAAC;YAED,uBAAuB;YACvB,IAAI,CAAC;gBACH,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC5B,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,IAAI,KAAK,CAAC,uBAAuB,QAAQ,EAAE,CAAC,CAAC;YACrD,CAAC;YAED,4BAA4B;YAC5B,MAAM,WAAW,GAAG,MAAM,qBAAqB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;YAEvE,0CAA0C;YAC1C,MAAM,QAAQ,GAAG,MAAM,sBAAsB,CAC3C,WAAW,EACX,MAAM,EACN,MAAM,EACN,SAAS,EACT,mBAAmB,CACpB,CAAC;YAEF,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,QAAQ;qBACf;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAChB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACzD,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,0CAA0C,YAAY,EAAE;qBAC/D;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;IAEF,mBAAmB;IACnB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,oBAAoB,CAAC,CAAC;AACxE,CAAC;AAED,IAAI,CAAC;IACH,MAAM,IAAI,EAAE,CAAC;AACf,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const supportedVllmReasoningParsers: readonly ["kimi_k2", "qwen3"];
|
|
2
|
+
export type VllmReasoningParser = (typeof supportedVllmReasoningParsers)[number];
|
|
3
|
+
type VisionContentPart = {
|
|
4
|
+
type: "image_url";
|
|
5
|
+
image_url: {
|
|
6
|
+
url: string;
|
|
7
|
+
};
|
|
8
|
+
} | {
|
|
9
|
+
type: "text";
|
|
10
|
+
text: string;
|
|
11
|
+
};
|
|
12
|
+
export interface OpenAICompatibleChatCompletionRequest {
|
|
13
|
+
model: string;
|
|
14
|
+
messages: Array<{
|
|
15
|
+
role: "user";
|
|
16
|
+
content: VisionContentPart[];
|
|
17
|
+
}>;
|
|
18
|
+
chat_template_kwargs?: Record<string, boolean>;
|
|
19
|
+
}
|
|
20
|
+
interface BuildOpenAICompatibleRequestOptions {
|
|
21
|
+
vllmReasoningParser?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function buildOpenAICompatibleRequest(imageBuffer: Buffer, modelName: string, options?: BuildOpenAICompatibleRequestOptions): OpenAICompatibleChatCompletionRequest;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=openaiCompatibleRequest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openaiCompatibleRequest.d.ts","sourceRoot":"","sources":["../../src/openaiCompatibleRequest.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,6BAA6B,+BAAgC,CAAC;AAE3E,MAAM,MAAM,mBAAmB,GAC7B,CAAC,OAAO,6BAA6B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD,KAAK,iBAAiB,GAClB;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;CACH,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEN,MAAM,WAAW,qCAAqC;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,iBAAiB,EAAE,CAAC;KAC9B,CAAC,CAAC;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChD;AAED,UAAU,mCAAmC;IAC3C,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAqCD,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,mCAAwC,GAChD,qCAAqC,CAmCvC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const supportedVllmReasoningParsers = ["kimi_k2", "qwen3"];
|
|
2
|
+
function parseVllmReasoningParser(vllmReasoningParser) {
|
|
3
|
+
if (!vllmReasoningParser) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
const normalizedParser = vllmReasoningParser
|
|
7
|
+
.trim()
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.replace(/[\s-]+/g, "_");
|
|
10
|
+
if (normalizedParser === "kimi_k2" || normalizedParser === "qwen3") {
|
|
11
|
+
return normalizedParser;
|
|
12
|
+
}
|
|
13
|
+
throw new Error(`Unsupported VLLM_REASONING_PARSER \"${vllmReasoningParser}\". Supported values: ${supportedVllmReasoningParsers.join(", ")}.`);
|
|
14
|
+
}
|
|
15
|
+
function getThinkingDisabledChatTemplateKwargs(vllmReasoningParser) {
|
|
16
|
+
if (!vllmReasoningParser) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
if (vllmReasoningParser === "kimi_k2") {
|
|
20
|
+
return { thinking: false };
|
|
21
|
+
}
|
|
22
|
+
return { enable_thinking: false };
|
|
23
|
+
}
|
|
24
|
+
export function buildOpenAICompatibleRequest(imageBuffer, modelName, options = {}) {
|
|
25
|
+
const base64Image = imageBuffer.toString("base64");
|
|
26
|
+
const vllmReasoningParser = parseVllmReasoningParser(options.vllmReasoningParser);
|
|
27
|
+
const requestBody = {
|
|
28
|
+
model: modelName,
|
|
29
|
+
messages: [
|
|
30
|
+
{
|
|
31
|
+
role: "user",
|
|
32
|
+
content: [
|
|
33
|
+
{
|
|
34
|
+
type: "image_url",
|
|
35
|
+
image_url: {
|
|
36
|
+
url: `data:image/png;base64,${base64Image}`,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
type: "text",
|
|
41
|
+
text: "Please convert this image to markdown format. Extract all text, tables, and structure accurately.",
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
const chatTemplateKwargs = getThinkingDisabledChatTemplateKwargs(vllmReasoningParser);
|
|
48
|
+
if (chatTemplateKwargs) {
|
|
49
|
+
requestBody.chat_template_kwargs = chatTemplateKwargs;
|
|
50
|
+
}
|
|
51
|
+
return requestBody;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=openaiCompatibleRequest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openaiCompatibleRequest.js","sourceRoot":"","sources":["../../src/openaiCompatibleRequest.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,SAAS,EAAE,OAAO,CAAU,CAAC;AA8B3E,SAAS,wBAAwB,CAC/B,mBAA4B;IAE5B,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACzB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,gBAAgB,GAAG,mBAAmB;SACzC,IAAI,EAAE;SACN,WAAW,EAAE;SACb,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAE3B,IAAI,gBAAgB,KAAK,SAAS,IAAI,gBAAgB,KAAK,OAAO,EAAE,CAAC;QACnE,OAAO,gBAAgB,CAAC;IAC1B,CAAC;IAED,MAAM,IAAI,KAAK,CACb,uCAAuC,mBAAmB,yBAAyB,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC/H,CAAC;AACJ,CAAC;AAED,SAAS,qCAAqC,CAC5C,mBAAyC;IAEzC,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACzB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,mBAAmB,KAAK,SAAS,EAAE,CAAC;QACtC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC7B,CAAC;IAED,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,4BAA4B,CAC1C,WAAmB,EACnB,SAAiB,EACjB,UAA+C,EAAE;IAEjD,MAAM,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,mBAAmB,GAAG,wBAAwB,CAClD,OAAO,CAAC,mBAAmB,CAC5B,CAAC;IACF,MAAM,WAAW,GAA0C;QACzD,KAAK,EAAE,SAAS;QAChB,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,WAAW;wBACjB,SAAS,EAAE;4BACT,GAAG,EAAE,yBAAyB,WAAW,EAAE;yBAC5C;qBACF;oBACD;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,mGAAmG;qBAC1G;iBACF;aACF;SACF;KACF,CAAC;IAEF,MAAM,kBAAkB,GAAG,qCAAqC,CAC9D,mBAAmB,CACpB,CAAC;IAEF,IAAI,kBAAkB,EAAE,CAAC;QACvB,WAAW,CAAC,oBAAoB,GAAG,kBAAkB,CAAC;IACxD,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC"}
|
package/dist/test/demo.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import "dotenv/config";
|
|
2
2
|
//# sourceMappingURL=demo.d.ts.map
|
package/dist/test/demo.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../../test/demo.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../../test/demo.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC"}
|
package/dist/test/demo.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
1
2
|
import { convertPdfPageToImage } from "../src/pdfConverter.js";
|
|
2
3
|
import * as fs from "node:fs/promises";
|
|
3
4
|
import * as path from "node:path";
|
|
@@ -55,9 +56,7 @@ async function main() {
|
|
|
55
56
|
console.error("\nPlease set environment variables:");
|
|
56
57
|
console.error(" QWEN_API_URL - Qwen API endpoint URL");
|
|
57
58
|
console.error(" QWEN_API_KEY - Your Qwen API key\n");
|
|
58
|
-
console.error("
|
|
59
|
-
console.error(' $env:QWEN_API_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"');
|
|
60
|
-
console.error(' $env:QWEN_API_KEY = "your-api-key-here"\n');
|
|
59
|
+
console.error("The project automatically loads a local .env file when present, so you can store the values there instead of exporting them every time.\n");
|
|
61
60
|
process.exit(1);
|
|
62
61
|
}
|
|
63
62
|
try {
|
package/dist/test/demo.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"demo.js","sourceRoot":"","sources":["../../test/demo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAgBlC,KAAK,UAAU,sBAAsB,CACnC,WAAmB,EACnB,MAAc,EACd,MAAc;IAEd,MAAM,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAEnD,MAAM,WAAW,GAAG;QAClB,KAAK,EAAE,aAAa;QACpB,KAAK,EAAE;YACL,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE;wBACP;4BACE,KAAK,EAAE,yBAAyB,WAAW,EAAE;yBAC9C;wBACD;4BACE,IAAI,EAAE,mGAAmG;yBAC1G;qBACF;iBACF;aACF;SACF;KACF,CAAC;IAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE;QACnC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,aAAa,EAAE,UAAU,MAAM,EAAE;SAClC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;KAClC,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CACb,4BAA4B,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,MAAM,SAAS,EAAE,CACpF,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAmB,CAAC;IAEzD,IAAI,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;QACxB,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;IAC5B,CAAC;IAED,MAAM,IAAI,KAAK,CACb,mCAAmC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAC5D,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,+BAA+B;IAC/B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IACxE,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;IAEhD,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,aAAa,OAAO,EAAE,CAAC,CAAC;IACpC,OAAO,CAAC,GAAG,CAAC,gBAAgB,UAAU,IAAI,CAAC,CAAC;IAE5C,wBAAwB;IACxB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IACxC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAExC,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAClD,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACrD,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;QACxD,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;QACtD,OAAO,CAAC,KAAK,
|
|
1
|
+
{"version":3,"file":"demo.js","sourceRoot":"","sources":["../../test/demo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openaiCompatibleRequest.test.d.ts","sourceRoot":"","sources":["../../test/openaiCompatibleRequest.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { buildOpenAICompatibleRequest, supportedVllmReasoningParsers, } from "../src/openaiCompatibleRequest.js";
|
|
3
|
+
function run() {
|
|
4
|
+
const sampleImage = Buffer.from("sample-image");
|
|
5
|
+
const genericRequest = buildOpenAICompatibleRequest(sampleImage, "moonshotai/Kimi-K2.6-Vision-Instruct");
|
|
6
|
+
assert.equal(genericRequest.chat_template_kwargs, undefined);
|
|
7
|
+
const kimiRequest = buildOpenAICompatibleRequest(sampleImage, "moonshotai/Kimi-K2.6-Vision-Instruct", { vllmReasoningParser: "kimi_k2" });
|
|
8
|
+
assert.deepEqual(kimiRequest.chat_template_kwargs, { thinking: false });
|
|
9
|
+
const qwenRequest = buildOpenAICompatibleRequest(sampleImage, "Qwen/Qwen3-VL-235B-A22B-Instruct", { vllmReasoningParser: "qwen3" });
|
|
10
|
+
assert.deepEqual(qwenRequest.chat_template_kwargs, {
|
|
11
|
+
enable_thinking: false,
|
|
12
|
+
});
|
|
13
|
+
assert.throws(() => buildOpenAICompatibleRequest(sampleImage, "moonshotai/Kimi-K2.6", {
|
|
14
|
+
vllmReasoningParser: "unknown-parser",
|
|
15
|
+
}), {
|
|
16
|
+
message: new RegExp(`Supported values: ${supportedVllmReasoningParsers.join(", ")}`),
|
|
17
|
+
});
|
|
18
|
+
console.log("✓ OpenAI-compatible request builder tests passed");
|
|
19
|
+
}
|
|
20
|
+
run();
|
|
21
|
+
//# sourceMappingURL=openaiCompatibleRequest.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openaiCompatibleRequest.test.js","sourceRoot":"","sources":["../../test/openaiCompatibleRequest.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EACL,4BAA4B,EAC5B,6BAA6B,GAC9B,MAAM,mCAAmC,CAAC;AAE3C,SAAS,GAAG;IACV,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAEhD,MAAM,cAAc,GAAG,4BAA4B,CACjD,WAAW,EACX,sCAAsC,CACvC,CAAC;IACF,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,oBAAoB,EAAE,SAAS,CAAC,CAAC;IAE7D,MAAM,WAAW,GAAG,4BAA4B,CAC9C,WAAW,EACX,sCAAsC,EACtC,EAAE,mBAAmB,EAAE,SAAS,EAAE,CACnC,CAAC;IACF,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,oBAAoB,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IAExE,MAAM,WAAW,GAAG,4BAA4B,CAC9C,WAAW,EACX,kCAAkC,EAClC,EAAE,mBAAmB,EAAE,OAAO,EAAE,CACjC,CAAC;IACF,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,oBAAoB,EAAE;QACjD,eAAe,EAAE,KAAK;KACvB,CAAC,CAAC;IAEH,MAAM,CAAC,MAAM,CACX,GAAG,EAAE,CACH,4BAA4B,CAAC,WAAW,EAAE,sBAAsB,EAAE;QAChE,mBAAmB,EAAE,gBAAgB;KACtC,CAAC,EACJ;QACE,OAAO,EAAE,IAAI,MAAM,CACjB,qBAAqB,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChE;KACF,CACF,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC;AAClE,CAAC;AAED,GAAG,EAAE,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import "dotenv/config";
|
|
2
2
|
//# sourceMappingURL=pdfToMarkdown.test.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pdfToMarkdown.test.d.ts","sourceRoot":"","sources":["../../test/pdfToMarkdown.test.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"pdfToMarkdown.test.d.ts","sourceRoot":"","sources":["../../test/pdfToMarkdown.test.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC"}
|
|
@@ -1,31 +1,15 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
1
2
|
import { convertPdfPageToImage } from "../src/pdfConverter.js";
|
|
3
|
+
import { buildOpenAICompatibleRequest } from "../src/openaiCompatibleRequest.js";
|
|
2
4
|
import * as fs from "node:fs/promises";
|
|
3
5
|
import * as path from "node:path";
|
|
4
6
|
/**
|
|
5
7
|
* Convert image to markdown using Qwen VL API
|
|
6
8
|
*/
|
|
7
|
-
async function convertImageToMarkdown(imageBuffer, apiUrl, apiKey) {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
messages: [
|
|
12
|
-
{
|
|
13
|
-
role: "user",
|
|
14
|
-
content: [
|
|
15
|
-
{
|
|
16
|
-
type: "image_url",
|
|
17
|
-
image_url: {
|
|
18
|
-
url: `data:image/png;base64,${base64Image}`,
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
type: "text",
|
|
23
|
-
text: "Please convert this image to markdown format. Extract all text, tables, and structure accurately.",
|
|
24
|
-
},
|
|
25
|
-
],
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
};
|
|
9
|
+
async function convertImageToMarkdown(imageBuffer, apiUrl, apiKey, modelName, vllmReasoningParser) {
|
|
10
|
+
const requestBody = buildOpenAICompatibleRequest(imageBuffer, modelName, {
|
|
11
|
+
vllmReasoningParser,
|
|
12
|
+
});
|
|
29
13
|
const response = await fetch(apiUrl, {
|
|
30
14
|
method: "POST",
|
|
31
15
|
headers: {
|
|
@@ -56,6 +40,8 @@ async function testPdfToMarkdown() {
|
|
|
56
40
|
const testPdfPath = path.join(process.cwd(), "test", "test.pdf");
|
|
57
41
|
const apiUrl = process.env.QWEN_API_URL;
|
|
58
42
|
const apiKey = process.env.QWEN_API_KEY;
|
|
43
|
+
const modelName = process.env.QWEN_MODEL || "Qwen3-VL-235B-A22B-Instruct";
|
|
44
|
+
const vllmReasoningParser = process.env.VLLM_REASONING_PARSER;
|
|
59
45
|
// Test 1: PDF to Image conversion
|
|
60
46
|
console.log("Test 1: Convert PDF page to image");
|
|
61
47
|
let imageBuffer;
|
|
@@ -87,22 +73,16 @@ async function testPdfToMarkdown() {
|
|
|
87
73
|
// Test 2: Check API credentials
|
|
88
74
|
console.log("\nTest 2: Verify API credentials");
|
|
89
75
|
if (!apiUrl || !apiKey) {
|
|
90
|
-
console.log("⚠ Test 2 skipped: QWEN_API_URL and QWEN_API_KEY
|
|
91
|
-
console.log("\
|
|
92
|
-
console.log(" Windows (PowerShell):");
|
|
93
|
-
console.log(' $env:QWEN_API_URL = "http://osl4420:13000/v1/chat/completions"');
|
|
94
|
-
console.log(' $env:QWEN_API_KEY = "sk-*****"');
|
|
95
|
-
console.log("\n Linux/Mac:");
|
|
96
|
-
console.log(' export QWEN_API_URL="https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"');
|
|
97
|
-
console.log(' export QWEN_API_KEY="your-api-key-here"');
|
|
76
|
+
console.log("⚠ Test 2 skipped: QWEN_API_URL and QWEN_API_KEY were not found in process.env or the project .env file");
|
|
77
|
+
console.log("\nAdd the credentials to the project .env file or export them in your shell, then rerun npm run test:full.");
|
|
98
78
|
return;
|
|
99
79
|
}
|
|
100
80
|
console.log("✓ Test 2 passed: API credentials found");
|
|
101
81
|
// Test 3: Full conversion - Image to Markdown
|
|
102
82
|
console.log("\nTest 3: Convert image to markdown using Qwen VL API");
|
|
103
83
|
try {
|
|
104
|
-
console.log(" Sending request to
|
|
105
|
-
const markdown = await convertImageToMarkdown(imageBuffer, apiUrl, apiKey);
|
|
84
|
+
console.log(" Sending request to the OpenAI-compatible vision API...");
|
|
85
|
+
const markdown = await convertImageToMarkdown(imageBuffer, apiUrl, apiKey, modelName, vllmReasoningParser);
|
|
106
86
|
if (!markdown || typeof markdown !== "string") {
|
|
107
87
|
throw new Error("Expected markdown string but got: " + typeof markdown);
|
|
108
88
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pdfToMarkdown.test.js","sourceRoot":"","sources":["../../test/pdfToMarkdown.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAclC;;GAEG;AACH,KAAK,UAAU,sBAAsB,CACnC,WAAmB,EACnB,MAAc,EACd,MAAc
|
|
1
|
+
{"version":3,"file":"pdfToMarkdown.test.js","sourceRoot":"","sources":["../../test/pdfToMarkdown.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pdf-to-markdown-mcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "MCP server that converts PDF pages to markdown using Qwen VL model",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"build": "tsc",
|
|
13
13
|
"start": "node dist/src/index.js",
|
|
14
14
|
"dev": "tsc --watch",
|
|
15
|
-
"test": "tsc && node dist/test/convertPdfPageToImage.test.js",
|
|
15
|
+
"test": "tsc && node dist/test/convertPdfPageToImage.test.js && node dist/test/openaiCompatibleRequest.test.js",
|
|
16
16
|
"test:full": "tsc && node dist/test/pdfToMarkdown.test.js",
|
|
17
17
|
"test:all": "npm test && npm run test:full",
|
|
18
18
|
"demo": "tsc && node dist/test/demo.js"
|
|
@@ -26,12 +26,19 @@
|
|
|
26
26
|
"author": "",
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
30
30
|
"canvas": "3.2.0",
|
|
31
|
-
"
|
|
31
|
+
"dotenv": "^16.6.1",
|
|
32
|
+
"pdfjs-dist": "4.8.69",
|
|
33
|
+
"zod": "^3.25.76"
|
|
32
34
|
},
|
|
33
35
|
"overrides": {
|
|
34
|
-
"hono":"4.
|
|
36
|
+
"hono":"4.12.18",
|
|
37
|
+
"ajv":"8.18.0",
|
|
38
|
+
"fast-uri":"3.1.2",
|
|
39
|
+
"ip-address":"10.2.0",
|
|
40
|
+
"@hono/node-server":"1.19.14",
|
|
41
|
+
"path-to-regexp":"8.4.2"
|
|
35
42
|
},
|
|
36
43
|
"devDependencies": {
|
|
37
44
|
"@types/node": "^22.10.2",
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import "dotenv/config";
|
|
3
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
6
|
import * as fs from "node:fs/promises";
|
|
7
|
+
import * as path from "node:path";
|
|
6
8
|
import { z } from "zod";
|
|
9
|
+
import { buildOpenAICompatibleRequest } from "./openaiCompatibleRequest.js";
|
|
7
10
|
import { convertPdfPageToImage } from "./pdfConverter.js";
|
|
8
11
|
|
|
9
12
|
interface QwenVLResponse {
|
|
@@ -25,30 +28,12 @@ async function convertImageToMarkdown(
|
|
|
25
28
|
imageBuffer: Buffer,
|
|
26
29
|
apiUrl: string,
|
|
27
30
|
apiKey: string,
|
|
28
|
-
modelName: string
|
|
31
|
+
modelName: string,
|
|
32
|
+
vllmReasoningParser?: string
|
|
29
33
|
): Promise<string> {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
model: modelName,
|
|
34
|
-
messages: [
|
|
35
|
-
{
|
|
36
|
-
role: "user",
|
|
37
|
-
content: [
|
|
38
|
-
{
|
|
39
|
-
type: "image_url",
|
|
40
|
-
image_url: {
|
|
41
|
-
url: `data:image/png;base64,${base64Image}`,
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
type: "text",
|
|
46
|
-
text: "Please convert this image to markdown format. Extract all text, tables, and structure accurately.",
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
|
-
};
|
|
34
|
+
const requestBody = buildOpenAICompatibleRequest(imageBuffer, modelName, {
|
|
35
|
+
vllmReasoningParser,
|
|
36
|
+
});
|
|
52
37
|
|
|
53
38
|
const response = await fetch(apiUrl, {
|
|
54
39
|
method: "POST",
|
|
@@ -89,6 +74,7 @@ async function main() {
|
|
|
89
74
|
const apiUrl = process.env.QWEN_API_URL;
|
|
90
75
|
const apiKey = process.env.QWEN_API_KEY;
|
|
91
76
|
const modelName = process.env.QWEN_MODEL || "Qwen3-VL-235B-A22B-Instruct";
|
|
77
|
+
const vllmReasoningParser = process.env.VLLM_REASONING_PARSER;
|
|
92
78
|
|
|
93
79
|
if (!apiUrl || !apiKey) {
|
|
94
80
|
throw new Error(
|
|
@@ -96,7 +82,7 @@ async function main() {
|
|
|
96
82
|
);
|
|
97
83
|
}
|
|
98
84
|
|
|
99
|
-
const se = "1.0.
|
|
85
|
+
const se = "1.0.9";
|
|
100
86
|
const server = new McpServer({
|
|
101
87
|
name: "pdf-to-markdown-mcp",
|
|
102
88
|
version: se,
|
|
@@ -133,6 +119,23 @@ async function main() {
|
|
|
133
119
|
throw new Error("page_number must be a positive integer");
|
|
134
120
|
}
|
|
135
121
|
|
|
122
|
+
// Validate pdf_path is within an allowed workspace directory
|
|
123
|
+
const workspaceEnv = process.env.WORKSPACE;
|
|
124
|
+
if (workspaceEnv) {
|
|
125
|
+
const allowedDirs = workspaceEnv
|
|
126
|
+
.split(",")
|
|
127
|
+
.map((d) => path.resolve(d.trim()));
|
|
128
|
+
const resolvedPdfPath = path.resolve(pdf_path);
|
|
129
|
+
const isAllowed = allowedDirs.some((dir) =>
|
|
130
|
+
resolvedPdfPath.startsWith(dir + path.sep) || resolvedPdfPath === dir
|
|
131
|
+
);
|
|
132
|
+
if (!isAllowed) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Access denied: pdf_path must be located within one of the allowed workspace directories`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
136
139
|
// Check if file exists
|
|
137
140
|
try {
|
|
138
141
|
await fs.access(pdf_path);
|
|
@@ -148,7 +151,8 @@ async function main() {
|
|
|
148
151
|
imageBuffer,
|
|
149
152
|
apiUrl,
|
|
150
153
|
apiKey,
|
|
151
|
-
modelName
|
|
154
|
+
modelName,
|
|
155
|
+
vllmReasoningParser
|
|
152
156
|
);
|
|
153
157
|
|
|
154
158
|
return {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export const supportedVllmReasoningParsers = ["kimi_k2", "qwen3"] as const;
|
|
2
|
+
|
|
3
|
+
export type VllmReasoningParser =
|
|
4
|
+
(typeof supportedVllmReasoningParsers)[number];
|
|
5
|
+
|
|
6
|
+
type VisionContentPart =
|
|
7
|
+
| {
|
|
8
|
+
type: "image_url";
|
|
9
|
+
image_url: {
|
|
10
|
+
url: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
| {
|
|
14
|
+
type: "text";
|
|
15
|
+
text: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export interface OpenAICompatibleChatCompletionRequest {
|
|
19
|
+
model: string;
|
|
20
|
+
messages: Array<{
|
|
21
|
+
role: "user";
|
|
22
|
+
content: VisionContentPart[];
|
|
23
|
+
}>;
|
|
24
|
+
chat_template_kwargs?: Record<string, boolean>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface BuildOpenAICompatibleRequestOptions {
|
|
28
|
+
vllmReasoningParser?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseVllmReasoningParser(
|
|
32
|
+
vllmReasoningParser?: string
|
|
33
|
+
): VllmReasoningParser | undefined {
|
|
34
|
+
if (!vllmReasoningParser) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const normalizedParser = vllmReasoningParser
|
|
39
|
+
.trim()
|
|
40
|
+
.toLowerCase()
|
|
41
|
+
.replace(/[\s-]+/g, "_");
|
|
42
|
+
|
|
43
|
+
if (normalizedParser === "kimi_k2" || normalizedParser === "qwen3") {
|
|
44
|
+
return normalizedParser;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Unsupported VLLM_REASONING_PARSER \"${vllmReasoningParser}\". Supported values: ${supportedVllmReasoningParsers.join(", ")}.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getThinkingDisabledChatTemplateKwargs(
|
|
53
|
+
vllmReasoningParser?: VllmReasoningParser
|
|
54
|
+
): Record<string, boolean> | undefined {
|
|
55
|
+
if (!vllmReasoningParser) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (vllmReasoningParser === "kimi_k2") {
|
|
60
|
+
return { thinking: false };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { enable_thinking: false };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function buildOpenAICompatibleRequest(
|
|
67
|
+
imageBuffer: Buffer,
|
|
68
|
+
modelName: string,
|
|
69
|
+
options: BuildOpenAICompatibleRequestOptions = {}
|
|
70
|
+
): OpenAICompatibleChatCompletionRequest {
|
|
71
|
+
const base64Image = imageBuffer.toString("base64");
|
|
72
|
+
const vllmReasoningParser = parseVllmReasoningParser(
|
|
73
|
+
options.vllmReasoningParser
|
|
74
|
+
);
|
|
75
|
+
const requestBody: OpenAICompatibleChatCompletionRequest = {
|
|
76
|
+
model: modelName,
|
|
77
|
+
messages: [
|
|
78
|
+
{
|
|
79
|
+
role: "user",
|
|
80
|
+
content: [
|
|
81
|
+
{
|
|
82
|
+
type: "image_url",
|
|
83
|
+
image_url: {
|
|
84
|
+
url: `data:image/png;base64,${base64Image}`,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: "text",
|
|
89
|
+
text: "Please convert this image to markdown format. Extract all text, tables, and structure accurately.",
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const chatTemplateKwargs = getThinkingDisabledChatTemplateKwargs(
|
|
97
|
+
vllmReasoningParser
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (chatTemplateKwargs) {
|
|
101
|
+
requestBody.chat_template_kwargs = chatTemplateKwargs;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return requestBody;
|
|
105
|
+
}
|
package/test/demo.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
1
2
|
import { convertPdfPageToImage } from "../src/pdfConverter.js";
|
|
2
3
|
import * as fs from "node:fs/promises";
|
|
3
4
|
import * as path from "node:path";
|
|
@@ -88,9 +89,9 @@ async function main() {
|
|
|
88
89
|
console.error("\nPlease set environment variables:");
|
|
89
90
|
console.error(" QWEN_API_URL - Qwen API endpoint URL");
|
|
90
91
|
console.error(" QWEN_API_KEY - Your Qwen API key\n");
|
|
91
|
-
console.error(
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
console.error(
|
|
93
|
+
"The project automatically loads a local .env file when present, so you can store the values there instead of exporting them every time.\n"
|
|
94
|
+
);
|
|
94
95
|
process.exit(1);
|
|
95
96
|
}
|
|
96
97
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import {
|
|
3
|
+
buildOpenAICompatibleRequest,
|
|
4
|
+
supportedVllmReasoningParsers,
|
|
5
|
+
} from "../src/openaiCompatibleRequest.js";
|
|
6
|
+
|
|
7
|
+
function run() {
|
|
8
|
+
const sampleImage = Buffer.from("sample-image");
|
|
9
|
+
|
|
10
|
+
const genericRequest = buildOpenAICompatibleRequest(
|
|
11
|
+
sampleImage,
|
|
12
|
+
"moonshotai/Kimi-K2.6-Vision-Instruct"
|
|
13
|
+
);
|
|
14
|
+
assert.equal(genericRequest.chat_template_kwargs, undefined);
|
|
15
|
+
|
|
16
|
+
const kimiRequest = buildOpenAICompatibleRequest(
|
|
17
|
+
sampleImage,
|
|
18
|
+
"moonshotai/Kimi-K2.6-Vision-Instruct",
|
|
19
|
+
{ vllmReasoningParser: "kimi_k2" }
|
|
20
|
+
);
|
|
21
|
+
assert.deepEqual(kimiRequest.chat_template_kwargs, { thinking: false });
|
|
22
|
+
|
|
23
|
+
const qwenRequest = buildOpenAICompatibleRequest(
|
|
24
|
+
sampleImage,
|
|
25
|
+
"Qwen/Qwen3-VL-235B-A22B-Instruct",
|
|
26
|
+
{ vllmReasoningParser: "qwen3" }
|
|
27
|
+
);
|
|
28
|
+
assert.deepEqual(qwenRequest.chat_template_kwargs, {
|
|
29
|
+
enable_thinking: false,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
assert.throws(
|
|
33
|
+
() =>
|
|
34
|
+
buildOpenAICompatibleRequest(sampleImage, "moonshotai/Kimi-K2.6", {
|
|
35
|
+
vllmReasoningParser: "unknown-parser",
|
|
36
|
+
}),
|
|
37
|
+
{
|
|
38
|
+
message: new RegExp(
|
|
39
|
+
`Supported values: ${supportedVllmReasoningParsers.join(", ")}`
|
|
40
|
+
),
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
console.log("✓ OpenAI-compatible request builder tests passed");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
run();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
1
2
|
import { convertPdfPageToImage } from "../src/pdfConverter.js";
|
|
3
|
+
import { buildOpenAICompatibleRequest } from "../src/openaiCompatibleRequest.js";
|
|
2
4
|
import * as fs from "node:fs/promises";
|
|
3
5
|
import * as path from "node:path";
|
|
4
6
|
|
|
@@ -20,30 +22,13 @@ interface QwenVLResponse {
|
|
|
20
22
|
async function convertImageToMarkdown(
|
|
21
23
|
imageBuffer: Buffer,
|
|
22
24
|
apiUrl: string,
|
|
23
|
-
apiKey: string
|
|
25
|
+
apiKey: string,
|
|
26
|
+
modelName: string,
|
|
27
|
+
vllmReasoningParser?: string
|
|
24
28
|
): Promise<string> {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
model: "Qwen3-VL-235B-A22B-Instruct",
|
|
29
|
-
messages: [
|
|
30
|
-
{
|
|
31
|
-
role: "user",
|
|
32
|
-
content: [
|
|
33
|
-
{
|
|
34
|
-
type: "image_url",
|
|
35
|
-
image_url: {
|
|
36
|
-
url: `data:image/png;base64,${base64Image}`,
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
type: "text",
|
|
41
|
-
text: "Please convert this image to markdown format. Extract all text, tables, and structure accurately.",
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
},
|
|
45
|
-
],
|
|
46
|
-
};
|
|
29
|
+
const requestBody = buildOpenAICompatibleRequest(imageBuffer, modelName, {
|
|
30
|
+
vllmReasoningParser,
|
|
31
|
+
});
|
|
47
32
|
|
|
48
33
|
const response = await fetch(apiUrl, {
|
|
49
34
|
method: "POST",
|
|
@@ -86,6 +71,8 @@ async function testPdfToMarkdown() {
|
|
|
86
71
|
const testPdfPath = path.join(process.cwd(), "test", "test.pdf");
|
|
87
72
|
const apiUrl = process.env.QWEN_API_URL;
|
|
88
73
|
const apiKey = process.env.QWEN_API_KEY;
|
|
74
|
+
const modelName = process.env.QWEN_MODEL || "Qwen3-VL-235B-A22B-Instruct";
|
|
75
|
+
const vllmReasoningParser = process.env.VLLM_REASONING_PARSER;
|
|
89
76
|
|
|
90
77
|
// Test 1: PDF to Image conversion
|
|
91
78
|
console.log("Test 1: Convert PDF page to image");
|
|
@@ -124,14 +111,12 @@ async function testPdfToMarkdown() {
|
|
|
124
111
|
// Test 2: Check API credentials
|
|
125
112
|
console.log("\nTest 2: Verify API credentials");
|
|
126
113
|
if (!apiUrl || !apiKey) {
|
|
127
|
-
console.log(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
console.log(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
console.log(' export QWEN_API_URL="https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"');
|
|
134
|
-
console.log(' export QWEN_API_KEY="your-api-key-here"');
|
|
114
|
+
console.log(
|
|
115
|
+
"⚠ Test 2 skipped: QWEN_API_URL and QWEN_API_KEY were not found in process.env or the project .env file"
|
|
116
|
+
);
|
|
117
|
+
console.log(
|
|
118
|
+
"\nAdd the credentials to the project .env file or export them in your shell, then rerun npm run test:full."
|
|
119
|
+
);
|
|
135
120
|
return;
|
|
136
121
|
}
|
|
137
122
|
|
|
@@ -140,8 +125,14 @@ async function testPdfToMarkdown() {
|
|
|
140
125
|
// Test 3: Full conversion - Image to Markdown
|
|
141
126
|
console.log("\nTest 3: Convert image to markdown using Qwen VL API");
|
|
142
127
|
try {
|
|
143
|
-
console.log(" Sending request to
|
|
144
|
-
const markdown = await convertImageToMarkdown(
|
|
128
|
+
console.log(" Sending request to the OpenAI-compatible vision API...");
|
|
129
|
+
const markdown = await convertImageToMarkdown(
|
|
130
|
+
imageBuffer,
|
|
131
|
+
apiUrl,
|
|
132
|
+
apiKey,
|
|
133
|
+
modelName,
|
|
134
|
+
vllmReasoningParser
|
|
135
|
+
);
|
|
145
136
|
|
|
146
137
|
if (!markdown || typeof markdown !== "string") {
|
|
147
138
|
throw new Error("Expected markdown string but got: " + typeof markdown);
|