wechat-dev-mcp 1.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.
- package/README.md +94 -0
- package/README_zh-CN.md +94 -0
- package/index.js +368 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# WeChat Developer Tools MCP Server
|
|
2
|
+
|
|
3
|
+
[中文](README_zh-CN.md)
|
|
4
|
+
|
|
5
|
+
This is a Model Context Protocol (MCP) server that connects to WeChat Developer Tools via `miniprogram-automator`. It allows you to control the IDE and the mini-program from an MCP client (like Claude Desktop or an AI agent).
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
1. **Node.js**: Version 18+ is recommended (though it may work on older versions with some polyfills, this project is set up for modern Node).
|
|
10
|
+
2. **WeChat Developer Tools**: Must be installed and running.
|
|
11
|
+
3. **Enable Automation**: In WeChat Developer Tools, go to **Settings -> Security Settings** and enable **Service Port** (CLI/HTTP invocation).
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### Using with Claude Desktop (Recommended)
|
|
16
|
+
|
|
17
|
+
Add the following to your `claude_desktop_config.json` (e.g., `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"wechat-devtools": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": [
|
|
25
|
+
"-y",
|
|
26
|
+
"wechat-dev-mcp"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Manual Installation
|
|
34
|
+
|
|
35
|
+
To install globally:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install -g wechat-dev-mcp
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Then configure:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"wechat-devtools": {
|
|
47
|
+
"command": "wechat-dev-mcp",
|
|
48
|
+
"args": []
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Local Development
|
|
55
|
+
|
|
56
|
+
1. Clone the repository
|
|
57
|
+
2. Install dependencies:
|
|
58
|
+
```bash
|
|
59
|
+
npm install
|
|
60
|
+
```
|
|
61
|
+
3. Build and run locally:
|
|
62
|
+
```bash
|
|
63
|
+
node index.js
|
|
64
|
+
```
|
|
65
|
+
4. Configure Claude Desktop to point to your local file:
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"mcpServers": {
|
|
69
|
+
"wechat-devtools": {
|
|
70
|
+
"command": "node",
|
|
71
|
+
"args": ["/absolute/path/to/wechat-dev-mcp/index.js"]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Available Tools
|
|
78
|
+
|
|
79
|
+
* **`launch`**: Launch and connect to a mini-program project.
|
|
80
|
+
* `projectPath`: Absolute path to the project.
|
|
81
|
+
* `cliPath`: (Optional) Path to the DevTools CLI.
|
|
82
|
+
* **`connect`**: Connect to an already running DevTools instance.
|
|
83
|
+
* `wsEndpoint`: WebSocket endpoint (e.g., `ws://localhost:9420`).
|
|
84
|
+
* **`navigate_to`**: Navigate to a page (e.g., `/pages/index/index`).
|
|
85
|
+
* **`get_page_data`**: Get data from the current page.
|
|
86
|
+
* **`set_page_data`**: Set data on the current page.
|
|
87
|
+
* **`get_element`**: Get text, attributes, wxml of an element or tap it.
|
|
88
|
+
* **`call_method`**: Call a method on the current page instance.
|
|
89
|
+
* **`disconnect`**: Disconnect automation.
|
|
90
|
+
|
|
91
|
+
## Troubleshooting
|
|
92
|
+
|
|
93
|
+
* **Connection Refused**: Ensure WeChat Developer Tools is running and the Service Port is enabled in Settings.
|
|
94
|
+
* **Path Issues**: Use absolute paths for `projectPath`.
|
package/README_zh-CN.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# WeChat Developer Tools MCP Server
|
|
2
|
+
|
|
3
|
+
[English](README.md)
|
|
4
|
+
|
|
5
|
+
这是一个连接微信开发者工具(WeChat Developer Tools)的 Model Context Protocol (MCP) 服务器,基于 `miniprogram-automator` 实现。它允许你通过 MCP 客户端(如 Claude Desktop 或 AI Agent)控制 IDE 和小程序。
|
|
6
|
+
|
|
7
|
+
## 前置条件
|
|
8
|
+
|
|
9
|
+
1. **Node.js**: 建议使用版本 18+(尽管可能在旧版本上也能运行,但本项目是针对现代 Node 环境设置的)。
|
|
10
|
+
2. **微信开发者工具**: 必须已安装并运行。
|
|
11
|
+
3. **开启自动化**: 在微信开发者工具中,进入 **设置 -> 安全设置**,开启 **服务端口**(CLI/HTTP 调用)。
|
|
12
|
+
|
|
13
|
+
## 快速开始
|
|
14
|
+
|
|
15
|
+
### 在 Claude Desktop 中使用(推荐)
|
|
16
|
+
|
|
17
|
+
将以下内容添加到你的 `claude_desktop_config.json`(例如 macOS 上的 `~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"wechat-devtools": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": [
|
|
25
|
+
"-y",
|
|
26
|
+
"wechat-dev-mcp"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 手动安装
|
|
34
|
+
|
|
35
|
+
全局安装:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install -g wechat-dev-mcp
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
配置:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"wechat-devtools": {
|
|
47
|
+
"command": "wechat-dev-mcp",
|
|
48
|
+
"args": []
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 本地开发
|
|
55
|
+
|
|
56
|
+
1. 克隆仓库
|
|
57
|
+
2. 安装依赖:
|
|
58
|
+
```bash
|
|
59
|
+
npm install
|
|
60
|
+
```
|
|
61
|
+
3. 本地运行:
|
|
62
|
+
```bash
|
|
63
|
+
node index.js
|
|
64
|
+
```
|
|
65
|
+
4. 配置 Claude Desktop 指向本地文件:
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"mcpServers": {
|
|
69
|
+
"wechat-devtools": {
|
|
70
|
+
"command": "node",
|
|
71
|
+
"args": ["/absolute/path/to/wechat-dev-mcp/index.js"]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 可用工具
|
|
78
|
+
|
|
79
|
+
* **`launch`**: 启动并连接到一个小程序项目。
|
|
80
|
+
* `projectPath`: 项目的绝对路径。
|
|
81
|
+
* `cliPath`: (可选)开发者工具 CLI 的路径。
|
|
82
|
+
* **`connect`**: 连接到一个已经在运行的开发者工具实例。
|
|
83
|
+
* `wsEndpoint`: WebSocket 端点(例如 `ws://localhost:9420`)。
|
|
84
|
+
* **`navigate_to`**: 跳转到指定页面(例如 `/pages/index/index`)。
|
|
85
|
+
* **`get_page_data`**: 获取当前页面的数据。
|
|
86
|
+
* **`set_page_data`**: 设置当前页面的数据。
|
|
87
|
+
* **`get_element`**: 获取元素的文本、属性、wxml 或点击元素。
|
|
88
|
+
* **`call_method`**: 调用当前页面实例的方法。
|
|
89
|
+
* **`disconnect`**: 断开自动化连接。
|
|
90
|
+
|
|
91
|
+
## 常见问题排查
|
|
92
|
+
|
|
93
|
+
* **连接被拒绝 (Connection Refused)**: 确保微信开发者工具正在运行,并且在设置中开启了服务端口。
|
|
94
|
+
* **路径问题**: `projectPath` 请务必使用绝对路径。
|
package/index.js
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// 1. Monkey patch console.log to ensure library logs don't corrupt JSON-RPC on stdout
|
|
4
|
+
const originalLog = console.log;
|
|
5
|
+
console.log = function(...args) {
|
|
6
|
+
console.error(...args);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import {
|
|
12
|
+
CallToolRequestSchema,
|
|
13
|
+
ListToolsRequestSchema,
|
|
14
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
15
|
+
import automator from "miniprogram-automator";
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
|
|
18
|
+
// Global state
|
|
19
|
+
let miniProgram = null;
|
|
20
|
+
const DEFAULT_PORT = process.env.WECHAT_PORT || 9420;
|
|
21
|
+
const consoleLogs = []; // Ring buffer for logs
|
|
22
|
+
|
|
23
|
+
function setupListeners(mp) {
|
|
24
|
+
// Clear old logs on new connection
|
|
25
|
+
consoleLogs.length = 0;
|
|
26
|
+
|
|
27
|
+
// Listen for console logs
|
|
28
|
+
mp.on('console', msg => {
|
|
29
|
+
// Keep last 50 logs
|
|
30
|
+
if (consoleLogs.length >= 50) {
|
|
31
|
+
consoleLogs.shift();
|
|
32
|
+
}
|
|
33
|
+
consoleLogs.push({
|
|
34
|
+
type: 'console',
|
|
35
|
+
level: msg.level,
|
|
36
|
+
text: msg.text, // automator msg.text might be a promise or string depending on version, usually string in newer
|
|
37
|
+
args: msg.args, // raw args
|
|
38
|
+
timestamp: Date.now()
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Listen for exceptions
|
|
43
|
+
mp.on('exception', err => {
|
|
44
|
+
if (consoleLogs.length >= 50) {
|
|
45
|
+
consoleLogs.shift();
|
|
46
|
+
}
|
|
47
|
+
consoleLogs.push({
|
|
48
|
+
type: 'exception',
|
|
49
|
+
level: 'error',
|
|
50
|
+
text: err.message || JSON.stringify(err),
|
|
51
|
+
timestamp: Date.now()
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Tool definitions
|
|
57
|
+
const TOOLS = {
|
|
58
|
+
LAUNCH: "launch",
|
|
59
|
+
CONNECT: "connect",
|
|
60
|
+
CHECK_HEALTH: "check_health",
|
|
61
|
+
NAVIGATE_TO: "navigate_to",
|
|
62
|
+
GET_PAGE_DATA: "get_page_data",
|
|
63
|
+
SET_PAGE_DATA: "set_page_data",
|
|
64
|
+
GET_ELEMENT: "get_element",
|
|
65
|
+
CALL_METHOD: "call_method",
|
|
66
|
+
DISCONNECT: "disconnect"
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const server = new Server(
|
|
70
|
+
{
|
|
71
|
+
name: "wechat-devtools-mcp",
|
|
72
|
+
version: "1.0.0",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
capabilities: {
|
|
76
|
+
tools: {},
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
82
|
+
return {
|
|
83
|
+
tools: [
|
|
84
|
+
{
|
|
85
|
+
name: TOOLS.LAUNCH,
|
|
86
|
+
description: "Launch and connect to WeChat Developer Tools. [REQUIRED INITIAL STEP] Use this tool first to start controlling a mini-program. Requires the absolute project path.",
|
|
87
|
+
inputSchema: zodToJsonSchema(
|
|
88
|
+
z.object({
|
|
89
|
+
projectPath: z.string().describe("Absolute path to the mini-program project"),
|
|
90
|
+
cliPath: z.string().optional().describe("Path to the WeChat DevTools CLI executable (optional, will try to auto-detect)"),
|
|
91
|
+
port: z.number().optional().describe("Port for automation (optional)"),
|
|
92
|
+
})
|
|
93
|
+
),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: TOOLS.CONNECT,
|
|
97
|
+
description: "Connect to an already running WeChat Developer Tools instance via WebSocket. Use this if 'launch' fails or you want to attach to an existing session.",
|
|
98
|
+
inputSchema: zodToJsonSchema(
|
|
99
|
+
z.object({
|
|
100
|
+
wsEndpoint: z.string().optional().describe(`WebSocket endpoint (e.g., ws://localhost:9420). Defaults to ws://localhost:${DEFAULT_PORT}`),
|
|
101
|
+
})
|
|
102
|
+
),
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: TOOLS.CHECK_HEALTH,
|
|
106
|
+
description: "[CRITICAL] Use this tool to verify if the mini-program is running correctly after ANY code changes. It returns the current page path, network status, and recent console errors.",
|
|
107
|
+
inputSchema: zodToJsonSchema(z.object({})),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: TOOLS.NAVIGATE_TO,
|
|
111
|
+
description: "Navigate to a specific page in the mini-program.",
|
|
112
|
+
inputSchema: zodToJsonSchema(
|
|
113
|
+
z.object({
|
|
114
|
+
url: z.string().describe("The URL of the page to navigate to (e.g., /pages/index/index)"),
|
|
115
|
+
})
|
|
116
|
+
),
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: TOOLS.GET_PAGE_DATA,
|
|
120
|
+
description: "Get the data of the current page. Useful for verifying state changes after interactions or API calls.",
|
|
121
|
+
inputSchema: zodToJsonSchema(
|
|
122
|
+
z.object({
|
|
123
|
+
path: z.string().optional().describe("The data path to retrieve (optional, returns full data if omitted)"),
|
|
124
|
+
})
|
|
125
|
+
),
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: TOOLS.SET_PAGE_DATA,
|
|
129
|
+
description: "Set data on the current page. Use this to mock state or trigger UI updates for testing.",
|
|
130
|
+
inputSchema: zodToJsonSchema(
|
|
131
|
+
z.object({
|
|
132
|
+
data: z.record(z.any()).describe("The data object to set"),
|
|
133
|
+
})
|
|
134
|
+
),
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: TOOLS.GET_ELEMENT,
|
|
138
|
+
description: "Get information about an element (text, wxml, attributes, computed style) or interact with it (tap, input, trigger). Use this to inspect UI or perform actions.",
|
|
139
|
+
inputSchema: zodToJsonSchema(
|
|
140
|
+
z.object({
|
|
141
|
+
selector: z.string().describe("The CSS selector of the element"),
|
|
142
|
+
action: z.enum(["text", "wxml", "outerWxml", "attribute", "style", "tap", "input", "trigger"]).optional().default("text").describe("Action to perform: 'text' (content), 'wxml' (structure), 'attribute' (get attr), 'style' (get style), 'tap' (click), 'input' (enter text), 'trigger' (custom event)"),
|
|
143
|
+
attributeName: z.string().optional().describe("Attribute name (required if action is 'attribute')"),
|
|
144
|
+
styleName: z.string().optional().describe("Style name (required if action is 'style')"),
|
|
145
|
+
value: z.string().optional().describe("Value to input (required if action is 'input')"),
|
|
146
|
+
eventName: z.string().optional().describe("Event name to trigger (required if action is 'trigger')"),
|
|
147
|
+
detail: z.record(z.any()).optional().describe("Event detail object (optional for 'trigger')"),
|
|
148
|
+
})
|
|
149
|
+
),
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: TOOLS.CALL_METHOD,
|
|
153
|
+
description: "Call a method on the current page.",
|
|
154
|
+
inputSchema: zodToJsonSchema(
|
|
155
|
+
z.object({
|
|
156
|
+
method: z.string().describe("The name of the method to call"),
|
|
157
|
+
args: z.array(z.union([z.string(), z.number(), z.boolean(), z.object({}).passthrough()])).optional().default([]).describe("Arguments to pass to the method"),
|
|
158
|
+
})
|
|
159
|
+
),
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: TOOLS.DISCONNECT,
|
|
163
|
+
description: "Disconnect from the mini-program.",
|
|
164
|
+
inputSchema: zodToJsonSchema(z.object({})),
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
171
|
+
try {
|
|
172
|
+
const { name, arguments: args } = request.params;
|
|
173
|
+
|
|
174
|
+
switch (name) {
|
|
175
|
+
case TOOLS.LAUNCH: {
|
|
176
|
+
if (miniProgram) {
|
|
177
|
+
return { content: [{ type: "text", text: "Already connected to a Mini Program instance. Disconnect first." }] };
|
|
178
|
+
}
|
|
179
|
+
const { projectPath, cliPath, port } = args;
|
|
180
|
+
const options = { projectPath, cliPath, port };
|
|
181
|
+
// Remove undefined keys
|
|
182
|
+
Object.keys(options).forEach(key => options[key] === undefined && delete options[key]);
|
|
183
|
+
|
|
184
|
+
// Try to connect to existing instance first
|
|
185
|
+
const tryPort = port || DEFAULT_PORT;
|
|
186
|
+
try {
|
|
187
|
+
// Attempt connection (short timeout might be good but connect usually fails fast if port closed)
|
|
188
|
+
miniProgram = await automator.connect({ wsEndpoint: `ws://localhost:${tryPort}` });
|
|
189
|
+
setupListeners(miniProgram);
|
|
190
|
+
return { content: [{ type: "text", text: `Connected to existing Mini Program instance on port ${tryPort}.` }] };
|
|
191
|
+
} catch (e) {
|
|
192
|
+
// If connection fails, launch new instance
|
|
193
|
+
// console.error("Connect failed, launching new instance:", e);
|
|
194
|
+
miniProgram = await automator.launch(options);
|
|
195
|
+
setupListeners(miniProgram);
|
|
196
|
+
return { content: [{ type: "text", text: "Successfully launched and connected to Mini Program." }] };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
case TOOLS.CONNECT: {
|
|
201
|
+
if (miniProgram) {
|
|
202
|
+
return { content: [{ type: "text", text: "Already connected to a Mini Program instance. Disconnect first." }] };
|
|
203
|
+
}
|
|
204
|
+
let { wsEndpoint } = args;
|
|
205
|
+
if (!wsEndpoint) {
|
|
206
|
+
wsEndpoint = `ws://localhost:${DEFAULT_PORT}`;
|
|
207
|
+
}
|
|
208
|
+
miniProgram = await automator.connect({ wsEndpoint });
|
|
209
|
+
setupListeners(miniProgram);
|
|
210
|
+
return { content: [{ type: "text", text: "Successfully connected to Mini Program." }] };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
case TOOLS.CHECK_HEALTH: {
|
|
214
|
+
if (!miniProgram) {
|
|
215
|
+
return { content: [{ type: "text", text: JSON.stringify({ connected: false, error: "Not connected" }) }] };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 1. Get Page Path
|
|
219
|
+
let pagePath = "unknown";
|
|
220
|
+
try {
|
|
221
|
+
const page = await miniProgram.currentPage();
|
|
222
|
+
pagePath = page ? page.path : "no_page_found";
|
|
223
|
+
} catch (e) {
|
|
224
|
+
pagePath = `error_getting_path: ${e.message}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 2. Get Recent Errors (Console Error or Exceptions)
|
|
228
|
+
// Filter logs where level is 'error' or type is 'exception'
|
|
229
|
+
const recentErrors = consoleLogs
|
|
230
|
+
.filter(log => log.level === 'error' || log.type === 'exception')
|
|
231
|
+
.slice(-5) // Get last 5
|
|
232
|
+
.map(log => `[${new Date(log.timestamp).toISOString().split('T')[1].slice(0,8)}] ${log.text}`);
|
|
233
|
+
|
|
234
|
+
// 3. Network Status (Mock / System Info)
|
|
235
|
+
let networkType = "unknown";
|
|
236
|
+
try {
|
|
237
|
+
const res = await miniProgram.systemInfo();
|
|
238
|
+
// systemInfo in automator might not directly have networkType?
|
|
239
|
+
// Actually miniProgram.systemInfo() returns what wx.getSystemInfo returns.
|
|
240
|
+
// But networkType is usually in wx.getNetworkType.
|
|
241
|
+
// Let's try to call wx.getNetworkType via remote evaluation if possible,
|
|
242
|
+
// but miniProgram.evaluate or callWxMethod might be needed.
|
|
243
|
+
// Automator has callWxMethod? No, page has callMethod (for internal methods).
|
|
244
|
+
// miniProgram has `remote`? No.
|
|
245
|
+
// Actually miniProgram.evaluate() runs code in the app service context.
|
|
246
|
+
const netRes = await miniProgram.evaluate(() => new Promise(resolve => wx.getNetworkType({ success: resolve, fail: () => resolve({ networkType: 'fail' }) })));
|
|
247
|
+
if (netRes) networkType = netRes.networkType;
|
|
248
|
+
} catch (e) {
|
|
249
|
+
networkType = `check_failed: ${e.message}`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
content: [{
|
|
254
|
+
type: "text",
|
|
255
|
+
text: JSON.stringify({
|
|
256
|
+
connected: true,
|
|
257
|
+
pagePath,
|
|
258
|
+
networkType,
|
|
259
|
+
recentConsoleErrors: recentErrors.length > 0 ? recentErrors : ["No recent errors"]
|
|
260
|
+
}, null, 2)
|
|
261
|
+
}]
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
case TOOLS.DISCONNECT: {
|
|
266
|
+
if (!miniProgram) {
|
|
267
|
+
return { content: [{ type: "text", text: "Not connected." }] };
|
|
268
|
+
}
|
|
269
|
+
await miniProgram.disconnect();
|
|
270
|
+
miniProgram = null;
|
|
271
|
+
return { content: [{ type: "text", text: "Disconnected." }] };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// For all other commands, check connection first
|
|
275
|
+
default: {
|
|
276
|
+
if (!miniProgram) {
|
|
277
|
+
return { isError: true, content: [{ type: "text", text: "Not connected to Mini Program. Use launch or connect first." }] };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Handle other tools
|
|
281
|
+
switch (name) {
|
|
282
|
+
case TOOLS.NAVIGATE_TO: {
|
|
283
|
+
const { url } = args;
|
|
284
|
+
const page = await miniProgram.reLaunch(url); // using reLaunch to be safe, or navigateTo
|
|
285
|
+
// Note: automator.navigateTo returns the page object
|
|
286
|
+
return { content: [{ type: "text", text: `Navigated to ${url}. Path: ${page.path}` }] };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
case TOOLS.GET_PAGE_DATA: {
|
|
290
|
+
const page = await miniProgram.currentPage();
|
|
291
|
+
const { path } = args;
|
|
292
|
+
const data = await page.data(path);
|
|
293
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
case TOOLS.SET_PAGE_DATA: {
|
|
297
|
+
const page = await miniProgram.currentPage();
|
|
298
|
+
const { data } = args;
|
|
299
|
+
await page.setData(data);
|
|
300
|
+
return { content: [{ type: "text", text: "Data set successfully." }] };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
case TOOLS.GET_ELEMENT: {
|
|
304
|
+
const page = await miniProgram.currentPage();
|
|
305
|
+
const { selector, action, attributeName, styleName, value, eventName, detail } = args;
|
|
306
|
+
const element = await page.$(selector);
|
|
307
|
+
|
|
308
|
+
if (!element) {
|
|
309
|
+
return { isError: true, content: [{ type: "text", text: `Element not found: ${selector}` }] };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let result;
|
|
313
|
+
if (action === "text") result = await element.text();
|
|
314
|
+
else if (action === "wxml") result = await element.wxml();
|
|
315
|
+
else if (action === "outerWxml") result = await element.outerWxml();
|
|
316
|
+
else if (action === "attribute") result = await element.attribute(attributeName);
|
|
317
|
+
else if (action === "style") result = await element.style(styleName);
|
|
318
|
+
else if (action === "tap") {
|
|
319
|
+
await element.tap();
|
|
320
|
+
result = "Tapped element";
|
|
321
|
+
}
|
|
322
|
+
else if (action === "input") {
|
|
323
|
+
await element.input(value || "");
|
|
324
|
+
result = `Input value "${value}" set`;
|
|
325
|
+
}
|
|
326
|
+
else if (action === "trigger") {
|
|
327
|
+
await element.trigger(eventName, detail || {});
|
|
328
|
+
result = `Triggered event "${eventName}"`;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return { content: [{ type: "text", text: String(result) }] };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
case TOOLS.CALL_METHOD: {
|
|
335
|
+
const page = await miniProgram.currentPage();
|
|
336
|
+
const { method, args: methodArgs } = args;
|
|
337
|
+
const result = await page.callMethod(method, ...methodArgs);
|
|
338
|
+
return { content: [{ type: "text", text: result === undefined ? "undefined" : JSON.stringify(result, null, 2) }] };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
default:
|
|
342
|
+
return { isError: true, content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
} catch (error) {
|
|
347
|
+
return {
|
|
348
|
+
isError: true,
|
|
349
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Helper for Zod to JSON Schema since we can't easily import zod-to-json-schema in this environment without proper setup or it might conflict
|
|
355
|
+
// Actually, I can use a simplified helper or just rely on manual schema construction if needed, but the library is installed.
|
|
356
|
+
// Let's try to import it properly.
|
|
357
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
358
|
+
|
|
359
|
+
async function main() {
|
|
360
|
+
const transport = new StdioServerTransport();
|
|
361
|
+
await server.connect(transport);
|
|
362
|
+
console.error("WeChat DevTools MCP Server running on stdio");
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
main().catch((error) => {
|
|
366
|
+
console.error("Fatal error:", error);
|
|
367
|
+
process.exit(1);
|
|
368
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wechat-dev-mcp",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "A Model Context Protocol (MCP) server for controlling WeChat Developer Tools",
|
|
5
|
+
"bin": {
|
|
6
|
+
"wechat-dev-mcp": "./index.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"index.js",
|
|
10
|
+
"README.md",
|
|
11
|
+
"README_zh-CN.md"
|
|
12
|
+
],
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "index.js",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"mcp-server",
|
|
21
|
+
"wechat",
|
|
22
|
+
"miniprogram",
|
|
23
|
+
"devtools",
|
|
24
|
+
"automation",
|
|
25
|
+
"miniprogram-automator"
|
|
26
|
+
],
|
|
27
|
+
"author": "CuiJiawei",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
31
|
+
"miniprogram-automator": "^0.12.1",
|
|
32
|
+
"zod": "^3.23.0",
|
|
33
|
+
"zod-to-json-schema": "^3.25.1"
|
|
34
|
+
}
|
|
35
|
+
}
|