ms-vite-plugin 1.4.47 → 1.4.49

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.
Files changed (43) hide show
  1. package/dist/build.js +1 -292
  2. package/dist/cli.js +6 -1234
  3. package/dist/mcp/device-config.d.ts +0 -47
  4. package/dist/mcp/device-config.js +1 -183
  5. package/dist/mcp/device-log.d.ts +0 -8
  6. package/dist/mcp/device-log.js +3 -27
  7. package/dist/mcp/doc-tools.d.ts +0 -7
  8. package/dist/mcp/doc-tools.js +2 -39
  9. package/dist/mcp/docs-service.d.ts +0 -13
  10. package/dist/mcp/docs-service.js +1 -63
  11. package/dist/mcp/httpapi-docs-service.d.ts +0 -31
  12. package/dist/mcp/httpapi-docs-service.js +1 -122
  13. package/dist/mcp/httpapi-tools.d.ts +0 -7
  14. package/dist/mcp/httpapi-tools.js +4 -222
  15. package/dist/mcp/image-tools.d.ts +0 -62
  16. package/dist/mcp/image-tools.js +8 -607
  17. package/dist/mcp/ocr-tools.d.ts +0 -25
  18. package/dist/mcp/ocr-tools.js +5 -413
  19. package/dist/mcp/project.d.ts +0 -14
  20. package/dist/mcp/project.js +1 -74
  21. package/dist/mcp/runtime-tools.d.ts +0 -7
  22. package/dist/mcp/runtime-tools.js +11 -308
  23. package/dist/mcp/tool-utils.d.ts +0 -40
  24. package/dist/mcp/tool-utils.js +1 -81
  25. package/dist/mcp/tools.d.ts +0 -12
  26. package/dist/mcp/tools.js +1 -31
  27. package/dist/mcp/types.d.ts +0 -12
  28. package/dist/mcp/types.js +1 -11
  29. package/dist/mcp-server.js +1 -85
  30. package/dist/packager.d.ts +0 -7
  31. package/dist/packager.js +1 -125
  32. package/dist/project.d.ts +0 -61
  33. package/dist/project.js +1 -619
  34. package/dist/stopGuardPlugin.d.ts +0 -6
  35. package/dist/stopGuardPlugin.js +1 -165
  36. package/dist/version.d.ts +0 -10
  37. package/dist/version.js +1 -63
  38. package/dist/ws-manager.d.ts +0 -95
  39. package/dist/ws-manager.js +1 -392
  40. package/docs/api/image.md +249 -25
  41. package/docs/apicn/image.md +245 -19
  42. package/docs/apipython/image.md +238 -18
  43. package/package.json +3 -2
@@ -1,607 +1,8 @@
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.cropLocalImage = cropLocalImage;
40
- exports.cropDeviceScreen = cropDeviceScreen;
41
- exports.pickLocalImageColor = pickLocalImageColor;
42
- exports.pickDeviceScreenColor = pickDeviceScreenColor;
43
- exports.makeLocalImageTransparent = makeLocalImageTransparent;
44
- exports.registerImageTools = registerImageTools;
45
- const fsExtra = __importStar(require("fs-extra"));
46
- const os = __importStar(require("os"));
47
- const path = __importStar(require("path"));
48
- const sharp_1 = __importDefault(require("sharp"));
49
- const z = __importStar(require("zod/v4"));
50
- const project_1 = require("../project");
51
- const tool_utils_1 = require("./tool-utils");
52
- /**
53
- * 解析输出路径
54
- * @param inputPath 输入图片路径
55
- * @param outputPath 可选输出路径
56
- * @param suffix 默认文件名后缀
57
- * @returns 返回输出图片绝对路径
58
- * @example
59
- * resolveImageOutputPath("/tmp/a.jpg", undefined, "crop")
60
- */
61
- function resolveImageOutputPath(inputPath, outputPath, suffix) {
62
- if (outputPath && outputPath.trim()) {
63
- return path.resolve(outputPath.trim());
64
- }
65
- const parsed = path.parse(path.resolve(inputPath));
66
- return path.join(parsed.dir, `${parsed.name}-${suffix}.png`);
67
- }
68
- /**
69
- * 解析生成类输出路径
70
- * @param outputPath 可选输出路径
71
- * @param fileNamePrefix 默认文件名前缀
72
- * @param extension 文件扩展名(不含点)
73
- * @returns 返回输出图片绝对路径
74
- * @example
75
- * resolveGeneratedImageOutputPath(undefined, "ms-mcp-screen-crop", "png")
76
- */
77
- function resolveGeneratedImageOutputPath(outputPath, fileNamePrefix, extension) {
78
- if (outputPath && outputPath.trim()) {
79
- return path.resolve(outputPath.trim());
80
- }
81
- return path.join(os.tmpdir(), `${fileNamePrefix}-${Date.now()}-${Math.random()
82
- .toString(36)
83
- .slice(2, 8)}.${extension}`);
84
- }
85
- /**
86
- * 解析图片路径并确保文件存在
87
- * @param imagePath 原始图片路径
88
- * @returns 返回绝对路径
89
- * @example
90
- * const inputPath = await resolveExistingImagePath("./screen.jpg")
91
- */
92
- async function resolveExistingImagePath(imagePath) {
93
- const inputPath = path.resolve(imagePath.trim());
94
- if (!(await fsExtra.pathExists(inputPath))) {
95
- throw new Error(`图片不存在: ${inputPath}`);
96
- }
97
- return inputPath;
98
- }
99
- /**
100
- * 解析十六进制颜色
101
- * @param value 颜色文本,支持 #RRGGBB 或 RRGGBB
102
- * @returns 返回 RGB 颜色
103
- * @example
104
- * parseHexColor("#ffffff")
105
- */
106
- function parseHexColor(value) {
107
- const normalized = value.trim().replace(/^#/, "");
108
- if (!/^[0-9a-fA-F]{6}$/.test(normalized)) {
109
- throw new Error("transparentColor 必须是 #RRGGBB 或 RRGGBB 格式。");
110
- }
111
- return {
112
- r: Number.parseInt(normalized.slice(0, 2), 16),
113
- g: Number.parseInt(normalized.slice(2, 4), 16),
114
- b: Number.parseInt(normalized.slice(4, 6), 16),
115
- };
116
- }
117
- /**
118
- * 将 RGB 颜色格式化为 #RRGGBB
119
- * @param color RGB 颜色
120
- * @returns 返回十六进制颜色文本
121
- * @example
122
- * formatRgbHex({ r: 255, g: 255, b: 255 })
123
- */
124
- function formatRgbHex(color) {
125
- return `#${color.r.toString(16).padStart(2, "0")}${color.g
126
- .toString(16)
127
- .padStart(2, "0")}${color.b.toString(16).padStart(2, "0")}`;
128
- }
129
- /**
130
- * 从图片角落采样背景色
131
- * @param pixels RGBA 像素数据
132
- * @param width 图片宽度
133
- * @param height 图片高度
134
- * @param corner 采样角落
135
- * @returns 返回采样到的 RGB 颜色
136
- * @example
137
- * sampleCornerColor(pixels, 100, 100, "topLeft")
138
- */
139
- function sampleCornerColor(pixels, width, height, corner) {
140
- const x = corner === "topRight" || corner === "bottomRight" ? width - 1 : 0;
141
- const y = corner === "bottomLeft" || corner === "bottomRight" ? height - 1 : 0;
142
- const offset = (y * width + x) * 4;
143
- return {
144
- r: pixels[offset] ?? 0,
145
- g: pixels[offset + 1] ?? 0,
146
- b: pixels[offset + 2] ?? 0,
147
- };
148
- }
149
- /**
150
- * 判断颜色是否在容差范围内
151
- * @param pixels RGBA 像素数据
152
- * @param offset 像素偏移
153
- * @param color 目标 RGB 颜色
154
- * @param tolerance RGB 欧氏距离容差
155
- * @returns 匹配返回 true
156
- * @example
157
- * isColorMatch(pixels, 0, { r: 255, g: 255, b: 255 }, 20)
158
- */
159
- function isColorMatch(pixels, offset, color, tolerance) {
160
- const dr = (pixels[offset] ?? 0) - color.r;
161
- const dg = (pixels[offset + 1] ?? 0) - color.g;
162
- const db = (pixels[offset + 2] ?? 0) - color.b;
163
- return Math.sqrt(dr * dr + dg * dg + db * db) <= tolerance;
164
- }
165
- /**
166
- * 读取图片为 RGBA 原始像素
167
- * @param input 图片路径或 Buffer
168
- * @returns 返回像素和图片尺寸
169
- * @example
170
- * const image = await readRgbaImage("/tmp/screen.jpg")
171
- */
172
- async function readRgbaImage(input) {
173
- const { data, info } = await (0, sharp_1.default)(input)
174
- .ensureAlpha()
175
- .raw()
176
- .toBuffer({ resolveWithObject: true });
177
- return {
178
- data,
179
- width: info.width,
180
- height: info.height,
181
- };
182
- }
183
- /**
184
- * 获取指定坐标附近的平均颜色
185
- * @param data RGBA 像素数据
186
- * @param width 图片宽度
187
- * @param height 图片高度
188
- * @param x 目标坐标 x
189
- * @param y 目标坐标 y
190
- * @param radius 采样半径
191
- * @returns 返回平均 RGBA 颜色和采样像素数量
192
- * @example
193
- * pickAverageColor(data, 100, 100, 10, 10, 1)
194
- */
195
- function pickAverageColor(data, width, height, x, y, radius) {
196
- if (x >= width || y >= height) {
197
- throw new Error(`坐标超出图片范围: image=${width}x${height}, point=${x},${y}`);
198
- }
199
- const startX = Math.max(0, x - radius);
200
- const endX = Math.min(width - 1, x + radius);
201
- const startY = Math.max(0, y - radius);
202
- const endY = Math.min(height - 1, y + radius);
203
- let r = 0;
204
- let g = 0;
205
- let b = 0;
206
- let a = 0;
207
- let sampleCount = 0;
208
- for (let currentY = startY; currentY <= endY; currentY += 1) {
209
- for (let currentX = startX; currentX <= endX; currentX += 1) {
210
- const offset = (currentY * width + currentX) * 4;
211
- r += data[offset] ?? 0;
212
- g += data[offset + 1] ?? 0;
213
- b += data[offset + 2] ?? 0;
214
- a += data[offset + 3] ?? 0;
215
- sampleCount += 1;
216
- }
217
- }
218
- return {
219
- color: {
220
- r: Math.round(r / sampleCount),
221
- g: Math.round(g / sampleCount),
222
- b: Math.round(b / sampleCount),
223
- a: Math.round(a / sampleCount),
224
- },
225
- sampleCount,
226
- };
227
- }
228
- /**
229
- * 格式化取色结果
230
- * @param source 颜色来源
231
- * @param x 坐标 x
232
- * @param y 坐标 y
233
- * @param radius 采样半径
234
- * @param imageWidth 图片宽度
235
- * @param imageHeight 图片高度
236
- * @param color 颜色
237
- * @param sampleCount 采样像素数量
238
- * @returns 返回 MCP 文本
239
- * @example
240
- * formatPickedColorText("screen", 1, 1, 0, 100, 100, color, 1)
241
- */
242
- function formatPickedColorText(source, x, y, radius, imageWidth, imageHeight, color, sampleCount) {
243
- return [
244
- "取色成功",
245
- `source: ${source}`,
246
- `image: ${imageWidth}x${imageHeight}`,
247
- `point: x=${x}, y=${y}`,
248
- `radius: ${radius}`,
249
- `samplePixels: ${sampleCount}`,
250
- `hex: ${formatRgbHex(color)}`,
251
- `rgb: ${color.r},${color.g},${color.b}`,
252
- `rgba: ${color.r},${color.g},${color.b},${color.a}`,
253
- ].join("\n");
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
- }
413
- /**
414
- * 注册图片处理 MCP 工具
415
- * @param server MCP 服务实例
416
- * @returns 无返回值
417
- * @example
418
- * registerImageTools(server)
419
- */
420
- function registerImageTools(server) {
421
- server.registerTool("image_crop", {
422
- title: "Image Crop",
423
- description: "裁切本地图片并输出 PNG 文件,适合从截图中裁出找图模板。",
424
- inputSchema: {
425
- imagePath: z.string().min(1).describe("输入图片路径"),
426
- x: z.number().int().min(0).describe("裁切起点 x"),
427
- y: z.number().int().min(0).describe("裁切起点 y"),
428
- width: z.number().int().min(1).describe("裁切宽度"),
429
- height: z.number().int().min(1).describe("裁切高度"),
430
- outputPath: z
431
- .string()
432
- .min(1)
433
- .optional()
434
- .describe("可选输出路径;不传则在输入图片同目录生成 *-crop.png"),
435
- },
436
- }, async ({ imagePath, x, y, width, height, outputPath }) => {
437
- const inputPath = await resolveExistingImagePath(imagePath);
438
- const targetPath = resolveImageOutputPath(inputPath, outputPath, "crop");
439
- const metadata = await (0, sharp_1.default)(inputPath).metadata();
440
- const imageWidth = metadata.width ?? 0;
441
- const imageHeight = metadata.height ?? 0;
442
- if (x + width > imageWidth || y + height > imageHeight) {
443
- return (0, tool_utils_1.createTextToolResult)(`裁切范围超出图片尺寸: image=${imageWidth}x${imageHeight}, crop=${x},${y},${width},${height}`, true);
444
- }
445
- await fsExtra.ensureDir(path.dirname(targetPath));
446
- const info = await (0, sharp_1.default)(inputPath)
447
- .extract({ left: x, top: y, width, height })
448
- .png()
449
- .toFile(targetPath);
450
- return (0, tool_utils_1.createTextToolResult)([
451
- "图片裁切成功",
452
- `input: ${inputPath}`,
453
- `output: ${targetPath}`,
454
- `crop: x=${x}, y=${y}, width=${width}, height=${height}`,
455
- `size: ${info.size} bytes`,
456
- ].join("\n"));
457
- });
458
- server.registerTool("screen_crop", {
459
- title: "Screen Crop",
460
- description: "从当前默认设备截图中按坐标裁切 PNG 文件,适合自动截取找图模板。",
461
- inputSchema: {
462
- x: z.number().int().min(0).describe("裁切起点 x"),
463
- y: z.number().int().min(0).describe("裁切起点 y"),
464
- width: z.number().int().min(1).describe("裁切宽度"),
465
- height: z.number().int().min(1).describe("裁切高度"),
466
- outputPath: z
467
- .string()
468
- .min(1)
469
- .optional()
470
- .describe("可选输出路径;不传则写入系统临时目录"),
471
- },
472
- }, async ({ x, y, width, height, outputPath }) => {
473
- const target = await (0, tool_utils_1.resolveRuntimeHttpTarget)();
474
- const screenshot = await (0, project_1.getScreenshotOnDevice)((0, tool_utils_1.createRuntimeHttpRequestOptions)(target));
475
- const metadata = await (0, sharp_1.default)(screenshot).metadata();
476
- const imageWidth = metadata.width ?? 0;
477
- const imageHeight = metadata.height ?? 0;
478
- if (x + width > imageWidth || y + height > imageHeight) {
479
- return (0, tool_utils_1.createTextToolResult)(`裁切范围超出截图尺寸: image=${imageWidth}x${imageHeight}, crop=${x},${y},${width},${height}`, true);
480
- }
481
- const targetPath = resolveGeneratedImageOutputPath(outputPath, "ms-mcp-screen-crop", "png");
482
- await fsExtra.ensureDir(path.dirname(targetPath));
483
- const info = await (0, sharp_1.default)(screenshot)
484
- .extract({ left: x, top: y, width, height })
485
- .png()
486
- .toFile(targetPath);
487
- return (0, tool_utils_1.createTextToolResult)([
488
- "屏幕裁图成功",
489
- `device: ${target.label}`,
490
- `output: ${targetPath}`,
491
- `screen: ${imageWidth}x${imageHeight}`,
492
- `crop: x=${x}, y=${y}, width=${width}, height=${height}`,
493
- `size: ${info.size} bytes`,
494
- ].join("\n"));
495
- });
496
- server.registerTool("image_pick_color", {
497
- title: "Image Pick Color",
498
- description: "读取本地图片指定坐标颜色,返回 hex/rgb/rgba,适合生成找色代码。",
499
- inputSchema: {
500
- imagePath: z.string().min(1).describe("输入图片路径"),
501
- x: z.number().int().min(0).describe("目标坐标 x"),
502
- y: z.number().int().min(0).describe("目标坐标 y"),
503
- radius: z
504
- .number()
505
- .int()
506
- .min(0)
507
- .max(20)
508
- .optional()
509
- .default(0)
510
- .describe("采样半径,0 表示只取单点,默认 0"),
511
- },
512
- }, async ({ imagePath, x, y, radius }) => {
513
- const inputPath = await resolveExistingImagePath(imagePath);
514
- const image = await readRgbaImage(inputPath);
515
- const picked = pickAverageColor(image.data, image.width, image.height, x, y, radius);
516
- return (0, tool_utils_1.createTextToolResult)(formatPickedColorText(inputPath, x, y, radius, image.width, image.height, picked.color, picked.sampleCount));
517
- });
518
- server.registerTool("screen_pick_color", {
519
- title: "Screen Pick Color",
520
- description: "从当前默认设备截图中读取指定坐标颜色,返回 hex/rgb/rgba,适合自动生成找色代码。",
521
- inputSchema: {
522
- x: z.number().int().min(0).describe("目标坐标 x"),
523
- y: z.number().int().min(0).describe("目标坐标 y"),
524
- radius: z
525
- .number()
526
- .int()
527
- .min(0)
528
- .max(20)
529
- .optional()
530
- .default(0)
531
- .describe("采样半径,0 表示只取单点,默认 0"),
532
- },
533
- }, async ({ x, y, radius }) => {
534
- const target = await (0, tool_utils_1.resolveRuntimeHttpTarget)();
535
- const screenshot = await (0, project_1.getScreenshotOnDevice)((0, tool_utils_1.createRuntimeHttpRequestOptions)(target));
536
- const image = await readRgbaImage(screenshot);
537
- const picked = pickAverageColor(image.data, image.width, image.height, x, y, radius);
538
- return (0, tool_utils_1.createTextToolResult)(formatPickedColorText(target.label, x, y, radius, image.width, image.height, picked.color, picked.sampleCount));
539
- });
540
- server.registerTool("image_make_transparent", {
541
- title: "Image Make Transparent",
542
- description: "将图片中接近指定颜色或角落背景色的像素设为透明,并输出 PNG 文件,适合制作找图模板透明图。",
543
- inputSchema: {
544
- imagePath: z.string().min(1).describe("输入图片路径"),
545
- outputPath: z
546
- .string()
547
- .min(1)
548
- .optional()
549
- .describe("可选输出路径;不传则在输入图片同目录生成 *-transparent.png"),
550
- transparentColor: z
551
- .string()
552
- .min(1)
553
- .optional()
554
- .describe("要透明化的颜色,格式 #RRGGBB;不传则从指定角落采样"),
555
- sampleCorner: z
556
- .enum(["topLeft", "topRight", "bottomLeft", "bottomRight"])
557
- .optional()
558
- .default("topLeft")
559
- .describe("未传 transparentColor 时采样的背景角落,默认 topLeft"),
560
- tolerance: z
561
- .number()
562
- .int()
563
- .min(0)
564
- .max(441)
565
- .optional()
566
- .default(24)
567
- .describe("RGB 欧氏距离容差,默认 24,最大 441"),
568
- },
569
- }, async ({ imagePath, outputPath, transparentColor, sampleCorner, tolerance, }) => {
570
- const inputPath = await resolveExistingImagePath(imagePath);
571
- const targetPath = resolveImageOutputPath(inputPath, outputPath, "transparent");
572
- const { data, info } = await (0, sharp_1.default)(inputPath)
573
- .ensureAlpha()
574
- .raw()
575
- .toBuffer({ resolveWithObject: true });
576
- const targetColor = transparentColor
577
- ? parseHexColor(transparentColor)
578
- : sampleCornerColor(data, info.width, info.height, sampleCorner);
579
- let transparentPixelCount = 0;
580
- for (let offset = 0; offset < data.length; offset += 4) {
581
- if (isColorMatch(data, offset, targetColor, tolerance)) {
582
- data[offset + 3] = 0;
583
- transparentPixelCount += 1;
584
- }
585
- }
586
- await fsExtra.ensureDir(path.dirname(targetPath));
587
- const output = await (0, sharp_1.default)(data, {
588
- raw: {
589
- width: info.width,
590
- height: info.height,
591
- channels: 4,
592
- },
593
- })
594
- .png()
595
- .toFile(targetPath);
596
- return (0, tool_utils_1.createTextToolResult)([
597
- "透明图制作成功",
598
- `input: ${inputPath}`,
599
- `output: ${targetPath}`,
600
- `image: ${info.width}x${info.height}`,
601
- `transparentColor: ${formatRgbHex(targetColor)}`,
602
- `tolerance: ${tolerance}`,
603
- `transparentPixels: ${transparentPixelCount}`,
604
- `size: ${output.size} bytes`,
605
- ].join("\n"));
606
- });
607
- }
1
+ "use strict";var B=exports&&exports.__createBinding||(Object.create?(function(r,t,e,n){n===void 0&&(n=e);var i=Object.getOwnPropertyDescriptor(t,e);(!i||("get"in i?!t.__esModule:i.writable||i.configurable))&&(i={enumerable:!0,get:function(){return t[e]}}),Object.defineProperty(r,n,i)}):(function(r,t,e,n){n===void 0&&(n=e),r[n]=t[e]})),G=exports&&exports.__setModuleDefault||(Object.create?(function(r,t){Object.defineProperty(r,"default",{enumerable:!0,value:t})}):function(r,t){r.default=t}),v=exports&&exports.__importStar||(function(){var r=function(t){return r=Object.getOwnPropertyNames||function(e){var n=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(n[n.length]=i);return n},r(t)};return function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var n=r(t),i=0;i<n.length;i++)n[i]!=="default"&&B(e,t,n[i]);return G(e,t),e}})(),L=exports&&exports.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.cropLocalImage=H,exports.cropDeviceScreen=k,exports.pickLocalImageColor=E,exports.pickDeviceScreenColor=F,exports.makeLocalImageTransparent=N,exports.registerImageTools=W;const b=v(require("fs-extra")),q=v(require("os")),h=v(require("path")),d=L(require("sharp")),l=v(require("zod/v4")),C=require("../project"),f=require("./tool-utils");function R(r,t,e){if(t&&t.trim())return h.resolve(t.trim());const n=h.parse(h.resolve(r));return h.join(n.dir,`${n.name}-${e}.png`)}function D(r,t,e){return r&&r.trim()?h.resolve(r.trim()):h.join(q.tmpdir(),`${t}-${Date.now()}-${Math.random().toString(36).slice(2,8)}.${e}`)}async function _(r){const t=h.resolve(r.trim());if(!await b.pathExists(t))throw new Error(`\u56FE\u7247\u4E0D\u5B58\u5728: ${t}`);return t}function I(r){const t=r.trim().replace(/^#/,"");if(!/^[0-9a-fA-F]{6}$/.test(t))throw new Error("transparentColor \u5FC5\u987B\u662F #RRGGBB \u6216 RRGGBB \u683C\u5F0F\u3002");return{r:Number.parseInt(t.slice(0,2),16),g:Number.parseInt(t.slice(2,4),16),b:Number.parseInt(t.slice(4,6),16)}}function j(r){return`#${r.r.toString(16).padStart(2,"0")}${r.g.toString(16).padStart(2,"0")}${r.b.toString(16).padStart(2,"0")}`}function M(r,t,e,n){const i=n==="topRight"||n==="bottomRight"?t-1:0,a=((n==="bottomLeft"||n==="bottomRight"?e-1:0)*t+i)*4;return{r:r[a]??0,g:r[a+1]??0,b:r[a+2]??0}}function z(r,t,e,n){const i=(r[t]??0)-e.r,o=(r[t+1]??0)-e.g,a=(r[t+2]??0)-e.b;return Math.sqrt(i*i+o*o+a*a)<=n}async function T(r){const{data:t,info:e}=await(0,d.default)(r).ensureAlpha().raw().toBuffer({resolveWithObject:!0});return{data:t,width:e.width,height:e.height}}function S(r,t,e,n,i,o){if(n>=t||i>=e)throw new Error(`\u5750\u6807\u8D85\u51FA\u56FE\u7247\u8303\u56F4: image=${t}x${e}, point=${n},${i}`);const a=Math.max(0,n-o),s=Math.min(t-1,n+o),c=Math.max(0,i-o),u=Math.min(e-1,i+o);let p=0,m=0,g=0,$=0,w=0;for(let y=c;y<=u;y+=1)for(let O=a;O<=s;O+=1){const P=(y*t+O)*4;p+=r[P]??0,m+=r[P+1]??0,g+=r[P+2]??0,$+=r[P+3]??0,w+=1}return{color:{r:Math.round(p/w),g:Math.round(m/w),b:Math.round(g/w),a:Math.round($/w)},sampleCount:w}}function x(r,t,e,n,i,o,a,s){return["\u53D6\u8272\u6210\u529F",`source: ${r}`,`image: ${i}x${o}`,`point: x=${t}, y=${e}`,`radius: ${n}`,`samplePixels: ${s}`,`hex: ${j(a)}`,`rgb: ${a.r},${a.g},${a.b}`,`rgba: ${a.r},${a.g},${a.b},${a.a}`].join(`
2
+ `)}async function H(r,t,e,n,i,o){const a=await _(r),s=R(a,o,"crop"),c=await(0,d.default)(a).metadata(),u=c.width??0,p=c.height??0;if(t+n>u||e+i>p)throw new Error(`\u88C1\u5207\u8303\u56F4\u8D85\u51FA\u56FE\u7247\u5C3A\u5BF8: image=${u}x${p}, crop=${t},${e},${n},${i}`);await b.ensureDir(h.dirname(s));const m=await(0,d.default)(a).extract({left:t,top:e,width:n,height:i}).png().toFile(s);return["\u56FE\u7247\u88C1\u5207\u6210\u529F",`input: ${a}`,`output: ${s}`,`crop: x=${t}, y=${e}, width=${n}, height=${i}`,`size: ${m.size} bytes`].join(`
3
+ `)}async function k(r,t,e,n,i,o){const a=await(0,C.getScreenshotOnDevice)({ip:r.ip,port:r.port,transport:"http"}),s=await(0,d.default)(a).metadata(),c=s.width??0,u=s.height??0;if(t+n>c||e+i>u)throw new Error(`\u88C1\u5207\u8303\u56F4\u8D85\u51FA\u622A\u56FE\u5C3A\u5BF8: image=${c}x${u}, crop=${t},${e},${n},${i}`);const p=D(o,"ms-mcp-screen-crop","png");await b.ensureDir(h.dirname(p));const m=await(0,d.default)(a).extract({left:t,top:e,width:n,height:i}).png().toFile(p);return["\u5C4F\u5E55\u88C1\u56FE\u6210\u529F",`device: ${r.label}`,`output: ${p}`,`screen: ${c}x${u}`,`crop: x=${t}, y=${e}, width=${n}, height=${i}`,`size: ${m.size} bytes`].join(`
4
+ `)}async function E(r,t,e,n){const i=await _(r),o=await T(i),a=S(o.data,o.width,o.height,t,e,n);return x(i,t,e,n,o.width,o.height,a.color,a.sampleCount)}async function F(r,t,e,n){const i=await(0,C.getScreenshotOnDevice)({ip:r.ip,port:r.port,transport:"http"}),o=await T(i),a=S(o.data,o.width,o.height,t,e,n);return x(r.label,t,e,n,o.width,o.height,a.color,a.sampleCount)}async function N(r,t,e,n,i){const o=await _(r),a=R(o,t,"transparent"),{data:s,info:c}=await(0,d.default)(o).ensureAlpha().raw().toBuffer({resolveWithObject:!0}),u=e?I(e):M(s,c.width,c.height,n);let p=0;for(let g=0;g<s.length;g+=4)z(s,g,u,i)&&(s[g+3]=0,p+=1);await b.ensureDir(h.dirname(a));const m=await(0,d.default)(s,{raw:{width:c.width,height:c.height,channels:4}}).png().toFile(a);return["\u900F\u660E\u56FE\u5236\u4F5C\u6210\u529F",`input: ${o}`,`output: ${a}`,`image: ${c.width}x${c.height}`,`transparentColor: ${j(u)}`,`tolerance: ${i}`,`transparentPixels: ${p}`,`size: ${m.size} bytes`].join(`
5
+ `)}function W(r){r.registerTool("image_crop",{title:"Image Crop",description:"\u88C1\u5207\u672C\u5730\u56FE\u7247\u5E76\u8F93\u51FA PNG \u6587\u4EF6\uFF0C\u9002\u5408\u4ECE\u622A\u56FE\u4E2D\u88C1\u51FA\u627E\u56FE\u6A21\u677F\u3002",inputSchema:{imagePath:l.string().min(1).describe("\u8F93\u5165\u56FE\u7247\u8DEF\u5F84"),x:l.number().int().min(0).describe("\u88C1\u5207\u8D77\u70B9 x"),y:l.number().int().min(0).describe("\u88C1\u5207\u8D77\u70B9 y"),width:l.number().int().min(1).describe("\u88C1\u5207\u5BBD\u5EA6"),height:l.number().int().min(1).describe("\u88C1\u5207\u9AD8\u5EA6"),outputPath:l.string().min(1).optional().describe("\u53EF\u9009\u8F93\u51FA\u8DEF\u5F84\uFF1B\u4E0D\u4F20\u5219\u5728\u8F93\u5165\u56FE\u7247\u540C\u76EE\u5F55\u751F\u6210 *-crop.png")}},async({imagePath:t,x:e,y:n,width:i,height:o,outputPath:a})=>{const s=await _(t),c=R(s,a,"crop"),u=await(0,d.default)(s).metadata(),p=u.width??0,m=u.height??0;if(e+i>p||n+o>m)return(0,f.createTextToolResult)(`\u88C1\u5207\u8303\u56F4\u8D85\u51FA\u56FE\u7247\u5C3A\u5BF8: image=${p}x${m}, crop=${e},${n},${i},${o}`,!0);await b.ensureDir(h.dirname(c));const g=await(0,d.default)(s).extract({left:e,top:n,width:i,height:o}).png().toFile(c);return(0,f.createTextToolResult)(["\u56FE\u7247\u88C1\u5207\u6210\u529F",`input: ${s}`,`output: ${c}`,`crop: x=${e}, y=${n}, width=${i}, height=${o}`,`size: ${g.size} bytes`].join(`
6
+ `))}),r.registerTool("screen_crop",{title:"Screen Crop",description:"\u4ECE\u5F53\u524D\u9ED8\u8BA4\u8BBE\u5907\u622A\u56FE\u4E2D\u6309\u5750\u6807\u88C1\u5207 PNG \u6587\u4EF6\uFF0C\u9002\u5408\u81EA\u52A8\u622A\u53D6\u627E\u56FE\u6A21\u677F\u3002",inputSchema:{x:l.number().int().min(0).describe("\u88C1\u5207\u8D77\u70B9 x"),y:l.number().int().min(0).describe("\u88C1\u5207\u8D77\u70B9 y"),width:l.number().int().min(1).describe("\u88C1\u5207\u5BBD\u5EA6"),height:l.number().int().min(1).describe("\u88C1\u5207\u9AD8\u5EA6"),outputPath:l.string().min(1).optional().describe("\u53EF\u9009\u8F93\u51FA\u8DEF\u5F84\uFF1B\u4E0D\u4F20\u5219\u5199\u5165\u7CFB\u7EDF\u4E34\u65F6\u76EE\u5F55")}},async({x:t,y:e,width:n,height:i,outputPath:o})=>{const a=await(0,f.resolveRuntimeHttpTarget)(),s=await(0,C.getScreenshotOnDevice)((0,f.createRuntimeHttpRequestOptions)(a)),c=await(0,d.default)(s).metadata(),u=c.width??0,p=c.height??0;if(t+n>u||e+i>p)return(0,f.createTextToolResult)(`\u88C1\u5207\u8303\u56F4\u8D85\u51FA\u622A\u56FE\u5C3A\u5BF8: image=${u}x${p}, crop=${t},${e},${n},${i}`,!0);const m=D(o,"ms-mcp-screen-crop","png");await b.ensureDir(h.dirname(m));const g=await(0,d.default)(s).extract({left:t,top:e,width:n,height:i}).png().toFile(m);return(0,f.createTextToolResult)(["\u5C4F\u5E55\u88C1\u56FE\u6210\u529F",`device: ${a.label}`,`output: ${m}`,`screen: ${u}x${p}`,`crop: x=${t}, y=${e}, width=${n}, height=${i}`,`size: ${g.size} bytes`].join(`
7
+ `))}),r.registerTool("image_pick_color",{title:"Image Pick Color",description:"\u8BFB\u53D6\u672C\u5730\u56FE\u7247\u6307\u5B9A\u5750\u6807\u989C\u8272\uFF0C\u8FD4\u56DE hex/rgb/rgba\uFF0C\u9002\u5408\u751F\u6210\u627E\u8272\u4EE3\u7801\u3002",inputSchema:{imagePath:l.string().min(1).describe("\u8F93\u5165\u56FE\u7247\u8DEF\u5F84"),x:l.number().int().min(0).describe("\u76EE\u6807\u5750\u6807 x"),y:l.number().int().min(0).describe("\u76EE\u6807\u5750\u6807 y"),radius:l.number().int().min(0).max(20).optional().default(0).describe("\u91C7\u6837\u534A\u5F84\uFF0C0 \u8868\u793A\u53EA\u53D6\u5355\u70B9\uFF0C\u9ED8\u8BA4 0")}},async({imagePath:t,x:e,y:n,radius:i})=>{const o=await _(t),a=await T(o),s=S(a.data,a.width,a.height,e,n,i);return(0,f.createTextToolResult)(x(o,e,n,i,a.width,a.height,s.color,s.sampleCount))}),r.registerTool("screen_pick_color",{title:"Screen Pick Color",description:"\u4ECE\u5F53\u524D\u9ED8\u8BA4\u8BBE\u5907\u622A\u56FE\u4E2D\u8BFB\u53D6\u6307\u5B9A\u5750\u6807\u989C\u8272\uFF0C\u8FD4\u56DE hex/rgb/rgba\uFF0C\u9002\u5408\u81EA\u52A8\u751F\u6210\u627E\u8272\u4EE3\u7801\u3002",inputSchema:{x:l.number().int().min(0).describe("\u76EE\u6807\u5750\u6807 x"),y:l.number().int().min(0).describe("\u76EE\u6807\u5750\u6807 y"),radius:l.number().int().min(0).max(20).optional().default(0).describe("\u91C7\u6837\u534A\u5F84\uFF0C0 \u8868\u793A\u53EA\u53D6\u5355\u70B9\uFF0C\u9ED8\u8BA4 0")}},async({x:t,y:e,radius:n})=>{const i=await(0,f.resolveRuntimeHttpTarget)(),o=await(0,C.getScreenshotOnDevice)((0,f.createRuntimeHttpRequestOptions)(i)),a=await T(o),s=S(a.data,a.width,a.height,t,e,n);return(0,f.createTextToolResult)(x(i.label,t,e,n,a.width,a.height,s.color,s.sampleCount))}),r.registerTool("image_make_transparent",{title:"Image Make Transparent",description:"\u5C06\u56FE\u7247\u4E2D\u63A5\u8FD1\u6307\u5B9A\u989C\u8272\u6216\u89D2\u843D\u80CC\u666F\u8272\u7684\u50CF\u7D20\u8BBE\u4E3A\u900F\u660E\uFF0C\u5E76\u8F93\u51FA PNG \u6587\u4EF6\uFF0C\u9002\u5408\u5236\u4F5C\u627E\u56FE\u6A21\u677F\u900F\u660E\u56FE\u3002",inputSchema:{imagePath:l.string().min(1).describe("\u8F93\u5165\u56FE\u7247\u8DEF\u5F84"),outputPath:l.string().min(1).optional().describe("\u53EF\u9009\u8F93\u51FA\u8DEF\u5F84\uFF1B\u4E0D\u4F20\u5219\u5728\u8F93\u5165\u56FE\u7247\u540C\u76EE\u5F55\u751F\u6210 *-transparent.png"),transparentColor:l.string().min(1).optional().describe("\u8981\u900F\u660E\u5316\u7684\u989C\u8272\uFF0C\u683C\u5F0F #RRGGBB\uFF1B\u4E0D\u4F20\u5219\u4ECE\u6307\u5B9A\u89D2\u843D\u91C7\u6837"),sampleCorner:l.enum(["topLeft","topRight","bottomLeft","bottomRight"]).optional().default("topLeft").describe("\u672A\u4F20 transparentColor \u65F6\u91C7\u6837\u7684\u80CC\u666F\u89D2\u843D\uFF0C\u9ED8\u8BA4 topLeft"),tolerance:l.number().int().min(0).max(441).optional().default(24).describe("RGB \u6B27\u6C0F\u8DDD\u79BB\u5BB9\u5DEE\uFF0C\u9ED8\u8BA4 24\uFF0C\u6700\u5927 441")}},async({imagePath:t,outputPath:e,transparentColor:n,sampleCorner:i,tolerance:o})=>{const a=await _(t),s=R(a,e,"transparent"),{data:c,info:u}=await(0,d.default)(a).ensureAlpha().raw().toBuffer({resolveWithObject:!0}),p=n?I(n):M(c,u.width,u.height,i);let m=0;for(let $=0;$<c.length;$+=4)z(c,$,p,o)&&(c[$+3]=0,m+=1);await b.ensureDir(h.dirname(s));const g=await(0,d.default)(c,{raw:{width:u.width,height:u.height,channels:4}}).png().toFile(s);return(0,f.createTextToolResult)(["\u900F\u660E\u56FE\u5236\u4F5C\u6210\u529F",`input: ${a}`,`output: ${s}`,`image: ${u.width}x${u.height}`,`transparentColor: ${j(p)}`,`tolerance: ${o}`,`transparentPixels: ${m}`,`size: ${g.size} bytes`].join(`
8
+ `))})}
@@ -2,9 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  export type OcrEngine = "appleocr" | "paddleocr";
3
3
  export type OcrMode = "recognize" | "numbers" | "findText";
4
4
  export type PaddleOcrModel = "ppocr-v6-tiny" | "ppocr-v6-small" | "ppocr-v5";
5
- /** PaddleOCR 内置可切换模型 ID;同一时间只保留一个 active 模型。 */
6
5
  export declare const PADDLE_OCR_MODELS: readonly ["ppocr-v6-tiny", "ppocr-v6-small", "ppocr-v5"];
7
- /** PaddleOCR 检测阶段默认最大边长,与运行时一致;传 0 或负数时回落到该值。 */
8
6
  export declare const PADDLE_OCR_DEFAULT_MAX_SIDE_LEN = 768;
9
7
  export type ScriptResultResponse = {
10
8
  success?: boolean;
@@ -36,32 +34,9 @@ export type OcrRecognitionResult = {
36
34
  text: string;
37
35
  };
38
36
  export declare const APPLE_OCR_LANGUAGES: readonly ["en-US", "fr-FR", "it-IT", "de-DE", "es-ES", "pt-BR", "zh-Hans", "zh-Hant"];
39
- /**
40
- * 格式化 OCR 工具结果
41
- * @param response runScript 响应
42
- * @param outputPath 可选输出文件路径
43
- * @returns 返回 MCP 文本内容
44
- * @example
45
- * await formatOcrToolText({ success: true, result: [] })
46
- */
47
37
  export declare function formatOcrToolText(response: ScriptResultResponse, outputPath?: string): Promise<string>;
48
- /**
49
- * 在指定 HTTP 设备上执行 OCR 识别
50
- * @param target 设备 HTTP 地址
51
- * @param options OCR 参数
52
- * @returns 返回 HTTP 状态码、原始结果与格式化文本
53
- * @example
54
- * await runOcrRecognitionOnDevice({ ip: "192.168.1.10", port: "9800" }, options)
55
- */
56
38
  export declare function runOcrRecognitionOnDevice(target: {
57
39
  ip: string;
58
40
  port: string;
59
41
  }, options: OcrRecognitionOptions): Promise<OcrRecognitionResult>;
60
- /**
61
- * 注册 OCR MCP 工具
62
- * @param server MCP 服务实例
63
- * @returns 无返回值
64
- * @example
65
- * registerOcrTools(server)
66
- */
67
42
  export declare function registerOcrTools(server: McpServer): void;