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/rspack.cjs
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
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
|
+
VueMcpPlugin: ()=>VueMcpPlugin
|
|
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 external_node_fs_namespaceObject = require("node:fs");
|
|
49
|
+
var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_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 setupRspackRoutes(base, getServer, middlewares) {
|
|
322
|
+
middlewares.push({
|
|
323
|
+
name: 'vue-devtools-mcp',
|
|
324
|
+
path: `${base}/mcp`,
|
|
325
|
+
async middleware (req, res) {
|
|
326
|
+
const server = await getServer();
|
|
327
|
+
await mcpHandlerStateless(server, req, res);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
function _define_property(obj, key, value) {
|
|
332
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
333
|
+
value: value,
|
|
334
|
+
enumerable: true,
|
|
335
|
+
configurable: true,
|
|
336
|
+
writable: true
|
|
337
|
+
});
|
|
338
|
+
else obj[key] = value;
|
|
339
|
+
return obj;
|
|
340
|
+
}
|
|
341
|
+
function getVueMcpPath() {
|
|
342
|
+
const pluginPath = external_node_path_default().dirname((0, external_node_url_namespaceObject.fileURLToPath)(__rslib_import_meta_url__));
|
|
343
|
+
return pluginPath.replaceAll('\\', '/');
|
|
344
|
+
}
|
|
345
|
+
function getOverlayBootstrapPath() {
|
|
346
|
+
const fileUrl = new URL('./overlay-bootstrap.js', __rslib_import_meta_url__);
|
|
347
|
+
return (0, external_node_url_namespaceObject.fileURLToPath)(fileUrl).replaceAll('\\', '/').replace('/src/', '/dist/');
|
|
348
|
+
}
|
|
349
|
+
const PLUGIN_NAME = "VueMcpPlugin";
|
|
350
|
+
class VueMcpPlugin {
|
|
351
|
+
applyInspector(compiler) {}
|
|
352
|
+
applyHtmlInjection(compiler) {
|
|
353
|
+
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation)=>{
|
|
354
|
+
const hooks = compiler.rspack.HtmlRspackPlugin.getCompilationHooks(compilation);
|
|
355
|
+
if (!hooks.alterAssetTags) return;
|
|
356
|
+
hooks.alterAssetTags.tap(PLUGIN_NAME, (data)=>{
|
|
357
|
+
const overlayPath = getOverlayBootstrapPath();
|
|
358
|
+
try {
|
|
359
|
+
const js = external_node_fs_default().readFileSync(overlayPath, {
|
|
360
|
+
encoding: 'utf8'
|
|
361
|
+
});
|
|
362
|
+
data.assetTags.scripts.push({
|
|
363
|
+
tagName: "script",
|
|
364
|
+
voidTag: false,
|
|
365
|
+
attributes: {
|
|
366
|
+
id: 'overlay-bootstrap.js'
|
|
367
|
+
},
|
|
368
|
+
innerHTML: js
|
|
369
|
+
});
|
|
370
|
+
console.log(' ➜ VueDevtools MCP: injection html => overlay-bootstrap.js');
|
|
371
|
+
} catch (e) {}
|
|
372
|
+
return data;
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
apply(compiler) {
|
|
377
|
+
if ('production' === compiler.options.mode) return;
|
|
378
|
+
const ctx = createVueMcpContext();
|
|
379
|
+
getVueMcpPath();
|
|
380
|
+
const wsPath = '/__vue-devtools-mcp-ws';
|
|
381
|
+
this.applyInspector(compiler);
|
|
382
|
+
this.applyHtmlInjection(compiler);
|
|
383
|
+
compiler.hooks.environment.tap(PLUGIN_NAME, ()=>{
|
|
384
|
+
if (false === compiler.options.devServer) return;
|
|
385
|
+
compiler.options.devServer ??= {};
|
|
386
|
+
const rawSetupMiddlewares = compiler.options.devServer.setupMiddlewares;
|
|
387
|
+
compiler.options.devServer.setupMiddlewares = (middlewares, devServer)=>{
|
|
388
|
+
if ('function' == typeof rawSetupMiddlewares) middlewares = rawSetupMiddlewares.call(compiler.options.devServer, middlewares, devServer);
|
|
389
|
+
if (!devServer) {
|
|
390
|
+
console.warn('❌ rspack-dev-server is not defined here.');
|
|
391
|
+
return middlewares;
|
|
392
|
+
}
|
|
393
|
+
const httpServer = devServer.server;
|
|
394
|
+
if (!httpServer) {
|
|
395
|
+
console.error('❌ [vue-devtools-mcp] no rspack devServer.server, require @rspack/core >= 1.3');
|
|
396
|
+
return middlewares;
|
|
397
|
+
}
|
|
398
|
+
const wss = new external_ws_namespaceObject.WebSocketServer({
|
|
399
|
+
noServer: true
|
|
400
|
+
});
|
|
401
|
+
wss.on('connection', ()=>{
|
|
402
|
+
console.debug('✅ Client connected to vue-devtools-mcp WebSocket!');
|
|
403
|
+
});
|
|
404
|
+
httpServer.on('upgrade', (request, socket, head)=>{
|
|
405
|
+
const { pathname } = new URL(request.url || '', 'wss://base.url');
|
|
406
|
+
if (pathname !== wsPath) return;
|
|
407
|
+
wss.handleUpgrade(request, socket, head, (client)=>{
|
|
408
|
+
wss.emit('connection', client, request);
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
const rpc = createServerRpcFunc(ctx);
|
|
412
|
+
ctx.rpcServer = createRPCServer(wss, rpc, {
|
|
413
|
+
timeout: 3000
|
|
414
|
+
});
|
|
415
|
+
ctx.rpc = rpc;
|
|
416
|
+
const { printUrl = true, mcpPath = '/__mcp' } = this.options;
|
|
417
|
+
setupRspackRoutes(mcpPath, async ()=>{
|
|
418
|
+
let mcp = createMcpServerDefault(this.options, ctx);
|
|
419
|
+
mcp = await this.options.mcpServerSetup?.(mcp, compiler) || mcp;
|
|
420
|
+
return mcp;
|
|
421
|
+
}, middlewares);
|
|
422
|
+
if (printUrl) {
|
|
423
|
+
const port = (compiler.options.devServer || {}).port || 8080;
|
|
424
|
+
const mcpUrl = `http://localhost:${port}${mcpPath}/mcp`;
|
|
425
|
+
setTimeout(()=>{
|
|
426
|
+
if (devServer.logger?.info) devServer.logger.info(` ➜ MCP: Server is running at ${mcpUrl}`);
|
|
427
|
+
else console.log(` ➜ MCP: Server is running at ${mcpUrl}`);
|
|
428
|
+
}, 300);
|
|
429
|
+
}
|
|
430
|
+
return middlewares;
|
|
431
|
+
};
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
constructor(options = {}){
|
|
435
|
+
_define_property(this, "name", PLUGIN_NAME);
|
|
436
|
+
_define_property(this, "options", void 0);
|
|
437
|
+
this.options = options;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
exports.VueMcpPlugin = __webpack_exports__.VueMcpPlugin;
|
|
441
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
442
|
+
"VueMcpPlugin"
|
|
443
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
444
|
+
Object.defineProperty(exports, '__esModule', {
|
|
445
|
+
value: true
|
|
446
|
+
});
|
package/dist/rspack.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import node_path from "node:path";
|
|
3
|
+
import node_fs from "node:fs";
|
|
4
|
+
import { WebSocketServer } from "ws";
|
|
5
|
+
import { createMcpServerDefault, createVueMcpContext, createRPCServer, createServerRpcFunc, setupRspackRoutes } from "./551.js";
|
|
6
|
+
function _define_property(obj, key, value) {
|
|
7
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
8
|
+
value: value,
|
|
9
|
+
enumerable: true,
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: true
|
|
12
|
+
});
|
|
13
|
+
else obj[key] = value;
|
|
14
|
+
return obj;
|
|
15
|
+
}
|
|
16
|
+
function getVueMcpPath() {
|
|
17
|
+
const pluginPath = node_path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
return pluginPath.replaceAll('\\', '/');
|
|
19
|
+
}
|
|
20
|
+
function getOverlayBootstrapPath() {
|
|
21
|
+
const fileUrl = new URL('./overlay-bootstrap.js', import.meta.url);
|
|
22
|
+
return fileURLToPath(fileUrl).replaceAll('\\', '/').replace('/src/', '/dist/');
|
|
23
|
+
}
|
|
24
|
+
const PLUGIN_NAME = "VueMcpPlugin";
|
|
25
|
+
class VueMcpPlugin {
|
|
26
|
+
applyInspector(compiler) {}
|
|
27
|
+
applyHtmlInjection(compiler) {
|
|
28
|
+
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation)=>{
|
|
29
|
+
const hooks = compiler.rspack.HtmlRspackPlugin.getCompilationHooks(compilation);
|
|
30
|
+
if (!hooks.alterAssetTags) return;
|
|
31
|
+
hooks.alterAssetTags.tap(PLUGIN_NAME, (data)=>{
|
|
32
|
+
const overlayPath = getOverlayBootstrapPath();
|
|
33
|
+
try {
|
|
34
|
+
const js = node_fs.readFileSync(overlayPath, {
|
|
35
|
+
encoding: 'utf8'
|
|
36
|
+
});
|
|
37
|
+
data.assetTags.scripts.push({
|
|
38
|
+
tagName: "script",
|
|
39
|
+
voidTag: false,
|
|
40
|
+
attributes: {
|
|
41
|
+
id: 'overlay-bootstrap.js'
|
|
42
|
+
},
|
|
43
|
+
innerHTML: js
|
|
44
|
+
});
|
|
45
|
+
console.log(' ➜ VueDevtools MCP: injection html => overlay-bootstrap.js');
|
|
46
|
+
} catch (e) {}
|
|
47
|
+
return data;
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
apply(compiler) {
|
|
52
|
+
if ('production' === compiler.options.mode) return;
|
|
53
|
+
const ctx = createVueMcpContext();
|
|
54
|
+
getVueMcpPath();
|
|
55
|
+
const wsPath = '/__vue-devtools-mcp-ws';
|
|
56
|
+
this.applyInspector(compiler);
|
|
57
|
+
this.applyHtmlInjection(compiler);
|
|
58
|
+
compiler.hooks.environment.tap(PLUGIN_NAME, ()=>{
|
|
59
|
+
if (false === compiler.options.devServer) return;
|
|
60
|
+
compiler.options.devServer ??= {};
|
|
61
|
+
const rawSetupMiddlewares = compiler.options.devServer.setupMiddlewares;
|
|
62
|
+
compiler.options.devServer.setupMiddlewares = (middlewares, devServer)=>{
|
|
63
|
+
if ('function' == typeof rawSetupMiddlewares) middlewares = rawSetupMiddlewares.call(compiler.options.devServer, middlewares, devServer);
|
|
64
|
+
if (!devServer) {
|
|
65
|
+
console.warn('❌ rspack-dev-server is not defined here.');
|
|
66
|
+
return middlewares;
|
|
67
|
+
}
|
|
68
|
+
const httpServer = devServer.server;
|
|
69
|
+
if (!httpServer) {
|
|
70
|
+
console.error('❌ [vue-devtools-mcp] no rspack devServer.server, require @rspack/core >= 1.3');
|
|
71
|
+
return middlewares;
|
|
72
|
+
}
|
|
73
|
+
const wss = new WebSocketServer({
|
|
74
|
+
noServer: true
|
|
75
|
+
});
|
|
76
|
+
wss.on('connection', ()=>{
|
|
77
|
+
console.debug('✅ Client connected to vue-devtools-mcp WebSocket!');
|
|
78
|
+
});
|
|
79
|
+
httpServer.on('upgrade', (request, socket, head)=>{
|
|
80
|
+
const { pathname } = new URL(request.url || '', 'wss://base.url');
|
|
81
|
+
if (pathname !== wsPath) return;
|
|
82
|
+
wss.handleUpgrade(request, socket, head, (client)=>{
|
|
83
|
+
wss.emit('connection', client, request);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
const rpc = createServerRpcFunc(ctx);
|
|
87
|
+
ctx.rpcServer = createRPCServer(wss, rpc, {
|
|
88
|
+
timeout: 3000
|
|
89
|
+
});
|
|
90
|
+
ctx.rpc = rpc;
|
|
91
|
+
const { printUrl = true, mcpPath = '/__mcp' } = this.options;
|
|
92
|
+
setupRspackRoutes(mcpPath, async ()=>{
|
|
93
|
+
let mcp = createMcpServerDefault(this.options, ctx);
|
|
94
|
+
mcp = await this.options.mcpServerSetup?.(mcp, compiler) || mcp;
|
|
95
|
+
return mcp;
|
|
96
|
+
}, middlewares);
|
|
97
|
+
if (printUrl) {
|
|
98
|
+
const port = (compiler.options.devServer || {}).port || 8080;
|
|
99
|
+
const mcpUrl = `http://localhost:${port}${mcpPath}/mcp`;
|
|
100
|
+
setTimeout(()=>{
|
|
101
|
+
if (devServer.logger?.info) devServer.logger.info(` ➜ MCP: Server is running at ${mcpUrl}`);
|
|
102
|
+
else console.log(` ➜ MCP: Server is running at ${mcpUrl}`);
|
|
103
|
+
}, 300);
|
|
104
|
+
}
|
|
105
|
+
return middlewares;
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
constructor(options = {}){
|
|
110
|
+
_define_property(this, "name", PLUGIN_NAME);
|
|
111
|
+
_define_property(this, "options", void 0);
|
|
112
|
+
this.options = options;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export { VueMcpPlugin };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { BirpcGroupReturn, EventOptions } from "birpc";
|
|
2
|
+
import type { WebSocketServer } from "ws";
|
|
3
|
+
export declare function createRPCServer<ClientFunction, ServerFunctions extends object>(ws: WebSocketServer, functions: ServerFunctions, options?: EventOptions<ClientFunction, ServerFunctions>): BirpcGroupReturn<ClientFunction>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { RsbuildDevServer } from "@rsbuild/core";
|
|
3
|
+
import type { DevServerMiddleware } from "@rspack/core";
|
|
4
|
+
export declare function setupRoutes<DEV extends RsbuildDevServer>(base: string, getServer: () => Promise<McpServer>, dev: DEV): void;
|
|
5
|
+
export declare function setupRspackRoutes(base: string, getServer: () => Promise<McpServer>, middlewares: DevServerMiddleware[]): void;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { PluginVueMcpOptions, VueMcpContext } from "../types.ts";
|
|
3
|
+
import type { RsbuildPluginAPI } from "@rsbuild/core";
|
|
4
|
+
import type { Compiler as RspackCompiler } from "@rspack/core";
|
|
5
|
+
export declare function createMcpServerDefault<DEV extends RsbuildPluginAPI | RspackCompiler>(options: PluginVueMcpOptions<DEV>, ctx: VueMcpContext): McpServer;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Compiler, RspackPluginInstance } from "@rspack/core";
|
|
2
|
+
import type { PluginVueMcpOptions } from "./types.ts";
|
|
3
|
+
export declare class VueMcpPlugin implements RspackPluginInstance {
|
|
4
|
+
readonly name = "VueMcpPlugin";
|
|
5
|
+
private readonly options;
|
|
6
|
+
constructor(options?: PluginVueMcpOptions<Compiler>);
|
|
7
|
+
private applyInspector;
|
|
8
|
+
private applyHtmlInjection;
|
|
9
|
+
apply(compiler: Compiler): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { Implementation as McpServerInfo } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import type { Hookable } from "hookable";
|
|
4
|
+
import type { BirpcGroupReturn } from "birpc";
|
|
5
|
+
export type Awaitable<T> = T | Promise<T>;
|
|
6
|
+
export interface RpcFunctions {
|
|
7
|
+
getInspectorTree: (options: {
|
|
8
|
+
event: string;
|
|
9
|
+
componentName?: string;
|
|
10
|
+
}) => void;
|
|
11
|
+
onInspectorTreeUpdated: (event: string, data: string) => void;
|
|
12
|
+
getInspectorState: (options: {
|
|
13
|
+
event: string;
|
|
14
|
+
componentName: string;
|
|
15
|
+
}) => void;
|
|
16
|
+
onInspectorStateUpdated: (event: string, data: string) => void;
|
|
17
|
+
editComponentState: (options: {
|
|
18
|
+
componentName: string;
|
|
19
|
+
path: string[];
|
|
20
|
+
value: string;
|
|
21
|
+
valueType: string;
|
|
22
|
+
}) => void;
|
|
23
|
+
highlightComponent: (options: {
|
|
24
|
+
componentName: string;
|
|
25
|
+
}) => void;
|
|
26
|
+
getRouterInfo: (options: {
|
|
27
|
+
event: string;
|
|
28
|
+
}) => void;
|
|
29
|
+
onRouterInfoUpdated: (event: string, data: string) => void;
|
|
30
|
+
getPiniaState: (options: {
|
|
31
|
+
event: string;
|
|
32
|
+
storeName: string;
|
|
33
|
+
}) => void;
|
|
34
|
+
onPiniaInfoUpdated: (event: string, data: string) => void;
|
|
35
|
+
getPiniaTree: (options: {
|
|
36
|
+
event: string;
|
|
37
|
+
}) => void;
|
|
38
|
+
onPiniaTreeUpdated: (event: string, data: string) => void;
|
|
39
|
+
}
|
|
40
|
+
export interface VueMcpContext {
|
|
41
|
+
hooks: Hookable;
|
|
42
|
+
rpc: RpcFunctions;
|
|
43
|
+
rpcServer: BirpcGroupReturn<RpcFunctions>;
|
|
44
|
+
}
|
|
45
|
+
export interface PluginVueMcpOptions<DEV> {
|
|
46
|
+
/**
|
|
47
|
+
* The host to listen on, default is `localhost`
|
|
48
|
+
*/
|
|
49
|
+
host?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Print the MCP server URL in the console
|
|
52
|
+
*
|
|
53
|
+
* @default true
|
|
54
|
+
*/
|
|
55
|
+
printUrl?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* The MCP server info. Ingored when `mcpServer` is provided
|
|
58
|
+
*/
|
|
59
|
+
mcpServerInfo?: McpServerInfo;
|
|
60
|
+
/**
|
|
61
|
+
* Setup the MCP server, this is called when the MCP server is created
|
|
62
|
+
* You may also return a new MCP server to replace the default one
|
|
63
|
+
*/
|
|
64
|
+
mcpServerSetup?: (server: McpServer, api: DEV) => Awaitable<void | McpServer>;
|
|
65
|
+
/**
|
|
66
|
+
* The path to the MCP server, default is `/__mcp`
|
|
67
|
+
*/
|
|
68
|
+
mcpPath?: string;
|
|
69
|
+
/**
|
|
70
|
+
* append an import to the module id ending with `appendTo` instead of adding a script into body
|
|
71
|
+
* useful for projects that do not use html file as an entry
|
|
72
|
+
*
|
|
73
|
+
* WARNING: only set this if you know exactly what it does.
|
|
74
|
+
* @default ''
|
|
75
|
+
*/
|
|
76
|
+
appendTo?: string | RegExp;
|
|
77
|
+
}
|