ms-vite-plugin 1.4.15 → 1.4.16
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/dist/cli.js +1002 -9
- package/dist/mcp/doc-tools.d.ts +0 -8
- package/dist/mcp/doc-tools.js +40 -93
- package/dist/mcp/docs-service.d.ts +0 -23
- package/dist/mcp/docs-service.js +0 -40
- package/dist/mcp/httpapi-docs-service.d.ts +9 -0
- package/dist/mcp/httpapi-docs-service.js +3 -7
- package/dist/mcp/httpapi-tools.js +3 -1
- package/dist/mcp/image-tools.d.ts +69 -0
- package/dist/mcp/image-tools.js +163 -0
- package/dist/mcp/ocr-tools.d.ts +50 -0
- package/dist/mcp/ocr-tools.js +59 -48
- package/dist/mcp/runtime-tools.js +12 -4
- package/dist/mcp/tool-utils.d.ts +3 -2
- package/dist/mcp/tool-utils.js +9 -3
- package/dist/mcp/tools.js +0 -1
- package/dist/project.d.ts +3 -2
- package/dist/project.js +8 -2
- package/docs/AGENTS.md +46 -36
- package/docs/SKILL.md +45 -27
- package/docs/api/paddleocr.md +11 -33
- package/docs/apicn/paddleocr.md +5 -29
- package/docs/apipython/paddleocr.md +34 -55
- package/docs/mcp-agent-description.md +52 -41
- package/package.json +1 -1
package/dist/mcp/doc-tools.d.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
/**
|
|
3
|
-
* 注册文档资源
|
|
4
|
-
* @param server MCP 服务实例
|
|
5
|
-
* @returns 无返回值
|
|
6
|
-
* @example
|
|
7
|
-
* registerDocResources(server)
|
|
8
|
-
*/
|
|
9
|
-
export declare function registerDocResources(server: McpServer): void;
|
|
10
2
|
/**
|
|
11
3
|
* 注册文档工具
|
|
12
4
|
* @param server MCP 服务实例
|
package/dist/mcp/doc-tools.js
CHANGED
|
@@ -33,73 +33,27 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.registerDocResources = registerDocResources;
|
|
37
36
|
exports.registerDocTools = registerDocTools;
|
|
38
|
-
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
39
37
|
const z = __importStar(require("zod/v4"));
|
|
40
38
|
const docs_service_1 = require("./docs-service");
|
|
39
|
+
const httpapi_docs_service_1 = require("./httpapi-docs-service");
|
|
41
40
|
const tool_utils_1 = require("./tool-utils");
|
|
42
41
|
const types_1 = require("./types");
|
|
43
42
|
/**
|
|
44
|
-
*
|
|
45
|
-
* @
|
|
46
|
-
* @returns 无返回值
|
|
43
|
+
* 格式化 KuaiJS 文档路径清单
|
|
44
|
+
* @returns 返回可直接交给 AI 读取的文档路径文本
|
|
47
45
|
* @example
|
|
48
|
-
*
|
|
46
|
+
* const text = formatDocsPathsText()
|
|
49
47
|
*/
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
uri: "kuaijs://docs/current-language",
|
|
60
|
-
mimeType: "application/json",
|
|
61
|
-
text: JSON.stringify({
|
|
62
|
-
currentLanguage: language,
|
|
63
|
-
label: types_1.DOC_LANGUAGE_LABELS[language],
|
|
64
|
-
}, null, 2),
|
|
65
|
-
},
|
|
66
|
-
],
|
|
67
|
-
};
|
|
68
|
-
});
|
|
69
|
-
server.registerResource("api-doc", new mcp_js_1.ResourceTemplate("kuaijs://docs/current/{slug}", {
|
|
70
|
-
list: async () => {
|
|
71
|
-
const language = (0, docs_service_1.getCurrentDocsLanguage)();
|
|
72
|
-
const docs = await (0, docs_service_1.getApiDocsByLanguage)(language);
|
|
73
|
-
return {
|
|
74
|
-
resources: docs.map((doc) => ({
|
|
75
|
-
uri: `kuaijs://docs/current/${doc.slug}`,
|
|
76
|
-
name: doc.title,
|
|
77
|
-
description: `[${types_1.DOC_LANGUAGE_LABELS[language]}] ${doc.slug}`,
|
|
78
|
-
mimeType: "text/markdown",
|
|
79
|
-
})),
|
|
80
|
-
};
|
|
81
|
-
},
|
|
82
|
-
}), {
|
|
83
|
-
title: "KuaiJS API documents",
|
|
84
|
-
mimeType: "text/markdown",
|
|
85
|
-
}, async (_uri, variables) => {
|
|
86
|
-
const language = (0, docs_service_1.getCurrentDocsLanguage)();
|
|
87
|
-
const docs = await (0, docs_service_1.getApiDocsByLanguage)(language);
|
|
88
|
-
const slug = String(variables.slug || "");
|
|
89
|
-
const target = docs.find((doc) => doc.slug === slug);
|
|
90
|
-
if (!target) {
|
|
91
|
-
throw new Error(`文档不存在: ${slug}`);
|
|
92
|
-
}
|
|
93
|
-
return {
|
|
94
|
-
contents: [
|
|
95
|
-
{
|
|
96
|
-
uri: `kuaijs://docs/current/${target.slug}`,
|
|
97
|
-
mimeType: "text/markdown",
|
|
98
|
-
text: target.content,
|
|
99
|
-
},
|
|
100
|
-
],
|
|
101
|
-
};
|
|
102
|
-
});
|
|
48
|
+
function formatDocsPathsText() {
|
|
49
|
+
const languages = ["js", "js_zh", "python"];
|
|
50
|
+
return [
|
|
51
|
+
"KuaiJS 文档路径:",
|
|
52
|
+
`docsRoot: ${(0, docs_service_1.getDocsRootDir)()}`,
|
|
53
|
+
...languages.map((language) => `${language}: ${(0, docs_service_1.getDocsDirByLanguage)(language)} (${types_1.DOC_LANGUAGE_LABELS[language]})`),
|
|
54
|
+
`httpApi: ${(0, httpapi_docs_service_1.getHttpApiDocPath)()}`,
|
|
55
|
+
"说明: 直接读取上述 markdown 路径;search/read 工具用于定位具体文档。",
|
|
56
|
+
].join("\n");
|
|
103
57
|
}
|
|
104
58
|
/**
|
|
105
59
|
* 注册文档工具
|
|
@@ -109,51 +63,37 @@ function registerDocResources(server) {
|
|
|
109
63
|
* registerDocTools(server)
|
|
110
64
|
*/
|
|
111
65
|
function registerDocTools(server) {
|
|
112
|
-
server.registerTool("
|
|
113
|
-
title: "Get Docs
|
|
114
|
-
description: "
|
|
66
|
+
server.registerTool("get_docs_paths", {
|
|
67
|
+
title: "Get Docs Paths",
|
|
68
|
+
description: "获取 KuaiJS 本地文档路径,便于 AI 直接读取 markdown 文件而不是猜测位置。",
|
|
115
69
|
inputSchema: {},
|
|
116
70
|
}, async () => {
|
|
117
|
-
|
|
118
|
-
return (0, tool_utils_1.createTextToolResult)(`当前文档语言: ${language} (${types_1.DOC_LANGUAGE_LABELS[language]})`);
|
|
119
|
-
});
|
|
120
|
-
server.registerTool("set_docs_language", {
|
|
121
|
-
title: "Set Docs Language",
|
|
122
|
-
description: "设置当前 KuaiJS API 文档语言,影响后续文档查询与读取。",
|
|
123
|
-
inputSchema: {
|
|
124
|
-
language: z
|
|
125
|
-
.enum(["js", "js_zh", "python"])
|
|
126
|
-
.describe("文档语言:js | js_zh | python"),
|
|
127
|
-
},
|
|
128
|
-
}, async ({ language }) => {
|
|
129
|
-
const active = (0, docs_service_1.setCurrentDocsLanguage)(language);
|
|
130
|
-
const docsDir = (0, docs_service_1.getDocsDirByLanguage)(active);
|
|
131
|
-
return (0, tool_utils_1.createTextToolResult)(`文档语言已切换为 ${active} (${types_1.DOC_LANGUAGE_LABELS[active]})\n目录: ${docsDir}`);
|
|
71
|
+
return (0, tool_utils_1.createTextToolResult)(formatDocsPathsText());
|
|
132
72
|
});
|
|
133
73
|
server.registerTool("list_api_docs", {
|
|
134
74
|
title: "List API Docs",
|
|
135
|
-
description: "
|
|
75
|
+
description: "列出指定语言下可用的 API 文档;不传 language 时默认 js_zh。",
|
|
136
76
|
inputSchema: {
|
|
137
77
|
language: z
|
|
138
78
|
.enum(["js", "js_zh", "python"])
|
|
139
79
|
.optional()
|
|
140
|
-
.describe("
|
|
80
|
+
.describe("可选语言,不传默认 js_zh"),
|
|
141
81
|
},
|
|
142
82
|
}, async ({ language }) => {
|
|
143
|
-
const activeLanguage =
|
|
83
|
+
const activeLanguage = language ?? "js_zh";
|
|
144
84
|
const docs = await (0, docs_service_1.getApiDocsByLanguage)(activeLanguage);
|
|
145
|
-
const lines = docs.map((doc, index) => (0, tool_utils_1.formatApiDocSummary)(activeLanguage, doc.title, doc.slug, index));
|
|
85
|
+
const lines = docs.map((doc, index) => (0, tool_utils_1.formatApiDocSummary)(activeLanguage, doc.title, doc.slug, index, doc.filePath));
|
|
146
86
|
return (0, tool_utils_1.createTextToolResult)(lines.length === 0
|
|
147
|
-
?
|
|
87
|
+
? `文档语言 ${activeLanguage} 下没有可用文档。`
|
|
148
88
|
: [
|
|
149
|
-
|
|
89
|
+
`文档语言: ${activeLanguage} (${types_1.DOC_LANGUAGE_LABELS[activeLanguage]})`,
|
|
150
90
|
"",
|
|
151
91
|
...lines,
|
|
152
92
|
].join("\n"));
|
|
153
93
|
});
|
|
154
94
|
server.registerTool("search_api_docs", {
|
|
155
95
|
title: "Search API Docs",
|
|
156
|
-
description: "
|
|
96
|
+
description: "在指定语言文档中按关键字搜索;不传 language 时默认 js_zh。",
|
|
157
97
|
inputSchema: {
|
|
158
98
|
query: z.string().min(1).describe("搜索关键字"),
|
|
159
99
|
limit: z
|
|
@@ -167,10 +107,10 @@ function registerDocTools(server) {
|
|
|
167
107
|
language: z
|
|
168
108
|
.enum(["js", "js_zh", "python"])
|
|
169
109
|
.optional()
|
|
170
|
-
.describe("
|
|
110
|
+
.describe("可选语言,不传默认 js_zh"),
|
|
171
111
|
},
|
|
172
112
|
}, async ({ query, limit, language }) => {
|
|
173
|
-
const activeLanguage =
|
|
113
|
+
const activeLanguage = language ?? "js_zh";
|
|
174
114
|
const docs = await (0, docs_service_1.getApiDocsByLanguage)(activeLanguage);
|
|
175
115
|
const normalizedQuery = query.toLowerCase();
|
|
176
116
|
const matches = docs
|
|
@@ -180,31 +120,38 @@ function registerDocTools(server) {
|
|
|
180
120
|
.slice(0, limit)
|
|
181
121
|
.map((item) => item.doc);
|
|
182
122
|
const text = matches.length === 0
|
|
183
|
-
?
|
|
123
|
+
? `文档语言: ${activeLanguage} (${types_1.DOC_LANGUAGE_LABELS[activeLanguage]})\n未找到与 "${query}" 匹配的文档。`
|
|
184
124
|
: [
|
|
185
|
-
|
|
125
|
+
`文档语言: ${activeLanguage} (${types_1.DOC_LANGUAGE_LABELS[activeLanguage]})`,
|
|
186
126
|
"",
|
|
187
|
-
...matches.map((doc, index) => (0, tool_utils_1.formatApiDocSummary)(activeLanguage, doc.title, doc.slug, index)),
|
|
127
|
+
...matches.map((doc, index) => (0, tool_utils_1.formatApiDocSummary)(activeLanguage, doc.title, doc.slug, index, doc.filePath)),
|
|
188
128
|
].join("\n");
|
|
189
129
|
return (0, tool_utils_1.createTextToolResult)(text);
|
|
190
130
|
});
|
|
191
131
|
server.registerTool("read_api_doc", {
|
|
192
132
|
title: "Read API Doc",
|
|
193
|
-
description: "
|
|
133
|
+
description: "读取指定语言下某个文档的完整 markdown 内容;不传 language 时默认 js_zh。",
|
|
194
134
|
inputSchema: {
|
|
195
135
|
slug: z.string().min(1).describe("文档 slug(文件名,不含 .md)"),
|
|
196
136
|
language: z
|
|
197
137
|
.enum(["js", "js_zh", "python"])
|
|
198
138
|
.optional()
|
|
199
|
-
.describe("
|
|
139
|
+
.describe("可选语言,不传默认 js_zh"),
|
|
200
140
|
},
|
|
201
141
|
}, async ({ slug, language }) => {
|
|
202
|
-
const activeLanguage =
|
|
142
|
+
const activeLanguage = language ?? "js_zh";
|
|
203
143
|
const docs = await (0, docs_service_1.getApiDocsByLanguage)(activeLanguage);
|
|
204
144
|
const target = docs.find((doc) => doc.slug === slug);
|
|
205
145
|
if (!target) {
|
|
206
146
|
return (0, tool_utils_1.createTextToolResult)(`未找到文档 ${slug}.md(语言: ${activeLanguage})。`, true);
|
|
207
147
|
}
|
|
208
|
-
return (0, tool_utils_1.createTextToolResult)(
|
|
148
|
+
return (0, tool_utils_1.createTextToolResult)([
|
|
149
|
+
`标题: ${target.title}`,
|
|
150
|
+
`语言: ${activeLanguage}`,
|
|
151
|
+
`URI: ${(0, docs_service_1.getDocUri)(activeLanguage, target.slug)}`,
|
|
152
|
+
`path: ${target.filePath}`,
|
|
153
|
+
"",
|
|
154
|
+
target.content,
|
|
155
|
+
].join("\n"));
|
|
209
156
|
});
|
|
210
157
|
}
|
|
@@ -1,27 +1,4 @@
|
|
|
1
1
|
import type { ApiDocItem, ApiDocsLanguage } from "./types";
|
|
2
|
-
/**
|
|
3
|
-
* 获取当前文档语言
|
|
4
|
-
* @returns 返回当前语言
|
|
5
|
-
* @example
|
|
6
|
-
* const language = getCurrentDocsLanguage()
|
|
7
|
-
*/
|
|
8
|
-
export declare function getCurrentDocsLanguage(): ApiDocsLanguage;
|
|
9
|
-
/**
|
|
10
|
-
* 设置当前文档语言
|
|
11
|
-
* @param language 目标语言
|
|
12
|
-
* @returns 设置后的语言
|
|
13
|
-
* @example
|
|
14
|
-
* const language = setCurrentDocsLanguage("python")
|
|
15
|
-
*/
|
|
16
|
-
export declare function setCurrentDocsLanguage(language: ApiDocsLanguage): ApiDocsLanguage;
|
|
17
|
-
/**
|
|
18
|
-
* 根据可选入参解析实际使用的文档语言
|
|
19
|
-
* @param language 工具调用时传入的语言(可选)
|
|
20
|
-
* @returns 返回最终生效的语言
|
|
21
|
-
* @example
|
|
22
|
-
* const activeLanguage = resolveDocsLanguage(undefined)
|
|
23
|
-
*/
|
|
24
|
-
export declare function resolveDocsLanguage(language?: ApiDocsLanguage): ApiDocsLanguage;
|
|
25
2
|
/**
|
|
26
3
|
* 获取 docs 根目录
|
|
27
4
|
* @returns 返回 docs 目录绝对路径
|
package/dist/mcp/docs-service.js
CHANGED
|
@@ -33,9 +33,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.getCurrentDocsLanguage = getCurrentDocsLanguage;
|
|
37
|
-
exports.setCurrentDocsLanguage = setCurrentDocsLanguage;
|
|
38
|
-
exports.resolveDocsLanguage = resolveDocsLanguage;
|
|
39
36
|
exports.getDocsRootDir = getDocsRootDir;
|
|
40
37
|
exports.getDocsDirByLanguage = getDocsDirByLanguage;
|
|
41
38
|
exports.getApiDocsByLanguage = getApiDocsByLanguage;
|
|
@@ -43,40 +40,6 @@ exports.scoreApiDoc = scoreApiDoc;
|
|
|
43
40
|
exports.getDocUri = getDocUri;
|
|
44
41
|
const fsExtra = __importStar(require("fs-extra"));
|
|
45
42
|
const path = __importStar(require("path"));
|
|
46
|
-
/**
|
|
47
|
-
* 当前 MCP 进程默认文档语言
|
|
48
|
-
*/
|
|
49
|
-
let currentDocsLanguage = "js_zh";
|
|
50
|
-
/**
|
|
51
|
-
* 获取当前文档语言
|
|
52
|
-
* @returns 返回当前语言
|
|
53
|
-
* @example
|
|
54
|
-
* const language = getCurrentDocsLanguage()
|
|
55
|
-
*/
|
|
56
|
-
function getCurrentDocsLanguage() {
|
|
57
|
-
return currentDocsLanguage;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* 设置当前文档语言
|
|
61
|
-
* @param language 目标语言
|
|
62
|
-
* @returns 设置后的语言
|
|
63
|
-
* @example
|
|
64
|
-
* const language = setCurrentDocsLanguage("python")
|
|
65
|
-
*/
|
|
66
|
-
function setCurrentDocsLanguage(language) {
|
|
67
|
-
currentDocsLanguage = language;
|
|
68
|
-
return currentDocsLanguage;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* 根据可选入参解析实际使用的文档语言
|
|
72
|
-
* @param language 工具调用时传入的语言(可选)
|
|
73
|
-
* @returns 返回最终生效的语言
|
|
74
|
-
* @example
|
|
75
|
-
* const activeLanguage = resolveDocsLanguage(undefined)
|
|
76
|
-
*/
|
|
77
|
-
function resolveDocsLanguage(language) {
|
|
78
|
-
return language ?? currentDocsLanguage;
|
|
79
|
-
}
|
|
80
43
|
/**
|
|
81
44
|
* 获取 docs 根目录
|
|
82
45
|
* @returns 返回 docs 目录绝对路径
|
|
@@ -111,9 +74,6 @@ function getDocsDirByLanguage(language) {
|
|
|
111
74
|
*/
|
|
112
75
|
async function getApiDocsByLanguage(language) {
|
|
113
76
|
const docsDir = getDocsDirByLanguage(language);
|
|
114
|
-
if (!(await fsExtra.pathExists(docsDir))) {
|
|
115
|
-
throw new Error(`文档目录不存在: ${docsDir}`);
|
|
116
|
-
}
|
|
117
77
|
const fileNames = await fsExtra.readdir(docsDir);
|
|
118
78
|
const mdFiles = fileNames
|
|
119
79
|
.filter((name) => name.toLowerCase().endsWith(".md"))
|
|
@@ -8,6 +8,8 @@ export type HttpApiMethod = "GET" | "POST";
|
|
|
8
8
|
export type HttpApiDocEntry = {
|
|
9
9
|
/** 稳定 slug,可用于读取单个接口文档 */
|
|
10
10
|
slug: string;
|
|
11
|
+
/** HTTP API 文档绝对路径 */
|
|
12
|
+
filePath: string;
|
|
11
13
|
/** 接口请求方法 */
|
|
12
14
|
method: HttpApiMethod;
|
|
13
15
|
/** 接口路径 */
|
|
@@ -23,6 +25,13 @@ export type HttpApiDocEntry = {
|
|
|
23
25
|
/** 接口完整 markdown 片段 */
|
|
24
26
|
content: string;
|
|
25
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* 获取 HTTP API 文档路径
|
|
30
|
+
* @returns 返回 HTTP API 文档绝对路径
|
|
31
|
+
* @example
|
|
32
|
+
* const filePath = getHttpApiDocPath()
|
|
33
|
+
*/
|
|
34
|
+
export declare function getHttpApiDocPath(): string;
|
|
26
35
|
/**
|
|
27
36
|
* 读取 HTTP API markdown 文档
|
|
28
37
|
* @returns 返回完整 markdown 文本
|
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getHttpApiDocPath = getHttpApiDocPath;
|
|
36
37
|
exports.readHttpApiMarkdown = readHttpApiMarkdown;
|
|
37
38
|
exports.createHttpApiSlug = createHttpApiSlug;
|
|
38
39
|
exports.getHttpApiDocEntries = getHttpApiDocEntries;
|
|
@@ -62,9 +63,6 @@ function getHttpApiDocPath() {
|
|
|
62
63
|
*/
|
|
63
64
|
async function readHttpApiMarkdown() {
|
|
64
65
|
const filePath = getHttpApiDocPath();
|
|
65
|
-
if (!(await fsExtra.pathExists(filePath))) {
|
|
66
|
-
throw new Error("HTTP API 文档不可用。");
|
|
67
|
-
}
|
|
68
66
|
return fsExtra.readFile(filePath, "utf8");
|
|
69
67
|
}
|
|
70
68
|
/**
|
|
@@ -89,10 +87,7 @@ function createHttpApiSlug(method, apiPath) {
|
|
|
89
87
|
*/
|
|
90
88
|
async function getHttpApiDocEntries() {
|
|
91
89
|
const filePath = getHttpApiDocPath();
|
|
92
|
-
const stat = await fsExtra.stat(filePath)
|
|
93
|
-
if (!stat) {
|
|
94
|
-
throw new Error("HTTP API 文档不可用。");
|
|
95
|
-
}
|
|
90
|
+
const stat = await fsExtra.stat(filePath);
|
|
96
91
|
if (cachedHttpApiDocEntries?.mtimeMs === stat.mtimeMs) {
|
|
97
92
|
return cachedHttpApiDocEntries.entries;
|
|
98
93
|
}
|
|
@@ -134,6 +129,7 @@ async function getHttpApiDocEntries() {
|
|
|
134
129
|
}
|
|
135
130
|
entries.push({
|
|
136
131
|
slug: createHttpApiSlug(method, apiPath),
|
|
132
|
+
filePath,
|
|
137
133
|
method,
|
|
138
134
|
path: apiPath,
|
|
139
135
|
title,
|
|
@@ -59,6 +59,7 @@ function formatHttpApiDocSummary(entry, index) {
|
|
|
59
59
|
`slug: ${entry.slug}`,
|
|
60
60
|
`section: ${entry.section || "未分组"}`,
|
|
61
61
|
`lines: ${entry.startLine}-${entry.endLine}`,
|
|
62
|
+
`file: ${entry.filePath}`,
|
|
62
63
|
`uri: ${HTTP_API_ENDPOINT_RESOURCE_PREFIX}/${entry.slug}`,
|
|
63
64
|
].join("\n");
|
|
64
65
|
}
|
|
@@ -183,7 +184,7 @@ function registerHttpApiTools(server) {
|
|
|
183
184
|
resources: entries.map((entry) => ({
|
|
184
185
|
uri: `${HTTP_API_ENDPOINT_RESOURCE_PREFIX}/${entry.slug}`,
|
|
185
186
|
name: `${entry.method} ${entry.path}`,
|
|
186
|
-
description: `${entry.title} [${entry.section || "未分组"}]`,
|
|
187
|
+
description: `${entry.title} [${entry.section || "未分组"}] | file: ${entry.filePath}`,
|
|
187
188
|
mimeType: "text/markdown",
|
|
188
189
|
})),
|
|
189
190
|
};
|
|
@@ -271,6 +272,7 @@ function registerHttpApiTools(server) {
|
|
|
271
272
|
`slug: ${entry.slug}`,
|
|
272
273
|
`section: ${entry.section || "未分组"}`,
|
|
273
274
|
`lines: ${entry.startLine}-${entry.endLine}`,
|
|
275
|
+
`file: ${entry.filePath}`,
|
|
274
276
|
"",
|
|
275
277
|
entry.content,
|
|
276
278
|
].join("\n"));
|
|
@@ -1,4 +1,73 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export type ImageSampleCorner = "topLeft" | "topRight" | "bottomLeft" | "bottomRight";
|
|
3
|
+
/**
|
|
4
|
+
* 裁切本地图片
|
|
5
|
+
* @param imagePath 输入图片路径
|
|
6
|
+
* @param x 裁切起点 x
|
|
7
|
+
* @param y 裁切起点 y
|
|
8
|
+
* @param width 裁切宽度
|
|
9
|
+
* @param height 裁切高度
|
|
10
|
+
* @param outputPath 可选输出路径
|
|
11
|
+
* @returns 返回操作结果文本
|
|
12
|
+
* @example
|
|
13
|
+
* await cropLocalImage("./screen.jpg", 0, 0, 100, 100)
|
|
14
|
+
*/
|
|
15
|
+
export declare function cropLocalImage(imagePath: string, x: number, y: number, width: number, height: number, outputPath?: string): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* 裁切设备截图
|
|
18
|
+
* @param target 设备 HTTP 地址
|
|
19
|
+
* @param x 裁切起点 x
|
|
20
|
+
* @param y 裁切起点 y
|
|
21
|
+
* @param width 裁切宽度
|
|
22
|
+
* @param height 裁切高度
|
|
23
|
+
* @param outputPath 可选输出路径
|
|
24
|
+
* @returns 返回操作结果文本
|
|
25
|
+
* @example
|
|
26
|
+
* await cropDeviceScreen({ ip: "192.168.1.10", port: "9800", label: "192.168.1.10:9800" }, 0, 0, 100, 100)
|
|
27
|
+
*/
|
|
28
|
+
export declare function cropDeviceScreen(target: {
|
|
29
|
+
ip: string;
|
|
30
|
+
port: string;
|
|
31
|
+
label: string;
|
|
32
|
+
}, x: number, y: number, width: number, height: number, outputPath?: string): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* 读取本地图片颜色
|
|
35
|
+
* @param imagePath 输入图片路径
|
|
36
|
+
* @param x 目标坐标 x
|
|
37
|
+
* @param y 目标坐标 y
|
|
38
|
+
* @param radius 采样半径
|
|
39
|
+
* @returns 返回取色结果文本
|
|
40
|
+
* @example
|
|
41
|
+
* await pickLocalImageColor("./screen.jpg", 10, 10, 0)
|
|
42
|
+
*/
|
|
43
|
+
export declare function pickLocalImageColor(imagePath: string, x: number, y: number, radius: number): Promise<string>;
|
|
44
|
+
/**
|
|
45
|
+
* 读取设备截图颜色
|
|
46
|
+
* @param target 设备 HTTP 地址
|
|
47
|
+
* @param x 目标坐标 x
|
|
48
|
+
* @param y 目标坐标 y
|
|
49
|
+
* @param radius 采样半径
|
|
50
|
+
* @returns 返回取色结果文本
|
|
51
|
+
* @example
|
|
52
|
+
* await pickDeviceScreenColor({ ip: "192.168.1.10", port: "9800", label: "192.168.1.10:9800" }, 10, 10, 0)
|
|
53
|
+
*/
|
|
54
|
+
export declare function pickDeviceScreenColor(target: {
|
|
55
|
+
ip: string;
|
|
56
|
+
port: string;
|
|
57
|
+
label: string;
|
|
58
|
+
}, x: number, y: number, radius: number): Promise<string>;
|
|
59
|
+
/**
|
|
60
|
+
* 制作本地图片透明图
|
|
61
|
+
* @param imagePath 输入图片路径
|
|
62
|
+
* @param outputPath 可选输出路径
|
|
63
|
+
* @param transparentColor 可选透明色
|
|
64
|
+
* @param sampleCorner 未传透明色时采样的角落
|
|
65
|
+
* @param tolerance 颜色容差
|
|
66
|
+
* @returns 返回操作结果文本
|
|
67
|
+
* @example
|
|
68
|
+
* await makeLocalImageTransparent("./button.png", undefined, "#ffffff", "topLeft", 24)
|
|
69
|
+
*/
|
|
70
|
+
export declare function makeLocalImageTransparent(imagePath: string, outputPath: string | undefined, transparentColor: string | undefined, sampleCorner: ImageSampleCorner, tolerance: number): Promise<string>;
|
|
2
71
|
/**
|
|
3
72
|
* 注册图片处理 MCP 工具
|
|
4
73
|
* @param server MCP 服务实例
|
package/dist/mcp/image-tools.js
CHANGED
|
@@ -36,6 +36,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.cropLocalImage = cropLocalImage;
|
|
40
|
+
exports.cropDeviceScreen = cropDeviceScreen;
|
|
41
|
+
exports.pickLocalImageColor = pickLocalImageColor;
|
|
42
|
+
exports.pickDeviceScreenColor = pickDeviceScreenColor;
|
|
43
|
+
exports.makeLocalImageTransparent = makeLocalImageTransparent;
|
|
39
44
|
exports.registerImageTools = registerImageTools;
|
|
40
45
|
const fsExtra = __importStar(require("fs-extra"));
|
|
41
46
|
const os = __importStar(require("os"));
|
|
@@ -247,6 +252,164 @@ function formatPickedColorText(source, x, y, radius, imageWidth, imageHeight, co
|
|
|
247
252
|
`rgba: ${color.r},${color.g},${color.b},${color.a}`,
|
|
248
253
|
].join("\n");
|
|
249
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* 裁切本地图片
|
|
257
|
+
* @param imagePath 输入图片路径
|
|
258
|
+
* @param x 裁切起点 x
|
|
259
|
+
* @param y 裁切起点 y
|
|
260
|
+
* @param width 裁切宽度
|
|
261
|
+
* @param height 裁切高度
|
|
262
|
+
* @param outputPath 可选输出路径
|
|
263
|
+
* @returns 返回操作结果文本
|
|
264
|
+
* @example
|
|
265
|
+
* await cropLocalImage("./screen.jpg", 0, 0, 100, 100)
|
|
266
|
+
*/
|
|
267
|
+
async function cropLocalImage(imagePath, x, y, width, height, outputPath) {
|
|
268
|
+
const inputPath = await resolveExistingImagePath(imagePath);
|
|
269
|
+
const targetPath = resolveImageOutputPath(inputPath, outputPath, "crop");
|
|
270
|
+
const metadata = await (0, sharp_1.default)(inputPath).metadata();
|
|
271
|
+
const imageWidth = metadata.width ?? 0;
|
|
272
|
+
const imageHeight = metadata.height ?? 0;
|
|
273
|
+
if (x + width > imageWidth || y + height > imageHeight) {
|
|
274
|
+
throw new Error(`裁切范围超出图片尺寸: image=${imageWidth}x${imageHeight}, crop=${x},${y},${width},${height}`);
|
|
275
|
+
}
|
|
276
|
+
await fsExtra.ensureDir(path.dirname(targetPath));
|
|
277
|
+
const info = await (0, sharp_1.default)(inputPath)
|
|
278
|
+
.extract({ left: x, top: y, width, height })
|
|
279
|
+
.png()
|
|
280
|
+
.toFile(targetPath);
|
|
281
|
+
return [
|
|
282
|
+
"图片裁切成功",
|
|
283
|
+
`input: ${inputPath}`,
|
|
284
|
+
`output: ${targetPath}`,
|
|
285
|
+
`crop: x=${x}, y=${y}, width=${width}, height=${height}`,
|
|
286
|
+
`size: ${info.size} bytes`,
|
|
287
|
+
].join("\n");
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* 裁切设备截图
|
|
291
|
+
* @param target 设备 HTTP 地址
|
|
292
|
+
* @param x 裁切起点 x
|
|
293
|
+
* @param y 裁切起点 y
|
|
294
|
+
* @param width 裁切宽度
|
|
295
|
+
* @param height 裁切高度
|
|
296
|
+
* @param outputPath 可选输出路径
|
|
297
|
+
* @returns 返回操作结果文本
|
|
298
|
+
* @example
|
|
299
|
+
* await cropDeviceScreen({ ip: "192.168.1.10", port: "9800", label: "192.168.1.10:9800" }, 0, 0, 100, 100)
|
|
300
|
+
*/
|
|
301
|
+
async function cropDeviceScreen(target, x, y, width, height, outputPath) {
|
|
302
|
+
const screenshot = await (0, project_1.getScreenshotOnDevice)({
|
|
303
|
+
ip: target.ip,
|
|
304
|
+
port: target.port,
|
|
305
|
+
transport: "http",
|
|
306
|
+
});
|
|
307
|
+
const metadata = await (0, sharp_1.default)(screenshot).metadata();
|
|
308
|
+
const imageWidth = metadata.width ?? 0;
|
|
309
|
+
const imageHeight = metadata.height ?? 0;
|
|
310
|
+
if (x + width > imageWidth || y + height > imageHeight) {
|
|
311
|
+
throw new Error(`裁切范围超出截图尺寸: image=${imageWidth}x${imageHeight}, crop=${x},${y},${width},${height}`);
|
|
312
|
+
}
|
|
313
|
+
const targetPath = resolveGeneratedImageOutputPath(outputPath, "ms-mcp-screen-crop", "png");
|
|
314
|
+
await fsExtra.ensureDir(path.dirname(targetPath));
|
|
315
|
+
const info = await (0, sharp_1.default)(screenshot)
|
|
316
|
+
.extract({ left: x, top: y, width, height })
|
|
317
|
+
.png()
|
|
318
|
+
.toFile(targetPath);
|
|
319
|
+
return [
|
|
320
|
+
"屏幕裁图成功",
|
|
321
|
+
`device: ${target.label}`,
|
|
322
|
+
`output: ${targetPath}`,
|
|
323
|
+
`screen: ${imageWidth}x${imageHeight}`,
|
|
324
|
+
`crop: x=${x}, y=${y}, width=${width}, height=${height}`,
|
|
325
|
+
`size: ${info.size} bytes`,
|
|
326
|
+
].join("\n");
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* 读取本地图片颜色
|
|
330
|
+
* @param imagePath 输入图片路径
|
|
331
|
+
* @param x 目标坐标 x
|
|
332
|
+
* @param y 目标坐标 y
|
|
333
|
+
* @param radius 采样半径
|
|
334
|
+
* @returns 返回取色结果文本
|
|
335
|
+
* @example
|
|
336
|
+
* await pickLocalImageColor("./screen.jpg", 10, 10, 0)
|
|
337
|
+
*/
|
|
338
|
+
async function pickLocalImageColor(imagePath, x, y, radius) {
|
|
339
|
+
const inputPath = await resolveExistingImagePath(imagePath);
|
|
340
|
+
const image = await readRgbaImage(inputPath);
|
|
341
|
+
const picked = pickAverageColor(image.data, image.width, image.height, x, y, radius);
|
|
342
|
+
return formatPickedColorText(inputPath, x, y, radius, image.width, image.height, picked.color, picked.sampleCount);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* 读取设备截图颜色
|
|
346
|
+
* @param target 设备 HTTP 地址
|
|
347
|
+
* @param x 目标坐标 x
|
|
348
|
+
* @param y 目标坐标 y
|
|
349
|
+
* @param radius 采样半径
|
|
350
|
+
* @returns 返回取色结果文本
|
|
351
|
+
* @example
|
|
352
|
+
* await pickDeviceScreenColor({ ip: "192.168.1.10", port: "9800", label: "192.168.1.10:9800" }, 10, 10, 0)
|
|
353
|
+
*/
|
|
354
|
+
async function pickDeviceScreenColor(target, x, y, radius) {
|
|
355
|
+
const screenshot = await (0, project_1.getScreenshotOnDevice)({
|
|
356
|
+
ip: target.ip,
|
|
357
|
+
port: target.port,
|
|
358
|
+
transport: "http",
|
|
359
|
+
});
|
|
360
|
+
const image = await readRgbaImage(screenshot);
|
|
361
|
+
const picked = pickAverageColor(image.data, image.width, image.height, x, y, radius);
|
|
362
|
+
return formatPickedColorText(target.label, x, y, radius, image.width, image.height, picked.color, picked.sampleCount);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* 制作本地图片透明图
|
|
366
|
+
* @param imagePath 输入图片路径
|
|
367
|
+
* @param outputPath 可选输出路径
|
|
368
|
+
* @param transparentColor 可选透明色
|
|
369
|
+
* @param sampleCorner 未传透明色时采样的角落
|
|
370
|
+
* @param tolerance 颜色容差
|
|
371
|
+
* @returns 返回操作结果文本
|
|
372
|
+
* @example
|
|
373
|
+
* await makeLocalImageTransparent("./button.png", undefined, "#ffffff", "topLeft", 24)
|
|
374
|
+
*/
|
|
375
|
+
async function makeLocalImageTransparent(imagePath, outputPath, transparentColor, sampleCorner, tolerance) {
|
|
376
|
+
const inputPath = await resolveExistingImagePath(imagePath);
|
|
377
|
+
const targetPath = resolveImageOutputPath(inputPath, outputPath, "transparent");
|
|
378
|
+
const { data, info } = await (0, sharp_1.default)(inputPath)
|
|
379
|
+
.ensureAlpha()
|
|
380
|
+
.raw()
|
|
381
|
+
.toBuffer({ resolveWithObject: true });
|
|
382
|
+
const targetColor = transparentColor
|
|
383
|
+
? parseHexColor(transparentColor)
|
|
384
|
+
: sampleCornerColor(data, info.width, info.height, sampleCorner);
|
|
385
|
+
let transparentPixelCount = 0;
|
|
386
|
+
for (let offset = 0; offset < data.length; offset += 4) {
|
|
387
|
+
if (isColorMatch(data, offset, targetColor, tolerance)) {
|
|
388
|
+
data[offset + 3] = 0;
|
|
389
|
+
transparentPixelCount += 1;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
await fsExtra.ensureDir(path.dirname(targetPath));
|
|
393
|
+
const output = await (0, sharp_1.default)(data, {
|
|
394
|
+
raw: {
|
|
395
|
+
width: info.width,
|
|
396
|
+
height: info.height,
|
|
397
|
+
channels: 4,
|
|
398
|
+
},
|
|
399
|
+
})
|
|
400
|
+
.png()
|
|
401
|
+
.toFile(targetPath);
|
|
402
|
+
return [
|
|
403
|
+
"透明图制作成功",
|
|
404
|
+
`input: ${inputPath}`,
|
|
405
|
+
`output: ${targetPath}`,
|
|
406
|
+
`image: ${info.width}x${info.height}`,
|
|
407
|
+
`transparentColor: ${formatRgbHex(targetColor)}`,
|
|
408
|
+
`tolerance: ${tolerance}`,
|
|
409
|
+
`transparentPixels: ${transparentPixelCount}`,
|
|
410
|
+
`size: ${output.size} bytes`,
|
|
411
|
+
].join("\n");
|
|
412
|
+
}
|
|
250
413
|
/**
|
|
251
414
|
* 注册图片处理 MCP 工具
|
|
252
415
|
* @param server MCP 服务实例
|