@nuxtjs/mcp-toolkit 0.0.0 → 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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +133 -0
  3. package/dist/module.d.mts +42 -0
  4. package/dist/module.json +11 -0
  5. package/dist/module.mjs +116 -0
  6. package/dist/runtime/server/mcp/config.d.ts +10 -0
  7. package/dist/runtime/server/mcp/config.js +12 -0
  8. package/dist/runtime/server/mcp/constants.d.ts +22 -0
  9. package/dist/runtime/server/mcp/constants.js +10 -0
  10. package/dist/runtime/server/mcp/definitions/handlers.d.ts +18 -0
  11. package/dist/runtime/server/mcp/definitions/handlers.js +3 -0
  12. package/dist/runtime/server/mcp/definitions/index.d.ts +4 -0
  13. package/dist/runtime/server/mcp/definitions/index.js +4 -0
  14. package/dist/runtime/server/mcp/definitions/prompts.d.ts +79 -0
  15. package/dist/runtime/server/mcp/definitions/prompts.js +32 -0
  16. package/dist/runtime/server/mcp/definitions/resources.d.ts +130 -0
  17. package/dist/runtime/server/mcp/definitions/resources.js +94 -0
  18. package/dist/runtime/server/mcp/definitions/tools.d.ts +64 -0
  19. package/dist/runtime/server/mcp/definitions/tools.js +38 -0
  20. package/dist/runtime/server/mcp/definitions/utils.d.ts +14 -0
  21. package/dist/runtime/server/mcp/definitions/utils.js +23 -0
  22. package/dist/runtime/server/mcp/devtools/index.d.ts +3 -0
  23. package/dist/runtime/server/mcp/devtools/index.js +342 -0
  24. package/dist/runtime/server/mcp/handler.d.ts +2 -0
  25. package/dist/runtime/server/mcp/handler.js +37 -0
  26. package/dist/runtime/server/mcp/loaders/index.d.ts +28 -0
  27. package/dist/runtime/server/mcp/loaders/index.js +119 -0
  28. package/dist/runtime/server/mcp/loaders/utils.d.ts +13 -0
  29. package/dist/runtime/server/mcp/loaders/utils.js +139 -0
  30. package/dist/runtime/server/mcp/utils.d.ts +18 -0
  31. package/dist/runtime/server/mcp/utils.js +42 -0
  32. package/dist/runtime/server/mcp/validators/index.d.ts +4 -0
  33. package/dist/runtime/server/mcp/validators/index.js +3 -0
  34. package/dist/runtime/server/mcp/validators/prompts.d.ts +14 -0
  35. package/dist/runtime/server/mcp/validators/prompts.js +36 -0
  36. package/dist/runtime/server/mcp/validators/resources.d.ts +14 -0
  37. package/dist/runtime/server/mcp/validators/resources.js +43 -0
  38. package/dist/runtime/server/mcp/validators/tools.d.ts +14 -0
  39. package/dist/runtime/server/mcp/validators/tools.js +47 -0
  40. package/dist/runtime/server/types/hooks.d.ts +16 -0
  41. package/dist/runtime/server/types/index.d.ts +1 -0
  42. package/dist/runtime/server/types/index.js +1 -0
  43. package/dist/runtime/server/types.server.d.ts +14 -0
  44. package/dist/types.d.mts +5 -0
  45. package/package.json +74 -2
  46. /package/dist/{.gitkeep → runtime/server/types/hooks.js} +0 -0
@@ -0,0 +1,94 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { resolve, extname } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { enrichNameTitle } from "./utils.js";
5
+ function getMimeType(filePath) {
6
+ const ext = extname(filePath).toLowerCase();
7
+ switch (ext) {
8
+ case ".md":
9
+ return "text/markdown";
10
+ case ".ts":
11
+ case ".mts":
12
+ case ".cts":
13
+ return "text/typescript";
14
+ case ".js":
15
+ case ".mjs":
16
+ case ".cjs":
17
+ return "text/javascript";
18
+ case ".json":
19
+ return "application/json";
20
+ case ".html":
21
+ return "text/html";
22
+ case ".css":
23
+ return "text/css";
24
+ case ".xml":
25
+ return "text/xml";
26
+ case ".csv":
27
+ return "text/csv";
28
+ case ".yaml":
29
+ case ".yml":
30
+ return "text/yaml";
31
+ default:
32
+ return "text/plain";
33
+ }
34
+ }
35
+ export function registerResourceFromDefinition(server, resource) {
36
+ const { name, title } = enrichNameTitle({
37
+ name: resource.name,
38
+ title: resource.title,
39
+ _meta: resource._meta,
40
+ type: "resource"
41
+ });
42
+ let uri = resource.uri;
43
+ let handler = resource.handler;
44
+ const metadata = {
45
+ ...resource.metadata,
46
+ title: resource.title || resource.metadata?.title || title
47
+ };
48
+ if ("file" in resource && resource.file) {
49
+ const filePath = resolve(process.cwd(), resource.file);
50
+ if (!uri) {
51
+ uri = pathToFileURL(filePath).toString();
52
+ }
53
+ if (!handler) {
54
+ handler = async (requestUri) => {
55
+ try {
56
+ const content = await readFile(filePath, "utf-8");
57
+ return {
58
+ contents: [{
59
+ uri: requestUri.toString(),
60
+ mimeType: resource.metadata?.mimeType || getMimeType(filePath),
61
+ text: content
62
+ }]
63
+ };
64
+ } catch (error) {
65
+ throw new Error(`Failed to read file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
66
+ }
67
+ };
68
+ }
69
+ }
70
+ if (!uri) {
71
+ throw new Error(`Resource ${name} is missing a URI`);
72
+ }
73
+ if (!handler) {
74
+ throw new Error(`Resource ${name} is missing a handler`);
75
+ }
76
+ if (typeof uri === "string") {
77
+ return server.registerResource(
78
+ name,
79
+ uri,
80
+ metadata,
81
+ handler
82
+ );
83
+ } else {
84
+ return server.registerResource(
85
+ name,
86
+ uri,
87
+ metadata,
88
+ handler
89
+ );
90
+ }
91
+ }
92
+ export function defineMcpResource(definition) {
93
+ return definition;
94
+ }
@@ -0,0 +1,64 @@
1
+ import type { z, ZodRawShape, ZodTypeAny } from 'zod';
2
+ import type { CallToolResult, ServerRequest, ServerNotification, ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
3
+ import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
4
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
+ /**
6
+ * Callback type for MCP tools, matching the SDK's ToolCallback type
7
+ */
8
+ export type McpToolCallback<Args extends ZodRawShape | undefined = ZodRawShape> = Args extends ZodRawShape ? (args: z.objectOutputType<Args, ZodTypeAny>, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => CallToolResult | Promise<CallToolResult> : (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => CallToolResult | Promise<CallToolResult>;
9
+ /**
10
+ * Definition of an MCP tool matching the SDK's registerTool signature
11
+ * This structure is identical to what you'd pass to server.registerTool()
12
+ */
13
+ export interface McpToolDefinition<InputSchema extends ZodRawShape | undefined = ZodRawShape, OutputSchema extends ZodRawShape = ZodRawShape> {
14
+ name?: string;
15
+ title?: string;
16
+ description?: string;
17
+ inputSchema?: InputSchema;
18
+ outputSchema?: OutputSchema;
19
+ annotations?: ToolAnnotations;
20
+ _meta?: Record<string, unknown>;
21
+ handler: McpToolCallback<InputSchema>;
22
+ }
23
+ /**
24
+ * Helper function to register a tool from a McpToolDefinition
25
+ * This provides better type inference when registering tools
26
+ */
27
+ export declare function registerToolFromDefinition<InputSchema extends ZodRawShape | undefined = ZodRawShape, OutputSchema extends ZodRawShape = ZodRawShape>(server: McpServer, tool: McpToolDefinition<InputSchema, OutputSchema>): import("@modelcontextprotocol/sdk/server/mcp.js").RegisteredTool;
28
+ /**
29
+ * Define an MCP tool that will be automatically registered
30
+ *
31
+ * This function matches the exact structure of server.registerTool() from the MCP SDK,
32
+ * making it easy to migrate code from the SDK to this module.
33
+ *
34
+ * If `name` or `title` are not provided, they will be automatically generated from the filename
35
+ * (e.g., `list_documentation.ts` → `name: 'list-documentation'`, `title: 'List Documentation'`).
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * // server/mcp/tools/my-tool.ts
40
+ * import { z } from 'zod'
41
+ *
42
+ * export default defineMcpTool({
43
+ * name: 'calculate-bmi',
44
+ * title: 'BMI Calculator',
45
+ * description: 'Calculate Body Mass Index',
46
+ * inputSchema: {
47
+ * weightKg: z.number(),
48
+ * heightM: z.number()
49
+ * },
50
+ * outputSchema: { bmi: z.number() },
51
+ * handler: async ({ weightKg, heightM }) => {
52
+ * const output = { bmi: weightKg / (heightM * heightM) }
53
+ * return {
54
+ * content: [{
55
+ * type: 'text',
56
+ * text: JSON.stringify(output)
57
+ * }],
58
+ * structuredContent: output
59
+ * }
60
+ * }
61
+ * })
62
+ * ```
63
+ */
64
+ export declare function defineMcpTool<const InputSchema extends ZodRawShape | undefined = ZodRawShape, const OutputSchema extends ZodRawShape = ZodRawShape>(definition: McpToolDefinition<InputSchema, OutputSchema>): McpToolDefinition<InputSchema, OutputSchema>;
@@ -0,0 +1,38 @@
1
+ import { enrichNameTitle } from "./utils.js";
2
+ export function registerToolFromDefinition(server, tool) {
3
+ const { name, title } = enrichNameTitle({
4
+ name: tool.name,
5
+ title: tool.title,
6
+ _meta: tool._meta,
7
+ type: "tool"
8
+ });
9
+ if (tool.inputSchema) {
10
+ return server.registerTool(
11
+ name,
12
+ {
13
+ title,
14
+ description: tool.description,
15
+ inputSchema: tool.inputSchema,
16
+ outputSchema: tool.outputSchema,
17
+ annotations: tool.annotations,
18
+ _meta: tool._meta
19
+ },
20
+ tool.handler
21
+ );
22
+ } else {
23
+ return server.registerTool(
24
+ name,
25
+ {
26
+ title,
27
+ description: tool.description,
28
+ outputSchema: tool.outputSchema,
29
+ annotations: tool.annotations,
30
+ _meta: tool._meta
31
+ },
32
+ tool.handler
33
+ );
34
+ }
35
+ }
36
+ export function defineMcpTool(definition) {
37
+ return definition;
38
+ }
@@ -0,0 +1,14 @@
1
+ export interface EnrichNameTitleOptions {
2
+ name?: string;
3
+ title?: string;
4
+ _meta?: Record<string, unknown>;
5
+ type: 'tool' | 'resource' | 'prompt';
6
+ }
7
+ export interface EnrichNameTitleResult {
8
+ name: string;
9
+ title?: string;
10
+ }
11
+ /**
12
+ * Enrich name and title from filename if missing
13
+ */
14
+ export declare function enrichNameTitle(options: EnrichNameTitleOptions): EnrichNameTitleResult;
@@ -0,0 +1,23 @@
1
+ import { kebabCase, titleCase } from "scule";
2
+ export function enrichNameTitle(options) {
3
+ const { name, title, _meta, type } = options;
4
+ const filename = _meta?.filename;
5
+ let enrichedName = name;
6
+ let enrichedTitle = title;
7
+ if (filename) {
8
+ const nameWithoutExt = filename.replace(/\.(ts|js|mts|mjs)$/, "");
9
+ if (!enrichedName) {
10
+ enrichedName = kebabCase(nameWithoutExt);
11
+ }
12
+ if (!enrichedTitle) {
13
+ enrichedTitle = titleCase(nameWithoutExt);
14
+ }
15
+ }
16
+ if (!enrichedName) {
17
+ throw new Error(`Failed to auto-generate ${type} name from filename. Please provide a name explicitly.`);
18
+ }
19
+ return {
20
+ name: enrichedName,
21
+ title: enrichedTitle
22
+ };
23
+ }
@@ -0,0 +1,3 @@
1
+ import type { Nuxt } from 'nuxt/schema';
2
+ import type { ModuleOptions } from '../../../../module.js';
3
+ export declare function addDevToolsCustomTabs(nuxt: Nuxt, options: ModuleOptions): void;
@@ -0,0 +1,342 @@
1
+ import { logger } from "@nuxt/kit";
2
+ import { spawn } from "node:child_process";
3
+ const log = logger.withTag("@nuxtjs/mcp-toolkit");
4
+ const INSPECTOR_TIMEOUT = 15e3;
5
+ const HEALTH_CHECK_TIMEOUT = 3e3;
6
+ const INSPECTOR_DEFAULT_CLIENT_PORT = 6274;
7
+ const INSPECTOR_DEFAULT_SERVER_PORT = 6277;
8
+ const HEALTH_CHECK_RETRIES = 5;
9
+ const MAX_BUFFER_SIZE = 10240;
10
+ const ERROR_PATTERNS = [
11
+ /PORT IS IN USE/i,
12
+ /EADDRINUSE/i,
13
+ /address already in use/i,
14
+ /port \d+ is already in use/i
15
+ ];
16
+ let inspectorProcess = null;
17
+ let inspectorUrl = null;
18
+ let isReady = false;
19
+ let promise = null;
20
+ let proxyAuthToken = null;
21
+ function resetState() {
22
+ inspectorProcess = null;
23
+ inspectorUrl = null;
24
+ isReady = false;
25
+ proxyAuthToken = null;
26
+ }
27
+ function buildInspectorUrl(baseUrl, mcpServerUrl) {
28
+ const urlObj = new URL(baseUrl);
29
+ urlObj.searchParams.set("transport", "streamable-http");
30
+ urlObj.searchParams.set("serverUrl", mcpServerUrl);
31
+ if (proxyAuthToken) {
32
+ urlObj.searchParams.set("MCP_PROXY_AUTH_TOKEN", proxyAuthToken);
33
+ }
34
+ return urlObj.toString();
35
+ }
36
+ function extractProxyToken(text) {
37
+ const match = text.match(/Session token:\s+([a-f0-9]+)/i);
38
+ return match?.[1] ?? null;
39
+ }
40
+ function limitBuffer(buffer, maxSize) {
41
+ if (buffer.length <= maxSize) {
42
+ return buffer;
43
+ }
44
+ return buffer.slice(-maxSize);
45
+ }
46
+ function getInspectorClientPort() {
47
+ if (process.env.CLIENT_PORT) {
48
+ const port = Number.parseInt(process.env.CLIENT_PORT, 10);
49
+ if (!Number.isNaN(port) && port > 0 && port < 65536) {
50
+ return port;
51
+ }
52
+ }
53
+ if (process.env.MCP_INSPECTOR_PORT) {
54
+ const port = Number.parseInt(process.env.MCP_INSPECTOR_PORT, 10);
55
+ if (!Number.isNaN(port) && port > 0 && port < 65536) {
56
+ return port;
57
+ }
58
+ }
59
+ return INSPECTOR_DEFAULT_CLIENT_PORT;
60
+ }
61
+ function getInspectorServerPort() {
62
+ if (process.env.SERVER_PORT) {
63
+ const port = Number.parseInt(process.env.SERVER_PORT, 10);
64
+ if (!Number.isNaN(port) && port > 0 && port < 65536) {
65
+ return port;
66
+ }
67
+ }
68
+ return INSPECTOR_DEFAULT_SERVER_PORT;
69
+ }
70
+ function buildInspectorBaseUrl(port) {
71
+ return `http://localhost:${port}`;
72
+ }
73
+ function containsError(text) {
74
+ return ERROR_PATTERNS.some((pattern) => pattern.test(text));
75
+ }
76
+ async function waitForInspectorReady(url) {
77
+ for (let attempt = 0; attempt < HEALTH_CHECK_RETRIES; attempt++) {
78
+ try {
79
+ const controller = new AbortController();
80
+ const timeoutId = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT);
81
+ const response = await fetch(url, {
82
+ method: "GET",
83
+ signal: controller.signal
84
+ });
85
+ clearTimeout(timeoutId);
86
+ if (response.status >= 200 && response.status < 400) {
87
+ if (!proxyAuthToken) {
88
+ const text = await response.text().catch(() => "");
89
+ const token = extractProxyToken(text);
90
+ if (token) {
91
+ proxyAuthToken = token;
92
+ }
93
+ }
94
+ return true;
95
+ }
96
+ } catch {
97
+ if (attempt < HEALTH_CHECK_RETRIES - 1) {
98
+ continue;
99
+ }
100
+ }
101
+ }
102
+ return false;
103
+ }
104
+ function cleanupListeners(process2, handlers) {
105
+ if (handlers.stdout && process2.stdout) {
106
+ process2.stdout.removeListener("data", handlers.stdout);
107
+ }
108
+ if (handlers.stderr && process2.stderr) {
109
+ process2.stderr.removeListener("data", handlers.stderr);
110
+ }
111
+ if (handlers.error) {
112
+ process2.removeListener("error", handlers.error);
113
+ }
114
+ if (handlers.exit) {
115
+ process2.removeListener("exit", handlers.exit);
116
+ }
117
+ }
118
+ async function launchMcpInspector(nuxt, options) {
119
+ if (inspectorProcess) {
120
+ return;
121
+ }
122
+ const mcpServerUrl = `http://localhost:${nuxt.options.devServer?.port || 3e3}${options.route || "/mcp"}`;
123
+ const inspectorClientPort = getInspectorClientPort();
124
+ const inspectorServerPort = getInspectorServerPort();
125
+ const inspectorBaseUrl = buildInspectorBaseUrl(inspectorClientPort);
126
+ log.info("\u{1F680} Launching MCP Inspector...");
127
+ try {
128
+ const env = {
129
+ ...globalThis.process.env,
130
+ MCP_AUTO_OPEN_ENABLED: "false",
131
+ CLIENT_PORT: String(inspectorClientPort),
132
+ SERVER_PORT: String(inspectorServerPort)
133
+ };
134
+ inspectorProcess = spawn("npx", [
135
+ "-y",
136
+ "@modelcontextprotocol/inspector",
137
+ "streamable-http",
138
+ mcpServerUrl
139
+ ], {
140
+ stdio: ["ignore", "pipe", "pipe"],
141
+ env
142
+ });
143
+ const childProcess = inspectorProcess;
144
+ let stdoutBuffer = "";
145
+ let stderrBuffer = "";
146
+ let isResolved = false;
147
+ let timeoutId = null;
148
+ await new Promise((res, rej) => {
149
+ const resolve = () => {
150
+ if (timeoutId) {
151
+ clearTimeout(timeoutId);
152
+ }
153
+ cleanupListeners(childProcess, handlers);
154
+ res();
155
+ };
156
+ const reject = (error) => {
157
+ if (timeoutId) {
158
+ clearTimeout(timeoutId);
159
+ }
160
+ cleanupListeners(childProcess, handlers);
161
+ resetState();
162
+ rej(error);
163
+ };
164
+ const handlers = {
165
+ stdout: (data) => {
166
+ const text = data.toString();
167
+ stdoutBuffer += text;
168
+ stdoutBuffer = limitBuffer(stdoutBuffer, MAX_BUFFER_SIZE);
169
+ if (!proxyAuthToken) {
170
+ const token = extractProxyToken(text);
171
+ if (token) {
172
+ proxyAuthToken = token;
173
+ }
174
+ }
175
+ },
176
+ stderr: (data) => {
177
+ const text = data.toString();
178
+ stderrBuffer += text;
179
+ stderrBuffer = limitBuffer(stderrBuffer, MAX_BUFFER_SIZE);
180
+ if (!proxyAuthToken) {
181
+ const token = extractProxyToken(text);
182
+ if (token) {
183
+ proxyAuthToken = token;
184
+ }
185
+ }
186
+ if (containsError(stderrBuffer) && !isResolved) {
187
+ isResolved = true;
188
+ log.error("\u274C MCP Inspector port is already in use");
189
+ handleError(
190
+ new Error("MCP Inspector port is already in use. Please stop any running Inspector instances."),
191
+ timeoutId,
192
+ reject
193
+ );
194
+ }
195
+ },
196
+ error: (error) => {
197
+ if (!isResolved) {
198
+ isResolved = true;
199
+ log.error(`\u274C Failed to start inspector process: ${error.message}`);
200
+ handleError(error, timeoutId, reject);
201
+ }
202
+ },
203
+ exit: (code) => {
204
+ if (!isResolved) {
205
+ isResolved = true;
206
+ const errorOutput = stderrBuffer || stdoutBuffer || "No output";
207
+ const lastLines = errorOutput.split("\n").slice(-10).join("\n");
208
+ const errorMessage = code !== 0 && code !== null ? `MCP Inspector exited with code ${code}. Last output:
209
+ ${lastLines}` : `MCP Inspector exited unexpectedly. Last output:
210
+ ${lastLines}`;
211
+ log.error(`\u274C Inspector process exited with code ${code ?? "null"}`);
212
+ handleError(new Error(errorMessage), timeoutId, reject);
213
+ }
214
+ }
215
+ };
216
+ childProcess.stdout?.on("data", handlers.stdout);
217
+ childProcess.stderr?.on("data", handlers.stderr);
218
+ childProcess.on("error", handlers.error);
219
+ childProcess.on("exit", handlers.exit);
220
+ const startHealthCheck = async () => {
221
+ for (let delay = 500; delay <= 3e3; delay += 500) {
222
+ await new Promise((res2) => setTimeout(res2, delay));
223
+ if (isResolved) {
224
+ return;
225
+ }
226
+ if (childProcess.killed || childProcess.exitCode !== null) {
227
+ if (!isResolved) {
228
+ isResolved = true;
229
+ const errorOutput = stderrBuffer || stdoutBuffer || "Process exited unexpectedly";
230
+ log.error(`\u274C Inspector process exited before health check (code: ${childProcess.exitCode})`);
231
+ handleError(
232
+ new Error(`MCP Inspector process exited before health check. Output:
233
+ ${errorOutput.slice(0, 500)}`),
234
+ timeoutId,
235
+ reject
236
+ );
237
+ }
238
+ return;
239
+ }
240
+ const isReady2 = await waitForInspectorReady(inspectorBaseUrl);
241
+ if (isReady2 && !isResolved) {
242
+ isResolved = true;
243
+ log.success(`\u2705 MCP Inspector is ready at ${inspectorBaseUrl}`);
244
+ handleUrlDetected(inspectorBaseUrl, mcpServerUrl, timeoutId, resolve);
245
+ return;
246
+ }
247
+ }
248
+ if (!isResolved) {
249
+ log.warn("\u26A0\uFE0F Inspector health check failed");
250
+ }
251
+ };
252
+ startHealthCheck().catch((error) => {
253
+ if (!isResolved) {
254
+ isResolved = true;
255
+ handleError(error, timeoutId, reject);
256
+ }
257
+ });
258
+ timeoutId = setTimeout(() => {
259
+ if (!isResolved) {
260
+ isResolved = true;
261
+ const errorOutput = stderrBuffer || stdoutBuffer || "Unknown error";
262
+ const lastLines = errorOutput.split("\n").slice(-10).join("\n");
263
+ log.error(`\u274C Inspector startup timeout after ${INSPECTOR_TIMEOUT}ms`);
264
+ handleError(
265
+ new Error(`MCP Inspector failed to start - timeout after ${INSPECTOR_TIMEOUT}ms.
266
+ Last output:
267
+ ${lastLines}`),
268
+ null,
269
+ reject
270
+ );
271
+ }
272
+ }, INSPECTOR_TIMEOUT);
273
+ });
274
+ } catch (error) {
275
+ log.error("\u274C Failed to launch MCP Inspector:", error);
276
+ resetState();
277
+ throw error;
278
+ }
279
+ }
280
+ function handleUrlDetected(baseUrl, mcpServerUrl, timeoutId, resolve) {
281
+ if (timeoutId) {
282
+ clearTimeout(timeoutId);
283
+ }
284
+ const builtUrl = buildInspectorUrl(baseUrl, mcpServerUrl);
285
+ inspectorUrl = builtUrl;
286
+ isReady = true;
287
+ resolve();
288
+ }
289
+ function handleError(error, timeoutId, reject) {
290
+ if (timeoutId) {
291
+ clearTimeout(timeoutId);
292
+ }
293
+ reject(error);
294
+ }
295
+ function stopMcpInspector() {
296
+ if (inspectorProcess) {
297
+ inspectorProcess.kill();
298
+ resetState();
299
+ }
300
+ }
301
+ export function addDevToolsCustomTabs(nuxt, options) {
302
+ nuxt.hook("devtools:customTabs", (tabs) => {
303
+ if (!options.enabled) {
304
+ return;
305
+ }
306
+ tabs.push({
307
+ category: "server",
308
+ name: "mcp-inspector",
309
+ title: "MCP Inspector",
310
+ icon: "i-lucide-circuit-board",
311
+ view: isReady && inspectorUrl ? {
312
+ type: "iframe",
313
+ src: inspectorUrl
314
+ } : {
315
+ type: "launch",
316
+ description: "Launch MCP Inspector to test/debug your MCP server",
317
+ actions: [
318
+ {
319
+ label: promise ? "Starting..." : "Launch Inspector",
320
+ pending: !!promise,
321
+ handle() {
322
+ promise = promise || launchMcpInspector(nuxt, options).finally(() => {
323
+ promise = null;
324
+ });
325
+ return promise;
326
+ }
327
+ },
328
+ ...inspectorProcess ? [{
329
+ label: "Stop Inspector",
330
+ handle() {
331
+ stopMcpInspector();
332
+ promise = null;
333
+ }
334
+ }] : []
335
+ ]
336
+ }
337
+ });
338
+ });
339
+ nuxt.hook("close", () => {
340
+ stopMcpInspector();
341
+ });
342
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
2
+ export default _default;
@@ -0,0 +1,37 @@
1
+ import { getRouterParam } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { tools } from "#nuxt-mcp/tools.mjs";
4
+ import { resources } from "#nuxt-mcp/resources.mjs";
5
+ import { prompts } from "#nuxt-mcp/prompts.mjs";
6
+ import { handlers } from "#nuxt-mcp/handlers.mjs";
7
+ import { createMcpHandler } from "./utils.js";
8
+ import { getMcpConfig } from "./config.js";
9
+ export default createMcpHandler((event) => {
10
+ const runtimeConfig = useRuntimeConfig(event).mcp;
11
+ const config = getMcpConfig(runtimeConfig);
12
+ const handlerName = getRouterParam(event, "handler");
13
+ if (handlerName) {
14
+ const handlerDef = handlers.find(
15
+ (h) => h.name === handlerName
16
+ );
17
+ if (!handlerDef) {
18
+ throw new Error(`Handler "${handlerName}" not found`);
19
+ }
20
+ return {
21
+ name: handlerDef.name,
22
+ version: handlerDef.version ?? config.version,
23
+ browserRedirect: handlerDef.browserRedirect ?? config.browserRedirect,
24
+ tools: handlerDef.tools,
25
+ resources: handlerDef.resources,
26
+ prompts: handlerDef.prompts
27
+ };
28
+ }
29
+ return {
30
+ name: config.name || "MCP Server",
31
+ version: config.version,
32
+ browserRedirect: config.browserRedirect,
33
+ tools,
34
+ resources,
35
+ prompts
36
+ };
37
+ });
@@ -0,0 +1,28 @@
1
+ import { type LoadResult } from './utils.js';
2
+ export interface LoaderPaths {
3
+ tools: string[];
4
+ resources: string[];
5
+ prompts: string[];
6
+ handlers?: string[];
7
+ }
8
+ export interface HandlerRouteInfo {
9
+ name: string;
10
+ route?: string;
11
+ }
12
+ declare function loadHandlers(paths?: string[]): Promise<LoadResult>;
13
+ export declare function loadTools(paths: string[]): Promise<LoadResult>;
14
+ export declare function loadResources(paths: string[]): Promise<LoadResult>;
15
+ export declare function loadPrompts(paths: string[]): Promise<LoadResult>;
16
+ export { loadHandlers };
17
+ export declare function loadAllDefinitions(paths: LoaderPaths): Promise<{
18
+ total: number;
19
+ tools: LoadResult;
20
+ resources: LoadResult;
21
+ prompts: LoadResult;
22
+ handlers: LoadResult;
23
+ }>;
24
+ /**
25
+ * Get handler route information from loaded handlers
26
+ * This is used at runtime to identify handlers by their routes
27
+ */
28
+ export declare function getHandlerRoutes(): Promise<HandlerRouteInfo[]>;