@weapp-vite/mcp 1.3.0 → 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/README.md CHANGED
@@ -35,6 +35,16 @@ const handle = await startWeappViteMcpServer({
35
35
  await handle.close?.()
36
36
  ```
37
37
 
38
+ `streamable-http` 模式会同时暴露 DevTools runtime REST 接口,默认前缀为
39
+ `/api/weapp/devtools`。可以通过 `restEndpoint` 调整,或设为 `false` 关闭:
40
+
41
+ ```ts
42
+ const handle = await startWeappViteMcpServer({
43
+ transport: 'streamable-http',
44
+ restEndpoint: '/api/weapp/devtools',
45
+ })
46
+ ```
47
+
38
48
  ## 主要 Tools
39
49
 
40
50
  - `workspace_catalog`: 输出 `weapp-vite / wevu / wevu-compiler` 目录、版本、脚本
@@ -66,6 +76,31 @@ await handle.close?.()
66
76
 
67
77
  建议调用顺序:先 `weapp_devtools_connect`,再 `weapp_devtools_active_page`,之后再执行 `weapp_devtools_capture` 或 `weapp_runtime_*`。
68
78
 
79
+ ### DevTools Runtime REST
80
+
81
+ REST 接口和 MCP runtime tools 复用同一个 automator 会话管理器,适合脚本或其他非 MCP 客户端连续操控模拟器:
82
+
83
+ - `POST /api/weapp/devtools/connect`
84
+ - `POST /api/weapp/devtools/route`
85
+ - `GET|POST /api/weapp/devtools/active-page`
86
+ - `GET|POST /api/weapp/devtools/page-stack`
87
+ - `POST /api/weapp/devtools/capture`
88
+ - `POST /api/weapp/devtools/host-api`
89
+ - `GET|DELETE /api/weapp/devtools/console`
90
+ - `DELETE /api/weapp/devtools/session`
91
+
92
+ 示例:
93
+
94
+ ```bash
95
+ curl -X POST http://127.0.0.1:3088/api/weapp/devtools/route \
96
+ -H 'content-type: application/json' \
97
+ -d '{"projectPath":"dist/build/mp-weixin","transition":"reLaunch","path":"pages/index/index"}'
98
+
99
+ curl -X POST http://127.0.0.1:3088/api/weapp/devtools/capture \
100
+ -H 'content-type: application/json' \
101
+ -d '{"projectPath":"dist/build/mp-weixin","outputPath":".tmp/home.png"}'
102
+ ```
103
+
69
104
  ## 主要 Resources
70
105
 
71
106
  - `weapp-vite://workspace/catalog`
package/dist/index.d.mts CHANGED
@@ -86,15 +86,78 @@ declare function searchTextInDirectory(root: string, query: string, options?: {
86
86
  maxResults?: number;
87
87
  }): Promise<SearchMatch[]>;
88
88
 
89
+ interface MiniProgramElement {
90
+ tagName?: string;
91
+ $?: (selector: string) => Promise<MiniProgramElement | null>;
92
+ $$?: (selector: string) => Promise<MiniProgramElement[]>;
93
+ tap: () => Promise<void>;
94
+ }
95
+ interface MiniProgramPage {
96
+ path: string;
97
+ query?: unknown;
98
+ $: (selector: string) => Promise<MiniProgramElement | null>;
99
+ $$?: (selector: string) => Promise<MiniProgramElement[]>;
100
+ data: (path?: string) => Promise<unknown>;
101
+ size: () => Promise<unknown>;
102
+ scrollTop: () => Promise<unknown>;
103
+ waitFor: (milliseconds: number) => Promise<void>;
104
+ }
105
+ interface MiniProgramLike {
106
+ on: (name: 'console' | 'exception', handler: (payload: unknown) => void) => void;
107
+ off?: (name: 'console' | 'exception', handler: (payload: unknown) => void) => void;
108
+ currentPage: () => Promise<MiniProgramPage>;
109
+ systemInfo: () => Promise<unknown>;
110
+ pageStack: () => Promise<MiniProgramPage[]>;
111
+ navigateTo: (url: string) => Promise<MiniProgramPage>;
112
+ redirectTo: (url: string) => Promise<MiniProgramPage>;
113
+ reLaunch: (url: string) => Promise<MiniProgramPage>;
114
+ switchTab: (url: string) => Promise<MiniProgramPage>;
115
+ navigateBack: () => Promise<MiniProgramPage>;
116
+ screenshot: () => Promise<string | Uint8Array>;
117
+ callWxMethod: (method: string, ...args: unknown[]) => Promise<unknown>;
118
+ }
119
+ interface RuntimeConnectionInput {
120
+ projectPath: string;
121
+ timeout?: number;
122
+ preferOpenedSession?: boolean;
123
+ }
124
+ interface RuntimeConsoleLogEntry {
125
+ level: string;
126
+ message: string;
127
+ timestamp: number;
128
+ raw: unknown;
129
+ }
130
+ declare class RuntimeSessionManager {
131
+ private readonly workspaceRoot;
132
+ private readonly runtimeHooks;
133
+ private readonly logs;
134
+ private readonly attachedSessions;
135
+ private readonly maxLogs;
136
+ constructor(workspaceRoot: string, runtimeHooks?: DevtoolsRuntimeHooks);
137
+ close(input: RuntimeConnectionInput): Promise<void>;
138
+ clearLogs(): void;
139
+ getLogs(): RuntimeConsoleLogEntry[];
140
+ resolveProjectPath(projectPath: string): string;
141
+ resolveWorkspacePath(filePath: string): string;
142
+ withMiniProgram<T>(input: RuntimeConnectionInput, runner: (miniProgram: MiniProgramLike) => Promise<T>): Promise<T>;
143
+ withPage<T>(input: RuntimeConnectionInput, runner: (page: MiniProgramPage, miniProgram: MiniProgramLike) => Promise<T>): Promise<T>;
144
+ private attach;
145
+ private detach;
146
+ private pushLog;
147
+ }
148
+
89
149
  interface CreateServerOptions {
90
150
  runtimeHooks?: DevtoolsRuntimeHooks;
91
151
  workspaceRoot?: string;
92
152
  }
93
153
  declare function createWeappViteMcpServer(options?: CreateServerOptions): Promise<{
154
+ runtimeManager: RuntimeSessionManager;
94
155
  server: McpServer;
95
156
  workspaceRoot: string;
96
157
  }>;
97
158
 
159
+ declare const DEFAULT_RUNTIME_REST_ENDPOINT = "/api/weapp/devtools";
160
+
98
161
  declare const DEFAULT_MCP_HOST = "127.0.0.1";
99
162
  declare const DEFAULT_MCP_PORT = 3088;
100
163
  declare const DEFAULT_MCP_ENDPOINT = "/mcp";
@@ -103,6 +166,7 @@ interface StartMcpServerOptions extends CreateServerOptions {
103
166
  host?: string;
104
167
  port?: number;
105
168
  endpoint?: string;
169
+ restEndpoint?: string | false;
106
170
  unref?: boolean;
107
171
  quiet?: boolean;
108
172
  onReady?: (message: string) => void;
@@ -137,5 +201,5 @@ declare function resolveWorkspaceRoot(start?: string): string;
137
201
  declare function assertInsideRoot(root: string, targetPath: string): string;
138
202
  declare function resolveSubPath(root: string, relativePath: string): string;
139
203
 
140
- 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 };
204
+ 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 };
141
205
  export type { CommandResult, CreateServerOptions, ExposedPackageId, ExposedPackageSummary, McpServerHandle, SearchMatch, StartMcpServerOptions };
package/dist/index.d.ts CHANGED
@@ -86,15 +86,78 @@ declare function searchTextInDirectory(root: string, query: string, options?: {
86
86
  maxResults?: number;
87
87
  }): Promise<SearchMatch[]>;
88
88
 
89
+ interface MiniProgramElement {
90
+ tagName?: string;
91
+ $?: (selector: string) => Promise<MiniProgramElement | null>;
92
+ $$?: (selector: string) => Promise<MiniProgramElement[]>;
93
+ tap: () => Promise<void>;
94
+ }
95
+ interface MiniProgramPage {
96
+ path: string;
97
+ query?: unknown;
98
+ $: (selector: string) => Promise<MiniProgramElement | null>;
99
+ $$?: (selector: string) => Promise<MiniProgramElement[]>;
100
+ data: (path?: string) => Promise<unknown>;
101
+ size: () => Promise<unknown>;
102
+ scrollTop: () => Promise<unknown>;
103
+ waitFor: (milliseconds: number) => Promise<void>;
104
+ }
105
+ interface MiniProgramLike {
106
+ on: (name: 'console' | 'exception', handler: (payload: unknown) => void) => void;
107
+ off?: (name: 'console' | 'exception', handler: (payload: unknown) => void) => void;
108
+ currentPage: () => Promise<MiniProgramPage>;
109
+ systemInfo: () => Promise<unknown>;
110
+ pageStack: () => Promise<MiniProgramPage[]>;
111
+ navigateTo: (url: string) => Promise<MiniProgramPage>;
112
+ redirectTo: (url: string) => Promise<MiniProgramPage>;
113
+ reLaunch: (url: string) => Promise<MiniProgramPage>;
114
+ switchTab: (url: string) => Promise<MiniProgramPage>;
115
+ navigateBack: () => Promise<MiniProgramPage>;
116
+ screenshot: () => Promise<string | Uint8Array>;
117
+ callWxMethod: (method: string, ...args: unknown[]) => Promise<unknown>;
118
+ }
119
+ interface RuntimeConnectionInput {
120
+ projectPath: string;
121
+ timeout?: number;
122
+ preferOpenedSession?: boolean;
123
+ }
124
+ interface RuntimeConsoleLogEntry {
125
+ level: string;
126
+ message: string;
127
+ timestamp: number;
128
+ raw: unknown;
129
+ }
130
+ declare class RuntimeSessionManager {
131
+ private readonly workspaceRoot;
132
+ private readonly runtimeHooks;
133
+ private readonly logs;
134
+ private readonly attachedSessions;
135
+ private readonly maxLogs;
136
+ constructor(workspaceRoot: string, runtimeHooks?: DevtoolsRuntimeHooks);
137
+ close(input: RuntimeConnectionInput): Promise<void>;
138
+ clearLogs(): void;
139
+ getLogs(): RuntimeConsoleLogEntry[];
140
+ resolveProjectPath(projectPath: string): string;
141
+ resolveWorkspacePath(filePath: string): string;
142
+ withMiniProgram<T>(input: RuntimeConnectionInput, runner: (miniProgram: MiniProgramLike) => Promise<T>): Promise<T>;
143
+ withPage<T>(input: RuntimeConnectionInput, runner: (page: MiniProgramPage, miniProgram: MiniProgramLike) => Promise<T>): Promise<T>;
144
+ private attach;
145
+ private detach;
146
+ private pushLog;
147
+ }
148
+
89
149
  interface CreateServerOptions {
90
150
  runtimeHooks?: DevtoolsRuntimeHooks;
91
151
  workspaceRoot?: string;
92
152
  }
93
153
  declare function createWeappViteMcpServer(options?: CreateServerOptions): Promise<{
154
+ runtimeManager: RuntimeSessionManager;
94
155
  server: McpServer;
95
156
  workspaceRoot: string;
96
157
  }>;
97
158
 
159
+ declare const DEFAULT_RUNTIME_REST_ENDPOINT = "/api/weapp/devtools";
160
+
98
161
  declare const DEFAULT_MCP_HOST = "127.0.0.1";
99
162
  declare const DEFAULT_MCP_PORT = 3088;
100
163
  declare const DEFAULT_MCP_ENDPOINT = "/mcp";
@@ -103,6 +166,7 @@ interface StartMcpServerOptions extends CreateServerOptions {
103
166
  host?: string;
104
167
  port?: number;
105
168
  endpoint?: string;
169
+ restEndpoint?: string | false;
106
170
  unref?: boolean;
107
171
  quiet?: boolean;
108
172
  onReady?: (message: string) => void;
@@ -137,5 +201,5 @@ declare function resolveWorkspaceRoot(start?: string): string;
137
201
  declare function assertInsideRoot(root: string, targetPath: string): string;
138
202
  declare function resolveSubPath(root: string, relativePath: string): string;
139
203
 
140
- 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 };
204
+ 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 };
141
205
  export type { CommandResult, CreateServerOptions, ExposedPackageId, ExposedPackageSummary, McpServerHandle, SearchMatch, StartMcpServerOptions };
package/dist/index.mjs CHANGED
@@ -1,17 +1,18 @@
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
- import { ResourceTemplate, McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
- import { z } from 'zod';
8
+ import fs$1 from 'node:fs/promises';
9
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';
10
13
  import fs from 'node:fs';
11
- import fs$1 from 'node:fs/promises';
12
14
  import { createRequire } from 'node:module';
13
15
  import { spawn } from 'node:child_process';
14
- import { closeSharedMiniProgram, acquireSharedMiniProgram, releaseSharedMiniProgram } from '@weapp-vite/devtools-runtime';
15
16
 
16
17
  const MCP_SERVER_NAME = "@weapp-vite/mcp";
17
18
  const MCP_SERVER_VERSION = "2.0.0";
@@ -1625,10 +1626,11 @@ function registerRuntimePageTools(server, manager) {
1625
1626
  }
1626
1627
 
1627
1628
  function registerRuntimeTools(server, options) {
1628
- const manager = new RuntimeSessionManager(options.workspaceRoot, options.runtimeHooks);
1629
+ const manager = options.manager ?? new RuntimeSessionManager(options.workspaceRoot, options.runtimeHooks);
1629
1630
  registerDevtoolsRuntimeTools(server, manager);
1630
1631
  registerRuntimePageTools(server, manager);
1631
1632
  registerRuntimeNodeTools(server, manager);
1633
+ return manager;
1632
1634
  }
1633
1635
 
1634
1636
  async function loadPackageSummary(workspaceRoot, id) {
@@ -1907,12 +1909,14 @@ async function createWeappViteMcpServer(options) {
1907
1909
  name: MCP_SERVER_NAME,
1908
1910
  version: MCP_SERVER_VERSION
1909
1911
  });
1912
+ const runtimeManager = new RuntimeSessionManager(workspaceRoot, options?.runtimeHooks);
1910
1913
  registerServerTools(server, {
1911
1914
  workspaceRoot,
1912
1915
  packageIds,
1913
1916
  packageIdSchema
1914
1917
  });
1915
1918
  registerRuntimeTools(server, {
1919
+ manager: runtimeManager,
1916
1920
  runtimeHooks: options?.runtimeHooks,
1917
1921
  workspaceRoot
1918
1922
  });
@@ -1925,11 +1929,338 @@ async function createWeappViteMcpServer(options) {
1925
1929
  packageIds
1926
1930
  });
1927
1931
  return {
1932
+ runtimeManager,
1928
1933
  server,
1929
1934
  workspaceRoot
1930
1935
  };
1931
1936
  }
1932
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)
1987
+ }
1988
+ });
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);
2112
+ return {
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)
2118
+ };
2119
+ });
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
+ };
2135
+ }
2136
+ if (!input.path) {
2137
+ throw new Error("transition \u4E0D\u662F navigateBack \u65F6\u5FC5\u987B\u63D0\u4F9B path\u3002");
2138
+ }
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);
2147
+ }
2148
+ return {
2149
+ transition,
2150
+ url,
2151
+ activePage: page ? { path: page.path, query: toSerializableValue(page.query) } : null
2152
+ };
2153
+ });
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);
2184
+ return {
2185
+ path: resolvedOutputPath,
2186
+ bytes: buffer.length
2187
+ };
2188
+ }
2189
+ return {
2190
+ base64: buffer.toString("base64"),
2191
+ bytes: buffer.length
2192
+ };
2193
+ });
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;
2262
+ }
2263
+
1933
2264
  const DEFAULT_MCP_HOST = "127.0.0.1";
1934
2265
  const DEFAULT_MCP_PORT = 3088;
1935
2266
  const DEFAULT_MCP_ENDPOINT = "/mcp";
@@ -1991,6 +2322,7 @@ async function startStreamableHttpServer(options) {
1991
2322
  endpoint = DEFAULT_MCP_ENDPOINT,
1992
2323
  host = DEFAULT_MCP_HOST,
1993
2324
  port = DEFAULT_MCP_PORT,
2325
+ restEndpoint = DEFAULT_RUNTIME_REST_ENDPOINT,
1994
2326
  workspaceRoot,
1995
2327
  runtimeHooks,
1996
2328
  unref = false,
@@ -1999,15 +2331,23 @@ async function startStreamableHttpServer(options) {
1999
2331
  } = options;
2000
2332
  const normalizedEndpoint = normalizeEndpoint(endpoint);
2001
2333
  const normalizedPort = normalizePort(port);
2002
- const { server: mcpServer } = await createWeappViteMcpServer({ runtimeHooks, workspaceRoot });
2334
+ const normalizedRestEndpoint = normalizeRuntimeRestEndpoint(restEndpoint);
2335
+ const { runtimeManager, server: mcpServer } = await createWeappViteMcpServer({ runtimeHooks, workspaceRoot });
2003
2336
  const transport = new StreamableHTTPServerTransport({
2004
- sessionIdGenerator: void 0
2337
+ sessionIdGenerator: randomUUID
2005
2338
  });
2006
2339
  await mcpServer.connect(transport);
2007
2340
  const httpServer = http.createServer(async (req, res) => {
2008
2341
  try {
2009
2342
  const hostHeader = req.headers.host ?? `${host}:${normalizedPort}`;
2010
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
+ }
2011
2351
  if (url.pathname !== normalizedEndpoint) {
2012
2352
  writeJson(res, 404, {
2013
2353
  jsonrpc: "2.0",
@@ -2055,6 +2395,9 @@ async function startStreamableHttpServer(options) {
2055
2395
  }
2056
2396
  if (!quiet) {
2057
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
+ }
2058
2401
  }
2059
2402
  return {
2060
2403
  transport: "streamable-http",
@@ -2100,4 +2443,4 @@ ${message}
2100
2443
  });
2101
2444
  }
2102
2445
 
2103
- 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 };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@weapp-vite/mcp",
3
3
  "type": "module",
4
- "version": "1.3.0",
4
+ "version": "1.3.1",
5
5
  "description": "mcp",
6
6
  "author": "ice breaker <1324318532@qq.com>",
7
7
  "license": "ISC",
@@ -32,8 +32,8 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@modelcontextprotocol/sdk": "^1.29.0",
35
- "zod": "^4.3.6",
36
- "@weapp-vite/devtools-runtime": "0.2.0"
35
+ "zod": "^4.4.2",
36
+ "@weapp-vite/devtools-runtime": "0.2.1"
37
37
  },
38
38
  "scripts": {
39
39
  "dev": "unbuild --stub",