vite-plugin-aipanel 1.2.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +72 -0
  2. package/es/client/vue-devtools-bridge.d.ts +58 -0
  3. package/es/client/vue-devtools-bridge.mjs +150 -0
  4. package/es/core/injector.d.ts +2 -0
  5. package/es/core/injector.mjs +15 -0
  6. package/es/core/mcp-chrome.d.ts +56 -0
  7. package/es/core/mcp-chrome.mjs +133 -0
  8. package/es/core/mcp-proxy.d.ts +23 -0
  9. package/es/core/mcp-proxy.mjs +212 -0
  10. package/es/core/mcp-tools.d.ts +15 -0
  11. package/es/core/mcp-tools.mjs +671 -0
  12. package/es/core/provider-loader.d.ts +19 -0
  13. package/es/core/provider-loader.mjs +30 -0
  14. package/es/core/proxy-server.d.ts +12 -0
  15. package/es/core/proxy-server.mjs +148 -0
  16. package/es/core/service.d.ts +39 -0
  17. package/es/core/service.mjs +267 -0
  18. package/es/endpoints/context.d.ts +3 -0
  19. package/es/endpoints/context.mjs +106 -0
  20. package/es/endpoints/index.d.ts +9 -0
  21. package/es/endpoints/index.mjs +30 -0
  22. package/es/endpoints/logs.d.ts +9 -0
  23. package/es/endpoints/logs.mjs +86 -0
  24. package/es/endpoints/mcp.d.ts +6 -0
  25. package/es/endpoints/mcp.mjs +610 -0
  26. package/es/endpoints/sessions.d.ts +3 -0
  27. package/es/endpoints/sessions.mjs +62 -0
  28. package/es/endpoints/sse.d.ts +3 -0
  29. package/es/endpoints/sse.mjs +44 -0
  30. package/es/endpoints/start.d.ts +3 -0
  31. package/es/endpoints/start.mjs +21 -0
  32. package/es/endpoints/types.d.ts +34 -0
  33. package/es/endpoints/types.mjs +0 -0
  34. package/es/endpoints/vue-devtools.d.ts +9 -0
  35. package/es/endpoints/vue-devtools.mjs +107 -0
  36. package/es/endpoints/warmup.d.ts +3 -0
  37. package/es/endpoints/warmup.mjs +42 -0
  38. package/es/endpoints/widget.d.ts +3 -0
  39. package/es/endpoints/widget.mjs +36 -0
  40. package/es/env.d.ts +1 -0
  41. package/es/index.d.ts +22 -0
  42. package/es/index.mjs +342 -0
  43. package/es/utils/format-bridge.d.ts +9 -0
  44. package/es/utils/format-bridge.mjs +80 -0
  45. package/es/utils/paths.d.ts +3 -0
  46. package/es/utils/paths.mjs +45 -0
  47. package/es/utils/system.d.ts +3 -0
  48. package/es/utils/system.mjs +76 -0
  49. package/lib/client/vue-devtools-bridge.cjs +150 -0
  50. package/lib/client/vue-devtools-bridge.d.ts +58 -0
  51. package/lib/client.css +2 -0
  52. package/lib/client.js +6950 -0
  53. package/lib/core/injector.cjs +34 -0
  54. package/lib/core/injector.d.ts +2 -0
  55. package/lib/core/mcp-chrome.cjs +161 -0
  56. package/lib/core/mcp-chrome.d.ts +56 -0
  57. package/lib/core/mcp-proxy.cjs +244 -0
  58. package/lib/core/mcp-proxy.d.ts +23 -0
  59. package/lib/core/mcp-tools.cjs +694 -0
  60. package/lib/core/mcp-tools.d.ts +15 -0
  61. package/lib/core/provider-loader.cjs +53 -0
  62. package/lib/core/provider-loader.d.ts +19 -0
  63. package/lib/core/proxy-server.cjs +181 -0
  64. package/lib/core/proxy-server.d.ts +12 -0
  65. package/lib/core/service.cjs +289 -0
  66. package/lib/core/service.d.ts +39 -0
  67. package/lib/endpoints/context.cjs +129 -0
  68. package/lib/endpoints/context.d.ts +3 -0
  69. package/lib/endpoints/index.cjs +58 -0
  70. package/lib/endpoints/index.d.ts +9 -0
  71. package/lib/endpoints/logs.cjs +107 -0
  72. package/lib/endpoints/logs.d.ts +9 -0
  73. package/lib/endpoints/mcp.cjs +624 -0
  74. package/lib/endpoints/mcp.d.ts +6 -0
  75. package/lib/endpoints/sessions.cjs +85 -0
  76. package/lib/endpoints/sessions.d.ts +3 -0
  77. package/lib/endpoints/sse.cjs +67 -0
  78. package/lib/endpoints/sse.d.ts +3 -0
  79. package/lib/endpoints/start.cjs +44 -0
  80. package/lib/endpoints/start.d.ts +3 -0
  81. package/lib/endpoints/types.cjs +15 -0
  82. package/lib/endpoints/types.d.ts +34 -0
  83. package/lib/endpoints/vue-devtools.cjs +132 -0
  84. package/lib/endpoints/vue-devtools.d.ts +9 -0
  85. package/lib/endpoints/warmup.cjs +65 -0
  86. package/lib/endpoints/warmup.d.ts +3 -0
  87. package/lib/endpoints/widget.cjs +69 -0
  88. package/lib/endpoints/widget.d.ts +3 -0
  89. package/lib/env.d.ts +1 -0
  90. package/lib/index.cjs +361 -0
  91. package/lib/index.d.ts +22 -0
  92. package/lib/utils/format-bridge.cjs +102 -0
  93. package/lib/utils/format-bridge.d.ts +9 -0
  94. package/lib/utils/paths.cjs +80 -0
  95. package/lib/utils/paths.d.ts +3 -0
  96. package/lib/utils/system.cjs +110 -0
  97. package/lib/utils/system.d.ts +3 -0
  98. package/package.json +68 -0
@@ -0,0 +1,86 @@
1
+ import {
2
+ getProcessLogBuffer
3
+ } from "@aipanel/core/node";
4
+ import { RequestContext, createLogger } from "@aipanel/core/node";
5
+ import { LOGS_API_PATH } from "@aipanel/core";
6
+ const log = createLogger("Endpoints:Logs");
7
+ function setupLogsEndpoint(server) {
8
+ server.middlewares.use(LOGS_API_PATH, async (req, res) => {
9
+ const reqCtx = new RequestContext(req.method || "GET", LOGS_API_PATH);
10
+ res.setHeader("Content-Type", "application/json");
11
+ res.setHeader("Access-Control-Allow-Origin", "*");
12
+ res.setHeader("Access-Control-Allow-Methods", "GET, DELETE, OPTIONS");
13
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
14
+ if (req.method === "OPTIONS") {
15
+ res.writeHead(200);
16
+ res.end();
17
+ reqCtx.end(200);
18
+ return;
19
+ }
20
+ const buffer = getProcessLogBuffer();
21
+ if (req.method === "GET") {
22
+ try {
23
+ const url = new URL(req.url || "", `http://${req.headers.host || "localhost"}`);
24
+ const levelParam = url.searchParams.get("level");
25
+ const limitParam = url.searchParams.get("limit");
26
+ const sourceParam = url.searchParams.get("source");
27
+ const sinceParam = url.searchParams.get("since");
28
+ const options = {};
29
+ if (levelParam) {
30
+ const levels = levelParam.split(",").map((l) => l.trim());
31
+ options.level = levels.length === 1 ? levels[0] : levels;
32
+ }
33
+ if (limitParam) {
34
+ const limit = parseInt(limitParam, 10);
35
+ if (limit > 0 && limit <= 1e3) {
36
+ options.limit = limit;
37
+ }
38
+ }
39
+ if (sourceParam) {
40
+ options.source = sourceParam;
41
+ }
42
+ if (sinceParam) {
43
+ options.since = sinceParam;
44
+ }
45
+ const logs = buffer.getLogs(options);
46
+ log.debug("Logs requested", {
47
+ params: { level: levelParam, limit: limitParam, source: sourceParam, since: sinceParam },
48
+ resultCount: logs.length,
49
+ bufferSize: buffer.size()
50
+ });
51
+ res.writeHead(200);
52
+ res.end(
53
+ JSON.stringify({
54
+ logs,
55
+ meta: {
56
+ total: buffer.size(),
57
+ returned: logs.length,
58
+ filters: options
59
+ }
60
+ })
61
+ );
62
+ reqCtx.end(200);
63
+ } catch (e) {
64
+ log.error("Failed to get logs", { error: e });
65
+ res.writeHead(500);
66
+ res.end(JSON.stringify({ error: "Internal server error" }));
67
+ reqCtx.error(e);
68
+ }
69
+ return;
70
+ }
71
+ if (req.method === "DELETE") {
72
+ buffer.clear();
73
+ log.debug("Log buffer cleared");
74
+ res.writeHead(200);
75
+ res.end(JSON.stringify({ success: true, message: "Log buffer cleared" }));
76
+ reqCtx.end(200);
77
+ return;
78
+ }
79
+ res.writeHead(405);
80
+ res.end(JSON.stringify({ error: "Method not allowed" }));
81
+ reqCtx.end(405);
82
+ });
83
+ }
84
+ export {
85
+ setupLogsEndpoint
86
+ };
@@ -0,0 +1,6 @@
1
+ import type { ViteDevServer } from "vite";
2
+ import type { LogFileConfig, PageContext } from "@aipanel/core";
3
+ import { MCP_API_PATH } from "@aipanel/core";
4
+ import { McpProxy } from "../core/mcp-proxy";
5
+ export { MCP_API_PATH };
6
+ export declare function setupMcpEndpoint(server: ViteDevServer, mcp: McpProxy, getPageContext: () => PageContext, logFiles?: LogFileConfig[]): void;
@@ -0,0 +1,610 @@
1
+ import { MCP_API_PATH, VUE_DEVTOOLS_ACTIONS, sleep } from "@aipanel/core";
2
+ import {
3
+ createLogger,
4
+ getProcessLogBuffer,
5
+ readLogFileTail
6
+ } from "@aipanel/core/node";
7
+ import {
8
+ parseListPages,
9
+ resolveChromePageId,
10
+ getProjectOrigins,
11
+ isProjectPage,
12
+ validatePageId
13
+ } from "../core/mcp-chrome.mjs";
14
+ import { CUSTOM_TOOLS } from "../core/mcp-tools.mjs";
15
+ import { executeAction } from "./vue-devtools.mjs";
16
+ import { findGitRoot } from "../utils/system.mjs";
17
+ const log = createLogger("McpEndpoint");
18
+ function setupMcpEndpoint(server, mcp, getPageContext, logFiles = []) {
19
+ const projectRoot = findGitRoot(process.cwd());
20
+ server.middlewares.use(async (req, res, next) => {
21
+ if (!req.url?.startsWith(MCP_API_PATH)) return next();
22
+ res.setHeader("Access-Control-Allow-Origin", "*");
23
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
24
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id, Authorization");
25
+ res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id");
26
+ if (req.method === "OPTIONS") {
27
+ res.writeHead(204);
28
+ res.end();
29
+ return;
30
+ }
31
+ if (req.method === "GET") {
32
+ handleGetSse(req, res, mcp);
33
+ return;
34
+ }
35
+ if (req.method === "DELETE") {
36
+ res.writeHead(200);
37
+ res.end();
38
+ return;
39
+ }
40
+ if (req.method === "POST") {
41
+ await handlePost(
42
+ req,
43
+ res,
44
+ mcp,
45
+ getProjectOrigins(server),
46
+ getPageContext,
47
+ logFiles,
48
+ projectRoot
49
+ );
50
+ return;
51
+ }
52
+ next();
53
+ });
54
+ }
55
+ function handleGetSse(req, res, mcp) {
56
+ res.writeHead(200, {
57
+ "Content-Type": "text/event-stream",
58
+ "Cache-Control": "no-cache",
59
+ Connection: "keep-alive",
60
+ "Mcp-Session-Id": mcp.sessionId
61
+ });
62
+ res.write(":ok\n\n");
63
+ const keepAlive = setInterval(() => res.write(":ping\n\n"), 15e3);
64
+ req.on("close", () => clearInterval(keepAlive));
65
+ }
66
+ async function handlePost(req, res, mcp, projectOrigins, getPageContext, logFiles, projectRoot) {
67
+ try {
68
+ const body = await readBody(req);
69
+ if (!body) {
70
+ res.writeHead(400);
71
+ res.end("Empty body");
72
+ return;
73
+ }
74
+ const { method, id } = tryParseRequest(body);
75
+ log.debug("MCP request", { method, body: body.substring(0, 150) });
76
+ switch (method) {
77
+ case "tools/list":
78
+ return handleToolsList(res, id, mcp.sessionId, logFiles);
79
+ case "tools/call":
80
+ return await handleToolsCall(
81
+ res,
82
+ id,
83
+ body,
84
+ mcp,
85
+ projectOrigins,
86
+ getPageContext,
87
+ logFiles,
88
+ projectRoot
89
+ );
90
+ default:
91
+ return await handleForward(res, body, mcp);
92
+ }
93
+ } catch (e) {
94
+ log.debug("MCP POST error", { error: e.message });
95
+ sendMcpJson(res, 500, {
96
+ jsonrpc: "2.0",
97
+ error: { code: -32603, message: e.message }
98
+ });
99
+ }
100
+ }
101
+ function handleToolsList(res, id, sessionId, logFiles) {
102
+ const tools = [...CUSTOM_TOOLS, ...buildServiceLogTools(logFiles)];
103
+ sendMcpJson(res, 200, { jsonrpc: "2.0", id, result: { tools } }, sessionId);
104
+ }
105
+ function handleToolsCall(res, id, body, mcp, projectOrigins, getPageContext, logFiles, projectRoot) {
106
+ const params = tryParseParams(body);
107
+ const toolName = params?.name;
108
+ const args = params?.arguments ?? {};
109
+ if (toolName === "chrome-devtools_current_page") {
110
+ return handleGetPageContext(res, id, mcp, projectOrigins, getPageContext);
111
+ }
112
+ if (toolName?.startsWith("vue-devtools_")) {
113
+ return handleVueDevtoolsTool(res, id, mcp, projectOrigins, toolName, args);
114
+ }
115
+ if (toolName === "logs-devtools_vite_logs") {
116
+ return handleViteLogsTool(res, id, args, mcp.sessionId);
117
+ }
118
+ if (toolName && isServiceLogTool(toolName, logFiles)) {
119
+ return handleServiceLogsTool(res, id, toolName, args, logFiles, projectRoot, mcp.sessionId);
120
+ }
121
+ if (toolName?.startsWith("chrome-devtools_")) {
122
+ const mapped = toolName.slice("chrome-devtools_".length);
123
+ if (toolName === "chrome-devtools_list_pages") {
124
+ return handleListPages(res, id, mcp, projectOrigins, getPageContext);
125
+ }
126
+ if (toolName === "chrome-devtools_new_page") {
127
+ return handleNewPage(res, id, mcp, args, projectOrigins, getPageContext);
128
+ }
129
+ return handleDevTool(res, id, mcp, mapped, args, projectOrigins, toolName);
130
+ }
131
+ sendMcpError(res, id, -32601, `Tool not found: ${toolName}`, mcp.sessionId);
132
+ }
133
+ async function resolveProjectPages(mcp, projectOrigins, getPageContext) {
134
+ const listResult = await mcp.callChromeDevTool("list_pages", {});
135
+ if (listResult?.error || !listResult?.result) {
136
+ throw new Error(listResult?.error?.message ?? "\u65E0\u6CD5\u83B7\u53D6 Chrome \u9875\u9762\u5217\u8868");
137
+ }
138
+ const text = listResult?.result?.content?.[0]?.text;
139
+ const allPages = text ? parseListPages(text) : [];
140
+ const filtered = allPages.filter((p) => isProjectPage(p.url, projectOrigins));
141
+ log.debug("Chrome pages", { total: allPages.length, urls: allPages.map((p) => p.url) });
142
+ log.debug("project origins", { origins: projectOrigins });
143
+ log.debug("filtered pages", { count: filtered.length, pageIds: filtered.map((p) => p.pageId) });
144
+ let activePageId = null;
145
+ let activePageError;
146
+ if (filtered.length > 0) {
147
+ const pc = getPageContext();
148
+ const chromeSelectedPageId = allPages.find((p) => p.selected)?.pageId;
149
+ const resolved = await resolveChromePageId(
150
+ mcp,
151
+ pc.url,
152
+ pc.title,
153
+ projectOrigins,
154
+ pc.sessionId,
155
+ filtered,
156
+ chromeSelectedPageId
157
+ );
158
+ activePageId = resolved.ok ? resolved.pageId : null;
159
+ if (!resolved.ok) activePageError = resolved.error;
160
+ }
161
+ return { filtered, activePageId, activePageError };
162
+ }
163
+ async function confirmOpenProjectPages(mcp, projectOrigins, getPageContext) {
164
+ let pages = [];
165
+ let ok = false;
166
+ for (let attempt = 0; attempt < 3; attempt++) {
167
+ try {
168
+ ({ filtered: pages } = await resolveProjectPages(mcp, projectOrigins, getPageContext));
169
+ ok = true;
170
+ break;
171
+ } catch {
172
+ if (attempt < 2) await sleep(500);
173
+ }
174
+ }
175
+ if (!ok) return null;
176
+ if (pages.length === 0) {
177
+ await sleep(500);
178
+ try {
179
+ ({ filtered: pages } = await resolveProjectPages(mcp, projectOrigins, getPageContext));
180
+ } catch {
181
+ }
182
+ }
183
+ return pages;
184
+ }
185
+ async function handleListPages(res, id, mcp, projectOrigins, getPageContext) {
186
+ try {
187
+ const { filtered, activePageId } = await resolveProjectPages(
188
+ mcp,
189
+ projectOrigins,
190
+ getPageContext
191
+ );
192
+ if (filtered.length === 0) {
193
+ sendMcpResult(res, id, "\u6682\u65E0\u9879\u76EE\u9875\u9762\uFF0C\u8BF7\u5148\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u672C\u5730\u5F00\u53D1\u9875\u9762", mcp.sessionId);
194
+ return;
195
+ }
196
+ const pageList = filtered.map((p) => ({
197
+ pageId: p.pageId,
198
+ url: p.url,
199
+ title: p.title,
200
+ active: activePageId != null ? p.pageId === activePageId : false,
201
+ selected: p.selected
202
+ }));
203
+ sendMcpResult(res, id, JSON.stringify(pageList, null, 2), mcp.sessionId);
204
+ } catch (e) {
205
+ log.debug("handleListPages error", { error: e.message });
206
+ sendMcpError(res, id, -32603, `MCP \u8C03\u7528\u5931\u8D25: ${e.message}`, mcp.sessionId);
207
+ }
208
+ }
209
+ async function handleGetPageContext(res, id, mcp, projectOrigins, getPageContext) {
210
+ try {
211
+ const { filtered, activePageId, activePageError } = await resolveProjectPages(
212
+ mcp,
213
+ projectOrigins,
214
+ getPageContext
215
+ );
216
+ if (filtered.length === 0) {
217
+ sendMcpResult(res, id, "\u6682\u65E0\u9879\u76EE\u9875\u9762\uFF0C\u8BF7\u5148\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u672C\u5730\u5F00\u53D1\u9875\u9762", mcp.sessionId);
218
+ return;
219
+ }
220
+ const pc = getPageContext();
221
+ if (activePageId == null) {
222
+ sendMcpResult(
223
+ res,
224
+ id,
225
+ `\u65E0\u6CD5\u5B9A\u4F4D\u5F53\u524D\u9875\u9762\u5BF9\u5E94\u7684 pageId\uFF1A${activePageError ?? "\u672A\u5339\u914D\u5230\u5DF2\u6253\u5F00\u7684\u9875\u9762"}
226
+
227
+ \u5F53\u524D\u9875\u9762\u4E0A\u4E0B\u6587\uFF1A${pc.title} (${pc.url})
228
+
229
+ \u8BF7\u5237\u65B0\u76EE\u6807\u9875\u9762\u540E\u91CD\u8BD5\uFF1B\u6216\u4F7F\u7528 chrome-devtools_list_pages \u83B7\u53D6\u9875\u9762 ID \u540E\u76F4\u63A5\u64CD\u4F5C\u3002`,
230
+ mcp.sessionId
231
+ );
232
+ return;
233
+ }
234
+ const activePage = filtered.find((p) => p.pageId === activePageId);
235
+ sendMcpResult(
236
+ res,
237
+ id,
238
+ JSON.stringify(
239
+ {
240
+ url: activePage?.url ?? pc.url,
241
+ title: activePage?.title ?? pc.title,
242
+ pageId: activePageId
243
+ },
244
+ null,
245
+ 2
246
+ ),
247
+ mcp.sessionId
248
+ );
249
+ } catch (e) {
250
+ log.debug("handleGetPageContext error", { error: e.message });
251
+ sendMcpError(res, id, -32603, `MCP \u8C03\u7528\u5931\u8D25: ${e.message}`, mcp.sessionId);
252
+ }
253
+ }
254
+ async function handleNewPage(res, id, mcp, args, projectOrigins, getPageContext) {
255
+ try {
256
+ const url = args["url"];
257
+ if (typeof url !== "string" || url.trim().length === 0) {
258
+ sendMcpError(res, id, -32e3, "\u7F3A\u5C11 url \u53C2\u6570\uFF0C\u8BF7\u63D0\u4F9B\u8981\u6253\u5F00\u7684\u9875\u9762 URL", mcp.sessionId);
259
+ return;
260
+ }
261
+ if (!isProjectPage(url, projectOrigins)) {
262
+ sendMcpError(
263
+ res,
264
+ id,
265
+ -32e3,
266
+ `\u4E0D\u5141\u8BB8\u6253\u5F00\u975E\u672C\u9879\u76EE\u9875\u9762: ${url}\u3002\u4EC5\u5141\u8BB8\u6253\u5F00\u9879\u76EE\u9875\u9762 (${projectOrigins.join(", ")})`,
267
+ mcp.sessionId
268
+ );
269
+ return;
270
+ }
271
+ const projectPages = await confirmOpenProjectPages(mcp, projectOrigins, getPageContext);
272
+ if (projectPages === null) {
273
+ sendMcpError(
274
+ res,
275
+ id,
276
+ -32e3,
277
+ `\u65E0\u6CD5\u786E\u8BA4\u5F53\u524D\u9879\u76EE\u9875\u9762\u72B6\u6001\uFF0C\u4E3A\u907F\u514D\u91CD\u590D\u6253\u5F00\uFF0C\u8BF7\u786E\u8BA4 Chrome DevTools \u5DF2\u8FDE\u63A5\u540E\u91CD\u8BD5`,
278
+ mcp.sessionId
279
+ );
280
+ return;
281
+ }
282
+ if (projectPages.length > 0) {
283
+ const existing = projectPages.map((p) => `- ${p.title} (${p.url}) [pageId: ${p.pageId}]`).join("\n");
284
+ sendMcpResult(
285
+ res,
286
+ id,
287
+ `\u5F53\u524D\u9879\u76EE\u5DF2\u6709\u6253\u5F00\u7684\u9875\u9762\uFF0C\u65E0\u9700\u91CD\u590D\u6253\u5F00\u3002
288
+
289
+ \u5DF2\u6253\u5F00\u7684\u9875\u9762\uFF1A
290
+ ${existing}
291
+
292
+ \u5982\u9700\u64CD\u4F5C\u73B0\u6709\u9875\u9762\uFF0C\u8BF7\u4F7F\u7528 chrome-devtools_list_pages \u83B7\u53D6\u9875\u9762 ID\uFF0C\u518D\u914D\u5408\u5176\u4ED6 chrome-devtools_* \u5DE5\u5177\u4F7F\u7528\u3002`,
293
+ mcp.sessionId
294
+ );
295
+ return;
296
+ }
297
+ const forwardBody = JSON.stringify({
298
+ jsonrpc: "2.0",
299
+ id,
300
+ method: "tools/call",
301
+ params: { name: "new_page", arguments: { ...args, background: true } }
302
+ });
303
+ const responseText = await mcp.forward(forwardBody);
304
+ log.debug("MCP response", { tool: "new_page", response: responseText.substring(0, 100) });
305
+ sendMcpJson(res, 200, responseText, mcp.sessionId);
306
+ } catch (e) {
307
+ log.debug("handleNewPage error", { error: e.message });
308
+ sendMcpError(res, id, -32603, `MCP \u8C03\u7528\u5931\u8D25: ${e.message}`, mcp.sessionId);
309
+ }
310
+ }
311
+ async function handleDevTool(res, id, mcp, mapped, args, projectOrigins, toolName) {
312
+ try {
313
+ const pageId = args["pageId"];
314
+ if (typeof pageId !== "number") {
315
+ sendMcpError(res, id, -32e3, "\u7F3A\u5C11 pageId \u53C2\u6570\uFF0C\u8BF7\u5148\u83B7\u53D6\u9875\u9762 ID", mcp.sessionId);
316
+ return;
317
+ }
318
+ const validation = await validatePageId(mcp, pageId, projectOrigins);
319
+ log.debug("handleDevTool validation", {
320
+ pageId,
321
+ projectPages: validation.projectPages.length,
322
+ projectPageIds: validation.projectPages.map((p) => p.pageId),
323
+ origins: projectOrigins,
324
+ isValid: validation.valid
325
+ });
326
+ if (!validation.valid) {
327
+ sendMcpError(res, id, -32e3, validation.error, mcp.sessionId);
328
+ return;
329
+ }
330
+ if (toolName === "chrome-devtools_navigate_page" && args["type"] === "url") {
331
+ const targetUrl = args["url"];
332
+ if (typeof targetUrl === "string" && targetUrl.length > 0) {
333
+ if (!isProjectPage(targetUrl, projectOrigins)) {
334
+ sendMcpError(
335
+ res,
336
+ id,
337
+ -32e3,
338
+ `\u4E0D\u5141\u8BB8\u8DF3\u8F6C\u5230\u975E\u672C\u9879\u76EE\u9875\u9762: ${targetUrl}\u3002\u4EC5\u5141\u8BB8\u5BFC\u822A\u5230\u9879\u76EE\u9875\u9762 (${projectOrigins.join(", ")})`,
339
+ mcp.sessionId
340
+ );
341
+ return;
342
+ }
343
+ }
344
+ }
345
+ await mcp.callChromeDevTool("select_page", { pageId, bringToFront: false });
346
+ const forwardArgs = { ...args };
347
+ delete forwardArgs["pageId"];
348
+ const forwardBody = JSON.stringify({
349
+ jsonrpc: "2.0",
350
+ id,
351
+ method: "tools/call",
352
+ params: { name: mapped, arguments: forwardArgs }
353
+ });
354
+ const responseText = await mcp.forward(forwardBody);
355
+ log.debug("MCP response", { mapped, response: responseText.substring(0, 100) });
356
+ sendMcpJson(res, 200, responseText, mcp.sessionId);
357
+ } catch (e) {
358
+ log.debug("handleDevTool error", { error: e.message, mapped });
359
+ sendMcpError(res, id, -32603, `MCP \u8C03\u7528\u5931\u8D25: ${e.message}`, mcp.sessionId);
360
+ }
361
+ }
362
+ const VUE_DEVTOOLS_TOOL_ACTIONS = {
363
+ "vue-devtools_get_apps": VUE_DEVTOOLS_ACTIONS.GET_APPS,
364
+ "vue-devtools_set_active_app": VUE_DEVTOOLS_ACTIONS.TOGGLE_APP,
365
+ "vue-devtools_get_component_tree": VUE_DEVTOOLS_ACTIONS.GET_COMPONENT_TREE,
366
+ "vue-devtools_get_component_state": VUE_DEVTOOLS_ACTIONS.GET_COMPONENT_STATE,
367
+ "vue-devtools_get_component_render_code": VUE_DEVTOOLS_ACTIONS.GET_COMPONENT_RENDER_CODE,
368
+ "vue-devtools_get_current_route": VUE_DEVTOOLS_ACTIONS.GET_ROUTER_INFO,
369
+ "vue-devtools_get_routes": VUE_DEVTOOLS_ACTIONS.GET_ROUTER_INFO
370
+ };
371
+ async function handleVueDevtoolsTool(res, id, mcp, projectOrigins, toolName, args) {
372
+ try {
373
+ const action = VUE_DEVTOOLS_TOOL_ACTIONS[toolName];
374
+ if (!action) {
375
+ sendMcpError(res, id, -32601, `Tool not found: ${toolName}`, mcp.sessionId);
376
+ return;
377
+ }
378
+ const result = await executeAction(action, args, mcp, projectOrigins);
379
+ switch (toolName) {
380
+ case "vue-devtools_set_active_app":
381
+ sendMcpResult(res, id, `\u5DF2\u5207\u6362\u5230\u5E94\u7528 ${args["appId"]}`, mcp.sessionId);
382
+ return;
383
+ case "vue-devtools_get_current_route":
384
+ case "vue-devtools_get_routes": {
385
+ const parsed = typeof result === "string" ? parseJsonSafe(result) : result;
386
+ const data = parsed ?? {};
387
+ const value = toolName === "vue-devtools_get_current_route" ? data.currentRoute ?? null : data.routes ?? null;
388
+ sendMcpResult(res, id, JSON.stringify(value), mcp.sessionId);
389
+ return;
390
+ }
391
+ case "vue-devtools_get_component_render_code":
392
+ sendMcpResult(
393
+ res,
394
+ id,
395
+ typeof result === "string" ? result : JSON.stringify(result),
396
+ mcp.sessionId
397
+ );
398
+ return;
399
+ default:
400
+ sendMcpResult(res, id, JSON.stringify(result), mcp.sessionId);
401
+ return;
402
+ }
403
+ } catch (e) {
404
+ log.debug("handleVueDevtoolsTool error", { error: e.message });
405
+ sendMcpError(res, id, -32603, `Vue DevTools \u8C03\u7528\u5931\u8D25: ${e.message}`, mcp.sessionId);
406
+ }
407
+ }
408
+ function parseJsonSafe(text) {
409
+ try {
410
+ return JSON.parse(text);
411
+ } catch {
412
+ return text;
413
+ }
414
+ }
415
+ function handleViteLogsTool(res, id, args, sessionId) {
416
+ const { level, limit, source } = args;
417
+ const buffer = getProcessLogBuffer();
418
+ const logs = buffer.getLogs({
419
+ level: parseProcessLevelFilter(level),
420
+ limit: typeof limit === "number" && limit >= 1 ? limit : 50,
421
+ source: typeof source === "string" && source ? source : void 0
422
+ });
423
+ if (logs.length === 0) {
424
+ sendMcpResult(
425
+ res,
426
+ id,
427
+ `\u5F53\u524D\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u65E5\u5FD7\uFF08\u7F13\u51B2\u533A\u5171 ${buffer.size()} \u6761\uFF09\u3002
428
+
429
+ \u5EFA\u8BAE\uFF1A
430
+ - \u4E0D\u6307\u5B9A\u53C2\u6570\u83B7\u53D6\u6240\u6709\u65E5\u5FD7
431
+ - \u4F7F\u7528 level=error,warn \u83B7\u53D6\u9519\u8BEF\u548C\u8B66\u544A`,
432
+ sessionId
433
+ );
434
+ return;
435
+ }
436
+ const formattedLogs = logs.map((entry) => {
437
+ const time = new Date(entry.timestamp).toLocaleTimeString();
438
+ const levelIcon = entry.level === "error" ? "\u274C" : entry.level === "warn" ? "\u26A0\uFE0F" : entry.level === "info" ? "\u2139\uFE0F" : "";
439
+ return `${time} ${levelIcon} ${entry.message}`;
440
+ }).join("\n");
441
+ sendMcpResult(
442
+ res,
443
+ id,
444
+ `Vite \u5F00\u53D1\u670D\u52A1\u5668\u65E5\u5FD7\uFF08${logs.length}/${buffer.size()} \u6761\uFF09\uFF1A
445
+
446
+ ${formattedLogs}`,
447
+ sessionId
448
+ );
449
+ }
450
+ function parseProcessLevelFilter(level) {
451
+ if (typeof level !== "string" || !level.trim()) return void 0;
452
+ const levels = level.split(",").map((l) => l.trim()).filter(Boolean);
453
+ return levels.length ? levels : void 0;
454
+ }
455
+ function buildServiceLogTools(logFiles) {
456
+ return logFiles.map((cfg) => ({
457
+ name: `logs-devtools_${cfg.name}_logs`,
458
+ description: `\u83B7\u53D6 ${cfg.name} \u7684\u65E5\u5FD7\u3002
459
+
460
+ **\u4F55\u65F6\u4F7F\u7528\u6B64\u5DE5\u5177**\uFF1A
461
+ ${cfg.description}
462
+
463
+ **\u65E5\u5FD7\u5185\u5BB9**\uFF1A
464
+ - \u6765\u81EA\u65E5\u5FD7\u6587\u4EF6 ${cfg.path} \u7684\u5B9E\u65F6\u65E5\u5FD7
465
+ - \u9ED8\u8BA4\u8FD4\u56DE\u6700\u8FD1 200 \u884C\u65E5\u5FD7`,
466
+ inputSchema: {
467
+ type: "object",
468
+ properties: {
469
+ level: {
470
+ type: "string",
471
+ description: "\u65E5\u5FD7\u7EA7\u522B\u8FC7\u6EE4\uFF1Aerror(\u9519\u8BEF)\u3001warn(\u8B66\u544A)\u3001info(\u4FE1\u606F)\u3002\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF0C\u5982 'error,warn'"
472
+ },
473
+ limit: {
474
+ type: "integer",
475
+ minimum: 1,
476
+ maximum: 200,
477
+ default: 50,
478
+ description: "\u8FD4\u56DE\u6761\u6570\uFF0C\u9ED8\u8BA4 50\uFF0C\u6700\u5927 200"
479
+ }
480
+ }
481
+ }
482
+ }));
483
+ }
484
+ function isServiceLogTool(toolName, logFiles) {
485
+ return logFiles.some((cfg) => `logs-devtools_${cfg.name}_logs` === toolName);
486
+ }
487
+ async function handleServiceLogsTool(res, id, toolName, args, logFiles, projectRoot, sessionId) {
488
+ try {
489
+ const cfg = logFiles.find((c) => `logs-devtools_${c.name}_logs` === toolName);
490
+ if (!cfg) {
491
+ sendMcpError(res, id, -32601, `Tool not found: ${toolName}`, sessionId);
492
+ return;
493
+ }
494
+ const { level, limit } = args;
495
+ const requestedLimit = typeof limit === "number" ? limit : 50;
496
+ const entries = await readLogFileTail({
497
+ name: cfg.name,
498
+ filePath: cfg.path,
499
+ projectRoot,
500
+ lines: Math.max(requestedLimit * 3, 500),
501
+ level: parseFileLevelFilter(level),
502
+ limit: requestedLimit
503
+ });
504
+ if (entries.length === 0) {
505
+ sendMcpResult(
506
+ res,
507
+ id,
508
+ `\u5F53\u524D\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u65E5\u5FD7\u3002
509
+
510
+ \u5EFA\u8BAE\uFF1A
511
+ - \u4E0D\u6307\u5B9A\u53C2\u6570\u83B7\u53D6\u6240\u6709\u65E5\u5FD7
512
+ - \u4F7F\u7528 level=error,warn \u83B7\u53D6\u9519\u8BEF\u548C\u8B66\u544A`,
513
+ sessionId
514
+ );
515
+ return;
516
+ }
517
+ const formattedLogs = entries.map((entry) => {
518
+ const levelIcon = entry.level === "error" ? "\u274C" : entry.level === "warn" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
519
+ return `${levelIcon} ${entry.message}`;
520
+ }).join("\n");
521
+ sendMcpResult(
522
+ res,
523
+ id,
524
+ `${cfg.name} \u65E5\u5FD7\uFF08${entries.length} \u6761\uFF09\uFF1A
525
+
526
+ ${formattedLogs}`,
527
+ sessionId
528
+ );
529
+ } catch (e) {
530
+ log.debug("handleServiceLogsTool error", { error: e.message });
531
+ sendMcpError(res, id, -32603, `\u8BFB\u53D6\u65E5\u5FD7\u5931\u8D25: ${e.message}`, sessionId);
532
+ }
533
+ }
534
+ function parseFileLevelFilter(level) {
535
+ if (typeof level !== "string" || !level.trim()) return void 0;
536
+ const levels = level.split(",").map((l) => l.trim()).filter(Boolean);
537
+ return levels.length ? levels : void 0;
538
+ }
539
+ async function handleForward(res, body, mcp) {
540
+ try {
541
+ const responseText = await mcp.forward(body);
542
+ sendMcpJson(res, 200, responseText, mcp.sessionId);
543
+ } catch (e) {
544
+ log.debug("handleForward error", { error: e.message });
545
+ sendMcpError(res, null, -32603, `MCP \u8C03\u7528\u5931\u8D25: ${e.message}`, mcp.sessionId);
546
+ }
547
+ }
548
+ function sendMcpResult(res, id, text, sessionId) {
549
+ sendMcpJson(
550
+ res,
551
+ 200,
552
+ {
553
+ jsonrpc: "2.0",
554
+ id,
555
+ result: { content: [{ type: "text", text }] }
556
+ },
557
+ sessionId
558
+ );
559
+ }
560
+ function sendMcpError(res, id, code, message, sessionId) {
561
+ sendMcpJson(
562
+ res,
563
+ 200,
564
+ {
565
+ jsonrpc: "2.0",
566
+ id,
567
+ error: { code, message }
568
+ },
569
+ sessionId
570
+ );
571
+ }
572
+ function sendMcpJson(res, statusCode, body, sessionId) {
573
+ const headers = { "Content-Type": "application/json" };
574
+ if (sessionId) headers["Mcp-Session-Id"] = sessionId;
575
+ res.writeHead(statusCode, headers);
576
+ res.end(typeof body === "string" ? body : JSON.stringify(body));
577
+ }
578
+ function readBody(req, maxSize = 1024 * 1024) {
579
+ return new Promise((resolve, reject) => {
580
+ let body = "";
581
+ req.on("data", (chunk) => {
582
+ body += chunk.toString();
583
+ if (body.length > maxSize) {
584
+ req.destroy();
585
+ reject(new Error("Request body too large"));
586
+ }
587
+ });
588
+ req.on("end", () => resolve(body));
589
+ req.on("error", reject);
590
+ });
591
+ }
592
+ function tryParseRequest(body) {
593
+ try {
594
+ const data = JSON.parse(body);
595
+ return { method: data.method || "unknown", id: data.id ?? null };
596
+ } catch {
597
+ return { method: "unknown", id: null };
598
+ }
599
+ }
600
+ function tryParseParams(body) {
601
+ try {
602
+ return JSON.parse(body).params ?? null;
603
+ } catch {
604
+ return null;
605
+ }
606
+ }
607
+ export {
608
+ MCP_API_PATH,
609
+ setupMcpEndpoint
610
+ };
@@ -0,0 +1,3 @@
1
+ import type { ViteDevServer } from "vite";
2
+ import type { EndpointContext } from "./types";
3
+ export declare function setupSessionsEndpoint(server: ViteDevServer, ctx: EndpointContext): void;