rsbuild-plugin-vue-mcp 0.1.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/LICENSE +27 -0
- package/README.md +273 -0
- package/README_zh.md +264 -0
- package/dist/551.js +287 -0
- package/dist/index.cjs +428 -0
- package/dist/index.js +101 -0
- package/dist/overlay-bootstrap.js +1 -0
- package/dist/overlay.js +117 -0
- package/dist/rspack.cjs +446 -0
- package/dist/rspack.js +115 -0
- package/dist/types/core/dev-rpc.d.ts +3 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/mcp/connect.d.ts +5 -0
- package/dist/types/mcp/context.d.ts +2 -0
- package/dist/types/mcp/rpc.d.ts +2 -0
- package/dist/types/mcp/server.d.ts +5 -0
- package/dist/types/rspack.d.ts +10 -0
- package/dist/types/types.d.ts +77 -0
- package/package.json +84 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const __rslib_import_meta_url__ = /*#__PURE__*/ function() {
|
|
3
|
+
return "u" < typeof document ? new (require('url'.replace('', ''))).URL('file:' + __filename).href : document.currentScript && document.currentScript.src || new URL('main.js', document.baseURI).href;
|
|
4
|
+
}();
|
|
5
|
+
var __webpack_require__ = {};
|
|
6
|
+
(()=>{
|
|
7
|
+
__webpack_require__.n = (module)=>{
|
|
8
|
+
var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
|
|
9
|
+
__webpack_require__.d(getter, {
|
|
10
|
+
a: getter
|
|
11
|
+
});
|
|
12
|
+
return getter;
|
|
13
|
+
};
|
|
14
|
+
})();
|
|
15
|
+
(()=>{
|
|
16
|
+
__webpack_require__.d = (exports1, getters, values)=>{
|
|
17
|
+
var define = (defs, kind)=>{
|
|
18
|
+
for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
[kind]: defs[key]
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
define(getters, "get");
|
|
24
|
+
define(values, "value");
|
|
25
|
+
};
|
|
26
|
+
})();
|
|
27
|
+
(()=>{
|
|
28
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
29
|
+
})();
|
|
30
|
+
(()=>{
|
|
31
|
+
__webpack_require__.r = (exports1)=>{
|
|
32
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
33
|
+
value: 'Module'
|
|
34
|
+
});
|
|
35
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
36
|
+
value: true
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
var __webpack_exports__ = {};
|
|
41
|
+
__webpack_require__.r(__webpack_exports__);
|
|
42
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
43
|
+
pluginVueMcp: ()=>pluginVueMcp
|
|
44
|
+
});
|
|
45
|
+
const external_node_url_namespaceObject = require("node:url");
|
|
46
|
+
const external_node_path_namespaceObject = require("node:path");
|
|
47
|
+
var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
|
|
48
|
+
const promises_namespaceObject = require("node:fs/promises");
|
|
49
|
+
var promises_default = /*#__PURE__*/ __webpack_require__.n(promises_namespaceObject);
|
|
50
|
+
const external_ws_namespaceObject = require("ws");
|
|
51
|
+
const external_hookable_namespaceObject = require("hookable");
|
|
52
|
+
function createVueMcpContext() {
|
|
53
|
+
return {
|
|
54
|
+
hooks: (0, external_hookable_namespaceObject.createHooks)(),
|
|
55
|
+
rpc: null,
|
|
56
|
+
rpcServer: null
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function createServerRpcFunc(ctx) {
|
|
60
|
+
return {
|
|
61
|
+
getInspectorTree: (_)=>({}),
|
|
62
|
+
onInspectorTreeUpdated: (event, data)=>{
|
|
63
|
+
ctx.hooks.callHook(event, data).finally();
|
|
64
|
+
},
|
|
65
|
+
getInspectorState: (_)=>({}),
|
|
66
|
+
onInspectorStateUpdated: (event, data)=>{
|
|
67
|
+
ctx.hooks.callHook(event, data).finally();
|
|
68
|
+
},
|
|
69
|
+
editComponentState (_) {},
|
|
70
|
+
highlightComponent (_) {},
|
|
71
|
+
getRouterInfo: (_)=>({}),
|
|
72
|
+
onRouterInfoUpdated: (event, data)=>{
|
|
73
|
+
ctx.hooks.callHook(event, data).finally();
|
|
74
|
+
},
|
|
75
|
+
getPiniaTree: (_)=>({}),
|
|
76
|
+
onPiniaTreeUpdated: (event, data)=>{
|
|
77
|
+
ctx.hooks.callHook(event, data).finally();
|
|
78
|
+
},
|
|
79
|
+
getPiniaState: (_)=>({}),
|
|
80
|
+
onPiniaInfoUpdated: (event, data)=>{
|
|
81
|
+
ctx.hooks.callHook(event, data).finally();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const external_birpc_namespaceObject = require("birpc");
|
|
86
|
+
const channelCache = new WeakMap();
|
|
87
|
+
function createRPCServer(ws, functions, options = {}) {
|
|
88
|
+
const group = (0, external_birpc_namespaceObject.createBirpcGroup)(functions, ()=>Array.from(ws?.clients || []).map((channel)=>{
|
|
89
|
+
if (channel.readyState === channel.CLOSED) return;
|
|
90
|
+
const cached = channelCache.get(channel);
|
|
91
|
+
if (cached) return cached;
|
|
92
|
+
const options = {
|
|
93
|
+
on: (fn)=>channel.on('message', fn),
|
|
94
|
+
post: (data)=>channel.send(data),
|
|
95
|
+
serialize: (v)=>JSON.stringify(v),
|
|
96
|
+
deserialize: (v)=>JSON.parse(v)
|
|
97
|
+
};
|
|
98
|
+
channelCache.set(channel, options);
|
|
99
|
+
return options;
|
|
100
|
+
}).filter((c)=>!!c), options);
|
|
101
|
+
ws.on('connection', ()=>{
|
|
102
|
+
group.updateChannels();
|
|
103
|
+
});
|
|
104
|
+
return group.broadcast;
|
|
105
|
+
}
|
|
106
|
+
const mcp_js_namespaceObject = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
107
|
+
var package_namespaceObject = {
|
|
108
|
+
rE: "0.1.0"
|
|
109
|
+
};
|
|
110
|
+
const external_nanoid_namespaceObject = require("nanoid");
|
|
111
|
+
const external_zod_namespaceObject = require("zod");
|
|
112
|
+
function stringify(value) {
|
|
113
|
+
return "string" == typeof value ? value : JSON.stringify(value);
|
|
114
|
+
}
|
|
115
|
+
function createMcpServerDefault(options, ctx) {
|
|
116
|
+
const server = new mcp_js_namespaceObject.McpServer({
|
|
117
|
+
name: "rsbuild-vue-mcp",
|
|
118
|
+
description: 'Rsbuild/Rspack MCP plugin based on Vue DevTools',
|
|
119
|
+
version: package_namespaceObject.rE,
|
|
120
|
+
...options.mcpServerInfo
|
|
121
|
+
});
|
|
122
|
+
server.registerTool('get-component-tree', {
|
|
123
|
+
description: 'Get the Vue component tree in markdown tree syntax format.'
|
|
124
|
+
}, async ()=>new Promise((resolve, reject)=>{
|
|
125
|
+
const eventName = (0, external_nanoid_namespaceObject.nanoid)();
|
|
126
|
+
ctx.hooks.hookOnce(eventName, (res)=>{
|
|
127
|
+
resolve({
|
|
128
|
+
content: [
|
|
129
|
+
{
|
|
130
|
+
type: 'text',
|
|
131
|
+
text: stringify(res)
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
ctx.rpcServer.getInspectorTree({
|
|
137
|
+
event: eventName
|
|
138
|
+
}).catch((e)=>{
|
|
139
|
+
console.error(e);
|
|
140
|
+
reject(e);
|
|
141
|
+
});
|
|
142
|
+
}));
|
|
143
|
+
server.registerTool('get-component-state', {
|
|
144
|
+
description: 'Get the Vue component state in JSON structure format.',
|
|
145
|
+
inputSchema: {
|
|
146
|
+
componentName: external_zod_namespaceObject.z.string()
|
|
147
|
+
}
|
|
148
|
+
}, async ({ componentName })=>new Promise((resolve, reject)=>{
|
|
149
|
+
const eventName = (0, external_nanoid_namespaceObject.nanoid)();
|
|
150
|
+
ctx.hooks.hookOnce(eventName, (res)=>{
|
|
151
|
+
resolve({
|
|
152
|
+
content: [
|
|
153
|
+
{
|
|
154
|
+
type: 'text',
|
|
155
|
+
text: stringify(res)
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
ctx.rpcServer.getInspectorState({
|
|
161
|
+
event: eventName,
|
|
162
|
+
componentName
|
|
163
|
+
}).catch((e)=>{
|
|
164
|
+
console.error(e);
|
|
165
|
+
reject(e);
|
|
166
|
+
});
|
|
167
|
+
}));
|
|
168
|
+
server.registerTool('edit-component-state', {
|
|
169
|
+
description: 'Edit the Vue component state.',
|
|
170
|
+
inputSchema: {
|
|
171
|
+
componentName: external_zod_namespaceObject.z.string(),
|
|
172
|
+
path: external_zod_namespaceObject.z.array(external_zod_namespaceObject.z.string()),
|
|
173
|
+
value: external_zod_namespaceObject.z.string(),
|
|
174
|
+
valueType: external_zod_namespaceObject.z["enum"]([
|
|
175
|
+
'string',
|
|
176
|
+
'number',
|
|
177
|
+
'boolean',
|
|
178
|
+
'object',
|
|
179
|
+
'array'
|
|
180
|
+
])
|
|
181
|
+
}
|
|
182
|
+
}, async ({ componentName, path, value, valueType })=>new Promise((resolve, reject)=>{
|
|
183
|
+
ctx.rpcServer.editComponentState({
|
|
184
|
+
componentName,
|
|
185
|
+
path,
|
|
186
|
+
value,
|
|
187
|
+
valueType
|
|
188
|
+
}).then(()=>{
|
|
189
|
+
resolve({
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: 'text',
|
|
193
|
+
text: 'ok'
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
});
|
|
197
|
+
}).catch((e)=>{
|
|
198
|
+
console.error(e);
|
|
199
|
+
reject(e);
|
|
200
|
+
});
|
|
201
|
+
}));
|
|
202
|
+
server.registerTool('highlight-component', {
|
|
203
|
+
description: 'Highlight the Vue component.',
|
|
204
|
+
inputSchema: {
|
|
205
|
+
componentName: external_zod_namespaceObject.z.string()
|
|
206
|
+
}
|
|
207
|
+
}, async ({ componentName })=>new Promise((resolve, reject)=>{
|
|
208
|
+
ctx.rpcServer.highlightComponent({
|
|
209
|
+
componentName
|
|
210
|
+
}).then(()=>{
|
|
211
|
+
resolve({
|
|
212
|
+
content: [
|
|
213
|
+
{
|
|
214
|
+
type: 'text',
|
|
215
|
+
text: 'ok'
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
});
|
|
219
|
+
}).catch((e)=>{
|
|
220
|
+
console.error(e);
|
|
221
|
+
reject(e);
|
|
222
|
+
});
|
|
223
|
+
}));
|
|
224
|
+
server.registerTool('get-router-info', {
|
|
225
|
+
description: 'Get the Vue router info in JSON structure format.'
|
|
226
|
+
}, async ()=>new Promise((resolve, reject)=>{
|
|
227
|
+
const eventName = (0, external_nanoid_namespaceObject.nanoid)();
|
|
228
|
+
ctx.hooks.hookOnce(eventName, (res)=>{
|
|
229
|
+
resolve({
|
|
230
|
+
content: [
|
|
231
|
+
{
|
|
232
|
+
type: 'text',
|
|
233
|
+
text: stringify(res)
|
|
234
|
+
}
|
|
235
|
+
]
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
ctx.rpcServer.getRouterInfo({
|
|
239
|
+
event: eventName
|
|
240
|
+
}).catch((e)=>{
|
|
241
|
+
console.error(e);
|
|
242
|
+
reject(e);
|
|
243
|
+
});
|
|
244
|
+
}));
|
|
245
|
+
server.registerTool('get-pinia-state', {
|
|
246
|
+
description: 'Get the Pinia state in JSON structure format.',
|
|
247
|
+
inputSchema: {
|
|
248
|
+
storeName: external_zod_namespaceObject.z.string()
|
|
249
|
+
}
|
|
250
|
+
}, async ({ storeName })=>new Promise((resolve, reject)=>{
|
|
251
|
+
const eventName = (0, external_nanoid_namespaceObject.nanoid)();
|
|
252
|
+
ctx.hooks.hookOnce(eventName, (res)=>{
|
|
253
|
+
resolve({
|
|
254
|
+
content: [
|
|
255
|
+
{
|
|
256
|
+
type: 'text',
|
|
257
|
+
text: stringify(res)
|
|
258
|
+
}
|
|
259
|
+
]
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
ctx.rpcServer.getPiniaState({
|
|
263
|
+
event: eventName,
|
|
264
|
+
storeName
|
|
265
|
+
}).catch((e)=>{
|
|
266
|
+
console.error(e);
|
|
267
|
+
reject(e);
|
|
268
|
+
});
|
|
269
|
+
}));
|
|
270
|
+
server.registerTool('get-pinia-tree', {
|
|
271
|
+
description: 'Get the Pinia tree in JSON structure format.'
|
|
272
|
+
}, async ()=>new Promise((resolve, reject)=>{
|
|
273
|
+
const eventName = (0, external_nanoid_namespaceObject.nanoid)();
|
|
274
|
+
ctx.hooks.hookOnce(eventName, (res)=>{
|
|
275
|
+
resolve({
|
|
276
|
+
content: [
|
|
277
|
+
{
|
|
278
|
+
type: 'text',
|
|
279
|
+
text: stringify(res)
|
|
280
|
+
}
|
|
281
|
+
]
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
ctx.rpcServer.getPiniaTree({
|
|
285
|
+
event: eventName
|
|
286
|
+
}).catch((e)=>{
|
|
287
|
+
console.error(e);
|
|
288
|
+
reject(e);
|
|
289
|
+
});
|
|
290
|
+
}));
|
|
291
|
+
return server;
|
|
292
|
+
}
|
|
293
|
+
const streamableHttp_js_namespaceObject = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
294
|
+
async function mcpHandlerStateless(server, req, res) {
|
|
295
|
+
try {
|
|
296
|
+
const transport = new streamableHttp_js_namespaceObject.StreamableHTTPServerTransport({
|
|
297
|
+
sessionIdGenerator: void 0
|
|
298
|
+
});
|
|
299
|
+
await server.connect(transport);
|
|
300
|
+
await transport.handleRequest(req, res);
|
|
301
|
+
res.on('close', ()=>{
|
|
302
|
+
transport.close();
|
|
303
|
+
server.close();
|
|
304
|
+
});
|
|
305
|
+
} catch (error) {
|
|
306
|
+
console.error('Error handling MCP request:', error);
|
|
307
|
+
if (!res.headersSent) {
|
|
308
|
+
res.statusCode = 500;
|
|
309
|
+
res.setHeader('content-type', 'application/json');
|
|
310
|
+
res.end(JSON.stringify({
|
|
311
|
+
jsonrpc: '2.0',
|
|
312
|
+
error: {
|
|
313
|
+
code: -32603,
|
|
314
|
+
message: 'Internal server error'
|
|
315
|
+
},
|
|
316
|
+
id: null
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function setupRoutes(base, getServer, dev) {
|
|
322
|
+
dev.middlewares.use(`${base}/mcp`, async (req, res)=>{
|
|
323
|
+
const server = await getServer();
|
|
324
|
+
await mcpHandlerStateless(server, req, res);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
function getVueMcpPath() {
|
|
328
|
+
const pluginPath = external_node_path_default().dirname((0, external_node_url_namespaceObject.fileURLToPath)(__rslib_import_meta_url__));
|
|
329
|
+
return pluginPath.replaceAll('\\', '/');
|
|
330
|
+
}
|
|
331
|
+
function getOverlayBootstrapPath() {
|
|
332
|
+
const fileUrl = new URL('./overlay-bootstrap.js', __rslib_import_meta_url__);
|
|
333
|
+
return (0, external_node_url_namespaceObject.fileURLToPath)(fileUrl).replaceAll('\\', '/').replace('/src/', '/dist/');
|
|
334
|
+
}
|
|
335
|
+
const pluginVueMcp = (options = {})=>({
|
|
336
|
+
name: 'plugin-vue-mcp',
|
|
337
|
+
enforce: 'pre',
|
|
338
|
+
apply: 'serve',
|
|
339
|
+
async setup (api) {
|
|
340
|
+
const ctx = createVueMcpContext();
|
|
341
|
+
const vueMcpPath = getVueMcpPath();
|
|
342
|
+
const wsPath = '/__vue-devtools-mcp-ws';
|
|
343
|
+
if (options.appendTo) {
|
|
344
|
+
let firstInjected = false;
|
|
345
|
+
const reg = new RegExp(options.appendTo);
|
|
346
|
+
api.transform({
|
|
347
|
+
test: {
|
|
348
|
+
and: [
|
|
349
|
+
/[\\/]src[\\/].+\.(js|jsx|mjs|ts|tsx|mts)$/,
|
|
350
|
+
reg
|
|
351
|
+
],
|
|
352
|
+
not: /[\\/]node_modules[\\/]/
|
|
353
|
+
},
|
|
354
|
+
targets: [
|
|
355
|
+
'web'
|
|
356
|
+
],
|
|
357
|
+
order: 'pre'
|
|
358
|
+
}, (context)=>{
|
|
359
|
+
if ('dev' !== api.context.action || firstInjected) return context;
|
|
360
|
+
let code = context.code;
|
|
361
|
+
if ('string' != typeof code) return context;
|
|
362
|
+
firstInjected = true;
|
|
363
|
+
console.log(' ➜ VueDevtools MCP: injection `overlay.js` to:', context.resource);
|
|
364
|
+
return `${code}\n import '${vueMcpPath}/overlay.js';\n`;
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
if (!options.appendTo) api.modifyHTMLTags(async (tags)=>{
|
|
368
|
+
const overlayPath = getOverlayBootstrapPath();
|
|
369
|
+
try {
|
|
370
|
+
const js = await promises_default().readFile(overlayPath, {
|
|
371
|
+
encoding: 'utf8'
|
|
372
|
+
});
|
|
373
|
+
tags.headTags.push({
|
|
374
|
+
tag: "script",
|
|
375
|
+
attrs: {
|
|
376
|
+
id: 'overlay-bootstrap.js'
|
|
377
|
+
},
|
|
378
|
+
children: js
|
|
379
|
+
});
|
|
380
|
+
console.log(' ➜ VueDevtools MCP: injection html => overlay-bootstrap.js');
|
|
381
|
+
} catch (e) {}
|
|
382
|
+
return tags;
|
|
383
|
+
});
|
|
384
|
+
api.onBeforeStartDevServer(async ({ server })=>{
|
|
385
|
+
if (!server) return void console.error('❌ [vue-devtools-mcp] no rebuild devServer, requires Rsbuild >= 1.2.9');
|
|
386
|
+
const httpServer = server.httpServer;
|
|
387
|
+
if (!httpServer) return void console.warn('❌ [vue-devtools-mcp] HTTP Server not found, cannot start WebSocket server.');
|
|
388
|
+
const wss = new external_ws_namespaceObject.WebSocketServer({
|
|
389
|
+
noServer: true
|
|
390
|
+
});
|
|
391
|
+
wss.on('connection', ()=>{
|
|
392
|
+
console.debug('✅ Client connected to vue-devtools-mcp WebSocket!');
|
|
393
|
+
});
|
|
394
|
+
httpServer.on('upgrade', (request, socket, head)=>{
|
|
395
|
+
const { pathname } = new URL(request.url || '', 'wss://base.url');
|
|
396
|
+
if (pathname !== wsPath) return;
|
|
397
|
+
wss.handleUpgrade(request, socket, head, (client)=>{
|
|
398
|
+
wss.emit('connection', client, request);
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
const rpc = createServerRpcFunc(ctx);
|
|
402
|
+
ctx.rpcServer = createRPCServer(wss, rpc, {
|
|
403
|
+
timeout: 3000
|
|
404
|
+
});
|
|
405
|
+
ctx.rpc = rpc;
|
|
406
|
+
const { printUrl = true, mcpPath = '/__mcp' } = options;
|
|
407
|
+
setupRoutes(mcpPath, async ()=>{
|
|
408
|
+
let mcp = createMcpServerDefault(options, ctx);
|
|
409
|
+
mcp = await options.mcpServerSetup?.(mcp, api) || mcp;
|
|
410
|
+
return mcp;
|
|
411
|
+
}, server);
|
|
412
|
+
if (printUrl) {
|
|
413
|
+
const mcpUrl = `http://localhost:${server.port}${mcpPath}/mcp`;
|
|
414
|
+
setTimeout(()=>{
|
|
415
|
+
if (api.logger?.info) api.logger.info(` ➜ MCP: Server is running at ${mcpUrl}`);
|
|
416
|
+
else console.log(` ➜ MCP: Server is running at ${mcpUrl}`);
|
|
417
|
+
}, 300);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
exports.pluginVueMcp = __webpack_exports__.pluginVueMcp;
|
|
423
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
424
|
+
"pluginVueMcp"
|
|
425
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
426
|
+
Object.defineProperty(exports, '__esModule', {
|
|
427
|
+
value: true
|
|
428
|
+
});
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import node_path from "node:path";
|
|
3
|
+
import promises from "node:fs/promises";
|
|
4
|
+
import { WebSocketServer } from "ws";
|
|
5
|
+
import { createMcpServerDefault, createVueMcpContext, createRPCServer, setupRoutes, createServerRpcFunc } from "./551.js";
|
|
6
|
+
function getVueMcpPath() {
|
|
7
|
+
const pluginPath = node_path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
return pluginPath.replaceAll('\\', '/');
|
|
9
|
+
}
|
|
10
|
+
function getOverlayBootstrapPath() {
|
|
11
|
+
const fileUrl = new URL('./overlay-bootstrap.js', import.meta.url);
|
|
12
|
+
return fileURLToPath(fileUrl).replaceAll('\\', '/').replace('/src/', '/dist/');
|
|
13
|
+
}
|
|
14
|
+
const pluginVueMcp = (options = {})=>({
|
|
15
|
+
name: 'plugin-vue-mcp',
|
|
16
|
+
enforce: 'pre',
|
|
17
|
+
apply: 'serve',
|
|
18
|
+
async setup (api) {
|
|
19
|
+
const ctx = createVueMcpContext();
|
|
20
|
+
const vueMcpPath = getVueMcpPath();
|
|
21
|
+
const wsPath = '/__vue-devtools-mcp-ws';
|
|
22
|
+
if (options.appendTo) {
|
|
23
|
+
let firstInjected = false;
|
|
24
|
+
const reg = new RegExp(options.appendTo);
|
|
25
|
+
api.transform({
|
|
26
|
+
test: {
|
|
27
|
+
and: [
|
|
28
|
+
/[\\/]src[\\/].+\.(js|jsx|mjs|ts|tsx|mts)$/,
|
|
29
|
+
reg
|
|
30
|
+
],
|
|
31
|
+
not: /[\\/]node_modules[\\/]/
|
|
32
|
+
},
|
|
33
|
+
targets: [
|
|
34
|
+
'web'
|
|
35
|
+
],
|
|
36
|
+
order: 'pre'
|
|
37
|
+
}, (context)=>{
|
|
38
|
+
if ('dev' !== api.context.action || firstInjected) return context;
|
|
39
|
+
let code = context.code;
|
|
40
|
+
if ('string' != typeof code) return context;
|
|
41
|
+
firstInjected = true;
|
|
42
|
+
console.log(' ➜ VueDevtools MCP: injection `overlay.js` to:', context.resource);
|
|
43
|
+
return `${code}\n import '${vueMcpPath}/overlay.js';\n`;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (!options.appendTo) api.modifyHTMLTags(async (tags)=>{
|
|
47
|
+
const overlayPath = getOverlayBootstrapPath();
|
|
48
|
+
try {
|
|
49
|
+
const js = await promises.readFile(overlayPath, {
|
|
50
|
+
encoding: 'utf8'
|
|
51
|
+
});
|
|
52
|
+
tags.headTags.push({
|
|
53
|
+
tag: "script",
|
|
54
|
+
attrs: {
|
|
55
|
+
id: 'overlay-bootstrap.js'
|
|
56
|
+
},
|
|
57
|
+
children: js
|
|
58
|
+
});
|
|
59
|
+
console.log(' ➜ VueDevtools MCP: injection html => overlay-bootstrap.js');
|
|
60
|
+
} catch (e) {}
|
|
61
|
+
return tags;
|
|
62
|
+
});
|
|
63
|
+
api.onBeforeStartDevServer(async ({ server })=>{
|
|
64
|
+
if (!server) return void console.error('❌ [vue-devtools-mcp] no rebuild devServer, requires Rsbuild >= 1.2.9');
|
|
65
|
+
const httpServer = server.httpServer;
|
|
66
|
+
if (!httpServer) return void console.warn('❌ [vue-devtools-mcp] HTTP Server not found, cannot start WebSocket server.');
|
|
67
|
+
const wss = new WebSocketServer({
|
|
68
|
+
noServer: true
|
|
69
|
+
});
|
|
70
|
+
wss.on('connection', ()=>{
|
|
71
|
+
console.debug('✅ Client connected to vue-devtools-mcp WebSocket!');
|
|
72
|
+
});
|
|
73
|
+
httpServer.on('upgrade', (request, socket, head)=>{
|
|
74
|
+
const { pathname } = new URL(request.url || '', 'wss://base.url');
|
|
75
|
+
if (pathname !== wsPath) return;
|
|
76
|
+
wss.handleUpgrade(request, socket, head, (client)=>{
|
|
77
|
+
wss.emit('connection', client, request);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
const rpc = createServerRpcFunc(ctx);
|
|
81
|
+
ctx.rpcServer = createRPCServer(wss, rpc, {
|
|
82
|
+
timeout: 3000
|
|
83
|
+
});
|
|
84
|
+
ctx.rpc = rpc;
|
|
85
|
+
const { printUrl = true, mcpPath = '/__mcp' } = options;
|
|
86
|
+
setupRoutes(mcpPath, async ()=>{
|
|
87
|
+
let mcp = createMcpServerDefault(options, ctx);
|
|
88
|
+
mcp = await options.mcpServerSetup?.(mcp, api) || mcp;
|
|
89
|
+
return mcp;
|
|
90
|
+
}, server);
|
|
91
|
+
if (printUrl) {
|
|
92
|
+
const mcpUrl = `http://localhost:${server.port}${mcpPath}/mcp`;
|
|
93
|
+
setTimeout(()=>{
|
|
94
|
+
if (api.logger?.info) api.logger.info(` ➜ MCP: Server is running at ${mcpUrl}`);
|
|
95
|
+
else console.log(` ➜ MCP: Server is running at ${mcpUrl}`);
|
|
96
|
+
}, 300);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
export { pluginVueMcp };
|