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