ms-vite-plugin 1.2.2 → 1.2.3
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/mcp/image-tools.d.ts +9 -0
- package/dist/mcp/image-tools.js +444 -0
- package/dist/mcp/ocr-tools.d.ts +9 -0
- package/dist/mcp/ocr-tools.js +348 -0
- package/dist/mcp/tools.js +4 -0
- package/docs/SKILL.md +8 -0
- package/docs/mcp-agent-description.md +13 -0
- package/package.json +2 -1
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.registerImageTools = registerImageTools;
|
|
40
|
+
const fsExtra = __importStar(require("fs-extra"));
|
|
41
|
+
const os = __importStar(require("os"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const sharp_1 = __importDefault(require("sharp"));
|
|
44
|
+
const z = __importStar(require("zod/v4"));
|
|
45
|
+
const project_1 = require("../project");
|
|
46
|
+
const tool_utils_1 = require("./tool-utils");
|
|
47
|
+
/**
|
|
48
|
+
* 解析输出路径
|
|
49
|
+
* @param inputPath 输入图片路径
|
|
50
|
+
* @param outputPath 可选输出路径
|
|
51
|
+
* @param suffix 默认文件名后缀
|
|
52
|
+
* @returns 返回输出图片绝对路径
|
|
53
|
+
* @example
|
|
54
|
+
* resolveImageOutputPath("/tmp/a.jpg", undefined, "crop")
|
|
55
|
+
*/
|
|
56
|
+
function resolveImageOutputPath(inputPath, outputPath, suffix) {
|
|
57
|
+
if (outputPath && outputPath.trim()) {
|
|
58
|
+
return path.resolve(outputPath.trim());
|
|
59
|
+
}
|
|
60
|
+
const parsed = path.parse(path.resolve(inputPath));
|
|
61
|
+
return path.join(parsed.dir, `${parsed.name}-${suffix}.png`);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 解析生成类输出路径
|
|
65
|
+
* @param outputPath 可选输出路径
|
|
66
|
+
* @param fileNamePrefix 默认文件名前缀
|
|
67
|
+
* @param extension 文件扩展名(不含点)
|
|
68
|
+
* @returns 返回输出图片绝对路径
|
|
69
|
+
* @example
|
|
70
|
+
* resolveGeneratedImageOutputPath(undefined, "ms-mcp-screen-crop", "png")
|
|
71
|
+
*/
|
|
72
|
+
function resolveGeneratedImageOutputPath(outputPath, fileNamePrefix, extension) {
|
|
73
|
+
if (outputPath && outputPath.trim()) {
|
|
74
|
+
return path.resolve(outputPath.trim());
|
|
75
|
+
}
|
|
76
|
+
return path.join(os.tmpdir(), `${fileNamePrefix}-${Date.now()}-${Math.random()
|
|
77
|
+
.toString(36)
|
|
78
|
+
.slice(2, 8)}.${extension}`);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 解析图片路径并确保文件存在
|
|
82
|
+
* @param imagePath 原始图片路径
|
|
83
|
+
* @returns 返回绝对路径
|
|
84
|
+
* @example
|
|
85
|
+
* const inputPath = await resolveExistingImagePath("./screen.jpg")
|
|
86
|
+
*/
|
|
87
|
+
async function resolveExistingImagePath(imagePath) {
|
|
88
|
+
const inputPath = path.resolve(imagePath.trim());
|
|
89
|
+
if (!(await fsExtra.pathExists(inputPath))) {
|
|
90
|
+
throw new Error(`图片不存在: ${inputPath}`);
|
|
91
|
+
}
|
|
92
|
+
return inputPath;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 解析十六进制颜色
|
|
96
|
+
* @param value 颜色文本,支持 #RRGGBB 或 RRGGBB
|
|
97
|
+
* @returns 返回 RGB 颜色
|
|
98
|
+
* @example
|
|
99
|
+
* parseHexColor("#ffffff")
|
|
100
|
+
*/
|
|
101
|
+
function parseHexColor(value) {
|
|
102
|
+
const normalized = value.trim().replace(/^#/, "");
|
|
103
|
+
if (!/^[0-9a-fA-F]{6}$/.test(normalized)) {
|
|
104
|
+
throw new Error("transparentColor 必须是 #RRGGBB 或 RRGGBB 格式。");
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
r: Number.parseInt(normalized.slice(0, 2), 16),
|
|
108
|
+
g: Number.parseInt(normalized.slice(2, 4), 16),
|
|
109
|
+
b: Number.parseInt(normalized.slice(4, 6), 16),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 将 RGB 颜色格式化为 #RRGGBB
|
|
114
|
+
* @param color RGB 颜色
|
|
115
|
+
* @returns 返回十六进制颜色文本
|
|
116
|
+
* @example
|
|
117
|
+
* formatRgbHex({ r: 255, g: 255, b: 255 })
|
|
118
|
+
*/
|
|
119
|
+
function formatRgbHex(color) {
|
|
120
|
+
return `#${color.r.toString(16).padStart(2, "0")}${color.g
|
|
121
|
+
.toString(16)
|
|
122
|
+
.padStart(2, "0")}${color.b.toString(16).padStart(2, "0")}`;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 从图片角落采样背景色
|
|
126
|
+
* @param pixels RGBA 像素数据
|
|
127
|
+
* @param width 图片宽度
|
|
128
|
+
* @param height 图片高度
|
|
129
|
+
* @param corner 采样角落
|
|
130
|
+
* @returns 返回采样到的 RGB 颜色
|
|
131
|
+
* @example
|
|
132
|
+
* sampleCornerColor(pixels, 100, 100, "topLeft")
|
|
133
|
+
*/
|
|
134
|
+
function sampleCornerColor(pixels, width, height, corner) {
|
|
135
|
+
const x = corner === "topRight" || corner === "bottomRight" ? width - 1 : 0;
|
|
136
|
+
const y = corner === "bottomLeft" || corner === "bottomRight" ? height - 1 : 0;
|
|
137
|
+
const offset = (y * width + x) * 4;
|
|
138
|
+
return {
|
|
139
|
+
r: pixels[offset] ?? 0,
|
|
140
|
+
g: pixels[offset + 1] ?? 0,
|
|
141
|
+
b: pixels[offset + 2] ?? 0,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 判断颜色是否在容差范围内
|
|
146
|
+
* @param pixels RGBA 像素数据
|
|
147
|
+
* @param offset 像素偏移
|
|
148
|
+
* @param color 目标 RGB 颜色
|
|
149
|
+
* @param tolerance RGB 欧氏距离容差
|
|
150
|
+
* @returns 匹配返回 true
|
|
151
|
+
* @example
|
|
152
|
+
* isColorMatch(pixels, 0, { r: 255, g: 255, b: 255 }, 20)
|
|
153
|
+
*/
|
|
154
|
+
function isColorMatch(pixels, offset, color, tolerance) {
|
|
155
|
+
const dr = (pixels[offset] ?? 0) - color.r;
|
|
156
|
+
const dg = (pixels[offset + 1] ?? 0) - color.g;
|
|
157
|
+
const db = (pixels[offset + 2] ?? 0) - color.b;
|
|
158
|
+
return Math.sqrt(dr * dr + dg * dg + db * db) <= tolerance;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 读取图片为 RGBA 原始像素
|
|
162
|
+
* @param input 图片路径或 Buffer
|
|
163
|
+
* @returns 返回像素和图片尺寸
|
|
164
|
+
* @example
|
|
165
|
+
* const image = await readRgbaImage("/tmp/screen.jpg")
|
|
166
|
+
*/
|
|
167
|
+
async function readRgbaImage(input) {
|
|
168
|
+
const { data, info } = await (0, sharp_1.default)(input)
|
|
169
|
+
.ensureAlpha()
|
|
170
|
+
.raw()
|
|
171
|
+
.toBuffer({ resolveWithObject: true });
|
|
172
|
+
return {
|
|
173
|
+
data,
|
|
174
|
+
width: info.width,
|
|
175
|
+
height: info.height,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 获取指定坐标附近的平均颜色
|
|
180
|
+
* @param data RGBA 像素数据
|
|
181
|
+
* @param width 图片宽度
|
|
182
|
+
* @param height 图片高度
|
|
183
|
+
* @param x 目标坐标 x
|
|
184
|
+
* @param y 目标坐标 y
|
|
185
|
+
* @param radius 采样半径
|
|
186
|
+
* @returns 返回平均 RGBA 颜色和采样像素数量
|
|
187
|
+
* @example
|
|
188
|
+
* pickAverageColor(data, 100, 100, 10, 10, 1)
|
|
189
|
+
*/
|
|
190
|
+
function pickAverageColor(data, width, height, x, y, radius) {
|
|
191
|
+
if (x >= width || y >= height) {
|
|
192
|
+
throw new Error(`坐标超出图片范围: image=${width}x${height}, point=${x},${y}`);
|
|
193
|
+
}
|
|
194
|
+
const startX = Math.max(0, x - radius);
|
|
195
|
+
const endX = Math.min(width - 1, x + radius);
|
|
196
|
+
const startY = Math.max(0, y - radius);
|
|
197
|
+
const endY = Math.min(height - 1, y + radius);
|
|
198
|
+
let r = 0;
|
|
199
|
+
let g = 0;
|
|
200
|
+
let b = 0;
|
|
201
|
+
let a = 0;
|
|
202
|
+
let sampleCount = 0;
|
|
203
|
+
for (let currentY = startY; currentY <= endY; currentY += 1) {
|
|
204
|
+
for (let currentX = startX; currentX <= endX; currentX += 1) {
|
|
205
|
+
const offset = (currentY * width + currentX) * 4;
|
|
206
|
+
r += data[offset] ?? 0;
|
|
207
|
+
g += data[offset + 1] ?? 0;
|
|
208
|
+
b += data[offset + 2] ?? 0;
|
|
209
|
+
a += data[offset + 3] ?? 0;
|
|
210
|
+
sampleCount += 1;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
color: {
|
|
215
|
+
r: Math.round(r / sampleCount),
|
|
216
|
+
g: Math.round(g / sampleCount),
|
|
217
|
+
b: Math.round(b / sampleCount),
|
|
218
|
+
a: Math.round(a / sampleCount),
|
|
219
|
+
},
|
|
220
|
+
sampleCount,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* 格式化取色结果
|
|
225
|
+
* @param source 颜色来源
|
|
226
|
+
* @param x 坐标 x
|
|
227
|
+
* @param y 坐标 y
|
|
228
|
+
* @param radius 采样半径
|
|
229
|
+
* @param imageWidth 图片宽度
|
|
230
|
+
* @param imageHeight 图片高度
|
|
231
|
+
* @param color 颜色
|
|
232
|
+
* @param sampleCount 采样像素数量
|
|
233
|
+
* @returns 返回 MCP 文本
|
|
234
|
+
* @example
|
|
235
|
+
* formatPickedColorText("screen", 1, 1, 0, 100, 100, color, 1)
|
|
236
|
+
*/
|
|
237
|
+
function formatPickedColorText(source, x, y, radius, imageWidth, imageHeight, color, sampleCount) {
|
|
238
|
+
return [
|
|
239
|
+
"取色成功",
|
|
240
|
+
`source: ${source}`,
|
|
241
|
+
`image: ${imageWidth}x${imageHeight}`,
|
|
242
|
+
`point: x=${x}, y=${y}`,
|
|
243
|
+
`radius: ${radius}`,
|
|
244
|
+
`samplePixels: ${sampleCount}`,
|
|
245
|
+
`hex: ${formatRgbHex(color)}`,
|
|
246
|
+
`rgb: ${color.r},${color.g},${color.b}`,
|
|
247
|
+
`rgba: ${color.r},${color.g},${color.b},${color.a}`,
|
|
248
|
+
].join("\n");
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* 注册图片处理 MCP 工具
|
|
252
|
+
* @param server MCP 服务实例
|
|
253
|
+
* @returns 无返回值
|
|
254
|
+
* @example
|
|
255
|
+
* registerImageTools(server)
|
|
256
|
+
*/
|
|
257
|
+
function registerImageTools(server) {
|
|
258
|
+
server.registerTool("image_crop", {
|
|
259
|
+
title: "Image Crop",
|
|
260
|
+
description: "裁切本地图片并输出 PNG 文件,适合从截图中裁出找图模板。",
|
|
261
|
+
inputSchema: {
|
|
262
|
+
imagePath: z.string().min(1).describe("输入图片路径"),
|
|
263
|
+
x: z.number().int().min(0).describe("裁切起点 x"),
|
|
264
|
+
y: z.number().int().min(0).describe("裁切起点 y"),
|
|
265
|
+
width: z.number().int().min(1).describe("裁切宽度"),
|
|
266
|
+
height: z.number().int().min(1).describe("裁切高度"),
|
|
267
|
+
outputPath: z
|
|
268
|
+
.string()
|
|
269
|
+
.min(1)
|
|
270
|
+
.optional()
|
|
271
|
+
.describe("可选输出路径;不传则在输入图片同目录生成 *-crop.png"),
|
|
272
|
+
},
|
|
273
|
+
}, async ({ imagePath, x, y, width, height, outputPath }) => {
|
|
274
|
+
const inputPath = await resolveExistingImagePath(imagePath);
|
|
275
|
+
const targetPath = resolveImageOutputPath(inputPath, outputPath, "crop");
|
|
276
|
+
const metadata = await (0, sharp_1.default)(inputPath).metadata();
|
|
277
|
+
const imageWidth = metadata.width ?? 0;
|
|
278
|
+
const imageHeight = metadata.height ?? 0;
|
|
279
|
+
if (x + width > imageWidth || y + height > imageHeight) {
|
|
280
|
+
return (0, tool_utils_1.createTextToolResult)(`裁切范围超出图片尺寸: image=${imageWidth}x${imageHeight}, crop=${x},${y},${width},${height}`, true);
|
|
281
|
+
}
|
|
282
|
+
await fsExtra.ensureDir(path.dirname(targetPath));
|
|
283
|
+
const info = await (0, sharp_1.default)(inputPath)
|
|
284
|
+
.extract({ left: x, top: y, width, height })
|
|
285
|
+
.png()
|
|
286
|
+
.toFile(targetPath);
|
|
287
|
+
return (0, tool_utils_1.createTextToolResult)([
|
|
288
|
+
"图片裁切成功",
|
|
289
|
+
`input: ${inputPath}`,
|
|
290
|
+
`output: ${targetPath}`,
|
|
291
|
+
`crop: x=${x}, y=${y}, width=${width}, height=${height}`,
|
|
292
|
+
`size: ${info.size} bytes`,
|
|
293
|
+
].join("\n"));
|
|
294
|
+
});
|
|
295
|
+
server.registerTool("screen_crop", {
|
|
296
|
+
title: "Screen Crop",
|
|
297
|
+
description: "从当前默认设备截图中按坐标裁切 PNG 文件,适合自动截取找图模板。",
|
|
298
|
+
inputSchema: {
|
|
299
|
+
x: z.number().int().min(0).describe("裁切起点 x"),
|
|
300
|
+
y: z.number().int().min(0).describe("裁切起点 y"),
|
|
301
|
+
width: z.number().int().min(1).describe("裁切宽度"),
|
|
302
|
+
height: z.number().int().min(1).describe("裁切高度"),
|
|
303
|
+
outputPath: z
|
|
304
|
+
.string()
|
|
305
|
+
.min(1)
|
|
306
|
+
.optional()
|
|
307
|
+
.describe("可选输出路径;不传则写入系统临时目录"),
|
|
308
|
+
},
|
|
309
|
+
}, async ({ x, y, width, height, outputPath }) => {
|
|
310
|
+
const target = await (0, tool_utils_1.resolveRuntimeHttpTarget)();
|
|
311
|
+
const screenshot = await (0, project_1.getScreenshotOnDevice)((0, tool_utils_1.createRuntimeHttpRequestOptions)(target));
|
|
312
|
+
const metadata = await (0, sharp_1.default)(screenshot).metadata();
|
|
313
|
+
const imageWidth = metadata.width ?? 0;
|
|
314
|
+
const imageHeight = metadata.height ?? 0;
|
|
315
|
+
if (x + width > imageWidth || y + height > imageHeight) {
|
|
316
|
+
return (0, tool_utils_1.createTextToolResult)(`裁切范围超出截图尺寸: image=${imageWidth}x${imageHeight}, crop=${x},${y},${width},${height}`, true);
|
|
317
|
+
}
|
|
318
|
+
const targetPath = resolveGeneratedImageOutputPath(outputPath, "ms-mcp-screen-crop", "png");
|
|
319
|
+
await fsExtra.ensureDir(path.dirname(targetPath));
|
|
320
|
+
const info = await (0, sharp_1.default)(screenshot)
|
|
321
|
+
.extract({ left: x, top: y, width, height })
|
|
322
|
+
.png()
|
|
323
|
+
.toFile(targetPath);
|
|
324
|
+
return (0, tool_utils_1.createTextToolResult)([
|
|
325
|
+
"屏幕裁图成功",
|
|
326
|
+
`device: ${target.label}`,
|
|
327
|
+
`output: ${targetPath}`,
|
|
328
|
+
`screen: ${imageWidth}x${imageHeight}`,
|
|
329
|
+
`crop: x=${x}, y=${y}, width=${width}, height=${height}`,
|
|
330
|
+
`size: ${info.size} bytes`,
|
|
331
|
+
].join("\n"));
|
|
332
|
+
});
|
|
333
|
+
server.registerTool("image_pick_color", {
|
|
334
|
+
title: "Image Pick Color",
|
|
335
|
+
description: "读取本地图片指定坐标颜色,返回 hex/rgb/rgba,适合生成找色代码。",
|
|
336
|
+
inputSchema: {
|
|
337
|
+
imagePath: z.string().min(1).describe("输入图片路径"),
|
|
338
|
+
x: z.number().int().min(0).describe("目标坐标 x"),
|
|
339
|
+
y: z.number().int().min(0).describe("目标坐标 y"),
|
|
340
|
+
radius: z
|
|
341
|
+
.number()
|
|
342
|
+
.int()
|
|
343
|
+
.min(0)
|
|
344
|
+
.max(20)
|
|
345
|
+
.optional()
|
|
346
|
+
.default(0)
|
|
347
|
+
.describe("采样半径,0 表示只取单点,默认 0"),
|
|
348
|
+
},
|
|
349
|
+
}, async ({ imagePath, x, y, radius }) => {
|
|
350
|
+
const inputPath = await resolveExistingImagePath(imagePath);
|
|
351
|
+
const image = await readRgbaImage(inputPath);
|
|
352
|
+
const picked = pickAverageColor(image.data, image.width, image.height, x, y, radius);
|
|
353
|
+
return (0, tool_utils_1.createTextToolResult)(formatPickedColorText(inputPath, x, y, radius, image.width, image.height, picked.color, picked.sampleCount));
|
|
354
|
+
});
|
|
355
|
+
server.registerTool("screen_pick_color", {
|
|
356
|
+
title: "Screen Pick Color",
|
|
357
|
+
description: "从当前默认设备截图中读取指定坐标颜色,返回 hex/rgb/rgba,适合自动生成找色代码。",
|
|
358
|
+
inputSchema: {
|
|
359
|
+
x: z.number().int().min(0).describe("目标坐标 x"),
|
|
360
|
+
y: z.number().int().min(0).describe("目标坐标 y"),
|
|
361
|
+
radius: z
|
|
362
|
+
.number()
|
|
363
|
+
.int()
|
|
364
|
+
.min(0)
|
|
365
|
+
.max(20)
|
|
366
|
+
.optional()
|
|
367
|
+
.default(0)
|
|
368
|
+
.describe("采样半径,0 表示只取单点,默认 0"),
|
|
369
|
+
},
|
|
370
|
+
}, async ({ x, y, radius }) => {
|
|
371
|
+
const target = await (0, tool_utils_1.resolveRuntimeHttpTarget)();
|
|
372
|
+
const screenshot = await (0, project_1.getScreenshotOnDevice)((0, tool_utils_1.createRuntimeHttpRequestOptions)(target));
|
|
373
|
+
const image = await readRgbaImage(screenshot);
|
|
374
|
+
const picked = pickAverageColor(image.data, image.width, image.height, x, y, radius);
|
|
375
|
+
return (0, tool_utils_1.createTextToolResult)(formatPickedColorText(target.label, x, y, radius, image.width, image.height, picked.color, picked.sampleCount));
|
|
376
|
+
});
|
|
377
|
+
server.registerTool("image_make_transparent", {
|
|
378
|
+
title: "Image Make Transparent",
|
|
379
|
+
description: "将图片中接近指定颜色或角落背景色的像素设为透明,并输出 PNG 文件,适合制作找图模板透明图。",
|
|
380
|
+
inputSchema: {
|
|
381
|
+
imagePath: z.string().min(1).describe("输入图片路径"),
|
|
382
|
+
outputPath: z
|
|
383
|
+
.string()
|
|
384
|
+
.min(1)
|
|
385
|
+
.optional()
|
|
386
|
+
.describe("可选输出路径;不传则在输入图片同目录生成 *-transparent.png"),
|
|
387
|
+
transparentColor: z
|
|
388
|
+
.string()
|
|
389
|
+
.min(1)
|
|
390
|
+
.optional()
|
|
391
|
+
.describe("要透明化的颜色,格式 #RRGGBB;不传则从指定角落采样"),
|
|
392
|
+
sampleCorner: z
|
|
393
|
+
.enum(["topLeft", "topRight", "bottomLeft", "bottomRight"])
|
|
394
|
+
.optional()
|
|
395
|
+
.default("topLeft")
|
|
396
|
+
.describe("未传 transparentColor 时采样的背景角落,默认 topLeft"),
|
|
397
|
+
tolerance: z
|
|
398
|
+
.number()
|
|
399
|
+
.int()
|
|
400
|
+
.min(0)
|
|
401
|
+
.max(441)
|
|
402
|
+
.optional()
|
|
403
|
+
.default(24)
|
|
404
|
+
.describe("RGB 欧氏距离容差,默认 24,最大 441"),
|
|
405
|
+
},
|
|
406
|
+
}, async ({ imagePath, outputPath, transparentColor, sampleCorner, tolerance, }) => {
|
|
407
|
+
const inputPath = await resolveExistingImagePath(imagePath);
|
|
408
|
+
const targetPath = resolveImageOutputPath(inputPath, outputPath, "transparent");
|
|
409
|
+
const { data, info } = await (0, sharp_1.default)(inputPath)
|
|
410
|
+
.ensureAlpha()
|
|
411
|
+
.raw()
|
|
412
|
+
.toBuffer({ resolveWithObject: true });
|
|
413
|
+
const targetColor = transparentColor
|
|
414
|
+
? parseHexColor(transparentColor)
|
|
415
|
+
: sampleCornerColor(data, info.width, info.height, sampleCorner);
|
|
416
|
+
let transparentPixelCount = 0;
|
|
417
|
+
for (let offset = 0; offset < data.length; offset += 4) {
|
|
418
|
+
if (isColorMatch(data, offset, targetColor, tolerance)) {
|
|
419
|
+
data[offset + 3] = 0;
|
|
420
|
+
transparentPixelCount += 1;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
await fsExtra.ensureDir(path.dirname(targetPath));
|
|
424
|
+
const output = await (0, sharp_1.default)(data, {
|
|
425
|
+
raw: {
|
|
426
|
+
width: info.width,
|
|
427
|
+
height: info.height,
|
|
428
|
+
channels: 4,
|
|
429
|
+
},
|
|
430
|
+
})
|
|
431
|
+
.png()
|
|
432
|
+
.toFile(targetPath);
|
|
433
|
+
return (0, tool_utils_1.createTextToolResult)([
|
|
434
|
+
"透明图制作成功",
|
|
435
|
+
`input: ${inputPath}`,
|
|
436
|
+
`output: ${targetPath}`,
|
|
437
|
+
`image: ${info.width}x${info.height}`,
|
|
438
|
+
`transparentColor: ${formatRgbHex(targetColor)}`,
|
|
439
|
+
`tolerance: ${tolerance}`,
|
|
440
|
+
`transparentPixels: ${transparentPixelCount}`,
|
|
441
|
+
`size: ${output.size} bytes`,
|
|
442
|
+
].join("\n"));
|
|
443
|
+
});
|
|
444
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.registerOcrTools = registerOcrTools;
|
|
37
|
+
const fsExtra = __importStar(require("fs-extra"));
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const z = __importStar(require("zod/v4"));
|
|
41
|
+
const tool_utils_1 = require("./tool-utils");
|
|
42
|
+
const APPLE_OCR_LANGUAGES = [
|
|
43
|
+
"en-US",
|
|
44
|
+
"fr-FR",
|
|
45
|
+
"it-IT",
|
|
46
|
+
"de-DE",
|
|
47
|
+
"es-ES",
|
|
48
|
+
"pt-BR",
|
|
49
|
+
"zh-Hans",
|
|
50
|
+
"zh-Hant",
|
|
51
|
+
];
|
|
52
|
+
/**
|
|
53
|
+
* 生成安全的 JavaScript 字面量
|
|
54
|
+
* @param value 任意 JSON 兼容值
|
|
55
|
+
* @returns 返回可嵌入脚本的 JSON 字面量
|
|
56
|
+
* @example
|
|
57
|
+
* jsonLiteral(["zh-Hans", "en-US"])
|
|
58
|
+
*/
|
|
59
|
+
function jsonLiteral(value) {
|
|
60
|
+
return JSON.stringify(value);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 解析 OCR 输出文件路径
|
|
64
|
+
* @param outputPath 用户指定的可选路径
|
|
65
|
+
* @returns 返回最终输出文件绝对路径
|
|
66
|
+
* @example
|
|
67
|
+
* resolveOcrOutputPath(undefined)
|
|
68
|
+
*/
|
|
69
|
+
function resolveOcrOutputPath(outputPath) {
|
|
70
|
+
if (outputPath && outputPath.trim()) {
|
|
71
|
+
return path.resolve(outputPath.trim());
|
|
72
|
+
}
|
|
73
|
+
return path.join(os.tmpdir(), `ms-mcp-ocr-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 构建 Apple OCR 运行脚本
|
|
77
|
+
* @param mode OCR 模式
|
|
78
|
+
* @param input 输入源
|
|
79
|
+
* @param x 区域左上角 x
|
|
80
|
+
* @param y 区域左上角 y
|
|
81
|
+
* @param ex 区域右下角 x
|
|
82
|
+
* @param ey 区域右下角 y
|
|
83
|
+
* @param texts 查找文本数组
|
|
84
|
+
* @param languages 识别语言数组
|
|
85
|
+
* @returns 返回可交给 runScript 的 JavaScript 脚本
|
|
86
|
+
* @example
|
|
87
|
+
* buildAppleOcrScript("recognize", "screen", 0, 0, 0, 0)
|
|
88
|
+
*/
|
|
89
|
+
function buildAppleOcrScript(mode, input, x, y, ex, ey, texts, languages) {
|
|
90
|
+
if (mode === "findText") {
|
|
91
|
+
if (!texts || texts.length === 0) {
|
|
92
|
+
throw new Error("findText 模式必须传 texts。");
|
|
93
|
+
}
|
|
94
|
+
return `appleOcr.findText(${jsonLiteral(input)}, ${jsonLiteral(texts)}, ${x}, ${y}, ${ex}, ${ey}, ${jsonLiteral(languages)});`;
|
|
95
|
+
}
|
|
96
|
+
if (mode === "numbers") {
|
|
97
|
+
return `appleOcr.recognizeNumbers(${jsonLiteral(input)}, ${x}, ${y}, ${ex}, ${ey});`;
|
|
98
|
+
}
|
|
99
|
+
return `appleOcr.recognize(${jsonLiteral(input)}, ${x}, ${y}, ${ex}, ${ey}, ${jsonLiteral(languages)});`;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 构建 Paddle OCR 运行脚本
|
|
103
|
+
* @param mode OCR 模式
|
|
104
|
+
* @param input 输入源
|
|
105
|
+
* @param x 区域左上角 x
|
|
106
|
+
* @param y 区域左上角 y
|
|
107
|
+
* @param ex 区域右下角 x
|
|
108
|
+
* @param ey 区域右下角 y
|
|
109
|
+
* @param texts 查找文本数组
|
|
110
|
+
* @param confidenceThreshold 置信度阈值
|
|
111
|
+
* @param maxSideLen 模型最大边长
|
|
112
|
+
* @param useGpu 是否使用 GPU
|
|
113
|
+
* @returns 返回可交给 runScript 的 JavaScript 脚本
|
|
114
|
+
* @example
|
|
115
|
+
* buildPaddleOcrScript("recognize", "screen", 0, 0, 0, 0)
|
|
116
|
+
*/
|
|
117
|
+
function buildPaddleOcrScript(mode, input, x, y, ex, ey, texts, confidenceThreshold, maxSideLen, useGpu) {
|
|
118
|
+
if (mode === "numbers") {
|
|
119
|
+
throw new Error("PaddleOCR 文档未提供 numbers 模式,请使用 appleocr 引擎。");
|
|
120
|
+
}
|
|
121
|
+
const loadScript = `const __loaded = paddleOcr.loadV5(${maxSideLen}, ${useGpu});`;
|
|
122
|
+
const loadFailedScript = 'let __ocrResult; if (!__loaded) { __ocrResult = { success: false, error: "PaddleOCR loadV5 failed" }; }';
|
|
123
|
+
if (mode === "findText") {
|
|
124
|
+
if (!texts || texts.length === 0) {
|
|
125
|
+
throw new Error("findText 模式必须传 texts。");
|
|
126
|
+
}
|
|
127
|
+
return [
|
|
128
|
+
loadScript,
|
|
129
|
+
loadFailedScript,
|
|
130
|
+
`if (__loaded) { __ocrResult = paddleOcr.findText(${jsonLiteral(input)}, ${jsonLiteral(texts)}, ${x}, ${y}, ${ex}, ${ey}, ${confidenceThreshold}); }`,
|
|
131
|
+
"__ocrResult;",
|
|
132
|
+
].join("\n");
|
|
133
|
+
}
|
|
134
|
+
return [
|
|
135
|
+
loadScript,
|
|
136
|
+
loadFailedScript,
|
|
137
|
+
`if (__loaded) { __ocrResult = paddleOcr.recognize(${jsonLiteral(input)}, ${x}, ${y}, ${ex}, ${ey}, ${confidenceThreshold}); }`,
|
|
138
|
+
"__ocrResult;",
|
|
139
|
+
].join("\n");
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 构建 OCR 运行脚本
|
|
143
|
+
* @param engine OCR 引擎
|
|
144
|
+
* @param mode OCR 模式
|
|
145
|
+
* @param input 输入源
|
|
146
|
+
* @param x 区域左上角 x
|
|
147
|
+
* @param y 区域左上角 y
|
|
148
|
+
* @param ex 区域右下角 x
|
|
149
|
+
* @param ey 区域右下角 y
|
|
150
|
+
* @param texts 查找文本数组
|
|
151
|
+
* @param languages Apple OCR 识别语言数组
|
|
152
|
+
* @param confidenceThreshold PaddleOCR 置信度阈值
|
|
153
|
+
* @param paddleMaxSideLen PaddleOCR 模型最大边长
|
|
154
|
+
* @param paddleUseGpu PaddleOCR 是否使用 GPU
|
|
155
|
+
* @returns 返回可交给 runScript 的 JavaScript 脚本
|
|
156
|
+
* @example
|
|
157
|
+
* buildOcrScript("appleocr", "recognize", "screen", 0, 0, 0, 0)
|
|
158
|
+
*/
|
|
159
|
+
function buildOcrScript(engine, mode, input, x, y, ex, ey, texts, languages, confidenceThreshold, paddleMaxSideLen, paddleUseGpu) {
|
|
160
|
+
if (engine === "paddleocr") {
|
|
161
|
+
return buildPaddleOcrScript(mode, input, x, y, ex, ey, texts, confidenceThreshold, paddleMaxSideLen, paddleUseGpu);
|
|
162
|
+
}
|
|
163
|
+
return buildAppleOcrScript(mode, input, x, y, ex, ey, texts, languages);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 调用设备 runScript 接口
|
|
167
|
+
* @param ip 设备 IP
|
|
168
|
+
* @param port 设备端口
|
|
169
|
+
* @param script JavaScript 脚本文本
|
|
170
|
+
* @param timeoutMs 超时时间
|
|
171
|
+
* @returns 返回 runScript 的 JSON 响应
|
|
172
|
+
* @example
|
|
173
|
+
* await callRunScript("192.168.1.10", "9800", "1 + 1;", 30000)
|
|
174
|
+
*/
|
|
175
|
+
async function callRunScript(ip, port, script, timeoutMs) {
|
|
176
|
+
const controller = new AbortController();
|
|
177
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
178
|
+
try {
|
|
179
|
+
const response = await fetch(`http://${ip}:${port}/api/runScript`, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
signal: controller.signal,
|
|
182
|
+
headers: {
|
|
183
|
+
"Content-Type": "application/json",
|
|
184
|
+
},
|
|
185
|
+
body: JSON.stringify({ script }),
|
|
186
|
+
});
|
|
187
|
+
const text = await response.text();
|
|
188
|
+
let body;
|
|
189
|
+
try {
|
|
190
|
+
body = JSON.parse(text);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
throw new Error(`runScript 响应不是有效 JSON: ${text}`);
|
|
194
|
+
}
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
throw new Error(`runScript HTTP ${response.status}: ${text}`);
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
status: response.status,
|
|
200
|
+
body,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
205
|
+
throw new Error(`runScript 请求超时: ${timeoutMs}ms`);
|
|
206
|
+
}
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
clearTimeout(timeout);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 格式化 OCR 工具结果
|
|
215
|
+
* @param response runScript 响应
|
|
216
|
+
* @param outputPath 可选输出文件路径
|
|
217
|
+
* @returns 返回 MCP 文本内容
|
|
218
|
+
* @example
|
|
219
|
+
* await formatOcrToolText({ success: true, result: [] })
|
|
220
|
+
*/
|
|
221
|
+
async function formatOcrToolText(response, outputPath) {
|
|
222
|
+
const resultCount = Array.isArray(response.result)
|
|
223
|
+
? response.result.length
|
|
224
|
+
: undefined;
|
|
225
|
+
const responseText = (0, tool_utils_1.formatRuntimeJsonText)(response);
|
|
226
|
+
if (outputPath || responseText.length > 12000) {
|
|
227
|
+
const targetPath = resolveOcrOutputPath(outputPath);
|
|
228
|
+
await fsExtra.ensureDir(path.dirname(targetPath));
|
|
229
|
+
await fsExtra.writeJson(targetPath, response, { spaces: 2 });
|
|
230
|
+
return [
|
|
231
|
+
"OCR 执行成功",
|
|
232
|
+
...(resultCount === undefined ? [] : [`resultCount: ${resultCount}`]),
|
|
233
|
+
`resultType: ${response.resultType ?? "unknown"}`,
|
|
234
|
+
`output: ${targetPath}`,
|
|
235
|
+
].join("\n");
|
|
236
|
+
}
|
|
237
|
+
return [
|
|
238
|
+
"OCR 执行成功",
|
|
239
|
+
...(resultCount === undefined ? [] : [`resultCount: ${resultCount}`]),
|
|
240
|
+
`resultType: ${response.resultType ?? "unknown"}`,
|
|
241
|
+
"",
|
|
242
|
+
responseText,
|
|
243
|
+
].join("\n");
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* 注册 OCR MCP 工具
|
|
247
|
+
* @param server MCP 服务实例
|
|
248
|
+
* @returns 无返回值
|
|
249
|
+
* @example
|
|
250
|
+
* registerOcrTools(server)
|
|
251
|
+
*/
|
|
252
|
+
function registerOcrTools(server) {
|
|
253
|
+
server.registerTool("ocr_recognize", {
|
|
254
|
+
title: "OCR Recognize",
|
|
255
|
+
description: "通过设备 POST /api/runScript 执行快点JS OCR。支持 appleocr 与 paddleocr,默认 appleocr;OCR 没有独立 HTTP 接口时使用本工具。",
|
|
256
|
+
inputSchema: {
|
|
257
|
+
engine: z
|
|
258
|
+
.enum(["appleocr", "paddleocr"])
|
|
259
|
+
.optional()
|
|
260
|
+
.default("appleocr")
|
|
261
|
+
.describe("OCR 引擎:appleocr=Apple Vision,paddleocr=PaddleOCR"),
|
|
262
|
+
mode: z
|
|
263
|
+
.enum(["recognize", "numbers", "findText"])
|
|
264
|
+
.optional()
|
|
265
|
+
.default("recognize")
|
|
266
|
+
.describe("OCR 模式:recognize=识别文本,numbers=识别数字,findText=查找文本"),
|
|
267
|
+
input: z
|
|
268
|
+
.string()
|
|
269
|
+
.min(1)
|
|
270
|
+
.optional()
|
|
271
|
+
.default("screen")
|
|
272
|
+
.describe('输入源,默认 "screen",也可传图片路径、URL 或 imageId'),
|
|
273
|
+
x: z.number().int().min(0).optional().default(0).describe("区域左上角 x"),
|
|
274
|
+
y: z.number().int().min(0).optional().default(0).describe("区域左上角 y"),
|
|
275
|
+
ex: z
|
|
276
|
+
.number()
|
|
277
|
+
.int()
|
|
278
|
+
.min(0)
|
|
279
|
+
.optional()
|
|
280
|
+
.default(0)
|
|
281
|
+
.describe("区域右下角 x;全屏可传 0"),
|
|
282
|
+
ey: z
|
|
283
|
+
.number()
|
|
284
|
+
.int()
|
|
285
|
+
.min(0)
|
|
286
|
+
.optional()
|
|
287
|
+
.default(0)
|
|
288
|
+
.describe("区域右下角 y;全屏可传 0"),
|
|
289
|
+
texts: z
|
|
290
|
+
.array(z.string().min(1))
|
|
291
|
+
.optional()
|
|
292
|
+
.describe("findText 模式要查找的文本数组"),
|
|
293
|
+
languages: z
|
|
294
|
+
.array(z.enum(APPLE_OCR_LANGUAGES))
|
|
295
|
+
.optional()
|
|
296
|
+
.describe('Apple OCR 识别语言数组,默认由运行时使用 ["zh-Hans", "en-US"]'),
|
|
297
|
+
confidenceThreshold: z
|
|
298
|
+
.number()
|
|
299
|
+
.min(0)
|
|
300
|
+
.max(1)
|
|
301
|
+
.optional()
|
|
302
|
+
.default(0.6)
|
|
303
|
+
.describe("PaddleOCR 置信度阈值,默认 0.6"),
|
|
304
|
+
paddleMaxSideLen: z
|
|
305
|
+
.number()
|
|
306
|
+
.int()
|
|
307
|
+
.min(32)
|
|
308
|
+
.max(4096)
|
|
309
|
+
.optional()
|
|
310
|
+
.default(640)
|
|
311
|
+
.describe("PaddleOCR loadV5 最大边长,默认 640"),
|
|
312
|
+
paddleUseGpu: z
|
|
313
|
+
.boolean()
|
|
314
|
+
.optional()
|
|
315
|
+
.default(false)
|
|
316
|
+
.describe("PaddleOCR loadV5 是否使用 GPU,默认 false"),
|
|
317
|
+
outputPath: z
|
|
318
|
+
.string()
|
|
319
|
+
.min(1)
|
|
320
|
+
.optional()
|
|
321
|
+
.describe("可选输出 JSON 路径;不传且结果很长时写入系统临时目录"),
|
|
322
|
+
timeoutMs: z
|
|
323
|
+
.number()
|
|
324
|
+
.int()
|
|
325
|
+
.min(1000)
|
|
326
|
+
.max(600000)
|
|
327
|
+
.optional()
|
|
328
|
+
.default(60000)
|
|
329
|
+
.describe("runScript 请求超时时间,默认 60000 毫秒"),
|
|
330
|
+
},
|
|
331
|
+
}, async ({ engine, mode, input, x, y, ex, ey, texts, languages, confidenceThreshold, paddleMaxSideLen, paddleUseGpu, outputPath, timeoutMs, }) => {
|
|
332
|
+
const target = await (0, tool_utils_1.resolveRuntimeHttpTarget)();
|
|
333
|
+
const script = buildOcrScript(engine, mode, input, x, y, ex, ey, texts, languages, confidenceThreshold, paddleMaxSideLen, paddleUseGpu);
|
|
334
|
+
const response = await callRunScript(target.ip, target.port, script, timeoutMs);
|
|
335
|
+
const text = await formatOcrToolText(response.body, outputPath);
|
|
336
|
+
return (0, tool_utils_1.createTextToolResult)([
|
|
337
|
+
text,
|
|
338
|
+
"",
|
|
339
|
+
`device: ${target.label}`,
|
|
340
|
+
`engine: ${engine}`,
|
|
341
|
+
`mode: ${mode}`,
|
|
342
|
+
`input: ${input}`,
|
|
343
|
+
`region: x=${x}, y=${y}, ex=${ex}, ey=${ey}`,
|
|
344
|
+
`runScript: POST /api/runScript`,
|
|
345
|
+
`status: ${response.status}`,
|
|
346
|
+
].join("\n"), response.body.success === false);
|
|
347
|
+
});
|
|
348
|
+
}
|
package/dist/mcp/tools.js
CHANGED
|
@@ -8,6 +8,8 @@ const device_config_1 = require("./device-config");
|
|
|
8
8
|
Object.defineProperty(exports, "DEFAULT_DEVICE_PORT", { enumerable: true, get: function () { return device_config_1.DEFAULT_DEVICE_PORT; } });
|
|
9
9
|
const doc_tools_1 = require("./doc-tools");
|
|
10
10
|
const httpapi_tools_1 = require("./httpapi-tools");
|
|
11
|
+
const image_tools_1 = require("./image-tools");
|
|
12
|
+
const ocr_tools_1 = require("./ocr-tools");
|
|
11
13
|
const runtime_tools_1 = require("./runtime-tools");
|
|
12
14
|
/**
|
|
13
15
|
* 创建并注册 MCP 工具
|
|
@@ -23,6 +25,8 @@ function createMcpServer() {
|
|
|
23
25
|
(0, doc_tools_1.registerDocResources)(server);
|
|
24
26
|
(0, doc_tools_1.registerDocTools)(server);
|
|
25
27
|
(0, httpapi_tools_1.registerHttpApiTools)(server);
|
|
28
|
+
(0, image_tools_1.registerImageTools)(server);
|
|
29
|
+
(0, ocr_tools_1.registerOcrTools)(server);
|
|
26
30
|
(0, runtime_tools_1.registerRuntimeTools)(server);
|
|
27
31
|
return server;
|
|
28
32
|
}
|
package/docs/SKILL.md
CHANGED
|
@@ -163,6 +163,12 @@ MCP 可用时:
|
|
|
163
163
|
- `get_logs`
|
|
164
164
|
- `take_screenshot`
|
|
165
165
|
- `get_node_source`
|
|
166
|
+
- `image_crop`
|
|
167
|
+
- `screen_crop`
|
|
168
|
+
- `image_pick_color`
|
|
169
|
+
- `screen_pick_color`
|
|
170
|
+
- `ocr_recognize`
|
|
171
|
+
- `image_make_transparent`
|
|
166
172
|
|
|
167
173
|
### 通用 HTTP API
|
|
168
174
|
|
|
@@ -170,6 +176,8 @@ MCP 可用时:
|
|
|
170
176
|
|
|
171
177
|
控制、HID、IME、镜像、配置、当前应用、运行脚本等普通设备 HTTP API 通过 `search_http_api_docs`、`read_http_api_doc` 和 `http_api_call` 使用;命令 fallback 时可用已确认文档的 `curl` 调用。
|
|
172
178
|
|
|
179
|
+
OCR 没有独立 HTTP 接口,使用 `ocr_recognize` 通过 `POST /api/runScript` 执行快点JS OCR 脚本。`ocr_recognize` 支持 `appleocr` 和 `paddleocr`,默认 `appleocr`。`runScript` 的规则是最后一行表达式或变量会作为结果返回,不需要写 `return`。
|
|
180
|
+
|
|
173
181
|
## 禁止事项
|
|
174
182
|
|
|
175
183
|
- 不要在没有查询文档的情况下回答 API 用法或编写 API 调用代码。
|
|
@@ -163,6 +163,12 @@ UI 预览发起后不需要长时间等待结果,可以通过 `take_screenshot
|
|
|
163
163
|
- `get_logs`
|
|
164
164
|
- `take_screenshot`
|
|
165
165
|
- `get_node_source`
|
|
166
|
+
- `image_crop`
|
|
167
|
+
- `screen_crop`
|
|
168
|
+
- `image_pick_color`
|
|
169
|
+
- `screen_pick_color`
|
|
170
|
+
- `ocr_recognize`
|
|
171
|
+
- `image_make_transparent`
|
|
166
172
|
|
|
167
173
|
### 通用 HTTP API 调用
|
|
168
174
|
|
|
@@ -175,6 +181,13 @@ UI 预览发起后不需要长时间等待结果,可以通过 `take_screenshot
|
|
|
175
181
|
- 写快点JS脚本代码:先设置或确认文档语言,再查语言 API 文档。
|
|
176
182
|
- 调设备 HTTP API:先查 HTTP API 文档,再调用 `http_api_call` 或已确认接口的 `curl` fallback。
|
|
177
183
|
- 获取截图:优先使用 `take_screenshot`;命令 fallback 可用 `npx ms screenshot`。
|
|
184
|
+
- 从本地图片裁切找图模板:使用 `image_crop`。
|
|
185
|
+
- 从当前设备截图裁切找图模板:使用 `screen_crop`。
|
|
186
|
+
- 从本地图片指定坐标取色:使用 `image_pick_color`。
|
|
187
|
+
- 从当前设备截图指定坐标取色:使用 `screen_pick_color`。
|
|
188
|
+
- 执行 OCR 识别、数字识别或查找文字:使用 `ocr_recognize`;支持用户指定 `appleocr` 或 `paddleocr`,默认 `appleocr`。
|
|
189
|
+
- OCR 没有独立 HTTP 接口,通过 `POST /api/runScript` 执行快点JS OCR 脚本;`runScript` 的规则是最后一行表达式或变量会作为结果返回,不需要写 `return`。
|
|
190
|
+
- 制作透明找图模板:使用 `image_make_transparent`。
|
|
178
191
|
- 获取节点 XML:优先使用 `get_node_source`。
|
|
179
192
|
- 查看日志:优先使用 `get_logs`。
|
|
180
193
|
- 运行项目:优先使用 `run_project` 或 `run_ui_project`;命令 fallback 可用 `npx ms run` 或 `npx ms run-ui`。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ms-vite-plugin",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"commander": "^14.0.3",
|
|
27
27
|
"crc": "^4.3.2",
|
|
28
28
|
"fs-extra": "^11.3.4",
|
|
29
|
+
"sharp": "^0.34.5",
|
|
29
30
|
"uuid": "^14.0.0",
|
|
30
31
|
"vite": "^8.0.9",
|
|
31
32
|
"vite-plugin-bundle-obfuscator": "^1.11.0",
|