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.
- package/.memsearch/memory/2026-02-24.md +129 -0
- package/README.md +110 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +272 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
- package/src/index.ts +327 -0
- package/tsconfig.json +20 -0
|
@@ -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
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|