@weapp-vite/mcp 1.2.1 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,15 +1,17 @@
1
1
  import process from 'node:process';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { Buffer } from 'node:buffer';
4
+ import { randomUUID } from 'node:crypto';
4
5
  import http from 'node:http';
5
6
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
6
7
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
7
8
  import fs$1 from 'node:fs/promises';
8
- import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
9
- import { z } from 'zod';
10
- import { createRequire } from 'node:module';
11
9
  import path from 'node:path';
10
+ import { z } from 'zod';
11
+ import { closeSharedMiniProgram, acquireSharedMiniProgram, releaseSharedMiniProgram } from '@weapp-vite/devtools-runtime';
12
+ import { ResourceTemplate, McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
13
  import fs from 'node:fs';
14
+ import { createRequire } from 'node:module';
13
15
  import { spawn } from 'node:child_process';
14
16
 
15
17
  const MCP_SERVER_NAME = "@weapp-vite/mcp";
@@ -84,6 +86,118 @@ function resolveSubPath(root, relativePath) {
84
86
  return assertInsideRoot(root, path.resolve(root, relativePath));
85
87
  }
86
88
 
89
+ function registerServerPrompts(server, options) {
90
+ const { packageIds, packageIdSchema } = options;
91
+ server.registerPrompt("plan-weapp-vite-change", {
92
+ title: "Plan weapp-vite Change",
93
+ description: "\u6839\u636E\u53D8\u66F4\u76EE\u6807\u751F\u6210 weapp-vite / wevu \u4FEE\u6539\u8BA1\u5212\u63D0\u793A\u8BCD",
94
+ argsSchema: {
95
+ objective: z.string().min(1),
96
+ focusPackage: packageIdSchema.optional()
97
+ }
98
+ }, async ({ objective, focusPackage }) => {
99
+ const targets = focusPackage ? [focusPackage] : packageIds;
100
+ return {
101
+ messages: [
102
+ {
103
+ role: "user",
104
+ content: {
105
+ type: "text",
106
+ text: [
107
+ "\u4F60\u662F weapp-vite monorepo \u7EF4\u62A4\u8005\uFF0C\u8BF7\u7ED9\u51FA\u53EF\u6267\u884C\u7684\u6539\u9020\u8BA1\u5212\u3002",
108
+ `\u76EE\u6807\uFF1A${objective}`,
109
+ `\u805A\u7126\u5305\uFF1A${targets.join(", ")}`,
110
+ "\u8BF7\u5305\u542B\uFF1A\u5F71\u54CD\u9762\u3001\u98CE\u9669\u70B9\u3001\u6D4B\u8BD5\u7B56\u7565\u3001\u56DE\u6EDA\u7B56\u7565\u3002"
111
+ ].join("\n")
112
+ }
113
+ }
114
+ ]
115
+ };
116
+ });
117
+ server.registerPrompt("debug-wevu-runtime", {
118
+ title: "Debug wevu Runtime",
119
+ description: "\u7528\u4E8E\u5B9A\u4F4D wevu runtime \u751F\u547D\u5468\u671F/\u54CD\u5E94\u5F0F\u95EE\u9898\u7684\u6807\u51C6\u63D0\u793A\u8BCD",
120
+ argsSchema: {
121
+ symptom: z.string().min(1)
122
+ }
123
+ }, async ({ symptom }) => {
124
+ return {
125
+ messages: [
126
+ {
127
+ role: "user",
128
+ content: {
129
+ type: "text",
130
+ text: [
131
+ "\u8BF7\u57FA\u4E8E wevu runtime \u4EE3\u7801\u8DEF\u5F84\u8FDB\u884C\u5206\u5C42\u6392\u67E5\uFF1A",
132
+ "1. \u590D\u73B0\u573A\u666F\u4E0E\u6700\u5C0F\u6837\u4F8B",
133
+ "2. \u751F\u547D\u5468\u671F\u94A9\u5B50\u89E6\u53D1\u94FE",
134
+ "3. \u54CD\u5E94\u5F0F\u4E0E setData \u5DEE\u91CF\u540C\u6B65\u94FE",
135
+ "4. \u5355\u6D4B\u4E0E e2e \u56DE\u5F52\u8865\u4E01",
136
+ `\u73B0\u8C61\uFF1A${symptom}`
137
+ ].join("\n")
138
+ }
139
+ }
140
+ ]
141
+ };
142
+ });
143
+ server.registerPrompt("inspect-mini-program-page", {
144
+ title: "Inspect Mini Program Page",
145
+ 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",
146
+ argsSchema: {
147
+ projectPath: z.string().min(1),
148
+ pagePath: z.string().optional(),
149
+ focus: z.string().optional()
150
+ }
151
+ }, async ({ projectPath, pagePath, focus }) => {
152
+ return {
153
+ messages: [
154
+ {
155
+ role: "user",
156
+ content: {
157
+ type: "text",
158
+ text: [
159
+ "\u8BF7\u6309\u987A\u5E8F\u4F7F\u7528 weapp-vite MCP runtime tools \u68C0\u67E5\u5C0F\u7A0B\u5E8F\u9875\u9762\uFF1A",
160
+ `1. \u8C03\u7528 weapp_devtools_connect\uFF0CprojectPath=${projectPath}`,
161
+ pagePath ? `2. \u8C03\u7528 weapp_devtools_route \u8DF3\u8F6C\u5230 ${pagePath}` : "2. \u8C03\u7528 weapp_devtools_active_page \u786E\u8BA4\u5F53\u524D\u9875\u9762",
162
+ "3. \u8C03\u7528 weapp_devtools_capture \u83B7\u53D6\u622A\u56FE",
163
+ "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",
164
+ "5. \u8C03\u7528 weapp_devtools_console \u68C0\u67E5 console/exception \u65E5\u5FD7",
165
+ 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"
166
+ ].join("\n")
167
+ }
168
+ }
169
+ ]
170
+ };
171
+ });
172
+ server.registerPrompt("recover-mini-program-connection", {
173
+ title: "Recover Mini Program Connection",
174
+ description: "\u6062\u590D\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177 automator \u8FDE\u63A5\u7684\u6807\u51C6\u6D41\u7A0B",
175
+ argsSchema: {
176
+ projectPath: z.string().min(1),
177
+ lastError: z.string().optional()
178
+ }
179
+ }, async ({ projectPath, lastError }) => {
180
+ return {
181
+ messages: [
182
+ {
183
+ role: "user",
184
+ content: {
185
+ type: "text",
186
+ text: [
187
+ "\u8BF7\u6309\u987A\u5E8F\u6062\u590D weapp-vite MCP runtime \u8FDE\u63A5\uFF1A",
188
+ `1. \u8C03\u7528 weapp_devtools_connect\uFF0CprojectPath=${projectPath}\uFF0Creconnect=true`,
189
+ "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",
190
+ "3. \u5982\u679C\u662F\u534F\u8BAE\u8D85\u65F6\uFF0C\u5173\u95ED\u591A\u4F59 DevTools \u7A97\u53E3\u540E\u53EA\u91CD\u8BD5\u4E00\u6B21",
191
+ "4. \u6062\u590D\u540E\u8C03\u7528 weapp_devtools_active_page \u548C weapp_devtools_console \u786E\u8BA4\u72B6\u6001",
192
+ lastError ? `\u4E0A\u4E00\u6B21\u9519\u8BEF\uFF1A${lastError}` : "\u4E0A\u4E00\u6B21\u9519\u8BEF\uFF1A\u672A\u63D0\u4F9B\u3002"
193
+ ].join("\n")
194
+ }
195
+ }
196
+ ]
197
+ };
198
+ });
199
+ }
200
+
87
201
  async function pathExists(filePath) {
88
202
  try {
89
203
  await fs$1.access(filePath);
@@ -195,87 +309,6 @@ async function resolveExposedPackages(workspaceRoot) {
195
309
  }).sort((a, b) => a.id.localeCompare(b.id));
196
310
  }
197
311
 
198
- async function loadPackageSummary(workspaceRoot, id) {
199
- return resolveExposedPackage(workspaceRoot, id);
200
- }
201
- async function loadExposedCatalog(workspaceRoot) {
202
- return resolveExposedPackages(workspaceRoot);
203
- }
204
-
205
- const ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
206
- "pnpm",
207
- "node",
208
- "git",
209
- "rg"
210
- ]);
211
- function resolveExecutable(command) {
212
- if (process.platform === "win32") {
213
- if (command === "pnpm") {
214
- return "pnpm.cmd";
215
- }
216
- if (command === "git") {
217
- return "git.exe";
218
- }
219
- if (command === "rg") {
220
- return "rg.exe";
221
- }
222
- }
223
- return command;
224
- }
225
- function truncateOutput(text, maxChars) {
226
- if (text.length <= maxChars) {
227
- return text;
228
- }
229
- return `${text.slice(0, maxChars)}
230
-
231
- [truncated: ${text.length - maxChars} chars omitted]`;
232
- }
233
- async function runCommand(workspaceRoot, command, args, options) {
234
- if (!ALLOWED_COMMANDS.has(command)) {
235
- throw new Error(`\u4E0D\u5141\u8BB8\u7684\u547D\u4EE4\uFF1A${command}`);
236
- }
237
- const cwd = resolveSubPath(workspaceRoot, options?.cwdRelative ?? ".");
238
- const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
239
- const maxOutputChars = options?.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
240
- const executable = resolveExecutable(command);
241
- const child = spawn(executable, args, {
242
- cwd,
243
- env: process.env,
244
- stdio: ["ignore", "pipe", "pipe"],
245
- shell: false
246
- });
247
- let stdout = "";
248
- let stderr = "";
249
- let timedOut = false;
250
- const timer = setTimeout(() => {
251
- timedOut = true;
252
- child.kill("SIGTERM");
253
- }, timeoutMs);
254
- child.stdout.on("data", (chunk) => {
255
- stdout += chunk.toString();
256
- });
257
- child.stderr.on("data", (chunk) => {
258
- stderr += chunk.toString();
259
- });
260
- const exitCode = await new Promise((resolve, reject) => {
261
- child.on("error", reject);
262
- child.on("close", (code) => {
263
- resolve(code ?? -1);
264
- });
265
- }).finally(() => {
266
- clearTimeout(timer);
267
- });
268
- return {
269
- command,
270
- args,
271
- cwd: path.resolve(cwd),
272
- exitCode,
273
- stdout: truncateOutput(stdout.trim(), maxOutputChars),
274
- stderr: truncateOutput(stderr.trim(), maxOutputChars),
275
- timedOut
276
- };
277
- }
278
-
279
312
  async function walkFilesRecursive(root, current, output, maxResults) {
280
313
  if (output.length >= maxResults) {
281
314
  return;
@@ -398,8 +431,80 @@ function toToolError(error) {
398
431
  };
399
432
  }
400
433
 
401
- const packageIds = Object.keys(EXPOSED_PACKAGES);
402
- const packageIdSchema = z.enum(packageIds);
434
+ const ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
435
+ "pnpm",
436
+ "node",
437
+ "git",
438
+ "rg"
439
+ ]);
440
+ function resolveExecutable(command) {
441
+ if (process.platform === "win32") {
442
+ if (command === "pnpm") {
443
+ return "pnpm.cmd";
444
+ }
445
+ if (command === "git") {
446
+ return "git.exe";
447
+ }
448
+ if (command === "rg") {
449
+ return "rg.exe";
450
+ }
451
+ }
452
+ return command;
453
+ }
454
+ function truncateOutput(text, maxChars) {
455
+ if (text.length <= maxChars) {
456
+ return text;
457
+ }
458
+ return `${text.slice(0, maxChars)}
459
+
460
+ [truncated: ${text.length - maxChars} chars omitted]`;
461
+ }
462
+ async function runCommand(workspaceRoot, command, args, options) {
463
+ if (!ALLOWED_COMMANDS.has(command)) {
464
+ throw new Error(`\u4E0D\u5141\u8BB8\u7684\u547D\u4EE4\uFF1A${command}`);
465
+ }
466
+ const cwd = resolveSubPath(workspaceRoot, options?.cwdRelative ?? ".");
467
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
468
+ const maxOutputChars = options?.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
469
+ const executable = resolveExecutable(command);
470
+ const child = spawn(executable, args, {
471
+ cwd,
472
+ env: process.env,
473
+ stdio: ["ignore", "pipe", "pipe"],
474
+ shell: false
475
+ });
476
+ let stdout = "";
477
+ let stderr = "";
478
+ let timedOut = false;
479
+ const timer = setTimeout(() => {
480
+ timedOut = true;
481
+ child.kill("SIGTERM");
482
+ }, timeoutMs);
483
+ child.stdout.on("data", (chunk) => {
484
+ stdout += chunk.toString();
485
+ });
486
+ child.stderr.on("data", (chunk) => {
487
+ stderr += chunk.toString();
488
+ });
489
+ const exitCode = await new Promise((resolve, reject) => {
490
+ child.on("error", reject);
491
+ child.on("close", (code) => {
492
+ resolve(code ?? -1);
493
+ });
494
+ }).finally(() => {
495
+ clearTimeout(timer);
496
+ });
497
+ return {
498
+ command,
499
+ args,
500
+ cwd: path.resolve(cwd),
501
+ exitCode,
502
+ stdout: truncateOutput(stdout.trim(), maxOutputChars),
503
+ stderr: truncateOutput(stderr.trim(), maxOutputChars),
504
+ timedOut
505
+ };
506
+ }
507
+
403
508
  async function resolvePackageRoot(workspaceRoot, packageId) {
404
509
  const resolved = await resolveExposedPackage(workspaceRoot, packageId);
405
510
  if (!resolved.sourceRoot) {
@@ -432,21 +537,1120 @@ async function runWeappViteCliTool(workspaceRoot, input) {
432
537
  timeoutMs: input.timeoutMs ?? DEFAULT_TIMEOUT_MS
433
538
  });
434
539
  }
435
- async function createWeappViteMcpServer(options) {
436
- const workspaceRoot = resolveWorkspaceRoot(options?.workspaceRoot);
437
- const server = new McpServer({
438
- name: MCP_SERVER_NAME,
439
- version: MCP_SERVER_VERSION
440
- });
540
+
541
+ async function registerServerResources(server, options) {
542
+ const { workspaceRoot, packageIds } = options;
543
+ server.registerResource("workspace-catalog", "weapp-vite://workspace/catalog", {
544
+ title: "Workspace Catalog",
545
+ description: "weapp-vite / wevu \u5305\u76EE\u5F55\u3001\u7248\u672C\u548C\u811A\u672C\u5217\u8868",
546
+ mimeType: "application/json"
547
+ }, async () => {
548
+ const catalog2 = await resolveExposedPackages(workspaceRoot);
549
+ const text = JSON.stringify({ workspaceRoot, packages: catalog2 }, null, 2);
550
+ return {
551
+ contents: [{
552
+ uri: "weapp-vite://workspace/catalog",
553
+ mimeType: "application/json",
554
+ text
555
+ }]
556
+ };
557
+ });
558
+ const catalog = await resolveExposedPackages(workspaceRoot);
559
+ for (const summary of catalog) {
560
+ if (summary.docs.readme) {
561
+ const uri = toDocsUri(summary.id, "README.md");
562
+ server.registerResource(`docs-${summary.id}-readme`, uri, {
563
+ title: `${summary.id} README`,
564
+ mimeType: "text/markdown"
565
+ }, async () => {
566
+ const text = await readTextFile(summary.docs.readme);
567
+ return {
568
+ contents: [{ uri, mimeType: "text/markdown", text }]
569
+ };
570
+ });
571
+ }
572
+ if (summary.docs.changelog) {
573
+ const uri = toDocsUri(summary.id, "CHANGELOG.md");
574
+ server.registerResource(`docs-${summary.id}-changelog`, uri, {
575
+ title: `${summary.id} CHANGELOG`,
576
+ mimeType: "text/markdown"
577
+ }, async () => {
578
+ const text = await readTextFile(summary.docs.changelog);
579
+ return {
580
+ contents: [{ uri, mimeType: "text/markdown", text }]
581
+ };
582
+ });
583
+ }
584
+ }
585
+ const sourceTemplate = new ResourceTemplate("weapp-vite://source/{package}?path={path}", {
586
+ list: void 0,
587
+ complete: {
588
+ package: () => packageIds
589
+ }
590
+ });
591
+ server.registerResource("source-template", sourceTemplate, {
592
+ title: "Source Template",
593
+ description: "\u8BFB\u53D6 weapp-vite / wevu \u4EFB\u610F\u6E90\u7801\u6587\u4EF6\uFF08\u901A\u8FC7 package + path \u53C2\u6570\uFF09",
594
+ mimeType: "text/plain"
595
+ }, async (uri, variables) => {
596
+ try {
597
+ const packageId = String(variables.package ?? "");
598
+ if (!packageIds.includes(packageId)) {
599
+ throw new Error(`\u672A\u77E5 package\uFF1A${packageId}`);
600
+ }
601
+ const relativePath = decodeURIComponent(String(variables.path ?? ""));
602
+ const packageRoot = await resolvePackageRoot(workspaceRoot, packageId);
603
+ const { content } = await readFileContent(packageRoot, relativePath, {
604
+ maxChars: DEFAULT_MAX_FILE_CHARS
605
+ });
606
+ return {
607
+ contents: [{
608
+ uri: uri.toString(),
609
+ mimeType: "text/plain",
610
+ text: content
611
+ }]
612
+ };
613
+ } catch (error) {
614
+ return {
615
+ contents: [{
616
+ uri: uri.toString(),
617
+ mimeType: "text/plain",
618
+ text: `[resource-error] ${normalizeErrorMessage(error)}`
619
+ }]
620
+ };
621
+ }
622
+ });
623
+ }
624
+
625
+ 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
+ preferOpenedSession: z.boolean().optional()
629
+ });
630
+ const connectionInputSchema = {
631
+ projectPath: z.string().trim().min(1).describe("\u5C0F\u7A0B\u5E8F\u9879\u76EE\u8DEF\u5F84\uFF1B\u652F\u6301 workspaceRoot \u76F8\u5BF9\u8DEF\u5F84"),
632
+ timeout: z.number().int().positive().optional(),
633
+ preferOpenedSession: z.boolean().optional()
634
+ };
635
+ class RuntimeSessionManager {
636
+ constructor(workspaceRoot, runtimeHooks = createUnavailableRuntimeHooks()) {
637
+ this.workspaceRoot = workspaceRoot;
638
+ this.runtimeHooks = runtimeHooks;
639
+ }
640
+ logs = [];
641
+ attachedSessions = /* @__PURE__ */ new Map();
642
+ maxLogs = 1e3;
643
+ async close(input) {
644
+ const projectPath = this.resolveProjectPath(input.projectPath);
645
+ this.detach(projectPath);
646
+ await closeSharedMiniProgram(projectPath);
647
+ }
648
+ clearLogs() {
649
+ this.logs.length = 0;
650
+ }
651
+ getLogs() {
652
+ return [...this.logs];
653
+ }
654
+ resolveProjectPath(projectPath) {
655
+ return path.isAbsolute(projectPath) ? path.normalize(projectPath) : path.resolve(this.workspaceRoot, projectPath);
656
+ }
657
+ resolveWorkspacePath(filePath) {
658
+ return path.isAbsolute(filePath) ? path.normalize(filePath) : path.resolve(this.workspaceRoot, filePath);
659
+ }
660
+ async withMiniProgram(input, runner) {
661
+ const projectPath = this.resolveProjectPath(input.projectPath);
662
+ const miniProgram = await acquireSharedMiniProgram(this.runtimeHooks, {
663
+ projectPath,
664
+ timeout: input.timeout,
665
+ preferOpenedSession: input.preferOpenedSession,
666
+ sharedSession: true
667
+ });
668
+ this.attach(projectPath, miniProgram);
669
+ try {
670
+ return await runner(miniProgram);
671
+ } catch (error) {
672
+ this.detach(projectPath);
673
+ await closeSharedMiniProgram(projectPath);
674
+ throw error;
675
+ } finally {
676
+ releaseSharedMiniProgram(projectPath);
677
+ }
678
+ }
679
+ async withPage(input, runner) {
680
+ return await this.withMiniProgram(input, async (miniProgram) => {
681
+ const page = await miniProgram.currentPage();
682
+ if (!page) {
683
+ 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");
684
+ }
685
+ return await runner(page, miniProgram);
686
+ });
687
+ }
688
+ attach(projectPath, miniProgram) {
689
+ const existing = this.attachedSessions.get(projectPath);
690
+ if (existing?.miniProgram === miniProgram) {
691
+ return;
692
+ }
693
+ this.detach(projectPath);
694
+ const onConsole = (payload) => {
695
+ this.pushLog(normalizeConsoleEvent(payload));
696
+ };
697
+ const onException = (payload) => {
698
+ this.pushLog(normalizeExceptionEvent(payload));
699
+ };
700
+ miniProgram.on("console", onConsole);
701
+ miniProgram.on("exception", onException);
702
+ this.attachedSessions.set(projectPath, {
703
+ miniProgram,
704
+ onConsole,
705
+ onException
706
+ });
707
+ }
708
+ detach(projectPath) {
709
+ const attached = this.attachedSessions.get(projectPath);
710
+ if (!attached) {
711
+ return;
712
+ }
713
+ if (typeof attached.miniProgram.off === "function") {
714
+ attached.miniProgram.off("console", attached.onConsole);
715
+ attached.miniProgram.off("exception", attached.onException);
716
+ }
717
+ this.attachedSessions.delete(projectPath);
718
+ }
719
+ pushLog(entry) {
720
+ this.logs.push(entry);
721
+ while (this.logs.length > this.maxLogs) {
722
+ this.logs.shift();
723
+ }
724
+ }
725
+ }
726
+ function buildUrl(pagePath, query) {
727
+ const normalizedPath = pagePath.startsWith("/") ? pagePath : `/${pagePath}`;
728
+ if (!query || Object.keys(query).length === 0) {
729
+ return normalizedPath;
730
+ }
731
+ const search = new URLSearchParams(query).toString();
732
+ if (!search) {
733
+ return normalizedPath;
734
+ }
735
+ return `${normalizedPath}${normalizedPath.includes("?") ? "&" : "?"}${search}`;
736
+ }
737
+ function parseSelectorWithIndex(selector) {
738
+ const match = selector.match(/^(.+?)\[index=(\d+)\]$/);
739
+ if (!match?.[1] || !match[2]) {
740
+ return {
741
+ selector,
742
+ index: void 0
743
+ };
744
+ }
745
+ return {
746
+ selector: match[1],
747
+ index: Number.parseInt(match[2], 10)
748
+ };
749
+ }
750
+ async function resolveElement(page, selectorInput, innerSelector) {
751
+ const { selector, index } = parseSelectorWithIndex(selectorInput);
752
+ let element;
753
+ if (index === void 0) {
754
+ element = await page.$(selector);
755
+ } else {
756
+ const elements = await queryElements(page, selector);
757
+ if (index < 0 || index >= elements.length) {
758
+ 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`);
759
+ }
760
+ element = elements[index];
761
+ }
762
+ if (!element) {
763
+ throw new Error(`\u672A\u627E\u5230\u5143\u7D20: ${selectorInput}`);
764
+ }
765
+ if (!innerSelector) {
766
+ return element;
767
+ }
768
+ const inner = await callOptionalMethod(element, "$", innerSelector);
769
+ if (!inner) {
770
+ throw new Error(`\u5728\u5143\u7D20 "${selectorInput}" \u5185\u672A\u627E\u5230\u5143\u7D20: ${innerSelector}`);
771
+ }
772
+ return inner;
773
+ }
774
+ async function queryElements(page, selectorInput) {
775
+ const { selector, index } = parseSelectorWithIndex(selectorInput);
776
+ const elements = await callOptionalMethod(page, "$$", selector);
777
+ if (!Array.isArray(elements)) {
778
+ return [];
779
+ }
780
+ if (index === void 0) {
781
+ return elements;
782
+ }
783
+ if (index < 0 || index >= elements.length) {
784
+ 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`);
785
+ }
786
+ return [elements[index]];
787
+ }
788
+ async function summarizeElement(element, withWxml = false) {
789
+ const [text, value, size, offset, scrollWidth, scrollHeight, outerWxml] = await Promise.all([
790
+ callMaybe(element, "text"),
791
+ callMaybe(element, "value"),
792
+ callMaybe(element, "size"),
793
+ callMaybe(element, "offset"),
794
+ callMaybe(element, "scrollWidth"),
795
+ callMaybe(element, "scrollHeight"),
796
+ withWxml ? callMaybe(element, "outerWxml") : Promise.resolve(void 0)
797
+ ]);
798
+ return compactObject({
799
+ tagName: readProperty(element, "tagName"),
800
+ text,
801
+ value,
802
+ size,
803
+ offset,
804
+ scrollWidth,
805
+ scrollHeight,
806
+ outerWxml
807
+ });
808
+ }
809
+ function toSerializableValue(value) {
810
+ if (value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
811
+ return value;
812
+ }
813
+ if (typeof value === "bigint") {
814
+ return value.toString();
815
+ }
816
+ if (value instanceof Date) {
817
+ return value.toISOString();
818
+ }
819
+ if (Buffer.isBuffer(value)) {
820
+ return value.toString("base64");
821
+ }
822
+ if (Array.isArray(value)) {
823
+ return value.map((item) => toSerializableValue(item));
824
+ }
825
+ if (typeof value === "object") {
826
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, toSerializableValue(item)]));
827
+ }
828
+ return String(value);
829
+ }
830
+ async function callRequiredMethod(target, methodName, ...args) {
831
+ const method = readProperty(target, methodName);
832
+ if (typeof method !== "function") {
833
+ throw new TypeError(`\u5F53\u524D\u5BF9\u8C61\u4E0D\u652F\u6301 ${methodName} \u65B9\u6CD5\u3002`);
834
+ }
835
+ return await method.apply(target, args);
836
+ }
837
+ async function callOptionalMethod(target, methodName, ...args) {
838
+ const method = readProperty(target, methodName);
839
+ if (typeof method !== "function") {
840
+ return void 0;
841
+ }
842
+ return await method.apply(target, args);
843
+ }
844
+ async function callMaybe(target, methodName, ...args) {
845
+ try {
846
+ return await callOptionalMethod(target, methodName, ...args);
847
+ } catch {
848
+ return void 0;
849
+ }
850
+ }
851
+ function compactObject(input) {
852
+ return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
853
+ }
854
+ function readProperty(target, key) {
855
+ if (!target || typeof target !== "object") {
856
+ return void 0;
857
+ }
858
+ return target[key];
859
+ }
860
+ function normalizeConsoleEvent(payload) {
861
+ const record = toRecord$1(payload);
862
+ return {
863
+ level: normalizeLogLevel(record.type ?? record.level ?? record.method),
864
+ message: resolveLogMessage(record, payload),
865
+ timestamp: Date.now(),
866
+ raw: toSerializableValue(payload)
867
+ };
868
+ }
869
+ function normalizeExceptionEvent(payload) {
870
+ const record = toRecord$1(payload);
871
+ const error = toRecord$1(record.error);
872
+ const message = [
873
+ typeof error.message === "string" ? error.message : void 0,
874
+ typeof record.message === "string" ? record.message : void 0,
875
+ typeof error.stack === "string" ? error.stack : void 0,
876
+ typeof record.stack === "string" ? record.stack : void 0
877
+ ].filter(Boolean).join("\n");
878
+ return {
879
+ level: "error",
880
+ message: message || JSON.stringify(toSerializableValue(payload)),
881
+ timestamp: Date.now(),
882
+ raw: toSerializableValue(payload)
883
+ };
884
+ }
885
+ function normalizeLogLevel(value) {
886
+ const normalized = String(value ?? "log").toLowerCase();
887
+ if (normalized === "warning") {
888
+ return "warn";
889
+ }
890
+ if (["debug", "log", "info", "warn", "error"].includes(normalized)) {
891
+ return normalized;
892
+ }
893
+ return "log";
894
+ }
895
+ function resolveLogMessage(record, payload) {
896
+ if (typeof record.text === "string" && record.text) {
897
+ return record.text;
898
+ }
899
+ if (typeof record.message === "string" && record.message) {
900
+ return record.message;
901
+ }
902
+ if (Array.isArray(record.args)) {
903
+ return record.args.map(formatLogArgument).join(" ");
904
+ }
905
+ if (typeof payload === "string") {
906
+ return payload;
907
+ }
908
+ return JSON.stringify(toSerializableValue(payload));
909
+ }
910
+ function formatLogArgument(value) {
911
+ const record = toRecord$1(value);
912
+ if (record.value !== void 0) {
913
+ return typeof record.value === "string" ? record.value : JSON.stringify(toSerializableValue(record.value));
914
+ }
915
+ if (typeof record.description === "string") {
916
+ return record.description;
917
+ }
918
+ return typeof value === "string" ? value : JSON.stringify(toSerializableValue(value));
919
+ }
920
+ function toRecord$1(value) {
921
+ return value && typeof value === "object" ? value : {};
922
+ }
923
+ function createUnavailableRuntimeHooks() {
924
+ return {
925
+ async connectMiniProgram() {
926
+ throw new Error("\u672A\u914D\u7F6E DevTools runtime hooks\u3002\u8BF7\u901A\u8FC7 createWeappViteMcpServer({ runtimeHooks }) \u6CE8\u5165 automator \u8FDE\u63A5\u80FD\u529B\u3002");
927
+ }
928
+ };
929
+ }
930
+
931
+ const navigateSchema = {
932
+ ...connectionInputSchema,
933
+ path: z.string().trim().min(1).optional(),
934
+ query: z.record(z.string(), z.string()).optional(),
935
+ transition: z.enum(["navigateTo", "redirectTo", "reLaunch", "switchTab", "navigateBack"]).optional(),
936
+ waitMs: z.number().int().nonnegative().optional()
937
+ };
938
+ const screenshotSchema = {
939
+ ...connectionInputSchema,
940
+ outputPath: z.string().trim().min(1).optional()
941
+ };
942
+ const hostApiSchema = {
943
+ ...connectionInputSchema,
944
+ method: z.string().trim().min(1),
945
+ args: z.array(z.unknown()).optional()
946
+ };
947
+ function registerDevtoolsRuntimeTools(server, manager) {
948
+ server.registerTool("weapp_devtools_connect", {
949
+ title: "Ensure Mini Program Connection",
950
+ 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",
951
+ inputSchema: {
952
+ ...connectionInputSchema,
953
+ reconnect: z.boolean().optional()
954
+ }
955
+ }, async ({ reconnect, ...connection }) => {
956
+ try {
957
+ if (reconnect) {
958
+ await manager.close(connection);
959
+ }
960
+ const result = await manager.withMiniProgram(connection, async (miniProgram) => {
961
+ const page = await miniProgram.currentPage().catch(() => null);
962
+ const systemInfo = await miniProgram.systemInfo().catch(() => null);
963
+ return {
964
+ connected: true,
965
+ projectPath: connection.projectPath,
966
+ resolvedProjectPath: manager.resolveProjectPath(connection.projectPath),
967
+ currentPage: page ? { path: page.path, query: toSerializableValue(page.query) } : null,
968
+ systemInfo: toSerializableValue(systemInfo)
969
+ };
970
+ });
971
+ return toToolResult(result);
972
+ } catch (error) {
973
+ return toToolError(error);
974
+ }
975
+ });
976
+ server.registerTool("weapp_devtools_route", {
977
+ title: "Navigate Mini Program",
978
+ description: "\u5728\u5C0F\u7A0B\u5E8F\u5185\u6267\u884C navigateTo\u3001redirectTo\u3001reLaunch\u3001switchTab \u6216 navigateBack\u3002",
979
+ inputSchema: navigateSchema
980
+ }, async ({ path: pagePath, query, transition = "navigateTo", waitMs, ...connection }) => {
981
+ try {
982
+ const result = await manager.withMiniProgram(connection, async (miniProgram) => {
983
+ if (transition === "navigateBack") {
984
+ const page2 = await miniProgram.navigateBack();
985
+ if (waitMs && page2) {
986
+ await page2.waitFor(waitMs);
987
+ }
988
+ return {
989
+ transition,
990
+ activePage: page2 ? { path: page2.path, query: toSerializableValue(page2.query) } : null
991
+ };
992
+ }
993
+ if (!pagePath) {
994
+ throw new Error("transition \u4E0D\u662F navigateBack \u65F6\u5FC5\u987B\u63D0\u4F9B path\u3002");
995
+ }
996
+ const url = buildUrl(pagePath, query);
997
+ const page = await callRequiredMethod(
998
+ miniProgram,
999
+ transition,
1000
+ url
1001
+ );
1002
+ if (waitMs && page) {
1003
+ await page.waitFor(waitMs);
1004
+ }
1005
+ return {
1006
+ transition,
1007
+ url,
1008
+ activePage: page ? { path: page.path, query: toSerializableValue(page.query) } : null
1009
+ };
1010
+ });
1011
+ return toToolResult(result);
1012
+ } catch (error) {
1013
+ return toToolError(error);
1014
+ }
1015
+ });
1016
+ server.registerTool("weapp_devtools_active_page", {
1017
+ title: "Current Mini Program Page",
1018
+ 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",
1019
+ inputSchema: {
1020
+ ...connectionInputSchema,
1021
+ withData: z.boolean().optional()
1022
+ }
1023
+ }, async ({ withData, ...connection }) => {
1024
+ try {
1025
+ const result = await manager.withPage(connection, async (page) => {
1026
+ const [size, scrollTop, data] = await Promise.all([
1027
+ page.size().catch(() => null),
1028
+ page.scrollTop().catch(() => null),
1029
+ withData ? page.data().catch(() => null) : Promise.resolve(void 0)
1030
+ ]);
1031
+ return compactObject({
1032
+ path: page.path,
1033
+ query: toSerializableValue(page.query),
1034
+ size: toSerializableValue(size),
1035
+ scrollTop: toSerializableValue(scrollTop),
1036
+ data: toSerializableValue(data)
1037
+ });
1038
+ });
1039
+ return toToolResult(result);
1040
+ } catch (error) {
1041
+ return toToolError(error);
1042
+ }
1043
+ });
1044
+ server.registerTool("weapp_devtools_page_stack", {
1045
+ title: "Mini Program Page Stack",
1046
+ description: "\u83B7\u53D6\u5F53\u524D\u5C0F\u7A0B\u5E8F\u9875\u9762\u6808\u3002",
1047
+ inputSchema: connectionInputSchema
1048
+ }, async (connection) => {
1049
+ try {
1050
+ const result = await manager.withMiniProgram(connection, async (miniProgram) => {
1051
+ const stack = await miniProgram.pageStack();
1052
+ return stack.map((page) => ({
1053
+ path: page.path,
1054
+ query: toSerializableValue(page.query)
1055
+ }));
1056
+ });
1057
+ return toToolResult(result);
1058
+ } catch (error) {
1059
+ return toToolError(error);
1060
+ }
1061
+ });
1062
+ server.registerTool("weapp_devtools_capture", {
1063
+ title: "Mini Program Screenshot",
1064
+ description: "\u622A\u53D6\u5F53\u524D\u5C0F\u7A0B\u5E8F\u89C6\u53E3\uFF0C\u8FD4\u56DE base64\uFF0C\u6216\u4FDD\u5B58\u5230 workspaceRoot \u76F8\u5BF9 outputPath\u3002",
1065
+ inputSchema: screenshotSchema
1066
+ }, async ({ outputPath, ...connection }) => {
1067
+ try {
1068
+ const result = await manager.withMiniProgram(connection, async (miniProgram) => {
1069
+ const screenshot = await miniProgram.screenshot();
1070
+ const buffer = typeof screenshot === "string" ? Buffer.from(screenshot, "base64") : Buffer.from(screenshot);
1071
+ if (outputPath) {
1072
+ const resolvedOutputPath = manager.resolveWorkspacePath(outputPath);
1073
+ await fs$1.mkdir(path.dirname(resolvedOutputPath), { recursive: true });
1074
+ await fs$1.writeFile(resolvedOutputPath, buffer);
1075
+ return {
1076
+ path: resolvedOutputPath,
1077
+ bytes: buffer.length
1078
+ };
1079
+ }
1080
+ return {
1081
+ base64: buffer.toString("base64"),
1082
+ bytes: buffer.length
1083
+ };
1084
+ });
1085
+ return toToolResult(result);
1086
+ } catch (error) {
1087
+ return toToolError(error);
1088
+ }
1089
+ });
1090
+ server.registerTool("weapp_devtools_host_api", {
1091
+ title: "Call wx Method",
1092
+ description: "\u8C03\u7528\u5FAE\u4FE1\u5C0F\u7A0B\u5E8F wx API\uFF0C\u4F8B\u5982 wx.pageScrollTo\u3002",
1093
+ inputSchema: hostApiSchema
1094
+ }, async ({ method, args, ...connection }) => {
1095
+ try {
1096
+ const result = await manager.withMiniProgram(connection, async (miniProgram) => {
1097
+ const callArgs = args ?? [];
1098
+ const callResult = await miniProgram.callWxMethod(method, ...callArgs);
1099
+ return {
1100
+ method,
1101
+ args: toSerializableValue(callArgs),
1102
+ result: toSerializableValue(callResult)
1103
+ };
1104
+ });
1105
+ return toToolResult(result);
1106
+ } catch (error) {
1107
+ return toToolError(error);
1108
+ }
1109
+ });
1110
+ server.registerTool("weapp_devtools_console", {
1111
+ title: "Get Mini Program Logs",
1112
+ 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",
1113
+ inputSchema: {
1114
+ clear: z.boolean().optional()
1115
+ }
1116
+ }, async ({ clear }) => {
1117
+ try {
1118
+ const logs = manager.getLogs();
1119
+ if (clear) {
1120
+ manager.clearLogs();
1121
+ }
1122
+ return toToolResult({
1123
+ count: logs.length,
1124
+ logs
1125
+ });
1126
+ } catch (error) {
1127
+ return toToolError(error);
1128
+ }
1129
+ });
1130
+ }
1131
+
1132
+ const elementSelectorSchema = {
1133
+ ...connectionInputSchema,
1134
+ selector: z.string().trim().min(1),
1135
+ innerSelector: z.string().trim().min(1).optional()
1136
+ };
1137
+ function registerRuntimeNodeTools(server, manager) {
1138
+ server.registerTool("weapp_runtime_tap_node", {
1139
+ title: "Tap Element",
1140
+ description: "\u70B9\u51FB\u9875\u9762\u5143\u7D20\uFF0C\u652F\u6301 selector[index=N]\u3001innerSelector \u548C\u70B9\u51FB\u540E\u7B49\u5F85\u3002",
1141
+ inputSchema: {
1142
+ ...elementSelectorSchema,
1143
+ waitMs: z.number().int().nonnegative().optional()
1144
+ }
1145
+ }, async ({ selector, innerSelector, waitMs, ...connection }) => {
1146
+ try {
1147
+ const result = await manager.withPage(connection, async (page) => {
1148
+ const element = await resolveElement(page, selector, innerSelector);
1149
+ await element.tap();
1150
+ if (waitMs) {
1151
+ await page.waitFor(waitMs);
1152
+ }
1153
+ return {
1154
+ selector,
1155
+ innerSelector: innerSelector ?? null,
1156
+ waitedMs: waitMs ?? 0
1157
+ };
1158
+ });
1159
+ return toToolResult(result);
1160
+ } catch (error) {
1161
+ return toToolError(error);
1162
+ }
1163
+ });
1164
+ server.registerTool("weapp_runtime_input_node", {
1165
+ title: "Input Element Text",
1166
+ description: "\u5411 input \u6216 textarea \u5143\u7D20\u8F93\u5165\u6587\u672C\u3002",
1167
+ inputSchema: {
1168
+ ...elementSelectorSchema,
1169
+ value: z.union([z.string(), z.number()])
1170
+ }
1171
+ }, async ({ selector, innerSelector, value, ...connection }) => {
1172
+ try {
1173
+ const result = await manager.withPage(connection, async (page) => {
1174
+ const element = await resolveElement(page, selector, innerSelector);
1175
+ await callRequiredMethod(element, "input", String(value));
1176
+ return {
1177
+ selector,
1178
+ innerSelector: innerSelector ?? null,
1179
+ value: String(value)
1180
+ };
1181
+ });
1182
+ return toToolResult(result);
1183
+ } catch (error) {
1184
+ return toToolError(error);
1185
+ }
1186
+ });
1187
+ server.registerTool("weapp_runtime_invoke_component", {
1188
+ title: "Call Element Method",
1189
+ description: "\u8C03\u7528\u81EA\u5B9A\u4E49\u7EC4\u4EF6\u5143\u7D20\u5B9E\u4F8B\u65B9\u6CD5\u3002",
1190
+ inputSchema: {
1191
+ ...elementSelectorSchema,
1192
+ method: z.string().trim().min(1),
1193
+ args: z.array(z.unknown()).optional()
1194
+ }
1195
+ }, async ({ selector, innerSelector, method, args, ...connection }) => {
1196
+ try {
1197
+ const result = await manager.withPage(connection, async (page) => {
1198
+ const element = await resolveElement(page, selector, innerSelector);
1199
+ const callArgs = args ?? [];
1200
+ return {
1201
+ selector,
1202
+ innerSelector: innerSelector ?? null,
1203
+ method,
1204
+ args: toSerializableValue(callArgs),
1205
+ result: toSerializableValue(await callRequiredMethod(element, method, ...callArgs))
1206
+ };
1207
+ });
1208
+ return toToolResult(result);
1209
+ } catch (error) {
1210
+ return toToolError(error);
1211
+ }
1212
+ });
1213
+ server.registerTool("weapp_runtime_component_state", {
1214
+ title: "Get Element Data",
1215
+ description: "\u8BFB\u53D6\u81EA\u5B9A\u4E49\u7EC4\u4EF6\u5143\u7D20 data\uFF0C\u53EF\u901A\u8FC7 path \u8BFB\u53D6\u5D4C\u5957\u5B57\u6BB5\u3002",
1216
+ inputSchema: {
1217
+ ...elementSelectorSchema,
1218
+ path: z.string().trim().min(1).optional()
1219
+ }
1220
+ }, async ({ selector, innerSelector, path, ...connection }) => {
1221
+ try {
1222
+ const result = await manager.withPage(connection, async (page) => {
1223
+ const element = await resolveElement(page, selector, innerSelector);
1224
+ return {
1225
+ selector,
1226
+ innerSelector: innerSelector ?? null,
1227
+ path: path ?? null,
1228
+ data: toSerializableValue(await callRequiredMethod(element, "data", path))
1229
+ };
1230
+ });
1231
+ return toToolResult(result);
1232
+ } catch (error) {
1233
+ return toToolError(error);
1234
+ }
1235
+ });
1236
+ server.registerTool("weapp_runtime_update_component_state", {
1237
+ title: "Set Element Data",
1238
+ description: "\u8C03\u7528\u81EA\u5B9A\u4E49\u7EC4\u4EF6\u5143\u7D20 setData \u66F4\u65B0 data\u3002",
1239
+ inputSchema: {
1240
+ ...elementSelectorSchema,
1241
+ data: z.record(z.string(), z.unknown())
1242
+ }
1243
+ }, async ({ selector, innerSelector, data, ...connection }) => {
1244
+ try {
1245
+ const result = await manager.withPage(connection, async (page) => {
1246
+ const element = await resolveElement(page, selector, innerSelector);
1247
+ await callRequiredMethod(element, "setData", data);
1248
+ return {
1249
+ selector,
1250
+ innerSelector: innerSelector ?? null,
1251
+ keys: Object.keys(data)
1252
+ };
1253
+ });
1254
+ return toToolResult(result);
1255
+ } catch (error) {
1256
+ return toToolError(error);
1257
+ }
1258
+ });
1259
+ server.registerTool("weapp_runtime_find_child", {
1260
+ title: "Get Inner Element",
1261
+ description: "\u5728\u5143\u7D20\u8303\u56F4\u5185\u67E5\u8BE2\u5355\u4E2A\u5185\u90E8\u5143\u7D20\u3002",
1262
+ inputSchema: {
1263
+ ...elementSelectorSchema,
1264
+ targetSelector: z.string().trim().min(1),
1265
+ withWxml: z.boolean().optional()
1266
+ }
1267
+ }, async ({ selector, innerSelector, targetSelector, withWxml, ...connection }) => {
1268
+ try {
1269
+ const result = await manager.withPage(connection, async (page) => {
1270
+ const parent = await resolveElement(page, selector, innerSelector);
1271
+ const element = await callRequiredMethod(parent, "$", targetSelector);
1272
+ if (!element) {
1273
+ throw new Error(`\u5728\u5143\u7D20 "${selector}" \u5185\u672A\u627E\u5230\u5143\u7D20: ${targetSelector}`);
1274
+ }
1275
+ return {
1276
+ selector,
1277
+ innerSelector: innerSelector ?? null,
1278
+ targetSelector,
1279
+ ...await summarizeElement(element, withWxml)
1280
+ };
1281
+ });
1282
+ return toToolResult(result);
1283
+ } catch (error) {
1284
+ return toToolError(error);
1285
+ }
1286
+ });
1287
+ server.registerTool("weapp_runtime_find_children", {
1288
+ title: "Get Inner Elements",
1289
+ description: "\u5728\u5143\u7D20\u8303\u56F4\u5185\u67E5\u8BE2\u5185\u90E8\u5143\u7D20\u6570\u7EC4\u3002",
1290
+ inputSchema: {
1291
+ ...elementSelectorSchema,
1292
+ targetSelector: z.string().trim().min(1),
1293
+ withWxml: z.boolean().optional()
1294
+ }
1295
+ }, async ({ selector, innerSelector, targetSelector, withWxml, ...connection }) => {
1296
+ try {
1297
+ const result = await manager.withPage(connection, async (page) => {
1298
+ const parent = await resolveElement(page, selector, innerSelector);
1299
+ const elements = await callRequiredMethod(parent, "$$", targetSelector);
1300
+ if (!Array.isArray(elements)) {
1301
+ throw new TypeError(`\u5728\u5143\u7D20 "${selector}" \u5185\u67E5\u8BE2 "${targetSelector}" \u5931\u8D25\u3002`);
1302
+ }
1303
+ return {
1304
+ selector,
1305
+ innerSelector: innerSelector ?? null,
1306
+ targetSelector,
1307
+ count: elements.length,
1308
+ elements: await Promise.all(elements.map(async (element, index) => ({
1309
+ index,
1310
+ ...await summarizeElement(element, withWxml)
1311
+ })))
1312
+ };
1313
+ });
1314
+ return toToolResult(result);
1315
+ } catch (error) {
1316
+ return toToolError(error);
1317
+ }
1318
+ });
1319
+ server.registerTool("weapp_runtime_node_markup", {
1320
+ title: "Get Element WXML",
1321
+ description: "\u8BFB\u53D6\u5143\u7D20 inner WXML \u6216 outer WXML\u3002",
1322
+ inputSchema: {
1323
+ ...elementSelectorSchema,
1324
+ outer: z.boolean().optional()
1325
+ }
1326
+ }, async ({ selector, innerSelector, outer, ...connection }) => {
1327
+ try {
1328
+ const result = await manager.withPage(connection, async (page) => {
1329
+ const element = await resolveElement(page, selector, innerSelector);
1330
+ const method = outer ? "outerWxml" : "wxml";
1331
+ return {
1332
+ selector,
1333
+ innerSelector: innerSelector ?? null,
1334
+ type: method,
1335
+ wxml: toSerializableValue(await callRequiredMethod(element, method))
1336
+ };
1337
+ });
1338
+ return toToolResult(result);
1339
+ } catch (error) {
1340
+ return toToolError(error);
1341
+ }
1342
+ });
1343
+ server.registerTool("weapp_runtime_node_styles", {
1344
+ title: "Get Element Styles",
1345
+ description: "\u8BFB\u53D6\u5143\u7D20\u6837\u5F0F\u503C\u3002",
1346
+ inputSchema: {
1347
+ ...elementSelectorSchema,
1348
+ names: z.array(z.string().trim().min(1)).min(1)
1349
+ }
1350
+ }, async ({ selector, innerSelector, names, ...connection }) => {
1351
+ try {
1352
+ const result = await manager.withPage(connection, async (page) => {
1353
+ const element = await resolveElement(page, selector, innerSelector);
1354
+ return {
1355
+ selector,
1356
+ innerSelector: innerSelector ?? null,
1357
+ styles: Object.fromEntries(await Promise.all(names.map(async (name) => [
1358
+ name,
1359
+ toSerializableValue(await callMaybe(element, "style", name))
1360
+ ])))
1361
+ };
1362
+ });
1363
+ return toToolResult(result);
1364
+ } catch (error) {
1365
+ return toToolError(error);
1366
+ }
1367
+ });
1368
+ server.registerTool("weapp_runtime_node_attrs", {
1369
+ title: "Get Element Attributes",
1370
+ description: "\u8BFB\u53D6\u5143\u7D20 attribute \u503C\u3002",
1371
+ inputSchema: {
1372
+ ...elementSelectorSchema,
1373
+ names: z.array(z.string().trim().min(1)).min(1)
1374
+ }
1375
+ }, async ({ selector, innerSelector, names, ...connection }) => {
1376
+ try {
1377
+ const result = await manager.withPage(connection, async (page) => {
1378
+ const element = await resolveElement(page, selector, innerSelector);
1379
+ return {
1380
+ selector,
1381
+ innerSelector: innerSelector ?? null,
1382
+ attributes: Object.fromEntries(await Promise.all(names.map(async (name) => [
1383
+ name,
1384
+ toSerializableValue(await callMaybe(element, "attribute", name))
1385
+ ])))
1386
+ };
1387
+ });
1388
+ return toToolResult(result);
1389
+ } catch (error) {
1390
+ return toToolError(error);
1391
+ }
1392
+ });
1393
+ server.registerTool("weapp_runtime_scroll_node", {
1394
+ title: "Scroll Element",
1395
+ description: "\u6EDA\u52A8 scroll-view \u5143\u7D20\u5230\u6307\u5B9A\u4F4D\u7F6E\u3002",
1396
+ inputSchema: {
1397
+ ...elementSelectorSchema,
1398
+ x: z.number(),
1399
+ y: z.number()
1400
+ }
1401
+ }, async ({ selector, innerSelector, x, y, ...connection }) => {
1402
+ try {
1403
+ const result = await manager.withPage(connection, async (page) => {
1404
+ const element = await resolveElement(page, selector, innerSelector);
1405
+ await callRequiredMethod(element, "scrollTo", x, y);
1406
+ return {
1407
+ selector,
1408
+ innerSelector: innerSelector ?? null,
1409
+ x,
1410
+ y
1411
+ };
1412
+ });
1413
+ return toToolResult(result);
1414
+ } catch (error) {
1415
+ return toToolError(error);
1416
+ }
1417
+ });
1418
+ server.registerTool("weapp_runtime_measure_node", {
1419
+ title: "Get Element Bounding Rect",
1420
+ description: "\u8BFB\u53D6\u5143\u7D20\u89C6\u53E3\u77E9\u5F62\uFF1B\u4F18\u5148\u4F7F\u7528\u5143\u7D20 offset/size\uFF0C\u7F3A\u5931\u65F6\u8FD4\u56DE\u53EF\u7528\u5B57\u6BB5\u3002",
1421
+ inputSchema: elementSelectorSchema
1422
+ }, async ({ selector, innerSelector, ...connection }) => {
1423
+ try {
1424
+ const result = await manager.withPage(connection, async (page) => {
1425
+ const element = await resolveElement(page, selector, innerSelector);
1426
+ const offset = toRecord(await callMaybe(element, "offset"));
1427
+ const size = toRecord(await callMaybe(element, "size"));
1428
+ const left = toNumber(offset.left);
1429
+ const top = toNumber(offset.top);
1430
+ const width = toNumber(size.width ?? offset.width);
1431
+ const height = toNumber(size.height ?? offset.height);
1432
+ return {
1433
+ selector,
1434
+ innerSelector: innerSelector ?? null,
1435
+ boundingClientRect: compactObject({
1436
+ left,
1437
+ top,
1438
+ width,
1439
+ height,
1440
+ right: left !== void 0 && width !== void 0 ? left + width : void 0,
1441
+ bottom: top !== void 0 && height !== void 0 ? top + height : void 0,
1442
+ rawOffset: toSerializableValue(offset),
1443
+ rawSize: toSerializableValue(size),
1444
+ tagName: readProperty(element, "tagName")
1445
+ })
1446
+ };
1447
+ });
1448
+ return toToolResult(result);
1449
+ } catch (error) {
1450
+ return toToolError(error);
1451
+ }
1452
+ });
1453
+ }
1454
+ function toRecord(value) {
1455
+ return value && typeof value === "object" ? value : {};
1456
+ }
1457
+ function toNumber(value) {
1458
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1459
+ }
1460
+
1461
+ const selectorSchema = {
1462
+ ...connectionInputSchema,
1463
+ selector: z.string().trim().min(1),
1464
+ innerSelector: z.string().trim().min(1).optional(),
1465
+ withWxml: z.boolean().optional()
1466
+ };
1467
+ function registerRuntimePageTools(server, manager) {
1468
+ server.registerTool("weapp_runtime_find_node", {
1469
+ title: "Get Page Element",
1470
+ description: "\u901A\u8FC7\u9009\u62E9\u5668\u83B7\u53D6\u5F53\u524D\u9875\u9762\u5143\u7D20\u6458\u8981\uFF0C\u652F\u6301 selector[index=N]\u3001innerSelector \u4E0E withWxml\u3002",
1471
+ inputSchema: selectorSchema
1472
+ }, async ({ selector, innerSelector, withWxml, ...connection }) => {
1473
+ try {
1474
+ const result = await manager.withPage(connection, async (page) => {
1475
+ const element = await resolveElement(page, selector, innerSelector);
1476
+ return {
1477
+ selector,
1478
+ innerSelector: innerSelector ?? null,
1479
+ ...await summarizeElement(element, withWxml)
1480
+ };
1481
+ });
1482
+ return toToolResult(result);
1483
+ } catch (error) {
1484
+ return toToolError(error);
1485
+ }
1486
+ });
1487
+ server.registerTool("weapp_runtime_find_nodes", {
1488
+ title: "Get Page Elements",
1489
+ 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",
1490
+ inputSchema: {
1491
+ ...connectionInputSchema,
1492
+ selector: z.string().trim().min(1),
1493
+ withWxml: z.boolean().optional()
1494
+ }
1495
+ }, async ({ selector, withWxml, ...connection }) => {
1496
+ try {
1497
+ const result = await manager.withPage(connection, async (page) => {
1498
+ const elements = await queryElements(page, selector);
1499
+ return {
1500
+ selector,
1501
+ count: elements.length,
1502
+ elements: await Promise.all(elements.map(async (element, index) => ({
1503
+ index,
1504
+ ...await summarizeElement(element, withWxml)
1505
+ })))
1506
+ };
1507
+ });
1508
+ return toToolResult(result);
1509
+ } catch (error) {
1510
+ return toToolError(error);
1511
+ }
1512
+ });
1513
+ server.registerTool("weapp_runtime_wait_node", {
1514
+ title: "Wait Page Element",
1515
+ description: "\u8F6E\u8BE2\u7B49\u5F85\u5143\u7D20\u51FA\u73B0\uFF0C\u652F\u6301 selector[index=N]\u3002",
1516
+ inputSchema: {
1517
+ ...connectionInputSchema,
1518
+ selector: z.string().trim().min(1),
1519
+ timeoutMs: z.number().int().positive().optional(),
1520
+ intervalMs: z.number().int().positive().optional()
1521
+ }
1522
+ }, async ({ selector, timeoutMs = 5e3, intervalMs = 200, ...connection }) => {
1523
+ try {
1524
+ const result = await manager.withPage(connection, async (page) => {
1525
+ const startedAt = Date.now();
1526
+ while (Date.now() - startedAt <= timeoutMs) {
1527
+ const elements = await queryElements(page, selector).catch(() => []);
1528
+ if (elements.length > 0) {
1529
+ return {
1530
+ selector,
1531
+ found: true,
1532
+ count: elements.length,
1533
+ waitMs: Date.now() - startedAt
1534
+ };
1535
+ }
1536
+ await page.waitFor(intervalMs);
1537
+ }
1538
+ throw new Error(`\u7B49\u5F85\u5143\u7D20 "${selector}" \u8D85\u65F6 (${timeoutMs}ms)\u3002`);
1539
+ });
1540
+ return toToolResult(result);
1541
+ } catch (error) {
1542
+ return toToolError(error);
1543
+ }
1544
+ });
1545
+ server.registerTool("weapp_runtime_wait", {
1546
+ title: "Wait Page Timeout",
1547
+ description: "\u5728\u5F53\u524D\u9875\u9762\u7B49\u5F85\u6307\u5B9A\u6BEB\u79D2\u6570\u3002",
1548
+ inputSchema: {
1549
+ ...connectionInputSchema,
1550
+ milliseconds: z.number().int().nonnegative()
1551
+ }
1552
+ }, async ({ milliseconds, ...connection }) => {
1553
+ try {
1554
+ const result = await manager.withPage(connection, async (page) => {
1555
+ await page.waitFor(milliseconds);
1556
+ return { waitedMs: milliseconds };
1557
+ });
1558
+ return toToolResult(result);
1559
+ } catch (error) {
1560
+ return toToolError(error);
1561
+ }
1562
+ });
1563
+ server.registerTool("weapp_runtime_page_state", {
1564
+ title: "Get Page Data",
1565
+ description: "\u8BFB\u53D6\u5F53\u524D\u9875\u9762 data\uFF0C\u53EF\u901A\u8FC7 path \u8BFB\u53D6\u5D4C\u5957\u5B57\u6BB5\u3002",
1566
+ inputSchema: {
1567
+ ...connectionInputSchema,
1568
+ path: z.string().trim().min(1).optional()
1569
+ }
1570
+ }, async ({ path, ...connection }) => {
1571
+ try {
1572
+ const result = await manager.withPage(connection, async (page) => ({
1573
+ path: path ?? null,
1574
+ data: toSerializableValue(await page.data(path))
1575
+ }));
1576
+ return toToolResult(result);
1577
+ } catch (error) {
1578
+ return toToolError(error);
1579
+ }
1580
+ });
1581
+ server.registerTool("weapp_runtime_update_page_state", {
1582
+ title: "Set Page Data",
1583
+ description: "\u8C03\u7528\u5F53\u524D\u9875\u9762 setData \u66F4\u65B0 data\u3002",
1584
+ inputSchema: {
1585
+ ...connectionInputSchema,
1586
+ data: z.record(z.string(), z.unknown()),
1587
+ verify: z.boolean().optional()
1588
+ }
1589
+ }, async ({ data, verify, ...connection }) => {
1590
+ try {
1591
+ const result = await manager.withPage(connection, async (page) => {
1592
+ await callRequiredMethod(page, "setData", data);
1593
+ return {
1594
+ keys: Object.keys(data),
1595
+ data: verify ? toSerializableValue(await page.data()) : void 0
1596
+ };
1597
+ });
1598
+ return toToolResult(result);
1599
+ } catch (error) {
1600
+ return toToolError(error);
1601
+ }
1602
+ });
1603
+ server.registerTool("weapp_runtime_invoke_page", {
1604
+ title: "Call Page Method",
1605
+ description: "\u8C03\u7528\u5F53\u524D\u9875\u9762\u5B9E\u4F8B\u65B9\u6CD5\u3002",
1606
+ inputSchema: {
1607
+ ...connectionInputSchema,
1608
+ method: z.string().trim().min(1),
1609
+ args: z.array(z.unknown()).optional()
1610
+ }
1611
+ }, async ({ method, args, ...connection }) => {
1612
+ try {
1613
+ const result = await manager.withPage(connection, async (page) => {
1614
+ const callArgs = args ?? [];
1615
+ return {
1616
+ method,
1617
+ args: toSerializableValue(callArgs),
1618
+ result: toSerializableValue(await callRequiredMethod(page, method, ...callArgs))
1619
+ };
1620
+ });
1621
+ return toToolResult(result);
1622
+ } catch (error) {
1623
+ return toToolError(error);
1624
+ }
1625
+ });
1626
+ }
1627
+
1628
+ function registerRuntimeTools(server, options) {
1629
+ const manager = options.manager ?? new RuntimeSessionManager(options.workspaceRoot, options.runtimeHooks);
1630
+ registerDevtoolsRuntimeTools(server, manager);
1631
+ registerRuntimePageTools(server, manager);
1632
+ registerRuntimeNodeTools(server, manager);
1633
+ return manager;
1634
+ }
1635
+
1636
+ async function loadPackageSummary(workspaceRoot, id) {
1637
+ return resolveExposedPackage(workspaceRoot, id);
1638
+ }
1639
+ async function loadExposedCatalog(workspaceRoot) {
1640
+ return resolveExposedPackages(workspaceRoot);
1641
+ }
1642
+
1643
+ function registerServerTools(server, options) {
1644
+ const { workspaceRoot, packageIds, packageIdSchema } = options;
441
1645
  server.registerTool("workspace_catalog", {
442
1646
  title: "Workspace Catalog",
443
1647
  description: "\u8BFB\u53D6 weapp-vite / wevu \u76F8\u5173\u5305\u76EE\u5F55\u4E0E\u811A\u672C\u80FD\u529B\u6E05\u5355"
444
1648
  }, async () => {
445
1649
  try {
446
- const catalog2 = await loadExposedCatalog(workspaceRoot);
1650
+ const catalog = await loadExposedCatalog(workspaceRoot);
447
1651
  return toToolResult({
448
1652
  workspaceRoot,
449
- packages: catalog2
1653
+ packages: catalog
450
1654
  });
451
1655
  } catch (error) {
452
1656
  return toToolError(error);
@@ -695,142 +1899,366 @@ async function createWeappViteMcpServer(options) {
695
1899
  return toToolError(error);
696
1900
  }
697
1901
  });
698
- server.registerPrompt("plan-weapp-vite-change", {
699
- title: "Plan weapp-vite Change",
700
- description: "\u6839\u636E\u53D8\u66F4\u76EE\u6807\u751F\u6210 weapp-vite / wevu \u4FEE\u6539\u8BA1\u5212\u63D0\u793A\u8BCD",
701
- argsSchema: {
702
- objective: z.string().min(1),
703
- focusPackage: packageIdSchema.optional()
704
- }
705
- }, async ({ objective, focusPackage }) => {
706
- const targets = focusPackage ? [focusPackage] : packageIds;
707
- return {
708
- messages: [
709
- {
710
- role: "user",
711
- content: {
712
- type: "text",
713
- text: [
714
- "\u4F60\u662F weapp-vite monorepo \u7EF4\u62A4\u8005\uFF0C\u8BF7\u7ED9\u51FA\u53EF\u6267\u884C\u7684\u6539\u9020\u8BA1\u5212\u3002",
715
- `\u76EE\u6807\uFF1A${objective}`,
716
- `\u805A\u7126\u5305\uFF1A${targets.join(", ")}`,
717
- "\u8BF7\u5305\u542B\uFF1A\u5F71\u54CD\u9762\u3001\u98CE\u9669\u70B9\u3001\u6D4B\u8BD5\u7B56\u7565\u3001\u56DE\u6EDA\u7B56\u7565\u3002"
718
- ].join("\n")
719
- }
720
- }
721
- ]
722
- };
1902
+ }
1903
+
1904
+ const packageIds = Object.keys(EXPOSED_PACKAGES);
1905
+ const packageIdSchema = z.enum(packageIds);
1906
+ async function createWeappViteMcpServer(options) {
1907
+ const workspaceRoot = resolveWorkspaceRoot(options?.workspaceRoot);
1908
+ const server = new McpServer({
1909
+ name: MCP_SERVER_NAME,
1910
+ version: MCP_SERVER_VERSION
723
1911
  });
724
- server.registerPrompt("debug-wevu-runtime", {
725
- title: "Debug wevu Runtime",
726
- description: "\u7528\u4E8E\u5B9A\u4F4D wevu runtime \u751F\u547D\u5468\u671F/\u54CD\u5E94\u5F0F\u95EE\u9898\u7684\u6807\u51C6\u63D0\u793A\u8BCD",
727
- argsSchema: {
728
- symptom: z.string().min(1)
1912
+ const runtimeManager = new RuntimeSessionManager(workspaceRoot, options?.runtimeHooks);
1913
+ registerServerTools(server, {
1914
+ workspaceRoot,
1915
+ packageIds,
1916
+ packageIdSchema
1917
+ });
1918
+ registerRuntimeTools(server, {
1919
+ manager: runtimeManager,
1920
+ runtimeHooks: options?.runtimeHooks,
1921
+ workspaceRoot
1922
+ });
1923
+ registerServerPrompts(server, {
1924
+ packageIds,
1925
+ packageIdSchema
1926
+ });
1927
+ await registerServerResources(server, {
1928
+ workspaceRoot,
1929
+ packageIds
1930
+ });
1931
+ return {
1932
+ runtimeManager,
1933
+ server,
1934
+ workspaceRoot
1935
+ };
1936
+ }
1937
+
1938
+ const DEFAULT_RUNTIME_REST_ENDPOINT = "/api/weapp/devtools";
1939
+ const connectionSchema = z.object({
1940
+ projectPath: z.string().trim().min(1),
1941
+ timeout: z.number().int().positive().optional(),
1942
+ preferOpenedSession: z.boolean().optional()
1943
+ });
1944
+ const routeBodySchema = connectionSchema.extend({
1945
+ path: z.string().trim().min(1).optional(),
1946
+ query: z.record(z.string(), z.string()).optional(),
1947
+ transition: z.enum(["navigateTo", "redirectTo", "reLaunch", "switchTab", "navigateBack"]).optional(),
1948
+ waitMs: z.number().int().nonnegative().optional()
1949
+ });
1950
+ const activePageBodySchema = connectionSchema.extend({
1951
+ withData: z.boolean().optional()
1952
+ });
1953
+ const captureBodySchema = connectionSchema.extend({
1954
+ outputPath: z.string().trim().min(1).optional()
1955
+ });
1956
+ const hostApiBodySchema = connectionSchema.extend({
1957
+ method: z.string().trim().min(1),
1958
+ args: z.array(z.unknown()).optional()
1959
+ });
1960
+ function normalizeRuntimeRestEndpoint(input) {
1961
+ if (input === false) {
1962
+ return false;
1963
+ }
1964
+ const value = typeof input === "string" ? input.trim() : "";
1965
+ const endpoint = value || DEFAULT_RUNTIME_REST_ENDPOINT;
1966
+ return endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
1967
+ }
1968
+ function writeJson$1(res, statusCode, payload) {
1969
+ if (res.headersSent) {
1970
+ return;
1971
+ }
1972
+ res.statusCode = statusCode;
1973
+ res.setHeader("content-type", "application/json");
1974
+ res.end(JSON.stringify(payload));
1975
+ }
1976
+ function writeOk(res, result) {
1977
+ writeJson$1(res, 200, {
1978
+ ok: true,
1979
+ result
1980
+ });
1981
+ }
1982
+ function writeError(res, statusCode, error) {
1983
+ writeJson$1(res, statusCode, {
1984
+ ok: false,
1985
+ error: {
1986
+ message: error instanceof Error ? error.message : String(error)
729
1987
  }
730
- }, async ({ symptom }) => {
731
- return {
732
- messages: [
733
- {
734
- role: "user",
735
- content: {
736
- type: "text",
737
- text: [
738
- "\u8BF7\u57FA\u4E8E wevu runtime \u4EE3\u7801\u8DEF\u5F84\u8FDB\u884C\u5206\u5C42\u6392\u67E5\uFF1A",
739
- "1. \u590D\u73B0\u573A\u666F\u4E0E\u6700\u5C0F\u6837\u4F8B",
740
- "2. \u751F\u547D\u5468\u671F\u94A9\u5B50\u89E6\u53D1\u94FE",
741
- "3. \u54CD\u5E94\u5F0F\u4E0E setData \u5DEE\u91CF\u540C\u6B65\u94FE",
742
- "4. \u5355\u6D4B\u4E0E e2e \u56DE\u5F52\u8865\u4E01",
743
- `\u73B0\u8C61\uFF1A${symptom}`
744
- ].join("\n")
745
- }
746
- }
747
- ]
748
- };
749
1988
  });
750
- server.registerResource("workspace-catalog", "weapp-vite://workspace/catalog", {
751
- title: "Workspace Catalog",
752
- description: "weapp-vite / wevu \u5305\u76EE\u5F55\u3001\u7248\u672C\u548C\u811A\u672C\u5217\u8868",
753
- mimeType: "application/json"
754
- }, async () => {
755
- const catalog2 = await loadExposedCatalog(workspaceRoot);
756
- const text = JSON.stringify({ workspaceRoot, packages: catalog2 }, null, 2);
1989
+ }
1990
+ async function readJsonBody(req) {
1991
+ const chunks = [];
1992
+ for await (const chunk of req) {
1993
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
1994
+ }
1995
+ if (chunks.length === 0) {
1996
+ return {};
1997
+ }
1998
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
1999
+ if (!raw) {
2000
+ return {};
2001
+ }
2002
+ return JSON.parse(raw);
2003
+ }
2004
+ function readBooleanParam(value) {
2005
+ if (value == null) {
2006
+ return void 0;
2007
+ }
2008
+ return ["1", "true", "yes"].includes(value.toLowerCase());
2009
+ }
2010
+ function readNumberParam(value) {
2011
+ if (value == null || value.trim() === "") {
2012
+ return void 0;
2013
+ }
2014
+ const parsed = Number.parseInt(value, 10);
2015
+ return Number.isFinite(parsed) ? parsed : void 0;
2016
+ }
2017
+ function readConnectionFromQuery(url) {
2018
+ return connectionSchema.parse(compactObject({
2019
+ projectPath: url.searchParams.get("projectPath") ?? void 0,
2020
+ timeout: readNumberParam(url.searchParams.get("timeout")),
2021
+ preferOpenedSession: readBooleanParam(url.searchParams.get("preferOpenedSession"))
2022
+ }));
2023
+ }
2024
+ function getRouteContext(req, endpoint) {
2025
+ if (endpoint === false) {
2026
+ return void 0;
2027
+ }
2028
+ const hostHeader = req.headers.host ?? "127.0.0.1";
2029
+ const url = new URL(req.url ?? "/", `http://${hostHeader}`);
2030
+ const pathname = url.pathname.replace(/\/+$/, "") || "/";
2031
+ const normalizedEndpoint = endpoint.replace(/\/+$/, "") || "/";
2032
+ if (pathname !== normalizedEndpoint && !pathname.startsWith(`${normalizedEndpoint}/`)) {
2033
+ return void 0;
2034
+ }
2035
+ const requestPath = pathname === normalizedEndpoint ? "/" : pathname.slice(normalizedEndpoint.length);
2036
+ return {
2037
+ pathname,
2038
+ requestPath,
2039
+ url
2040
+ };
2041
+ }
2042
+ function getAllowedMethods(pathname) {
2043
+ if (pathname === "/") {
2044
+ return ["GET"];
2045
+ }
2046
+ if (pathname === "/connect" || pathname === "/route" || pathname === "/capture" || pathname === "/host-api") {
2047
+ return ["POST"];
2048
+ }
2049
+ if (pathname === "/active-page" || pathname === "/page-stack") {
2050
+ return ["GET", "POST"];
2051
+ }
2052
+ if (pathname === "/console") {
2053
+ return ["GET", "DELETE"];
2054
+ }
2055
+ if (pathname === "/session") {
2056
+ return ["DELETE"];
2057
+ }
2058
+ return [];
2059
+ }
2060
+ function assertMethod(res, method, requestPath) {
2061
+ const allowedMethods = getAllowedMethods(requestPath);
2062
+ if (allowedMethods.length === 0) {
2063
+ writeError(res, 404, `Not Found: ${requestPath}`);
2064
+ return false;
2065
+ }
2066
+ if (!allowedMethods.includes(method)) {
2067
+ res.setHeader("allow", allowedMethods.join(", "));
2068
+ writeError(res, 405, `Method Not Allowed: ${method}`);
2069
+ return false;
2070
+ }
2071
+ return true;
2072
+ }
2073
+ async function readConnectionBody(req) {
2074
+ return connectionSchema.parse(await readJsonBody(req));
2075
+ }
2076
+ async function getActivePage(manager, input) {
2077
+ return await manager.withPage(input, async (page) => {
2078
+ const [size, scrollTop, data] = await Promise.all([
2079
+ page.size().catch(() => null),
2080
+ page.scrollTop().catch(() => null),
2081
+ input.withData ? page.data().catch(() => null) : Promise.resolve(void 0)
2082
+ ]);
2083
+ return compactObject({
2084
+ path: page.path,
2085
+ query: toSerializableValue(page.query),
2086
+ size: toSerializableValue(size),
2087
+ scrollTop: toSerializableValue(scrollTop),
2088
+ data: toSerializableValue(data)
2089
+ });
2090
+ });
2091
+ }
2092
+ async function handleRoot(res, endpoint) {
2093
+ writeOk(res, {
2094
+ endpoint,
2095
+ routes: [
2096
+ "POST /connect",
2097
+ "POST /route",
2098
+ "GET|POST /active-page",
2099
+ "GET|POST /page-stack",
2100
+ "POST /capture",
2101
+ "POST /host-api",
2102
+ "GET|DELETE /console",
2103
+ "DELETE /session"
2104
+ ]
2105
+ });
2106
+ }
2107
+ async function handleConnect(req, res, manager) {
2108
+ const connection = await readConnectionBody(req);
2109
+ const result = await manager.withMiniProgram(connection, async (miniProgram) => {
2110
+ const page = await miniProgram.currentPage().catch(() => null);
2111
+ const systemInfo = await miniProgram.systemInfo().catch(() => null);
757
2112
  return {
758
- contents: [{
759
- uri: "weapp-vite://workspace/catalog",
760
- mimeType: "application/json",
761
- text
762
- }]
2113
+ connected: true,
2114
+ projectPath: connection.projectPath,
2115
+ resolvedProjectPath: manager.resolveProjectPath(connection.projectPath),
2116
+ currentPage: page ? { path: page.path, query: toSerializableValue(page.query) } : null,
2117
+ systemInfo: toSerializableValue(systemInfo)
763
2118
  };
764
2119
  });
765
- const catalog = await resolveExposedPackages(workspaceRoot);
766
- for (const summary of catalog) {
767
- if (summary.docs.readme) {
768
- const uri = toDocsUri(summary.id, "README.md");
769
- server.registerResource(`docs-${summary.id}-readme`, uri, {
770
- title: `${summary.id} README`,
771
- mimeType: "text/markdown"
772
- }, async () => {
773
- const text = await readTextFile(summary.docs.readme);
774
- return {
775
- contents: [{ uri, mimeType: "text/markdown", text }]
776
- };
777
- });
2120
+ writeOk(res, result);
2121
+ }
2122
+ async function handleRoute(req, res, manager) {
2123
+ const input = routeBodySchema.parse(await readJsonBody(req));
2124
+ const result = await manager.withMiniProgram(input, async (miniProgram) => {
2125
+ const transition = input.transition ?? "navigateTo";
2126
+ if (transition === "navigateBack") {
2127
+ const page2 = await miniProgram.navigateBack();
2128
+ if (input.waitMs && page2) {
2129
+ await page2.waitFor(input.waitMs);
2130
+ }
2131
+ return {
2132
+ transition,
2133
+ activePage: page2 ? { path: page2.path, query: toSerializableValue(page2.query) } : null
2134
+ };
778
2135
  }
779
- if (summary.docs.changelog) {
780
- const uri = toDocsUri(summary.id, "CHANGELOG.md");
781
- server.registerResource(`docs-${summary.id}-changelog`, uri, {
782
- title: `${summary.id} CHANGELOG`,
783
- mimeType: "text/markdown"
784
- }, async () => {
785
- const text = await readTextFile(summary.docs.changelog);
786
- return {
787
- contents: [{ uri, mimeType: "text/markdown", text }]
788
- };
789
- });
2136
+ if (!input.path) {
2137
+ throw new Error("transition \u4E0D\u662F navigateBack \u65F6\u5FC5\u987B\u63D0\u4F9B path\u3002");
790
2138
  }
791
- }
792
- const sourceTemplate = new ResourceTemplate("weapp-vite://source/{package}?path={path}", {
793
- list: void 0,
794
- complete: {
795
- package: () => packageIds
2139
+ const url = buildUrl(input.path, input.query);
2140
+ const page = await callRequiredMethod(
2141
+ miniProgram,
2142
+ transition,
2143
+ url
2144
+ );
2145
+ if (input.waitMs && page) {
2146
+ await page.waitFor(input.waitMs);
796
2147
  }
2148
+ return {
2149
+ transition,
2150
+ url,
2151
+ activePage: page ? { path: page.path, query: toSerializableValue(page.query) } : null
2152
+ };
797
2153
  });
798
- server.registerResource("source-template", sourceTemplate, {
799
- title: "Source Template",
800
- description: "\u8BFB\u53D6 weapp-vite / wevu \u4EFB\u610F\u6E90\u7801\u6587\u4EF6\uFF08\u901A\u8FC7 package + path \u53C2\u6570\uFF09",
801
- mimeType: "text/plain"
802
- }, async (uri, variables) => {
803
- try {
804
- const packageId = String(variables.package ?? "");
805
- if (!packageIds.includes(packageId)) {
806
- throw new Error(`\u672A\u77E5 package\uFF1A${packageId}`);
807
- }
808
- const relativePath = decodeURIComponent(String(variables.path ?? ""));
809
- const packageRoot = await resolvePackageRoot(workspaceRoot, packageId);
810
- const { content } = await readFileContent(packageRoot, relativePath, {
811
- maxChars: DEFAULT_MAX_FILE_CHARS
812
- });
813
- return {
814
- contents: [{
815
- uri: uri.toString(),
816
- mimeType: "text/plain",
817
- text: content
818
- }]
819
- };
820
- } catch (error) {
2154
+ writeOk(res, result);
2155
+ }
2156
+ async function handleActivePage(req, res, context, manager) {
2157
+ const input = req.method === "GET" ? activePageBodySchema.parse({
2158
+ ...readConnectionFromQuery(context.url),
2159
+ withData: readBooleanParam(context.url.searchParams.get("withData"))
2160
+ }) : activePageBodySchema.parse(await readJsonBody(req));
2161
+ const result = await getActivePage(manager, input);
2162
+ writeOk(res, result);
2163
+ }
2164
+ async function handlePageStack(req, res, context, manager) {
2165
+ const connection = req.method === "GET" ? readConnectionFromQuery(context.url) : await readConnectionBody(req);
2166
+ const result = await manager.withMiniProgram(connection, async (miniProgram) => {
2167
+ const stack = await miniProgram.pageStack();
2168
+ return stack.map((page) => ({
2169
+ path: page.path,
2170
+ query: toSerializableValue(page.query)
2171
+ }));
2172
+ });
2173
+ writeOk(res, result);
2174
+ }
2175
+ async function handleCapture(req, res, manager) {
2176
+ const input = captureBodySchema.parse(await readJsonBody(req));
2177
+ const result = await manager.withMiniProgram(input, async (miniProgram) => {
2178
+ const screenshot = await miniProgram.screenshot();
2179
+ const buffer = typeof screenshot === "string" ? Buffer.from(screenshot, "base64") : Buffer.from(screenshot);
2180
+ if (input.outputPath) {
2181
+ const resolvedOutputPath = manager.resolveWorkspacePath(input.outputPath);
2182
+ await fs$1.mkdir(path.dirname(resolvedOutputPath), { recursive: true });
2183
+ await fs$1.writeFile(resolvedOutputPath, buffer);
821
2184
  return {
822
- contents: [{
823
- uri: uri.toString(),
824
- mimeType: "text/plain",
825
- text: `[resource-error] ${normalizeErrorMessage(error)}`
826
- }]
2185
+ path: resolvedOutputPath,
2186
+ bytes: buffer.length
827
2187
  };
828
2188
  }
2189
+ return {
2190
+ base64: buffer.toString("base64"),
2191
+ bytes: buffer.length
2192
+ };
829
2193
  });
830
- return {
831
- server,
832
- workspaceRoot
833
- };
2194
+ writeOk(res, result);
2195
+ }
2196
+ async function handleHostApi(req, res, manager) {
2197
+ const input = hostApiBodySchema.parse(await readJsonBody(req));
2198
+ const result = await manager.withMiniProgram(input, async (miniProgram) => {
2199
+ const args = input.args ?? [];
2200
+ return {
2201
+ method: input.method,
2202
+ args: toSerializableValue(args),
2203
+ result: toSerializableValue(await miniProgram.callWxMethod(input.method, ...args))
2204
+ };
2205
+ });
2206
+ writeOk(res, result);
2207
+ }
2208
+ async function handleConsole(req, res, manager) {
2209
+ const logs = manager.getLogs();
2210
+ if (req.method === "DELETE") {
2211
+ manager.clearLogs();
2212
+ }
2213
+ writeOk(res, {
2214
+ count: logs.length,
2215
+ logs,
2216
+ cleared: req.method === "DELETE"
2217
+ });
2218
+ }
2219
+ async function handleSession(req, res, context, manager) {
2220
+ const connection = context.url.searchParams.has("projectPath") ? readConnectionFromQuery(context.url) : await readConnectionBody(req);
2221
+ await manager.close(connection);
2222
+ writeOk(res, {
2223
+ closed: true,
2224
+ projectPath: connection.projectPath,
2225
+ resolvedProjectPath: manager.resolveProjectPath(connection.projectPath)
2226
+ });
2227
+ }
2228
+ async function handleRuntimeRestRequest(req, res, options) {
2229
+ const endpoint = normalizeRuntimeRestEndpoint(options.endpoint);
2230
+ const context = getRouteContext(req, endpoint);
2231
+ if (!context) {
2232
+ return false;
2233
+ }
2234
+ const method = req.method ?? "GET";
2235
+ if (!assertMethod(res, method, context.requestPath)) {
2236
+ return true;
2237
+ }
2238
+ try {
2239
+ if (context.requestPath === "/") {
2240
+ await handleRoot(res, endpoint);
2241
+ } else if (context.requestPath === "/connect") {
2242
+ await handleConnect(req, res, options.manager);
2243
+ } else if (context.requestPath === "/route") {
2244
+ await handleRoute(req, res, options.manager);
2245
+ } else if (context.requestPath === "/active-page") {
2246
+ await handleActivePage(req, res, context, options.manager);
2247
+ } else if (context.requestPath === "/page-stack") {
2248
+ await handlePageStack(req, res, context, options.manager);
2249
+ } else if (context.requestPath === "/capture") {
2250
+ await handleCapture(req, res, options.manager);
2251
+ } else if (context.requestPath === "/host-api") {
2252
+ await handleHostApi(req, res, options.manager);
2253
+ } else if (context.requestPath === "/console") {
2254
+ await handleConsole(req, res, options.manager);
2255
+ } else if (context.requestPath === "/session") {
2256
+ await handleSession(req, res, context, options.manager);
2257
+ }
2258
+ } catch (error) {
2259
+ writeError(res, error instanceof SyntaxError || error instanceof z.ZodError ? 400 : 500, error);
2260
+ }
2261
+ return true;
834
2262
  }
835
2263
 
836
2264
  const DEFAULT_MCP_HOST = "127.0.0.1";
@@ -894,22 +2322,32 @@ async function startStreamableHttpServer(options) {
894
2322
  endpoint = DEFAULT_MCP_ENDPOINT,
895
2323
  host = DEFAULT_MCP_HOST,
896
2324
  port = DEFAULT_MCP_PORT,
2325
+ restEndpoint = DEFAULT_RUNTIME_REST_ENDPOINT,
897
2326
  workspaceRoot,
2327
+ runtimeHooks,
898
2328
  unref = false,
899
2329
  quiet = false,
900
2330
  onReady
901
2331
  } = options;
902
2332
  const normalizedEndpoint = normalizeEndpoint(endpoint);
903
2333
  const normalizedPort = normalizePort(port);
904
- const { server: mcpServer } = await createWeappViteMcpServer({ workspaceRoot });
2334
+ const normalizedRestEndpoint = normalizeRuntimeRestEndpoint(restEndpoint);
2335
+ const { runtimeManager, server: mcpServer } = await createWeappViteMcpServer({ runtimeHooks, workspaceRoot });
905
2336
  const transport = new StreamableHTTPServerTransport({
906
- sessionIdGenerator: void 0
2337
+ sessionIdGenerator: randomUUID
907
2338
  });
908
2339
  await mcpServer.connect(transport);
909
2340
  const httpServer = http.createServer(async (req, res) => {
910
2341
  try {
911
2342
  const hostHeader = req.headers.host ?? `${host}:${normalizedPort}`;
912
2343
  const url = new URL(req.url ?? "/", `http://${hostHeader}`);
2344
+ const handledByRest = await handleRuntimeRestRequest(req, res, {
2345
+ endpoint: normalizedRestEndpoint,
2346
+ manager: runtimeManager
2347
+ });
2348
+ if (handledByRest) {
2349
+ return;
2350
+ }
913
2351
  if (url.pathname !== normalizedEndpoint) {
914
2352
  writeJson(res, 404, {
915
2353
  jsonrpc: "2.0",
@@ -957,6 +2395,9 @@ async function startStreamableHttpServer(options) {
957
2395
  }
958
2396
  if (!quiet) {
959
2397
  onReady?.(`[mcp] streamable-http ready at http://${host}:${normalizedPort}${normalizedEndpoint}`);
2398
+ if (normalizedRestEndpoint !== false) {
2399
+ onReady?.(`[mcp] REST runtime ready at http://${host}:${normalizedPort}${normalizedRestEndpoint}`);
2400
+ }
960
2401
  }
961
2402
  return {
962
2403
  transport: "streamable-http",
@@ -1002,4 +2443,4 @@ ${message}
1002
2443
  });
1003
2444
  }
1004
2445
 
1005
- export { DEFAULT_MAX_FILE_CHARS, DEFAULT_MAX_OUTPUT_CHARS, DEFAULT_MAX_RESULTS, DEFAULT_MCP_ENDPOINT, DEFAULT_MCP_HOST, DEFAULT_MCP_PORT, DEFAULT_TIMEOUT_MS, EXPOSED_PACKAGES, MCP_SERVER_NAME, MCP_SERVER_VERSION, SKIPPED_DIR_NAMES, assertInsideRoot, createWeappViteMcpServer, formatJson, listFilesInDirectory, loadExposedCatalog, loadPackageSummary, normalizeErrorMessage, readFileContent, resolveSubPath, resolveWorkspaceRoot, runCommand, searchTextInDirectory, startStdioServer, startWeappViteMcpServer, toToolError, toToolResult };
2446
+ 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, formatJson, listFilesInDirectory, loadExposedCatalog, loadPackageSummary, normalizeErrorMessage, readFileContent, resolveSubPath, resolveWorkspaceRoot, runCommand, searchTextInDirectory, startStdioServer, startWeappViteMcpServer, toToolError, toToolResult };