@weapp-vite/mcp 1.5.8 → 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/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$1 from 'node:fs/promises';
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
- const MCP_SERVER_NAME = "@weapp-vite/mcp";
17
- const MCP_SERVER_VERSION = "2.0.0";
18
- const EXPOSED_PACKAGES = {
19
- "weapp-vite": {
20
- id: "weapp-vite",
21
- label: "weapp-vite",
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 resolveWorkspaceRoot(start = process.cwd()) {
54
- let current = path.resolve(start);
55
- while (true) {
56
- if (hasWorkspaceMarkers(current)) {
57
- return current;
58
- }
59
- const parent = path.dirname(current);
60
- if (parent === current) {
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
- current = parent;
64
- }
37
+ };
65
38
  }
66
- function assertInsideRoot(root, targetPath) {
67
- const resolvedRoot = path.resolve(root);
68
- const resolvedTarget = path.resolve(targetPath);
69
- const relative = path.relative(resolvedRoot, resolvedTarget);
70
- if (relative === "" || relative === ".") {
71
- return resolvedTarget;
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
- if (relative.startsWith("..") || path.isAbsolute(relative)) {
74
- throw new Error(`\u8DEF\u5F84\u8D8A\u754C\uFF1A${targetPath}`);
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
- return resolvedTarget;
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
- if (path.isAbsolute(relativePath)) {
83
- throw new Error("\u4EC5\u652F\u6301\u76F8\u5BF9\u8DEF\u5F84");
94
+ getLogs(projectPath, afterSequence = 0) {
95
+ return this.logs.filter((entry) => (!projectPath || entry.projectPath === this.resolveProjectPath(projectPath)) && (entry.sequence ?? 0) > afterSequence);
84
96
  }
85
- return assertInsideRoot(root, path.resolve(root, relativePath));
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
- async function toWorkspaceRelativePath(workspaceRoot, absolutePath) {
97
- const [realWorkspaceRoot, realAbsolutePath] = await Promise.all([
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
- return relativePath;
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
- const packageJson = await readPackageJson(packageJsonPath);
122
- const readmePath = path.join(absolutePath, "README.md");
123
- const changelogPath = path.join(absolutePath, "CHANGELOG.md");
124
- const sourceRoot = await pathExists(path.join(absolutePath, "src")) ? absolutePath : void 0;
125
- 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;
126
- return {
127
- id,
128
- label: config.label,
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
- const absolutePath = path.dirname(packageJsonPath);
152
- const relativePath = await toWorkspaceRelativePath(workspaceRoot, absolutePath);
153
- const packageJson = await readPackageJson(packageJsonPath);
154
- const docsRoot = path.join(absolutePath, "dist", "docs");
155
- const installedReadmePath = path.join(docsRoot, "README.md");
156
- const installedChangelogPath = path.join(docsRoot, "CHANGELOG.md");
157
- const sourceRoot = await pathExists(path.join(absolutePath, "src")) ? absolutePath : void 0;
158
- let cliPath;
159
- if (id === "weapp-vite") {
160
- const binEntry = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.["weapp-vite"];
161
- if (typeof binEntry === "string" && binEntry.trim()) {
162
- const resolvedCliPath = path.resolve(absolutePath, binEntry);
163
- if (await pathExists(resolvedCliPath)) {
164
- cliPath = resolvedCliPath;
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
- return {
169
- id,
170
- label: config.label,
171
- packageName: packageJson.name ?? config.packageName,
172
- version: packageJson.version ?? "0.0.0",
173
- absolutePath,
174
- relativePath,
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
- return [];
196
- }).sort((a, b) => a.id.localeCompare(b.id));
197
- }
198
-
199
- function registerServerPrompts(server, options) {
200
- const { packageIds, packageIdSchema } = options;
201
- server.registerPrompt("plan-weapp-vite-change", {
202
- title: "Plan weapp-vite Change",
203
- description: "\u6839\u636E\u53D8\u66F4\u76EE\u6807\u751F\u6210 weapp-vite / wevu \u4FEE\u6539\u8BA1\u5212\u63D0\u793A\u8BCD",
204
- argsSchema: z.object({
205
- objective: z.string().min(1),
206
- focusPackage: packageIdSchema.optional()
207
- })
208
- }, async ({ objective, focusPackage }) => {
209
- const targets = focusPackage ? [focusPackage] : packageIds;
210
- return {
211
- messages: [
212
- {
213
- role: "user",
214
- content: {
215
- type: "text",
216
- text: [
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
- server.registerPrompt("recover-mini-program-connection", {
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
- async function walkFilesRecursive(root, current, output, maxResults) {
312
- if (output.length >= maxResults) {
313
- return;
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
- const entries = await fs$1.readdir(current, { withFileTypes: true });
316
- for (const entry of entries) {
317
- if (output.length >= maxResults) {
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 (entry.isDirectory()) {
321
- if (SKIPPED_DIR_NAMES.has(entry.name)) {
322
- continue;
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
- if (!entry.isFile()) {
328
- continue;
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
- async function listFilesInDirectory(root, relativeDirectory = ".", maxResults = DEFAULT_MAX_RESULTS) {
336
- const dirPath = resolveSubPath(root, relativeDirectory);
337
- const files = [];
338
- await walkFilesRecursive(root, dirPath, files, maxResults);
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 lines = content.split("\n");
346
- const safeStart = Math.max(1, startLine ?? 1);
347
- const safeEnd = Math.max(safeStart, endLine ?? lines.length);
348
- return lines.slice(safeStart - 1, safeEnd).join("\n");
248
+ const search = new URLSearchParams(query).toString();
249
+ if (!search) {
250
+ return normalizedPath;
251
+ }
252
+ return `${normalizedPath}${normalizedPath.includes("?") ? "&" : "?"}${search}`;
349
253
  }
350
- async function readFileContent(root, relativeFilePath, options) {
351
- const filePath = assertInsideRoot(root, path.join(root, relativeFilePath));
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 collectMatchesFromText(query, relativeFilePath, content, maxResults, output) {
364
- const lines = content.split("\n");
365
- for (let index = 0; index < lines.length; index += 1) {
366
- if (output.length >= maxResults) {
367
- return;
368
- }
369
- const lineText = lines[index] ?? "";
370
- const column = lineText.toLowerCase().indexOf(query.toLowerCase());
371
- if (column === -1) {
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 searchTextInDirectory(root, query, options) {
383
- const maxResults = options?.maxResults ?? DEFAULT_MAX_RESULTS;
384
- const files = await listFilesInDirectory(root, options?.relativeDirectory ?? ".", Math.max(400, maxResults));
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
- content = await fs$1.readFile(absolutePath, "utf8");
394
- } catch {
395
- continue;
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
- return matches;
400
- }
401
-
402
- function formatJson(value) {
403
- return JSON.stringify(value, null, 2);
287
+ throw lastError;
404
288
  }
405
- function normalizeErrorMessage(error) {
406
- if (error instanceof Error) {
407
- return error.message;
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
- isError: true,
426
- content: [{
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 ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
434
- "pnpm",
435
- "node",
436
- "git",
437
- "rg"
438
- ]);
439
- function resolveExecutable(command) {
440
- if (process.platform === "win32") {
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
- return command;
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
- return `${text.slice(0, maxChars)}
458
-
459
- [truncated: ${text.length - maxChars} chars omitted]`;
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 runCommand(workspaceRoot, command, args, options) {
462
- if (!ALLOWED_COMMANDS.has(command)) {
463
- throw new Error(`\u4E0D\u5141\u8BB8\u7684\u547D\u4EE4\uFF1A${command}`);
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
- const cwd = resolveSubPath(workspaceRoot, options?.cwdRelative ?? ".");
466
- const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
467
- const maxOutputChars = options?.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
468
- const executable = resolveExecutable(command);
469
- const child = spawn(executable, args, {
470
- cwd,
471
- env: process.env,
472
- stdio: ["ignore", "pipe", "pipe"],
473
- shell: false
474
- });
475
- let stdout = "";
476
- let stderr = "";
477
- let timedOut = false;
478
- const timer = setTimeout(() => {
479
- timedOut = true;
480
- child.kill("SIGTERM");
481
- }, timeoutMs);
482
- child.stdout.on("data", (chunk) => {
483
- stdout += chunk.toString();
484
- });
485
- child.stderr.on("data", (chunk) => {
486
- stderr += chunk.toString();
487
- });
488
- const exitCode = await new Promise((resolve, reject) => {
489
- child.on("error", reject);
490
- child.on("close", (code) => {
491
- resolve(code ?? -1);
492
- });
493
- }).finally(() => {
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
- command,
498
- args,
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
- async function resolvePackageRoot(workspaceRoot, packageId) {
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 toDocsUri(packageId, fileName) {
515
- return `weapp-vite://docs/${packageId}/${fileName}`;
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 readTextFile(filePath) {
518
- return fs$1.readFile(filePath, "utf8");
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 runWeappViteCliTool(workspaceRoot, input) {
521
- const cliPath = (await resolveExposedPackage(workspaceRoot, "weapp-vite")).cliPath;
522
- if (!cliPath) {
523
- throw new Error("\u5F53\u524D\u5DE5\u4F5C\u533A\u4E2D\u7684 weapp-vite \u672A\u66B4\u9732 CLI \u5165\u53E3");
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
- const finalArgs = [cliPath, input.subCommand];
526
- if (input.projectPath) {
527
- finalArgs.push(resolveSubPath(workspaceRoot, input.projectPath));
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
- if (input.platform) {
530
- finalArgs.push("--platform", input.platform);
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 (Array.isArray(input.args) && input.args.length > 0) {
533
- finalArgs.push(...input.args);
442
+ if (["debug", "log", "info", "warn", "error"].includes(normalized)) {
443
+ return normalized;
534
444
  }
535
- return runCommand(workspaceRoot, "node", finalArgs, {
536
- timeoutMs: input.timeoutMs ?? DEFAULT_TIMEOUT_MS
537
- });
445
+ return "log";
538
446
  }
539
-
540
- function registerServerResources(server, options) {
541
- const { workspaceRoot, packageIds, exposedPackages } = options;
542
- server.registerResource("workspace-catalog", "weapp-vite://workspace/catalog", {
543
- title: "Workspace Catalog",
544
- description: "weapp-vite / wevu \u5305\u76EE\u5F55\u3001\u7248\u672C\u548C\u811A\u672C\u5217\u8868",
545
- mimeType: "application/json"
546
- }, async () => {
547
- const catalog = await resolveExposedPackages(workspaceRoot);
548
- const text = JSON.stringify({ workspaceRoot, packages: catalog }, null, 2);
549
- return {
550
- contents: [{
551
- uri: "weapp-vite://workspace/catalog",
552
- mimeType: "application/json",
553
- text
554
- }]
555
- };
556
- });
557
- for (const summary of exposedPackages) {
558
- if (summary.docs.readme) {
559
- const uri = toDocsUri(summary.id, "README.md");
560
- server.registerResource(`docs-${summary.id}-readme`, uri, {
561
- title: `${summary.id} README`,
562
- mimeType: "text/markdown"
563
- }, async () => {
564
- const text = await readTextFile(summary.docs.readme);
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
- contents: [{ uri, mimeType: "text/markdown", text }]
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
- if (summary.docs.changelog) {
571
- const uri = toDocsUri(summary.id, "CHANGELOG.md");
572
- server.registerResource(`docs-${summary.id}-changelog`, uri, {
573
- title: `${summary.id} CHANGELOG`,
574
- mimeType: "text/markdown"
575
- }, async () => {
576
- const text = await readTextFile(summary.docs.changelog);
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
- contents: [{ uri, mimeType: "text/markdown", text }]
561
+ transition,
562
+ url,
563
+ activePage: page ? { path: page.path, query: toSerializableValue(page.query) } : null
579
564
  };
580
565
  });
581
- }
582
- }
583
- const sourceTemplate = new ResourceTemplate("weapp-vite://source/{package}?path={path}", {
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.registerResource("source-template", sourceTemplate, {
590
- title: "Source Template",
591
- description: "\u8BFB\u53D6 weapp-vite / wevu \u4EFB\u610F\u6E90\u7801\u6587\u4EF6\uFF08\u901A\u8FC7 package + path \u53C2\u6570\uFF09",
592
- mimeType: "text/plain"
593
- }, async (uri, variables) => {
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 packageId = String(variables.package ?? "");
596
- if (!packageIds.includes(packageId)) {
597
- throw new Error(`\u672A\u77E5 package\uFF1A${packageId}`);
598
- }
599
- const relativePath = decodeURIComponent(String(variables.path ?? ""));
600
- const packageRoot = await resolvePackageRoot(workspaceRoot, packageId);
601
- const { content } = await readFileContent(packageRoot, relativePath, {
602
- maxChars: DEFAULT_MAX_FILE_CHARS
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
- const DEFAULT_SCREENSHOT_TIMEOUT = 6e4;
624
- const SCREENSHOT_RETRY_DELAY = 500;
625
- const connectionSchema$1 = z.object({
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
- return await runner(miniProgram);
682
- } finally {
683
- releaseSharedMiniProgram(projectPath, input.sessionId || input.port);
684
- }
685
- }
686
- async withPage(input, runner) {
687
- const projectPath = this.resolveProjectPath(input.projectPath);
688
- const sessionKey = this.resolveSessionKey(projectPath, input);
689
- return await this.withMiniProgram(input, async (miniProgram) => {
690
- const page = await this.resolveCurrentPage(sessionKey, miniProgram);
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
- this.detach(sessionKey);
718
- const onConsole = (payload) => {
719
- this.pushLog(normalizeConsoleEvent(payload));
720
- };
721
- const onException = (payload) => {
722
- this.pushLog(normalizeExceptionEvent(payload));
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 page = await miniProgram.currentPage();
735
- this.attachedSessions.get(sessionKey).currentPage = page;
736
- return page;
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
- const cachedPage = this.attachedSessions.get(sessionKey)?.currentPage;
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
- this.attachedSessions.delete(projectPath);
755
- }
756
- pushLog(entry) {
757
- this.logs.push(entry);
758
- while (this.logs.length > this.maxLogs) {
759
- this.logs.shift();
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
- function buildUrl(pagePath, query) {
764
- const normalizedPath = pagePath.startsWith("/") ? pagePath : `/${pagePath}`;
765
- if (!query || Object.keys(query).length === 0) {
766
- return normalizedPath;
767
- }
768
- const search = new URLSearchParams(query).toString();
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 screenshot = await miniProgram.screenshot({ timeout });
794
- if (screenshot == null) {
795
- throw new Error("DevTools screenshot returned no data.");
675
+ const logs = manager.getLogs();
676
+ if (clear) {
677
+ manager.clearLogs();
796
678
  }
797
- return typeof screenshot === "string" ? Buffer.from(screenshot, "base64") : Buffer.from(screenshot);
679
+ return toToolResult({
680
+ count: logs.length,
681
+ logs
682
+ });
798
683
  } catch (error) {
799
- lastError = error;
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 navigateSchema = {
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
- method: z.string().trim().min(1),
1017
- args: z.array(z.unknown()).optional()
691
+ selector: z.string().trim().min(1),
692
+ innerSelector: z.string().trim().min(1).optional()
1018
693
  };
1019
- function registerDevtoolsRuntimeTools(server, manager) {
1020
- server.registerTool("weapp_devtools_connect", {
1021
- title: "Ensure Mini Program Connection",
1022
- 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",
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
- ...connectionInputSchema,
1025
- reconnect: z.boolean().optional()
700
+ ...elementSelectorSchema,
701
+ waitMs: z.number().int().nonnegative().optional()
1026
702
  })
1027
- }, async ({ reconnect, ...connection }) => {
703
+ }, async ({ selector, innerSelector, waitMs, ...connection }) => {
1028
704
  try {
1029
- if (reconnect) {
1030
- await manager.close(connection);
1031
- }
1032
- const result = await manager.withMiniProgram(connection, async (miniProgram) => {
1033
- const page = await miniProgram.currentPage().catch(() => null);
1034
- manager.rememberPage(connection, page);
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
- connected: true,
1038
- projectPath: connection.projectPath,
1039
- resolvedProjectPath: manager.resolveProjectPath(connection.projectPath),
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("weapp_devtools_route", {
1050
- title: "Navigate Mini Program",
1051
- description: "\u5728\u5C0F\u7A0B\u5E8F\u5185\u6267\u884C navigateTo\u3001redirectTo\u3001reLaunch\u3001switchTab \u6216 navigateBack\u3002",
1052
- inputSchema: z.object(navigateSchema)
1053
- }, async ({ path: pagePath, query, transition = "navigateTo", waitMs, ...connection }) => {
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.withMiniProgram(connection, async (miniProgram) => {
1056
- if (transition === "navigateBack") {
1057
- const page2 = await miniProgram.navigateBack();
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
- transition,
1082
- url,
1083
- activePage: page ? { path: page.path, query: toSerializableValue(page.query) } : null
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("weapp_devtools_active_page", {
1092
- title: "Current Mini Program Page",
1093
- 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",
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
- ...connectionInputSchema,
1096
- withData: z.boolean().optional()
749
+ ...elementSelectorSchema,
750
+ method: z.string().trim().min(1),
751
+ args: z.array(z.unknown()).optional()
1097
752
  })
1098
- }, async ({ withData, ...connection }) => {
753
+ }, async ({ selector, innerSelector, method, args, ...connection }) => {
1099
754
  try {
1100
755
  const result = await manager.withPage(connection, async (page) => {
1101
- const [size, scrollTop, data] = await Promise.all([
1102
- page.size().catch(() => null),
1103
- page.scrollTop().catch(() => null),
1104
- withData ? page.data().catch(() => null) : Promise.resolve(void 0)
1105
- ]);
1106
- return compactObject({
1107
- path: page.path,
1108
- query: toSerializableValue(page.query),
1109
- size: toSerializableValue(size),
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
- server.registerTool("weapp_runtime_find_node_by_xpath", {
1597
- title: "Get Page Element by XPath",
1598
- 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",
1599
- inputSchema: z.object(xpathSchema)
1600
- }, async ({ xpath, withWxml, ...connection }) => {
1601
- try {
1602
- const result = await manager.withPage(connection, async (page) => {
1603
- const element = await callRequiredMethod(page, "getElementByXpath", xpath);
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
- xpath,
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
- server.registerTool("weapp_runtime_find_nodes_by_xpath", {
1618
- title: "Get Page Elements by XPath",
1619
- 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",
1620
- inputSchema: z.object(xpathSchema)
1621
- }, async ({ xpath, withWxml, ...connection }) => {
1622
- try {
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
- xpath,
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
- server.registerTool("weapp_runtime_update_page_state", {
1708
- title: "Set Page Data",
1709
- description: "\u8C03\u7528\u5F53\u524D\u9875\u9762 setData \u66F4\u65B0 data\u3002",
1710
- inputSchema: z.object({
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.registerTool("weapp_runtime_invoke_page", {
1730
- title: "Call Page Method",
1731
- description: "\u8C03\u7528\u5F53\u524D\u9875\u9762\u5B9E\u4F8B\u65B9\u6CD5\u3002",
1732
- inputSchema: z.object({
1733
- ...connectionInputSchema,
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 result = await manager.withPage(connection, async (page) => {
1740
- const callArgs = args ?? [];
1741
- return {
1742
- method,
1743
- args: toSerializableValue(callArgs),
1744
- result: toSerializableValue(await callRequiredMethod(page, "callMethod", method, ...callArgs))
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 toToolResult(result);
1925
+ return {
1926
+ contents: [{
1927
+ uri: uri.toString(),
1928
+ mimeType: "text/plain",
1929
+ text: content
1930
+ }]
1931
+ };
1748
1932
  } catch (error) {
1749
- return toToolError(error);
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: factory.createServer(),
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: readNumberParam(url.searchParams.get("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$1.mkdir(path.dirname(resolvedOutputPath), { recursive: true });
2339
- await fs$1.writeFile(resolvedOutputPath, buffer);
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, DEFAULT_MCP_ENDPOINT, DEFAULT_MCP_HOST, DEFAULT_MCP_PORT, DEFAULT_RUNTIME_REST_ENDPOINT, DEFAULT_TIMEOUT_MS, EXPOSED_PACKAGES, MCP_SERVER_NAME, MCP_SERVER_VERSION, SKIPPED_DIR_NAMES, assertInsideRoot, createWeappViteMcpServer, createWeappViteMcpServerFactory, formatJson, listFilesInDirectory, loadExposedCatalog, loadPackageSummary, normalizeErrorMessage, readFileContent, resolveSubPath, resolveWorkspaceRoot, runCommand, searchTextInDirectory, startStdioServer, startWeappViteMcpServer, toToolError, toToolResult };
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 };