markdown-new-mcp 1.0.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.
@@ -0,0 +1,129 @@
1
+
2
+ ## Session 11:48
3
+
4
+
5
+ ## Session 11:51
6
+
7
+
8
+ ## Session 11:59
9
+
10
+ ### 11:59
11
+ <!-- session:86a058f1-3f79-4cae-ba47-1cd9f184fff8 turn:8da4a43a-27c1-443e-8819-1890118e2af5 transcript:/Volumes/JJZ/jerryjiang/.claude/projects/-Volumes-JJZ-jerryjiang-unicom-dev-markdown-new-mcp/86a058f1-3f79-4cae-ba47-1cd9f184fff8.jsonl -->
12
+ - 用户要求将 markdown.new 的文件转 Markdown API 封装成 MCP 服务器
13
+ - 项目目录已创建在 /Volumes/JJZ/jerryjiang/unicom/dev/markdown-new-mcp/
14
+ - 分析了 markdown.new 的 API:POST `/` 端点用于直接上传转换,POST `/convert` 端点用于 URL 转换,支持 PDF、DOCX、XLSX、图片等格式,最大 10MB
15
+ - API 认证方式:Authorization: Bearer mk_... header 或 ?api_key=mk_... query 参数
16
+ - 探索了现有 MCP 项目结构和 TypeScript MCP SDK 的标准结构
17
+ - 创建了实现计划文件在 /Volumes/JJZ/jerryjiang/.claude/plans/iterative-strolling-ritchie.md
18
+ - 用户要求完成后使用 GITHUB_TOKEN 创建新的 GitHub repo
19
+
20
+
21
+ ## Session 12:00
22
+
23
+
24
+ ## Session 12:04
25
+
26
+ ### 12:04
27
+ <!-- session:e002accd-e739-4d6b-b854-87ea20879102 turn:769ba1cd-fe58-48df-a768-6f1411077b43 transcript:/Volumes/JJZ/jerryjiang/.claude/projects/-Volumes-JJZ-jerryjiang-unicom-dev-markdown-new-mcp/e002accd-e739-4d6b-b854-87ea20879102.jsonl -->
28
+ - 创建了 MCP 服务器项目,目录位于 /Volumes/JJZ/jerryjiang/unicom/dev/markdown-new-mcp/
29
+ - 实现了三个 MCP 工具:`convert_url_to_markdown`(URL转Markdown)、`convert_file_to_markdown`(文件上传转换)、`convert_url_to_json`(带元数据的URL转换)
30
+ - 修复了 TypeScript 编译错误(unknown 类型问题),添加了 `extractMarkdown` 辅助函数处理 API 响应
31
+ - 使用 GitHub API 创建了公开仓库 https://github.com/takltc/markdown-new-mcp
32
+ - 初始提交包含 6 个文件:package.json、tsconfig.json、.gitignore、README.md、src/index.ts、package-lock.json
33
+ - 代码已成功推送到 GitHub main 分支
34
+
35
+
36
+ ## Session 12:47
37
+
38
+ ### 12:47
39
+ <!-- session:e002accd-e739-4d6b-b854-87ea20879102 turn:d13fe578-465c-4010-8084-9f15f4845eb1 transcript:/Volumes/JJZ/jerryjiang/.claude/projects/-Volumes-JJZ-jerryjiang-unicom-dev-markdown-new-mcp/e002accd-e739-4d6b-b854-87ea20879102.jsonl -->
40
+ - 将 markdown-new-mcp MCP 服务器部署到 ms 服务器 (64.69.41.84)
41
+ - 修改了 src/index.ts 从 stdio 传输改为 SSE HTTP 传输,支持网络访问
42
+ - 在服务器 /opt/markdown-new-mcp 目录部署项目,创建了 systemd 服务
43
+ - 服务监听端口 38721,健康检查和 SSE 端点已验证可从本地访问
44
+ - 在 ~/.ssh/config 添加了 SSH 隧道配置 (Host mcp-markdown) 用于本地端口转发
45
+ - 服务地址: http://64.69.41.84:38721,健康检查: /health,SSE: /sse
46
+
47
+
48
+ ## Session 12:50
49
+
50
+ ### 12:50
51
+ <!-- session:e002accd-e739-4d6b-b854-87ea20879102 turn:89a137f1-0cf3-4189-b070-98cbf66419dc transcript:/Volumes/JJZ/jerryjiang/.claude/projects/-Volumes-JJZ-jerryjiang-unicom-dev-markdown-new-mcp/e002accd-e739-4d6b-b854-87ea20879102.jsonl -->
52
+ - 创建了 MCP 包装脚本 `~/.local/bin/markdown-new-mcp`,通过 SSH 直接执行远程服务器的 stdio 模式 MCP
53
+ - 将 `src/index.ts` 修改为支持 SSE HTTP 传输模式,服务监听端口 38721
54
+ - 在 `~/.claude/settings.json` 添加了 `mcpServers` 配置,指向本地脚本
55
+ - 在 `~/.ssh/config` 添加了 `mcp-markdown` SSH 隧道配置(备用方案)
56
+ - 服务部署在 ms 服务器 `/opt/markdown-new-mcp` 目录,通过 systemd 管理
57
+ - 三个 MCP 工具可用:`convert_url_to_markdown`、`convert_file_to_markdown`、`convert_url_to_json`
58
+
59
+
60
+ ## Session 12:52
61
+
62
+ ### 12:52
63
+ <!-- session:e002accd-e739-4d6b-b854-87ea20879102 turn:9ae6efd3-4ded-4d98-bf6e-fa7cf23d9aee transcript:/Volumes/JJZ/jerryjiang/.claude/projects/-Volumes-JJZ-jerryjiang-unicom-dev-markdown-new-mcp/e002accd-e739-4d6b-b854-87ea20879102.jsonl -->
64
+ - 初始代码已提交推送到 GitHub main 分支
65
+ - SSE 支持的修改尚未提交,需要用户确认是否提交
66
+
67
+
68
+ ## Session 12:52
69
+
70
+ ### 12:52
71
+ <!-- session:e002accd-e739-4d6b-b854-87ea20879102 turn:f4043bb2-918c-4c1d-bdb7-991789f223ac transcript:/Volumes/JJZ/jerryjiang/.claude/projects/-Volumes-JJZ-jerryjiang-unicom-dev-markdown-new-mcp/e002accd-e739-4d6b-b854-87ea20879102.jsonl -->
72
+ - 将 markdown-new-mcp MCP 服务器部署到 ms 服务器 (64.69.41.84)
73
+ - 修改 `src/index.ts` 从 stdio 传输改为 SSE HTTP 传输,支持网络访问
74
+ - 在服务器 `/opt/markdown-new-mcp` 目录部署项目,创建了 systemd 服务
75
+ - 服务监听端口 38721,健康检查和 SSE 端点已验证可从本地访问
76
+ - 创建了 MCP 包装脚本 `~/.local/bin/markdown-new-mcp`,通过 SSH 直接执行远程 stdio 模式 MCP
77
+ - 在 `~/.claude/settings.json` 添加了 `mcpServers` 配置,指向本地脚本
78
+ - 在 `~/.ssh/config` 添加了 `mcp-markdown` SSH 隧道配置(备用方案)
79
+ - SSE 支持修改已提交推送: `b4056e2 Add SSE transport support for remote deployment`
80
+
81
+
82
+ ## Session 12:54
83
+
84
+
85
+ ## Session 12:54
86
+
87
+
88
+ ## Session 12:55
89
+
90
+ ### 12:55
91
+ <!-- session:e002accd-e739-4d6b-b854-87ea20879102 turn:4e8d94d7-a1d0-45a2-b802-e86972a329ce transcript:/Volumes/JJZ/jerryjiang/.claude/projects/-Volumes-JJZ-jerryjiang-unicom-dev-markdown-new-mcp/e002accd-e739-4d6b-b854-87ea20879102.jsonl -->
92
+ - 将 markdown-new-mcp MCP 服务器部署到 ms 服务器 (64.69.41.84),修改 `src/index.ts` 从 stdio 传输改为 SSE HTTP 传输
93
+ - 在服务器 `/opt/markdown-new-mcp` 目录部署项目,创建 systemd 服务监听端口 38721
94
+ - 创建本地包装脚本 `~/.local/bin/markdown-new-mcp`,通过 SSH 直接执行远程 stdio 模式 MCP
95
+ - 在 `~/.claude/settings.json` 添加 `mcpServers` 配置指向本地脚本
96
+ - SSE 支持修改已提交推送: `b4056e2 Add SSE transport support for remote deployment`
97
+ - README 中的 npx 使用方式不可用,因为包未发布到 npm,只推送到了 GitHub
98
+
99
+
100
+ ## Session 13:01
101
+
102
+
103
+ ## Session 13:01
104
+
105
+
106
+ ## Session 13:02
107
+
108
+ ### 13:02
109
+ <!-- session:e002accd-e739-4d6b-b854-87ea20879102 turn:10f9cd55-6edd-4c38-93d0-0452ed89d396 transcript:/Volumes/JJZ/jerryjiang/.claude/projects/-Volumes-JJZ-jerryjiang-unicom-dev-markdown-new-mcp/e002accd-e739-4d6b-b854-87ea20879102.jsonl -->
110
+ - 将 markdown-new-mcp MCP 服务器部署到 ms 服务器 (64.69.41.84),修改 `src/index.ts` 从 stdio 传输改为 SSE HTTP 传输
111
+ - 在服务器 `/opt/markdown-new-mcp` 目录部署项目,创建 systemd 服务监听端口 38721
112
+ - 创建本地包装脚本 `~/.local/bin/markdown-new-mcp`,通过 SSH 直接执行远程 stdio 模式 MCP
113
+ - 在 `~/.claude/settings.json` 添加 `mcpServers` 配置指向本地脚本
114
+ - SSE 支持修改已提交推送: `b4056e2 Add SSE transport support for remote deployment`
115
+ - README 中的 npx 使用方式不可用,因为包未发布到 npm,只推送到了 GitHub
116
+
117
+
118
+ ## Session 13:03
119
+
120
+ ### 13:03
121
+ <!-- session:e002accd-e739-4d6b-b854-87ea20879102 turn:2a5dc373-3eb6-410b-8c5a-4c6064772fb7 transcript:/Volumes/JJZ/jerryjiang/.claude/projects/-Volumes-JJZ-jerryjiang-unicom-dev-markdown-new-mcp/e002accd-e739-4d6b-b854-87ea20879102.jsonl -->
122
+ - 将 markdown-new-mcp MCP 服务器部署到 ms 服务器 (64.69.41.84),修改 `src/index.ts` 从 stdio 传输改为 SSE HTTP 传输
123
+ - 在服务器 `/opt/markdown-new-mcp` 目录部署项目,创建 systemd 服务监听端口 38721
124
+ - 创建本地包装脚本 `~/.local/bin/markdown-new-mcp`,通过 SSH 直接执行远程 stdio 模式 MCP
125
+ - 在 `~/.claude/settings.json` 添加 `mcpServers` 配置指向本地脚本
126
+ - SSE 支持修改已提交推送: `b4056e2 Add SSE transport support for remote deployment`
127
+ - README 中的 npx 使用方式不可用,因为包未发布到 npm,只推送到了 GitHub
128
+ - 尝试用 npm token 发布失败,需要带有 bypass 2FA 权限的 Granular Access Token
129
+
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # markdown-new-mcp
2
+
3
+ MCP (Model Context Protocol) server for [markdown.new](https://markdown.new) file conversion API. Convert PDF, DOCX, XLSX, images and 20+ formats to Markdown.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install markdown-new-mcp
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### With Claude Desktop
14
+
15
+ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
16
+
17
+ ```json
18
+ {
19
+ "mcpServers": {
20
+ "markdown-new": {
21
+ "command": "npx",
22
+ "args": ["markdown-new-mcp"]
23
+ }
24
+ }
25
+ }
26
+ ```
27
+
28
+ ### With API Key (Optional)
29
+
30
+ For higher rate limits, you can provide an API key:
31
+
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "markdown-new": {
36
+ "command": "npx",
37
+ "args": ["markdown-new-mcp"],
38
+ "env": {
39
+ "MARKDOWN_NEW_API_KEY": "mk_your_api_key_here"
40
+ }
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ ## Available Tools
47
+
48
+ ### `convert_url_to_markdown`
49
+
50
+ Convert a remote file URL to Markdown.
51
+
52
+ **Parameters:**
53
+ - `url` (string, required): The URL of the remote file to convert
54
+ - `api_key` (string, optional): API key for higher rate limits
55
+
56
+ **Example:**
57
+ ```
58
+ Convert this PDF to markdown: https://example.com/document.pdf
59
+ ```
60
+
61
+ ### `convert_file_to_markdown`
62
+
63
+ Convert a local file to Markdown.
64
+
65
+ **Parameters:**
66
+ - `file_path` (string, required): The absolute path to the local file
67
+ - `api_key` (string, optional): API key for higher rate limits
68
+
69
+ **Example:**
70
+ ```
71
+ Convert /path/to/document.pdf to markdown
72
+ ```
73
+
74
+ ### `convert_url_to_json`
75
+
76
+ Convert a remote file URL to JSON with metadata (title, tokens, duration, etc).
77
+
78
+ **Parameters:**
79
+ - `url` (string, required): The URL of the remote file
80
+ - `api_key` (string, optional): API key for higher rate limits
81
+
82
+ ## Supported Formats
83
+
84
+ - **Documents:** PDF, DOCX, ODT
85
+ - **Spreadsheets:** XLSX, XLS, XLSM, XLSB, ET, ODS, Numbers
86
+ - **Images:** JPG, JPEG, PNG, WebP, SVG
87
+ - **Text/Data:** TXT, MD, CSV, JSON, XML, HTML, HTM
88
+
89
+ ## Limits
90
+
91
+ - **Without API Key:** 500 requests/day per IP
92
+ - **With API Key:** Higher limits available
93
+ - **Maximum File Size:** 10MB
94
+
95
+ ## Development
96
+
97
+ ```bash
98
+ # Install dependencies
99
+ npm install
100
+
101
+ # Build
102
+ npm run build
103
+
104
+ # Run locally
105
+ npm run dev
106
+ ```
107
+
108
+ ## License
109
+
110
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
5
+ import { z } from "zod";
6
+ import fs from "fs/promises";
7
+ import path from "path";
8
+ import { statSync } from "fs";
9
+ import http from "http";
10
+ const MARKDOWN_NEW_BASE_URL = "https://markdown.new";
11
+ function extractMarkdown(data) {
12
+ if (typeof data === "string") {
13
+ return data;
14
+ }
15
+ if (typeof data === "object" && data !== null) {
16
+ const obj = data;
17
+ if (typeof obj.markdown === "string") {
18
+ return obj.markdown;
19
+ }
20
+ if (typeof obj.content === "string") {
21
+ return obj.content;
22
+ }
23
+ }
24
+ return JSON.stringify(data, null, 2);
25
+ }
26
+ function createServer() {
27
+ const server = new McpServer({
28
+ name: "markdown-new-mcp",
29
+ version: "1.0.0",
30
+ });
31
+ server.tool("convert_url_to_markdown", "Convert a remote file URL to Markdown. Supports PDF, DOCX, XLSX, images and 20+ formats. Maximum file size: 10MB.", {
32
+ url: z.string().url().describe("The URL of the remote file to convert"),
33
+ api_key: z.string().optional().describe("Optional API key for higher rate limits (format: mk_...)"),
34
+ }, async ({ url, api_key }) => {
35
+ try {
36
+ const headers = {
37
+ "Content-Type": "application/json",
38
+ };
39
+ if (api_key) {
40
+ headers["Authorization"] = `Bearer ${api_key}`;
41
+ }
42
+ const response = await fetch(MARKDOWN_NEW_BASE_URL, {
43
+ method: "POST",
44
+ headers,
45
+ body: JSON.stringify({ url }),
46
+ });
47
+ if (!response.ok) {
48
+ const errorText = await response.text();
49
+ return {
50
+ content: [
51
+ {
52
+ type: "text",
53
+ text: `Error converting URL: ${response.status} ${response.statusText}\n${errorText}`,
54
+ },
55
+ ],
56
+ isError: true,
57
+ };
58
+ }
59
+ const data = await response.json();
60
+ const markdown = extractMarkdown(data);
61
+ return {
62
+ content: [
63
+ {
64
+ type: "text",
65
+ text: markdown,
66
+ },
67
+ ],
68
+ };
69
+ }
70
+ catch (error) {
71
+ const errorMessage = error instanceof Error ? error.message : String(error);
72
+ return {
73
+ content: [
74
+ {
75
+ type: "text",
76
+ text: `Failed to convert URL to Markdown: ${errorMessage}`,
77
+ },
78
+ ],
79
+ isError: true,
80
+ };
81
+ }
82
+ });
83
+ server.tool("convert_file_to_markdown", "Convert a local file to Markdown. Supports PDF, DOCX, XLSX, images and 20+ formats. Maximum file size: 10MB.", {
84
+ file_path: z.string().describe("The absolute path to the local file to convert"),
85
+ api_key: z.string().optional().describe("Optional API key for higher rate limits (format: mk_...)"),
86
+ }, async ({ file_path, api_key }) => {
87
+ try {
88
+ let fileStats;
89
+ try {
90
+ fileStats = statSync(file_path);
91
+ }
92
+ catch {
93
+ return {
94
+ content: [
95
+ {
96
+ type: "text",
97
+ text: `File not found: ${file_path}`,
98
+ },
99
+ ],
100
+ isError: true,
101
+ };
102
+ }
103
+ const MAX_FILE_SIZE = 10 * 1024 * 1024;
104
+ if (fileStats.size > MAX_FILE_SIZE) {
105
+ return {
106
+ content: [
107
+ {
108
+ type: "text",
109
+ text: `File too large: ${file_path} (${(fileStats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is 10MB.`,
110
+ },
111
+ ],
112
+ isError: true,
113
+ };
114
+ }
115
+ const fileContent = await fs.readFile(file_path);
116
+ const fileName = path.basename(file_path);
117
+ const formData = new FormData();
118
+ const blob = new Blob([fileContent]);
119
+ formData.append("file", blob, fileName);
120
+ const headers = {};
121
+ if (api_key) {
122
+ headers["Authorization"] = `Bearer ${api_key}`;
123
+ }
124
+ const response = await fetch(`${MARKDOWN_NEW_BASE_URL}/convert`, {
125
+ method: "POST",
126
+ headers,
127
+ body: formData,
128
+ });
129
+ if (!response.ok) {
130
+ const errorText = await response.text();
131
+ return {
132
+ content: [
133
+ {
134
+ type: "text",
135
+ text: `Error converting file: ${response.status} ${response.statusText}\n${errorText}`,
136
+ },
137
+ ],
138
+ isError: true,
139
+ };
140
+ }
141
+ const data = await response.json();
142
+ const markdown = extractMarkdown(data);
143
+ return {
144
+ content: [
145
+ {
146
+ type: "text",
147
+ text: markdown,
148
+ },
149
+ ],
150
+ };
151
+ }
152
+ catch (error) {
153
+ const errorMessage = error instanceof Error ? error.message : String(error);
154
+ return {
155
+ content: [
156
+ {
157
+ type: "text",
158
+ text: `Failed to convert file to Markdown: ${errorMessage}`,
159
+ },
160
+ ],
161
+ isError: true,
162
+ };
163
+ }
164
+ });
165
+ server.tool("convert_url_to_json", "Convert a remote file URL to JSON with metadata (title, tokens, duration, etc). Supports PDF, DOCX, XLSX, images and 20+ formats.", {
166
+ url: z.string().url().describe("The URL of the remote file to convert"),
167
+ api_key: z.string().optional().describe("Optional API key for higher rate limits (format: mk_...)"),
168
+ }, async ({ url, api_key }) => {
169
+ try {
170
+ const encodedUrl = encodeURIComponent(url);
171
+ const requestUrl = `${MARKDOWN_NEW_BASE_URL}/${encodedUrl}?format=json`;
172
+ const headers = {};
173
+ if (api_key) {
174
+ headers["Authorization"] = `Bearer ${api_key}`;
175
+ }
176
+ const response = await fetch(requestUrl, {
177
+ method: "GET",
178
+ headers,
179
+ });
180
+ if (!response.ok) {
181
+ const errorText = await response.text();
182
+ return {
183
+ content: [
184
+ {
185
+ type: "text",
186
+ text: `Error converting URL: ${response.status} ${response.statusText}\n${errorText}`,
187
+ },
188
+ ],
189
+ isError: true,
190
+ };
191
+ }
192
+ const data = await response.json();
193
+ return {
194
+ content: [
195
+ {
196
+ type: "text",
197
+ text: JSON.stringify(data, null, 2),
198
+ },
199
+ ],
200
+ };
201
+ }
202
+ catch (error) {
203
+ const errorMessage = error instanceof Error ? error.message : String(error);
204
+ return {
205
+ content: [
206
+ {
207
+ type: "text",
208
+ text: `Failed to convert URL to JSON: ${errorMessage}`,
209
+ },
210
+ ],
211
+ isError: true,
212
+ };
213
+ }
214
+ });
215
+ return server;
216
+ }
217
+ async function runStdio() {
218
+ const server = createServer();
219
+ const transport = new StdioServerTransport();
220
+ await server.connect(transport);
221
+ }
222
+ async function runSSE(port) {
223
+ const httpServer = http.createServer(async (req, res) => {
224
+ if (req.method === "GET" && req.url === "/health") {
225
+ res.writeHead(200, { "Content-Type": "application/json" });
226
+ res.end(JSON.stringify({ status: "ok", service: "markdown-new-mcp" }));
227
+ return;
228
+ }
229
+ if (req.method === "GET" && req.url === "/sse") {
230
+ const server = createServer();
231
+ const transport = new SSEServerTransport("/message", res);
232
+ await server.connect(transport);
233
+ return;
234
+ }
235
+ if (req.method === "POST" && req.url === "/message") {
236
+ let body = "";
237
+ req.on("data", (chunk) => {
238
+ body += chunk.toString();
239
+ });
240
+ req.on("end", () => {
241
+ res.writeHead(200, { "Content-Type": "application/json" });
242
+ res.end(JSON.stringify({ received: true }));
243
+ });
244
+ return;
245
+ }
246
+ res.writeHead(404);
247
+ res.end("Not Found");
248
+ });
249
+ return new Promise((resolve) => {
250
+ httpServer.listen(port, "0.0.0.0", () => {
251
+ console.log(`MCP SSE Server running on http://0.0.0.0:${port}`);
252
+ console.log(`Health check: http://0.0.0.0:${port}/health`);
253
+ console.log(`SSE endpoint: http://0.0.0.0:${port}/sse`);
254
+ resolve();
255
+ });
256
+ });
257
+ }
258
+ async function main() {
259
+ const mode = process.env.MCP_TRANSPORT || "stdio";
260
+ const port = parseInt(process.env.MCP_PORT || "38721", 10);
261
+ if (mode === "sse") {
262
+ await runSSE(port);
263
+ }
264
+ else {
265
+ await runStdio();
266
+ }
267
+ }
268
+ main().catch((error) => {
269
+ console.error("Fatal error in main():", error);
270
+ process.exit(1);
271
+ });
272
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "markdown-new-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for markdown.new file conversion API - convert PDF, DOCX, XLSX, images and 20+ formats to Markdown",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "markdown-new-mcp": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "tsx src/index.ts",
14
+ "prepare": "npm run build"
15
+ },
16
+ "keywords": [
17
+ "mcp",
18
+ "markdown",
19
+ "file-conversion",
20
+ "pdf-to-markdown",
21
+ "docx-to-markdown",
22
+ "xlsx-to-markdown"
23
+ ],
24
+ "author": "",
25
+ "license": "MIT",
26
+ "dependencies": {
27
+ "@modelcontextprotocol/sdk": "^1.27.0",
28
+ "zod": "^3.22.4"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^20.11.24",
32
+ "typescript": "^5.3.3",
33
+ "tsx": "^4.7.0"
34
+ },
35
+ "engines": {
36
+ "node": ">=18.0.0"
37
+ }
38
+ }
package/src/index.ts ADDED
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
6
+ import { z } from "zod";
7
+ import fs from "fs/promises";
8
+ import path from "path";
9
+ import { statSync } from "fs";
10
+ import http from "http";
11
+
12
+ const MARKDOWN_NEW_BASE_URL = "https://markdown.new";
13
+
14
+ interface MarkdownResponse {
15
+ markdown?: string;
16
+ content?: string;
17
+ title?: string;
18
+ tokens?: number;
19
+ duration?: number;
20
+ [key: string]: unknown;
21
+ }
22
+
23
+ function extractMarkdown(data: unknown): string {
24
+ if (typeof data === "string") {
25
+ return data;
26
+ }
27
+ if (typeof data === "object" && data !== null) {
28
+ const obj = data as MarkdownResponse;
29
+ if (typeof obj.markdown === "string") {
30
+ return obj.markdown;
31
+ }
32
+ if (typeof obj.content === "string") {
33
+ return obj.content;
34
+ }
35
+ }
36
+ return JSON.stringify(data, null, 2);
37
+ }
38
+
39
+ function createServer(): McpServer {
40
+ const server = new McpServer({
41
+ name: "markdown-new-mcp",
42
+ version: "1.0.0",
43
+ });
44
+
45
+ server.tool(
46
+ "convert_url_to_markdown",
47
+ "Convert a remote file URL to Markdown. Supports PDF, DOCX, XLSX, images and 20+ formats. Maximum file size: 10MB.",
48
+ {
49
+ url: z.string().url().describe("The URL of the remote file to convert"),
50
+ api_key: z.string().optional().describe("Optional API key for higher rate limits (format: mk_...)"),
51
+ },
52
+ async ({ url, api_key }) => {
53
+ try {
54
+ const headers: Record<string, string> = {
55
+ "Content-Type": "application/json",
56
+ };
57
+
58
+ if (api_key) {
59
+ headers["Authorization"] = `Bearer ${api_key}`;
60
+ }
61
+
62
+ const response = await fetch(MARKDOWN_NEW_BASE_URL, {
63
+ method: "POST",
64
+ headers,
65
+ body: JSON.stringify({ url }),
66
+ });
67
+
68
+ if (!response.ok) {
69
+ const errorText = await response.text();
70
+ return {
71
+ content: [
72
+ {
73
+ type: "text" as const,
74
+ text: `Error converting URL: ${response.status} ${response.statusText}\n${errorText}`,
75
+ },
76
+ ],
77
+ isError: true,
78
+ };
79
+ }
80
+
81
+ const data = await response.json();
82
+ const markdown = extractMarkdown(data);
83
+
84
+ return {
85
+ content: [
86
+ {
87
+ type: "text" as const,
88
+ text: markdown,
89
+ },
90
+ ],
91
+ };
92
+ } catch (error) {
93
+ const errorMessage = error instanceof Error ? error.message : String(error);
94
+ return {
95
+ content: [
96
+ {
97
+ type: "text" as const,
98
+ text: `Failed to convert URL to Markdown: ${errorMessage}`,
99
+ },
100
+ ],
101
+ isError: true,
102
+ };
103
+ }
104
+ }
105
+ );
106
+
107
+ server.tool(
108
+ "convert_file_to_markdown",
109
+ "Convert a local file to Markdown. Supports PDF, DOCX, XLSX, images and 20+ formats. Maximum file size: 10MB.",
110
+ {
111
+ file_path: z.string().describe("The absolute path to the local file to convert"),
112
+ api_key: z.string().optional().describe("Optional API key for higher rate limits (format: mk_...)"),
113
+ },
114
+ async ({ file_path, api_key }) => {
115
+ try {
116
+ let fileStats;
117
+ try {
118
+ fileStats = statSync(file_path);
119
+ } catch {
120
+ return {
121
+ content: [
122
+ {
123
+ type: "text" as const,
124
+ text: `File not found: ${file_path}`,
125
+ },
126
+ ],
127
+ isError: true,
128
+ };
129
+ }
130
+
131
+ const MAX_FILE_SIZE = 10 * 1024 * 1024;
132
+ if (fileStats.size > MAX_FILE_SIZE) {
133
+ return {
134
+ content: [
135
+ {
136
+ type: "text" as const,
137
+ text: `File too large: ${file_path} (${(fileStats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is 10MB.`,
138
+ },
139
+ ],
140
+ isError: true,
141
+ };
142
+ }
143
+
144
+ const fileContent = await fs.readFile(file_path);
145
+ const fileName = path.basename(file_path);
146
+
147
+ const formData = new FormData();
148
+ const blob = new Blob([fileContent]);
149
+ formData.append("file", blob, fileName);
150
+
151
+ const headers: Record<string, string> = {};
152
+
153
+ if (api_key) {
154
+ headers["Authorization"] = `Bearer ${api_key}`;
155
+ }
156
+
157
+ const response = await fetch(`${MARKDOWN_NEW_BASE_URL}/convert`, {
158
+ method: "POST",
159
+ headers,
160
+ body: formData,
161
+ });
162
+
163
+ if (!response.ok) {
164
+ const errorText = await response.text();
165
+ return {
166
+ content: [
167
+ {
168
+ type: "text" as const,
169
+ text: `Error converting file: ${response.status} ${response.statusText}\n${errorText}`,
170
+ },
171
+ ],
172
+ isError: true,
173
+ };
174
+ }
175
+
176
+ const data = await response.json();
177
+ const markdown = extractMarkdown(data);
178
+
179
+ return {
180
+ content: [
181
+ {
182
+ type: "text" as const,
183
+ text: markdown,
184
+ },
185
+ ],
186
+ };
187
+ } catch (error) {
188
+ const errorMessage = error instanceof Error ? error.message : String(error);
189
+ return {
190
+ content: [
191
+ {
192
+ type: "text" as const,
193
+ text: `Failed to convert file to Markdown: ${errorMessage}`,
194
+ },
195
+ ],
196
+ isError: true,
197
+ };
198
+ }
199
+ }
200
+ );
201
+
202
+ server.tool(
203
+ "convert_url_to_json",
204
+ "Convert a remote file URL to JSON with metadata (title, tokens, duration, etc). Supports PDF, DOCX, XLSX, images and 20+ formats.",
205
+ {
206
+ url: z.string().url().describe("The URL of the remote file to convert"),
207
+ api_key: z.string().optional().describe("Optional API key for higher rate limits (format: mk_...)"),
208
+ },
209
+ async ({ url, api_key }) => {
210
+ try {
211
+ const encodedUrl = encodeURIComponent(url);
212
+ const requestUrl = `${MARKDOWN_NEW_BASE_URL}/${encodedUrl}?format=json`;
213
+
214
+ const headers: Record<string, string> = {};
215
+
216
+ if (api_key) {
217
+ headers["Authorization"] = `Bearer ${api_key}`;
218
+ }
219
+
220
+ const response = await fetch(requestUrl, {
221
+ method: "GET",
222
+ headers,
223
+ });
224
+
225
+ if (!response.ok) {
226
+ const errorText = await response.text();
227
+ return {
228
+ content: [
229
+ {
230
+ type: "text" as const,
231
+ text: `Error converting URL: ${response.status} ${response.statusText}\n${errorText}`,
232
+ },
233
+ ],
234
+ isError: true,
235
+ };
236
+ }
237
+
238
+ const data = await response.json();
239
+
240
+ return {
241
+ content: [
242
+ {
243
+ type: "text" as const,
244
+ text: JSON.stringify(data, null, 2),
245
+ },
246
+ ],
247
+ };
248
+ } catch (error) {
249
+ const errorMessage = error instanceof Error ? error.message : String(error);
250
+ return {
251
+ content: [
252
+ {
253
+ type: "text" as const,
254
+ text: `Failed to convert URL to JSON: ${errorMessage}`,
255
+ },
256
+ ],
257
+ isError: true,
258
+ };
259
+ }
260
+ }
261
+ );
262
+
263
+ return server;
264
+ }
265
+
266
+ async function runStdio() {
267
+ const server = createServer();
268
+ const transport = new StdioServerTransport();
269
+ await server.connect(transport);
270
+ }
271
+
272
+ async function runSSE(port: number) {
273
+ const httpServer = http.createServer(async (req, res) => {
274
+ if (req.method === "GET" && req.url === "/health") {
275
+ res.writeHead(200, { "Content-Type": "application/json" });
276
+ res.end(JSON.stringify({ status: "ok", service: "markdown-new-mcp" }));
277
+ return;
278
+ }
279
+
280
+ if (req.method === "GET" && req.url === "/sse") {
281
+ const server = createServer();
282
+ const transport = new SSEServerTransport("/message", res);
283
+ await server.connect(transport);
284
+ return;
285
+ }
286
+
287
+ if (req.method === "POST" && req.url === "/message") {
288
+ let body = "";
289
+ req.on("data", (chunk) => {
290
+ body += chunk.toString();
291
+ });
292
+ req.on("end", () => {
293
+ res.writeHead(200, { "Content-Type": "application/json" });
294
+ res.end(JSON.stringify({ received: true }));
295
+ });
296
+ return;
297
+ }
298
+
299
+ res.writeHead(404);
300
+ res.end("Not Found");
301
+ });
302
+
303
+ return new Promise<void>((resolve) => {
304
+ httpServer.listen(port, "0.0.0.0", () => {
305
+ console.log(`MCP SSE Server running on http://0.0.0.0:${port}`);
306
+ console.log(`Health check: http://0.0.0.0:${port}/health`);
307
+ console.log(`SSE endpoint: http://0.0.0.0:${port}/sse`);
308
+ resolve();
309
+ });
310
+ });
311
+ }
312
+
313
+ async function main() {
314
+ const mode = process.env.MCP_TRANSPORT || "stdio";
315
+ const port = parseInt(process.env.MCP_PORT || "38721", 10);
316
+
317
+ if (mode === "sse") {
318
+ await runSSE(port);
319
+ } else {
320
+ await runStdio();
321
+ }
322
+ }
323
+
324
+ main().catch((error) => {
325
+ console.error("Fatal error in main():", error);
326
+ process.exit(1);
327
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "resolveJsonModule": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }