weixin-devtools-mcp 0.0.1

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.
@@ -0,0 +1,150 @@
1
+ /**
2
+ * 页面查询和等待工具
3
+ * 提供类似浏览器的$选择器和waitFor等待功能
4
+ */
5
+ import { z } from 'zod';
6
+ import { defineTool } from './ToolDefinition.js';
7
+ import { queryElements, waitForCondition } from '../tools.js';
8
+ /**
9
+ * $ 选择器工具 - 通过CSS选择器查找页面元素
10
+ */
11
+ export const querySelectorTool = defineTool({
12
+ name: '$',
13
+ description: '通过CSS选择器查找页面元素,返回匹配元素的详细信息',
14
+ schema: z.object({
15
+ selector: z.string().min(1, '选择器不能为空').describe('CSS选择器,如:view.container、#myId、.myClass、text=按钮'),
16
+ }),
17
+ annotations: {
18
+ audience: ['developers'],
19
+ },
20
+ handler: async (request, response, context) => {
21
+ const { selector } = request.params;
22
+ // 验证选择器
23
+ if (!selector || typeof selector !== 'string' || selector.trim() === '') {
24
+ throw new Error('选择器不能为空');
25
+ }
26
+ if (!context.currentPage) {
27
+ throw new Error('请先获取当前页面');
28
+ }
29
+ try {
30
+ const options = { selector };
31
+ const results = await queryElements(context.currentPage, context.elementMap, options);
32
+ if (results.length === 0) {
33
+ response.appendResponseLine(`未找到匹配选择器 "${selector}" 的元素`);
34
+ return;
35
+ }
36
+ response.appendResponseLine(`找到 ${results.length} 个匹配元素:`);
37
+ response.appendResponseLine('');
38
+ for (let i = 0; i < results.length; i++) {
39
+ const element = results[i];
40
+ response.appendResponseLine(`[${i + 1}] ${element.tagName} (uid: ${element.uid})`);
41
+ if (element.text) {
42
+ response.appendResponseLine(` 文本: ${element.text}`);
43
+ }
44
+ if (element.attributes) {
45
+ const attrs = Object.entries(element.attributes)
46
+ .map(([key, value]) => `${key}="${value}"`)
47
+ .join(' ');
48
+ response.appendResponseLine(` 属性: ${attrs}`);
49
+ }
50
+ if (element.position) {
51
+ const { left, top, width, height } = element.position;
52
+ response.appendResponseLine(` 位置: (${left}, ${top}) 大小: ${width}x${height}`);
53
+ }
54
+ response.appendResponseLine('');
55
+ }
56
+ // 查询可能会发现新元素,包含快照信息
57
+ response.setIncludeSnapshot(true);
58
+ }
59
+ catch (error) {
60
+ const errorMessage = error instanceof Error ? error.message : String(error);
61
+ response.appendResponseLine(`查询元素失败: ${errorMessage}`);
62
+ throw error;
63
+ }
64
+ },
65
+ });
66
+ /**
67
+ * waitFor 等待工具 - 等待条件满足
68
+ */
69
+ export const waitForTool = defineTool({
70
+ name: 'waitFor',
71
+ description: '等待条件满足,支持等待元素出现、消失、文本匹配等',
72
+ schema: z.object({
73
+ // 支持三种模式:
74
+ // 1. 时间等待: { delay: 1000 }
75
+ // 2. 选择器等待: { selector: ".button" }
76
+ // 3. 复杂条件: { selector: ".button", text: "提交", timeout: 5000 }
77
+ delay: z.number().optional().describe('等待指定毫秒数(时间等待模式)'),
78
+ selector: z.string().optional().describe('等待元素选择器(选择器等待模式)'),
79
+ timeout: z.number().optional().default(5000).describe('超时时间(毫秒),默认5000ms'),
80
+ text: z.string().optional().describe('等待元素包含指定文本'),
81
+ visible: z.boolean().optional().describe('等待元素可见状态,true为可见,false为隐藏'),
82
+ disappear: z.boolean().optional().default(false).describe('等待元素消失,默认false'),
83
+ }).refine((data) => data.delay !== undefined || data.selector !== undefined, { message: '必须提供 delay(时间等待)或 selector(选择器等待)' }),
84
+ annotations: {
85
+ audience: ['developers'],
86
+ },
87
+ handler: async (request, response, context) => {
88
+ const options = request.params;
89
+ if (!context.currentPage) {
90
+ throw new Error('请先获取当前页面');
91
+ }
92
+ try {
93
+ const startTime = Date.now();
94
+ // 构建等待描述信息和实际等待参数
95
+ let waitDescription = '';
96
+ let waitParam;
97
+ if (options.delay !== undefined) {
98
+ // 时间等待模式
99
+ waitDescription = `等待 ${options.delay}ms`;
100
+ waitParam = options.delay;
101
+ }
102
+ else if (options.selector) {
103
+ // 选择器等待模式
104
+ const parts = [];
105
+ parts.push(`选择器 "${options.selector}"`);
106
+ if (options.disappear)
107
+ parts.push('消失');
108
+ else
109
+ parts.push('出现');
110
+ if (options.text)
111
+ parts.push(`包含文本 "${options.text}"`);
112
+ if (options.visible !== undefined) {
113
+ parts.push(options.visible ? '可见' : '隐藏');
114
+ }
115
+ waitDescription = `等待 ${parts.join(' 且 ')}`;
116
+ if (options.timeout) {
117
+ waitDescription += ` (超时: ${options.timeout}ms)`;
118
+ }
119
+ // 构建 WaitForOptions 参数
120
+ waitParam = {
121
+ selector: options.selector,
122
+ timeout: options.timeout,
123
+ ...(options.text && { text: options.text }),
124
+ ...(options.visible !== undefined && { visible: options.visible }),
125
+ ...(options.disappear !== undefined && { disappear: options.disappear }),
126
+ };
127
+ }
128
+ else {
129
+ throw new Error('必须提供 delay 或 selector 参数');
130
+ }
131
+ response.appendResponseLine(`开始 ${waitDescription}...`);
132
+ const result = await waitForCondition(context.currentPage, waitParam);
133
+ const endTime = Date.now();
134
+ const duration = endTime - startTime;
135
+ if (result) {
136
+ response.appendResponseLine(`等待成功,耗时 ${duration}ms`);
137
+ // 等待完成后,页面可能发生变化
138
+ response.setIncludeSnapshot(true);
139
+ }
140
+ else {
141
+ response.appendResponseLine(`等待失败,耗时 ${duration}ms`);
142
+ }
143
+ }
144
+ catch (error) {
145
+ const errorMessage = error instanceof Error ? error.message : String(error);
146
+ response.appendResponseLine(`等待失败: ${errorMessage}`);
147
+ throw error;
148
+ }
149
+ },
150
+ });
@@ -0,0 +1,47 @@
1
+ /**
2
+ * 截图工具
3
+ * 负责页面截图功能,支持保存到文件或返回base64数据
4
+ */
5
+ import { z } from 'zod';
6
+ import { defineTool } from './ToolDefinition.js';
7
+ import { takeScreenshot } from '../tools.js';
8
+ /**
9
+ * 页面截图
10
+ */
11
+ export const screenshotTool = defineTool({
12
+ name: 'screenshot',
13
+ description: '对当前页面截图,支持返回base64数据或保存到文件',
14
+ schema: z.object({
15
+ path: z.string().optional().describe('图片保存路径(可选),如果不提供则返回base64编码的图片数据'),
16
+ }),
17
+ annotations: {
18
+ audience: ['developers'],
19
+ },
20
+ handler: async (request, response, context) => {
21
+ if (!context.miniProgram) {
22
+ throw new Error('请先连接到微信开发者工具');
23
+ }
24
+ const { path } = request.params;
25
+ try {
26
+ const options = {};
27
+ if (path)
28
+ options.path = path;
29
+ const result = await takeScreenshot(context.miniProgram, options);
30
+ if (path) {
31
+ response.appendResponseLine(`截图已保存到: ${path}`);
32
+ }
33
+ else if (result) {
34
+ response.appendResponseLine(`截图获取成功`);
35
+ response.appendResponseLine(`Base64数据长度: ${result.length} 字符`);
36
+ response.appendResponseLine(`格式: ${result.startsWith('data:image') ? 'data URL' : 'base64'}`);
37
+ // 附加图片到响应
38
+ response.attachImage(result, 'image/png');
39
+ }
40
+ }
41
+ catch (error) {
42
+ const errorMessage = error instanceof Error ? error.message : String(error);
43
+ response.appendResponseLine(`截图失败: ${errorMessage}`);
44
+ throw error;
45
+ }
46
+ },
47
+ });
@@ -0,0 +1,45 @@
1
+ /**
2
+ * 页面快照工具
3
+ * 负责获取页面元素快照和UID映射
4
+ */
5
+ import { z } from 'zod';
6
+ import { defineTool } from './ToolDefinition.js';
7
+ import { getPageSnapshot } from '../tools.js';
8
+ /**
9
+ * 获取页面快照
10
+ */
11
+ export const getPageSnapshotTool = defineTool({
12
+ name: 'get_page_snapshot',
13
+ description: '获取当前页面的元素快照,包含所有元素的uid信息',
14
+ schema: z.object({}),
15
+ annotations: {
16
+ audience: ['developers'],
17
+ },
18
+ handler: async (request, response, context) => {
19
+ if (!context.currentPage) {
20
+ throw new Error('请先获取当前页面');
21
+ }
22
+ try {
23
+ // 清空之前的元素映射
24
+ context.elementMap.clear();
25
+ // 获取页面快照
26
+ const { snapshot, elementMap } = await getPageSnapshot(context.currentPage);
27
+ // 更新上下文中的元素映射
28
+ elementMap.forEach((value, key) => {
29
+ context.elementMap.set(key, value);
30
+ });
31
+ response.appendResponseLine(`页面快照获取成功`);
32
+ response.appendResponseLine(`页面路径: ${snapshot.path}`);
33
+ response.appendResponseLine(`元素数量: ${snapshot.elements.length}`);
34
+ response.appendResponseLine('');
35
+ response.appendResponseLine(JSON.stringify(snapshot, null, 2));
36
+ // 设置包含快照信息
37
+ response.setIncludeSnapshot(true);
38
+ }
39
+ catch (error) {
40
+ const errorMessage = error instanceof Error ? error.message : String(error);
41
+ response.appendResponseLine(`获取页面快照失败: ${errorMessage}`);
42
+ throw error;
43
+ }
44
+ },
45
+ });