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/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 };