vite-plugin-aipanel 1.2.9 → 1.2.10

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 (56) hide show
  1. package/es/core/chrome-project.d.ts +15 -0
  2. package/es/core/chrome-project.mjs +13 -0
  3. package/es/core/mcp-chrome.d.ts +12 -8
  4. package/es/core/mcp-chrome.mjs +52 -10
  5. package/es/core/mcp-proxy.d.ts +14 -1
  6. package/es/core/mcp-proxy.mjs +49 -10
  7. package/es/core/mcp-tools.d.ts +51 -3
  8. package/es/core/mcp-tools.mjs +109 -659
  9. package/es/core/official-meta.d.ts +9 -0
  10. package/es/core/official-meta.mjs +62 -0
  11. package/es/core/official-tools.d.ts +7 -0
  12. package/es/core/official-tools.mjs +54 -0
  13. package/es/core/service.mjs +4 -5
  14. package/es/endpoints/context.mjs +2 -2
  15. package/es/endpoints/host-events.mjs +1 -2
  16. package/es/endpoints/index.d.ts +2 -2
  17. package/es/endpoints/index.mjs +2 -2
  18. package/es/endpoints/mcp.d.ts +2 -2
  19. package/es/endpoints/mcp.mjs +116 -87
  20. package/es/endpoints/sse.mjs +3 -3
  21. package/es/endpoints/vue-devtools.d.ts +5 -2
  22. package/es/endpoints/vue-devtools.mjs +3 -4
  23. package/es/index.mjs +21 -4
  24. package/lib/client.js +2322 -2310
  25. package/lib/core/chrome-project.cjs +36 -0
  26. package/lib/core/chrome-project.d.ts +15 -0
  27. package/lib/core/mcp-chrome.cjs +64 -10
  28. package/lib/core/mcp-chrome.d.ts +12 -8
  29. package/lib/core/mcp-proxy.cjs +50 -10
  30. package/lib/core/mcp-proxy.d.ts +14 -1
  31. package/lib/core/mcp-tools.cjs +124 -660
  32. package/lib/core/mcp-tools.d.ts +51 -3
  33. package/lib/core/official-meta.cjs +85 -0
  34. package/lib/core/official-meta.d.ts +9 -0
  35. package/lib/core/official-tools.cjs +70 -0
  36. package/lib/core/official-tools.d.ts +7 -0
  37. package/lib/core/service.cjs +4 -5
  38. package/lib/endpoints/context.cjs +1 -1
  39. package/lib/endpoints/host-events.cjs +0 -1
  40. package/lib/endpoints/index.cjs +2 -2
  41. package/lib/endpoints/index.d.ts +2 -2
  42. package/lib/endpoints/mcp.cjs +114 -85
  43. package/lib/endpoints/mcp.d.ts +2 -2
  44. package/lib/endpoints/sse.cjs +2 -2
  45. package/lib/endpoints/vue-devtools.cjs +4 -5
  46. package/lib/endpoints/vue-devtools.d.ts +5 -2
  47. package/lib/index.cjs +22 -5
  48. package/package.json +10 -7
  49. package/es/utils/format-bridge.d.ts +0 -9
  50. package/es/utils/format-bridge.mjs +0 -87
  51. package/es/utils/system.d.ts +0 -3
  52. package/es/utils/system.mjs +0 -76
  53. package/lib/utils/format-bridge.cjs +0 -103
  54. package/lib/utils/format-bridge.d.ts +0 -9
  55. package/lib/utils/system.cjs +0 -110
  56. package/lib/utils/system.d.ts +0 -3
@@ -0,0 +1,9 @@
1
+ /** AUTO-GENERATED by packages/vite/scripts/sync-official-meta.mjs (build) — 不手改;升级 chrome-devtools-mcp 后重跑构建即可。 */
2
+ export interface OfficialToolMeta {
3
+ name: string;
4
+ category: "INPUT" | "NAVIGATION" | "EMULATION" | "PERFORMANCE" | "NETWORK" | "DEBUGGING" | "EXTENSIONS" | "THIRD_PARTY" | "MEMORY" | "WEBMCP" | "PWA" | "";
5
+ readOnlyHint: boolean;
6
+ pageScoped: boolean;
7
+ conditions: string[];
8
+ }
9
+ export declare const OFFICIAL_TOOL_META: OfficialToolMeta[];
@@ -0,0 +1,62 @@
1
+ const OFFICIAL_TOOL_META = [
2
+ { name: "click", category: "INPUT", readOnlyHint: false, pageScoped: true, conditions: [] },
3
+ { name: "click_at", category: "INPUT", readOnlyHint: false, pageScoped: true, conditions: ["experimentalVision"] },
4
+ { name: "close_heapsnapshot", category: "MEMORY", readOnlyHint: false, pageScoped: false, conditions: ["memoryDebugging"] },
5
+ { name: "close_page", category: "NAVIGATION", readOnlyHint: false, pageScoped: false, conditions: [] },
6
+ { name: "compare_heapsnapshots", category: "MEMORY", readOnlyHint: true, pageScoped: false, conditions: ["memoryDebugging"] },
7
+ { name: "drag", category: "INPUT", readOnlyHint: false, pageScoped: true, conditions: [] },
8
+ { name: "emulate", category: "EMULATION", readOnlyHint: false, pageScoped: true, conditions: [] },
9
+ { name: "evaluate_script", category: "DEBUGGING", readOnlyHint: false, pageScoped: false, conditions: [] },
10
+ { name: "execute_3p_developer_tool", category: "THIRD_PARTY", readOnlyHint: false, pageScoped: true, conditions: [] },
11
+ { name: "execute_webmcp_tool", category: "WEBMCP", readOnlyHint: false, pageScoped: true, conditions: [] },
12
+ { name: "fill", category: "INPUT", readOnlyHint: false, pageScoped: true, conditions: [] },
13
+ { name: "fill_form", category: "INPUT", readOnlyHint: false, pageScoped: true, conditions: [] },
14
+ { name: "get_console_message", category: "DEBUGGING", readOnlyHint: true, pageScoped: true, conditions: [] },
15
+ { name: "get_heapsnapshot_class_nodes", category: "MEMORY", readOnlyHint: true, pageScoped: false, conditions: ["memoryDebugging"] },
16
+ { name: "get_heapsnapshot_details", category: "MEMORY", readOnlyHint: true, pageScoped: false, conditions: ["memoryDebugging"] },
17
+ { name: "get_heapsnapshot_dominators", category: "MEMORY", readOnlyHint: true, pageScoped: false, conditions: ["memoryDebugging"] },
18
+ { name: "get_heapsnapshot_duplicate_strings", category: "MEMORY", readOnlyHint: true, pageScoped: false, conditions: ["memoryDebugging"] },
19
+ { name: "get_heapsnapshot_edges", category: "MEMORY", readOnlyHint: true, pageScoped: false, conditions: ["memoryDebugging"] },
20
+ { name: "get_heapsnapshot_object_details", category: "MEMORY", readOnlyHint: true, pageScoped: false, conditions: ["memoryDebugging"] },
21
+ { name: "get_heapsnapshot_retainers", category: "MEMORY", readOnlyHint: true, pageScoped: false, conditions: ["memoryDebugging"] },
22
+ { name: "get_heapsnapshot_retaining_paths", category: "MEMORY", readOnlyHint: true, pageScoped: false, conditions: ["memoryDebugging"] },
23
+ { name: "get_heapsnapshot_summary", category: "MEMORY", readOnlyHint: true, pageScoped: false, conditions: ["memoryDebugging"] },
24
+ { name: "get_network_request", category: "NETWORK", readOnlyHint: false, pageScoped: true, conditions: [] },
25
+ { name: "get_os_app_state", category: "PWA", readOnlyHint: true, pageScoped: false, conditions: [] },
26
+ { name: "get_tab_id", category: "NAVIGATION", readOnlyHint: true, pageScoped: true, conditions: ["experimentalInteropTools"] },
27
+ { name: "handle_dialog", category: "INPUT", readOnlyHint: false, pageScoped: true, conditions: [] },
28
+ { name: "hover", category: "INPUT", readOnlyHint: false, pageScoped: true, conditions: [] },
29
+ { name: "install_extension", category: "EXTENSIONS", readOnlyHint: false, pageScoped: false, conditions: [] },
30
+ { name: "install_pwa", category: "PWA", readOnlyHint: false, pageScoped: false, conditions: [] },
31
+ { name: "launch_pwa", category: "PWA", readOnlyHint: false, pageScoped: false, conditions: [] },
32
+ { name: "lighthouse_audit", category: "DEBUGGING", readOnlyHint: false, pageScoped: true, conditions: [] },
33
+ { name: "list_3p_developer_tools", category: "THIRD_PARTY", readOnlyHint: true, pageScoped: true, conditions: [] },
34
+ { name: "list_extensions", category: "EXTENSIONS", readOnlyHint: true, pageScoped: false, conditions: [] },
35
+ { name: "list_network_requests", category: "NETWORK", readOnlyHint: true, pageScoped: true, conditions: [] },
36
+ { name: "list_pages", category: "NAVIGATION", readOnlyHint: true, pageScoped: false, conditions: [] },
37
+ { name: "list_webmcp_tools", category: "WEBMCP", readOnlyHint: true, pageScoped: true, conditions: [] },
38
+ { name: "navigate_page", category: "NAVIGATION", readOnlyHint: false, pageScoped: true, conditions: [] },
39
+ { name: "new_page", category: "NAVIGATION", readOnlyHint: false, pageScoped: false, conditions: [] },
40
+ { name: "performance_analyze_insight", category: "PERFORMANCE", readOnlyHint: true, pageScoped: true, conditions: [] },
41
+ { name: "performance_start_trace", category: "PERFORMANCE", readOnlyHint: false, pageScoped: true, conditions: [] },
42
+ { name: "performance_stop_trace", category: "PERFORMANCE", readOnlyHint: false, pageScoped: true, conditions: [] },
43
+ { name: "press_key", category: "INPUT", readOnlyHint: false, pageScoped: true, conditions: [] },
44
+ { name: "query_heapsnapshot_objects", category: "MEMORY", readOnlyHint: true, pageScoped: false, conditions: ["memoryDebugging"] },
45
+ { name: "reload_extension", category: "EXTENSIONS", readOnlyHint: false, pageScoped: false, conditions: [] },
46
+ { name: "resize_page", category: "EMULATION", readOnlyHint: false, pageScoped: true, conditions: [] },
47
+ { name: "screencast_start", category: "DEBUGGING", readOnlyHint: false, pageScoped: true, conditions: ["experimentalScreencast"] },
48
+ { name: "screencast_stop", category: "DEBUGGING", readOnlyHint: false, pageScoped: true, conditions: ["experimentalScreencast"] },
49
+ { name: "select_page", category: "NAVIGATION", readOnlyHint: true, pageScoped: false, conditions: [] },
50
+ { name: "take_heapsnapshot", category: "MEMORY", readOnlyHint: false, pageScoped: true, conditions: [] },
51
+ { name: "take_screenshot", category: "DEBUGGING", readOnlyHint: false, pageScoped: true, conditions: [] },
52
+ { name: "take_snapshot", category: "DEBUGGING", readOnlyHint: false, pageScoped: true, conditions: [] },
53
+ { name: "trigger_extension_action", category: "EXTENSIONS", readOnlyHint: false, pageScoped: false, conditions: [] },
54
+ { name: "type_text", category: "INPUT", readOnlyHint: false, pageScoped: true, conditions: [] },
55
+ { name: "uninstall_extension", category: "EXTENSIONS", readOnlyHint: false, pageScoped: false, conditions: [] },
56
+ { name: "uninstall_pwa", category: "PWA", readOnlyHint: false, pageScoped: false, conditions: [] },
57
+ { name: "upload_file", category: "INPUT", readOnlyHint: false, pageScoped: true, conditions: [] },
58
+ { name: "wait_for", category: "NAVIGATION", readOnlyHint: true, pageScoped: true, conditions: [] }
59
+ ];
60
+ export {
61
+ OFFICIAL_TOOL_META
62
+ };
@@ -0,0 +1,7 @@
1
+ import { type CustomTool } from "./mcp-tools";
2
+ import type { McpProxy } from "./mcp-proxy";
3
+ /**
4
+ * 项目白名单的官方工具集(英文 schema/描述,已追加 pageId)。
5
+ * 官方当前版本缺失某名时跳过(名称跟随官方,不本地复制)。
6
+ */
7
+ export declare function getOfficialProjectTools(mcp: McpProxy): Promise<CustomTool[]>;
@@ -0,0 +1,54 @@
1
+ import { createLogger } from "@aipanel/core/node";
2
+ import {
3
+ MCP_PREFIX,
4
+ OFFICIAL_NO_PAGE_TOOLS,
5
+ PROJECT_DESCRIPTION_NOTES,
6
+ currentOfficialShorts,
7
+ displayToolName,
8
+ withPageIdSchema
9
+ } from "./mcp-tools.mjs";
10
+ const log = createLogger("OfficialChromeTools");
11
+ let cached = null;
12
+ async function fetchOfficialEntries(mcp) {
13
+ const res = await mcp.call("tools/list", {});
14
+ const map = /* @__PURE__ */ new Map();
15
+ for (const t of res?.result?.tools ?? []) {
16
+ const raw = String(t?.name ?? "");
17
+ const short = raw.startsWith(MCP_PREFIX) ? raw.slice(MCP_PREFIX.length) : raw;
18
+ if (!short) continue;
19
+ const schema = t?.inputSchema ?? { type: "object", properties: {} };
20
+ map.set(short, { short, description: String(t?.description ?? ""), schema });
21
+ }
22
+ return map;
23
+ }
24
+ function ensureOfficialEntries(mcp) {
25
+ if (!cached) {
26
+ cached = fetchOfficialEntries(mcp).catch((e) => {
27
+ cached = null;
28
+ log.warn("Failed to fetch official chrome-devtools-mcp tools", {
29
+ error: e instanceof Error ? e.message : String(e)
30
+ });
31
+ throw e;
32
+ });
33
+ }
34
+ return cached;
35
+ }
36
+ async function getOfficialProjectTools(mcp) {
37
+ const map = await ensureOfficialEntries(mcp);
38
+ const tools = [];
39
+ for (const short of currentOfficialShorts()) {
40
+ const entry = map.get(short);
41
+ if (!entry) {
42
+ log.warn("Requested official tool missing from server tools/list (skip)", { short });
43
+ continue;
44
+ }
45
+ const schema = OFFICIAL_NO_PAGE_TOOLS.has(short) ? entry.schema : withPageIdSchema(entry.schema);
46
+ const note = PROJECT_DESCRIPTION_NOTES[short];
47
+ const description = note ? entry.description ? `${entry.description} ${note}` : note : entry.description;
48
+ tools.push({ name: displayToolName(short), description, inputSchema: schema });
49
+ }
50
+ return tools;
51
+ }
52
+ export {
53
+ getOfficialProjectTools
54
+ };
@@ -2,9 +2,8 @@ var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { randomUUID } from "node:crypto";
5
- import { DEFAULT_PROXY_PORT, SERVER_START_TIMEOUT, ChromeMcpWarmupErrorType } from "@aipanel/core";
6
- import { createLogger, findAvailablePort } from "@aipanel/core/node";
7
- import { findGitRoot, waitForServer } from "../utils/system.mjs";
5
+ import { DEFAULT_PROXY_PORT, SERVER_START_TIMEOUT, SSE_EVENT_TYPES, ChromeMcpWarmupErrorType } from "@aipanel/core";
6
+ import { createLogger, findAvailablePort, findGitRoot, waitForServer } from "@aipanel/core/node";
8
7
  import { startProxyServer } from "./proxy-server.mjs";
9
8
  const log = createLogger("Service");
10
9
  class AIPanelService {
@@ -43,7 +42,7 @@ class AIPanelService {
43
42
  this.currentTask = { task, ...data };
44
43
  this.sseClients.forEach((client) => {
45
44
  try {
46
- client.write(`data: ${JSON.stringify({ type: "TASK_UPDATE", task, ...data })}
45
+ client.write(`data: ${JSON.stringify({ type: SSE_EVENT_TYPES.TASK_UPDATE, task, ...data })}
47
46
 
48
47
  `);
49
48
  } catch (e) {
@@ -230,7 +229,7 @@ class AIPanelService {
230
229
  pushProviderEvent(event) {
231
230
  this.sseClients.forEach((client) => {
232
231
  try {
233
- client.write(`data: ${JSON.stringify({ type: "SESSION_EVENT", event })}
232
+ client.write(`data: ${JSON.stringify({ type: SSE_EVENT_TYPES.SESSION_EVENT, event })}
234
233
 
235
234
  `);
236
235
  } catch (e) {
@@ -1,4 +1,4 @@
1
- import { CONTEXT_API_PATH } from "@aipanel/core";
1
+ import { CONTEXT_API_PATH, SSE_EVENT_TYPES } from "@aipanel/core";
2
2
  import { RequestContext, createLogger } from "@aipanel/core/node";
3
3
  import { ensureNodeId } from "@aipanel/core";
4
4
  const log = createLogger("Endpoints:Context");
@@ -28,7 +28,7 @@ function setupContextEndpoint(server, ctx) {
28
28
  let sentCount = 0;
29
29
  ctx.sseClients.forEach((client) => {
30
30
  try {
31
- client.write(`data: ${JSON.stringify({ type: "CLEAR_ELEMENTS" })}
31
+ client.write(`data: ${JSON.stringify({ type: SSE_EVENT_TYPES.CLEAR_ELEMENTS })}
32
32
 
33
33
  `);
34
34
  sentCount++;
@@ -1,6 +1,5 @@
1
1
  import { HOST_EVENTS_API_PATH } from "@aipanel/core";
2
- import { RequestContext, createLogger } from "@aipanel/core/node";
3
- const log = createLogger("Endpoints:HostEvents");
2
+ import { RequestContext } from "@aipanel/core/node";
4
3
  function setupHostEventsEndpoint(server, ctx) {
5
4
  server.middlewares.use(HOST_EVENTS_API_PATH, async (req, res) => {
6
5
  const reqCtx = new RequestContext(req.method || "GET", HOST_EVENTS_API_PATH);
@@ -3,7 +3,7 @@ import type { EndpointContext } from "./types";
3
3
  import { MCP_API_PATH } from "./mcp";
4
4
  import { VUE_DEVTOOLS_API_PATH } from "./vue-devtools";
5
5
  import type { McpProxy } from "../core/mcp-proxy";
6
- import { LOGS_API_PATH, type LogFileConfig } from "@aipanel/core";
6
+ import { LOGS_API_PATH, type ChromeProjectOptions, type LogFileConfig } from "@aipanel/core";
7
7
  export * from "./types";
8
8
  export { LOGS_API_PATH, MCP_API_PATH, VUE_DEVTOOLS_API_PATH };
9
- export declare function setupMiddlewares(server: ViteDevServer, ctx: EndpointContext, mcp?: McpProxy, logFiles?: LogFileConfig[]): void;
9
+ export declare function setupMiddlewares(server: ViteDevServer, ctx: EndpointContext, mcp?: McpProxy, logFiles?: LogFileConfig[], chromeProject?: ChromeProjectOptions): void;
@@ -10,7 +10,7 @@ import { setupMcpEndpoint, MCP_API_PATH } from "./mcp.mjs";
10
10
  import { setupVueDevtoolsEndpoint, VUE_DEVTOOLS_API_PATH } from "./vue-devtools.mjs";
11
11
  import { LOGS_API_PATH } from "@aipanel/core";
12
12
  export * from "./types.mjs";
13
- function setupMiddlewares(server, ctx, mcp, logFiles) {
13
+ function setupMiddlewares(server, ctx, mcp, logFiles, chromeProject) {
14
14
  setupWidgetEndpoints(server, ctx);
15
15
  setupContextEndpoint(server, ctx);
16
16
  setupStartEndpoint(server, ctx);
@@ -20,7 +20,7 @@ function setupMiddlewares(server, ctx, mcp, logFiles) {
20
20
  setupWarmupEndpoint(server, ctx);
21
21
  setupLogsEndpoint(server);
22
22
  if (mcp) {
23
- setupMcpEndpoint(server, mcp, () => ctx.getPageContext(), logFiles ?? []);
23
+ setupMcpEndpoint(server, mcp, () => ctx.getPageContext(), logFiles ?? [], chromeProject);
24
24
  setupVueDevtoolsEndpoint(server, mcp);
25
25
  }
26
26
  }
@@ -1,6 +1,6 @@
1
1
  import type { ViteDevServer } from "vite";
2
- import type { LogFileConfig, PageContext } from "@aipanel/core";
2
+ import type { ChromeProjectOptions, LogFileConfig, PageContext } from "@aipanel/core";
3
3
  import { MCP_API_PATH } from "@aipanel/core";
4
4
  import { McpProxy } from "../core/mcp-proxy";
5
5
  export { MCP_API_PATH };
6
- export declare function setupMcpEndpoint(server: ViteDevServer, mcp: McpProxy, getPageContext: () => PageContext, logFiles?: LogFileConfig[]): void;
6
+ export declare function setupMcpEndpoint(server: ViteDevServer, mcp: McpProxy, getPageContext: () => PageContext, logFiles?: LogFileConfig[], chromeProject?: ChromeProjectOptions): void;
@@ -1,4 +1,4 @@
1
- import { MCP_API_PATH, VUE_DEVTOOLS_ACTIONS, sleep } from "@aipanel/core";
1
+ import { MCP_API_PATH, VUE_DEVTOOLS_ACTIONS } from "@aipanel/core";
2
2
  import {
3
3
  createLogger,
4
4
  getProcessLogBuffer,
@@ -7,15 +7,17 @@ import {
7
7
  import {
8
8
  parseListPages,
9
9
  resolveChromePageId,
10
- getProjectOrigins,
10
+ isPageAllowed,
11
11
  isProjectPage,
12
12
  validatePageId
13
13
  } from "../core/mcp-chrome.mjs";
14
- import { CUSTOM_TOOLS } from "../core/mcp-tools.mjs";
14
+ import { resolveProjectScope } from "../core/chrome-project.mjs";
15
+ import { CUSTOM_TOOLS, isAllowedToolName } from "../core/mcp-tools.mjs";
16
+ import { getOfficialProjectTools } from "../core/official-tools.mjs";
15
17
  import { executeAction } from "./vue-devtools.mjs";
16
- import { findGitRoot } from "../utils/system.mjs";
18
+ import { findGitRoot } from "@aipanel/core/node";
17
19
  const log = createLogger("McpEndpoint");
18
- function setupMcpEndpoint(server, mcp, getPageContext, logFiles = []) {
20
+ function setupMcpEndpoint(server, mcp, getPageContext, logFiles = [], chromeProject) {
19
21
  const projectRoot = findGitRoot(process.cwd());
20
22
  server.middlewares.use(async (req, res, next) => {
21
23
  if (!req.url?.startsWith(MCP_API_PATH)) return next();
@@ -38,11 +40,14 @@ function setupMcpEndpoint(server, mcp, getPageContext, logFiles = []) {
38
40
  return;
39
41
  }
40
42
  if (req.method === "POST") {
43
+ const scope = resolveProjectScope(server, chromeProject);
41
44
  await handlePost(
42
45
  req,
43
46
  res,
44
47
  mcp,
45
- getProjectOrigins(server),
48
+ scope.origins,
49
+ scope.operationsOrigins,
50
+ scope.includeExtensions,
46
51
  getPageContext,
47
52
  logFiles,
48
53
  projectRoot
@@ -63,7 +68,7 @@ function handleGetSse(req, res, mcp) {
63
68
  const keepAlive = setInterval(() => res.write(":ping\n\n"), 15e3);
64
69
  req.on("close", () => clearInterval(keepAlive));
65
70
  }
66
- async function handlePost(req, res, mcp, projectOrigins, getPageContext, logFiles, projectRoot) {
71
+ async function handlePost(req, res, mcp, projectOrigins, operationsOrigins, includeExtensions, getPageContext, logFiles, projectRoot) {
67
72
  try {
68
73
  const body = await readBody(req);
69
74
  if (!body) {
@@ -75,7 +80,7 @@ async function handlePost(req, res, mcp, projectOrigins, getPageContext, logFile
75
80
  log.debug("MCP request", { method, body: body.substring(0, 150) });
76
81
  switch (method) {
77
82
  case "tools/list":
78
- return handleToolsList(res, id, mcp.sessionId, logFiles);
83
+ return await handleToolsList(res, id, mcp, logFiles);
79
84
  case "tools/call":
80
85
  return await handleToolsCall(
81
86
  res,
@@ -83,6 +88,8 @@ async function handlePost(req, res, mcp, projectOrigins, getPageContext, logFile
83
88
  body,
84
89
  mcp,
85
90
  projectOrigins,
91
+ operationsOrigins,
92
+ includeExtensions,
86
93
  getPageContext,
87
94
  logFiles,
88
95
  projectRoot
@@ -98,16 +105,24 @@ async function handlePost(req, res, mcp, projectOrigins, getPageContext, logFile
98
105
  });
99
106
  }
100
107
  }
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);
108
+ async function handleToolsList(res, id, mcp, logFiles) {
109
+ let official = [];
110
+ try {
111
+ official = await getOfficialProjectTools(mcp);
112
+ } catch (e) {
113
+ log.warn("official chrome tools unavailable for tools/list", {
114
+ error: e instanceof Error ? e.message : String(e)
115
+ });
116
+ }
117
+ const tools = [...official, ...CUSTOM_TOOLS, ...buildServiceLogTools(logFiles)];
118
+ sendMcpJson(res, 200, { jsonrpc: "2.0", id, result: { tools } }, mcp.sessionId);
104
119
  }
105
- function handleToolsCall(res, id, body, mcp, projectOrigins, getPageContext, logFiles, projectRoot) {
120
+ function handleToolsCall(res, id, body, mcp, projectOrigins, operationsOrigins, includeExtensions, getPageContext, logFiles, projectRoot) {
106
121
  const params = tryParseParams(body);
107
122
  const toolName = params?.name;
108
123
  const args = params?.arguments ?? {};
109
124
  if (toolName === "chrome-devtools_current_page") {
110
- return handleGetPageContext(res, id, mcp, projectOrigins, getPageContext);
125
+ return handleGetPageContext(res, id, mcp, operationsOrigins, includeExtensions, getPageContext);
111
126
  }
112
127
  if (toolName?.startsWith("vue-devtools_")) {
113
128
  return handleVueDevtoolsTool(res, id, mcp, projectOrigins, toolName, args);
@@ -121,25 +136,40 @@ function handleToolsCall(res, id, body, mcp, projectOrigins, getPageContext, log
121
136
  if (toolName?.startsWith("chrome-devtools_")) {
122
137
  const mapped = toolName.slice("chrome-devtools_".length);
123
138
  if (toolName === "chrome-devtools_list_pages") {
124
- return handleListPages(res, id, mcp, projectOrigins, getPageContext);
139
+ return handleListPages(res, id, mcp, operationsOrigins, includeExtensions, getPageContext);
125
140
  }
126
141
  if (toolName === "chrome-devtools_new_page") {
127
- return handleNewPage(res, id, mcp, args, projectOrigins, getPageContext);
142
+ return handleNewPage(res, id, mcp, args, projectOrigins, operationsOrigins);
128
143
  }
129
- return handleDevTool(res, id, mcp, mapped, args, projectOrigins, toolName);
144
+ if (!isAllowedToolName(toolName)) {
145
+ return sendMcpError(res, id, -32601, `Tool not found: ${toolName}`, mcp.sessionId);
146
+ }
147
+ return handleDevTool(
148
+ res,
149
+ id,
150
+ mcp,
151
+ mapped,
152
+ args,
153
+ operationsOrigins,
154
+ includeExtensions,
155
+ projectRoot,
156
+ toolName
157
+ );
130
158
  }
131
159
  sendMcpError(res, id, -32601, `Tool not found: ${toolName}`, mcp.sessionId);
132
160
  }
133
- async function resolveProjectPages(mcp, projectOrigins, getPageContext) {
161
+ async function resolveProjectPages(mcp, operationsOrigins, includeExtensions, getPageContext) {
134
162
  const listResult = await mcp.callChromeDevTool("list_pages", {});
135
163
  if (listResult?.error || !listResult?.result) {
136
164
  throw new Error(listResult?.error?.message ?? "\u65E0\u6CD5\u83B7\u53D6 Chrome \u9875\u9762\u5217\u8868");
137
165
  }
138
166
  const text = listResult?.result?.content?.[0]?.text;
139
167
  const allPages = text ? parseListPages(text) : [];
140
- const filtered = allPages.filter((p) => isProjectPage(p.url, projectOrigins));
168
+ const filtered = allPages.filter(
169
+ (p) => isPageAllowed(p.url, operationsOrigins, includeExtensions)
170
+ );
141
171
  log.debug("Chrome pages", { total: allPages.length, urls: allPages.map((p) => p.url) });
142
- log.debug("project origins", { origins: projectOrigins });
172
+ log.debug("operations origins", { origins: operationsOrigins });
143
173
  log.debug("filtered pages", { count: filtered.length, pageIds: filtered.map((p) => p.pageId) });
144
174
  let activePageId = null;
145
175
  let activePageError;
@@ -150,7 +180,7 @@ async function resolveProjectPages(mcp, projectOrigins, getPageContext) {
150
180
  mcp,
151
181
  pc.url,
152
182
  pc.title,
153
- projectOrigins,
183
+ operationsOrigins,
154
184
  pc.sessionId,
155
185
  filtered,
156
186
  chromeSelectedPageId
@@ -160,37 +190,21 @@ async function resolveProjectPages(mcp, projectOrigins, getPageContext) {
160
190
  }
161
191
  return { filtered, activePageId, activePageError };
162
192
  }
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) {
193
+ async function handleListPages(res, id, mcp, operationsOrigins, includeExtensions, getPageContext) {
186
194
  try {
187
195
  const { filtered, activePageId } = await resolveProjectPages(
188
196
  mcp,
189
- projectOrigins,
197
+ operationsOrigins,
198
+ includeExtensions,
190
199
  getPageContext
191
200
  );
192
201
  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);
202
+ sendMcpResult(
203
+ res,
204
+ id,
205
+ "\u6682\u65E0\u53EF\u7528\u9875\u9762\uFF08\u9879\u76EE\u9875\u6216 allowOrigins \u767D\u540D\u5355\u9875\uFF09\uFF0C\u8BF7\u5148\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u76F8\u5E94\u9875\u9762",
206
+ mcp.sessionId
207
+ );
194
208
  return;
195
209
  }
196
210
  const pageList = filtered.map((p) => ({
@@ -206,15 +220,21 @@ async function handleListPages(res, id, mcp, projectOrigins, getPageContext) {
206
220
  sendMcpError(res, id, -32603, `MCP \u8C03\u7528\u5931\u8D25: ${e.message}`, mcp.sessionId);
207
221
  }
208
222
  }
209
- async function handleGetPageContext(res, id, mcp, projectOrigins, getPageContext) {
223
+ async function handleGetPageContext(res, id, mcp, operationsOrigins, includeExtensions, getPageContext) {
210
224
  try {
211
225
  const { filtered, activePageId, activePageError } = await resolveProjectPages(
212
226
  mcp,
213
- projectOrigins,
227
+ operationsOrigins,
228
+ includeExtensions,
214
229
  getPageContext
215
230
  );
216
231
  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);
232
+ sendMcpResult(
233
+ res,
234
+ id,
235
+ "\u6682\u65E0\u53EF\u7528\u9875\u9762\uFF08\u9879\u76EE\u9875\u6216 allowOrigins \u767D\u540D\u5355\u9875\uFF09\uFF0C\u8BF7\u5148\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u76F8\u5E94\u9875\u9762",
236
+ mcp.sessionId
237
+ );
218
238
  return;
219
239
  }
220
240
  const pc = getPageContext();
@@ -251,48 +271,54 @@ async function handleGetPageContext(res, id, mcp, projectOrigins, getPageContext
251
271
  sendMcpError(res, id, -32603, `MCP \u8C03\u7528\u5931\u8D25: ${e.message}`, mcp.sessionId);
252
272
  }
253
273
  }
254
- async function handleNewPage(res, id, mcp, args, projectOrigins, getPageContext) {
274
+ async function handleNewPage(res, id, mcp, args, projectOrigins, operationsOrigins) {
255
275
  try {
256
276
  const url = args["url"];
257
277
  if (typeof url !== "string" || url.trim().length === 0) {
258
278
  sendMcpError(res, id, -32e3, "\u7F3A\u5C11 url \u53C2\u6570\uFF0C\u8BF7\u63D0\u4F9B\u8981\u6253\u5F00\u7684\u9875\u9762 URL", mcp.sessionId);
259
279
  return;
260
280
  }
261
- if (!isProjectPage(url, projectOrigins)) {
281
+ if (!isProjectPage(url, operationsOrigins)) {
262
282
  sendMcpError(
263
283
  res,
264
284
  id,
265
285
  -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(", ")})`,
286
+ `\u4E0D\u5141\u8BB8\u6253\u5F00\u8BE5\u9875\u9762: ${url}\u3002\u4EC5\u5141\u8BB8\u9879\u76EE\u9875\u6216 allowOrigins \u767D\u540D\u5355\u9875 (${operationsOrigins.join(", ")})`,
267
287
  mcp.sessionId
268
288
  );
269
289
  return;
270
290
  }
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
291
+ if (isProjectPage(url, projectOrigins)) {
292
+ const listResult = await mcp.callChromeDevTool("list_pages", {});
293
+ if (listResult?.error || !listResult?.result) {
294
+ sendMcpError(
295
+ res,
296
+ id,
297
+ -32e3,
298
+ `\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`,
299
+ mcp.sessionId
300
+ );
301
+ return;
302
+ }
303
+ const text = listResult?.result?.content?.[0]?.text;
304
+ const projectOpen = (text ? parseListPages(text) : []).filter(
305
+ (p) => isProjectPage(p.url, projectOrigins)
279
306
  );
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
307
+ if (projectOpen.length > 0) {
308
+ const existing = projectOpen.map((p) => `- ${p.title} (${p.url}) [pageId: ${p.pageId}]`).join("\n");
309
+ sendMcpResult(
310
+ res,
311
+ id,
312
+ `\u5F53\u524D\u9879\u76EE\u5DF2\u6709\u6253\u5F00\u7684\u9875\u9762\uFF0C\u65E0\u9700\u91CD\u590D\u6253\u5F00\u3002
288
313
 
289
314
  \u5DF2\u6253\u5F00\u7684\u9875\u9762\uFF1A
290
315
  ${existing}
291
316
 
292
317
  \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;
318
+ mcp.sessionId
319
+ );
320
+ return;
321
+ }
296
322
  }
297
323
  const forwardBody = JSON.stringify({
298
324
  jsonrpc: "2.0",
@@ -308,37 +334,40 @@ ${existing}
308
334
  sendMcpError(res, id, -32603, `MCP \u8C03\u7528\u5931\u8D25: ${e.message}`, mcp.sessionId);
309
335
  }
310
336
  }
311
- async function handleDevTool(res, id, mcp, mapped, args, projectOrigins, toolName) {
337
+ async function handleDevTool(res, id, mcp, mapped, args, operationsOrigins, includeExtensions, projectRoot, toolName) {
312
338
  try {
313
339
  const pageId = args["pageId"];
314
340
  if (typeof pageId !== "number") {
315
341
  sendMcpError(res, id, -32e3, "\u7F3A\u5C11 pageId \u53C2\u6570\uFF0C\u8BF7\u5148\u83B7\u53D6\u9875\u9762 ID", mcp.sessionId);
316
342
  return;
317
343
  }
318
- const validation = await validatePageId(mcp, pageId, projectOrigins);
344
+ const validation = await validatePageId(mcp, pageId, operationsOrigins, includeExtensions);
319
345
  log.debug("handleDevTool validation", {
320
346
  pageId,
321
- projectPages: validation.projectPages.length,
322
- projectPageIds: validation.projectPages.map((p) => p.pageId),
323
- origins: projectOrigins,
347
+ allowedPages: validation.allowedPages.length,
348
+ allowedPageIds: validation.allowedPages.map((p) => p.pageId),
349
+ origins: operationsOrigins,
324
350
  isValid: validation.valid
325
351
  });
326
352
  if (!validation.valid) {
327
353
  sendMcpError(res, id, -32e3, validation.error, mcp.sessionId);
328
354
  return;
329
355
  }
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;
356
+ if (toolName === "chrome-devtools_navigate_page") {
357
+ const navType = typeof args["type"] === "string" ? args["type"] : "url";
358
+ if (navType === "url") {
359
+ const targetUrl = args["url"];
360
+ if (typeof targetUrl === "string" && targetUrl.length > 0) {
361
+ if (!isProjectPage(targetUrl, operationsOrigins)) {
362
+ sendMcpError(
363
+ res,
364
+ id,
365
+ -32e3,
366
+ `\u4E0D\u5141\u8BB8\u8DF3\u8F6C\u5230\u8BE5\u9875\u9762: ${targetUrl}\u3002\u4EC5\u5141\u8BB8\u5BFC\u822A\u5230\u9879\u76EE\u9875\u6216 allowOrigins \u767D\u540D\u5355\u9875 (${operationsOrigins.join(", ")})`,
367
+ mcp.sessionId
368
+ );
369
+ return;
370
+ }
342
371
  }
343
372
  }
344
373
  }
@@ -1,4 +1,4 @@
1
- import { SSE_EVENTS_PATH } from "@aipanel/core";
1
+ import { SSE_EVENT_TYPES, SSE_EVENTS_PATH } from "@aipanel/core";
2
2
  import { RequestContext, createLogger } from "@aipanel/core/node";
3
3
  const log = createLogger("Endpoints:SSE");
4
4
  function setupSseEndpoint(server, ctx) {
@@ -12,10 +12,10 @@ function setupSseEndpoint(server, ctx) {
12
12
  });
13
13
  ctx.sseClients.add(res);
14
14
  log.debug("SSE client connected", { totalClients: ctx.sseClients.size });
15
- res.write(`data: ${JSON.stringify({ type: "CONNECTED" })}
15
+ res.write(`data: ${JSON.stringify({ type: SSE_EVENT_TYPES.CONNECTED })}
16
16
 
17
17
  `);
18
- const statusPayload = { type: "STATUS_SYNC" };
18
+ const statusPayload = { type: SSE_EVENT_TYPES.STATUS_SYNC };
19
19
  if (ctx.isServiceStarted !== void 0) {
20
20
  statusPayload.isStarted = ctx.isServiceStarted;
21
21
  }
@@ -4,6 +4,9 @@
4
4
  */
5
5
  import type { ViteDevServer } from "vite";
6
6
  import type { McpProxy } from "../core/mcp-proxy";
7
- export declare const VUE_DEVTOOLS_API_PATH = "/__aipanel_vue_devtools__";
7
+ import { VUE_DEVTOOLS_API_PATH } from "@aipanel/core";
8
+ export { VUE_DEVTOOLS_API_PATH };
8
9
  export declare function setupVueDevtoolsEndpoint(server: ViteDevServer, mcp: McpProxy): void;
9
- export declare function executeAction(action: string, args: Record<string, unknown> | undefined, mcp: McpProxy, projectOrigins: string[]): Promise<unknown>;
10
+ export declare function executeAction(action: string, args: Record<string, unknown> | undefined, mcp: McpProxy,
11
+ /** vue-devtools 桥只注入在项目页面,范围为自动项目页 origins */
12
+ projectOrigins: string[]): Promise<unknown>;