rsbuild-plugin-vue-mcp 0.1.0 → 0.1.2

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 CHANGED
@@ -104,9 +104,10 @@ Add that URL to your client's MCP configuration (Cursor, Claude Desktop, VS Code
104
104
  ```json
105
105
  {
106
106
  "mcpServers": {
107
- "vue-mcp": {
107
+ "vue-devtools": {
108
108
  "type": "streamable-http",
109
109
  "url": "http://localhost:<YourPort>/__mcp/mcp",
110
+ "description": "Vue DevTools MCP - Rsbuild/Rspack MCP plugin based on Vue DevTools",
110
111
  "disabled": false
111
112
  }
112
113
  }
package/README_zh.md CHANGED
@@ -101,9 +101,10 @@ MCP 服务(基于 Streamable HTTP 传输)将在 `http://localhost:[port]/__m
101
101
  ```json
102
102
  {
103
103
  "mcpServers": {
104
- "vue-mcp": {
104
+ "vue-devtools": {
105
105
  "type": "streamable-http",
106
106
  "url": "http://localhost:<YourPort>/__mcp/mcp",
107
+ "description": "Vue DevTools MCP - Rsbuild/Rspack MCP plugin based on Vue DevTools",
107
108
  "disabled": false
108
109
  }
109
110
  }
package/dist/396.js ADDED
@@ -0,0 +1,237 @@
1
+ import { createBirpcGroup } from "birpc";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { z } from "zod";
4
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
5
+ function createVueMcpContext() {
6
+ return {
7
+ rpcServer: null
8
+ };
9
+ }
10
+ const channelCache = new WeakMap();
11
+ function createRPCServer(ws, functions, options = {}) {
12
+ const group = createBirpcGroup(functions, ()=>Array.from(ws?.clients || []).map((channel)=>{
13
+ if (channel.readyState === channel.CLOSED) return;
14
+ const cached = channelCache.get(channel);
15
+ if (cached) return cached;
16
+ const options = {
17
+ on: (fn)=>channel.on('message', fn),
18
+ post: (data)=>channel.send(data),
19
+ serialize: (v)=>JSON.stringify(v),
20
+ deserialize: (v)=>JSON.parse(v)
21
+ };
22
+ channelCache.set(channel, options);
23
+ return options;
24
+ }).filter((c)=>!!c), options);
25
+ ws.on('connection', ()=>{
26
+ group.updateChannels();
27
+ });
28
+ return group.broadcast;
29
+ }
30
+ var package_namespaceObject = {
31
+ rE: "0.1.2"
32
+ };
33
+ function stringify(value) {
34
+ return "string" == typeof value ? value : JSON.stringify(value);
35
+ }
36
+ function createMcpServerDefault(options, ctx) {
37
+ const server = new McpServer({
38
+ name: "rsbuild-vue-mcp",
39
+ description: 'Rsbuild/Rspack MCP plugin based on Vue DevTools',
40
+ version: package_namespaceObject.rE,
41
+ ...options.mcpServerInfo
42
+ });
43
+ server.registerTool('get-app-record-status', {
44
+ title: 'App Record Status',
45
+ description: "Get the current Devtools App status (appRecords and activeAppRecord). Must be called first before any other MCP tools. An empty content response usually means the user needs to open a web page with an app to debug. If multiple content items are returned, the user has multiple app pages open and should close all but one to avoid interference. If a content contains multiple appRecords, it means multiple Vue App instances exist on the page. `toggle-app` to switch activeAppRecord. "
46
+ }, async ()=>{
47
+ const res = await ctx.rpcServer.getAppRecordStatus();
48
+ return {
49
+ content: res.map((r)=>({
50
+ type: 'text',
51
+ text: stringify(r)
52
+ }))
53
+ };
54
+ });
55
+ server.registerTool('toggle-app', {
56
+ title: 'Toggle App',
57
+ description: 'Toggle the activeAppRecord by the id of the appRecord. All MCP tool operations on Components, Router, Pinia, etc. are performed based on the activeAppRecord.',
58
+ inputSchema: {
59
+ id: z.string()
60
+ }
61
+ }, async ({ id })=>{
62
+ await ctx.rpcServer.toggleApp({
63
+ id
64
+ });
65
+ return {
66
+ content: [
67
+ {
68
+ type: 'text',
69
+ text: 'ok'
70
+ }
71
+ ]
72
+ };
73
+ });
74
+ server.registerTool('get-component-tree', {
75
+ description: 'Get the Vue component tree in JSON tree syntax format (by Vue DevTools stringify, a two-dimensional serialization format that separates structure from values, replacing nested object references with IDs.).',
76
+ inputSchema: {
77
+ componentName: z.string().optional().describe('query by componentName (the component\'s name, not uid), default query all components')
78
+ }
79
+ }, async ({ componentName })=>{
80
+ const res = await ctx.rpcServer.getInspectorTree({
81
+ componentName
82
+ });
83
+ return {
84
+ content: res.map((r)=>({
85
+ type: 'text',
86
+ text: stringify(r)
87
+ }))
88
+ };
89
+ });
90
+ server.registerTool('get-component-state', {
91
+ description: 'Get the Vue component state in JSON structure format.',
92
+ inputSchema: {
93
+ componentName: z.string().describe('query by componentName (the component\'s name, not uid)')
94
+ }
95
+ }, async ({ componentName })=>{
96
+ const res = await ctx.rpcServer.getInspectorState({
97
+ componentName
98
+ });
99
+ return {
100
+ content: res.map((r)=>({
101
+ type: 'text',
102
+ text: stringify(r)
103
+ }))
104
+ };
105
+ });
106
+ server.registerTool('edit-component-state', {
107
+ description: 'Edit the Vue component state.',
108
+ inputSchema: {
109
+ componentName: z.string().describe('by componentName (the component\'s name, not uid)'),
110
+ path: z.array(z.string()),
111
+ value: z.string(),
112
+ valueType: z["enum"]([
113
+ 'string',
114
+ 'number',
115
+ 'boolean',
116
+ 'object',
117
+ 'array'
118
+ ])
119
+ }
120
+ }, async ({ componentName, path, value, valueType })=>{
121
+ await ctx.rpcServer.editComponentState({
122
+ componentName,
123
+ path,
124
+ value,
125
+ valueType
126
+ });
127
+ return {
128
+ content: [
129
+ {
130
+ type: 'text',
131
+ text: 'ok'
132
+ }
133
+ ]
134
+ };
135
+ });
136
+ server.registerTool('highlight-component', {
137
+ description: 'Highlight the Vue component.',
138
+ inputSchema: {
139
+ componentName: z.string().describe('by componentName (the component\'s name, not uid)')
140
+ }
141
+ }, async ({ componentName })=>{
142
+ await ctx.rpcServer.highlightComponent({
143
+ componentName
144
+ });
145
+ return {
146
+ content: [
147
+ {
148
+ type: 'text',
149
+ text: 'ok'
150
+ }
151
+ ]
152
+ };
153
+ });
154
+ server.registerTool('get-router-info', {
155
+ description: 'Get the Vue router info in JSON structure format.'
156
+ }, async ()=>{
157
+ const res = await ctx.rpcServer.getRouterInfo();
158
+ return {
159
+ content: res.map((r)=>({
160
+ type: 'text',
161
+ text: stringify(r)
162
+ }))
163
+ };
164
+ });
165
+ server.registerTool('get-pinia-state', {
166
+ description: 'Get the Pinia state in JSON structure format.',
167
+ inputSchema: {
168
+ storeName: z.string()
169
+ }
170
+ }, async ({ storeName })=>{
171
+ const res = await ctx.rpcServer.getPiniaState({
172
+ storeName
173
+ });
174
+ return {
175
+ content: res.map((r)=>({
176
+ type: 'text',
177
+ text: stringify(r)
178
+ }))
179
+ };
180
+ });
181
+ server.registerTool('get-pinia-tree', {
182
+ description: 'Get the Pinia tree in JSON structure format.'
183
+ }, async ()=>{
184
+ const res = await ctx.rpcServer.getPiniaTree();
185
+ return {
186
+ content: res.map((r)=>({
187
+ type: 'text',
188
+ text: stringify(r)
189
+ }))
190
+ };
191
+ });
192
+ return server;
193
+ }
194
+ async function mcpHandlerStateless(server, req, res) {
195
+ try {
196
+ const transport = new StreamableHTTPServerTransport({
197
+ sessionIdGenerator: void 0
198
+ });
199
+ await server.connect(transport);
200
+ await transport.handleRequest(req, res);
201
+ res.on('close', ()=>{
202
+ transport.close();
203
+ server.close();
204
+ });
205
+ } catch (error) {
206
+ console.error('Error handling MCP request:', error);
207
+ if (!res.headersSent) {
208
+ res.statusCode = 500;
209
+ res.setHeader('content-type', 'application/json');
210
+ res.end(JSON.stringify({
211
+ jsonrpc: '2.0',
212
+ error: {
213
+ code: -32603,
214
+ message: 'Internal server error'
215
+ },
216
+ id: null
217
+ }));
218
+ }
219
+ }
220
+ }
221
+ function setupRoutes(base, getServer, dev) {
222
+ dev.middlewares.use(`${base}/mcp`, async (req, res)=>{
223
+ const server = await getServer();
224
+ await mcpHandlerStateless(server, req, res);
225
+ });
226
+ }
227
+ function setupRspackRoutes(base, getServer, middlewares) {
228
+ middlewares.push({
229
+ name: 'vue-devtools-mcp',
230
+ path: `${base}/mcp`,
231
+ async middleware (req, res) {
232
+ const server = await getServer();
233
+ await mcpHandlerStateless(server, req, res);
234
+ }
235
+ });
236
+ }
237
+ export { createMcpServerDefault, createRPCServer, createVueMcpContext, setupRoutes, setupRspackRoutes };
package/dist/index.cjs CHANGED
@@ -48,40 +48,11 @@ var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_no
48
48
  const promises_namespaceObject = require("node:fs/promises");
49
49
  var promises_default = /*#__PURE__*/ __webpack_require__.n(promises_namespaceObject);
50
50
  const external_ws_namespaceObject = require("ws");
51
- const external_hookable_namespaceObject = require("hookable");
52
51
  function createVueMcpContext() {
53
52
  return {
54
- hooks: (0, external_hookable_namespaceObject.createHooks)(),
55
- rpc: null,
56
53
  rpcServer: null
57
54
  };
58
55
  }
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
56
  const external_birpc_namespaceObject = require("birpc");
86
57
  const channelCache = new WeakMap();
87
58
  function createRPCServer(ws, functions, options = {}) {
@@ -105,9 +76,8 @@ function createRPCServer(ws, functions, options = {}) {
105
76
  }
106
77
  const mcp_js_namespaceObject = require("@modelcontextprotocol/sdk/server/mcp.js");
107
78
  var package_namespaceObject = {
108
- rE: "0.1.0"
79
+ rE: "0.1.2"
109
80
  };
110
- const external_nanoid_namespaceObject = require("nanoid");
111
81
  const external_zod_namespaceObject = require("zod");
112
82
  function stringify(value) {
113
83
  return "string" == typeof value ? value : JSON.stringify(value);
@@ -119,56 +89,73 @@ function createMcpServerDefault(options, ctx) {
119
89
  version: package_namespaceObject.rE,
120
90
  ...options.mcpServerInfo
121
91
  });
92
+ server.registerTool('get-app-record-status', {
93
+ title: 'App Record Status',
94
+ description: "Get the current Devtools App status (appRecords and activeAppRecord). Must be called first before any other MCP tools. An empty content response usually means the user needs to open a web page with an app to debug. If multiple content items are returned, the user has multiple app pages open and should close all but one to avoid interference. If a content contains multiple appRecords, it means multiple Vue App instances exist on the page. `toggle-app` to switch activeAppRecord. "
95
+ }, async ()=>{
96
+ const res = await ctx.rpcServer.getAppRecordStatus();
97
+ return {
98
+ content: res.map((r)=>({
99
+ type: 'text',
100
+ text: stringify(r)
101
+ }))
102
+ };
103
+ });
104
+ server.registerTool('toggle-app', {
105
+ title: 'Toggle App',
106
+ description: 'Toggle the activeAppRecord by the id of the appRecord. All MCP tool operations on Components, Router, Pinia, etc. are performed based on the activeAppRecord.',
107
+ inputSchema: {
108
+ id: external_zod_namespaceObject.z.string()
109
+ }
110
+ }, async ({ id })=>{
111
+ await ctx.rpcServer.toggleApp({
112
+ id
113
+ });
114
+ return {
115
+ content: [
116
+ {
117
+ type: 'text',
118
+ text: 'ok'
119
+ }
120
+ ]
121
+ };
122
+ });
122
123
  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
- }));
124
+ description: 'Get the Vue component tree in JSON tree syntax format (by Vue DevTools stringify, a two-dimensional serialization format that separates structure from values, replacing nested object references with IDs.).',
125
+ inputSchema: {
126
+ componentName: external_zod_namespaceObject.z.string().optional().describe('query by componentName (the component\'s name, not uid), default query all components')
127
+ }
128
+ }, async ({ componentName })=>{
129
+ const res = await ctx.rpcServer.getInspectorTree({
130
+ componentName
131
+ });
132
+ return {
133
+ content: res.map((r)=>({
134
+ type: 'text',
135
+ text: stringify(r)
136
+ }))
137
+ };
138
+ });
143
139
  server.registerTool('get-component-state', {
144
140
  description: 'Get the Vue component state in JSON structure format.',
145
141
  inputSchema: {
146
- componentName: external_zod_namespaceObject.z.string()
142
+ componentName: external_zod_namespaceObject.z.string().describe('query by componentName (the component\'s name, not uid)')
147
143
  }
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
- }));
144
+ }, async ({ componentName })=>{
145
+ const res = await ctx.rpcServer.getInspectorState({
146
+ componentName
147
+ });
148
+ return {
149
+ content: res.map((r)=>({
150
+ type: 'text',
151
+ text: stringify(r)
152
+ }))
153
+ };
154
+ });
168
155
  server.registerTool('edit-component-state', {
169
156
  description: 'Edit the Vue component state.',
170
157
  inputSchema: {
171
- componentName: external_zod_namespaceObject.z.string(),
158
+ componentName: external_zod_namespaceObject.z.string().describe('by componentName (the component\'s name, not uid)'),
172
159
  path: external_zod_namespaceObject.z.array(external_zod_namespaceObject.z.string()),
173
160
  value: external_zod_namespaceObject.z.string(),
174
161
  valueType: external_zod_namespaceObject.z["enum"]([
@@ -179,115 +166,78 @@ function createMcpServerDefault(options, ctx) {
179
166
  'array'
180
167
  ])
181
168
  }
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
- }));
169
+ }, async ({ componentName, path, value, valueType })=>{
170
+ await ctx.rpcServer.editComponentState({
171
+ componentName,
172
+ path,
173
+ value,
174
+ valueType
175
+ });
176
+ return {
177
+ content: [
178
+ {
179
+ type: 'text',
180
+ text: 'ok'
181
+ }
182
+ ]
183
+ };
184
+ });
202
185
  server.registerTool('highlight-component', {
203
186
  description: 'Highlight the Vue component.',
204
187
  inputSchema: {
205
- componentName: external_zod_namespaceObject.z.string()
188
+ componentName: external_zod_namespaceObject.z.string().describe('by componentName (the component\'s name, not uid)')
206
189
  }
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
- }));
190
+ }, async ({ componentName })=>{
191
+ await ctx.rpcServer.highlightComponent({
192
+ componentName
193
+ });
194
+ return {
195
+ content: [
196
+ {
197
+ type: 'text',
198
+ text: 'ok'
199
+ }
200
+ ]
201
+ };
202
+ });
224
203
  server.registerTool('get-router-info', {
225
204
  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
- }));
205
+ }, async ()=>{
206
+ const res = await ctx.rpcServer.getRouterInfo();
207
+ return {
208
+ content: res.map((r)=>({
209
+ type: 'text',
210
+ text: stringify(r)
211
+ }))
212
+ };
213
+ });
245
214
  server.registerTool('get-pinia-state', {
246
215
  description: 'Get the Pinia state in JSON structure format.',
247
216
  inputSchema: {
248
217
  storeName: external_zod_namespaceObject.z.string()
249
218
  }
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
- }));
219
+ }, async ({ storeName })=>{
220
+ const res = await ctx.rpcServer.getPiniaState({
221
+ storeName
222
+ });
223
+ return {
224
+ content: res.map((r)=>({
225
+ type: 'text',
226
+ text: stringify(r)
227
+ }))
228
+ };
229
+ });
270
230
  server.registerTool('get-pinia-tree', {
271
231
  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
- }));
232
+ }, async ()=>{
233
+ const res = await ctx.rpcServer.getPiniaTree();
234
+ return {
235
+ content: res.map((r)=>({
236
+ type: 'text',
237
+ text: stringify(r)
238
+ }))
239
+ };
240
+ });
291
241
  return server;
292
242
  }
293
243
  const streamableHttp_js_namespaceObject = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
@@ -356,7 +306,7 @@ const pluginVueMcp = (options = {})=>({
356
306
  ],
357
307
  order: 'pre'
358
308
  }, (context)=>{
359
- if ('dev' !== api.context.action || firstInjected) return context;
309
+ if (api.context.action && 'dev' !== api.context.action || firstInjected) return context;
360
310
  let code = context.code;
361
311
  if ('string' != typeof code) return context;
362
312
  firstInjected = true;
@@ -398,11 +348,9 @@ const pluginVueMcp = (options = {})=>({
398
348
  wss.emit('connection', client, request);
399
349
  });
400
350
  });
401
- const rpc = createServerRpcFunc(ctx);
402
- ctx.rpcServer = createRPCServer(wss, rpc, {
351
+ ctx.rpcServer = createRPCServer(wss, {}, {
403
352
  timeout: 3000
404
353
  });
405
- ctx.rpc = rpc;
406
354
  const { printUrl = true, mcpPath = '/__mcp' } = options;
407
355
  setupRoutes(mcpPath, async ()=>{
408
356
  let mcp = createMcpServerDefault(options, ctx);
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { fileURLToPath } from "node:url";
2
2
  import node_path from "node:path";
3
3
  import promises from "node:fs/promises";
4
4
  import { WebSocketServer } from "ws";
5
- import { createMcpServerDefault, createVueMcpContext, createRPCServer, setupRoutes, createServerRpcFunc } from "./551.js";
5
+ import { createMcpServerDefault, createRPCServer, createVueMcpContext, setupRoutes } from "./396.js";
6
6
  function getVueMcpPath() {
7
7
  const pluginPath = node_path.dirname(fileURLToPath(import.meta.url));
8
8
  return pluginPath.replaceAll('\\', '/');
@@ -35,7 +35,7 @@ const pluginVueMcp = (options = {})=>({
35
35
  ],
36
36
  order: 'pre'
37
37
  }, (context)=>{
38
- if ('dev' !== api.context.action || firstInjected) return context;
38
+ if (api.context.action && 'dev' !== api.context.action || firstInjected) return context;
39
39
  let code = context.code;
40
40
  if ('string' != typeof code) return context;
41
41
  firstInjected = true;
@@ -77,11 +77,9 @@ const pluginVueMcp = (options = {})=>({
77
77
  wss.emit('connection', client, request);
78
78
  });
79
79
  });
80
- const rpc = createServerRpcFunc(ctx);
81
- ctx.rpcServer = createRPCServer(wss, rpc, {
80
+ ctx.rpcServer = createRPCServer(wss, {}, {
82
81
  timeout: 3000
83
82
  });
84
- ctx.rpc = rpc;
85
83
  const { printUrl = true, mcpPath = '/__mcp' } = options;
86
84
  setupRoutes(mcpPath, async ()=>{
87
85
  let mcp = createMcpServerDefault(options, ctx);