@nuxtjs/mcp-toolkit 0.7.0 → 0.10.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/module.d.mts +17 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +27 -8
- package/dist/runtime/server/mcp/badge-image.d.ts +1 -1
- package/dist/runtime/server/mcp/badge-image.js +42 -126
- package/dist/runtime/server/mcp/config.d.ts +5 -0
- package/dist/runtime/server/mcp/config.js +17 -3
- package/dist/runtime/server/mcp/definitions/cache.d.ts +1 -1
- package/dist/runtime/server/mcp/definitions/cache.js +33 -2
- package/dist/runtime/server/mcp/definitions/handlers.d.ts +4 -4
- package/dist/runtime/server/mcp/definitions/prompts.d.ts +7 -0
- package/dist/runtime/server/mcp/definitions/resources.d.ts +13 -0
- package/dist/runtime/server/mcp/definitions/tools.d.ts +9 -0
- package/dist/runtime/server/mcp/definitions/utils.js +6 -1
- package/dist/runtime/server/mcp/handler.js +6 -4
- package/dist/runtime/server/mcp/loaders/utils.js +1 -1
- package/dist/runtime/server/mcp/providers/cloudflare.js +3 -1
- package/dist/runtime/server/mcp/providers/node.js +83 -9
- package/dist/runtime/server/mcp/providers/types.d.ts +1 -1
- package/dist/runtime/server/mcp/server.d.ts +27 -0
- package/dist/runtime/server/mcp/server.js +44 -0
- package/dist/runtime/server/mcp/session.d.ts +12 -0
- package/dist/runtime/server/mcp/session.js +21 -0
- package/dist/runtime/server/mcp/utils.d.ts +17 -5
- package/dist/runtime/server/mcp/utils.js +41 -6
- package/dist/runtime/server/types/hooks.d.ts +4 -0
- package/package.json +15 -16
package/dist/module.d.mts
CHANGED
|
@@ -35,6 +35,23 @@ interface ModuleOptions {
|
|
|
35
35
|
* @default 'mcp'
|
|
36
36
|
*/
|
|
37
37
|
dir?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Enable MCP session management (stateful transport).
|
|
40
|
+
* When enabled, the server assigns session IDs and maintains state across requests,
|
|
41
|
+
* enabling SSE streaming, server-to-client notifications, and resumability.
|
|
42
|
+
*
|
|
43
|
+
* Pass `true` for defaults or an object to configure session behavior.
|
|
44
|
+
* @default false
|
|
45
|
+
* @see https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#session-management
|
|
46
|
+
*/
|
|
47
|
+
sessions?: boolean | {
|
|
48
|
+
enabled?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Maximum session duration in milliseconds. Sessions inactive longer than this are cleaned up.
|
|
51
|
+
* @default 1800000 (30 minutes)
|
|
52
|
+
*/
|
|
53
|
+
maxDuration?: number;
|
|
54
|
+
};
|
|
38
55
|
}
|
|
39
56
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
40
57
|
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -2,7 +2,6 @@ import { logger, createResolver, defineNuxtModule, addComponent, addServerTempla
|
|
|
2
2
|
import { loadAllDefinitions } from '../dist/runtime/server/mcp/loaders/index.js';
|
|
3
3
|
import { defaultMcpConfig, getMcpConfig } from '../dist/runtime/server/mcp/config.js';
|
|
4
4
|
import { ROUTES } from '../dist/runtime/server/mcp/constants.js';
|
|
5
|
-
import { addDevToolsCustomTabs } from '../dist/runtime/server/mcp/devtools/index.js';
|
|
6
5
|
import { execSync } from 'node:child_process';
|
|
7
6
|
import { existsSync, readFileSync } from 'node:fs';
|
|
8
7
|
import { homedir } from 'node:os';
|
|
@@ -72,7 +71,7 @@ function generateDeeplinkUrl(baseUrl, route, ide, serverName) {
|
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
const name = "@nuxtjs/mcp-toolkit";
|
|
75
|
-
const version = "0.
|
|
74
|
+
const version = "0.10.0";
|
|
76
75
|
|
|
77
76
|
const log = logger.withTag("@nuxtjs/mcp-toolkit");
|
|
78
77
|
const { resolve } = createResolver(import.meta.url);
|
|
@@ -86,15 +85,23 @@ const module$1 = defineNuxtModule({
|
|
|
86
85
|
},
|
|
87
86
|
defaults: defaultMcpConfig,
|
|
88
87
|
async setup(options, nuxt) {
|
|
89
|
-
|
|
88
|
+
const nitroOptions = nuxt.options.nitro;
|
|
89
|
+
if (nitroOptions?.static || nuxt.options._generate) {
|
|
90
90
|
log.warn("@nuxtjs/mcp-toolkit is not compatible with `nuxt generate` as it needs a server to run.");
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
93
|
const resolver = createResolver(import.meta.url);
|
|
94
|
+
if (typeof options.sessions === "boolean") {
|
|
95
|
+
options.sessions = { enabled: options.sessions };
|
|
96
|
+
}
|
|
94
97
|
const mcpConfig = getMcpConfig(options);
|
|
95
98
|
if (!options.enabled) {
|
|
96
99
|
return;
|
|
97
100
|
}
|
|
101
|
+
if (mcpConfig.sessions.enabled && nitroOptions) {
|
|
102
|
+
nitroOptions.storage ??= {};
|
|
103
|
+
nitroOptions.storage["mcp:sessions"] ??= { driver: "memory" };
|
|
104
|
+
}
|
|
98
105
|
addComponent({
|
|
99
106
|
name: "InstallButton",
|
|
100
107
|
filePath: resolver.resolve("runtime/components/InstallButton.vue")
|
|
@@ -163,10 +170,12 @@ const module$1 = defineNuxtModule({
|
|
|
163
170
|
path: resolver.resolve("runtime/server/types.server.d.ts")
|
|
164
171
|
});
|
|
165
172
|
});
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
173
|
+
if (nitroOptions) {
|
|
174
|
+
nitroOptions.typescript ??= {};
|
|
175
|
+
nitroOptions.typescript.tsConfig ??= {};
|
|
176
|
+
nitroOptions.typescript.tsConfig.include ??= [];
|
|
177
|
+
nitroOptions.typescript.tsConfig.include.push(resolver.resolve("runtime/server/types.server.d.ts"));
|
|
178
|
+
}
|
|
170
179
|
let isCloudflare = false;
|
|
171
180
|
if (!nuxt.options.dev) {
|
|
172
181
|
nuxt.hook("nitro:config", (nitroConfig) => {
|
|
@@ -182,6 +191,8 @@ const module$1 = defineNuxtModule({
|
|
|
182
191
|
}
|
|
183
192
|
});
|
|
184
193
|
const mcpDefinitionsPath = resolver.resolve("runtime/server/mcp/definitions");
|
|
194
|
+
const mcpSessionPath = resolver.resolve("runtime/server/mcp/session");
|
|
195
|
+
const mcpServerPath = resolver.resolve("runtime/server/mcp/server");
|
|
185
196
|
addServerImports([
|
|
186
197
|
"defineMcpTool",
|
|
187
198
|
"defineMcpResource",
|
|
@@ -192,6 +203,10 @@ const module$1 = defineNuxtModule({
|
|
|
192
203
|
"errorResult",
|
|
193
204
|
"imageResult"
|
|
194
205
|
].map((name2) => ({ name: name2, from: mcpDefinitionsPath })));
|
|
206
|
+
addServerImports([
|
|
207
|
+
{ name: "useMcpSession", from: mcpSessionPath },
|
|
208
|
+
{ name: "useMcpServer", from: mcpServerPath }
|
|
209
|
+
]);
|
|
195
210
|
addServerHandler({
|
|
196
211
|
route: options.route,
|
|
197
212
|
handler: resolver.resolve("runtime/server/mcp/handler")
|
|
@@ -204,7 +219,11 @@ const module$1 = defineNuxtModule({
|
|
|
204
219
|
route: `${options.route}/badge.svg`,
|
|
205
220
|
handler: resolver.resolve("runtime/server/mcp/badge-image")
|
|
206
221
|
});
|
|
207
|
-
|
|
222
|
+
if (nuxt.options.dev) {
|
|
223
|
+
import('../dist/runtime/server/mcp/devtools/index.js').then(({ addDevToolsCustomTabs }) => {
|
|
224
|
+
addDevToolsCustomTabs(nuxt, options);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
208
227
|
}
|
|
209
228
|
});
|
|
210
229
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
|
-
import satori from "satori";
|
|
3
2
|
const IDE_CONFIG = {
|
|
4
3
|
cursor: {
|
|
5
4
|
defaultLabel: "Install MCP in Cursor"
|
|
@@ -8,125 +7,51 @@ const IDE_CONFIG = {
|
|
|
8
7
|
defaultLabel: "Install MCP in VS Code"
|
|
9
8
|
}
|
|
10
9
|
};
|
|
11
|
-
function
|
|
12
|
-
return
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
children: [
|
|
20
|
-
{
|
|
21
|
-
type: "path",
|
|
22
|
-
props: {
|
|
23
|
-
fill: "#666",
|
|
24
|
-
d: "M11.925 24l10.425-6-10.425-6L1.5 18l10.425 6z"
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
type: "path",
|
|
29
|
-
props: {
|
|
30
|
-
fill: "#888",
|
|
31
|
-
d: "M22.35 18V6L11.925 0v12l10.425 6z"
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
type: "path",
|
|
36
|
-
props: {
|
|
37
|
-
fill: "#777",
|
|
38
|
-
d: "M11.925 0L1.5 6v12l10.425-6V0z"
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
type: "path",
|
|
43
|
-
props: {
|
|
44
|
-
fill: "#555",
|
|
45
|
-
d: "M22.35 6L11.925 24V12L22.35 6z"
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
type: "path",
|
|
50
|
-
props: {
|
|
51
|
-
fill: "#333",
|
|
52
|
-
d: "M22.35 6l-10.425 6L1.5 6h20.85z"
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
]
|
|
56
|
-
}
|
|
57
|
-
};
|
|
10
|
+
function cursorIconSvg() {
|
|
11
|
+
return `<g transform="translate(8,7) scale(0.75)">
|
|
12
|
+
<path fill="#999" d="M11.925 24l10.425-6-10.425-6L1.5 18l10.425 6z"/>
|
|
13
|
+
<path fill="#bbb" d="M22.35 18V6L11.925 0v12l10.425 6z"/>
|
|
14
|
+
<path fill="#aaa" d="M11.925 0L1.5 6v12l10.425-6V0z"/>
|
|
15
|
+
<path fill="#888" d="M22.35 6L11.925 24V12L22.35 6z"/>
|
|
16
|
+
<path fill="#fff" d="M22.35 6l-10.425 6L1.5 6h20.85z"/>
|
|
17
|
+
</g>`;
|
|
58
18
|
}
|
|
59
|
-
function
|
|
60
|
-
return
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
width: 18,
|
|
64
|
-
height: 18,
|
|
65
|
-
viewBox: "0 0 24 24",
|
|
66
|
-
children: [
|
|
67
|
-
{
|
|
68
|
-
type: "path",
|
|
69
|
-
props: {
|
|
70
|
-
fill: "#007ACC",
|
|
71
|
-
d: "M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63l-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12L.326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128l9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z"
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
]
|
|
75
|
-
}
|
|
76
|
-
};
|
|
19
|
+
function vscodeIconSvg() {
|
|
20
|
+
return `<g transform="translate(8,7) scale(0.75)">
|
|
21
|
+
<path fill="#007ACC" d="M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63l-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12L.326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128l9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z"/>
|
|
22
|
+
</g>`;
|
|
77
23
|
}
|
|
78
|
-
function
|
|
79
|
-
|
|
24
|
+
function estimateTextWidth(text, fontSize) {
|
|
25
|
+
const ratio = fontSize / 13;
|
|
26
|
+
let width = 0;
|
|
27
|
+
for (const ch of text) {
|
|
28
|
+
if (ch === " ") width += 3.6;
|
|
29
|
+
else if ("iIl|1!:;.,".includes(ch)) width += 4.2;
|
|
30
|
+
else if (`fjrt()[]{}'"/`.includes(ch)) width += 5.2;
|
|
31
|
+
else if ("mwMW".includes(ch)) width += 10;
|
|
32
|
+
else if (ch >= "A" && ch <= "Z") width += 8.5;
|
|
33
|
+
else width += 7;
|
|
34
|
+
}
|
|
35
|
+
return width * ratio;
|
|
80
36
|
}
|
|
81
|
-
|
|
37
|
+
function generateBadgeSVG(options) {
|
|
82
38
|
const { label, color, textColor, borderColor, showIcon, ide } = options;
|
|
83
|
-
const icon = getIcon(ide);
|
|
84
|
-
const element = {
|
|
85
|
-
type: "div",
|
|
86
|
-
props: {
|
|
87
|
-
style: {
|
|
88
|
-
display: "flex",
|
|
89
|
-
alignItems: "center",
|
|
90
|
-
gap: "8px",
|
|
91
|
-
padding: "6px 8px",
|
|
92
|
-
fontSize: "14px",
|
|
93
|
-
fontWeight: 500,
|
|
94
|
-
color: `#${textColor}`,
|
|
95
|
-
backgroundColor: `#${color}`,
|
|
96
|
-
border: `1px solid #${borderColor}`
|
|
97
|
-
},
|
|
98
|
-
children: showIcon ? [icon, { type: "span", props: { children: label } }] : [{ type: "span", props: { children: label } }]
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
39
|
const iconWidth = showIcon ? 26 : 0;
|
|
102
|
-
const textWidth = label
|
|
103
|
-
const padding =
|
|
104
|
-
const width = Math.
|
|
40
|
+
const textWidth = estimateTextWidth(label, 13);
|
|
41
|
+
const padding = showIcon ? 24 : 22;
|
|
42
|
+
const width = Math.ceil(iconWidth + textWidth + padding);
|
|
105
43
|
const height = 32;
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
]
|
|
117
|
-
});
|
|
118
|
-
return svg;
|
|
44
|
+
const textX = showIcon ? 34 : width / 2;
|
|
45
|
+
const textAnchor = showIcon ? "start" : "middle";
|
|
46
|
+
const icon = showIcon ? ide === "vscode" ? vscodeIconSvg() : cursorIconSvg() : "";
|
|
47
|
+
const escapedLabel = label.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
48
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
|
|
49
|
+
<rect x="0.5" y="0.5" width="${width - 1}" height="${height - 1}" rx="4" fill="#${color}" stroke="#${borderColor}"/>
|
|
50
|
+
${icon}
|
|
51
|
+
<text x="${textX}" y="21" fill="#${textColor}" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500" text-anchor="${textAnchor}">${escapedLabel}</text>
|
|
52
|
+
</svg>`;
|
|
119
53
|
}
|
|
120
|
-
|
|
121
|
-
const response = await fetch(
|
|
122
|
-
"https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuI6fAZ9hjp-Ek-_EeA.woff"
|
|
123
|
-
);
|
|
124
|
-
if (!response.ok) {
|
|
125
|
-
throw new Error(`Failed to load font: ${response.status} ${response.statusText}`);
|
|
126
|
-
}
|
|
127
|
-
return response.arrayBuffer();
|
|
128
|
-
}
|
|
129
|
-
export default defineEventHandler(async (event) => {
|
|
54
|
+
export default defineEventHandler((event) => {
|
|
130
55
|
const query = getQuery(event);
|
|
131
56
|
const ide = query.ide || "cursor";
|
|
132
57
|
const ideConfig = IDE_CONFIG[ide] || IDE_CONFIG.cursor;
|
|
@@ -138,17 +63,8 @@ export default defineEventHandler(async (event) => {
|
|
|
138
63
|
borderColor: query.borderColor || "404040",
|
|
139
64
|
showIcon: query.icon !== "false"
|
|
140
65
|
};
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return svg;
|
|
146
|
-
} catch {
|
|
147
|
-
setHeader(event, "Content-Type", "image/svg+xml");
|
|
148
|
-
setHeader(event, "Cache-Control", "no-cache");
|
|
149
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="140" height="32">
|
|
150
|
-
<rect width="140" height="32" fill="#171717" stroke="#404040"/>
|
|
151
|
-
<text x="70" y="20" fill="#fff" font-size="12" text-anchor="middle">${options.label}</text>
|
|
152
|
-
</svg>`;
|
|
153
|
-
}
|
|
66
|
+
const svg = generateBadgeSVG(options);
|
|
67
|
+
setHeader(event, "Content-Type", "image/svg+xml");
|
|
68
|
+
setHeader(event, "Cache-Control", "public, max-age=86400");
|
|
69
|
+
return svg;
|
|
154
70
|
});
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export interface McpSessionsConfig {
|
|
2
|
+
enabled: boolean;
|
|
3
|
+
maxDuration: number;
|
|
4
|
+
}
|
|
1
5
|
export interface McpConfig {
|
|
2
6
|
enabled: boolean;
|
|
3
7
|
route: string;
|
|
@@ -5,6 +9,7 @@ export interface McpConfig {
|
|
|
5
9
|
name: string;
|
|
6
10
|
version: string;
|
|
7
11
|
dir: string;
|
|
12
|
+
sessions: McpSessionsConfig;
|
|
8
13
|
}
|
|
9
14
|
export declare const defaultMcpConfig: McpConfig;
|
|
10
15
|
export declare function getMcpConfig(partial?: Partial<McpConfig>): McpConfig;
|
|
@@ -1,12 +1,26 @@
|
|
|
1
|
-
import { defu } from "defu";
|
|
2
1
|
export const defaultMcpConfig = {
|
|
3
2
|
enabled: true,
|
|
4
3
|
route: "/mcp",
|
|
5
4
|
browserRedirect: "/",
|
|
6
5
|
name: "",
|
|
7
6
|
version: "1.0.0",
|
|
8
|
-
dir: "mcp"
|
|
7
|
+
dir: "mcp",
|
|
8
|
+
sessions: {
|
|
9
|
+
enabled: false,
|
|
10
|
+
maxDuration: 30 * 60 * 1e3
|
|
11
|
+
// 30 minutes
|
|
12
|
+
}
|
|
9
13
|
};
|
|
10
14
|
export function getMcpConfig(partial) {
|
|
11
|
-
|
|
15
|
+
if (!partial) return { ...defaultMcpConfig };
|
|
16
|
+
const sessions = partial.sessions ? { ...defaultMcpConfig.sessions, ...partial.sessions } : defaultMcpConfig.sessions;
|
|
17
|
+
return {
|
|
18
|
+
enabled: partial.enabled ?? defaultMcpConfig.enabled,
|
|
19
|
+
route: partial.route ?? defaultMcpConfig.route,
|
|
20
|
+
browserRedirect: partial.browserRedirect ?? defaultMcpConfig.browserRedirect,
|
|
21
|
+
name: partial.name ?? defaultMcpConfig.name,
|
|
22
|
+
version: partial.version ?? defaultMcpConfig.version,
|
|
23
|
+
dir: partial.dir ?? defaultMcpConfig.dir,
|
|
24
|
+
sessions
|
|
25
|
+
};
|
|
12
26
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cache duration strings
|
|
2
|
+
* Cache duration strings (e.g. '1h', '30m', '2 days')
|
|
3
3
|
*/
|
|
4
4
|
export type MsCacheDuration = '1s' | '5s' | '10s' | '15s' | '30s' | '45s' | '1m' | '2m' | '5m' | '10m' | '15m' | '30m' | '45m' | '1h' | '2h' | '3h' | '4h' | '6h' | '8h' | '12h' | '24h' | '1d' | '2d' | '3d' | '7d' | '14d' | '30d' | '1w' | '2w' | '4w' | '1 second' | '1 minute' | '1 hour' | '1 day' | '1 week' | '2 seconds' | '5 seconds' | '10 seconds' | '30 seconds' | '2 minutes' | '5 minutes' | '10 minutes' | '15 minutes' | '30 minutes' | '2 hours' | '3 hours' | '6 hours' | '12 hours' | '24 hours' | '2 days' | '3 days' | '7 days' | '14 days' | '30 days' | '2 weeks' | '4 weeks' | (string & Record<never, never>);
|
|
5
5
|
/**
|
|
@@ -1,10 +1,41 @@
|
|
|
1
1
|
import { defineCachedFunction } from "nitropack/runtime";
|
|
2
|
-
|
|
2
|
+
const DURATION_UNITS = {
|
|
3
|
+
ms: 1,
|
|
4
|
+
millisecond: 1,
|
|
5
|
+
milliseconds: 1,
|
|
6
|
+
s: 1e3,
|
|
7
|
+
sec: 1e3,
|
|
8
|
+
second: 1e3,
|
|
9
|
+
seconds: 1e3,
|
|
10
|
+
m: 6e4,
|
|
11
|
+
min: 6e4,
|
|
12
|
+
minute: 6e4,
|
|
13
|
+
minutes: 6e4,
|
|
14
|
+
h: 36e5,
|
|
15
|
+
hr: 36e5,
|
|
16
|
+
hour: 36e5,
|
|
17
|
+
hours: 36e5,
|
|
18
|
+
d: 864e5,
|
|
19
|
+
day: 864e5,
|
|
20
|
+
days: 864e5,
|
|
21
|
+
w: 6048e5,
|
|
22
|
+
week: 6048e5,
|
|
23
|
+
weeks: 6048e5
|
|
24
|
+
};
|
|
25
|
+
function parseDurationToMs(str) {
|
|
26
|
+
const match = str.trim().match(/^(\d+)\s*([a-z]+)$/i);
|
|
27
|
+
if (!match) return void 0;
|
|
28
|
+
const value = Number(match[1]);
|
|
29
|
+
const unit = match[2].toLowerCase();
|
|
30
|
+
const multiplier = DURATION_UNITS[unit];
|
|
31
|
+
if (multiplier === void 0) return void 0;
|
|
32
|
+
return value * multiplier;
|
|
33
|
+
}
|
|
3
34
|
export function parseCacheDuration(duration) {
|
|
4
35
|
if (typeof duration === "number") {
|
|
5
36
|
return duration;
|
|
6
37
|
}
|
|
7
|
-
const parsed =
|
|
38
|
+
const parsed = parseDurationToMs(duration);
|
|
8
39
|
if (parsed === void 0) {
|
|
9
40
|
throw new Error(`Invalid cache duration: ${duration}`);
|
|
10
41
|
}
|
|
@@ -29,7 +29,7 @@ import type { McpPromptDefinition } from './prompts.js';
|
|
|
29
29
|
* }
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
|
-
export type McpMiddleware = (event: H3Event, next: () => Promise<Response
|
|
32
|
+
export type McpMiddleware = (event: H3Event, next: () => Promise<Response>) => Promise<Response | void> | Response | void;
|
|
33
33
|
/**
|
|
34
34
|
* Options for defining a custom MCP handler
|
|
35
35
|
* @see https://mcp-toolkit.nuxt.dev/core-concepts/handlers
|
|
@@ -70,9 +70,9 @@ export interface McpHandlerOptions {
|
|
|
70
70
|
* ```
|
|
71
71
|
*/
|
|
72
72
|
middleware?: McpMiddleware;
|
|
73
|
-
tools?: Array<McpToolDefinition<any, any
|
|
74
|
-
resources?: McpResourceDefinition[];
|
|
75
|
-
prompts?: McpPromptDefinition[];
|
|
73
|
+
tools?: Array<McpToolDefinition<any, any>> | ((event: H3Event) => Array<McpToolDefinition<any, any>> | Promise<Array<McpToolDefinition<any, any>>>);
|
|
74
|
+
resources?: McpResourceDefinition[] | ((event: H3Event) => McpResourceDefinition[] | Promise<McpResourceDefinition[]>);
|
|
75
|
+
prompts?: McpPromptDefinition[] | ((event: H3Event) => McpPromptDefinition[] | Promise<McpPromptDefinition[]>);
|
|
76
76
|
}
|
|
77
77
|
export interface McpHandlerDefinition extends Required<Omit<McpHandlerOptions, 'tools' | 'resources' | 'prompts' | 'middleware'>> {
|
|
78
78
|
tools: Array<McpToolDefinition<any, any>>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
1
2
|
import type { ZodRawShape } from 'zod';
|
|
2
3
|
import type { GetPromptResult, ServerRequest, ServerNotification } from '@modelcontextprotocol/sdk/types.js';
|
|
3
4
|
import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
@@ -18,6 +19,12 @@ export interface McpPromptDefinition<Args extends ZodRawShape | undefined = unde
|
|
|
18
19
|
inputSchema?: Args;
|
|
19
20
|
_meta?: Record<string, unknown>;
|
|
20
21
|
handler: McpPromptCallback<Args>;
|
|
22
|
+
/**
|
|
23
|
+
* Guard that controls whether this prompt is registered for a given request.
|
|
24
|
+
* Receives the H3 event (with `event.context` populated by middleware) and
|
|
25
|
+
* returns `true` to include the prompt or `false` to hide it.
|
|
26
|
+
*/
|
|
27
|
+
enabled?: (event: H3Event) => boolean | Promise<boolean>;
|
|
21
28
|
}
|
|
22
29
|
/**
|
|
23
30
|
* Register a prompt from a McpPromptDefinition
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
1
2
|
import type { McpServer, ResourceTemplate, ReadResourceCallback, ReadResourceTemplateCallback, ResourceMetadata } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
3
|
import { type McpCacheOptions, type McpCache } from './cache.js';
|
|
3
4
|
export type McpResourceCacheOptions = McpCacheOptions<URL>;
|
|
@@ -34,6 +35,12 @@ export interface StandardMcpResourceDefinition {
|
|
|
34
35
|
* @see https://nitro.build/guide/cache#options
|
|
35
36
|
*/
|
|
36
37
|
cache?: McpResourceCache;
|
|
38
|
+
/**
|
|
39
|
+
* Guard that controls whether this resource is registered for a given request.
|
|
40
|
+
* Receives the H3 event (with `event.context` populated by middleware) and
|
|
41
|
+
* returns `true` to include the resource or `false` to hide it.
|
|
42
|
+
*/
|
|
43
|
+
enabled?: (event: H3Event) => boolean | Promise<boolean>;
|
|
37
44
|
}
|
|
38
45
|
/**
|
|
39
46
|
* Definition of a file-based MCP resource
|
|
@@ -61,6 +68,12 @@ export interface FileMcpResourceDefinition {
|
|
|
61
68
|
* @see https://nitro.build/guide/cache#options
|
|
62
69
|
*/
|
|
63
70
|
cache?: McpResourceCache;
|
|
71
|
+
/**
|
|
72
|
+
* Guard that controls whether this resource is registered for a given request.
|
|
73
|
+
* Receives the H3 event (with `event.context` populated by middleware) and
|
|
74
|
+
* returns `true` to include the resource or `false` to hide it.
|
|
75
|
+
*/
|
|
76
|
+
enabled?: (event: H3Event) => boolean | Promise<boolean>;
|
|
64
77
|
}
|
|
65
78
|
/**
|
|
66
79
|
* Definition of an MCP resource matching the SDK's registerResource signature
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
1
2
|
import type { ZodRawShape } from 'zod';
|
|
2
3
|
import type { CallToolResult, ServerRequest, ServerNotification } from '@modelcontextprotocol/sdk/types.js';
|
|
3
4
|
import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
@@ -51,6 +52,14 @@ export interface McpToolDefinition<InputSchema extends ZodRawShape | undefined =
|
|
|
51
52
|
* @see https://nitro.build/guide/cache#options
|
|
52
53
|
*/
|
|
53
54
|
cache?: McpToolCache<InputSchema extends ZodRawShape ? ShapeOutput<InputSchema> : undefined>;
|
|
55
|
+
/**
|
|
56
|
+
* Guard that controls whether this tool is registered for a given request.
|
|
57
|
+
* Receives the H3 event (with `event.context` populated by middleware) and
|
|
58
|
+
* returns `true` to include the tool or `false` to hide it.
|
|
59
|
+
*
|
|
60
|
+
* Evaluated after middleware runs, so authentication context is available.
|
|
61
|
+
*/
|
|
62
|
+
enabled?: (event: H3Event) => boolean | Promise<boolean>;
|
|
54
63
|
}
|
|
55
64
|
/**
|
|
56
65
|
* Register a tool from a McpToolDefinition
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
function kebabCase(str) {
|
|
2
|
+
return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").toLowerCase();
|
|
3
|
+
}
|
|
4
|
+
function titleCase(str) {
|
|
5
|
+
return str.replace(/[-_]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
6
|
+
}
|
|
2
7
|
export function enrichNameTitle(options) {
|
|
3
8
|
const { name, title, _meta, type } = options;
|
|
4
9
|
const filename = _meta?.filename;
|
|
@@ -27,14 +27,16 @@ export default createMcpHandler((event) => {
|
|
|
27
27
|
}
|
|
28
28
|
const defaultHandlerDef = defaultHandler;
|
|
29
29
|
if (defaultHandlerDef) {
|
|
30
|
+
const globalTools = tools;
|
|
31
|
+
const globalResources = resources;
|
|
32
|
+
const globalPrompts = prompts;
|
|
30
33
|
return {
|
|
31
34
|
name: defaultHandlerDef.name ?? config.name ?? "MCP Server",
|
|
32
35
|
version: defaultHandlerDef.version ?? config.version,
|
|
33
36
|
browserRedirect: defaultHandlerDef.browserRedirect ?? config.browserRedirect,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
prompts: defaultHandlerDef.prompts ?? prompts,
|
|
37
|
+
tools: defaultHandlerDef.tools ?? globalTools,
|
|
38
|
+
resources: defaultHandlerDef.resources ?? globalResources,
|
|
39
|
+
prompts: defaultHandlerDef.prompts ?? globalPrompts,
|
|
38
40
|
middleware: defaultHandlerDef.middleware
|
|
39
41
|
};
|
|
40
42
|
}
|
|
@@ -6,7 +6,9 @@ const fallbackCtx = {
|
|
|
6
6
|
passThroughOnException: () => {
|
|
7
7
|
}
|
|
8
8
|
};
|
|
9
|
-
export default createMcpTransportHandler(async (
|
|
9
|
+
export default createMcpTransportHandler(async (createServer, event) => {
|
|
10
|
+
const server = createServer();
|
|
11
|
+
event.context._mcpServer = server;
|
|
10
12
|
const { createMcpHandler } = await import("agents/mcp");
|
|
11
13
|
const handler = createMcpHandler(server, {
|
|
12
14
|
route: ""
|
|
@@ -1,13 +1,87 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
2
|
+
import { toWebRequest, getHeader } from "h3";
|
|
3
|
+
import { useStorage } from "nitropack/runtime";
|
|
4
|
+
import config from "#nuxt-mcp-toolkit/config.mjs";
|
|
3
5
|
import { createMcpTransportHandler } from "./types.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
7
|
+
let cleanupInterval = null;
|
|
8
|
+
function ensureCleanup(maxDuration) {
|
|
9
|
+
if (cleanupInterval) return;
|
|
10
|
+
cleanupInterval = setInterval(() => {
|
|
11
|
+
const now = Date.now();
|
|
12
|
+
for (const [id, session] of sessions) {
|
|
13
|
+
if (now - session.lastAccessed > maxDuration) {
|
|
14
|
+
session.transport.close();
|
|
15
|
+
session.server.close();
|
|
16
|
+
sessions.delete(id);
|
|
17
|
+
useStorage(`mcp:sessions:${id}`).clear();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (sessions.size === 0 && cleanupInterval) {
|
|
21
|
+
clearInterval(cleanupInterval);
|
|
22
|
+
cleanupInterval = null;
|
|
23
|
+
}
|
|
24
|
+
}, 6e4);
|
|
25
|
+
}
|
|
26
|
+
export default createMcpTransportHandler(async (createServer, event) => {
|
|
27
|
+
const sessionsConfig = config.sessions;
|
|
28
|
+
const sessionsEnabled = sessionsConfig?.enabled ?? false;
|
|
29
|
+
const request = toWebRequest(event);
|
|
30
|
+
if (!sessionsEnabled) {
|
|
31
|
+
const server2 = createServer();
|
|
32
|
+
event.context._mcpServer = server2;
|
|
33
|
+
const transport2 = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
34
|
+
event.node.res.on("close", () => {
|
|
35
|
+
transport2.close();
|
|
36
|
+
server2.close();
|
|
37
|
+
});
|
|
38
|
+
await server2.connect(transport2);
|
|
39
|
+
return transport2.handleRequest(request);
|
|
40
|
+
}
|
|
41
|
+
const maxDuration = sessionsConfig?.maxDuration ?? 30 * 60 * 1e3;
|
|
42
|
+
const sessionId = getHeader(event, "mcp-session-id");
|
|
43
|
+
if (sessionId) {
|
|
44
|
+
const session = sessions.get(sessionId);
|
|
45
|
+
if (!session) {
|
|
46
|
+
return new Response(JSON.stringify({
|
|
47
|
+
jsonrpc: "2.0",
|
|
48
|
+
error: { code: -32001, message: "Session not found" },
|
|
49
|
+
id: null
|
|
50
|
+
}), {
|
|
51
|
+
status: 404,
|
|
52
|
+
headers: { "Content-Type": "application/json" }
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
session.lastAccessed = Date.now();
|
|
56
|
+
event.context._mcpServer = session.server;
|
|
57
|
+
return session.transport.handleRequest(request);
|
|
58
|
+
}
|
|
59
|
+
const server = createServer();
|
|
60
|
+
event.context._mcpServer = server;
|
|
61
|
+
let sessionStored = false;
|
|
62
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
63
|
+
sessionIdGenerator: () => globalThis.crypto.randomUUID(),
|
|
64
|
+
onsessioninitialized: (id) => {
|
|
65
|
+
sessionStored = true;
|
|
66
|
+
sessions.set(id, { server, transport, lastAccessed: Date.now() });
|
|
67
|
+
ensureCleanup(maxDuration);
|
|
68
|
+
}
|
|
9
69
|
});
|
|
70
|
+
transport.onclose = () => {
|
|
71
|
+
const sid = transport.sessionId;
|
|
72
|
+
if (sid && sessions.has(sid)) {
|
|
73
|
+
sessions.delete(sid);
|
|
74
|
+
useStorage(`mcp:sessions:${sid}`).clear();
|
|
75
|
+
}
|
|
76
|
+
server.close();
|
|
77
|
+
};
|
|
10
78
|
await server.connect(transport);
|
|
11
|
-
const
|
|
12
|
-
|
|
79
|
+
const response = await transport.handleRequest(request);
|
|
80
|
+
if (!sessionStored) {
|
|
81
|
+
event.node.res.on("close", () => {
|
|
82
|
+
transport.close();
|
|
83
|
+
server.close();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return response;
|
|
13
87
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import type { H3Event } from 'h3';
|
|
3
|
-
export type McpTransportHandler = (
|
|
3
|
+
export type McpTransportHandler = (createServer: () => McpServer, event: H3Event) => Promise<Response>;
|
|
4
4
|
export declare const createMcpTransportHandler: (handler: McpTransportHandler) => McpTransportHandler;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
export interface McpServerHelper {
|
|
3
|
+
/** Register a new tool mid-session. The client is notified automatically. */
|
|
4
|
+
registerTool: McpServer['registerTool'];
|
|
5
|
+
/** Register a new prompt mid-session. The client is notified automatically. */
|
|
6
|
+
registerPrompt: McpServer['registerPrompt'];
|
|
7
|
+
/** Register a new resource mid-session. The client is notified automatically. */
|
|
8
|
+
registerResource: McpServer['registerResource'];
|
|
9
|
+
/** Remove a dynamically registered tool by name. Returns `true` if found. */
|
|
10
|
+
removeTool(name: string): boolean;
|
|
11
|
+
/** Remove a dynamically registered prompt by name. Returns `true` if found. */
|
|
12
|
+
removePrompt(name: string): boolean;
|
|
13
|
+
/** Remove a dynamically registered resource by name. Returns `true` if found. */
|
|
14
|
+
removeResource(name: string): boolean;
|
|
15
|
+
/** The underlying `McpServer` instance for advanced SDK operations. */
|
|
16
|
+
server: McpServer;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Returns a helper to mutate the MCP server mid-session.
|
|
20
|
+
*
|
|
21
|
+
* Use inside tool, resource, or prompt handlers to register, remove,
|
|
22
|
+
* or update definitions while a session is active. The SDK automatically
|
|
23
|
+
* sends `list_changed` notifications to the client.
|
|
24
|
+
*
|
|
25
|
+
* Requires `nitro.experimental.asyncContext: true` in your Nuxt config.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useMcpServer(): McpServerHelper;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useEvent } from "nitropack/runtime";
|
|
2
|
+
const registrations = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
function getRegistrations(server) {
|
|
4
|
+
let reg = registrations.get(server);
|
|
5
|
+
if (!reg) {
|
|
6
|
+
reg = { tools: /* @__PURE__ */ new Map(), prompts: /* @__PURE__ */ new Map(), resources: /* @__PURE__ */ new Map() };
|
|
7
|
+
registrations.set(server, reg);
|
|
8
|
+
}
|
|
9
|
+
return reg;
|
|
10
|
+
}
|
|
11
|
+
function removeByName(map, name) {
|
|
12
|
+
const handle = map.get(name);
|
|
13
|
+
if (!handle) return false;
|
|
14
|
+
handle.remove();
|
|
15
|
+
map.delete(name);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
function wrapRegister(server, method, map) {
|
|
19
|
+
const fn = server[method].bind(server);
|
|
20
|
+
return (...args) => {
|
|
21
|
+
const handle = fn(...args);
|
|
22
|
+
map.set(args[0], handle);
|
|
23
|
+
return handle;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function useMcpServer() {
|
|
27
|
+
const event = useEvent();
|
|
28
|
+
const server = event.context._mcpServer;
|
|
29
|
+
if (!server) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"No MCP server instance available. Ensure this is called within an MCP tool/resource/prompt handler and `nitro.experimental.asyncContext` is true."
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const reg = getRegistrations(server);
|
|
35
|
+
return {
|
|
36
|
+
registerTool: wrapRegister(server, "registerTool", reg.tools),
|
|
37
|
+
registerPrompt: wrapRegister(server, "registerPrompt", reg.prompts),
|
|
38
|
+
registerResource: wrapRegister(server, "registerResource", reg.resources),
|
|
39
|
+
removeTool: (name) => removeByName(reg.tools, name),
|
|
40
|
+
removePrompt: (name) => removeByName(reg.prompts, name),
|
|
41
|
+
removeResource: (name) => removeByName(reg.resources, name),
|
|
42
|
+
server
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Storage } from 'unstorage';
|
|
2
|
+
export interface McpSessionStore<T = Record<string, unknown>> {
|
|
3
|
+
get<K extends keyof T & string>(key: K): Promise<T[K] | null>;
|
|
4
|
+
set<K extends keyof T & string>(key: K, value: T[K]): Promise<void>;
|
|
5
|
+
remove<K extends keyof T & string>(key: K): Promise<void>;
|
|
6
|
+
has<K extends keyof T & string>(key: K): Promise<boolean>;
|
|
7
|
+
keys(): Promise<string[]>;
|
|
8
|
+
clear(): Promise<void>;
|
|
9
|
+
/** Access the underlying unstorage instance */
|
|
10
|
+
storage: Storage;
|
|
11
|
+
}
|
|
12
|
+
export declare function useMcpSession<T = Record<string, unknown>>(): McpSessionStore<T>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useStorage, useEvent } from "nitropack/runtime";
|
|
2
|
+
import { getHeader } from "h3";
|
|
3
|
+
export function useMcpSession() {
|
|
4
|
+
const event = useEvent();
|
|
5
|
+
const sessionId = getHeader(event, "mcp-session-id");
|
|
6
|
+
if (!sessionId) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
"No active MCP session. Ensure `mcp.sessions` is enabled and `nitro.experimental.asyncContext` is true."
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
const storage = useStorage(`mcp:sessions:${sessionId}`);
|
|
12
|
+
return {
|
|
13
|
+
get: (key) => storage.getItem(key),
|
|
14
|
+
set: (key, value) => storage.setItem(key, value),
|
|
15
|
+
remove: (key) => storage.removeItem(key),
|
|
16
|
+
has: (key) => storage.hasItem(key),
|
|
17
|
+
keys: () => storage.getKeys(),
|
|
18
|
+
clear: () => storage.clear(),
|
|
19
|
+
storage
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import type { H3Event } from 'h3';
|
|
3
|
-
import type {
|
|
3
|
+
import type { McpMiddleware } from './definitions/handlers.js';
|
|
4
|
+
import type { McpPromptDefinition } from './definitions/prompts.js';
|
|
5
|
+
import type { McpResourceDefinition } from './definitions/resources.js';
|
|
6
|
+
import type { McpToolDefinition } from './definitions/tools.js';
|
|
4
7
|
export type { McpTransportHandler } from './providers/types.js';
|
|
5
8
|
export { createMcpTransportHandler } from './providers/types.js';
|
|
9
|
+
type MaybeDynamic<T> = T | ((event: H3Event) => T | Promise<T>);
|
|
10
|
+
type MaybeDynamicTools = MaybeDynamic<Array<McpToolDefinition<any, any>>>;
|
|
6
11
|
export interface ResolvedMcpConfig {
|
|
7
12
|
name: string;
|
|
8
13
|
version: string;
|
|
9
14
|
browserRedirect: string;
|
|
10
|
-
tools?:
|
|
11
|
-
resources?: McpResourceDefinition[]
|
|
12
|
-
prompts?: McpPromptDefinition[]
|
|
15
|
+
tools?: MaybeDynamicTools;
|
|
16
|
+
resources?: MaybeDynamic<McpResourceDefinition[]>;
|
|
17
|
+
prompts?: MaybeDynamic<McpPromptDefinition[]>;
|
|
13
18
|
middleware?: McpMiddleware;
|
|
14
19
|
}
|
|
20
|
+
interface StaticMcpConfig {
|
|
21
|
+
name: string;
|
|
22
|
+
version: string;
|
|
23
|
+
tools: McpToolDefinition[];
|
|
24
|
+
resources: McpResourceDefinition[];
|
|
25
|
+
prompts: McpPromptDefinition[];
|
|
26
|
+
}
|
|
15
27
|
export type CreateMcpHandlerConfig = ResolvedMcpConfig | ((event: H3Event) => ResolvedMcpConfig);
|
|
16
|
-
export declare function createMcpServer(config:
|
|
28
|
+
export declare function createMcpServer(config: StaticMcpConfig): McpServer;
|
|
17
29
|
export declare function createMcpHandler(config: CreateMcpHandlerConfig): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
@@ -1,25 +1,60 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { sendRedirect, getHeader, defineEventHandler } from "h3";
|
|
3
|
-
import {
|
|
3
|
+
import { registerPromptFromDefinition } from "./definitions/prompts.js";
|
|
4
|
+
import { registerResourceFromDefinition } from "./definitions/resources.js";
|
|
5
|
+
import { registerToolFromDefinition } from "./definitions/tools.js";
|
|
4
6
|
import handleMcpRequest from "#nuxt-mcp-toolkit/transport.mjs";
|
|
5
7
|
export { createMcpTransportHandler } from "./providers/types.js";
|
|
6
8
|
function resolveConfig(config, event) {
|
|
7
9
|
return typeof config === "function" ? config(event) : config;
|
|
8
10
|
}
|
|
11
|
+
async function filterByEnabled(definitions, event) {
|
|
12
|
+
const results = await Promise.all(
|
|
13
|
+
definitions.map(async (def) => {
|
|
14
|
+
if (!def.enabled) return true;
|
|
15
|
+
return def.enabled(event);
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
return definitions.filter((_, i) => results[i]);
|
|
19
|
+
}
|
|
20
|
+
async function resolveDynamicDefinitions(config, event) {
|
|
21
|
+
const tools = typeof config.tools === "function" ? await config.tools(event) : config.tools || [];
|
|
22
|
+
const resources = typeof config.resources === "function" ? await config.resources(event) : config.resources || [];
|
|
23
|
+
const prompts = typeof config.prompts === "function" ? await config.prompts(event) : config.prompts || [];
|
|
24
|
+
return {
|
|
25
|
+
name: config.name,
|
|
26
|
+
version: config.version,
|
|
27
|
+
tools: await filterByEnabled(tools, event),
|
|
28
|
+
resources: await filterByEnabled(resources, event),
|
|
29
|
+
prompts: await filterByEnabled(prompts, event)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function registerEmptyDefinitionFallbacks(server, config) {
|
|
33
|
+
if (!config.tools.length) {
|
|
34
|
+
server.registerTool("__init__", {}, async () => ({ content: [] })).remove();
|
|
35
|
+
}
|
|
36
|
+
if (!config.resources.length) {
|
|
37
|
+
server.registerResource("__init__", "noop://init", {}, async () => ({ contents: [] })).remove();
|
|
38
|
+
}
|
|
39
|
+
if (!config.prompts.length) {
|
|
40
|
+
server.registerPrompt("__init__", {}, async () => ({ messages: [] })).remove();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
9
43
|
export function createMcpServer(config) {
|
|
10
44
|
const server = new McpServer({
|
|
11
45
|
name: config.name,
|
|
12
46
|
version: config.version
|
|
13
47
|
});
|
|
14
|
-
for (const tool of config.tools
|
|
48
|
+
for (const tool of config.tools) {
|
|
15
49
|
registerToolFromDefinition(server, tool);
|
|
16
50
|
}
|
|
17
|
-
for (const resource of config.resources
|
|
51
|
+
for (const resource of config.resources) {
|
|
18
52
|
registerResourceFromDefinition(server, resource);
|
|
19
53
|
}
|
|
20
|
-
for (const prompt of config.prompts
|
|
54
|
+
for (const prompt of config.prompts) {
|
|
21
55
|
registerPromptFromDefinition(server, prompt);
|
|
22
56
|
}
|
|
57
|
+
registerEmptyDefinitionFallbacks(server, config);
|
|
23
58
|
return server;
|
|
24
59
|
}
|
|
25
60
|
export function createMcpHandler(config) {
|
|
@@ -29,8 +64,8 @@ export function createMcpHandler(config) {
|
|
|
29
64
|
return sendRedirect(event, resolvedConfig.browserRedirect);
|
|
30
65
|
}
|
|
31
66
|
const handler = async () => {
|
|
32
|
-
const
|
|
33
|
-
return handleMcpRequest(
|
|
67
|
+
const staticConfig = await resolveDynamicDefinitions(resolvedConfig, event);
|
|
68
|
+
return handleMcpRequest(() => createMcpServer(staticConfig), event);
|
|
34
69
|
};
|
|
35
70
|
if (resolvedConfig.middleware) {
|
|
36
71
|
let nextCalled = false;
|
|
@@ -3,6 +3,10 @@ declare module '@nuxt/schema' {
|
|
|
3
3
|
/**
|
|
4
4
|
* Add additional directories to scan for MCP definition files (tools, resources, prompts, handlers).
|
|
5
5
|
* @param paths - Object containing arrays of directory paths for each definition type.
|
|
6
|
+
* @param paths.tools - Array of directory paths to scan for tool definitions.
|
|
7
|
+
* @param paths.resources - Array of directory paths to scan for resource definitions.
|
|
8
|
+
* @param paths.prompts - Array of directory paths to scan for prompt definitions.
|
|
9
|
+
* @param paths.handlers - Array of directory paths to scan for handler definitions.
|
|
6
10
|
* @returns void | Promise<void>
|
|
7
11
|
*/
|
|
8
12
|
'mcp:definitions:paths': (paths: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuxtjs/mcp-toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Create MCP servers directly in your Nuxt application. Define tools, resources, and prompts with a simple and intuitive API.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -39,20 +39,19 @@
|
|
|
39
39
|
"dist"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
43
|
-
"@nuxt/kit": "^4.
|
|
44
|
-
"defu": "^6.1.4",
|
|
45
|
-
"ms": "^2.1.3",
|
|
46
|
-
"pathe": "^2.0.3",
|
|
47
|
-
"satori": "^0.19.2",
|
|
48
|
-
"scule": "^1.3.0",
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
43
|
+
"@nuxt/kit": "^4.4.2",
|
|
49
44
|
"tinyglobby": "^0.2.15"
|
|
50
45
|
},
|
|
51
46
|
"peerDependencies": {
|
|
47
|
+
"h3": "^1.15.6",
|
|
52
48
|
"zod": "^4.1.13",
|
|
53
|
-
"agents": ">=0.
|
|
49
|
+
"agents": ">=0.7.6"
|
|
54
50
|
},
|
|
55
51
|
"peerDependenciesMeta": {
|
|
52
|
+
"h3": {
|
|
53
|
+
"optional": false
|
|
54
|
+
},
|
|
56
55
|
"zod": {
|
|
57
56
|
"optional": false
|
|
58
57
|
},
|
|
@@ -61,18 +60,18 @@
|
|
|
61
60
|
}
|
|
62
61
|
},
|
|
63
62
|
"devDependencies": {
|
|
64
|
-
"@nuxt/devtools": "^3.2.
|
|
65
|
-
"@nuxt/eslint-config": "^1.15.
|
|
63
|
+
"@nuxt/devtools": "^3.2.3",
|
|
64
|
+
"@nuxt/eslint-config": "^1.15.2",
|
|
66
65
|
"@nuxt/module-builder": "^1.0.2",
|
|
67
|
-
"@nuxt/schema": "^4.
|
|
66
|
+
"@nuxt/schema": "^4.4.2",
|
|
68
67
|
"@nuxt/test-utils": "^4.0.0",
|
|
69
68
|
"@types/node": "latest",
|
|
70
69
|
"changelogen": "^0.6.2",
|
|
71
|
-
"eslint": "^9.39.
|
|
72
|
-
"nuxt": "^4.
|
|
70
|
+
"eslint": "^9.39.4",
|
|
71
|
+
"nuxt": "^4.4.2",
|
|
73
72
|
"typescript": "~5.9.3",
|
|
74
|
-
"vitest": "^4.0
|
|
75
|
-
"vue-tsc": "^3.2.
|
|
73
|
+
"vitest": "^4.1.0",
|
|
74
|
+
"vue-tsc": "^3.2.5"
|
|
76
75
|
},
|
|
77
76
|
"publishConfig": {
|
|
78
77
|
"access": "public"
|