mcp-expose 1.0.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/CHANGELOG.md +39 -0
- package/LICENSE +21 -0
- package/README.md +708 -0
- package/dist/adonisjs/index.cjs +708 -0
- package/dist/adonisjs/index.cjs.map +1 -0
- package/dist/adonisjs/index.d.cts +68 -0
- package/dist/adonisjs/index.d.ts +68 -0
- package/dist/adonisjs/index.js +684 -0
- package/dist/adonisjs/index.js.map +1 -0
- package/dist/express/index.cjs +789 -0
- package/dist/express/index.cjs.map +1 -0
- package/dist/express/index.d.cts +61 -0
- package/dist/express/index.d.ts +61 -0
- package/dist/express/index.js +763 -0
- package/dist/express/index.js.map +1 -0
- package/dist/fastify/index.cjs +660 -0
- package/dist/fastify/index.cjs.map +1 -0
- package/dist/fastify/index.d.cts +42 -0
- package/dist/fastify/index.d.ts +42 -0
- package/dist/fastify/index.js +635 -0
- package/dist/fastify/index.js.map +1 -0
- package/dist/hono/index.cjs +656 -0
- package/dist/hono/index.cjs.map +1 -0
- package/dist/hono/index.d.cts +44 -0
- package/dist/hono/index.d.ts +44 -0
- package/dist/hono/index.js +630 -0
- package/dist/hono/index.js.map +1 -0
- package/dist/index.cjs +720 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +81 -0
- package/dist/index.d.ts +81 -0
- package/dist/index.js +686 -0
- package/dist/index.js.map +1 -0
- package/dist/koa/index.cjs +739 -0
- package/dist/koa/index.cjs.map +1 -0
- package/dist/koa/index.d.cts +68 -0
- package/dist/koa/index.d.ts +68 -0
- package/dist/koa/index.js +709 -0
- package/dist/koa/index.js.map +1 -0
- package/dist/nestjs/index.cjs +1117 -0
- package/dist/nestjs/index.cjs.map +1 -0
- package/dist/nestjs/index.d.cts +104 -0
- package/dist/nestjs/index.d.ts +104 -0
- package/dist/nestjs/index.js +1079 -0
- package/dist/nestjs/index.js.map +1 -0
- package/dist/server-fzkb-wJo.d.cts +194 -0
- package/dist/server-fzkb-wJo.d.ts +194 -0
- package/package.json +225 -0
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/nestjs/index.ts
|
|
32
|
+
var nestjs_exports = {};
|
|
33
|
+
__export(nestjs_exports, {
|
|
34
|
+
MCP_MODULE_OPTIONS: () => MCP_MODULE_OPTIONS,
|
|
35
|
+
MCP_TOOL_METADATA: () => MCP_TOOL_METADATA,
|
|
36
|
+
McpModule: () => McpModule,
|
|
37
|
+
McpService: () => McpService,
|
|
38
|
+
McpTool: () => McpTool,
|
|
39
|
+
dtoToJsonSchema: () => dtoToJsonSchema
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(nestjs_exports);
|
|
42
|
+
var import_common = require("@nestjs/common");
|
|
43
|
+
var import_core = require("@nestjs/core");
|
|
44
|
+
|
|
45
|
+
// src/core/dispatch.ts
|
|
46
|
+
function buildUrl(base, req) {
|
|
47
|
+
const url = new URL(base.replace(/\/+$/, "") + req.path);
|
|
48
|
+
for (const [k, v] of Object.entries(req.query)) {
|
|
49
|
+
for (const item of Array.isArray(v) ? v : [
|
|
50
|
+
v
|
|
51
|
+
]) url.searchParams.append(k, item);
|
|
52
|
+
}
|
|
53
|
+
return url.toString();
|
|
54
|
+
}
|
|
55
|
+
__name(buildUrl, "buildUrl");
|
|
56
|
+
function createFetchDispatcher(opts) {
|
|
57
|
+
const doFetch = opts.fetch ?? fetch;
|
|
58
|
+
return async (req, ctx) => {
|
|
59
|
+
const base = typeof opts.baseUrl === "function" ? opts.baseUrl(ctx) : opts.baseUrl;
|
|
60
|
+
const res = await doFetch(buildUrl(base, req), {
|
|
61
|
+
method: req.method,
|
|
62
|
+
headers: req.headers,
|
|
63
|
+
body: req.body === void 0 || req.method === "GET" || req.method === "HEAD" ? void 0 : JSON.stringify(req.body),
|
|
64
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 3e4)
|
|
65
|
+
});
|
|
66
|
+
const headers = {};
|
|
67
|
+
res.headers.forEach((v, k) => headers[k] = v);
|
|
68
|
+
return {
|
|
69
|
+
status: res.status,
|
|
70
|
+
headers,
|
|
71
|
+
body: await res.text()
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
__name(createFetchDispatcher, "createFetchDispatcher");
|
|
76
|
+
|
|
77
|
+
// src/core/node.ts
|
|
78
|
+
function normalizeHeaders(headers) {
|
|
79
|
+
const out = {};
|
|
80
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
81
|
+
if (v === void 0) continue;
|
|
82
|
+
out[k.toLowerCase()] = Array.isArray(v) ? v.join(", ") : String(v);
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
__name(normalizeHeaders, "normalizeHeaders");
|
|
87
|
+
function loopbackBaseUrl(req) {
|
|
88
|
+
const socket = req.socket;
|
|
89
|
+
const port = socket.localPort;
|
|
90
|
+
const encrypted = socket.encrypted;
|
|
91
|
+
const bound = socket.server?.address();
|
|
92
|
+
let host = typeof bound === "object" && bound?.address || socket.localAddress || "127.0.0.1";
|
|
93
|
+
if (host.startsWith("::ffff:")) host = host.slice(7);
|
|
94
|
+
if (host === "::" || host === "0.0.0.0") host = "127.0.0.1";
|
|
95
|
+
if (host.includes(":")) host = `[${host}]`;
|
|
96
|
+
return `${encrypted ? "https" : "http"}://${host}:${port}`;
|
|
97
|
+
}
|
|
98
|
+
__name(loopbackBaseUrl, "loopbackBaseUrl");
|
|
99
|
+
|
|
100
|
+
// src/core/schema.ts
|
|
101
|
+
function isStandard(s) {
|
|
102
|
+
return typeof s === "object" && s !== null && "~standard" in s;
|
|
103
|
+
}
|
|
104
|
+
__name(isStandard, "isStandard");
|
|
105
|
+
function toJsonSchema(schema) {
|
|
106
|
+
if (schema === void 0 || schema === null) return void 0;
|
|
107
|
+
let out;
|
|
108
|
+
if (isStandard(schema) && typeof schema["~standard"].jsonSchema?.input === "function") {
|
|
109
|
+
out = schema["~standard"].jsonSchema.input({
|
|
110
|
+
target: "draft-2020-12"
|
|
111
|
+
});
|
|
112
|
+
} else if (typeof schema.toJSONSchema === "function") {
|
|
113
|
+
out = schema.toJSONSchema();
|
|
114
|
+
} else if (isStandard(schema)) {
|
|
115
|
+
throw new Error("mcp-expose: this schema library does not support JSON Schema export. Pass a plain JSON Schema instead.");
|
|
116
|
+
} else {
|
|
117
|
+
out = schema;
|
|
118
|
+
}
|
|
119
|
+
const { $schema: _ignored, ...rest } = out;
|
|
120
|
+
return rest;
|
|
121
|
+
}
|
|
122
|
+
__name(toJsonSchema, "toJsonSchema");
|
|
123
|
+
function toValidator(schema) {
|
|
124
|
+
if (!isStandard(schema) || typeof schema["~standard"].validate !== "function") return void 0;
|
|
125
|
+
const validate = schema["~standard"].validate;
|
|
126
|
+
return async (value) => {
|
|
127
|
+
const result = await validate(value);
|
|
128
|
+
if (!result.issues) return {
|
|
129
|
+
ok: true,
|
|
130
|
+
value: result.value
|
|
131
|
+
};
|
|
132
|
+
const message = result.issues.map((i) => {
|
|
133
|
+
const path = (i.path ?? []).map((p) => typeof p === "object" ? String(p.key) : String(p)).join(".");
|
|
134
|
+
return path ? `${path}: ${i.message}` : i.message;
|
|
135
|
+
}).join("; ");
|
|
136
|
+
return {
|
|
137
|
+
ok: false,
|
|
138
|
+
message
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
__name(toValidator, "toValidator");
|
|
143
|
+
function checkRequired(schema, args) {
|
|
144
|
+
const missing = (schema.required ?? []).filter((k) => args[k] === void 0);
|
|
145
|
+
return missing.length ? `Missing required argument(s): ${missing.join(", ")}` : void 0;
|
|
146
|
+
}
|
|
147
|
+
__name(checkRequired, "checkRequired");
|
|
148
|
+
|
|
149
|
+
// src/core/route-tool.ts
|
|
150
|
+
var DEFAULT_FORWARD_HEADERS = [
|
|
151
|
+
"authorization",
|
|
152
|
+
"cookie",
|
|
153
|
+
"x-api-key",
|
|
154
|
+
"accept-language"
|
|
155
|
+
];
|
|
156
|
+
var BODY_METHODS = /* @__PURE__ */ new Set([
|
|
157
|
+
"POST",
|
|
158
|
+
"PUT",
|
|
159
|
+
"PATCH"
|
|
160
|
+
]);
|
|
161
|
+
var PARAM_RE = /:([A-Za-z0-9_]+)\??|\{([A-Za-z0-9_]+)\}/g;
|
|
162
|
+
function pathParams(path) {
|
|
163
|
+
return [
|
|
164
|
+
...path.matchAll(PARAM_RE)
|
|
165
|
+
].map((m) => m[1] ?? m[2]);
|
|
166
|
+
}
|
|
167
|
+
__name(pathParams, "pathParams");
|
|
168
|
+
function encodeSegment(name, value) {
|
|
169
|
+
const raw = String(value);
|
|
170
|
+
if (raw === "" || raw === "." || raw === "..") {
|
|
171
|
+
throw new Error(`Invalid value for path parameter "${name}": ${JSON.stringify(raw)}`);
|
|
172
|
+
}
|
|
173
|
+
return encodeURIComponent(raw);
|
|
174
|
+
}
|
|
175
|
+
__name(encodeSegment, "encodeSegment");
|
|
176
|
+
function interpolatePath(path, values) {
|
|
177
|
+
return path.replace(/\/:([A-Za-z0-9_]+)\?/g, (_, k) => values[k] === void 0 ? "" : `/${encodeSegment(k, values[k])}`).replace(PARAM_RE, (_, a, b) => {
|
|
178
|
+
const k = a ?? b;
|
|
179
|
+
return encodeSegment(k, values[k]);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
__name(interpolatePath, "interpolatePath");
|
|
183
|
+
function joinPaths(...parts) {
|
|
184
|
+
const joined = parts.filter((p) => !!p && p !== "/").map((p) => p.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
|
|
185
|
+
return `/${joined}`;
|
|
186
|
+
}
|
|
187
|
+
__name(joinPaths, "joinPaths");
|
|
188
|
+
function toolNameFromRoute(method, path) {
|
|
189
|
+
const segments = path.split("/").filter(Boolean).map((s) => {
|
|
190
|
+
const m = /^(?::([A-Za-z0-9_]+)\??|\{([A-Za-z0-9_]+)\})$/.exec(s);
|
|
191
|
+
return m ? `by_${m[1] ?? m[2]}` : s;
|
|
192
|
+
});
|
|
193
|
+
return sanitizeToolName([
|
|
194
|
+
method.toLowerCase(),
|
|
195
|
+
...segments
|
|
196
|
+
].join("_"));
|
|
197
|
+
}
|
|
198
|
+
__name(toolNameFromRoute, "toolNameFromRoute");
|
|
199
|
+
function sanitizeToolName(name) {
|
|
200
|
+
return name.replace(/[^A-Za-z0-9_-]+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").slice(0, 64);
|
|
201
|
+
}
|
|
202
|
+
__name(sanitizeToolName, "sanitizeToolName");
|
|
203
|
+
function defaultAnnotations(method) {
|
|
204
|
+
switch (method) {
|
|
205
|
+
case "GET":
|
|
206
|
+
case "HEAD":
|
|
207
|
+
return {
|
|
208
|
+
readOnlyHint: true,
|
|
209
|
+
openWorldHint: false
|
|
210
|
+
};
|
|
211
|
+
case "DELETE":
|
|
212
|
+
return {
|
|
213
|
+
readOnlyHint: false,
|
|
214
|
+
destructiveHint: true,
|
|
215
|
+
idempotentHint: true,
|
|
216
|
+
openWorldHint: false
|
|
217
|
+
};
|
|
218
|
+
case "PUT":
|
|
219
|
+
return {
|
|
220
|
+
readOnlyHint: false,
|
|
221
|
+
destructiveHint: false,
|
|
222
|
+
idempotentHint: true,
|
|
223
|
+
openWorldHint: false
|
|
224
|
+
};
|
|
225
|
+
default:
|
|
226
|
+
return {
|
|
227
|
+
readOnlyHint: false,
|
|
228
|
+
destructiveHint: false,
|
|
229
|
+
openWorldHint: false
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
__name(defaultAnnotations, "defaultAnnotations");
|
|
234
|
+
function buildInputSchema(route, opts) {
|
|
235
|
+
const method = route.method.toUpperCase();
|
|
236
|
+
const params = pathParams(route.path);
|
|
237
|
+
const mapping = {
|
|
238
|
+
params,
|
|
239
|
+
query: /* @__PURE__ */ new Set(),
|
|
240
|
+
body: /* @__PURE__ */ new Set(),
|
|
241
|
+
wholeBody: false,
|
|
242
|
+
bodyMethod: BODY_METHODS.has(method)
|
|
243
|
+
};
|
|
244
|
+
if (opts.input) {
|
|
245
|
+
const schema2 = toJsonSchema(opts.input);
|
|
246
|
+
return {
|
|
247
|
+
schema: {
|
|
248
|
+
type: "object",
|
|
249
|
+
...schema2
|
|
250
|
+
},
|
|
251
|
+
mapping
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
const properties = {};
|
|
255
|
+
const required = /* @__PURE__ */ new Set();
|
|
256
|
+
const paramSchema = toJsonSchema(opts.params);
|
|
257
|
+
for (const p of params) {
|
|
258
|
+
properties[p] = paramSchema?.properties?.[p] ?? {
|
|
259
|
+
type: "string",
|
|
260
|
+
description: `Path parameter "${p}"`
|
|
261
|
+
};
|
|
262
|
+
if (!route.path.includes(`:${p}?`)) required.add(p);
|
|
263
|
+
}
|
|
264
|
+
const querySchema = toJsonSchema(opts.query);
|
|
265
|
+
for (const [k, v] of Object.entries(querySchema?.properties ?? {})) {
|
|
266
|
+
properties[k] = v;
|
|
267
|
+
mapping.query.add(k);
|
|
268
|
+
}
|
|
269
|
+
for (const k of querySchema?.required ?? []) required.add(k);
|
|
270
|
+
const bodySchema = toJsonSchema(opts.body);
|
|
271
|
+
if (bodySchema) {
|
|
272
|
+
if (bodySchema.properties && (bodySchema.type === void 0 || bodySchema.type === "object")) {
|
|
273
|
+
for (const [k, v] of Object.entries(bodySchema.properties)) {
|
|
274
|
+
properties[k] = v;
|
|
275
|
+
mapping.body.add(k);
|
|
276
|
+
}
|
|
277
|
+
for (const k of bodySchema.required ?? []) required.add(k);
|
|
278
|
+
} else {
|
|
279
|
+
properties.body = bodySchema;
|
|
280
|
+
required.add("body");
|
|
281
|
+
mapping.wholeBody = true;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const schema = {
|
|
285
|
+
type: "object",
|
|
286
|
+
properties
|
|
287
|
+
};
|
|
288
|
+
if (required.size) schema.required = [
|
|
289
|
+
...required
|
|
290
|
+
];
|
|
291
|
+
return {
|
|
292
|
+
schema,
|
|
293
|
+
mapping
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
__name(buildInputSchema, "buildInputSchema");
|
|
297
|
+
function splitArgs(args, mapping) {
|
|
298
|
+
const pathValues = {};
|
|
299
|
+
const query = {};
|
|
300
|
+
let body;
|
|
301
|
+
for (const [k, v] of Object.entries(args)) {
|
|
302
|
+
if (v === void 0) continue;
|
|
303
|
+
if (mapping.params.includes(k)) {
|
|
304
|
+
pathValues[k] = v;
|
|
305
|
+
} else if (mapping.wholeBody && k === "body") {
|
|
306
|
+
body = v;
|
|
307
|
+
} else if (mapping.query.has(k) || !mapping.body.has(k) && !mapping.bodyMethod) {
|
|
308
|
+
query[k] = Array.isArray(v) ? v.map(String) : typeof v === "object" && v !== null ? JSON.stringify(v) : String(v);
|
|
309
|
+
} else {
|
|
310
|
+
body = {
|
|
311
|
+
...body ?? {},
|
|
312
|
+
[k]: v
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (body === void 0 && mapping.bodyMethod) body = {};
|
|
317
|
+
return {
|
|
318
|
+
pathValues,
|
|
319
|
+
query,
|
|
320
|
+
body
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
__name(splitArgs, "splitArgs");
|
|
324
|
+
function responseToResult(res, maxChars) {
|
|
325
|
+
let text = res.body;
|
|
326
|
+
if (text.length > maxChars) text = `${text.slice(0, maxChars)}
|
|
327
|
+
\u2026[truncated ${text.length - maxChars} chars]`;
|
|
328
|
+
const isError = res.status >= 400;
|
|
329
|
+
const result = {
|
|
330
|
+
content: [
|
|
331
|
+
{
|
|
332
|
+
type: "text",
|
|
333
|
+
text: isError ? `HTTP ${res.status}
|
|
334
|
+
${text}` : text || `HTTP ${res.status}`
|
|
335
|
+
}
|
|
336
|
+
]
|
|
337
|
+
};
|
|
338
|
+
if (isError) {
|
|
339
|
+
result.isError = true;
|
|
340
|
+
return result;
|
|
341
|
+
}
|
|
342
|
+
if ((res.headers["content-type"] ?? "").includes("json") && text === res.body) {
|
|
343
|
+
try {
|
|
344
|
+
const parsed = JSON.parse(res.body);
|
|
345
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) result.structuredContent = parsed;
|
|
346
|
+
} catch {
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
__name(responseToResult, "responseToResult");
|
|
352
|
+
function pickForwardHeaders(ctx, names) {
|
|
353
|
+
const out = {};
|
|
354
|
+
for (const name of names) {
|
|
355
|
+
const v = ctx.headers[name.toLowerCase()];
|
|
356
|
+
if (v !== void 0) out[name.toLowerCase()] = v;
|
|
357
|
+
}
|
|
358
|
+
return out;
|
|
359
|
+
}
|
|
360
|
+
__name(pickForwardHeaders, "pickForwardHeaders");
|
|
361
|
+
function createRouteTool(route, opts, dispatch, serverOpts = {}) {
|
|
362
|
+
const method = route.method.toUpperCase();
|
|
363
|
+
const name = sanitizeToolName(opts.name ?? toolNameFromRoute(method, route.path));
|
|
364
|
+
const { schema, mapping } = buildInputSchema(route, opts);
|
|
365
|
+
const forward = serverOpts.forwardHeaders ?? DEFAULT_FORWARD_HEADERS;
|
|
366
|
+
const maxChars = serverOpts.maxResponseChars ?? 1e5;
|
|
367
|
+
return {
|
|
368
|
+
name,
|
|
369
|
+
title: opts.title,
|
|
370
|
+
description: opts.description ?? `${method} ${route.path}`,
|
|
371
|
+
inputSchema: schema,
|
|
372
|
+
annotations: {
|
|
373
|
+
...defaultAnnotations(method),
|
|
374
|
+
...opts.annotations
|
|
375
|
+
},
|
|
376
|
+
validate: toValidator(opts.input),
|
|
377
|
+
async handler(args, ctx) {
|
|
378
|
+
const missing = opts.input ? void 0 : checkRequired(schema, args);
|
|
379
|
+
if (missing) return {
|
|
380
|
+
isError: true,
|
|
381
|
+
content: [
|
|
382
|
+
{
|
|
383
|
+
type: "text",
|
|
384
|
+
text: missing
|
|
385
|
+
}
|
|
386
|
+
]
|
|
387
|
+
};
|
|
388
|
+
const { pathValues, query, body } = splitArgs(args, mapping);
|
|
389
|
+
const headers = {
|
|
390
|
+
accept: "application/json",
|
|
391
|
+
...pickForwardHeaders(ctx, forward),
|
|
392
|
+
...opts.headers,
|
|
393
|
+
"x-mcp-tool": name
|
|
394
|
+
};
|
|
395
|
+
if (ctx.clientIp) headers["x-forwarded-for"] = ctx.clientIp;
|
|
396
|
+
if (body !== void 0) headers["content-type"] = "application/json";
|
|
397
|
+
let path;
|
|
398
|
+
try {
|
|
399
|
+
path = interpolatePath(route.path, pathValues);
|
|
400
|
+
} catch (err) {
|
|
401
|
+
return {
|
|
402
|
+
isError: true,
|
|
403
|
+
content: [
|
|
404
|
+
{
|
|
405
|
+
type: "text",
|
|
406
|
+
text: err.message
|
|
407
|
+
}
|
|
408
|
+
]
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
const res = await dispatch({
|
|
412
|
+
method,
|
|
413
|
+
path,
|
|
414
|
+
query,
|
|
415
|
+
body,
|
|
416
|
+
headers
|
|
417
|
+
}, ctx);
|
|
418
|
+
return responseToResult(res, maxChars);
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
__name(createRouteTool, "createRouteTool");
|
|
423
|
+
|
|
424
|
+
// src/core/server.ts
|
|
425
|
+
var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
426
|
+
"2025-11-25",
|
|
427
|
+
"2025-06-18",
|
|
428
|
+
"2025-03-26",
|
|
429
|
+
"2024-11-05"
|
|
430
|
+
];
|
|
431
|
+
var LATEST_PROTOCOL_VERSION = SUPPORTED_PROTOCOL_VERSIONS[0];
|
|
432
|
+
var PARSE_ERROR = -32700;
|
|
433
|
+
var INVALID_REQUEST = -32600;
|
|
434
|
+
var METHOD_NOT_FOUND = -32601;
|
|
435
|
+
var INVALID_PARAMS = -32602;
|
|
436
|
+
var ok = /* @__PURE__ */ __name((id, result) => ({
|
|
437
|
+
jsonrpc: "2.0",
|
|
438
|
+
id,
|
|
439
|
+
result
|
|
440
|
+
}), "ok");
|
|
441
|
+
var fail = /* @__PURE__ */ __name((id, code, message) => ({
|
|
442
|
+
jsonrpc: "2.0",
|
|
443
|
+
id,
|
|
444
|
+
error: {
|
|
445
|
+
code,
|
|
446
|
+
message
|
|
447
|
+
}
|
|
448
|
+
}), "fail");
|
|
449
|
+
var McpServer = class {
|
|
450
|
+
static {
|
|
451
|
+
__name(this, "McpServer");
|
|
452
|
+
}
|
|
453
|
+
options;
|
|
454
|
+
tools = /* @__PURE__ */ new Map();
|
|
455
|
+
loaders = [];
|
|
456
|
+
constructor(options) {
|
|
457
|
+
this.options = options;
|
|
458
|
+
}
|
|
459
|
+
/** Register a tool. Throws on duplicate names. */
|
|
460
|
+
addTool(tool) {
|
|
461
|
+
if (this.tools.has(tool.name)) {
|
|
462
|
+
throw new Error(`mcp-expose: duplicate tool name "${tool.name}". Set an explicit \`name\`.`);
|
|
463
|
+
}
|
|
464
|
+
this.tools.set(tool.name, tool);
|
|
465
|
+
return this;
|
|
466
|
+
}
|
|
467
|
+
/** Register a function that adds tools lazily, right before the first MCP request is served. */
|
|
468
|
+
onLoad(loader) {
|
|
469
|
+
this.loaders.push(loader);
|
|
470
|
+
return this;
|
|
471
|
+
}
|
|
472
|
+
async listTools() {
|
|
473
|
+
await this.load();
|
|
474
|
+
return [
|
|
475
|
+
...this.tools.values()
|
|
476
|
+
];
|
|
477
|
+
}
|
|
478
|
+
async load() {
|
|
479
|
+
if (!this.loaders.length) return;
|
|
480
|
+
const loaders = this.loaders;
|
|
481
|
+
this.loaders = [];
|
|
482
|
+
for (const l of loaders) await l();
|
|
483
|
+
}
|
|
484
|
+
/** Handle one JSON-RPC message (or a batch). Returns null when no response is due. */
|
|
485
|
+
async handleMessage(message, ctx) {
|
|
486
|
+
if (Array.isArray(message)) {
|
|
487
|
+
const results = (await Promise.all(message.map((m) => this.handleOne(m, ctx)))).filter((r) => r !== null);
|
|
488
|
+
return results.length ? results : null;
|
|
489
|
+
}
|
|
490
|
+
return this.handleOne(message, ctx);
|
|
491
|
+
}
|
|
492
|
+
async handleOne(message, ctx) {
|
|
493
|
+
const msg = message;
|
|
494
|
+
if (!msg || typeof msg !== "object" || msg.jsonrpc !== "2.0") {
|
|
495
|
+
return fail(null, INVALID_REQUEST, "Invalid JSON-RPC message");
|
|
496
|
+
}
|
|
497
|
+
if (typeof msg.method !== "string" || msg.id === void 0) return null;
|
|
498
|
+
const id = msg.id;
|
|
499
|
+
const params = msg.params ?? {};
|
|
500
|
+
await this.load();
|
|
501
|
+
switch (msg.method) {
|
|
502
|
+
case "initialize": {
|
|
503
|
+
const requested = params.protocolVersion;
|
|
504
|
+
const protocolVersion = requested && SUPPORTED_PROTOCOL_VERSIONS.includes(requested) ? requested : LATEST_PROTOCOL_VERSION;
|
|
505
|
+
return ok(id, {
|
|
506
|
+
protocolVersion,
|
|
507
|
+
capabilities: {
|
|
508
|
+
tools: {
|
|
509
|
+
listChanged: false
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
serverInfo: {
|
|
513
|
+
name: this.options.name,
|
|
514
|
+
version: this.options.version ?? "1.0.0"
|
|
515
|
+
},
|
|
516
|
+
...this.options.instructions ? {
|
|
517
|
+
instructions: this.options.instructions
|
|
518
|
+
} : {}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
case "ping":
|
|
522
|
+
return ok(id, {});
|
|
523
|
+
case "tools/list":
|
|
524
|
+
return ok(id, {
|
|
525
|
+
tools: [
|
|
526
|
+
...this.tools.values()
|
|
527
|
+
].map((t) => ({
|
|
528
|
+
name: t.name,
|
|
529
|
+
...t.title ? {
|
|
530
|
+
title: t.title
|
|
531
|
+
} : {},
|
|
532
|
+
description: t.description,
|
|
533
|
+
inputSchema: t.inputSchema,
|
|
534
|
+
...t.annotations ? {
|
|
535
|
+
annotations: t.annotations
|
|
536
|
+
} : {}
|
|
537
|
+
}))
|
|
538
|
+
});
|
|
539
|
+
case "tools/call": {
|
|
540
|
+
const tool = this.tools.get(params.name);
|
|
541
|
+
if (!tool) return fail(id, INVALID_PARAMS, `Unknown tool: ${String(params.name)}`);
|
|
542
|
+
return ok(id, await this.callTool(tool, params.arguments ?? {}, ctx));
|
|
543
|
+
}
|
|
544
|
+
case "resources/list":
|
|
545
|
+
return ok(id, {
|
|
546
|
+
resources: []
|
|
547
|
+
});
|
|
548
|
+
case "prompts/list":
|
|
549
|
+
return ok(id, {
|
|
550
|
+
prompts: []
|
|
551
|
+
});
|
|
552
|
+
default:
|
|
553
|
+
return fail(id, METHOD_NOT_FOUND, `Method not found: ${msg.method}`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
async callTool(tool, args, ctx) {
|
|
557
|
+
try {
|
|
558
|
+
if (tool.validate) {
|
|
559
|
+
const v = await tool.validate(args);
|
|
560
|
+
if (!v.ok) return {
|
|
561
|
+
isError: true,
|
|
562
|
+
content: [
|
|
563
|
+
{
|
|
564
|
+
type: "text",
|
|
565
|
+
text: `Invalid arguments: ${v.message}`
|
|
566
|
+
}
|
|
567
|
+
]
|
|
568
|
+
};
|
|
569
|
+
args = v.value;
|
|
570
|
+
}
|
|
571
|
+
return await tool.handler(args, ctx);
|
|
572
|
+
} catch (err) {
|
|
573
|
+
return {
|
|
574
|
+
isError: true,
|
|
575
|
+
content: [
|
|
576
|
+
{
|
|
577
|
+
type: "text",
|
|
578
|
+
text: `Tool error: ${err.message}`
|
|
579
|
+
}
|
|
580
|
+
]
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
/** Check the Origin header (DNS rebinding protection). */
|
|
585
|
+
isOriginAllowed(origin) {
|
|
586
|
+
if (!origin) return true;
|
|
587
|
+
const allowed = this.options.allowedOrigins;
|
|
588
|
+
if (allowed === "*") return true;
|
|
589
|
+
return (allowed ?? []).includes(origin);
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Framework-neutral Streamable HTTP handler. Adapters convert their request
|
|
593
|
+
* into McpHttpRequest, call this, and write the McpHttpResponse back.
|
|
594
|
+
*/
|
|
595
|
+
async handleHttp(req, ctx) {
|
|
596
|
+
const json = {
|
|
597
|
+
"content-type": "application/json"
|
|
598
|
+
};
|
|
599
|
+
if (!this.isOriginAllowed(req.headers.origin)) {
|
|
600
|
+
return {
|
|
601
|
+
status: 403,
|
|
602
|
+
headers: json,
|
|
603
|
+
body: JSON.stringify(fail(null, INVALID_REQUEST, "Origin not allowed"))
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
if (req.method.toUpperCase() !== "POST") {
|
|
607
|
+
return {
|
|
608
|
+
status: 405,
|
|
609
|
+
headers: {
|
|
610
|
+
allow: "POST"
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
let body = req.body;
|
|
615
|
+
if (typeof body === "string" || body instanceof Uint8Array) {
|
|
616
|
+
try {
|
|
617
|
+
body = JSON.parse(typeof body === "string" ? body : new TextDecoder().decode(body));
|
|
618
|
+
} catch {
|
|
619
|
+
return {
|
|
620
|
+
status: 400,
|
|
621
|
+
headers: json,
|
|
622
|
+
body: JSON.stringify(fail(null, PARSE_ERROR, "Parse error"))
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
const response = await this.handleMessage(body, ctx);
|
|
627
|
+
if (response === null) return {
|
|
628
|
+
status: 202,
|
|
629
|
+
headers: {}
|
|
630
|
+
};
|
|
631
|
+
return {
|
|
632
|
+
status: 200,
|
|
633
|
+
headers: json,
|
|
634
|
+
body: JSON.stringify(response)
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
// src/nestjs/dto-schema.ts
|
|
640
|
+
var PRIMITIVES = /* @__PURE__ */ new Map([
|
|
641
|
+
[
|
|
642
|
+
String,
|
|
643
|
+
{
|
|
644
|
+
type: "string"
|
|
645
|
+
}
|
|
646
|
+
],
|
|
647
|
+
[
|
|
648
|
+
Number,
|
|
649
|
+
{
|
|
650
|
+
type: "number"
|
|
651
|
+
}
|
|
652
|
+
],
|
|
653
|
+
[
|
|
654
|
+
Boolean,
|
|
655
|
+
{
|
|
656
|
+
type: "boolean"
|
|
657
|
+
}
|
|
658
|
+
],
|
|
659
|
+
[
|
|
660
|
+
Date,
|
|
661
|
+
{
|
|
662
|
+
type: "string",
|
|
663
|
+
format: "date-time"
|
|
664
|
+
}
|
|
665
|
+
],
|
|
666
|
+
[
|
|
667
|
+
Array,
|
|
668
|
+
{
|
|
669
|
+
type: "array"
|
|
670
|
+
}
|
|
671
|
+
],
|
|
672
|
+
[
|
|
673
|
+
Object,
|
|
674
|
+
{
|
|
675
|
+
type: "object"
|
|
676
|
+
}
|
|
677
|
+
]
|
|
678
|
+
]);
|
|
679
|
+
function primitiveSchema(type) {
|
|
680
|
+
const s = PRIMITIVES.get(type);
|
|
681
|
+
return s ? {
|
|
682
|
+
...s
|
|
683
|
+
} : void 0;
|
|
684
|
+
}
|
|
685
|
+
__name(primitiveSchema, "primitiveSchema");
|
|
686
|
+
function applyRule(s, name, c = []) {
|
|
687
|
+
switch (name) {
|
|
688
|
+
case "isString":
|
|
689
|
+
s.type = "string";
|
|
690
|
+
break;
|
|
691
|
+
case "isNumber":
|
|
692
|
+
case "isNumberString":
|
|
693
|
+
s.type = name === "isNumber" ? "number" : "string";
|
|
694
|
+
break;
|
|
695
|
+
case "isInt":
|
|
696
|
+
s.type = "integer";
|
|
697
|
+
break;
|
|
698
|
+
case "isBoolean":
|
|
699
|
+
s.type = "boolean";
|
|
700
|
+
break;
|
|
701
|
+
case "isDate":
|
|
702
|
+
case "isDateString":
|
|
703
|
+
case "isISO8601":
|
|
704
|
+
s.type = "string";
|
|
705
|
+
s.format = "date-time";
|
|
706
|
+
break;
|
|
707
|
+
case "isEmail":
|
|
708
|
+
s.type = "string";
|
|
709
|
+
s.format = "email";
|
|
710
|
+
break;
|
|
711
|
+
case "isUrl":
|
|
712
|
+
s.type = "string";
|
|
713
|
+
s.format = "uri";
|
|
714
|
+
break;
|
|
715
|
+
case "isUUID":
|
|
716
|
+
s.type = "string";
|
|
717
|
+
s.format = "uuid";
|
|
718
|
+
break;
|
|
719
|
+
case "isPositive":
|
|
720
|
+
s.exclusiveMinimum = 0;
|
|
721
|
+
break;
|
|
722
|
+
case "isNegative":
|
|
723
|
+
s.exclusiveMaximum = 0;
|
|
724
|
+
break;
|
|
725
|
+
case "min":
|
|
726
|
+
s.minimum = c[0];
|
|
727
|
+
break;
|
|
728
|
+
case "max":
|
|
729
|
+
s.maximum = c[0];
|
|
730
|
+
break;
|
|
731
|
+
case "minLength":
|
|
732
|
+
s.minLength = c[0];
|
|
733
|
+
break;
|
|
734
|
+
case "maxLength":
|
|
735
|
+
s.maxLength = c[0];
|
|
736
|
+
break;
|
|
737
|
+
case "length":
|
|
738
|
+
s.minLength = c[0];
|
|
739
|
+
if (c[1] !== void 0) s.maxLength = c[1];
|
|
740
|
+
break;
|
|
741
|
+
case "matches":
|
|
742
|
+
if (c[0] instanceof RegExp) s.pattern = c[0].source;
|
|
743
|
+
break;
|
|
744
|
+
case "isIn":
|
|
745
|
+
s.enum = c[0];
|
|
746
|
+
break;
|
|
747
|
+
case "isEnum":
|
|
748
|
+
s.enum = c[1] ?? Object.values(c[0]);
|
|
749
|
+
break;
|
|
750
|
+
case "isNotEmpty":
|
|
751
|
+
if (s.type === "string") s.minLength = Math.max(1, Number(s.minLength ?? 0));
|
|
752
|
+
break;
|
|
753
|
+
case "isArray":
|
|
754
|
+
s.type = "array";
|
|
755
|
+
break;
|
|
756
|
+
case "arrayMinSize":
|
|
757
|
+
s.minItems = c[0];
|
|
758
|
+
break;
|
|
759
|
+
case "arrayMaxSize":
|
|
760
|
+
s.maxItems = c[0];
|
|
761
|
+
break;
|
|
762
|
+
case "isObject":
|
|
763
|
+
s.type = "object";
|
|
764
|
+
break;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
__name(applyRule, "applyRule");
|
|
768
|
+
function dtoToJsonSchema(dto, storage, depth = 0) {
|
|
769
|
+
const schema = {
|
|
770
|
+
type: "object",
|
|
771
|
+
properties: {}
|
|
772
|
+
};
|
|
773
|
+
if (!storage || depth > 5) return schema;
|
|
774
|
+
const metas = storage.getTargetValidationMetadatas(dto, "", true, false);
|
|
775
|
+
const properties = {};
|
|
776
|
+
const optional = /* @__PURE__ */ new Set();
|
|
777
|
+
for (const m of metas) {
|
|
778
|
+
const prop = properties[m.propertyName] ??= initial(dto, m.propertyName);
|
|
779
|
+
if (m.type === "conditionalValidation" && m.name === "isOptional") {
|
|
780
|
+
optional.add(m.propertyName);
|
|
781
|
+
} else if (m.type === "nestedValidation") {
|
|
782
|
+
const nestedType = Reflect.getMetadata?.("design:type", dto.prototype, m.propertyName);
|
|
783
|
+
if (typeof nestedType === "function" && !PRIMITIVES.has(nestedType)) {
|
|
784
|
+
Object.assign(prop, dtoToJsonSchema(nestedType, storage, depth + 1));
|
|
785
|
+
}
|
|
786
|
+
} else if (m.type === "customValidation" && m.name) {
|
|
787
|
+
if (m.each) {
|
|
788
|
+
prop.type = "array";
|
|
789
|
+
const items = prop.items ??= {};
|
|
790
|
+
applyRule(items, m.name, m.constraints);
|
|
791
|
+
} else {
|
|
792
|
+
applyRule(prop, m.name, m.constraints);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
schema.properties = properties;
|
|
797
|
+
const required = Object.keys(properties).filter((p) => !optional.has(p));
|
|
798
|
+
if (required.length) schema.required = required;
|
|
799
|
+
return schema;
|
|
800
|
+
}
|
|
801
|
+
__name(dtoToJsonSchema, "dtoToJsonSchema");
|
|
802
|
+
function initial(dto, prop) {
|
|
803
|
+
const designType = Reflect.getMetadata?.("design:type", dto.prototype, prop);
|
|
804
|
+
const s = primitiveSchema(designType) ?? {};
|
|
805
|
+
const api = Reflect.getMetadata?.("swagger/apiModelProperties", dto.prototype, prop);
|
|
806
|
+
if (api?.description) s.description = api.description;
|
|
807
|
+
if (api?.example !== void 0) s.examples = [
|
|
808
|
+
api.example
|
|
809
|
+
];
|
|
810
|
+
if (Array.isArray(api?.enum)) s.enum = api.enum;
|
|
811
|
+
return s;
|
|
812
|
+
}
|
|
813
|
+
__name(initial, "initial");
|
|
814
|
+
|
|
815
|
+
// src/nestjs/index.ts
|
|
816
|
+
var PATH_METADATA = "path";
|
|
817
|
+
var METHOD_METADATA = "method";
|
|
818
|
+
var ROUTE_ARGS_METADATA = "__routeArguments__";
|
|
819
|
+
var VERSION_METADATA = "__version__";
|
|
820
|
+
var REQUEST_METHODS = [
|
|
821
|
+
"GET",
|
|
822
|
+
"POST",
|
|
823
|
+
"PUT",
|
|
824
|
+
"DELETE",
|
|
825
|
+
"PATCH",
|
|
826
|
+
"ALL",
|
|
827
|
+
"OPTIONS",
|
|
828
|
+
"HEAD"
|
|
829
|
+
];
|
|
830
|
+
var PARAMTYPE_BODY = 3;
|
|
831
|
+
var PARAMTYPE_QUERY = 4;
|
|
832
|
+
var PARAMTYPE_PARAM = 5;
|
|
833
|
+
var MCP_TOOL_METADATA = "mcp-expose:tool";
|
|
834
|
+
var MCP_MODULE_OPTIONS = /* @__PURE__ */ Symbol("MCP_MODULE_OPTIONS");
|
|
835
|
+
function McpTool(options = {}) {
|
|
836
|
+
return (0, import_common.SetMetadata)(MCP_TOOL_METADATA, options);
|
|
837
|
+
}
|
|
838
|
+
__name(McpTool, "McpTool");
|
|
839
|
+
var McpService = class {
|
|
840
|
+
static {
|
|
841
|
+
__name(this, "McpService");
|
|
842
|
+
}
|
|
843
|
+
discovery;
|
|
844
|
+
appConfig;
|
|
845
|
+
options;
|
|
846
|
+
server;
|
|
847
|
+
constructor(discovery, appConfig, options) {
|
|
848
|
+
this.discovery = discovery;
|
|
849
|
+
this.appConfig = appConfig;
|
|
850
|
+
this.options = options;
|
|
851
|
+
this.server = new McpServer(options);
|
|
852
|
+
for (const t of options.tools ?? []) this.server.addTool(t);
|
|
853
|
+
const dispatch = createFetchDispatcher({
|
|
854
|
+
baseUrl: options.baseUrl ?? ((ctx) => loopbackBaseUrl(ctx.raw))
|
|
855
|
+
});
|
|
856
|
+
this.server.onLoad(async () => {
|
|
857
|
+
const storage = options.classValidatorStorage ?? await loadClassValidator();
|
|
858
|
+
for (const t of this.discover(storage)) {
|
|
859
|
+
this.server.addTool(createRouteTool(t.route, t.options, dispatch, options));
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
globalPrefix() {
|
|
864
|
+
return this.appConfig.getGlobalPrefix?.() ?? "";
|
|
865
|
+
}
|
|
866
|
+
versionSegment(controller, handler) {
|
|
867
|
+
const versioning = this.appConfig.getVersioning?.();
|
|
868
|
+
if (!versioning || versioning.type !== 0) return void 0;
|
|
869
|
+
let version = Reflect.getMetadata(VERSION_METADATA, handler) ?? Reflect.getMetadata(VERSION_METADATA, controller) ?? versioning.defaultVersion;
|
|
870
|
+
if (Array.isArray(version)) version = version[0];
|
|
871
|
+
if (version === void 0 || typeof version === "symbol") return void 0;
|
|
872
|
+
const prefix = versioning.prefix === false ? "" : versioning.prefix ?? "v";
|
|
873
|
+
return `${prefix}${String(version)}`;
|
|
874
|
+
}
|
|
875
|
+
discover(storage) {
|
|
876
|
+
const found = [];
|
|
877
|
+
for (const wrapper of this.discovery.getControllers()) {
|
|
878
|
+
const controller = wrapper.metatype;
|
|
879
|
+
if (!controller?.prototype) continue;
|
|
880
|
+
const proto = controller.prototype;
|
|
881
|
+
const ctrlPaths = toArray(Reflect.getMetadata(PATH_METADATA, controller) ?? "/");
|
|
882
|
+
const ctrlName = controller.name.replace(/Controller$/, "");
|
|
883
|
+
for (const key of Object.getOwnPropertyNames(proto)) {
|
|
884
|
+
if (key === "constructor") continue;
|
|
885
|
+
const handler = Object.getOwnPropertyDescriptor(proto, key)?.value;
|
|
886
|
+
if (typeof handler !== "function") continue;
|
|
887
|
+
const meta = Reflect.getMetadata(MCP_TOOL_METADATA, handler);
|
|
888
|
+
if (!meta) continue;
|
|
889
|
+
const method = REQUEST_METHODS[Reflect.getMetadata(METHOD_METADATA, handler)] ?? "GET";
|
|
890
|
+
const methodPath = toArray(Reflect.getMetadata(PATH_METADATA, handler) ?? "/")[0];
|
|
891
|
+
const path = joinPaths(this.globalPrefix(), this.versionSegment(controller, handler), this.options.pathPrefix, ctrlPaths[0], methodPath);
|
|
892
|
+
found.push({
|
|
893
|
+
route: {
|
|
894
|
+
method: method === "ALL" ? "POST" : method,
|
|
895
|
+
path
|
|
896
|
+
},
|
|
897
|
+
options: {
|
|
898
|
+
name: sanitizeToolName(`${toSnake(ctrlName)}_${toSnake(key)}`),
|
|
899
|
+
...describeArgs(controller, key, storage),
|
|
900
|
+
...meta
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
return found;
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
function describeArgs(controller, key, storage) {
|
|
909
|
+
const args = Reflect.getMetadata(ROUTE_ARGS_METADATA, controller, key) ?? {};
|
|
910
|
+
const types = Reflect.getMetadata("design:paramtypes", controller.prototype, key) ?? [];
|
|
911
|
+
const params = {
|
|
912
|
+
type: "object",
|
|
913
|
+
properties: {}
|
|
914
|
+
};
|
|
915
|
+
const query = {
|
|
916
|
+
type: "object",
|
|
917
|
+
properties: {},
|
|
918
|
+
required: []
|
|
919
|
+
};
|
|
920
|
+
let body;
|
|
921
|
+
const schemaFor = /* @__PURE__ */ __name((t) => primitiveSchema(t) ?? (typeof t === "function" ? dtoToJsonSchema(t, storage) : {}), "schemaFor");
|
|
922
|
+
for (const [k, arg] of Object.entries(args)) {
|
|
923
|
+
const paramtype = Number(k.split(":")[0]);
|
|
924
|
+
const t = types[arg.index];
|
|
925
|
+
const field = typeof arg.data === "string" ? arg.data : void 0;
|
|
926
|
+
if (paramtype === PARAMTYPE_PARAM && field) {
|
|
927
|
+
params.properties[field] = primitiveSchema(t) ?? {
|
|
928
|
+
type: "string"
|
|
929
|
+
};
|
|
930
|
+
} else if (paramtype === PARAMTYPE_QUERY) {
|
|
931
|
+
if (field) query.properties[field] = primitiveSchema(t) ?? {
|
|
932
|
+
type: "string"
|
|
933
|
+
};
|
|
934
|
+
else Object.assign(query, mergeObject(query, schemaFor(t)));
|
|
935
|
+
} else if (paramtype === PARAMTYPE_BODY) {
|
|
936
|
+
if (field) {
|
|
937
|
+
body ??= {
|
|
938
|
+
type: "object",
|
|
939
|
+
properties: {},
|
|
940
|
+
required: []
|
|
941
|
+
};
|
|
942
|
+
body.properties[field] = schemaFor(t);
|
|
943
|
+
body.required.push(field);
|
|
944
|
+
} else {
|
|
945
|
+
body = mergeObject(body, schemaFor(t));
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
const out = {
|
|
950
|
+
params
|
|
951
|
+
};
|
|
952
|
+
if (Object.keys(query.properties).length) out.query = query;
|
|
953
|
+
if (body) out.body = body;
|
|
954
|
+
return out;
|
|
955
|
+
}
|
|
956
|
+
__name(describeArgs, "describeArgs");
|
|
957
|
+
function mergeObject(a, b) {
|
|
958
|
+
if (!a) return b;
|
|
959
|
+
return {
|
|
960
|
+
type: "object",
|
|
961
|
+
properties: {
|
|
962
|
+
...a.properties,
|
|
963
|
+
...b.properties
|
|
964
|
+
},
|
|
965
|
+
required: [
|
|
966
|
+
...a.required ?? [],
|
|
967
|
+
...b.required ?? []
|
|
968
|
+
]
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
__name(mergeObject, "mergeObject");
|
|
972
|
+
async function loadClassValidator() {
|
|
973
|
+
try {
|
|
974
|
+
const cv = await import("class-validator");
|
|
975
|
+
return cv.getMetadataStorage?.();
|
|
976
|
+
} catch {
|
|
977
|
+
return void 0;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
__name(loadClassValidator, "loadClassValidator");
|
|
981
|
+
function toArray(v) {
|
|
982
|
+
return Array.isArray(v) ? v : [
|
|
983
|
+
v
|
|
984
|
+
];
|
|
985
|
+
}
|
|
986
|
+
__name(toArray, "toArray");
|
|
987
|
+
function toSnake(s) {
|
|
988
|
+
return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
|
|
989
|
+
}
|
|
990
|
+
__name(toSnake, "toSnake");
|
|
991
|
+
(0, import_common.Injectable)()(McpService);
|
|
992
|
+
(0, import_common.Inject)(import_core.DiscoveryService)(McpService, void 0, 0);
|
|
993
|
+
(0, import_common.Inject)(import_core.ApplicationConfig)(McpService, void 0, 1);
|
|
994
|
+
(0, import_common.Inject)(MCP_MODULE_OPTIONS)(McpService, void 0, 2);
|
|
995
|
+
function createMcpController(options) {
|
|
996
|
+
let McpController = class McpController {
|
|
997
|
+
static {
|
|
998
|
+
__name(this, "McpController");
|
|
999
|
+
}
|
|
1000
|
+
mcp;
|
|
1001
|
+
constructor(mcp) {
|
|
1002
|
+
this.mcp = mcp;
|
|
1003
|
+
}
|
|
1004
|
+
async handle(req, res) {
|
|
1005
|
+
const nodeReq = req.raw ?? req;
|
|
1006
|
+
const headers = normalizeHeaders(req.headers);
|
|
1007
|
+
const ctx = {
|
|
1008
|
+
headers,
|
|
1009
|
+
clientIp: req.ip,
|
|
1010
|
+
raw: nodeReq
|
|
1011
|
+
};
|
|
1012
|
+
const out = await this.mcp.server.handleHttp({
|
|
1013
|
+
method: req.method,
|
|
1014
|
+
headers,
|
|
1015
|
+
body: req.body
|
|
1016
|
+
}, ctx);
|
|
1017
|
+
if (typeof res.code === "function") {
|
|
1018
|
+
res.code(out.status).headers(out.headers).send(out.body);
|
|
1019
|
+
} else {
|
|
1020
|
+
res.status(out.status).set(out.headers);
|
|
1021
|
+
if (out.body === void 0) res.end();
|
|
1022
|
+
else res.send(out.body);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
const proto = McpController.prototype;
|
|
1027
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, "handle");
|
|
1028
|
+
(0, import_common.Controller)({
|
|
1029
|
+
path: options.path ?? "mcp",
|
|
1030
|
+
version: import_common.VERSION_NEUTRAL
|
|
1031
|
+
})(McpController);
|
|
1032
|
+
(0, import_common.Inject)(McpService)(McpController, void 0, 0);
|
|
1033
|
+
(0, import_common.All)()(proto, "handle", descriptor);
|
|
1034
|
+
(0, import_common.Req)()(proto, "handle", 0);
|
|
1035
|
+
(0, import_common.Res)()(proto, "handle", 1);
|
|
1036
|
+
if (options.guards?.length) (0, import_common.UseGuards)(...options.guards)(McpController);
|
|
1037
|
+
return McpController;
|
|
1038
|
+
}
|
|
1039
|
+
__name(createMcpController, "createMcpController");
|
|
1040
|
+
var McpModule = class _McpModule {
|
|
1041
|
+
static {
|
|
1042
|
+
__name(this, "McpModule");
|
|
1043
|
+
}
|
|
1044
|
+
static forRoot(options) {
|
|
1045
|
+
return {
|
|
1046
|
+
module: _McpModule,
|
|
1047
|
+
global: true,
|
|
1048
|
+
imports: [
|
|
1049
|
+
import_core.DiscoveryModule
|
|
1050
|
+
],
|
|
1051
|
+
controllers: [
|
|
1052
|
+
createMcpController(options)
|
|
1053
|
+
],
|
|
1054
|
+
providers: [
|
|
1055
|
+
{
|
|
1056
|
+
provide: MCP_MODULE_OPTIONS,
|
|
1057
|
+
useValue: options
|
|
1058
|
+
},
|
|
1059
|
+
McpService
|
|
1060
|
+
],
|
|
1061
|
+
exports: [
|
|
1062
|
+
McpService
|
|
1063
|
+
]
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Resolve the options from other providers, e.g. `ConfigService`:
|
|
1068
|
+
*
|
|
1069
|
+
* McpModule.forRootAsync({
|
|
1070
|
+
* imports: [ConfigModule],
|
|
1071
|
+
* inject: [ConfigService],
|
|
1072
|
+
* useFactory: (config: ConfigService) => ({ name: config.get('APP_NAME') }),
|
|
1073
|
+
* })
|
|
1074
|
+
*
|
|
1075
|
+
* `path` and `guards` define the MCP controller, so they are passed directly.
|
|
1076
|
+
*/
|
|
1077
|
+
static forRootAsync(options) {
|
|
1078
|
+
return {
|
|
1079
|
+
module: _McpModule,
|
|
1080
|
+
global: true,
|
|
1081
|
+
imports: [
|
|
1082
|
+
import_core.DiscoveryModule,
|
|
1083
|
+
...options.imports ?? []
|
|
1084
|
+
],
|
|
1085
|
+
controllers: [
|
|
1086
|
+
createMcpController(options)
|
|
1087
|
+
],
|
|
1088
|
+
providers: [
|
|
1089
|
+
{
|
|
1090
|
+
provide: MCP_MODULE_OPTIONS,
|
|
1091
|
+
inject: options.inject ?? [],
|
|
1092
|
+
useFactory: /* @__PURE__ */ __name(async (...args) => ({
|
|
1093
|
+
...await options.useFactory(...args),
|
|
1094
|
+
...options.path ? {
|
|
1095
|
+
path: options.path
|
|
1096
|
+
} : {}
|
|
1097
|
+
}), "useFactory")
|
|
1098
|
+
},
|
|
1099
|
+
McpService
|
|
1100
|
+
],
|
|
1101
|
+
exports: [
|
|
1102
|
+
McpService
|
|
1103
|
+
]
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
(0, import_common.Module)({})(McpModule);
|
|
1108
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1109
|
+
0 && (module.exports = {
|
|
1110
|
+
MCP_MODULE_OPTIONS,
|
|
1111
|
+
MCP_TOOL_METADATA,
|
|
1112
|
+
McpModule,
|
|
1113
|
+
McpService,
|
|
1114
|
+
McpTool,
|
|
1115
|
+
dtoToJsonSchema
|
|
1116
|
+
});
|
|
1117
|
+
//# sourceMappingURL=index.cjs.map
|