ms-vite-plugin 1.4.15 → 1.4.17
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 +707 -9
- package/dist/mcp/doc-tools.d.ts +0 -8
- package/dist/mcp/doc-tools.js +17 -188
- package/dist/mcp/docs-service.d.ts +1 -50
- package/dist/mcp/docs-service.js +0 -105
- package/dist/mcp/httpapi-docs-service.d.ts +8 -32
- package/dist/mcp/httpapi-docs-service.js +8 -89
- package/dist/mcp/httpapi-tools.d.ts +1 -1
- package/dist/mcp/httpapi-tools.js +7 -155
- 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 +0 -12
- package/dist/mcp/tool-utils.js +0 -15
- package/dist/mcp/tools.js +0 -1
- package/dist/mcp/types.d.ts +0 -13
- package/dist/project.d.ts +3 -2
- package/dist/project.js +8 -2
- package/docs/AGENTS.md +54 -52
- package/docs/SKILL.md +52 -45
- 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 +58 -73
- package/package.json +1 -1
|
@@ -34,34 +34,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.registerHttpApiTools = registerHttpApiTools;
|
|
37
|
-
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
38
37
|
const z = __importStar(require("zod/v4"));
|
|
39
38
|
const httpapi_docs_service_1 = require("./httpapi-docs-service");
|
|
40
39
|
const tool_utils_1 = require("./tool-utils");
|
|
41
|
-
/**
|
|
42
|
-
* HTTP API 文档完整资源 URI
|
|
43
|
-
*/
|
|
44
|
-
const HTTP_API_DOC_RESOURCE_URI = "kuaijs://httpapi/api";
|
|
45
|
-
const HTTP_API_ENDPOINT_RESOURCE_PREFIX = "kuaijs://httpapi/endpoints";
|
|
46
|
-
/**
|
|
47
|
-
* 格式化 HTTP API 文档搜索摘要
|
|
48
|
-
* @param entry HTTP API 文档条目
|
|
49
|
-
* @param index 列表序号
|
|
50
|
-
* @returns 返回摘要文本
|
|
51
|
-
* @example
|
|
52
|
-
* formatHttpApiDocSummary(entry, 0)
|
|
53
|
-
*/
|
|
54
|
-
function formatHttpApiDocSummary(entry, index) {
|
|
55
|
-
return [
|
|
56
|
-
`${index + 1}. ${entry.title}`,
|
|
57
|
-
`method: ${entry.method}`,
|
|
58
|
-
`path: ${entry.path}`,
|
|
59
|
-
`slug: ${entry.slug}`,
|
|
60
|
-
`section: ${entry.section || "未分组"}`,
|
|
61
|
-
`lines: ${entry.startLine}-${entry.endLine}`,
|
|
62
|
-
`uri: ${HTTP_API_ENDPOINT_RESOURCE_PREFIX}/${entry.slug}`,
|
|
63
|
-
].join("\n");
|
|
64
|
-
}
|
|
65
40
|
/**
|
|
66
41
|
* 标准化并校验 HTTP API 路径输入
|
|
67
42
|
* @param apiPath 用户传入路径
|
|
@@ -157,127 +132,16 @@ async function readHttpApiResponseText(response, responseFormat) {
|
|
|
157
132
|
}
|
|
158
133
|
}
|
|
159
134
|
/**
|
|
160
|
-
*
|
|
135
|
+
* 注册通用 HTTP API 调用工具
|
|
161
136
|
* @param server MCP 服务实例
|
|
162
137
|
* @returns 无返回值
|
|
163
138
|
* @example
|
|
164
139
|
* registerHttpApiTools(server)
|
|
165
140
|
*/
|
|
166
141
|
function registerHttpApiTools(server) {
|
|
167
|
-
server.registerResource("http-api-doc", HTTP_API_DOC_RESOURCE_URI, {
|
|
168
|
-
title: "KuaiJS HTTP API document",
|
|
169
|
-
mimeType: "text/markdown",
|
|
170
|
-
}, async () => ({
|
|
171
|
-
contents: [
|
|
172
|
-
{
|
|
173
|
-
uri: HTTP_API_DOC_RESOURCE_URI,
|
|
174
|
-
mimeType: "text/markdown",
|
|
175
|
-
text: await (0, httpapi_docs_service_1.readHttpApiMarkdown)(),
|
|
176
|
-
},
|
|
177
|
-
],
|
|
178
|
-
}));
|
|
179
|
-
server.registerResource("http-api-endpoint", new mcp_js_1.ResourceTemplate(`${HTTP_API_ENDPOINT_RESOURCE_PREFIX}/{slug}`, {
|
|
180
|
-
list: async () => {
|
|
181
|
-
const entries = await (0, httpapi_docs_service_1.getHttpApiDocEntries)();
|
|
182
|
-
return {
|
|
183
|
-
resources: entries.map((entry) => ({
|
|
184
|
-
uri: `${HTTP_API_ENDPOINT_RESOURCE_PREFIX}/${entry.slug}`,
|
|
185
|
-
name: `${entry.method} ${entry.path}`,
|
|
186
|
-
description: `${entry.title} [${entry.section || "未分组"}]`,
|
|
187
|
-
mimeType: "text/markdown",
|
|
188
|
-
})),
|
|
189
|
-
};
|
|
190
|
-
},
|
|
191
|
-
}), {
|
|
192
|
-
title: "KuaiJS HTTP API endpoint documents",
|
|
193
|
-
mimeType: "text/markdown",
|
|
194
|
-
}, async (_uri, variables) => {
|
|
195
|
-
const slug = String(variables.slug || "");
|
|
196
|
-
const entry = await (0, httpapi_docs_service_1.findHttpApiDocEntry)(slug);
|
|
197
|
-
if (!entry) {
|
|
198
|
-
throw new Error(`HTTP API 文档不存在: ${slug}`);
|
|
199
|
-
}
|
|
200
|
-
return {
|
|
201
|
-
contents: [
|
|
202
|
-
{
|
|
203
|
-
uri: `${HTTP_API_ENDPOINT_RESOURCE_PREFIX}/${entry.slug}`,
|
|
204
|
-
mimeType: "text/markdown",
|
|
205
|
-
text: entry.content,
|
|
206
|
-
},
|
|
207
|
-
],
|
|
208
|
-
};
|
|
209
|
-
});
|
|
210
|
-
server.registerTool("search_http_api_docs", {
|
|
211
|
-
title: "Search HTTP API Docs",
|
|
212
|
-
description: "搜索 HTTP API 文档中声明的接口。调用 http_api_call 前应先用本工具确认接口 method、path、参数和 slug。",
|
|
213
|
-
inputSchema: {
|
|
214
|
-
query: z
|
|
215
|
-
.string()
|
|
216
|
-
.min(1)
|
|
217
|
-
.describe("搜索关键词,例如 click、/api/control/click、截图"),
|
|
218
|
-
limit: z
|
|
219
|
-
.number()
|
|
220
|
-
.int()
|
|
221
|
-
.min(1)
|
|
222
|
-
.max(20)
|
|
223
|
-
.optional()
|
|
224
|
-
.default(8)
|
|
225
|
-
.describe("返回条数上限,默认 8"),
|
|
226
|
-
},
|
|
227
|
-
}, async ({ query, limit }) => {
|
|
228
|
-
const normalizedQuery = query.toLowerCase();
|
|
229
|
-
const entries = await (0, httpapi_docs_service_1.getHttpApiDocEntries)();
|
|
230
|
-
const matches = entries
|
|
231
|
-
.map((entry) => ({
|
|
232
|
-
entry,
|
|
233
|
-
score: (0, httpapi_docs_service_1.scoreHttpApiDocEntry)(entry, normalizedQuery),
|
|
234
|
-
}))
|
|
235
|
-
.filter((item) => item.score > 0)
|
|
236
|
-
.sort((a, b) => b.score - a.score ||
|
|
237
|
-
a.entry.path.localeCompare(b.entry.path) ||
|
|
238
|
-
a.entry.method.localeCompare(b.entry.method))
|
|
239
|
-
.slice(0, limit)
|
|
240
|
-
.map((item) => item.entry);
|
|
241
|
-
return (0, tool_utils_1.createTextToolResult)(matches.length === 0
|
|
242
|
-
? `未找到与 "${query}" 匹配的 HTTP API 文档。`
|
|
243
|
-
: [
|
|
244
|
-
"HTTP API 文档匹配结果:",
|
|
245
|
-
"",
|
|
246
|
-
...matches.map((entry, index) => formatHttpApiDocSummary(entry, index)),
|
|
247
|
-
].join("\n"));
|
|
248
|
-
});
|
|
249
|
-
server.registerTool("read_http_api_doc", {
|
|
250
|
-
title: "Read HTTP API Doc",
|
|
251
|
-
description: "按 slug 或 path 读取 HTTP API 文档中的接口片段。调用 http_api_call 前应读取目标接口片段,确认参数位置、类型和返回结构。",
|
|
252
|
-
inputSchema: {
|
|
253
|
-
identifier: z
|
|
254
|
-
.string()
|
|
255
|
-
.min(1)
|
|
256
|
-
.describe("接口 slug 或 path,例如 get-api-control-click 或 /api/control/click"),
|
|
257
|
-
method: z
|
|
258
|
-
.enum(["GET", "POST"])
|
|
259
|
-
.optional()
|
|
260
|
-
.describe("可选 HTTP 方法,用于同路径多方法时消歧"),
|
|
261
|
-
},
|
|
262
|
-
}, async ({ identifier, method }) => {
|
|
263
|
-
const entry = await (0, httpapi_docs_service_1.findHttpApiDocEntry)(identifier, method);
|
|
264
|
-
if (!entry) {
|
|
265
|
-
return (0, tool_utils_1.createTextToolResult)(`未找到 HTTP API 文档: ${identifier}\n请先调用 search_http_api_docs 搜索可用接口。`, true);
|
|
266
|
-
}
|
|
267
|
-
return (0, tool_utils_1.createTextToolResult)([
|
|
268
|
-
`title: ${entry.title}`,
|
|
269
|
-
`method: ${entry.method}`,
|
|
270
|
-
`path: ${entry.path}`,
|
|
271
|
-
`slug: ${entry.slug}`,
|
|
272
|
-
`section: ${entry.section || "未分组"}`,
|
|
273
|
-
`lines: ${entry.startLine}-${entry.endLine}`,
|
|
274
|
-
"",
|
|
275
|
-
entry.content,
|
|
276
|
-
].join("\n"));
|
|
277
|
-
});
|
|
278
142
|
server.registerTool("http_api_call", {
|
|
279
143
|
title: "HTTP API Call",
|
|
280
|
-
description: "调用 HTTP API
|
|
144
|
+
description: "调用 HTTP API 文档中已声明的接口。使用前应直接读取 get_docs_paths 返回的 HTTP API 文档,确认 method、path、query/body 参数;本工具会拒绝文档中未声明的 method/path。",
|
|
281
145
|
inputSchema: {
|
|
282
146
|
method: z
|
|
283
147
|
.enum(["GET", "POST"])
|
|
@@ -286,10 +150,6 @@ function registerHttpApiTools(server) {
|
|
|
286
150
|
.string()
|
|
287
151
|
.min(1)
|
|
288
152
|
.describe("HTTP API 相对路径,例如 /api/control/click;不能包含 query string"),
|
|
289
|
-
docSlug: z
|
|
290
|
-
.string()
|
|
291
|
-
.min(1)
|
|
292
|
-
.describe("search_http_api_docs/read_http_api_doc 返回的接口 slug"),
|
|
293
153
|
query: z
|
|
294
154
|
.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()]))
|
|
295
155
|
.optional()
|
|
@@ -312,21 +172,13 @@ function registerHttpApiTools(server) {
|
|
|
312
172
|
.default(30000)
|
|
313
173
|
.describe("请求超时时间,默认 30000 毫秒"),
|
|
314
174
|
},
|
|
315
|
-
}, async ({ method, path,
|
|
175
|
+
}, async ({ method, path, query, body, responseFormat, timeoutMs, }) => {
|
|
316
176
|
const apiPath = normalizeHttpApiPath(path);
|
|
317
|
-
const entry = await (0, httpapi_docs_service_1.findHttpApiDocEntry)(
|
|
177
|
+
const entry = await (0, httpapi_docs_service_1.findHttpApiDocEntry)(apiPath, method);
|
|
318
178
|
if (!entry) {
|
|
319
179
|
return (0, tool_utils_1.createTextToolResult)([
|
|
320
|
-
`HTTP API
|
|
321
|
-
"
|
|
322
|
-
].join("\n"), true);
|
|
323
|
-
}
|
|
324
|
-
if (entry.method !== method || entry.path !== apiPath) {
|
|
325
|
-
return (0, tool_utils_1.createTextToolResult)([
|
|
326
|
-
"HTTP API 调用与文档条目不匹配。",
|
|
327
|
-
`传入: ${method} ${apiPath} (${docSlug})`,
|
|
328
|
-
`文档: ${entry.method} ${entry.path} (${entry.slug})`,
|
|
329
|
-
"请重新调用 read_http_api_doc 确认目标接口。",
|
|
180
|
+
`HTTP API 文档中不存在该接口: ${method} ${apiPath}`,
|
|
181
|
+
"请直接读取 get_docs_paths 返回的 HTTP API 文档路径,确认接口 method、path 和参数。",
|
|
330
182
|
].join("\n"), true);
|
|
331
183
|
}
|
|
332
184
|
if (method === "GET" && body !== undefined) {
|
|
@@ -349,7 +201,7 @@ function registerHttpApiTools(server) {
|
|
|
349
201
|
return (0, tool_utils_1.createTextToolResult)([
|
|
350
202
|
`HTTP API 调用${response.ok ? "成功" : "失败"}: ${method} ${apiPath}`,
|
|
351
203
|
`设备: ${target.label}`,
|
|
352
|
-
`文档: ${entry.title} (
|
|
204
|
+
`文档: ${entry.title} (lines ${entry.startLine}-${entry.endLine})`,
|
|
353
205
|
`状态码: ${response.status}`,
|
|
354
206
|
"",
|
|
355
207
|
responseText,
|
|
@@ -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 服务实例
|
package/dist/mcp/ocr-tools.d.ts
CHANGED
|
@@ -1,4 +1,54 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export type OcrEngine = "appleocr" | "paddleocr";
|
|
3
|
+
export type OcrMode = "recognize" | "numbers" | "findText";
|
|
4
|
+
export type ScriptResultResponse = {
|
|
5
|
+
success?: boolean;
|
|
6
|
+
result?: unknown;
|
|
7
|
+
resultType?: string;
|
|
8
|
+
error?: string;
|
|
9
|
+
};
|
|
10
|
+
export type OcrRecognitionOptions = {
|
|
11
|
+
engine: OcrEngine;
|
|
12
|
+
mode: OcrMode;
|
|
13
|
+
input: string;
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
ex: number;
|
|
17
|
+
ey: number;
|
|
18
|
+
texts?: string[];
|
|
19
|
+
languages?: string[];
|
|
20
|
+
confidenceThreshold: number;
|
|
21
|
+
exactMatch: boolean;
|
|
22
|
+
outputPath?: string;
|
|
23
|
+
timeoutMs: number;
|
|
24
|
+
};
|
|
25
|
+
export type OcrRecognitionResult = {
|
|
26
|
+
status: number;
|
|
27
|
+
body: ScriptResultResponse;
|
|
28
|
+
text: string;
|
|
29
|
+
};
|
|
30
|
+
export declare const APPLE_OCR_LANGUAGES: readonly ["en-US", "fr-FR", "it-IT", "de-DE", "es-ES", "pt-BR", "zh-Hans", "zh-Hant"];
|
|
31
|
+
/**
|
|
32
|
+
* 格式化 OCR 工具结果
|
|
33
|
+
* @param response runScript 响应
|
|
34
|
+
* @param outputPath 可选输出文件路径
|
|
35
|
+
* @returns 返回 MCP 文本内容
|
|
36
|
+
* @example
|
|
37
|
+
* await formatOcrToolText({ success: true, result: [] })
|
|
38
|
+
*/
|
|
39
|
+
export declare function formatOcrToolText(response: ScriptResultResponse, outputPath?: string): Promise<string>;
|
|
40
|
+
/**
|
|
41
|
+
* 在指定 HTTP 设备上执行 OCR 识别
|
|
42
|
+
* @param target 设备 HTTP 地址
|
|
43
|
+
* @param options OCR 参数
|
|
44
|
+
* @returns 返回 HTTP 状态码、原始结果与格式化文本
|
|
45
|
+
* @example
|
|
46
|
+
* await runOcrRecognitionOnDevice({ ip: "192.168.1.10", port: "9800" }, options)
|
|
47
|
+
*/
|
|
48
|
+
export declare function runOcrRecognitionOnDevice(target: {
|
|
49
|
+
ip: string;
|
|
50
|
+
port: string;
|
|
51
|
+
}, options: OcrRecognitionOptions): Promise<OcrRecognitionResult>;
|
|
2
52
|
/**
|
|
3
53
|
* 注册 OCR MCP 工具
|
|
4
54
|
* @param server MCP 服务实例
|