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/551.js
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { createHooks } from "hookable";
|
|
2
|
+
import { createBirpcGroup } from "birpc";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { nanoid } from "nanoid";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
7
|
+
function createVueMcpContext() {
|
|
8
|
+
return {
|
|
9
|
+
hooks: createHooks(),
|
|
10
|
+
rpc: null,
|
|
11
|
+
rpcServer: null
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function createServerRpcFunc(ctx) {
|
|
15
|
+
return {
|
|
16
|
+
getInspectorTree: (_)=>({}),
|
|
17
|
+
onInspectorTreeUpdated: (event, data)=>{
|
|
18
|
+
ctx.hooks.callHook(event, data).finally();
|
|
19
|
+
},
|
|
20
|
+
getInspectorState: (_)=>({}),
|
|
21
|
+
onInspectorStateUpdated: (event, data)=>{
|
|
22
|
+
ctx.hooks.callHook(event, data).finally();
|
|
23
|
+
},
|
|
24
|
+
editComponentState (_) {},
|
|
25
|
+
highlightComponent (_) {},
|
|
26
|
+
getRouterInfo: (_)=>({}),
|
|
27
|
+
onRouterInfoUpdated: (event, data)=>{
|
|
28
|
+
ctx.hooks.callHook(event, data).finally();
|
|
29
|
+
},
|
|
30
|
+
getPiniaTree: (_)=>({}),
|
|
31
|
+
onPiniaTreeUpdated: (event, data)=>{
|
|
32
|
+
ctx.hooks.callHook(event, data).finally();
|
|
33
|
+
},
|
|
34
|
+
getPiniaState: (_)=>({}),
|
|
35
|
+
onPiniaInfoUpdated: (event, data)=>{
|
|
36
|
+
ctx.hooks.callHook(event, data).finally();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const channelCache = new WeakMap();
|
|
41
|
+
function createRPCServer(ws, functions, options = {}) {
|
|
42
|
+
const group = createBirpcGroup(functions, ()=>Array.from(ws?.clients || []).map((channel)=>{
|
|
43
|
+
if (channel.readyState === channel.CLOSED) return;
|
|
44
|
+
const cached = channelCache.get(channel);
|
|
45
|
+
if (cached) return cached;
|
|
46
|
+
const options = {
|
|
47
|
+
on: (fn)=>channel.on('message', fn),
|
|
48
|
+
post: (data)=>channel.send(data),
|
|
49
|
+
serialize: (v)=>JSON.stringify(v),
|
|
50
|
+
deserialize: (v)=>JSON.parse(v)
|
|
51
|
+
};
|
|
52
|
+
channelCache.set(channel, options);
|
|
53
|
+
return options;
|
|
54
|
+
}).filter((c)=>!!c), options);
|
|
55
|
+
ws.on('connection', ()=>{
|
|
56
|
+
group.updateChannels();
|
|
57
|
+
});
|
|
58
|
+
return group.broadcast;
|
|
59
|
+
}
|
|
60
|
+
var package_namespaceObject = {
|
|
61
|
+
rE: "0.1.0"
|
|
62
|
+
};
|
|
63
|
+
function stringify(value) {
|
|
64
|
+
return "string" == typeof value ? value : JSON.stringify(value);
|
|
65
|
+
}
|
|
66
|
+
function createMcpServerDefault(options, ctx) {
|
|
67
|
+
const server = new McpServer({
|
|
68
|
+
name: "rsbuild-vue-mcp",
|
|
69
|
+
description: 'Rsbuild/Rspack MCP plugin based on Vue DevTools',
|
|
70
|
+
version: package_namespaceObject.rE,
|
|
71
|
+
...options.mcpServerInfo
|
|
72
|
+
});
|
|
73
|
+
server.registerTool('get-component-tree', {
|
|
74
|
+
description: 'Get the Vue component tree in markdown tree syntax format.'
|
|
75
|
+
}, async ()=>new Promise((resolve, reject)=>{
|
|
76
|
+
const eventName = nanoid();
|
|
77
|
+
ctx.hooks.hookOnce(eventName, (res)=>{
|
|
78
|
+
resolve({
|
|
79
|
+
content: [
|
|
80
|
+
{
|
|
81
|
+
type: 'text',
|
|
82
|
+
text: stringify(res)
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
ctx.rpcServer.getInspectorTree({
|
|
88
|
+
event: eventName
|
|
89
|
+
}).catch((e)=>{
|
|
90
|
+
console.error(e);
|
|
91
|
+
reject(e);
|
|
92
|
+
});
|
|
93
|
+
}));
|
|
94
|
+
server.registerTool('get-component-state', {
|
|
95
|
+
description: 'Get the Vue component state in JSON structure format.',
|
|
96
|
+
inputSchema: {
|
|
97
|
+
componentName: z.string()
|
|
98
|
+
}
|
|
99
|
+
}, async ({ componentName })=>new Promise((resolve, reject)=>{
|
|
100
|
+
const eventName = nanoid();
|
|
101
|
+
ctx.hooks.hookOnce(eventName, (res)=>{
|
|
102
|
+
resolve({
|
|
103
|
+
content: [
|
|
104
|
+
{
|
|
105
|
+
type: 'text',
|
|
106
|
+
text: stringify(res)
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
ctx.rpcServer.getInspectorState({
|
|
112
|
+
event: eventName,
|
|
113
|
+
componentName
|
|
114
|
+
}).catch((e)=>{
|
|
115
|
+
console.error(e);
|
|
116
|
+
reject(e);
|
|
117
|
+
});
|
|
118
|
+
}));
|
|
119
|
+
server.registerTool('edit-component-state', {
|
|
120
|
+
description: 'Edit the Vue component state.',
|
|
121
|
+
inputSchema: {
|
|
122
|
+
componentName: z.string(),
|
|
123
|
+
path: z.array(z.string()),
|
|
124
|
+
value: z.string(),
|
|
125
|
+
valueType: z["enum"]([
|
|
126
|
+
'string',
|
|
127
|
+
'number',
|
|
128
|
+
'boolean',
|
|
129
|
+
'object',
|
|
130
|
+
'array'
|
|
131
|
+
])
|
|
132
|
+
}
|
|
133
|
+
}, async ({ componentName, path, value, valueType })=>new Promise((resolve, reject)=>{
|
|
134
|
+
ctx.rpcServer.editComponentState({
|
|
135
|
+
componentName,
|
|
136
|
+
path,
|
|
137
|
+
value,
|
|
138
|
+
valueType
|
|
139
|
+
}).then(()=>{
|
|
140
|
+
resolve({
|
|
141
|
+
content: [
|
|
142
|
+
{
|
|
143
|
+
type: 'text',
|
|
144
|
+
text: 'ok'
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
});
|
|
148
|
+
}).catch((e)=>{
|
|
149
|
+
console.error(e);
|
|
150
|
+
reject(e);
|
|
151
|
+
});
|
|
152
|
+
}));
|
|
153
|
+
server.registerTool('highlight-component', {
|
|
154
|
+
description: 'Highlight the Vue component.',
|
|
155
|
+
inputSchema: {
|
|
156
|
+
componentName: z.string()
|
|
157
|
+
}
|
|
158
|
+
}, async ({ componentName })=>new Promise((resolve, reject)=>{
|
|
159
|
+
ctx.rpcServer.highlightComponent({
|
|
160
|
+
componentName
|
|
161
|
+
}).then(()=>{
|
|
162
|
+
resolve({
|
|
163
|
+
content: [
|
|
164
|
+
{
|
|
165
|
+
type: 'text',
|
|
166
|
+
text: 'ok'
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
});
|
|
170
|
+
}).catch((e)=>{
|
|
171
|
+
console.error(e);
|
|
172
|
+
reject(e);
|
|
173
|
+
});
|
|
174
|
+
}));
|
|
175
|
+
server.registerTool('get-router-info', {
|
|
176
|
+
description: 'Get the Vue router info in JSON structure format.'
|
|
177
|
+
}, async ()=>new Promise((resolve, reject)=>{
|
|
178
|
+
const eventName = nanoid();
|
|
179
|
+
ctx.hooks.hookOnce(eventName, (res)=>{
|
|
180
|
+
resolve({
|
|
181
|
+
content: [
|
|
182
|
+
{
|
|
183
|
+
type: 'text',
|
|
184
|
+
text: stringify(res)
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
ctx.rpcServer.getRouterInfo({
|
|
190
|
+
event: eventName
|
|
191
|
+
}).catch((e)=>{
|
|
192
|
+
console.error(e);
|
|
193
|
+
reject(e);
|
|
194
|
+
});
|
|
195
|
+
}));
|
|
196
|
+
server.registerTool('get-pinia-state', {
|
|
197
|
+
description: 'Get the Pinia state in JSON structure format.',
|
|
198
|
+
inputSchema: {
|
|
199
|
+
storeName: z.string()
|
|
200
|
+
}
|
|
201
|
+
}, async ({ storeName })=>new Promise((resolve, reject)=>{
|
|
202
|
+
const eventName = nanoid();
|
|
203
|
+
ctx.hooks.hookOnce(eventName, (res)=>{
|
|
204
|
+
resolve({
|
|
205
|
+
content: [
|
|
206
|
+
{
|
|
207
|
+
type: 'text',
|
|
208
|
+
text: stringify(res)
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
ctx.rpcServer.getPiniaState({
|
|
214
|
+
event: eventName,
|
|
215
|
+
storeName
|
|
216
|
+
}).catch((e)=>{
|
|
217
|
+
console.error(e);
|
|
218
|
+
reject(e);
|
|
219
|
+
});
|
|
220
|
+
}));
|
|
221
|
+
server.registerTool('get-pinia-tree', {
|
|
222
|
+
description: 'Get the Pinia tree in JSON structure format.'
|
|
223
|
+
}, async ()=>new Promise((resolve, reject)=>{
|
|
224
|
+
const eventName = nanoid();
|
|
225
|
+
ctx.hooks.hookOnce(eventName, (res)=>{
|
|
226
|
+
resolve({
|
|
227
|
+
content: [
|
|
228
|
+
{
|
|
229
|
+
type: 'text',
|
|
230
|
+
text: stringify(res)
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
ctx.rpcServer.getPiniaTree({
|
|
236
|
+
event: eventName
|
|
237
|
+
}).catch((e)=>{
|
|
238
|
+
console.error(e);
|
|
239
|
+
reject(e);
|
|
240
|
+
});
|
|
241
|
+
}));
|
|
242
|
+
return server;
|
|
243
|
+
}
|
|
244
|
+
async function mcpHandlerStateless(server, req, res) {
|
|
245
|
+
try {
|
|
246
|
+
const transport = new StreamableHTTPServerTransport({
|
|
247
|
+
sessionIdGenerator: void 0
|
|
248
|
+
});
|
|
249
|
+
await server.connect(transport);
|
|
250
|
+
await transport.handleRequest(req, res);
|
|
251
|
+
res.on('close', ()=>{
|
|
252
|
+
transport.close();
|
|
253
|
+
server.close();
|
|
254
|
+
});
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error('Error handling MCP request:', error);
|
|
257
|
+
if (!res.headersSent) {
|
|
258
|
+
res.statusCode = 500;
|
|
259
|
+
res.setHeader('content-type', 'application/json');
|
|
260
|
+
res.end(JSON.stringify({
|
|
261
|
+
jsonrpc: '2.0',
|
|
262
|
+
error: {
|
|
263
|
+
code: -32603,
|
|
264
|
+
message: 'Internal server error'
|
|
265
|
+
},
|
|
266
|
+
id: null
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function setupRoutes(base, getServer, dev) {
|
|
272
|
+
dev.middlewares.use(`${base}/mcp`, async (req, res)=>{
|
|
273
|
+
const server = await getServer();
|
|
274
|
+
await mcpHandlerStateless(server, req, res);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
function setupRspackRoutes(base, getServer, middlewares) {
|
|
278
|
+
middlewares.push({
|
|
279
|
+
name: 'vue-devtools-mcp',
|
|
280
|
+
path: `${base}/mcp`,
|
|
281
|
+
async middleware (req, res) {
|
|
282
|
+
const server = await getServer();
|
|
283
|
+
await mcpHandlerStateless(server, req, res);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
export { createMcpServerDefault, createRPCServer, createServerRpcFunc, createVueMcpContext, setupRoutes, setupRspackRoutes };
|