@weapp-vite/mcp 1.5.7 → 2.0.0
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 +2 -0
- package/dist/index.d.mts +96 -77
- package/dist/index.d.ts +96 -77
- package/dist/index.mjs +1561 -1348
- package/package.json +10 -7
package/dist/index.mjs
CHANGED
|
@@ -4,1041 +4,714 @@ import http from 'node:http';
|
|
|
4
4
|
import { toNodeHandler, localhostHostValidation, hostHeaderValidation, localhostOriginValidation, originValidation } from '@modelcontextprotocol/node';
|
|
5
5
|
import { ResourceTemplate, McpServer, createMcpHandler } from '@modelcontextprotocol/server';
|
|
6
6
|
import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
|
7
|
+
import { DEFAULT_RUNTIME_REST_ENDPOINT, DEFAULT_MCP_ENDPOINT, DEFAULT_MCP_HOST, DEFAULT_MCP_PORT } from '@weapp-core/constants';
|
|
8
|
+
export { DEFAULT_MCP_ENDPOINT, DEFAULT_MCP_HOST, DEFAULT_MCP_PORT, DEFAULT_RUNTIME_REST_ENDPOINT } from '@weapp-core/constants';
|
|
7
9
|
import { Buffer } from 'node:buffer';
|
|
8
|
-
import fs
|
|
10
|
+
import fs from 'node:fs/promises';
|
|
9
11
|
import path from 'node:path';
|
|
10
12
|
import { z } from 'zod';
|
|
11
|
-
import { closeSharedMiniProgram, resolveDevtoolsProjectPath, resolveDevtoolsWorkspacePath, acquireSharedMiniProgram, releaseSharedMiniProgram, resolveSharedMiniProgramSessionKey, toDevtoolsSerializableValue } from '@weapp-vite/devtools-runtime';
|
|
13
|
+
import { withRuntimeLease, closeSharedMiniProgram, closeOwnedSharedMiniProgram, resolveDevtoolsProjectPath, resolveDevtoolsWorkspacePath, hasSharedMiniProgram, acquireSharedMiniProgram, releaseSharedMiniProgram, resolveSharedMiniProgramSessionKey, retainSharedMiniProgram, releaseSharedMiniProgramByKey, toDevtoolsSerializableValue, acquireRuntimeLease, runWithRuntimeLease } from '@weapp-vite/devtools-runtime';
|
|
14
|
+
import { AcceptanceService, redactor, redactValue } from '@weapp-vite/acceptance';
|
|
12
15
|
import { createRequire } from 'node:module';
|
|
13
|
-
import fs from 'node:fs';
|
|
16
|
+
import fs$1 from 'node:fs';
|
|
14
17
|
import { spawn } from 'node:child_process';
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
packageName: "weapp-vite",
|
|
23
|
-
relativePath: path.join("packages", "weapp-vite")
|
|
24
|
-
},
|
|
25
|
-
"wevu": {
|
|
26
|
-
id: "wevu",
|
|
27
|
-
label: "wevu",
|
|
28
|
-
packageName: "wevu",
|
|
29
|
-
relativePath: path.join("packages-runtime", "wevu")
|
|
30
|
-
},
|
|
31
|
-
"wevu-compiler": {
|
|
32
|
-
id: "wevu-compiler",
|
|
33
|
-
label: "wevu-compiler",
|
|
34
|
-
packageName: "@wevu/compiler",
|
|
35
|
-
relativePath: path.join("packages-runtime", "wevu-compiler")
|
|
19
|
+
function formatJson(value) {
|
|
20
|
+
return JSON.stringify(value, null, 2);
|
|
21
|
+
}
|
|
22
|
+
function normalizeErrorMessage(error) {
|
|
23
|
+
if (error instanceof Error) {
|
|
24
|
+
return error.message;
|
|
36
25
|
}
|
|
37
|
-
|
|
38
|
-
const DEFAULT_TIMEOUT_MS = 12e4;
|
|
39
|
-
const DEFAULT_MAX_OUTPUT_CHARS = 12e4;
|
|
40
|
-
const DEFAULT_MAX_FILE_CHARS = 8e4;
|
|
41
|
-
const DEFAULT_MAX_RESULTS = 200;
|
|
42
|
-
const SKIPPED_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
43
|
-
"node_modules",
|
|
44
|
-
".git",
|
|
45
|
-
".turbo",
|
|
46
|
-
"dist",
|
|
47
|
-
"coverage"
|
|
48
|
-
]);
|
|
49
|
-
|
|
50
|
-
function hasWorkspaceMarkers(dir) {
|
|
51
|
-
return fs.existsSync(path.join(dir, "pnpm-workspace.yaml")) && fs.existsSync(path.join(dir, "package.json"));
|
|
26
|
+
return String(error);
|
|
52
27
|
}
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return path.resolve(start);
|
|
28
|
+
function toToolResult(data, text) {
|
|
29
|
+
return {
|
|
30
|
+
content: [{
|
|
31
|
+
type: "text",
|
|
32
|
+
text: text ?? formatJson(data)
|
|
33
|
+
}],
|
|
34
|
+
structuredContent: {
|
|
35
|
+
result: data
|
|
62
36
|
}
|
|
63
|
-
|
|
64
|
-
}
|
|
37
|
+
};
|
|
65
38
|
}
|
|
66
|
-
function
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
39
|
+
function toToolError(error) {
|
|
40
|
+
const message = normalizeErrorMessage(error);
|
|
41
|
+
return {
|
|
42
|
+
isError: true,
|
|
43
|
+
content: [{
|
|
44
|
+
type: "text",
|
|
45
|
+
text: message
|
|
46
|
+
}]
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const DEFAULT_SCREENSHOT_TIMEOUT = 6e4;
|
|
51
|
+
const SCREENSHOT_RETRY_DELAY = 500;
|
|
52
|
+
const connectionSchema$1 = z.object({
|
|
53
|
+
cliPath: z.string().trim().min(1).optional(),
|
|
54
|
+
projectPath: z.string().trim().min(1).describe("\u5C0F\u7A0B\u5E8F\u9879\u76EE\u8DEF\u5F84\uFF1B\u652F\u6301 workspaceRoot \u76F8\u5BF9\u8DEF\u5F84"),
|
|
55
|
+
timeout: z.number().int().positive().optional(),
|
|
56
|
+
port: z.number().int().positive().max(65535).optional(),
|
|
57
|
+
preferOpenedSession: z.boolean().optional(),
|
|
58
|
+
preserveProjectRoot: z.boolean().optional(),
|
|
59
|
+
sessionId: z.string().trim().min(1).optional()
|
|
60
|
+
});
|
|
61
|
+
const connectionInputSchema = {
|
|
62
|
+
cliPath: z.string().trim().min(1).optional(),
|
|
63
|
+
projectPath: z.string().trim().min(1).describe("\u5C0F\u7A0B\u5E8F\u9879\u76EE\u8DEF\u5F84\uFF1B\u652F\u6301 workspaceRoot \u76F8\u5BF9\u8DEF\u5F84"),
|
|
64
|
+
timeout: z.number().int().positive().optional(),
|
|
65
|
+
port: z.number().int().positive().max(65535).optional(),
|
|
66
|
+
preferOpenedSession: z.boolean().optional(),
|
|
67
|
+
preserveProjectRoot: z.boolean().optional(),
|
|
68
|
+
sessionId: z.string().trim().min(1).optional()
|
|
69
|
+
};
|
|
70
|
+
class RuntimeSessionManager {
|
|
71
|
+
constructor(workspaceRoot, runtimeHooks = createUnavailableRuntimeHooks()) {
|
|
72
|
+
this.workspaceRoot = workspaceRoot;
|
|
73
|
+
this.runtimeHooks = runtimeHooks;
|
|
72
74
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
resolvedInputs = /* @__PURE__ */ new Map();
|
|
76
|
+
resolvedSessionKeys = /* @__PURE__ */ new Map();
|
|
77
|
+
logs = [];
|
|
78
|
+
attachedSessions = /* @__PURE__ */ new Map();
|
|
79
|
+
maxLogs = 1e3;
|
|
80
|
+
logSequence = 0;
|
|
81
|
+
ownedSessions = /* @__PURE__ */ new Map();
|
|
82
|
+
async close(input) {
|
|
83
|
+
input = await this.resolveInput(input);
|
|
84
|
+
const projectPath = this.resolveProjectPath(input.projectPath);
|
|
85
|
+
const sessionKey = this.resolveSessionKey(projectPath, input);
|
|
86
|
+
await withRuntimeLease(projectPath, async () => {
|
|
87
|
+
this.detach(sessionKey);
|
|
88
|
+
await closeSharedMiniProgram(projectPath, input.sessionId || input.port, input);
|
|
89
|
+
});
|
|
75
90
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
function resolveSubPath(root, relativePath) {
|
|
79
|
-
if (!relativePath || relativePath === ".") {
|
|
80
|
-
return path.resolve(root);
|
|
91
|
+
clearLogs() {
|
|
92
|
+
this.logs.length = 0;
|
|
81
93
|
}
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
getLogs(projectPath, afterSequence = 0) {
|
|
95
|
+
return this.logs.filter((entry) => (!projectPath || entry.projectPath === this.resolveProjectPath(projectPath)) && (entry.sequence ?? 0) > afterSequence);
|
|
84
96
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
async function pathExists(filePath) {
|
|
89
|
-
try {
|
|
90
|
-
await fs$1.access(filePath);
|
|
91
|
-
return true;
|
|
92
|
-
} catch {
|
|
93
|
-
return false;
|
|
97
|
+
getLogCursor() {
|
|
98
|
+
return this.logSequence;
|
|
94
99
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
fs$1.realpath(workspaceRoot),
|
|
99
|
-
fs$1.realpath(absolutePath)
|
|
100
|
-
]);
|
|
101
|
-
const relativePath = path.relative(realWorkspaceRoot, realAbsolutePath);
|
|
102
|
-
if (!relativePath || relativePath === ".") {
|
|
103
|
-
return ".";
|
|
100
|
+
async prepareProject(projectPath, signal) {
|
|
101
|
+
const options = await this.resolveInput({ projectPath });
|
|
102
|
+
await this.runtimeHooks.prepareProject?.(projectPath, signal, options);
|
|
104
103
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
async function readPackageJson(filePath) {
|
|
108
|
-
const content = await fs$1.readFile(filePath, "utf8");
|
|
109
|
-
return JSON.parse(content);
|
|
110
|
-
}
|
|
111
|
-
function createWorkspaceRequire(workspaceRoot) {
|
|
112
|
-
return createRequire(path.join(workspaceRoot, "__weapp_vite_mcp__.cjs"));
|
|
113
|
-
}
|
|
114
|
-
async function resolveMonorepoPackage(workspaceRoot, id) {
|
|
115
|
-
const config = EXPOSED_PACKAGES[id];
|
|
116
|
-
const absolutePath = assertInsideRoot(workspaceRoot, path.join(workspaceRoot, config.relativePath));
|
|
117
|
-
const packageJsonPath = path.join(absolutePath, "package.json");
|
|
118
|
-
if (!await pathExists(packageJsonPath)) {
|
|
119
|
-
return void 0;
|
|
104
|
+
fork(workspaceRoot = this.workspaceRoot) {
|
|
105
|
+
return new RuntimeSessionManager(workspaceRoot, this.runtimeHooks);
|
|
120
106
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
packageName: packageJson.name ?? config.packageName,
|
|
130
|
-
version: packageJson.version ?? "0.0.0",
|
|
131
|
-
absolutePath,
|
|
132
|
-
relativePath: config.relativePath,
|
|
133
|
-
scripts: Object.keys(packageJson.scripts ?? {}),
|
|
134
|
-
docs: {
|
|
135
|
-
readme: await pathExists(readmePath) ? readmePath : void 0,
|
|
136
|
-
changelog: await pathExists(changelogPath) ? changelogPath : void 0
|
|
137
|
-
},
|
|
138
|
-
sourceRoot,
|
|
139
|
-
cliPath
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
async function resolveInstalledPackage(workspaceRoot, id) {
|
|
143
|
-
const config = EXPOSED_PACKAGES[id];
|
|
144
|
-
const workspaceRequire = createWorkspaceRequire(workspaceRoot);
|
|
145
|
-
let packageJsonPath;
|
|
146
|
-
try {
|
|
147
|
-
packageJsonPath = workspaceRequire.resolve(`${config.packageName}/package.json`);
|
|
148
|
-
} catch {
|
|
149
|
-
return void 0;
|
|
107
|
+
async dispose() {
|
|
108
|
+
for (const key of this.attachedSessions.keys()) {
|
|
109
|
+
this.detach(key);
|
|
110
|
+
}
|
|
111
|
+
for (const { input, miniProgram } of this.ownedSessions.values()) {
|
|
112
|
+
await closeOwnedSharedMiniProgram(input, miniProgram);
|
|
113
|
+
}
|
|
114
|
+
this.ownedSessions.clear();
|
|
150
115
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
116
|
+
resolveProjectPath(projectPath) {
|
|
117
|
+
return resolveDevtoolsProjectPath(this.workspaceRoot, projectPath);
|
|
118
|
+
}
|
|
119
|
+
resolveWorkspacePath(filePath) {
|
|
120
|
+
return resolveDevtoolsWorkspacePath(this.workspaceRoot, filePath);
|
|
121
|
+
}
|
|
122
|
+
async withMiniProgram(input, runner) {
|
|
123
|
+
input = await this.resolveInput(input);
|
|
124
|
+
const projectPath = this.resolveProjectPath(input.projectPath);
|
|
125
|
+
return withRuntimeLease(projectPath, async () => {
|
|
126
|
+
const existed = hasSharedMiniProgram({ ...input, projectPath });
|
|
127
|
+
const miniProgram = await acquireSharedMiniProgram(this.runtimeHooks, {
|
|
128
|
+
...input,
|
|
129
|
+
port: input.port,
|
|
130
|
+
projectPath,
|
|
131
|
+
sessionId: input.sessionId,
|
|
132
|
+
timeout: input.timeout,
|
|
133
|
+
preferOpenedSession: input.preferOpenedSession,
|
|
134
|
+
preserveProjectRoot: input.preserveProjectRoot ?? true,
|
|
135
|
+
sharedSession: true
|
|
136
|
+
});
|
|
137
|
+
const sessionKey = this.resolveSessionKey(projectPath, input);
|
|
138
|
+
if (!existed) {
|
|
139
|
+
this.ownedSessions.set(sessionKey, { input: { ...input, projectPath }, miniProgram });
|
|
140
|
+
}
|
|
141
|
+
this.attach(sessionKey, miniProgram, projectPath);
|
|
142
|
+
try {
|
|
143
|
+
return await runner(miniProgram);
|
|
144
|
+
} finally {
|
|
145
|
+
releaseSharedMiniProgram(projectPath, input.sessionId || input.port, input);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async withPage(input, runner) {
|
|
150
|
+
input = await this.resolveInput(input);
|
|
151
|
+
const projectPath = this.resolveProjectPath(input.projectPath);
|
|
152
|
+
const sessionKey = this.resolveSessionKey(projectPath, input);
|
|
153
|
+
return await this.withMiniProgram(input, async (miniProgram) => {
|
|
154
|
+
const page = await this.resolveCurrentPage(sessionKey, miniProgram);
|
|
155
|
+
if (!page) {
|
|
156
|
+
throw new Error("\u5F53\u524D\u6CA1\u6709\u6D3B\u52A8\u9875\u9762\uFF0C\u8BF7\u5148\u8C03\u7528 weapp_devtools_connect \u786E\u8BA4 DevTools \u4F1A\u8BDD\u3002");
|
|
165
157
|
}
|
|
158
|
+
return await runner(page, miniProgram);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
rememberPage(input, page) {
|
|
162
|
+
if (!page) {
|
|
163
|
+
return;
|
|
166
164
|
}
|
|
165
|
+
const projectPath = this.resolveProjectPath(input.projectPath);
|
|
166
|
+
const sessionKey = this.resolveSessionKey(projectPath, input);
|
|
167
|
+
this.attachedSessions.get(sessionKey).currentPage = page;
|
|
167
168
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
scripts: Object.keys(packageJson.scripts ?? {}),
|
|
176
|
-
docs: {
|
|
177
|
-
readme: await pathExists(installedReadmePath) ? installedReadmePath : void 0,
|
|
178
|
-
changelog: await pathExists(installedChangelogPath) ? installedChangelogPath : void 0
|
|
179
|
-
},
|
|
180
|
-
sourceRoot,
|
|
181
|
-
cliPath
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
async function resolveExposedPackage(workspaceRoot, id) {
|
|
185
|
-
return await resolveMonorepoPackage(workspaceRoot, id) ?? await resolveInstalledPackage(workspaceRoot, id) ?? Promise.reject(new Error(`\u672A\u627E\u5230\u66B4\u9732\u5305\uFF1A${id}`));
|
|
186
|
-
}
|
|
187
|
-
async function resolveExposedPackages(workspaceRoot) {
|
|
188
|
-
const resolved = await Promise.allSettled(
|
|
189
|
-
Object.keys(EXPOSED_PACKAGES).map((id) => resolveExposedPackage(workspaceRoot, id))
|
|
190
|
-
);
|
|
191
|
-
return resolved.flatMap((result) => {
|
|
192
|
-
if (result.status === "fulfilled") {
|
|
193
|
-
return [result.value];
|
|
169
|
+
async resolveInput(input) {
|
|
170
|
+
const options = { ...input, projectPath: this.resolveProjectPath(input.projectPath) };
|
|
171
|
+
const key = resolveSharedMiniProgramSessionKey(options);
|
|
172
|
+
let resolution = this.resolvedInputs.get(key);
|
|
173
|
+
if (!resolution) {
|
|
174
|
+
resolution = this.runtimeHooks.resolveSessionOptions?.(options) ?? Promise.resolve(options);
|
|
175
|
+
this.resolvedInputs.set(key, resolution);
|
|
194
176
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
"\u4F60\u662F weapp-vite monorepo \u7EF4\u62A4\u8005\uFF0C\u8BF7\u7ED9\u51FA\u53EF\u6267\u884C\u7684\u6539\u9020\u8BA1\u5212\u3002",
|
|
218
|
-
`\u76EE\u6807\uFF1A${objective}`,
|
|
219
|
-
`\u805A\u7126\u5305\uFF1A${targets.join(", ")}`,
|
|
220
|
-
"\u8BF7\u5305\u542B\uFF1A\u5F71\u54CD\u9762\u3001\u98CE\u9669\u70B9\u3001\u6D4B\u8BD5\u7B56\u7565\u3001\u56DE\u6EDA\u7B56\u7565\u3002"
|
|
221
|
-
].join("\n")
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
]
|
|
225
|
-
};
|
|
226
|
-
});
|
|
227
|
-
server.registerPrompt("debug-wevu-runtime", {
|
|
228
|
-
title: "Debug wevu Runtime",
|
|
229
|
-
description: "\u7528\u4E8E\u5B9A\u4F4D wevu runtime \u751F\u547D\u5468\u671F/\u54CD\u5E94\u5F0F\u95EE\u9898\u7684\u6807\u51C6\u63D0\u793A\u8BCD",
|
|
230
|
-
argsSchema: z.object({
|
|
231
|
-
symptom: z.string().min(1)
|
|
232
|
-
})
|
|
233
|
-
}, async ({ symptom }) => {
|
|
234
|
-
return {
|
|
235
|
-
messages: [
|
|
236
|
-
{
|
|
237
|
-
role: "user",
|
|
238
|
-
content: {
|
|
239
|
-
type: "text",
|
|
240
|
-
text: [
|
|
241
|
-
"\u8BF7\u57FA\u4E8E wevu runtime \u4EE3\u7801\u8DEF\u5F84\u8FDB\u884C\u5206\u5C42\u6392\u67E5\uFF1A",
|
|
242
|
-
"1. \u590D\u73B0\u573A\u666F\u4E0E\u6700\u5C0F\u6837\u4F8B",
|
|
243
|
-
"2. \u751F\u547D\u5468\u671F\u94A9\u5B50\u89E6\u53D1\u94FE",
|
|
244
|
-
"3. \u54CD\u5E94\u5F0F\u4E0E setData \u5DEE\u91CF\u540C\u6B65\u94FE",
|
|
245
|
-
"4. \u5355\u6D4B\u4E0E e2e \u56DE\u5F52\u8865\u4E01",
|
|
246
|
-
`\u73B0\u8C61\uFF1A${symptom}`
|
|
247
|
-
].join("\n")
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
]
|
|
251
|
-
};
|
|
252
|
-
});
|
|
253
|
-
server.registerPrompt("inspect-mini-program-page", {
|
|
254
|
-
title: "Inspect Mini Program Page",
|
|
255
|
-
description: "\u8FDE\u63A5\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u5E76\u68C0\u67E5\u5F53\u524D\u5C0F\u7A0B\u5E8F\u9875\u9762\u7684\u6807\u51C6\u6D41\u7A0B",
|
|
256
|
-
argsSchema: z.object({
|
|
257
|
-
projectPath: z.string().min(1),
|
|
258
|
-
pagePath: z.string().optional(),
|
|
259
|
-
focus: z.string().optional()
|
|
260
|
-
})
|
|
261
|
-
}, async ({ projectPath, pagePath, focus }) => {
|
|
262
|
-
return {
|
|
263
|
-
messages: [
|
|
264
|
-
{
|
|
265
|
-
role: "user",
|
|
266
|
-
content: {
|
|
267
|
-
type: "text",
|
|
268
|
-
text: [
|
|
269
|
-
"\u8BF7\u6309\u987A\u5E8F\u4F7F\u7528 weapp-vite MCP runtime tools \u68C0\u67E5\u5C0F\u7A0B\u5E8F\u9875\u9762\uFF1A",
|
|
270
|
-
`1. \u8C03\u7528 weapp_devtools_connect\uFF0CprojectPath=${projectPath}`,
|
|
271
|
-
pagePath ? `2. \u8C03\u7528 weapp_devtools_route \u8DF3\u8F6C\u5230 ${pagePath}` : "2. \u8C03\u7528 weapp_devtools_active_page \u786E\u8BA4\u5F53\u524D\u9875\u9762",
|
|
272
|
-
"3. \u8C03\u7528 weapp_devtools_capture \u83B7\u53D6\u622A\u56FE",
|
|
273
|
-
"4. \u5982\u9700\u68C0\u67E5\u7ED3\u6784\uFF0C\u4F18\u5148\u8C03\u7528 weapp_runtime_find_node/weapp_runtime_find_nodes\uFF0C\u5FC5\u8981\u65F6\u8BBE\u7F6E withWxml=true",
|
|
274
|
-
"5. \u8C03\u7528 weapp_devtools_console \u68C0\u67E5 console/exception \u65E5\u5FD7",
|
|
275
|
-
focus ? `\u5173\u6CE8\u70B9\uFF1A${focus}` : "\u5173\u6CE8\u70B9\uFF1A\u9875\u9762\u662F\u5426\u6B63\u786E\u6E32\u67D3\u3001\u662F\u5426\u5B58\u5728\u8FD0\u884C\u65F6\u9519\u8BEF\u3002"
|
|
276
|
-
].join("\n")
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
]
|
|
177
|
+
try {
|
|
178
|
+
const resolved = await resolution;
|
|
179
|
+
this.resolvedSessionKeys.set(key, resolveSharedMiniProgramSessionKey(resolved));
|
|
180
|
+
return { ...resolved, ...input, projectPath: options.projectPath, cliPath: resolved.cliPath, installationId: resolved.installationId };
|
|
181
|
+
} catch (error) {
|
|
182
|
+
this.resolvedInputs.delete(key);
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
resolveSessionKey(projectPath, input) {
|
|
187
|
+
const key = resolveSharedMiniProgramSessionKey({ ...input, projectPath });
|
|
188
|
+
return this.resolvedSessionKeys.get(key) ?? key;
|
|
189
|
+
}
|
|
190
|
+
attach(sessionKey, miniProgram, projectPath) {
|
|
191
|
+
const existing = this.attachedSessions.get(sessionKey);
|
|
192
|
+
if (existing?.miniProgram === miniProgram) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
this.detach(sessionKey);
|
|
196
|
+
retainSharedMiniProgram(sessionKey);
|
|
197
|
+
const onConsole = (payload) => {
|
|
198
|
+
this.pushLog({ ...normalizeConsoleEvent(payload), projectPath, sessionKey });
|
|
280
199
|
};
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
title: "Recover Mini Program Connection",
|
|
284
|
-
description: "\u6062\u590D\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177 automator \u8FDE\u63A5\u7684\u6807\u51C6\u6D41\u7A0B",
|
|
285
|
-
argsSchema: z.object({
|
|
286
|
-
projectPath: z.string().min(1),
|
|
287
|
-
lastError: z.string().optional()
|
|
288
|
-
})
|
|
289
|
-
}, async ({ projectPath, lastError }) => {
|
|
290
|
-
return {
|
|
291
|
-
messages: [
|
|
292
|
-
{
|
|
293
|
-
role: "user",
|
|
294
|
-
content: {
|
|
295
|
-
type: "text",
|
|
296
|
-
text: [
|
|
297
|
-
"\u8BF7\u6309\u987A\u5E8F\u6062\u590D weapp-vite MCP runtime \u8FDE\u63A5\uFF1A",
|
|
298
|
-
`1. \u8C03\u7528 weapp_devtools_connect\uFF0CprojectPath=${projectPath}\uFF0Creconnect=true`,
|
|
299
|
-
"2. \u5982\u679C\u4ECD\u5931\u8D25\uFF0C\u68C0\u67E5\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u662F\u5426\u5DF2\u5F00\u542F\u670D\u52A1\u7AEF\u53E3\u4E0E\u81EA\u52A8\u5316\u6D4B\u8BD5\u80FD\u529B",
|
|
300
|
-
"3. \u5982\u679C\u662F\u534F\u8BAE\u8D85\u65F6\uFF0C\u5173\u95ED\u591A\u4F59 DevTools \u7A97\u53E3\u540E\u53EA\u91CD\u8BD5\u4E00\u6B21",
|
|
301
|
-
"4. \u6062\u590D\u540E\u8C03\u7528 weapp_devtools_active_page \u548C weapp_devtools_console \u786E\u8BA4\u72B6\u6001",
|
|
302
|
-
lastError ? `\u4E0A\u4E00\u6B21\u9519\u8BEF\uFF1A${lastError}` : "\u4E0A\u4E00\u6B21\u9519\u8BEF\uFF1A\u672A\u63D0\u4F9B\u3002"
|
|
303
|
-
].join("\n")
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
]
|
|
200
|
+
const onException = (payload) => {
|
|
201
|
+
this.pushLog({ ...normalizeExceptionEvent(payload), projectPath, sessionKey });
|
|
307
202
|
};
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
203
|
+
miniProgram.on("console", onConsole);
|
|
204
|
+
miniProgram.on("exception", onException);
|
|
205
|
+
this.attachedSessions.set(sessionKey, {
|
|
206
|
+
miniProgram,
|
|
207
|
+
onConsole,
|
|
208
|
+
onException
|
|
209
|
+
});
|
|
314
210
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
211
|
+
async resolveCurrentPage(sessionKey, miniProgram) {
|
|
212
|
+
try {
|
|
213
|
+
const page = await miniProgram.currentPage();
|
|
214
|
+
this.attachedSessions.get(sessionKey).currentPage = page;
|
|
215
|
+
return page;
|
|
216
|
+
} catch (error) {
|
|
217
|
+
const cachedPage = this.attachedSessions.get(sessionKey)?.currentPage;
|
|
218
|
+
if (cachedPage && isCurrentPageRecoverableError(error)) {
|
|
219
|
+
return cachedPage;
|
|
220
|
+
}
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
detach(projectPath) {
|
|
225
|
+
const attached = this.attachedSessions.get(projectPath);
|
|
226
|
+
if (!attached) {
|
|
318
227
|
return;
|
|
319
228
|
}
|
|
320
|
-
if (
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
await walkFilesRecursive(root, path.join(current, entry.name), output, maxResults);
|
|
325
|
-
continue;
|
|
229
|
+
if (typeof attached.miniProgram.off === "function") {
|
|
230
|
+
attached.miniProgram.off("console", attached.onConsole);
|
|
231
|
+
attached.miniProgram.off("exception", attached.onException);
|
|
326
232
|
}
|
|
327
|
-
|
|
328
|
-
|
|
233
|
+
releaseSharedMiniProgramByKey(projectPath);
|
|
234
|
+
this.attachedSessions.delete(projectPath);
|
|
235
|
+
}
|
|
236
|
+
pushLog(entry) {
|
|
237
|
+
this.logs.push({ ...entry, sequence: ++this.logSequence });
|
|
238
|
+
while (this.logs.length > this.maxLogs) {
|
|
239
|
+
this.logs.shift();
|
|
329
240
|
}
|
|
330
|
-
const absolutePath = path.join(current, entry.name);
|
|
331
|
-
const relativePath = path.relative(root, absolutePath).split(path.sep).join("/");
|
|
332
|
-
output.push(relativePath);
|
|
333
241
|
}
|
|
334
242
|
}
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
return files;
|
|
340
|
-
}
|
|
341
|
-
function sliceLines(content, startLine, endLine) {
|
|
342
|
-
if (!startLine && !endLine) {
|
|
343
|
-
return content;
|
|
243
|
+
function buildUrl(pagePath, query) {
|
|
244
|
+
const normalizedPath = pagePath.startsWith("/") ? pagePath : `/${pagePath}`;
|
|
245
|
+
if (!query || Object.keys(query).length === 0) {
|
|
246
|
+
return normalizedPath;
|
|
344
247
|
}
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
248
|
+
const search = new URLSearchParams(query).toString();
|
|
249
|
+
if (!search) {
|
|
250
|
+
return normalizedPath;
|
|
251
|
+
}
|
|
252
|
+
return `${normalizedPath}${normalizedPath.includes("?") ? "&" : "?"}${search}`;
|
|
349
253
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const content = await fs$1.readFile(filePath, "utf8");
|
|
353
|
-
const selected = sliceLines(content, options?.startLine, options?.endLine);
|
|
354
|
-
const maxChars = options?.maxChars ?? DEFAULT_MAX_FILE_CHARS;
|
|
355
|
-
const clipped = selected.length > maxChars ? `${selected.slice(0, maxChars)}
|
|
356
|
-
|
|
357
|
-
[truncated: ${selected.length - maxChars} chars omitted]` : selected;
|
|
358
|
-
return {
|
|
359
|
-
filePath,
|
|
360
|
-
content: clipped
|
|
361
|
-
};
|
|
254
|
+
function sleep(ms) {
|
|
255
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
362
256
|
}
|
|
363
|
-
function
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
continue;
|
|
373
|
-
}
|
|
374
|
-
output.push({
|
|
375
|
-
filePath: relativeFilePath,
|
|
376
|
-
line: index + 1,
|
|
377
|
-
column: column + 1,
|
|
378
|
-
text: lineText.trim()
|
|
379
|
-
});
|
|
257
|
+
function isCaptureScreenshotProtocolTimeout(error) {
|
|
258
|
+
return error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === "App.captureScreenshot";
|
|
259
|
+
}
|
|
260
|
+
function isCurrentPageRecoverableError(error) {
|
|
261
|
+
if (!(error instanceof Error)) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
if ("code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error) {
|
|
265
|
+
return error.method === "App.getCurrentPage" || error.method === "App.getPageStack" || error.method === "App.callFunction";
|
|
380
266
|
}
|
|
267
|
+
return error.message.includes("page is not on top of page stack");
|
|
381
268
|
}
|
|
382
|
-
async function
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
const matches = [];
|
|
386
|
-
for (const file of files) {
|
|
387
|
-
if (matches.length >= maxResults) {
|
|
388
|
-
break;
|
|
389
|
-
}
|
|
390
|
-
const absolutePath = path.join(root, file);
|
|
391
|
-
let content = "";
|
|
269
|
+
async function captureMiniProgramScreenshot(miniProgram, timeout = DEFAULT_SCREENSHOT_TIMEOUT) {
|
|
270
|
+
let lastError;
|
|
271
|
+
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
|
392
272
|
try {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
273
|
+
const screenshot = await miniProgram.screenshot({ timeout });
|
|
274
|
+
if (screenshot == null) {
|
|
275
|
+
throw new Error("DevTools screenshot returned no data.");
|
|
276
|
+
}
|
|
277
|
+
return typeof screenshot === "string" ? Buffer.from(screenshot, "base64") : Buffer.from(screenshot);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
lastError = error;
|
|
280
|
+
if (!isCaptureScreenshotProtocolTimeout(error) || attempt === 2) {
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
await miniProgram.currentPage().catch(() => void 0);
|
|
284
|
+
await sleep(SCREENSHOT_RETRY_DELAY);
|
|
396
285
|
}
|
|
397
|
-
collectMatchesFromText(query, file, content, maxResults, matches);
|
|
398
286
|
}
|
|
399
|
-
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function formatJson(value) {
|
|
403
|
-
return JSON.stringify(value, null, 2);
|
|
287
|
+
throw lastError;
|
|
404
288
|
}
|
|
405
|
-
function
|
|
406
|
-
|
|
407
|
-
|
|
289
|
+
function parseSelectorWithIndex(selector) {
|
|
290
|
+
const match = selector.match(/^(.+?)\[index=(\d+)\]$/);
|
|
291
|
+
if (!match?.[1] || !match[2]) {
|
|
292
|
+
return {
|
|
293
|
+
selector,
|
|
294
|
+
index: void 0
|
|
295
|
+
};
|
|
408
296
|
}
|
|
409
|
-
return String(error);
|
|
410
|
-
}
|
|
411
|
-
function toToolResult(data, text) {
|
|
412
|
-
return {
|
|
413
|
-
content: [{
|
|
414
|
-
type: "text",
|
|
415
|
-
text: text ?? formatJson(data)
|
|
416
|
-
}],
|
|
417
|
-
structuredContent: {
|
|
418
|
-
result: data
|
|
419
|
-
}
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
function toToolError(error) {
|
|
423
|
-
const message = normalizeErrorMessage(error);
|
|
424
297
|
return {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
type: "text",
|
|
428
|
-
text: message
|
|
429
|
-
}]
|
|
298
|
+
selector: match[1],
|
|
299
|
+
index: Number.parseInt(match[2], 10)
|
|
430
300
|
};
|
|
431
301
|
}
|
|
432
|
-
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if (command === "pnpm") {
|
|
442
|
-
return "pnpm.cmd";
|
|
443
|
-
}
|
|
444
|
-
if (command === "git") {
|
|
445
|
-
return "git.exe";
|
|
446
|
-
}
|
|
447
|
-
if (command === "rg") {
|
|
448
|
-
return "rg.exe";
|
|
302
|
+
async function resolveElement(page, selectorInput, innerSelector, queryOptions) {
|
|
303
|
+
const { selector, index } = parseSelectorWithIndex(selectorInput);
|
|
304
|
+
let element;
|
|
305
|
+
if (index === void 0) {
|
|
306
|
+
element = await page.$(selector, queryOptions);
|
|
307
|
+
} else {
|
|
308
|
+
const elements = await queryElements(page, selector, queryOptions);
|
|
309
|
+
if (index < 0 || index >= elements.length) {
|
|
310
|
+
throw new Error(`\u9009\u62E9\u5668 "${selector}" \u7684 index=${index} \u8D85\u51FA\u8303\u56F4\uFF0C\u5F53\u524D\u5339\u914D ${elements.length} \u4E2A\u5143\u7D20\u3002`);
|
|
449
311
|
}
|
|
312
|
+
element = elements[index];
|
|
450
313
|
}
|
|
451
|
-
|
|
452
|
-
}
|
|
453
|
-
function truncateOutput(text, maxChars) {
|
|
454
|
-
if (text.length <= maxChars) {
|
|
455
|
-
return text;
|
|
314
|
+
if (!element) {
|
|
315
|
+
throw new Error(`\u672A\u627E\u5230\u5143\u7D20: ${selectorInput}`);
|
|
456
316
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
317
|
+
if (!innerSelector) {
|
|
318
|
+
return element;
|
|
319
|
+
}
|
|
320
|
+
const inner = await callOptionalMethod(element, "$", innerSelector);
|
|
321
|
+
if (!inner) {
|
|
322
|
+
throw new Error(`\u5728\u5143\u7D20 "${selectorInput}" \u5185\u672A\u627E\u5230\u5143\u7D20: ${innerSelector}`);
|
|
323
|
+
}
|
|
324
|
+
return inner;
|
|
460
325
|
}
|
|
461
|
-
async function
|
|
462
|
-
|
|
463
|
-
|
|
326
|
+
async function queryElements(page, selectorInput, queryOptions) {
|
|
327
|
+
const { selector, index } = parseSelectorWithIndex(selectorInput);
|
|
328
|
+
const elements = await callOptionalMethod(page, "$$", selector, queryOptions);
|
|
329
|
+
if (!Array.isArray(elements)) {
|
|
330
|
+
return [];
|
|
464
331
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
clearTimeout(timer);
|
|
332
|
+
if (index === void 0) {
|
|
333
|
+
return elements;
|
|
334
|
+
}
|
|
335
|
+
if (index < 0 || index >= elements.length) {
|
|
336
|
+
throw new Error(`\u9009\u62E9\u5668 "${selector}" \u7684 index=${index} \u8D85\u51FA\u8303\u56F4\uFF0C\u5F53\u524D\u5339\u914D ${elements.length} \u4E2A\u5143\u7D20\u3002`);
|
|
337
|
+
}
|
|
338
|
+
return [elements[index]];
|
|
339
|
+
}
|
|
340
|
+
async function summarizeElement(element, withWxml = false) {
|
|
341
|
+
const [text, value, size, offset, scrollWidth, scrollHeight, markup] = await Promise.all([
|
|
342
|
+
callMaybe(element, "text"),
|
|
343
|
+
callMaybe(element, "value"),
|
|
344
|
+
callMaybe(element, "size"),
|
|
345
|
+
callMaybe(element, "offset"),
|
|
346
|
+
callMaybe(element, "scrollWidth"),
|
|
347
|
+
callMaybe(element, "scrollHeight"),
|
|
348
|
+
withWxml ? readElementMarkup(element).catch(() => null) : Promise.resolve(null)
|
|
349
|
+
]);
|
|
350
|
+
return compactObject({
|
|
351
|
+
tagName: readProperty(element, "tagName"),
|
|
352
|
+
text,
|
|
353
|
+
value,
|
|
354
|
+
size,
|
|
355
|
+
offset,
|
|
356
|
+
scrollWidth,
|
|
357
|
+
scrollHeight,
|
|
358
|
+
outerWxml: withWxml && markup?.type === "outerWxml" ? markup.wxml : null,
|
|
359
|
+
wxml: withWxml && markup?.type !== "outerWxml" ? markup?.wxml ?? null : void 0,
|
|
360
|
+
wxmlType: withWxml ? markup?.type ?? null : void 0
|
|
495
361
|
});
|
|
362
|
+
}
|
|
363
|
+
async function readElementMarkup(element, outer = false) {
|
|
364
|
+
if (outer) {
|
|
365
|
+
const outerWxml = await callMaybe(element, "outerWxml");
|
|
366
|
+
if (typeof outerWxml === "string") {
|
|
367
|
+
return {
|
|
368
|
+
type: "outerWxml",
|
|
369
|
+
wxml: outerWxml
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const wxml = await callRequiredMethod(element, "wxml");
|
|
496
374
|
return {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
cwd: path.resolve(cwd),
|
|
500
|
-
exitCode,
|
|
501
|
-
stdout: truncateOutput(stdout.trim(), maxOutputChars),
|
|
502
|
-
stderr: truncateOutput(stderr.trim(), maxOutputChars),
|
|
503
|
-
timedOut
|
|
375
|
+
type: outer ? "wxml-fallback" : "wxml",
|
|
376
|
+
wxml: typeof wxml === "string" ? wxml : String(wxml ?? "")
|
|
504
377
|
};
|
|
505
378
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
const resolved = await resolveExposedPackage(workspaceRoot, packageId);
|
|
509
|
-
if (!resolved.sourceRoot) {
|
|
510
|
-
throw new Error(`\u5F53\u524D\u5DE5\u4F5C\u533A\u4E2D\u7684 ${packageId} \u4E0D\u5305\u542B\u6E90\u7801\u76EE\u5F55\uFF0C\u8BF7\u6539\u4E3A\u4F18\u5148\u8BFB\u53D6\u672C\u5730\u968F\u5305\u6587\u6863\u3002`);
|
|
511
|
-
}
|
|
512
|
-
return assertInsideRoot(workspaceRoot, resolved.sourceRoot);
|
|
379
|
+
function toSerializableValue(value) {
|
|
380
|
+
return toDevtoolsSerializableValue(value);
|
|
513
381
|
}
|
|
514
|
-
function
|
|
515
|
-
|
|
382
|
+
async function callRequiredMethod(target, methodName, ...args) {
|
|
383
|
+
const method = readProperty(target, methodName);
|
|
384
|
+
if (typeof method !== "function") {
|
|
385
|
+
throw new TypeError(`\u5F53\u524D\u5BF9\u8C61\u4E0D\u652F\u6301 ${methodName} \u65B9\u6CD5\u3002`);
|
|
386
|
+
}
|
|
387
|
+
return await method.apply(target, args);
|
|
516
388
|
}
|
|
517
|
-
async function
|
|
518
|
-
|
|
389
|
+
async function callOptionalMethod(target, methodName, ...args) {
|
|
390
|
+
const method = readProperty(target, methodName);
|
|
391
|
+
if (typeof method !== "function") {
|
|
392
|
+
return void 0;
|
|
393
|
+
}
|
|
394
|
+
return await method.apply(target, args);
|
|
519
395
|
}
|
|
520
|
-
async function
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
396
|
+
async function callMaybe(target, methodName, ...args) {
|
|
397
|
+
try {
|
|
398
|
+
return await callOptionalMethod(target, methodName, ...args);
|
|
399
|
+
} catch {
|
|
400
|
+
return void 0;
|
|
524
401
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
402
|
+
}
|
|
403
|
+
function compactObject(input) {
|
|
404
|
+
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
|
|
405
|
+
}
|
|
406
|
+
function readProperty(target, key) {
|
|
407
|
+
if (!target || typeof target !== "object") {
|
|
408
|
+
return void 0;
|
|
528
409
|
}
|
|
529
|
-
|
|
530
|
-
|
|
410
|
+
return target[key];
|
|
411
|
+
}
|
|
412
|
+
function normalizeConsoleEvent(payload) {
|
|
413
|
+
const record = toRecord$1(payload);
|
|
414
|
+
return {
|
|
415
|
+
level: normalizeLogLevel(record.type ?? record.level ?? record.method),
|
|
416
|
+
message: resolveLogMessage(record, payload),
|
|
417
|
+
timestamp: Date.now(),
|
|
418
|
+
raw: toSerializableValue(payload)
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function normalizeExceptionEvent(payload) {
|
|
422
|
+
const record = toRecord$1(payload);
|
|
423
|
+
const error = toRecord$1(record.error);
|
|
424
|
+
const message = [
|
|
425
|
+
typeof error.message === "string" ? error.message : void 0,
|
|
426
|
+
typeof record.message === "string" ? record.message : void 0,
|
|
427
|
+
typeof error.stack === "string" ? error.stack : void 0,
|
|
428
|
+
typeof record.stack === "string" ? record.stack : void 0
|
|
429
|
+
].filter(Boolean).join("\n");
|
|
430
|
+
return {
|
|
431
|
+
level: "error",
|
|
432
|
+
message: message || JSON.stringify(toSerializableValue(payload)),
|
|
433
|
+
timestamp: Date.now(),
|
|
434
|
+
raw: toSerializableValue(payload)
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function normalizeLogLevel(value) {
|
|
438
|
+
const normalized = String(value ?? "log").toLowerCase();
|
|
439
|
+
if (normalized === "warning") {
|
|
440
|
+
return "warn";
|
|
531
441
|
}
|
|
532
|
-
if (
|
|
533
|
-
|
|
442
|
+
if (["debug", "log", "info", "warn", "error"].includes(normalized)) {
|
|
443
|
+
return normalized;
|
|
534
444
|
}
|
|
535
|
-
return
|
|
536
|
-
timeoutMs: input.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
537
|
-
});
|
|
445
|
+
return "log";
|
|
538
446
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
447
|
+
function resolveLogMessage(record, payload) {
|
|
448
|
+
if (typeof record.text === "string" && record.text) {
|
|
449
|
+
return record.text;
|
|
450
|
+
}
|
|
451
|
+
if (typeof record.message === "string" && record.message) {
|
|
452
|
+
return record.message;
|
|
453
|
+
}
|
|
454
|
+
if (Array.isArray(record.args)) {
|
|
455
|
+
return record.args.map(formatLogArgument).join(" ");
|
|
456
|
+
}
|
|
457
|
+
if (typeof payload === "string") {
|
|
458
|
+
return payload;
|
|
459
|
+
}
|
|
460
|
+
return JSON.stringify(toSerializableValue(payload));
|
|
461
|
+
}
|
|
462
|
+
function formatLogArgument(value) {
|
|
463
|
+
const record = toRecord$1(value);
|
|
464
|
+
if (record.value !== void 0) {
|
|
465
|
+
return typeof record.value === "string" ? record.value : JSON.stringify(toSerializableValue(record.value));
|
|
466
|
+
}
|
|
467
|
+
if (typeof record.description === "string") {
|
|
468
|
+
return record.description;
|
|
469
|
+
}
|
|
470
|
+
return typeof value === "string" ? value : JSON.stringify(toSerializableValue(value));
|
|
471
|
+
}
|
|
472
|
+
function toRecord$1(value) {
|
|
473
|
+
return value && typeof value === "object" ? value : {};
|
|
474
|
+
}
|
|
475
|
+
function createUnavailableRuntimeHooks() {
|
|
476
|
+
return {
|
|
477
|
+
async connectMiniProgram() {
|
|
478
|
+
throw new Error("\u672A\u914D\u7F6E DevTools runtime hooks\u3002\u8BF7\u901A\u8FC7 createWeappViteMcpServer({ runtimeHooks }) \u6CE8\u5165 automator \u8FDE\u63A5\u80FD\u529B\u3002");
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const navigateSchema = {
|
|
484
|
+
...connectionInputSchema,
|
|
485
|
+
path: z.string().trim().min(1).optional(),
|
|
486
|
+
query: z.record(z.string(), z.string()).optional(),
|
|
487
|
+
transition: z.enum(["navigateTo", "redirectTo", "reLaunch", "switchTab", "navigateBack"]).optional(),
|
|
488
|
+
waitMs: z.number().int().nonnegative().optional()
|
|
489
|
+
};
|
|
490
|
+
const screenshotSchema = {
|
|
491
|
+
...connectionInputSchema,
|
|
492
|
+
outputPath: z.string().trim().min(1).optional()
|
|
493
|
+
};
|
|
494
|
+
const hostApiSchema = {
|
|
495
|
+
...connectionInputSchema,
|
|
496
|
+
method: z.string().trim().min(1),
|
|
497
|
+
args: z.array(z.unknown()).optional()
|
|
498
|
+
};
|
|
499
|
+
function registerDevtoolsRuntimeTools(server, manager) {
|
|
500
|
+
server.registerTool("weapp_devtools_connect", {
|
|
501
|
+
title: "Ensure Mini Program Connection",
|
|
502
|
+
description: "\u786E\u4FDD\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177 automator \u4F1A\u8BDD\u53EF\u7528\uFF0C\u5E76\u8FD4\u56DE\u5F53\u524D\u9875\u9762\u4E0E\u7CFB\u7EDF\u4FE1\u606F\u3002",
|
|
503
|
+
inputSchema: z.object({
|
|
504
|
+
...connectionInputSchema,
|
|
505
|
+
reconnect: z.boolean().optional()
|
|
506
|
+
})
|
|
507
|
+
}, async ({ reconnect, ...connection }) => {
|
|
508
|
+
try {
|
|
509
|
+
if (reconnect) {
|
|
510
|
+
await manager.close(connection);
|
|
511
|
+
}
|
|
512
|
+
const result = await manager.withMiniProgram(connection, async (miniProgram) => {
|
|
513
|
+
const page = await miniProgram.currentPage().catch(() => null);
|
|
514
|
+
manager.rememberPage(connection, page);
|
|
515
|
+
const systemInfo = await miniProgram.systemInfo().catch(() => null);
|
|
565
516
|
return {
|
|
566
|
-
|
|
517
|
+
connected: true,
|
|
518
|
+
projectPath: connection.projectPath,
|
|
519
|
+
resolvedProjectPath: manager.resolveProjectPath(connection.projectPath),
|
|
520
|
+
currentPage: page ? { path: page.path, query: toSerializableValue(page.query) } : null,
|
|
521
|
+
systemInfo: toSerializableValue(systemInfo)
|
|
567
522
|
};
|
|
568
523
|
});
|
|
524
|
+
return toToolResult(result);
|
|
525
|
+
} catch (error) {
|
|
526
|
+
return toToolError(error);
|
|
569
527
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
528
|
+
});
|
|
529
|
+
server.registerTool("weapp_devtools_route", {
|
|
530
|
+
title: "Navigate Mini Program",
|
|
531
|
+
description: "\u5728\u5C0F\u7A0B\u5E8F\u5185\u6267\u884C navigateTo\u3001redirectTo\u3001reLaunch\u3001switchTab \u6216 navigateBack\u3002",
|
|
532
|
+
inputSchema: z.object(navigateSchema)
|
|
533
|
+
}, async ({ path: pagePath, query, transition = "navigateTo", waitMs, ...connection }) => {
|
|
534
|
+
try {
|
|
535
|
+
const result = await manager.withMiniProgram(connection, async (miniProgram) => {
|
|
536
|
+
if (transition === "navigateBack") {
|
|
537
|
+
const page2 = await miniProgram.navigateBack();
|
|
538
|
+
manager.rememberPage(connection, page2);
|
|
539
|
+
if (waitMs && page2) {
|
|
540
|
+
await page2.waitFor(waitMs);
|
|
541
|
+
}
|
|
542
|
+
return {
|
|
543
|
+
transition,
|
|
544
|
+
activePage: page2 ? { path: page2.path, query: toSerializableValue(page2.query) } : null
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
if (!pagePath) {
|
|
548
|
+
throw new Error("transition \u4E0D\u662F navigateBack \u65F6\u5FC5\u987B\u63D0\u4F9B path\u3002");
|
|
549
|
+
}
|
|
550
|
+
const url = buildUrl(pagePath, query);
|
|
551
|
+
const page = await callRequiredMethod(
|
|
552
|
+
miniProgram,
|
|
553
|
+
transition,
|
|
554
|
+
url
|
|
555
|
+
);
|
|
556
|
+
manager.rememberPage(connection, page);
|
|
557
|
+
if (waitMs && page) {
|
|
558
|
+
await page.waitFor(waitMs);
|
|
559
|
+
}
|
|
577
560
|
return {
|
|
578
|
-
|
|
561
|
+
transition,
|
|
562
|
+
url,
|
|
563
|
+
activePage: page ? { path: page.path, query: toSerializableValue(page.query) } : null
|
|
579
564
|
};
|
|
580
565
|
});
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
list: void 0,
|
|
585
|
-
complete: {
|
|
586
|
-
package: () => packageIds
|
|
566
|
+
return toToolResult(result);
|
|
567
|
+
} catch (error) {
|
|
568
|
+
return toToolError(error);
|
|
587
569
|
}
|
|
588
570
|
});
|
|
589
|
-
server.
|
|
590
|
-
title: "
|
|
591
|
-
description: "\
|
|
592
|
-
|
|
593
|
-
|
|
571
|
+
server.registerTool("weapp_devtools_active_page", {
|
|
572
|
+
title: "Current Mini Program Page",
|
|
573
|
+
description: "\u83B7\u53D6\u5F53\u524D\u9875\u9762\u8DEF\u5F84\u3001\u67E5\u8BE2\u53C2\u6570\u3001\u5C3A\u5BF8\u3001\u6EDA\u52A8\u4F4D\u7F6E\uFF1B\u53EF\u9009\u8FD4\u56DE\u9875\u9762 data\u3002",
|
|
574
|
+
inputSchema: z.object({
|
|
575
|
+
...connectionInputSchema,
|
|
576
|
+
withData: z.boolean().optional()
|
|
577
|
+
})
|
|
578
|
+
}, async ({ withData, ...connection }) => {
|
|
594
579
|
try {
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
580
|
+
const result = await manager.withPage(connection, async (page) => {
|
|
581
|
+
const [size, scrollTop, data] = await Promise.all([
|
|
582
|
+
page.size().catch(() => null),
|
|
583
|
+
page.scrollTop().catch(() => null),
|
|
584
|
+
withData ? page.data().catch(() => null) : Promise.resolve(void 0)
|
|
585
|
+
]);
|
|
586
|
+
return compactObject({
|
|
587
|
+
path: page.path,
|
|
588
|
+
query: toSerializableValue(page.query),
|
|
589
|
+
size: toSerializableValue(size),
|
|
590
|
+
scrollTop: toSerializableValue(scrollTop),
|
|
591
|
+
data: toSerializableValue(data)
|
|
592
|
+
});
|
|
603
593
|
});
|
|
604
|
-
return
|
|
605
|
-
contents: [{
|
|
606
|
-
uri: uri.toString(),
|
|
607
|
-
mimeType: "text/plain",
|
|
608
|
-
text: content
|
|
609
|
-
}]
|
|
610
|
-
};
|
|
594
|
+
return toToolResult(result);
|
|
611
595
|
} catch (error) {
|
|
612
|
-
return
|
|
613
|
-
contents: [{
|
|
614
|
-
uri: uri.toString(),
|
|
615
|
-
mimeType: "text/plain",
|
|
616
|
-
text: `[resource-error] ${normalizeErrorMessage(error)}`
|
|
617
|
-
}]
|
|
618
|
-
};
|
|
596
|
+
return toToolError(error);
|
|
619
597
|
}
|
|
620
598
|
});
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
projectPath: z.string().trim().min(1).describe("\u5C0F\u7A0B\u5E8F\u9879\u76EE\u8DEF\u5F84\uFF1B\u652F\u6301 workspaceRoot \u76F8\u5BF9\u8DEF\u5F84"),
|
|
627
|
-
timeout: z.number().int().positive().optional(),
|
|
628
|
-
port: z.number().int().positive().optional(),
|
|
629
|
-
preferOpenedSession: z.boolean().optional(),
|
|
630
|
-
preserveProjectRoot: z.boolean().optional(),
|
|
631
|
-
sessionId: z.string().trim().min(1).optional()
|
|
632
|
-
});
|
|
633
|
-
const connectionInputSchema = {
|
|
634
|
-
projectPath: z.string().trim().min(1).describe("\u5C0F\u7A0B\u5E8F\u9879\u76EE\u8DEF\u5F84\uFF1B\u652F\u6301 workspaceRoot \u76F8\u5BF9\u8DEF\u5F84"),
|
|
635
|
-
timeout: z.number().int().positive().optional(),
|
|
636
|
-
port: z.number().int().positive().optional(),
|
|
637
|
-
preferOpenedSession: z.boolean().optional(),
|
|
638
|
-
preserveProjectRoot: z.boolean().optional(),
|
|
639
|
-
sessionId: z.string().trim().min(1).optional()
|
|
640
|
-
};
|
|
641
|
-
class RuntimeSessionManager {
|
|
642
|
-
constructor(workspaceRoot, runtimeHooks = createUnavailableRuntimeHooks()) {
|
|
643
|
-
this.workspaceRoot = workspaceRoot;
|
|
644
|
-
this.runtimeHooks = runtimeHooks;
|
|
645
|
-
}
|
|
646
|
-
logs = [];
|
|
647
|
-
attachedSessions = /* @__PURE__ */ new Map();
|
|
648
|
-
maxLogs = 1e3;
|
|
649
|
-
async close(input) {
|
|
650
|
-
const projectPath = this.resolveProjectPath(input.projectPath);
|
|
651
|
-
const sessionKey = this.resolveSessionKey(projectPath, input);
|
|
652
|
-
this.detach(sessionKey);
|
|
653
|
-
await closeSharedMiniProgram(projectPath, input.sessionId || input.port);
|
|
654
|
-
}
|
|
655
|
-
clearLogs() {
|
|
656
|
-
this.logs.length = 0;
|
|
657
|
-
}
|
|
658
|
-
getLogs() {
|
|
659
|
-
return [...this.logs];
|
|
660
|
-
}
|
|
661
|
-
resolveProjectPath(projectPath) {
|
|
662
|
-
return resolveDevtoolsProjectPath(this.workspaceRoot, projectPath);
|
|
663
|
-
}
|
|
664
|
-
resolveWorkspacePath(filePath) {
|
|
665
|
-
return resolveDevtoolsWorkspacePath(this.workspaceRoot, filePath);
|
|
666
|
-
}
|
|
667
|
-
async withMiniProgram(input, runner) {
|
|
668
|
-
const projectPath = this.resolveProjectPath(input.projectPath);
|
|
669
|
-
const miniProgram = await acquireSharedMiniProgram(this.runtimeHooks, {
|
|
670
|
-
port: input.port,
|
|
671
|
-
projectPath,
|
|
672
|
-
sessionId: input.sessionId,
|
|
673
|
-
timeout: input.timeout,
|
|
674
|
-
preferOpenedSession: input.preferOpenedSession,
|
|
675
|
-
preserveProjectRoot: input.preserveProjectRoot ?? true,
|
|
676
|
-
sharedSession: true
|
|
677
|
-
});
|
|
678
|
-
const sessionKey = this.resolveSessionKey(projectPath, input);
|
|
679
|
-
this.attach(sessionKey, miniProgram);
|
|
599
|
+
server.registerTool("weapp_devtools_page_stack", {
|
|
600
|
+
title: "Mini Program Page Stack",
|
|
601
|
+
description: "\u83B7\u53D6\u5F53\u524D\u5C0F\u7A0B\u5E8F\u9875\u9762\u6808\u3002",
|
|
602
|
+
inputSchema: connectionSchema$1
|
|
603
|
+
}, async (connection) => {
|
|
680
604
|
try {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
if (!page) {
|
|
692
|
-
throw new Error("\u5F53\u524D\u6CA1\u6709\u6D3B\u52A8\u9875\u9762\uFF0C\u8BF7\u5148\u8C03\u7528 weapp_devtools_connect \u786E\u8BA4 DevTools \u4F1A\u8BDD\u3002");
|
|
693
|
-
}
|
|
694
|
-
return await runner(page, miniProgram);
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
rememberPage(input, page) {
|
|
698
|
-
if (!page) {
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
const projectPath = this.resolveProjectPath(input.projectPath);
|
|
702
|
-
const sessionKey = this.resolveSessionKey(projectPath, input);
|
|
703
|
-
this.attachedSessions.get(sessionKey).currentPage = page;
|
|
704
|
-
}
|
|
705
|
-
resolveSessionKey(projectPath, input) {
|
|
706
|
-
return resolveSharedMiniProgramSessionKey({
|
|
707
|
-
port: input.port,
|
|
708
|
-
projectPath,
|
|
709
|
-
sessionId: input.sessionId
|
|
710
|
-
});
|
|
711
|
-
}
|
|
712
|
-
attach(sessionKey, miniProgram) {
|
|
713
|
-
const existing = this.attachedSessions.get(sessionKey);
|
|
714
|
-
if (existing?.miniProgram === miniProgram) {
|
|
715
|
-
return;
|
|
605
|
+
const result = await manager.withMiniProgram(connection, async (miniProgram) => {
|
|
606
|
+
const stack = await miniProgram.pageStack();
|
|
607
|
+
return stack.map((page) => ({
|
|
608
|
+
path: page.path,
|
|
609
|
+
query: toSerializableValue(page.query)
|
|
610
|
+
}));
|
|
611
|
+
});
|
|
612
|
+
return toToolResult(result);
|
|
613
|
+
} catch (error) {
|
|
614
|
+
return toToolError(error);
|
|
716
615
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
};
|
|
724
|
-
miniProgram.on("console", onConsole);
|
|
725
|
-
miniProgram.on("exception", onException);
|
|
726
|
-
this.attachedSessions.set(sessionKey, {
|
|
727
|
-
miniProgram,
|
|
728
|
-
onConsole,
|
|
729
|
-
onException
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
async resolveCurrentPage(sessionKey, miniProgram) {
|
|
616
|
+
});
|
|
617
|
+
server.registerTool("weapp_devtools_capture", {
|
|
618
|
+
title: "Mini Program Screenshot",
|
|
619
|
+
description: "\u622A\u53D6\u5F53\u524D\u5C0F\u7A0B\u5E8F\u89C6\u53E3\uFF0C\u8FD4\u56DE base64\uFF0C\u6216\u4FDD\u5B58\u5230 workspaceRoot \u76F8\u5BF9 outputPath\u3002",
|
|
620
|
+
inputSchema: z.object(screenshotSchema)
|
|
621
|
+
}, async ({ outputPath, ...connection }) => {
|
|
733
622
|
try {
|
|
734
|
-
const
|
|
735
|
-
|
|
736
|
-
|
|
623
|
+
const result = await manager.withMiniProgram(connection, async (miniProgram) => {
|
|
624
|
+
const buffer = await captureMiniProgramScreenshot(
|
|
625
|
+
miniProgram,
|
|
626
|
+
connection.timeout ?? DEFAULT_SCREENSHOT_TIMEOUT
|
|
627
|
+
);
|
|
628
|
+
if (outputPath) {
|
|
629
|
+
const resolvedOutputPath = manager.resolveWorkspacePath(outputPath);
|
|
630
|
+
await fs.mkdir(path.dirname(resolvedOutputPath), { recursive: true });
|
|
631
|
+
await fs.writeFile(resolvedOutputPath, buffer);
|
|
632
|
+
return {
|
|
633
|
+
path: resolvedOutputPath,
|
|
634
|
+
bytes: buffer.length
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
base64: buffer.toString("base64"),
|
|
639
|
+
bytes: buffer.length
|
|
640
|
+
};
|
|
641
|
+
});
|
|
642
|
+
return toToolResult(result);
|
|
737
643
|
} catch (error) {
|
|
738
|
-
|
|
739
|
-
if (cachedPage && isCurrentPageRecoverableError(error)) {
|
|
740
|
-
return cachedPage;
|
|
741
|
-
}
|
|
742
|
-
throw error;
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
detach(projectPath) {
|
|
746
|
-
const attached = this.attachedSessions.get(projectPath);
|
|
747
|
-
if (!attached) {
|
|
748
|
-
return;
|
|
749
|
-
}
|
|
750
|
-
if (typeof attached.miniProgram.off === "function") {
|
|
751
|
-
attached.miniProgram.off("console", attached.onConsole);
|
|
752
|
-
attached.miniProgram.off("exception", attached.onException);
|
|
644
|
+
return toToolError(error);
|
|
753
645
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
646
|
+
});
|
|
647
|
+
server.registerTool("weapp_devtools_host_api", {
|
|
648
|
+
title: "Call wx Method",
|
|
649
|
+
description: "\u8C03\u7528\u5FAE\u4FE1\u5C0F\u7A0B\u5E8F wx API\uFF0C\u4F8B\u5982 wx.pageScrollTo\u3002",
|
|
650
|
+
inputSchema: z.object(hostApiSchema)
|
|
651
|
+
}, async ({ method, args, ...connection }) => {
|
|
652
|
+
try {
|
|
653
|
+
const result = await manager.withMiniProgram(connection, async (miniProgram) => {
|
|
654
|
+
const callArgs = args ?? [];
|
|
655
|
+
const callResult = await miniProgram.callWxMethod(method, ...callArgs);
|
|
656
|
+
return {
|
|
657
|
+
method,
|
|
658
|
+
args: toSerializableValue(callArgs),
|
|
659
|
+
result: toSerializableValue(callResult)
|
|
660
|
+
};
|
|
661
|
+
});
|
|
662
|
+
return toToolResult(result);
|
|
663
|
+
} catch (error) {
|
|
664
|
+
return toToolError(error);
|
|
760
665
|
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
if (!search) {
|
|
770
|
-
return normalizedPath;
|
|
771
|
-
}
|
|
772
|
-
return `${normalizedPath}${normalizedPath.includes("?") ? "&" : "?"}${search}`;
|
|
773
|
-
}
|
|
774
|
-
function sleep(ms) {
|
|
775
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
776
|
-
}
|
|
777
|
-
function isCaptureScreenshotProtocolTimeout(error) {
|
|
778
|
-
return error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === "App.captureScreenshot";
|
|
779
|
-
}
|
|
780
|
-
function isCurrentPageRecoverableError(error) {
|
|
781
|
-
if (!(error instanceof Error)) {
|
|
782
|
-
return false;
|
|
783
|
-
}
|
|
784
|
-
if ("code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error) {
|
|
785
|
-
return error.method === "App.getCurrentPage" || error.method === "App.getPageStack" || error.method === "App.callFunction";
|
|
786
|
-
}
|
|
787
|
-
return error.message.includes("page is not on top of page stack");
|
|
788
|
-
}
|
|
789
|
-
async function captureMiniProgramScreenshot(miniProgram, timeout = DEFAULT_SCREENSHOT_TIMEOUT) {
|
|
790
|
-
let lastError;
|
|
791
|
-
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
|
666
|
+
});
|
|
667
|
+
server.registerTool("weapp_devtools_console", {
|
|
668
|
+
title: "Get Mini Program Logs",
|
|
669
|
+
description: "\u8BFB\u53D6 MCP \u4F1A\u8BDD\u6355\u83B7\u5230\u7684\u5C0F\u7A0B\u5E8F console/exception \u65E5\u5FD7\uFF1B\u53EF\u9009\u8BFB\u53D6\u540E\u6E05\u7A7A\u3002",
|
|
670
|
+
inputSchema: z.object({
|
|
671
|
+
clear: z.boolean().optional()
|
|
672
|
+
})
|
|
673
|
+
}, async ({ clear }) => {
|
|
792
674
|
try {
|
|
793
|
-
const
|
|
794
|
-
if (
|
|
795
|
-
|
|
675
|
+
const logs = manager.getLogs();
|
|
676
|
+
if (clear) {
|
|
677
|
+
manager.clearLogs();
|
|
796
678
|
}
|
|
797
|
-
return
|
|
679
|
+
return toToolResult({
|
|
680
|
+
count: logs.length,
|
|
681
|
+
logs
|
|
682
|
+
});
|
|
798
683
|
} catch (error) {
|
|
799
|
-
|
|
800
|
-
if (!isCaptureScreenshotProtocolTimeout(error) || attempt === 2) {
|
|
801
|
-
throw error;
|
|
802
|
-
}
|
|
803
|
-
await miniProgram.currentPage().catch(() => void 0);
|
|
804
|
-
await sleep(SCREENSHOT_RETRY_DELAY);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
throw lastError;
|
|
808
|
-
}
|
|
809
|
-
function parseSelectorWithIndex(selector) {
|
|
810
|
-
const match = selector.match(/^(.+?)\[index=(\d+)\]$/);
|
|
811
|
-
if (!match?.[1] || !match[2]) {
|
|
812
|
-
return {
|
|
813
|
-
selector,
|
|
814
|
-
index: void 0
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
return {
|
|
818
|
-
selector: match[1],
|
|
819
|
-
index: Number.parseInt(match[2], 10)
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
async function resolveElement(page, selectorInput, innerSelector, queryOptions) {
|
|
823
|
-
const { selector, index } = parseSelectorWithIndex(selectorInput);
|
|
824
|
-
let element;
|
|
825
|
-
if (index === void 0) {
|
|
826
|
-
element = await page.$(selector, queryOptions);
|
|
827
|
-
} else {
|
|
828
|
-
const elements = await queryElements(page, selector, queryOptions);
|
|
829
|
-
if (index < 0 || index >= elements.length) {
|
|
830
|
-
throw new Error(`\u9009\u62E9\u5668 "${selector}" \u7684 index=${index} \u8D85\u51FA\u8303\u56F4\uFF0C\u5F53\u524D\u5339\u914D ${elements.length} \u4E2A\u5143\u7D20\u3002`);
|
|
684
|
+
return toToolError(error);
|
|
831
685
|
}
|
|
832
|
-
element = elements[index];
|
|
833
|
-
}
|
|
834
|
-
if (!element) {
|
|
835
|
-
throw new Error(`\u672A\u627E\u5230\u5143\u7D20: ${selectorInput}`);
|
|
836
|
-
}
|
|
837
|
-
if (!innerSelector) {
|
|
838
|
-
return element;
|
|
839
|
-
}
|
|
840
|
-
const inner = await callOptionalMethod(element, "$", innerSelector);
|
|
841
|
-
if (!inner) {
|
|
842
|
-
throw new Error(`\u5728\u5143\u7D20 "${selectorInput}" \u5185\u672A\u627E\u5230\u5143\u7D20: ${innerSelector}`);
|
|
843
|
-
}
|
|
844
|
-
return inner;
|
|
845
|
-
}
|
|
846
|
-
async function queryElements(page, selectorInput, queryOptions) {
|
|
847
|
-
const { selector, index } = parseSelectorWithIndex(selectorInput);
|
|
848
|
-
const elements = await callOptionalMethod(page, "$$", selector, queryOptions);
|
|
849
|
-
if (!Array.isArray(elements)) {
|
|
850
|
-
return [];
|
|
851
|
-
}
|
|
852
|
-
if (index === void 0) {
|
|
853
|
-
return elements;
|
|
854
|
-
}
|
|
855
|
-
if (index < 0 || index >= elements.length) {
|
|
856
|
-
throw new Error(`\u9009\u62E9\u5668 "${selector}" \u7684 index=${index} \u8D85\u51FA\u8303\u56F4\uFF0C\u5F53\u524D\u5339\u914D ${elements.length} \u4E2A\u5143\u7D20\u3002`);
|
|
857
|
-
}
|
|
858
|
-
return [elements[index]];
|
|
859
|
-
}
|
|
860
|
-
async function summarizeElement(element, withWxml = false) {
|
|
861
|
-
const [text, value, size, offset, scrollWidth, scrollHeight, markup] = await Promise.all([
|
|
862
|
-
callMaybe(element, "text"),
|
|
863
|
-
callMaybe(element, "value"),
|
|
864
|
-
callMaybe(element, "size"),
|
|
865
|
-
callMaybe(element, "offset"),
|
|
866
|
-
callMaybe(element, "scrollWidth"),
|
|
867
|
-
callMaybe(element, "scrollHeight"),
|
|
868
|
-
withWxml ? readElementMarkup(element).catch(() => null) : Promise.resolve(null)
|
|
869
|
-
]);
|
|
870
|
-
return compactObject({
|
|
871
|
-
tagName: readProperty(element, "tagName"),
|
|
872
|
-
text,
|
|
873
|
-
value,
|
|
874
|
-
size,
|
|
875
|
-
offset,
|
|
876
|
-
scrollWidth,
|
|
877
|
-
scrollHeight,
|
|
878
|
-
outerWxml: withWxml && markup?.type === "outerWxml" ? markup.wxml : null,
|
|
879
|
-
wxml: withWxml && markup?.type !== "outerWxml" ? markup?.wxml ?? null : void 0,
|
|
880
|
-
wxmlType: withWxml ? markup?.type ?? null : void 0
|
|
881
686
|
});
|
|
882
687
|
}
|
|
883
|
-
async function readElementMarkup(element, outer = false) {
|
|
884
|
-
if (outer) {
|
|
885
|
-
const outerWxml = await callMaybe(element, "outerWxml");
|
|
886
|
-
if (typeof outerWxml === "string") {
|
|
887
|
-
return {
|
|
888
|
-
type: "outerWxml",
|
|
889
|
-
wxml: outerWxml
|
|
890
|
-
};
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
const wxml = await callRequiredMethod(element, "wxml");
|
|
894
|
-
return {
|
|
895
|
-
type: outer ? "wxml-fallback" : "wxml",
|
|
896
|
-
wxml: typeof wxml === "string" ? wxml : String(wxml ?? "")
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
function toSerializableValue(value) {
|
|
900
|
-
return toDevtoolsSerializableValue(value);
|
|
901
|
-
}
|
|
902
|
-
async function callRequiredMethod(target, methodName, ...args) {
|
|
903
|
-
const method = readProperty(target, methodName);
|
|
904
|
-
if (typeof method !== "function") {
|
|
905
|
-
throw new TypeError(`\u5F53\u524D\u5BF9\u8C61\u4E0D\u652F\u6301 ${methodName} \u65B9\u6CD5\u3002`);
|
|
906
|
-
}
|
|
907
|
-
return await method.apply(target, args);
|
|
908
|
-
}
|
|
909
|
-
async function callOptionalMethod(target, methodName, ...args) {
|
|
910
|
-
const method = readProperty(target, methodName);
|
|
911
|
-
if (typeof method !== "function") {
|
|
912
|
-
return void 0;
|
|
913
|
-
}
|
|
914
|
-
return await method.apply(target, args);
|
|
915
|
-
}
|
|
916
|
-
async function callMaybe(target, methodName, ...args) {
|
|
917
|
-
try {
|
|
918
|
-
return await callOptionalMethod(target, methodName, ...args);
|
|
919
|
-
} catch {
|
|
920
|
-
return void 0;
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
function compactObject(input) {
|
|
924
|
-
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
|
|
925
|
-
}
|
|
926
|
-
function readProperty(target, key) {
|
|
927
|
-
if (!target || typeof target !== "object") {
|
|
928
|
-
return void 0;
|
|
929
|
-
}
|
|
930
|
-
return target[key];
|
|
931
|
-
}
|
|
932
|
-
function normalizeConsoleEvent(payload) {
|
|
933
|
-
const record = toRecord$1(payload);
|
|
934
|
-
return {
|
|
935
|
-
level: normalizeLogLevel(record.type ?? record.level ?? record.method),
|
|
936
|
-
message: resolveLogMessage(record, payload),
|
|
937
|
-
timestamp: Date.now(),
|
|
938
|
-
raw: toSerializableValue(payload)
|
|
939
|
-
};
|
|
940
|
-
}
|
|
941
|
-
function normalizeExceptionEvent(payload) {
|
|
942
|
-
const record = toRecord$1(payload);
|
|
943
|
-
const error = toRecord$1(record.error);
|
|
944
|
-
const message = [
|
|
945
|
-
typeof error.message === "string" ? error.message : void 0,
|
|
946
|
-
typeof record.message === "string" ? record.message : void 0,
|
|
947
|
-
typeof error.stack === "string" ? error.stack : void 0,
|
|
948
|
-
typeof record.stack === "string" ? record.stack : void 0
|
|
949
|
-
].filter(Boolean).join("\n");
|
|
950
|
-
return {
|
|
951
|
-
level: "error",
|
|
952
|
-
message: message || JSON.stringify(toSerializableValue(payload)),
|
|
953
|
-
timestamp: Date.now(),
|
|
954
|
-
raw: toSerializableValue(payload)
|
|
955
|
-
};
|
|
956
|
-
}
|
|
957
|
-
function normalizeLogLevel(value) {
|
|
958
|
-
const normalized = String(value ?? "log").toLowerCase();
|
|
959
|
-
if (normalized === "warning") {
|
|
960
|
-
return "warn";
|
|
961
|
-
}
|
|
962
|
-
if (["debug", "log", "info", "warn", "error"].includes(normalized)) {
|
|
963
|
-
return normalized;
|
|
964
|
-
}
|
|
965
|
-
return "log";
|
|
966
|
-
}
|
|
967
|
-
function resolveLogMessage(record, payload) {
|
|
968
|
-
if (typeof record.text === "string" && record.text) {
|
|
969
|
-
return record.text;
|
|
970
|
-
}
|
|
971
|
-
if (typeof record.message === "string" && record.message) {
|
|
972
|
-
return record.message;
|
|
973
|
-
}
|
|
974
|
-
if (Array.isArray(record.args)) {
|
|
975
|
-
return record.args.map(formatLogArgument).join(" ");
|
|
976
|
-
}
|
|
977
|
-
if (typeof payload === "string") {
|
|
978
|
-
return payload;
|
|
979
|
-
}
|
|
980
|
-
return JSON.stringify(toSerializableValue(payload));
|
|
981
|
-
}
|
|
982
|
-
function formatLogArgument(value) {
|
|
983
|
-
const record = toRecord$1(value);
|
|
984
|
-
if (record.value !== void 0) {
|
|
985
|
-
return typeof record.value === "string" ? record.value : JSON.stringify(toSerializableValue(record.value));
|
|
986
|
-
}
|
|
987
|
-
if (typeof record.description === "string") {
|
|
988
|
-
return record.description;
|
|
989
|
-
}
|
|
990
|
-
return typeof value === "string" ? value : JSON.stringify(toSerializableValue(value));
|
|
991
|
-
}
|
|
992
|
-
function toRecord$1(value) {
|
|
993
|
-
return value && typeof value === "object" ? value : {};
|
|
994
|
-
}
|
|
995
|
-
function createUnavailableRuntimeHooks() {
|
|
996
|
-
return {
|
|
997
|
-
async connectMiniProgram() {
|
|
998
|
-
throw new Error("\u672A\u914D\u7F6E DevTools runtime hooks\u3002\u8BF7\u901A\u8FC7 createWeappViteMcpServer({ runtimeHooks }) \u6CE8\u5165 automator \u8FDE\u63A5\u80FD\u529B\u3002");
|
|
999
|
-
}
|
|
1000
|
-
};
|
|
1001
|
-
}
|
|
1002
688
|
|
|
1003
|
-
const
|
|
1004
|
-
...connectionInputSchema,
|
|
1005
|
-
path: z.string().trim().min(1).optional(),
|
|
1006
|
-
query: z.record(z.string(), z.string()).optional(),
|
|
1007
|
-
transition: z.enum(["navigateTo", "redirectTo", "reLaunch", "switchTab", "navigateBack"]).optional(),
|
|
1008
|
-
waitMs: z.number().int().nonnegative().optional()
|
|
1009
|
-
};
|
|
1010
|
-
const screenshotSchema = {
|
|
1011
|
-
...connectionInputSchema,
|
|
1012
|
-
outputPath: z.string().trim().min(1).optional()
|
|
1013
|
-
};
|
|
1014
|
-
const hostApiSchema = {
|
|
689
|
+
const elementSelectorSchema = {
|
|
1015
690
|
...connectionInputSchema,
|
|
1016
|
-
|
|
1017
|
-
|
|
691
|
+
selector: z.string().trim().min(1),
|
|
692
|
+
innerSelector: z.string().trim().min(1).optional()
|
|
1018
693
|
};
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
694
|
+
const nativeElementQuery = { fallback: false };
|
|
695
|
+
function registerRuntimeNodeTools(server, manager) {
|
|
696
|
+
server.registerTool("weapp_runtime_tap_node", {
|
|
697
|
+
title: "Tap Element",
|
|
698
|
+
description: "\u70B9\u51FB\u9875\u9762\u5143\u7D20\uFF0C\u652F\u6301 selector[index=N]\u3001innerSelector \u548C\u70B9\u51FB\u540E\u7B49\u5F85\u3002",
|
|
1023
699
|
inputSchema: z.object({
|
|
1024
|
-
...
|
|
1025
|
-
|
|
700
|
+
...elementSelectorSchema,
|
|
701
|
+
waitMs: z.number().int().nonnegative().optional()
|
|
1026
702
|
})
|
|
1027
|
-
}, async ({
|
|
703
|
+
}, async ({ selector, innerSelector, waitMs, ...connection }) => {
|
|
1028
704
|
try {
|
|
1029
|
-
|
|
1030
|
-
await
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
const systemInfo = await miniProgram.systemInfo().catch(() => null);
|
|
705
|
+
const result = await manager.withPage(connection, async (page) => {
|
|
706
|
+
const element = await resolveElement(page, selector, innerSelector, nativeElementQuery);
|
|
707
|
+
await element.tap();
|
|
708
|
+
if (waitMs) {
|
|
709
|
+
await page.waitFor(waitMs);
|
|
710
|
+
}
|
|
1036
711
|
return {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
currentPage: page ? { path: page.path, query: toSerializableValue(page.query) } : null,
|
|
1041
|
-
systemInfo: toSerializableValue(systemInfo)
|
|
712
|
+
selector,
|
|
713
|
+
innerSelector: innerSelector ?? null,
|
|
714
|
+
waitedMs: waitMs ?? 0
|
|
1042
715
|
};
|
|
1043
716
|
});
|
|
1044
717
|
return toToolResult(result);
|
|
@@ -1046,41 +719,22 @@ function registerDevtoolsRuntimeTools(server, manager) {
|
|
|
1046
719
|
return toToolError(error);
|
|
1047
720
|
}
|
|
1048
721
|
});
|
|
1049
|
-
server.registerTool("
|
|
1050
|
-
title: "
|
|
1051
|
-
description: "\
|
|
1052
|
-
inputSchema: z.object(
|
|
1053
|
-
|
|
722
|
+
server.registerTool("weapp_runtime_input_node", {
|
|
723
|
+
title: "Input Element Text",
|
|
724
|
+
description: "\u5411 input \u6216 textarea \u5143\u7D20\u8F93\u5165\u6587\u672C\u3002",
|
|
725
|
+
inputSchema: z.object({
|
|
726
|
+
...elementSelectorSchema,
|
|
727
|
+
value: z.union([z.string(), z.number()])
|
|
728
|
+
})
|
|
729
|
+
}, async ({ selector, innerSelector, value, ...connection }) => {
|
|
1054
730
|
try {
|
|
1055
|
-
const result = await manager.
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
manager.rememberPage(connection, page2);
|
|
1059
|
-
if (waitMs && page2) {
|
|
1060
|
-
await page2.waitFor(waitMs);
|
|
1061
|
-
}
|
|
1062
|
-
return {
|
|
1063
|
-
transition,
|
|
1064
|
-
activePage: page2 ? { path: page2.path, query: toSerializableValue(page2.query) } : null
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
|
-
if (!pagePath) {
|
|
1068
|
-
throw new Error("transition \u4E0D\u662F navigateBack \u65F6\u5FC5\u987B\u63D0\u4F9B path\u3002");
|
|
1069
|
-
}
|
|
1070
|
-
const url = buildUrl(pagePath, query);
|
|
1071
|
-
const page = await callRequiredMethod(
|
|
1072
|
-
miniProgram,
|
|
1073
|
-
transition,
|
|
1074
|
-
url
|
|
1075
|
-
);
|
|
1076
|
-
manager.rememberPage(connection, page);
|
|
1077
|
-
if (waitMs && page) {
|
|
1078
|
-
await page.waitFor(waitMs);
|
|
1079
|
-
}
|
|
731
|
+
const result = await manager.withPage(connection, async (page) => {
|
|
732
|
+
const element = await resolveElement(page, selector, innerSelector, nativeElementQuery);
|
|
733
|
+
await callRequiredMethod(element, "input", String(value));
|
|
1080
734
|
return {
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
735
|
+
selector,
|
|
736
|
+
innerSelector: innerSelector ?? null,
|
|
737
|
+
value: String(value)
|
|
1084
738
|
};
|
|
1085
739
|
});
|
|
1086
740
|
return toToolResult(result);
|
|
@@ -1088,200 +742,26 @@ function registerDevtoolsRuntimeTools(server, manager) {
|
|
|
1088
742
|
return toToolError(error);
|
|
1089
743
|
}
|
|
1090
744
|
});
|
|
1091
|
-
server.registerTool("
|
|
1092
|
-
title: "
|
|
1093
|
-
description: "\
|
|
745
|
+
server.registerTool("weapp_runtime_invoke_component", {
|
|
746
|
+
title: "Call Element Method",
|
|
747
|
+
description: "\u8C03\u7528\u81EA\u5B9A\u4E49\u7EC4\u4EF6\u5143\u7D20\u5B9E\u4F8B\u65B9\u6CD5\u3002",
|
|
1094
748
|
inputSchema: z.object({
|
|
1095
|
-
...
|
|
1096
|
-
|
|
749
|
+
...elementSelectorSchema,
|
|
750
|
+
method: z.string().trim().min(1),
|
|
751
|
+
args: z.array(z.unknown()).optional()
|
|
1097
752
|
})
|
|
1098
|
-
}, async ({
|
|
753
|
+
}, async ({ selector, innerSelector, method, args, ...connection }) => {
|
|
1099
754
|
try {
|
|
1100
755
|
const result = await manager.withPage(connection, async (page) => {
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
scrollTop: toSerializableValue(scrollTop),
|
|
1111
|
-
data: toSerializableValue(data)
|
|
1112
|
-
});
|
|
1113
|
-
});
|
|
1114
|
-
return toToolResult(result);
|
|
1115
|
-
} catch (error) {
|
|
1116
|
-
return toToolError(error);
|
|
1117
|
-
}
|
|
1118
|
-
});
|
|
1119
|
-
server.registerTool("weapp_devtools_page_stack", {
|
|
1120
|
-
title: "Mini Program Page Stack",
|
|
1121
|
-
description: "\u83B7\u53D6\u5F53\u524D\u5C0F\u7A0B\u5E8F\u9875\u9762\u6808\u3002",
|
|
1122
|
-
inputSchema: connectionSchema$1
|
|
1123
|
-
}, async (connection) => {
|
|
1124
|
-
try {
|
|
1125
|
-
const result = await manager.withMiniProgram(connection, async (miniProgram) => {
|
|
1126
|
-
const stack = await miniProgram.pageStack();
|
|
1127
|
-
return stack.map((page) => ({
|
|
1128
|
-
path: page.path,
|
|
1129
|
-
query: toSerializableValue(page.query)
|
|
1130
|
-
}));
|
|
1131
|
-
});
|
|
1132
|
-
return toToolResult(result);
|
|
1133
|
-
} catch (error) {
|
|
1134
|
-
return toToolError(error);
|
|
1135
|
-
}
|
|
1136
|
-
});
|
|
1137
|
-
server.registerTool("weapp_devtools_capture", {
|
|
1138
|
-
title: "Mini Program Screenshot",
|
|
1139
|
-
description: "\u622A\u53D6\u5F53\u524D\u5C0F\u7A0B\u5E8F\u89C6\u53E3\uFF0C\u8FD4\u56DE base64\uFF0C\u6216\u4FDD\u5B58\u5230 workspaceRoot \u76F8\u5BF9 outputPath\u3002",
|
|
1140
|
-
inputSchema: z.object(screenshotSchema)
|
|
1141
|
-
}, async ({ outputPath, ...connection }) => {
|
|
1142
|
-
try {
|
|
1143
|
-
const result = await manager.withMiniProgram(connection, async (miniProgram) => {
|
|
1144
|
-
const buffer = await captureMiniProgramScreenshot(
|
|
1145
|
-
miniProgram,
|
|
1146
|
-
connection.timeout ?? DEFAULT_SCREENSHOT_TIMEOUT
|
|
1147
|
-
);
|
|
1148
|
-
if (outputPath) {
|
|
1149
|
-
const resolvedOutputPath = manager.resolveWorkspacePath(outputPath);
|
|
1150
|
-
await fs$1.mkdir(path.dirname(resolvedOutputPath), { recursive: true });
|
|
1151
|
-
await fs$1.writeFile(resolvedOutputPath, buffer);
|
|
1152
|
-
return {
|
|
1153
|
-
path: resolvedOutputPath,
|
|
1154
|
-
bytes: buffer.length
|
|
1155
|
-
};
|
|
1156
|
-
}
|
|
1157
|
-
return {
|
|
1158
|
-
base64: buffer.toString("base64"),
|
|
1159
|
-
bytes: buffer.length
|
|
1160
|
-
};
|
|
1161
|
-
});
|
|
1162
|
-
return toToolResult(result);
|
|
1163
|
-
} catch (error) {
|
|
1164
|
-
return toToolError(error);
|
|
1165
|
-
}
|
|
1166
|
-
});
|
|
1167
|
-
server.registerTool("weapp_devtools_host_api", {
|
|
1168
|
-
title: "Call wx Method",
|
|
1169
|
-
description: "\u8C03\u7528\u5FAE\u4FE1\u5C0F\u7A0B\u5E8F wx API\uFF0C\u4F8B\u5982 wx.pageScrollTo\u3002",
|
|
1170
|
-
inputSchema: z.object(hostApiSchema)
|
|
1171
|
-
}, async ({ method, args, ...connection }) => {
|
|
1172
|
-
try {
|
|
1173
|
-
const result = await manager.withMiniProgram(connection, async (miniProgram) => {
|
|
1174
|
-
const callArgs = args ?? [];
|
|
1175
|
-
const callResult = await miniProgram.callWxMethod(method, ...callArgs);
|
|
1176
|
-
return {
|
|
1177
|
-
method,
|
|
1178
|
-
args: toSerializableValue(callArgs),
|
|
1179
|
-
result: toSerializableValue(callResult)
|
|
1180
|
-
};
|
|
1181
|
-
});
|
|
1182
|
-
return toToolResult(result);
|
|
1183
|
-
} catch (error) {
|
|
1184
|
-
return toToolError(error);
|
|
1185
|
-
}
|
|
1186
|
-
});
|
|
1187
|
-
server.registerTool("weapp_devtools_console", {
|
|
1188
|
-
title: "Get Mini Program Logs",
|
|
1189
|
-
description: "\u8BFB\u53D6 MCP \u4F1A\u8BDD\u6355\u83B7\u5230\u7684\u5C0F\u7A0B\u5E8F console/exception \u65E5\u5FD7\uFF1B\u53EF\u9009\u8BFB\u53D6\u540E\u6E05\u7A7A\u3002",
|
|
1190
|
-
inputSchema: z.object({
|
|
1191
|
-
clear: z.boolean().optional()
|
|
1192
|
-
})
|
|
1193
|
-
}, async ({ clear }) => {
|
|
1194
|
-
try {
|
|
1195
|
-
const logs = manager.getLogs();
|
|
1196
|
-
if (clear) {
|
|
1197
|
-
manager.clearLogs();
|
|
1198
|
-
}
|
|
1199
|
-
return toToolResult({
|
|
1200
|
-
count: logs.length,
|
|
1201
|
-
logs
|
|
1202
|
-
});
|
|
1203
|
-
} catch (error) {
|
|
1204
|
-
return toToolError(error);
|
|
1205
|
-
}
|
|
1206
|
-
});
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
const elementSelectorSchema = {
|
|
1210
|
-
...connectionInputSchema,
|
|
1211
|
-
selector: z.string().trim().min(1),
|
|
1212
|
-
innerSelector: z.string().trim().min(1).optional()
|
|
1213
|
-
};
|
|
1214
|
-
const nativeElementQuery = { fallback: false };
|
|
1215
|
-
function registerRuntimeNodeTools(server, manager) {
|
|
1216
|
-
server.registerTool("weapp_runtime_tap_node", {
|
|
1217
|
-
title: "Tap Element",
|
|
1218
|
-
description: "\u70B9\u51FB\u9875\u9762\u5143\u7D20\uFF0C\u652F\u6301 selector[index=N]\u3001innerSelector \u548C\u70B9\u51FB\u540E\u7B49\u5F85\u3002",
|
|
1219
|
-
inputSchema: z.object({
|
|
1220
|
-
...elementSelectorSchema,
|
|
1221
|
-
waitMs: z.number().int().nonnegative().optional()
|
|
1222
|
-
})
|
|
1223
|
-
}, async ({ selector, innerSelector, waitMs, ...connection }) => {
|
|
1224
|
-
try {
|
|
1225
|
-
const result = await manager.withPage(connection, async (page) => {
|
|
1226
|
-
const element = await resolveElement(page, selector, innerSelector, nativeElementQuery);
|
|
1227
|
-
await element.tap();
|
|
1228
|
-
if (waitMs) {
|
|
1229
|
-
await page.waitFor(waitMs);
|
|
1230
|
-
}
|
|
1231
|
-
return {
|
|
1232
|
-
selector,
|
|
1233
|
-
innerSelector: innerSelector ?? null,
|
|
1234
|
-
waitedMs: waitMs ?? 0
|
|
1235
|
-
};
|
|
1236
|
-
});
|
|
1237
|
-
return toToolResult(result);
|
|
1238
|
-
} catch (error) {
|
|
1239
|
-
return toToolError(error);
|
|
1240
|
-
}
|
|
1241
|
-
});
|
|
1242
|
-
server.registerTool("weapp_runtime_input_node", {
|
|
1243
|
-
title: "Input Element Text",
|
|
1244
|
-
description: "\u5411 input \u6216 textarea \u5143\u7D20\u8F93\u5165\u6587\u672C\u3002",
|
|
1245
|
-
inputSchema: z.object({
|
|
1246
|
-
...elementSelectorSchema,
|
|
1247
|
-
value: z.union([z.string(), z.number()])
|
|
1248
|
-
})
|
|
1249
|
-
}, async ({ selector, innerSelector, value, ...connection }) => {
|
|
1250
|
-
try {
|
|
1251
|
-
const result = await manager.withPage(connection, async (page) => {
|
|
1252
|
-
const element = await resolveElement(page, selector, innerSelector, nativeElementQuery);
|
|
1253
|
-
await callRequiredMethod(element, "input", String(value));
|
|
1254
|
-
return {
|
|
1255
|
-
selector,
|
|
1256
|
-
innerSelector: innerSelector ?? null,
|
|
1257
|
-
value: String(value)
|
|
1258
|
-
};
|
|
1259
|
-
});
|
|
1260
|
-
return toToolResult(result);
|
|
1261
|
-
} catch (error) {
|
|
1262
|
-
return toToolError(error);
|
|
1263
|
-
}
|
|
1264
|
-
});
|
|
1265
|
-
server.registerTool("weapp_runtime_invoke_component", {
|
|
1266
|
-
title: "Call Element Method",
|
|
1267
|
-
description: "\u8C03\u7528\u81EA\u5B9A\u4E49\u7EC4\u4EF6\u5143\u7D20\u5B9E\u4F8B\u65B9\u6CD5\u3002",
|
|
1268
|
-
inputSchema: z.object({
|
|
1269
|
-
...elementSelectorSchema,
|
|
1270
|
-
method: z.string().trim().min(1),
|
|
1271
|
-
args: z.array(z.unknown()).optional()
|
|
1272
|
-
})
|
|
1273
|
-
}, async ({ selector, innerSelector, method, args, ...connection }) => {
|
|
1274
|
-
try {
|
|
1275
|
-
const result = await manager.withPage(connection, async (page) => {
|
|
1276
|
-
const element = await resolveElement(page, selector, innerSelector, nativeElementQuery);
|
|
1277
|
-
const callArgs = args ?? [];
|
|
1278
|
-
return {
|
|
1279
|
-
selector,
|
|
1280
|
-
innerSelector: innerSelector ?? null,
|
|
1281
|
-
method,
|
|
1282
|
-
args: toSerializableValue(callArgs),
|
|
1283
|
-
result: toSerializableValue(await callRequiredMethod(element, "callMethod", method, ...callArgs))
|
|
1284
|
-
};
|
|
756
|
+
const element = await resolveElement(page, selector, innerSelector, nativeElementQuery);
|
|
757
|
+
const callArgs = args ?? [];
|
|
758
|
+
return {
|
|
759
|
+
selector,
|
|
760
|
+
innerSelector: innerSelector ?? null,
|
|
761
|
+
method,
|
|
762
|
+
args: toSerializableValue(callArgs),
|
|
763
|
+
result: toSerializableValue(await callRequiredMethod(element, "callMethod", method, ...callArgs))
|
|
764
|
+
};
|
|
1285
765
|
});
|
|
1286
766
|
return toToolResult(result);
|
|
1287
767
|
} catch (error) {
|
|
@@ -1567,198 +1047,900 @@ function registerRuntimePageTools(server, manager) {
|
|
|
1567
1047
|
return toToolError(error);
|
|
1568
1048
|
}
|
|
1569
1049
|
});
|
|
1570
|
-
server.registerTool("weapp_runtime_find_nodes", {
|
|
1571
|
-
title: "Get Page Elements",
|
|
1572
|
-
description: "\u901A\u8FC7\u9009\u62E9\u5668\u83B7\u53D6\u5F53\u524D\u9875\u9762\u5143\u7D20\u6570\u7EC4\u6458\u8981\uFF0C\u652F\u6301 selector[index=N] \u4E0E withWxml\u3002",
|
|
1573
|
-
inputSchema: z.object({
|
|
1574
|
-
...connectionInputSchema,
|
|
1575
|
-
selector: z.string().trim().min(1),
|
|
1576
|
-
withWxml: z.boolean().optional()
|
|
1577
|
-
})
|
|
1578
|
-
}, async ({ selector, withWxml, ...connection }) => {
|
|
1579
|
-
try {
|
|
1580
|
-
const result = await manager.withPage(connection, async (page) => {
|
|
1581
|
-
const elements = await queryElements(page, selector);
|
|
1582
|
-
return {
|
|
1583
|
-
selector,
|
|
1584
|
-
count: elements.length,
|
|
1585
|
-
elements: await Promise.all(elements.map(async (element, index) => ({
|
|
1586
|
-
index,
|
|
1587
|
-
...await summarizeElement(element, withWxml)
|
|
1588
|
-
})))
|
|
1589
|
-
};
|
|
1590
|
-
});
|
|
1591
|
-
return toToolResult(result);
|
|
1592
|
-
} catch (error) {
|
|
1593
|
-
return toToolError(error);
|
|
1594
|
-
}
|
|
1050
|
+
server.registerTool("weapp_runtime_find_nodes", {
|
|
1051
|
+
title: "Get Page Elements",
|
|
1052
|
+
description: "\u901A\u8FC7\u9009\u62E9\u5668\u83B7\u53D6\u5F53\u524D\u9875\u9762\u5143\u7D20\u6570\u7EC4\u6458\u8981\uFF0C\u652F\u6301 selector[index=N] \u4E0E withWxml\u3002",
|
|
1053
|
+
inputSchema: z.object({
|
|
1054
|
+
...connectionInputSchema,
|
|
1055
|
+
selector: z.string().trim().min(1),
|
|
1056
|
+
withWxml: z.boolean().optional()
|
|
1057
|
+
})
|
|
1058
|
+
}, async ({ selector, withWxml, ...connection }) => {
|
|
1059
|
+
try {
|
|
1060
|
+
const result = await manager.withPage(connection, async (page) => {
|
|
1061
|
+
const elements = await queryElements(page, selector);
|
|
1062
|
+
return {
|
|
1063
|
+
selector,
|
|
1064
|
+
count: elements.length,
|
|
1065
|
+
elements: await Promise.all(elements.map(async (element, index) => ({
|
|
1066
|
+
index,
|
|
1067
|
+
...await summarizeElement(element, withWxml)
|
|
1068
|
+
})))
|
|
1069
|
+
};
|
|
1070
|
+
});
|
|
1071
|
+
return toToolResult(result);
|
|
1072
|
+
} catch (error) {
|
|
1073
|
+
return toToolError(error);
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
server.registerTool("weapp_runtime_find_node_by_xpath", {
|
|
1077
|
+
title: "Get Page Element by XPath",
|
|
1078
|
+
description: "\u901A\u8FC7 XPath \u83B7\u53D6\u5F53\u524D\u9875\u9762\u7B2C\u4E00\u4E2A\u5339\u914D\u5143\u7D20\uFF0C\u9002\u5408\u6309\u6587\u672C\u3001\u5C5E\u6027\u6216\u5C42\u7EA7\u5173\u7CFB\u5B9A\u4F4D\u3002",
|
|
1079
|
+
inputSchema: z.object(xpathSchema)
|
|
1080
|
+
}, async ({ xpath, withWxml, ...connection }) => {
|
|
1081
|
+
try {
|
|
1082
|
+
const result = await manager.withPage(connection, async (page) => {
|
|
1083
|
+
const element = await callRequiredMethod(page, "getElementByXpath", xpath);
|
|
1084
|
+
if (!element) {
|
|
1085
|
+
throw new Error(`XPath \u672A\u627E\u5230\u5143\u7D20: "${xpath}"`);
|
|
1086
|
+
}
|
|
1087
|
+
return {
|
|
1088
|
+
xpath,
|
|
1089
|
+
...await summarizeElement(element, withWxml)
|
|
1090
|
+
};
|
|
1091
|
+
});
|
|
1092
|
+
return toToolResult(result);
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
return toToolError(error);
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
server.registerTool("weapp_runtime_find_nodes_by_xpath", {
|
|
1098
|
+
title: "Get Page Elements by XPath",
|
|
1099
|
+
description: "\u901A\u8FC7 XPath \u83B7\u53D6\u5F53\u524D\u9875\u9762\u5168\u90E8\u5339\u914D\u5143\u7D20\uFF0C\u9002\u5408\u6279\u91CF\u68C0\u67E5\u7ED3\u6784\u4E0E\u5185\u5BB9\u3002",
|
|
1100
|
+
inputSchema: z.object(xpathSchema)
|
|
1101
|
+
}, async ({ xpath, withWxml, ...connection }) => {
|
|
1102
|
+
try {
|
|
1103
|
+
const result = await manager.withPage(connection, async (page) => {
|
|
1104
|
+
const elements = await callRequiredMethod(page, "getElementsByXpath", xpath);
|
|
1105
|
+
return {
|
|
1106
|
+
xpath,
|
|
1107
|
+
count: elements.length,
|
|
1108
|
+
elements: await Promise.all(elements.map(async (element, index) => ({
|
|
1109
|
+
index,
|
|
1110
|
+
...await summarizeElement(element, withWxml)
|
|
1111
|
+
})))
|
|
1112
|
+
};
|
|
1113
|
+
});
|
|
1114
|
+
return toToolResult(result);
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
return toToolError(error);
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
server.registerTool("weapp_runtime_wait_node", {
|
|
1120
|
+
title: "Wait Page Element",
|
|
1121
|
+
description: "\u8F6E\u8BE2\u7B49\u5F85\u5143\u7D20\u51FA\u73B0\uFF0C\u652F\u6301 selector[index=N]\u3002",
|
|
1122
|
+
inputSchema: z.object({
|
|
1123
|
+
...connectionInputSchema,
|
|
1124
|
+
selector: z.string().trim().min(1),
|
|
1125
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
1126
|
+
intervalMs: z.number().int().positive().optional()
|
|
1127
|
+
})
|
|
1128
|
+
}, async ({ selector, timeoutMs = 5e3, intervalMs = 200, ...connection }) => {
|
|
1129
|
+
try {
|
|
1130
|
+
const result = await manager.withPage(connection, async (page) => {
|
|
1131
|
+
const startedAt = Date.now();
|
|
1132
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
1133
|
+
const elements = await queryElements(page, selector).catch(() => []);
|
|
1134
|
+
if (elements.length > 0) {
|
|
1135
|
+
return {
|
|
1136
|
+
selector,
|
|
1137
|
+
found: true,
|
|
1138
|
+
count: elements.length,
|
|
1139
|
+
waitMs: Date.now() - startedAt
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
await page.waitFor(intervalMs);
|
|
1143
|
+
}
|
|
1144
|
+
throw new Error(`\u7B49\u5F85\u5143\u7D20 "${selector}" \u8D85\u65F6 (${timeoutMs}ms)\u3002`);
|
|
1145
|
+
});
|
|
1146
|
+
return toToolResult(result);
|
|
1147
|
+
} catch (error) {
|
|
1148
|
+
return toToolError(error);
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
server.registerTool("weapp_runtime_wait", {
|
|
1152
|
+
title: "Wait Page Timeout",
|
|
1153
|
+
description: "\u5728\u5F53\u524D\u9875\u9762\u7B49\u5F85\u6307\u5B9A\u6BEB\u79D2\u6570\u3002",
|
|
1154
|
+
inputSchema: z.object({
|
|
1155
|
+
...connectionInputSchema,
|
|
1156
|
+
milliseconds: z.number().int().nonnegative()
|
|
1157
|
+
})
|
|
1158
|
+
}, async ({ milliseconds, ...connection }) => {
|
|
1159
|
+
try {
|
|
1160
|
+
const result = await manager.withPage(connection, async (page) => {
|
|
1161
|
+
await page.waitFor(milliseconds);
|
|
1162
|
+
return { waitedMs: milliseconds };
|
|
1163
|
+
});
|
|
1164
|
+
return toToolResult(result);
|
|
1165
|
+
} catch (error) {
|
|
1166
|
+
return toToolError(error);
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
server.registerTool("weapp_runtime_page_state", {
|
|
1170
|
+
title: "Get Page Data",
|
|
1171
|
+
description: "\u8BFB\u53D6\u5F53\u524D\u9875\u9762 data\uFF0C\u53EF\u901A\u8FC7 path \u8BFB\u53D6\u5D4C\u5957\u5B57\u6BB5\u3002",
|
|
1172
|
+
inputSchema: z.object({
|
|
1173
|
+
...connectionInputSchema,
|
|
1174
|
+
path: z.string().trim().min(1).optional()
|
|
1175
|
+
})
|
|
1176
|
+
}, async ({ path, ...connection }) => {
|
|
1177
|
+
try {
|
|
1178
|
+
const result = await manager.withPage(connection, async (page) => ({
|
|
1179
|
+
path: path ?? null,
|
|
1180
|
+
data: toSerializableValue(await page.data(path))
|
|
1181
|
+
}));
|
|
1182
|
+
return toToolResult(result);
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
return toToolError(error);
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
server.registerTool("weapp_runtime_update_page_state", {
|
|
1188
|
+
title: "Set Page Data",
|
|
1189
|
+
description: "\u8C03\u7528\u5F53\u524D\u9875\u9762 setData \u66F4\u65B0 data\u3002",
|
|
1190
|
+
inputSchema: z.object({
|
|
1191
|
+
...connectionInputSchema,
|
|
1192
|
+
data: z.record(z.string(), z.unknown()),
|
|
1193
|
+
verify: z.boolean().optional()
|
|
1194
|
+
})
|
|
1195
|
+
}, async ({ data, verify, ...connection }) => {
|
|
1196
|
+
try {
|
|
1197
|
+
const result = await manager.withPage(connection, async (page) => {
|
|
1198
|
+
await callRequiredMethod(page, "setData", data);
|
|
1199
|
+
return {
|
|
1200
|
+
keys: Object.keys(data),
|
|
1201
|
+
data: verify ? toSerializableValue(await page.data()) : void 0
|
|
1202
|
+
};
|
|
1203
|
+
});
|
|
1204
|
+
return toToolResult(result);
|
|
1205
|
+
} catch (error) {
|
|
1206
|
+
return toToolError(error);
|
|
1207
|
+
}
|
|
1208
|
+
});
|
|
1209
|
+
server.registerTool("weapp_runtime_invoke_page", {
|
|
1210
|
+
title: "Call Page Method",
|
|
1211
|
+
description: "\u8C03\u7528\u5F53\u524D\u9875\u9762\u5B9E\u4F8B\u65B9\u6CD5\u3002",
|
|
1212
|
+
inputSchema: z.object({
|
|
1213
|
+
...connectionInputSchema,
|
|
1214
|
+
method: z.string().trim().min(1),
|
|
1215
|
+
args: z.array(z.unknown()).optional()
|
|
1216
|
+
})
|
|
1217
|
+
}, async ({ method, args, ...connection }) => {
|
|
1218
|
+
try {
|
|
1219
|
+
const result = await manager.withPage(connection, async (page) => {
|
|
1220
|
+
const callArgs = args ?? [];
|
|
1221
|
+
return {
|
|
1222
|
+
method,
|
|
1223
|
+
args: toSerializableValue(callArgs),
|
|
1224
|
+
result: toSerializableValue(await callRequiredMethod(page, "callMethod", method, ...callArgs))
|
|
1225
|
+
};
|
|
1226
|
+
});
|
|
1227
|
+
return toToolResult(result);
|
|
1228
|
+
} catch (error) {
|
|
1229
|
+
return toToolError(error);
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
function registerRuntimeTools(server, options) {
|
|
1235
|
+
const manager = options.manager ?? new RuntimeSessionManager(options.workspaceRoot, options.runtimeHooks);
|
|
1236
|
+
registerDevtoolsRuntimeTools(server, manager);
|
|
1237
|
+
registerRuntimePageTools(server, manager);
|
|
1238
|
+
registerRuntimeNodeTools(server, manager);
|
|
1239
|
+
return manager;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
function acceptanceRuntime(parent) {
|
|
1243
|
+
return async (_server, context) => {
|
|
1244
|
+
const lease = await acquireRuntimeLease(context.root);
|
|
1245
|
+
const manager = parent.fork(context.root);
|
|
1246
|
+
const cursor = manager.getLogCursor();
|
|
1247
|
+
const tools = [];
|
|
1248
|
+
try {
|
|
1249
|
+
const collector = {
|
|
1250
|
+
registerTool(name, definition, handler) {
|
|
1251
|
+
const shape = definition.inputSchema;
|
|
1252
|
+
const schema = shape && typeof shape.parse === "function" ? shape : z.object(shape ?? {});
|
|
1253
|
+
tools.push({ name: `weapp__${name}`, description: definition.description ?? name, schema, mutates: true, async execute(input) {
|
|
1254
|
+
context.signal.throwIfAborted();
|
|
1255
|
+
return runWithRuntimeLease(lease, async () => {
|
|
1256
|
+
if (name === "weapp_devtools_console") {
|
|
1257
|
+
const result = { logs: manager.getLogs(context.root, cursor) };
|
|
1258
|
+
return { text: JSON.stringify({ result }), data: { result } };
|
|
1259
|
+
}
|
|
1260
|
+
const parsed = schema.parse(input);
|
|
1261
|
+
if (name === "weapp_devtools_connect") {
|
|
1262
|
+
await manager.prepareProject(context.root, context.signal);
|
|
1263
|
+
context.signal.throwIfAborted();
|
|
1264
|
+
await parent.withMiniProgram(parsed, async () => {
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
context.signal.throwIfAborted();
|
|
1268
|
+
const response = await handler(parsed);
|
|
1269
|
+
if (response.isError) {
|
|
1270
|
+
throw new Error(JSON.stringify(response.content));
|
|
1271
|
+
}
|
|
1272
|
+
const text = response.content?.filter((c) => c.type === "text").map((c) => c.text).join("\n") ?? "";
|
|
1273
|
+
return { text, data: response.structuredContent ?? JSON.parse(text) };
|
|
1274
|
+
});
|
|
1275
|
+
} });
|
|
1276
|
+
return {};
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
registerRuntimeTools(collector, { manager, workspaceRoot: context.root });
|
|
1280
|
+
return { tools, close: async () => {
|
|
1281
|
+
try {
|
|
1282
|
+
await manager.dispose();
|
|
1283
|
+
} finally {
|
|
1284
|
+
await lease.release();
|
|
1285
|
+
}
|
|
1286
|
+
} };
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
try {
|
|
1289
|
+
await manager.dispose();
|
|
1290
|
+
} finally {
|
|
1291
|
+
await lease.release();
|
|
1292
|
+
}
|
|
1293
|
+
throw error;
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
async function createRuntimeAcceptanceService(root, options = {}, hooks) {
|
|
1298
|
+
const manager = new RuntimeSessionManager(root, hooks);
|
|
1299
|
+
const service = await AcceptanceService.create(root, { ...options, connect: options.connect ?? acceptanceRuntime(manager) });
|
|
1300
|
+
const close = service.close.bind(service);
|
|
1301
|
+
service.close = async () => {
|
|
1302
|
+
try {
|
|
1303
|
+
await close();
|
|
1304
|
+
} finally {
|
|
1305
|
+
await manager.dispose();
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
return service;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
const MCP_SERVER_NAME = "@weapp-vite/mcp";
|
|
1312
|
+
const MCP_SERVER_VERSION = "2.0.0";
|
|
1313
|
+
const EXPOSED_PACKAGES = {
|
|
1314
|
+
"weapp-vite": {
|
|
1315
|
+
id: "weapp-vite",
|
|
1316
|
+
label: "weapp-vite",
|
|
1317
|
+
packageName: "weapp-vite",
|
|
1318
|
+
relativePath: path.join("packages", "weapp-vite")
|
|
1319
|
+
},
|
|
1320
|
+
"wevu": {
|
|
1321
|
+
id: "wevu",
|
|
1322
|
+
label: "wevu",
|
|
1323
|
+
packageName: "wevu",
|
|
1324
|
+
relativePath: path.join("packages-runtime", "wevu")
|
|
1325
|
+
},
|
|
1326
|
+
"wevu-compiler": {
|
|
1327
|
+
id: "wevu-compiler",
|
|
1328
|
+
label: "wevu-compiler",
|
|
1329
|
+
packageName: "@wevu/compiler",
|
|
1330
|
+
relativePath: path.join("packages-runtime", "wevu-compiler")
|
|
1331
|
+
}
|
|
1332
|
+
};
|
|
1333
|
+
const DEFAULT_TIMEOUT_MS = 12e4;
|
|
1334
|
+
const DEFAULT_MAX_OUTPUT_CHARS = 12e4;
|
|
1335
|
+
const DEFAULT_MAX_FILE_CHARS = 8e4;
|
|
1336
|
+
const DEFAULT_MAX_RESULTS = 200;
|
|
1337
|
+
const SKIPPED_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
1338
|
+
"node_modules",
|
|
1339
|
+
".git",
|
|
1340
|
+
".turbo",
|
|
1341
|
+
"dist",
|
|
1342
|
+
"coverage"
|
|
1343
|
+
]);
|
|
1344
|
+
|
|
1345
|
+
function hasWorkspaceMarkers(dir) {
|
|
1346
|
+
return fs$1.existsSync(path.join(dir, "pnpm-workspace.yaml")) && fs$1.existsSync(path.join(dir, "package.json"));
|
|
1347
|
+
}
|
|
1348
|
+
function resolveWorkspaceRoot(start = process.cwd()) {
|
|
1349
|
+
let current = path.resolve(start);
|
|
1350
|
+
while (true) {
|
|
1351
|
+
if (hasWorkspaceMarkers(current)) {
|
|
1352
|
+
return current;
|
|
1353
|
+
}
|
|
1354
|
+
const parent = path.dirname(current);
|
|
1355
|
+
if (parent === current) {
|
|
1356
|
+
return path.resolve(start);
|
|
1357
|
+
}
|
|
1358
|
+
current = parent;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
function assertInsideRoot(root, targetPath) {
|
|
1362
|
+
const resolvedRoot = path.resolve(root);
|
|
1363
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
1364
|
+
const relative = path.relative(resolvedRoot, resolvedTarget);
|
|
1365
|
+
if (relative === "" || relative === ".") {
|
|
1366
|
+
return resolvedTarget;
|
|
1367
|
+
}
|
|
1368
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
1369
|
+
throw new Error(`\u8DEF\u5F84\u8D8A\u754C\uFF1A${targetPath}`);
|
|
1370
|
+
}
|
|
1371
|
+
return resolvedTarget;
|
|
1372
|
+
}
|
|
1373
|
+
function resolveSubPath(root, relativePath) {
|
|
1374
|
+
if (!relativePath || relativePath === ".") {
|
|
1375
|
+
return path.resolve(root);
|
|
1376
|
+
}
|
|
1377
|
+
if (path.isAbsolute(relativePath)) {
|
|
1378
|
+
throw new Error("\u4EC5\u652F\u6301\u76F8\u5BF9\u8DEF\u5F84");
|
|
1379
|
+
}
|
|
1380
|
+
return assertInsideRoot(root, path.resolve(root, relativePath));
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
async function pathExists(filePath) {
|
|
1384
|
+
try {
|
|
1385
|
+
await fs.access(filePath);
|
|
1386
|
+
return true;
|
|
1387
|
+
} catch {
|
|
1388
|
+
return false;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
async function toWorkspaceRelativePath(workspaceRoot, absolutePath) {
|
|
1392
|
+
const [realWorkspaceRoot, realAbsolutePath] = await Promise.all([
|
|
1393
|
+
fs.realpath(workspaceRoot),
|
|
1394
|
+
fs.realpath(absolutePath)
|
|
1395
|
+
]);
|
|
1396
|
+
const relativePath = path.relative(realWorkspaceRoot, realAbsolutePath);
|
|
1397
|
+
if (!relativePath || relativePath === ".") {
|
|
1398
|
+
return ".";
|
|
1399
|
+
}
|
|
1400
|
+
return relativePath;
|
|
1401
|
+
}
|
|
1402
|
+
async function readPackageJson(filePath) {
|
|
1403
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
1404
|
+
return JSON.parse(content);
|
|
1405
|
+
}
|
|
1406
|
+
function createWorkspaceRequire(workspaceRoot) {
|
|
1407
|
+
return createRequire(path.join(workspaceRoot, "__weapp_vite_mcp__.cjs"));
|
|
1408
|
+
}
|
|
1409
|
+
async function resolveMonorepoPackage(workspaceRoot, id) {
|
|
1410
|
+
const config = EXPOSED_PACKAGES[id];
|
|
1411
|
+
const absolutePath = assertInsideRoot(workspaceRoot, path.join(workspaceRoot, config.relativePath));
|
|
1412
|
+
const packageJsonPath = path.join(absolutePath, "package.json");
|
|
1413
|
+
if (!await pathExists(packageJsonPath)) {
|
|
1414
|
+
return void 0;
|
|
1415
|
+
}
|
|
1416
|
+
const packageJson = await readPackageJson(packageJsonPath);
|
|
1417
|
+
const readmePath = path.join(absolutePath, "README.md");
|
|
1418
|
+
const changelogPath = path.join(absolutePath, "CHANGELOG.md");
|
|
1419
|
+
const sourceRoot = await pathExists(path.join(absolutePath, "src")) ? absolutePath : void 0;
|
|
1420
|
+
const cliPath = id === "weapp-vite" ? await pathExists(path.join(absolutePath, "bin", "weapp-vite.js")) ? path.join(absolutePath, "bin", "weapp-vite.js") : void 0 : void 0;
|
|
1421
|
+
return {
|
|
1422
|
+
id,
|
|
1423
|
+
label: config.label,
|
|
1424
|
+
packageName: packageJson.name ?? config.packageName,
|
|
1425
|
+
version: packageJson.version ?? "0.0.0",
|
|
1426
|
+
absolutePath,
|
|
1427
|
+
relativePath: config.relativePath,
|
|
1428
|
+
scripts: Object.keys(packageJson.scripts ?? {}),
|
|
1429
|
+
docs: {
|
|
1430
|
+
readme: await pathExists(readmePath) ? readmePath : void 0,
|
|
1431
|
+
changelog: await pathExists(changelogPath) ? changelogPath : void 0
|
|
1432
|
+
},
|
|
1433
|
+
sourceRoot,
|
|
1434
|
+
cliPath
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
async function resolveInstalledPackage(workspaceRoot, id) {
|
|
1438
|
+
const config = EXPOSED_PACKAGES[id];
|
|
1439
|
+
const workspaceRequire = createWorkspaceRequire(workspaceRoot);
|
|
1440
|
+
let packageJsonPath;
|
|
1441
|
+
try {
|
|
1442
|
+
packageJsonPath = workspaceRequire.resolve(`${config.packageName}/package.json`);
|
|
1443
|
+
} catch {
|
|
1444
|
+
return void 0;
|
|
1445
|
+
}
|
|
1446
|
+
const absolutePath = path.dirname(packageJsonPath);
|
|
1447
|
+
const relativePath = await toWorkspaceRelativePath(workspaceRoot, absolutePath);
|
|
1448
|
+
const packageJson = await readPackageJson(packageJsonPath);
|
|
1449
|
+
const docsRoot = path.join(absolutePath, "dist", "docs");
|
|
1450
|
+
const installedReadmePath = path.join(docsRoot, "README.md");
|
|
1451
|
+
const installedChangelogPath = path.join(docsRoot, "CHANGELOG.md");
|
|
1452
|
+
const sourceRoot = await pathExists(path.join(absolutePath, "src")) ? absolutePath : void 0;
|
|
1453
|
+
let cliPath;
|
|
1454
|
+
if (id === "weapp-vite") {
|
|
1455
|
+
const binEntry = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.["weapp-vite"];
|
|
1456
|
+
if (typeof binEntry === "string" && binEntry.trim()) {
|
|
1457
|
+
const resolvedCliPath = path.resolve(absolutePath, binEntry);
|
|
1458
|
+
if (await pathExists(resolvedCliPath)) {
|
|
1459
|
+
cliPath = resolvedCliPath;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
return {
|
|
1464
|
+
id,
|
|
1465
|
+
label: config.label,
|
|
1466
|
+
packageName: packageJson.name ?? config.packageName,
|
|
1467
|
+
version: packageJson.version ?? "0.0.0",
|
|
1468
|
+
absolutePath,
|
|
1469
|
+
relativePath,
|
|
1470
|
+
scripts: Object.keys(packageJson.scripts ?? {}),
|
|
1471
|
+
docs: {
|
|
1472
|
+
readme: await pathExists(installedReadmePath) ? installedReadmePath : void 0,
|
|
1473
|
+
changelog: await pathExists(installedChangelogPath) ? installedChangelogPath : void 0
|
|
1474
|
+
},
|
|
1475
|
+
sourceRoot,
|
|
1476
|
+
cliPath
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
async function resolveExposedPackage(workspaceRoot, id) {
|
|
1480
|
+
return await resolveMonorepoPackage(workspaceRoot, id) ?? await resolveInstalledPackage(workspaceRoot, id) ?? Promise.reject(new Error(`\u672A\u627E\u5230\u66B4\u9732\u5305\uFF1A${id}`));
|
|
1481
|
+
}
|
|
1482
|
+
async function resolveExposedPackages(workspaceRoot) {
|
|
1483
|
+
const resolved = await Promise.allSettled(
|
|
1484
|
+
Object.keys(EXPOSED_PACKAGES).map((id) => resolveExposedPackage(workspaceRoot, id))
|
|
1485
|
+
);
|
|
1486
|
+
return resolved.flatMap((result) => {
|
|
1487
|
+
if (result.status === "fulfilled") {
|
|
1488
|
+
return [result.value];
|
|
1489
|
+
}
|
|
1490
|
+
return [];
|
|
1491
|
+
}).sort((a, b) => a.id.localeCompare(b.id));
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
function registerAcceptanceTools(server, service) {
|
|
1495
|
+
const json = (data) => {
|
|
1496
|
+
const structuredContent = redactValue(data);
|
|
1497
|
+
return { content: [{ type: "text", text: JSON.stringify(structuredContent) }], structuredContent };
|
|
1498
|
+
};
|
|
1499
|
+
const guarded = (fn) => async (args) => {
|
|
1500
|
+
try {
|
|
1501
|
+
return await fn(args);
|
|
1502
|
+
} catch (error) {
|
|
1503
|
+
return { isError: true, content: [{ type: "text", text: redactor()(error instanceof Error ? error.message : String(error)) }] };
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
const jobId = z.uuid();
|
|
1507
|
+
server.registerTool("weapp_project_inspect", {
|
|
1508
|
+
description: "Inspect mini-program support, configured acceptance and missing prerequisites. No model or runtime startup.",
|
|
1509
|
+
inputSchema: z.object({}),
|
|
1510
|
+
annotations: { readOnlyHint: true }
|
|
1511
|
+
}, guarded(async () => json(await (await service()).inspect())));
|
|
1512
|
+
server.registerTool("weapp_acceptance_start", {
|
|
1513
|
+
description: "Start configured checks and deterministic WeChat DevTools scenarios. Returns a jobId immediately. Requires previously reviewed project trust; no model calls or code edits.",
|
|
1514
|
+
inputSchema: z.object({}),
|
|
1515
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false }
|
|
1516
|
+
}, guarded(async () => json(await (await service()).start())));
|
|
1517
|
+
server.registerTool("weapp_acceptance_status", {
|
|
1518
|
+
description: "Get current acceptance progress and source freshness. Poll until status is no longer running.",
|
|
1519
|
+
inputSchema: z.object({ jobId }),
|
|
1520
|
+
annotations: { readOnlyHint: true }
|
|
1521
|
+
}, guarded(async ({ jobId: jobId2 }) => {
|
|
1522
|
+
const report = await (await service()).report(jobId2);
|
|
1523
|
+
return json({ version: report.version, jobId: jobId2, status: report.status, passed: report.passed, snapshot: report.snapshot, checks: report.checks.map(({ output: _output, ...check }) => check), lastStep: report.steps.at(-1), reason: report.reason });
|
|
1524
|
+
}));
|
|
1525
|
+
server.registerTool("weapp_acceptance_cancel", {
|
|
1526
|
+
description: "Cancel an acceptance task owned by this server. Completed interactions are not undone or replayed.",
|
|
1527
|
+
inputSchema: z.object({ jobId }),
|
|
1528
|
+
annotations: { readOnlyHint: false, idempotentHint: true }
|
|
1529
|
+
}, guarded(async ({ jobId: jobId2 }) => json(await (await service()).cancel(jobId2))));
|
|
1530
|
+
server.registerTool("weapp_acceptance_report", {
|
|
1531
|
+
description: "Read the version 2 acceptance report, or one named screenshot/console artifact from that report. Evidence may be stale; inspect snapshot.stale before claiming success.",
|
|
1532
|
+
inputSchema: z.object({ jobId, artifact: z.string().optional() }),
|
|
1533
|
+
annotations: { readOnlyHint: true }
|
|
1534
|
+
}, guarded(async ({ jobId: jobId2, artifact }) => {
|
|
1535
|
+
if (!artifact) {
|
|
1536
|
+
return json(await (await service()).report(jobId2));
|
|
1537
|
+
}
|
|
1538
|
+
const result = await (await service()).artifact(jobId2, artifact);
|
|
1539
|
+
const report = await (await service()).report(jobId2);
|
|
1540
|
+
return {
|
|
1541
|
+
...json({ jobId: jobId2, artifact, status: report.status, stale: report.snapshot.stale }),
|
|
1542
|
+
content: [
|
|
1543
|
+
{ type: "text", text: JSON.stringify({ jobId: jobId2, artifact, status: report.status, stale: report.snapshot.stale }) },
|
|
1544
|
+
result.mediaType === "image/png" ? { type: "image", data: result.data, mimeType: result.mediaType } : { type: "text", text: result.data }
|
|
1545
|
+
]
|
|
1546
|
+
};
|
|
1547
|
+
}));
|
|
1548
|
+
return server;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
function registerServerPrompts(server, options) {
|
|
1552
|
+
const { packageIds, packageIdSchema } = options;
|
|
1553
|
+
server.registerPrompt("plan-weapp-vite-change", {
|
|
1554
|
+
title: "Plan weapp-vite Change",
|
|
1555
|
+
description: "\u6839\u636E\u53D8\u66F4\u76EE\u6807\u751F\u6210 weapp-vite / wevu \u4FEE\u6539\u8BA1\u5212\u63D0\u793A\u8BCD",
|
|
1556
|
+
argsSchema: z.object({
|
|
1557
|
+
objective: z.string().min(1),
|
|
1558
|
+
focusPackage: packageIdSchema.optional()
|
|
1559
|
+
})
|
|
1560
|
+
}, async ({ objective, focusPackage }) => {
|
|
1561
|
+
const targets = focusPackage ? [focusPackage] : packageIds;
|
|
1562
|
+
return {
|
|
1563
|
+
messages: [
|
|
1564
|
+
{
|
|
1565
|
+
role: "user",
|
|
1566
|
+
content: {
|
|
1567
|
+
type: "text",
|
|
1568
|
+
text: [
|
|
1569
|
+
"\u4F60\u662F weapp-vite monorepo \u7EF4\u62A4\u8005\uFF0C\u8BF7\u7ED9\u51FA\u53EF\u6267\u884C\u7684\u6539\u9020\u8BA1\u5212\u3002",
|
|
1570
|
+
`\u76EE\u6807\uFF1A${objective}`,
|
|
1571
|
+
`\u805A\u7126\u5305\uFF1A${targets.join(", ")}`,
|
|
1572
|
+
"\u8BF7\u5305\u542B\uFF1A\u5F71\u54CD\u9762\u3001\u98CE\u9669\u70B9\u3001\u6D4B\u8BD5\u7B56\u7565\u3001\u56DE\u6EDA\u7B56\u7565\u3002"
|
|
1573
|
+
].join("\n")
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
]
|
|
1577
|
+
};
|
|
1578
|
+
});
|
|
1579
|
+
server.registerPrompt("debug-wevu-runtime", {
|
|
1580
|
+
title: "Debug wevu Runtime",
|
|
1581
|
+
description: "\u7528\u4E8E\u5B9A\u4F4D wevu runtime \u751F\u547D\u5468\u671F/\u54CD\u5E94\u5F0F\u95EE\u9898\u7684\u6807\u51C6\u63D0\u793A\u8BCD",
|
|
1582
|
+
argsSchema: z.object({
|
|
1583
|
+
symptom: z.string().min(1)
|
|
1584
|
+
})
|
|
1585
|
+
}, async ({ symptom }) => {
|
|
1586
|
+
return {
|
|
1587
|
+
messages: [
|
|
1588
|
+
{
|
|
1589
|
+
role: "user",
|
|
1590
|
+
content: {
|
|
1591
|
+
type: "text",
|
|
1592
|
+
text: [
|
|
1593
|
+
"\u8BF7\u57FA\u4E8E wevu runtime \u4EE3\u7801\u8DEF\u5F84\u8FDB\u884C\u5206\u5C42\u6392\u67E5\uFF1A",
|
|
1594
|
+
"1. \u590D\u73B0\u573A\u666F\u4E0E\u6700\u5C0F\u6837\u4F8B",
|
|
1595
|
+
"2. \u751F\u547D\u5468\u671F\u94A9\u5B50\u89E6\u53D1\u94FE",
|
|
1596
|
+
"3. \u54CD\u5E94\u5F0F\u4E0E setData \u5DEE\u91CF\u540C\u6B65\u94FE",
|
|
1597
|
+
"4. \u5355\u6D4B\u4E0E e2e \u56DE\u5F52\u8865\u4E01",
|
|
1598
|
+
`\u73B0\u8C61\uFF1A${symptom}`
|
|
1599
|
+
].join("\n")
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
]
|
|
1603
|
+
};
|
|
1604
|
+
});
|
|
1605
|
+
server.registerPrompt("inspect-mini-program-page", {
|
|
1606
|
+
title: "Inspect Mini Program Page",
|
|
1607
|
+
description: "\u8FDE\u63A5\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u5E76\u68C0\u67E5\u5F53\u524D\u5C0F\u7A0B\u5E8F\u9875\u9762\u7684\u6807\u51C6\u6D41\u7A0B",
|
|
1608
|
+
argsSchema: z.object({
|
|
1609
|
+
projectPath: z.string().min(1),
|
|
1610
|
+
pagePath: z.string().optional(),
|
|
1611
|
+
focus: z.string().optional()
|
|
1612
|
+
})
|
|
1613
|
+
}, async ({ projectPath, pagePath, focus }) => {
|
|
1614
|
+
return {
|
|
1615
|
+
messages: [
|
|
1616
|
+
{
|
|
1617
|
+
role: "user",
|
|
1618
|
+
content: {
|
|
1619
|
+
type: "text",
|
|
1620
|
+
text: [
|
|
1621
|
+
"\u8BF7\u6309\u987A\u5E8F\u4F7F\u7528 weapp-vite MCP runtime tools \u68C0\u67E5\u5C0F\u7A0B\u5E8F\u9875\u9762\uFF1A",
|
|
1622
|
+
`1. \u8C03\u7528 weapp_devtools_connect\uFF0CprojectPath=${projectPath}`,
|
|
1623
|
+
pagePath ? `2. \u8C03\u7528 weapp_devtools_route \u8DF3\u8F6C\u5230 ${pagePath}` : "2. \u8C03\u7528 weapp_devtools_active_page \u786E\u8BA4\u5F53\u524D\u9875\u9762",
|
|
1624
|
+
"3. \u8C03\u7528 weapp_devtools_capture \u83B7\u53D6\u622A\u56FE",
|
|
1625
|
+
"4. \u5982\u9700\u68C0\u67E5\u7ED3\u6784\uFF0C\u4F18\u5148\u8C03\u7528 weapp_runtime_find_node/weapp_runtime_find_nodes\uFF0C\u5FC5\u8981\u65F6\u8BBE\u7F6E withWxml=true",
|
|
1626
|
+
"5. \u8C03\u7528 weapp_devtools_console \u68C0\u67E5 console/exception \u65E5\u5FD7",
|
|
1627
|
+
focus ? `\u5173\u6CE8\u70B9\uFF1A${focus}` : "\u5173\u6CE8\u70B9\uFF1A\u9875\u9762\u662F\u5426\u6B63\u786E\u6E32\u67D3\u3001\u662F\u5426\u5B58\u5728\u8FD0\u884C\u65F6\u9519\u8BEF\u3002"
|
|
1628
|
+
].join("\n")
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
]
|
|
1632
|
+
};
|
|
1633
|
+
});
|
|
1634
|
+
server.registerPrompt("recover-mini-program-connection", {
|
|
1635
|
+
title: "Recover Mini Program Connection",
|
|
1636
|
+
description: "\u6062\u590D\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177 automator \u8FDE\u63A5\u7684\u6807\u51C6\u6D41\u7A0B",
|
|
1637
|
+
argsSchema: z.object({
|
|
1638
|
+
projectPath: z.string().min(1),
|
|
1639
|
+
lastError: z.string().optional()
|
|
1640
|
+
})
|
|
1641
|
+
}, async ({ projectPath, lastError }) => {
|
|
1642
|
+
return {
|
|
1643
|
+
messages: [
|
|
1644
|
+
{
|
|
1645
|
+
role: "user",
|
|
1646
|
+
content: {
|
|
1647
|
+
type: "text",
|
|
1648
|
+
text: [
|
|
1649
|
+
"\u8BF7\u6309\u987A\u5E8F\u6062\u590D weapp-vite MCP runtime \u8FDE\u63A5\uFF1A",
|
|
1650
|
+
`1. \u8C03\u7528 weapp_devtools_connect\uFF0CprojectPath=${projectPath}\uFF0Creconnect=true`,
|
|
1651
|
+
"2. \u5982\u679C\u4ECD\u5931\u8D25\uFF0C\u68C0\u67E5\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u662F\u5426\u5DF2\u5F00\u542F\u670D\u52A1\u7AEF\u53E3\u4E0E\u81EA\u52A8\u5316\u6D4B\u8BD5\u80FD\u529B",
|
|
1652
|
+
"3. \u5982\u679C\u662F\u534F\u8BAE\u8D85\u65F6\uFF0C\u5173\u95ED\u591A\u4F59 DevTools \u7A97\u53E3\u540E\u53EA\u91CD\u8BD5\u4E00\u6B21",
|
|
1653
|
+
"4. \u6062\u590D\u540E\u8C03\u7528 weapp_devtools_active_page \u548C weapp_devtools_console \u786E\u8BA4\u72B6\u6001",
|
|
1654
|
+
lastError ? `\u4E0A\u4E00\u6B21\u9519\u8BEF\uFF1A${lastError}` : "\u4E0A\u4E00\u6B21\u9519\u8BEF\uFF1A\u672A\u63D0\u4F9B\u3002"
|
|
1655
|
+
].join("\n")
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
]
|
|
1659
|
+
};
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
async function walkFilesRecursive(root, current, output, maxResults) {
|
|
1664
|
+
if (output.length >= maxResults) {
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
const entries = await fs.readdir(current, { withFileTypes: true });
|
|
1668
|
+
for (const entry of entries) {
|
|
1669
|
+
if (output.length >= maxResults) {
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
if (entry.isDirectory()) {
|
|
1673
|
+
if (SKIPPED_DIR_NAMES.has(entry.name)) {
|
|
1674
|
+
continue;
|
|
1675
|
+
}
|
|
1676
|
+
await walkFilesRecursive(root, path.join(current, entry.name), output, maxResults);
|
|
1677
|
+
continue;
|
|
1678
|
+
}
|
|
1679
|
+
if (!entry.isFile()) {
|
|
1680
|
+
continue;
|
|
1681
|
+
}
|
|
1682
|
+
const absolutePath = path.join(current, entry.name);
|
|
1683
|
+
const relativePath = path.relative(root, absolutePath).split(path.sep).join("/");
|
|
1684
|
+
output.push(relativePath);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
async function listFilesInDirectory(root, relativeDirectory = ".", maxResults = DEFAULT_MAX_RESULTS) {
|
|
1688
|
+
const dirPath = resolveSubPath(root, relativeDirectory);
|
|
1689
|
+
const files = [];
|
|
1690
|
+
await walkFilesRecursive(root, dirPath, files, maxResults);
|
|
1691
|
+
return files;
|
|
1692
|
+
}
|
|
1693
|
+
function sliceLines(content, startLine, endLine) {
|
|
1694
|
+
if (!startLine && !endLine) {
|
|
1695
|
+
return content;
|
|
1696
|
+
}
|
|
1697
|
+
const lines = content.split("\n");
|
|
1698
|
+
const safeStart = Math.max(1, startLine ?? 1);
|
|
1699
|
+
const safeEnd = Math.max(safeStart, endLine ?? lines.length);
|
|
1700
|
+
return lines.slice(safeStart - 1, safeEnd).join("\n");
|
|
1701
|
+
}
|
|
1702
|
+
async function readFileContent(root, relativeFilePath, options) {
|
|
1703
|
+
const filePath = assertInsideRoot(root, path.join(root, relativeFilePath));
|
|
1704
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
1705
|
+
const selected = sliceLines(content, options?.startLine, options?.endLine);
|
|
1706
|
+
const maxChars = options?.maxChars ?? DEFAULT_MAX_FILE_CHARS;
|
|
1707
|
+
const clipped = selected.length > maxChars ? `${selected.slice(0, maxChars)}
|
|
1708
|
+
|
|
1709
|
+
[truncated: ${selected.length - maxChars} chars omitted]` : selected;
|
|
1710
|
+
return {
|
|
1711
|
+
filePath,
|
|
1712
|
+
content: clipped
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
function collectMatchesFromText(query, relativeFilePath, content, maxResults, output) {
|
|
1716
|
+
const lines = content.split("\n");
|
|
1717
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1718
|
+
if (output.length >= maxResults) {
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
const lineText = lines[index] ?? "";
|
|
1722
|
+
const column = lineText.toLowerCase().indexOf(query.toLowerCase());
|
|
1723
|
+
if (column === -1) {
|
|
1724
|
+
continue;
|
|
1725
|
+
}
|
|
1726
|
+
output.push({
|
|
1727
|
+
filePath: relativeFilePath,
|
|
1728
|
+
line: index + 1,
|
|
1729
|
+
column: column + 1,
|
|
1730
|
+
text: lineText.trim()
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
async function searchTextInDirectory(root, query, options) {
|
|
1735
|
+
const maxResults = options?.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
1736
|
+
const files = await listFilesInDirectory(root, options?.relativeDirectory ?? ".", Math.max(400, maxResults));
|
|
1737
|
+
const matches = [];
|
|
1738
|
+
for (const file of files) {
|
|
1739
|
+
if (matches.length >= maxResults) {
|
|
1740
|
+
break;
|
|
1741
|
+
}
|
|
1742
|
+
const absolutePath = path.join(root, file);
|
|
1743
|
+
let content = "";
|
|
1744
|
+
try {
|
|
1745
|
+
content = await fs.readFile(absolutePath, "utf8");
|
|
1746
|
+
} catch {
|
|
1747
|
+
continue;
|
|
1748
|
+
}
|
|
1749
|
+
collectMatchesFromText(query, file, content, maxResults, matches);
|
|
1750
|
+
}
|
|
1751
|
+
return matches;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
const ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
1755
|
+
"pnpm",
|
|
1756
|
+
"node",
|
|
1757
|
+
"git",
|
|
1758
|
+
"rg"
|
|
1759
|
+
]);
|
|
1760
|
+
function resolveExecutable(command) {
|
|
1761
|
+
if (process.platform === "win32") {
|
|
1762
|
+
if (command === "pnpm") {
|
|
1763
|
+
return "pnpm.cmd";
|
|
1764
|
+
}
|
|
1765
|
+
if (command === "git") {
|
|
1766
|
+
return "git.exe";
|
|
1767
|
+
}
|
|
1768
|
+
if (command === "rg") {
|
|
1769
|
+
return "rg.exe";
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
return command;
|
|
1773
|
+
}
|
|
1774
|
+
function truncateOutput(text, maxChars) {
|
|
1775
|
+
if (text.length <= maxChars) {
|
|
1776
|
+
return text;
|
|
1777
|
+
}
|
|
1778
|
+
return `${text.slice(0, maxChars)}
|
|
1779
|
+
|
|
1780
|
+
[truncated: ${text.length - maxChars} chars omitted]`;
|
|
1781
|
+
}
|
|
1782
|
+
async function runCommand(workspaceRoot, command, args, options) {
|
|
1783
|
+
if (!ALLOWED_COMMANDS.has(command)) {
|
|
1784
|
+
throw new Error(`\u4E0D\u5141\u8BB8\u7684\u547D\u4EE4\uFF1A${command}`);
|
|
1785
|
+
}
|
|
1786
|
+
const cwd = resolveSubPath(workspaceRoot, options?.cwdRelative ?? ".");
|
|
1787
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1788
|
+
const maxOutputChars = options?.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
1789
|
+
const executable = resolveExecutable(command);
|
|
1790
|
+
const child = spawn(executable, args, {
|
|
1791
|
+
cwd,
|
|
1792
|
+
env: process.env,
|
|
1793
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1794
|
+
shell: false
|
|
1795
|
+
});
|
|
1796
|
+
let stdout = "";
|
|
1797
|
+
let stderr = "";
|
|
1798
|
+
let timedOut = false;
|
|
1799
|
+
const timer = setTimeout(() => {
|
|
1800
|
+
timedOut = true;
|
|
1801
|
+
child.kill("SIGTERM");
|
|
1802
|
+
}, timeoutMs);
|
|
1803
|
+
child.stdout.on("data", (chunk) => {
|
|
1804
|
+
stdout += chunk.toString();
|
|
1805
|
+
});
|
|
1806
|
+
child.stderr.on("data", (chunk) => {
|
|
1807
|
+
stderr += chunk.toString();
|
|
1808
|
+
});
|
|
1809
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
1810
|
+
child.on("error", reject);
|
|
1811
|
+
child.on("close", (code) => {
|
|
1812
|
+
resolve(code ?? -1);
|
|
1813
|
+
});
|
|
1814
|
+
}).finally(() => {
|
|
1815
|
+
clearTimeout(timer);
|
|
1816
|
+
});
|
|
1817
|
+
return {
|
|
1818
|
+
command,
|
|
1819
|
+
args,
|
|
1820
|
+
cwd: path.resolve(cwd),
|
|
1821
|
+
exitCode,
|
|
1822
|
+
stdout: truncateOutput(stdout.trim(), maxOutputChars),
|
|
1823
|
+
stderr: truncateOutput(stderr.trim(), maxOutputChars),
|
|
1824
|
+
timedOut
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
async function resolvePackageRoot(workspaceRoot, packageId) {
|
|
1829
|
+
const resolved = await resolveExposedPackage(workspaceRoot, packageId);
|
|
1830
|
+
if (!resolved.sourceRoot) {
|
|
1831
|
+
throw new Error(`\u5F53\u524D\u5DE5\u4F5C\u533A\u4E2D\u7684 ${packageId} \u4E0D\u5305\u542B\u6E90\u7801\u76EE\u5F55\uFF0C\u8BF7\u6539\u4E3A\u4F18\u5148\u8BFB\u53D6\u672C\u5730\u968F\u5305\u6587\u6863\u3002`);
|
|
1832
|
+
}
|
|
1833
|
+
return assertInsideRoot(workspaceRoot, resolved.sourceRoot);
|
|
1834
|
+
}
|
|
1835
|
+
function toDocsUri(packageId, fileName) {
|
|
1836
|
+
return `weapp-vite://docs/${packageId}/${fileName}`;
|
|
1837
|
+
}
|
|
1838
|
+
async function readTextFile(filePath) {
|
|
1839
|
+
return fs.readFile(filePath, "utf8");
|
|
1840
|
+
}
|
|
1841
|
+
async function runWeappViteCliTool(workspaceRoot, input) {
|
|
1842
|
+
const cliPath = (await resolveExposedPackage(workspaceRoot, "weapp-vite")).cliPath;
|
|
1843
|
+
if (!cliPath) {
|
|
1844
|
+
throw new Error("\u5F53\u524D\u5DE5\u4F5C\u533A\u4E2D\u7684 weapp-vite \u672A\u66B4\u9732 CLI \u5165\u53E3");
|
|
1845
|
+
}
|
|
1846
|
+
const finalArgs = [cliPath, input.subCommand];
|
|
1847
|
+
if (input.projectPath) {
|
|
1848
|
+
finalArgs.push(resolveSubPath(workspaceRoot, input.projectPath));
|
|
1849
|
+
}
|
|
1850
|
+
if (input.platform) {
|
|
1851
|
+
finalArgs.push("--platform", input.platform);
|
|
1852
|
+
}
|
|
1853
|
+
if (Array.isArray(input.args) && input.args.length > 0) {
|
|
1854
|
+
finalArgs.push(...input.args);
|
|
1855
|
+
}
|
|
1856
|
+
return runCommand(workspaceRoot, "node", finalArgs, {
|
|
1857
|
+
timeoutMs: input.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
function registerServerResources(server, options) {
|
|
1862
|
+
const { workspaceRoot, packageIds, exposedPackages } = options;
|
|
1863
|
+
server.registerResource("workspace-catalog", "weapp-vite://workspace/catalog", {
|
|
1864
|
+
title: "Workspace Catalog",
|
|
1865
|
+
description: "weapp-vite / wevu \u5305\u76EE\u5F55\u3001\u7248\u672C\u548C\u811A\u672C\u5217\u8868",
|
|
1866
|
+
mimeType: "application/json"
|
|
1867
|
+
}, async () => {
|
|
1868
|
+
const catalog = await resolveExposedPackages(workspaceRoot);
|
|
1869
|
+
const text = JSON.stringify({ workspaceRoot, packages: catalog }, null, 2);
|
|
1870
|
+
return {
|
|
1871
|
+
contents: [{
|
|
1872
|
+
uri: "weapp-vite://workspace/catalog",
|
|
1873
|
+
mimeType: "application/json",
|
|
1874
|
+
text
|
|
1875
|
+
}]
|
|
1876
|
+
};
|
|
1595
1877
|
});
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
const
|
|
1604
|
-
if (!element) {
|
|
1605
|
-
throw new Error(`XPath \u672A\u627E\u5230\u5143\u7D20: "${xpath}"`);
|
|
1606
|
-
}
|
|
1878
|
+
for (const summary of exposedPackages) {
|
|
1879
|
+
if (summary.docs.readme) {
|
|
1880
|
+
const uri = toDocsUri(summary.id, "README.md");
|
|
1881
|
+
server.registerResource(`docs-${summary.id}-readme`, uri, {
|
|
1882
|
+
title: `${summary.id} README`,
|
|
1883
|
+
mimeType: "text/markdown"
|
|
1884
|
+
}, async () => {
|
|
1885
|
+
const text = await readTextFile(summary.docs.readme);
|
|
1607
1886
|
return {
|
|
1608
|
-
|
|
1609
|
-
...await summarizeElement(element, withWxml)
|
|
1887
|
+
contents: [{ uri, mimeType: "text/markdown", text }]
|
|
1610
1888
|
};
|
|
1611
1889
|
});
|
|
1612
|
-
return toToolResult(result);
|
|
1613
|
-
} catch (error) {
|
|
1614
|
-
return toToolError(error);
|
|
1615
1890
|
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
const result = await manager.withPage(connection, async (page) => {
|
|
1624
|
-
const elements = await callRequiredMethod(page, "getElementsByXpath", xpath);
|
|
1891
|
+
if (summary.docs.changelog) {
|
|
1892
|
+
const uri = toDocsUri(summary.id, "CHANGELOG.md");
|
|
1893
|
+
server.registerResource(`docs-${summary.id}-changelog`, uri, {
|
|
1894
|
+
title: `${summary.id} CHANGELOG`,
|
|
1895
|
+
mimeType: "text/markdown"
|
|
1896
|
+
}, async () => {
|
|
1897
|
+
const text = await readTextFile(summary.docs.changelog);
|
|
1625
1898
|
return {
|
|
1626
|
-
|
|
1627
|
-
count: elements.length,
|
|
1628
|
-
elements: await Promise.all(elements.map(async (element, index) => ({
|
|
1629
|
-
index,
|
|
1630
|
-
...await summarizeElement(element, withWxml)
|
|
1631
|
-
})))
|
|
1899
|
+
contents: [{ uri, mimeType: "text/markdown", text }]
|
|
1632
1900
|
};
|
|
1633
1901
|
});
|
|
1634
|
-
return toToolResult(result);
|
|
1635
|
-
} catch (error) {
|
|
1636
|
-
return toToolError(error);
|
|
1637
|
-
}
|
|
1638
|
-
});
|
|
1639
|
-
server.registerTool("weapp_runtime_wait_node", {
|
|
1640
|
-
title: "Wait Page Element",
|
|
1641
|
-
description: "\u8F6E\u8BE2\u7B49\u5F85\u5143\u7D20\u51FA\u73B0\uFF0C\u652F\u6301 selector[index=N]\u3002",
|
|
1642
|
-
inputSchema: z.object({
|
|
1643
|
-
...connectionInputSchema,
|
|
1644
|
-
selector: z.string().trim().min(1),
|
|
1645
|
-
timeoutMs: z.number().int().positive().optional(),
|
|
1646
|
-
intervalMs: z.number().int().positive().optional()
|
|
1647
|
-
})
|
|
1648
|
-
}, async ({ selector, timeoutMs = 5e3, intervalMs = 200, ...connection }) => {
|
|
1649
|
-
try {
|
|
1650
|
-
const result = await manager.withPage(connection, async (page) => {
|
|
1651
|
-
const startedAt = Date.now();
|
|
1652
|
-
while (Date.now() - startedAt <= timeoutMs) {
|
|
1653
|
-
const elements = await queryElements(page, selector).catch(() => []);
|
|
1654
|
-
if (elements.length > 0) {
|
|
1655
|
-
return {
|
|
1656
|
-
selector,
|
|
1657
|
-
found: true,
|
|
1658
|
-
count: elements.length,
|
|
1659
|
-
waitMs: Date.now() - startedAt
|
|
1660
|
-
};
|
|
1661
|
-
}
|
|
1662
|
-
await page.waitFor(intervalMs);
|
|
1663
|
-
}
|
|
1664
|
-
throw new Error(`\u7B49\u5F85\u5143\u7D20 "${selector}" \u8D85\u65F6 (${timeoutMs}ms)\u3002`);
|
|
1665
|
-
});
|
|
1666
|
-
return toToolResult(result);
|
|
1667
|
-
} catch (error) {
|
|
1668
|
-
return toToolError(error);
|
|
1669
|
-
}
|
|
1670
|
-
});
|
|
1671
|
-
server.registerTool("weapp_runtime_wait", {
|
|
1672
|
-
title: "Wait Page Timeout",
|
|
1673
|
-
description: "\u5728\u5F53\u524D\u9875\u9762\u7B49\u5F85\u6307\u5B9A\u6BEB\u79D2\u6570\u3002",
|
|
1674
|
-
inputSchema: z.object({
|
|
1675
|
-
...connectionInputSchema,
|
|
1676
|
-
milliseconds: z.number().int().nonnegative()
|
|
1677
|
-
})
|
|
1678
|
-
}, async ({ milliseconds, ...connection }) => {
|
|
1679
|
-
try {
|
|
1680
|
-
const result = await manager.withPage(connection, async (page) => {
|
|
1681
|
-
await page.waitFor(milliseconds);
|
|
1682
|
-
return { waitedMs: milliseconds };
|
|
1683
|
-
});
|
|
1684
|
-
return toToolResult(result);
|
|
1685
|
-
} catch (error) {
|
|
1686
|
-
return toToolError(error);
|
|
1687
|
-
}
|
|
1688
|
-
});
|
|
1689
|
-
server.registerTool("weapp_runtime_page_state", {
|
|
1690
|
-
title: "Get Page Data",
|
|
1691
|
-
description: "\u8BFB\u53D6\u5F53\u524D\u9875\u9762 data\uFF0C\u53EF\u901A\u8FC7 path \u8BFB\u53D6\u5D4C\u5957\u5B57\u6BB5\u3002",
|
|
1692
|
-
inputSchema: z.object({
|
|
1693
|
-
...connectionInputSchema,
|
|
1694
|
-
path: z.string().trim().min(1).optional()
|
|
1695
|
-
})
|
|
1696
|
-
}, async ({ path, ...connection }) => {
|
|
1697
|
-
try {
|
|
1698
|
-
const result = await manager.withPage(connection, async (page) => ({
|
|
1699
|
-
path: path ?? null,
|
|
1700
|
-
data: toSerializableValue(await page.data(path))
|
|
1701
|
-
}));
|
|
1702
|
-
return toToolResult(result);
|
|
1703
|
-
} catch (error) {
|
|
1704
|
-
return toToolError(error);
|
|
1705
1902
|
}
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
...connectionInputSchema,
|
|
1712
|
-
data: z.record(z.string(), z.unknown()),
|
|
1713
|
-
verify: z.boolean().optional()
|
|
1714
|
-
})
|
|
1715
|
-
}, async ({ data, verify, ...connection }) => {
|
|
1716
|
-
try {
|
|
1717
|
-
const result = await manager.withPage(connection, async (page) => {
|
|
1718
|
-
await callRequiredMethod(page, "setData", data);
|
|
1719
|
-
return {
|
|
1720
|
-
keys: Object.keys(data),
|
|
1721
|
-
data: verify ? toSerializableValue(await page.data()) : void 0
|
|
1722
|
-
};
|
|
1723
|
-
});
|
|
1724
|
-
return toToolResult(result);
|
|
1725
|
-
} catch (error) {
|
|
1726
|
-
return toToolError(error);
|
|
1903
|
+
}
|
|
1904
|
+
const sourceTemplate = new ResourceTemplate("weapp-vite://source/{package}?path={path}", {
|
|
1905
|
+
list: void 0,
|
|
1906
|
+
complete: {
|
|
1907
|
+
package: () => packageIds
|
|
1727
1908
|
}
|
|
1728
1909
|
});
|
|
1729
|
-
server.
|
|
1730
|
-
title: "
|
|
1731
|
-
description: "\
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
method: z.string().trim().min(1),
|
|
1735
|
-
args: z.array(z.unknown()).optional()
|
|
1736
|
-
})
|
|
1737
|
-
}, async ({ method, args, ...connection }) => {
|
|
1910
|
+
server.registerResource("source-template", sourceTemplate, {
|
|
1911
|
+
title: "Source Template",
|
|
1912
|
+
description: "\u8BFB\u53D6 weapp-vite / wevu \u4EFB\u610F\u6E90\u7801\u6587\u4EF6\uFF08\u901A\u8FC7 package + path \u53C2\u6570\uFF09",
|
|
1913
|
+
mimeType: "text/plain"
|
|
1914
|
+
}, async (uri, variables) => {
|
|
1738
1915
|
try {
|
|
1739
|
-
const
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1916
|
+
const packageId = String(variables.package ?? "");
|
|
1917
|
+
if (!packageIds.includes(packageId)) {
|
|
1918
|
+
throw new Error(`\u672A\u77E5 package\uFF1A${packageId}`);
|
|
1919
|
+
}
|
|
1920
|
+
const relativePath = decodeURIComponent(String(variables.path ?? ""));
|
|
1921
|
+
const packageRoot = await resolvePackageRoot(workspaceRoot, packageId);
|
|
1922
|
+
const { content } = await readFileContent(packageRoot, relativePath, {
|
|
1923
|
+
maxChars: DEFAULT_MAX_FILE_CHARS
|
|
1746
1924
|
});
|
|
1747
|
-
return
|
|
1925
|
+
return {
|
|
1926
|
+
contents: [{
|
|
1927
|
+
uri: uri.toString(),
|
|
1928
|
+
mimeType: "text/plain",
|
|
1929
|
+
text: content
|
|
1930
|
+
}]
|
|
1931
|
+
};
|
|
1748
1932
|
} catch (error) {
|
|
1749
|
-
return
|
|
1933
|
+
return {
|
|
1934
|
+
contents: [{
|
|
1935
|
+
uri: uri.toString(),
|
|
1936
|
+
mimeType: "text/plain",
|
|
1937
|
+
text: `[resource-error] ${normalizeErrorMessage(error)}`
|
|
1938
|
+
}]
|
|
1939
|
+
};
|
|
1750
1940
|
}
|
|
1751
1941
|
});
|
|
1752
1942
|
}
|
|
1753
1943
|
|
|
1754
|
-
function registerRuntimeTools(server, options) {
|
|
1755
|
-
const manager = options.manager ?? new RuntimeSessionManager(options.workspaceRoot, options.runtimeHooks);
|
|
1756
|
-
registerDevtoolsRuntimeTools(server, manager);
|
|
1757
|
-
registerRuntimePageTools(server, manager);
|
|
1758
|
-
registerRuntimeNodeTools(server, manager);
|
|
1759
|
-
return manager;
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
1944
|
async function loadPackageSummary(workspaceRoot, id) {
|
|
1763
1945
|
return resolveExposedPackage(workspaceRoot, id);
|
|
1764
1946
|
}
|
|
@@ -2041,9 +2223,28 @@ const packageIds = Object.keys(EXPOSED_PACKAGES);
|
|
|
2041
2223
|
const packageIdSchema = z.enum(packageIds);
|
|
2042
2224
|
async function createWeappViteMcpServerFactory(options) {
|
|
2043
2225
|
const workspaceRoot = resolveWorkspaceRoot(options?.workspaceRoot);
|
|
2226
|
+
const acceptanceRoot = path.resolve(options?.workspaceRoot ?? process.cwd());
|
|
2044
2227
|
const exposedPackages = await resolveExposedPackages(workspaceRoot);
|
|
2045
2228
|
const runtimeManager = new RuntimeSessionManager(workspaceRoot, options?.runtimeHooks);
|
|
2229
|
+
let closed = false;
|
|
2230
|
+
let acceptance;
|
|
2231
|
+
const getAcceptance = () => {
|
|
2232
|
+
if (closed) {
|
|
2233
|
+
return Promise.reject(new Error("MCP service is shutting down"));
|
|
2234
|
+
}
|
|
2235
|
+
return acceptance ??= AcceptanceService.create(acceptanceRoot, { connect: acceptanceRuntime(runtimeManager) });
|
|
2236
|
+
};
|
|
2046
2237
|
return {
|
|
2238
|
+
close: async () => {
|
|
2239
|
+
closed = true;
|
|
2240
|
+
try {
|
|
2241
|
+
if (acceptance) {
|
|
2242
|
+
await (await acceptance).close();
|
|
2243
|
+
}
|
|
2244
|
+
} finally {
|
|
2245
|
+
await runtimeManager.dispose();
|
|
2246
|
+
}
|
|
2247
|
+
},
|
|
2047
2248
|
runtimeManager,
|
|
2048
2249
|
workspaceRoot,
|
|
2049
2250
|
createServer: () => {
|
|
@@ -2051,6 +2252,7 @@ async function createWeappViteMcpServerFactory(options) {
|
|
|
2051
2252
|
name: MCP_SERVER_NAME,
|
|
2052
2253
|
version: MCP_SERVER_VERSION
|
|
2053
2254
|
});
|
|
2255
|
+
registerAcceptanceTools(server, getAcceptance);
|
|
2054
2256
|
registerServerTools(server, {
|
|
2055
2257
|
workspaceRoot,
|
|
2056
2258
|
packageIds,
|
|
@@ -2076,18 +2278,28 @@ async function createWeappViteMcpServerFactory(options) {
|
|
|
2076
2278
|
}
|
|
2077
2279
|
async function createWeappViteMcpServer(options) {
|
|
2078
2280
|
const factory = await createWeappViteMcpServerFactory(options);
|
|
2281
|
+
const server = factory.createServer();
|
|
2282
|
+
const closeProtocol = server.close.bind(server);
|
|
2283
|
+
server.close = async () => {
|
|
2284
|
+
try {
|
|
2285
|
+
await factory.close();
|
|
2286
|
+
} finally {
|
|
2287
|
+
await closeProtocol();
|
|
2288
|
+
}
|
|
2289
|
+
};
|
|
2079
2290
|
return {
|
|
2291
|
+
close: server.close,
|
|
2080
2292
|
runtimeManager: factory.runtimeManager,
|
|
2081
|
-
server
|
|
2293
|
+
server,
|
|
2082
2294
|
workspaceRoot: factory.workspaceRoot
|
|
2083
2295
|
};
|
|
2084
2296
|
}
|
|
2085
2297
|
|
|
2086
|
-
const DEFAULT_RUNTIME_REST_ENDPOINT = "/api/weapp/devtools";
|
|
2087
2298
|
const connectionSchema = z.object({
|
|
2299
|
+
cliPath: z.string().trim().min(1).optional(),
|
|
2088
2300
|
projectPath: z.string().trim().min(1),
|
|
2089
2301
|
timeout: z.number().int().positive().optional(),
|
|
2090
|
-
port: z.number().int().positive().optional(),
|
|
2302
|
+
port: z.number().int().positive().max(65535).optional(),
|
|
2091
2303
|
preferOpenedSession: z.boolean().optional(),
|
|
2092
2304
|
preserveProjectRoot: z.boolean().optional(),
|
|
2093
2305
|
sessionId: z.string().trim().min(1).optional()
|
|
@@ -2166,10 +2378,12 @@ function readNumberParam(value) {
|
|
|
2166
2378
|
return Number.isFinite(parsed) ? parsed : void 0;
|
|
2167
2379
|
}
|
|
2168
2380
|
function readConnectionFromQuery(url) {
|
|
2381
|
+
const rawPort = url.searchParams.get("port");
|
|
2169
2382
|
return connectionSchema.parse(compactObject({
|
|
2170
2383
|
projectPath: url.searchParams.get("projectPath") ?? void 0,
|
|
2384
|
+
cliPath: url.searchParams.get("cliPath") ?? void 0,
|
|
2171
2385
|
timeout: readNumberParam(url.searchParams.get("timeout")),
|
|
2172
|
-
port:
|
|
2386
|
+
port: rawPort === null ? void 0 : /^\d+$/.test(rawPort) ? Number(rawPort) : Number.NaN,
|
|
2173
2387
|
preferOpenedSession: readBooleanParam(url.searchParams.get("preferOpenedSession")),
|
|
2174
2388
|
preserveProjectRoot: readBooleanParam(url.searchParams.get("preserveProjectRoot")),
|
|
2175
2389
|
sessionId: url.searchParams.get("sessionId") ?? void 0
|
|
@@ -2335,8 +2549,8 @@ async function handleCapture(req, res, manager) {
|
|
|
2335
2549
|
);
|
|
2336
2550
|
if (input.outputPath) {
|
|
2337
2551
|
const resolvedOutputPath = manager.resolveWorkspacePath(input.outputPath);
|
|
2338
|
-
await fs
|
|
2339
|
-
await fs
|
|
2552
|
+
await fs.mkdir(path.dirname(resolvedOutputPath), { recursive: true });
|
|
2553
|
+
await fs.writeFile(resolvedOutputPath, buffer);
|
|
2340
2554
|
return {
|
|
2341
2555
|
path: resolvedOutputPath,
|
|
2342
2556
|
bytes: buffer.length
|
|
@@ -2417,9 +2631,6 @@ async function handleRuntimeRestRequest(req, res, options) {
|
|
|
2417
2631
|
return true;
|
|
2418
2632
|
}
|
|
2419
2633
|
|
|
2420
|
-
const DEFAULT_MCP_HOST = "127.0.0.1";
|
|
2421
|
-
const DEFAULT_MCP_PORT = 3088;
|
|
2422
|
-
const DEFAULT_MCP_ENDPOINT = "/mcp";
|
|
2423
2634
|
function normalizeEndpoint(input) {
|
|
2424
2635
|
const value = typeof input === "string" ? input.trim() : "";
|
|
2425
2636
|
if (!value) {
|
|
@@ -2452,6 +2663,7 @@ async function startStdioServer(options) {
|
|
|
2452
2663
|
return {
|
|
2453
2664
|
transport: "stdio",
|
|
2454
2665
|
close: async () => {
|
|
2666
|
+
await factory.close();
|
|
2455
2667
|
await handle.close();
|
|
2456
2668
|
}
|
|
2457
2669
|
};
|
|
@@ -2550,6 +2762,7 @@ async function startStreamableHttpServer(options) {
|
|
|
2550
2762
|
return {
|
|
2551
2763
|
transport: "streamable-http",
|
|
2552
2764
|
close: async () => {
|
|
2765
|
+
await factory.close();
|
|
2553
2766
|
await mcpHandler.close();
|
|
2554
2767
|
await new Promise((resolve, reject) => {
|
|
2555
2768
|
httpServer.close((error) => {
|
|
@@ -2588,4 +2801,4 @@ ${message}
|
|
|
2588
2801
|
});
|
|
2589
2802
|
}
|
|
2590
2803
|
|
|
2591
|
-
export { DEFAULT_MAX_FILE_CHARS, DEFAULT_MAX_OUTPUT_CHARS, DEFAULT_MAX_RESULTS,
|
|
2804
|
+
export { DEFAULT_MAX_FILE_CHARS, DEFAULT_MAX_OUTPUT_CHARS, DEFAULT_MAX_RESULTS, DEFAULT_TIMEOUT_MS, EXPOSED_PACKAGES, MCP_SERVER_NAME, MCP_SERVER_VERSION, SKIPPED_DIR_NAMES, acceptanceRuntime, assertInsideRoot, createRuntimeAcceptanceService, createWeappViteMcpServer, createWeappViteMcpServerFactory, formatJson, listFilesInDirectory, loadExposedCatalog, loadPackageSummary, normalizeErrorMessage, readFileContent, registerAcceptanceTools, resolveSubPath, resolveWorkspaceRoot, runCommand, searchTextInDirectory, startStdioServer, startWeappViteMcpServer, toToolError, toToolResult };
|