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.
Files changed (48) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/LICENSE +21 -0
  3. package/README.md +708 -0
  4. package/dist/adonisjs/index.cjs +708 -0
  5. package/dist/adonisjs/index.cjs.map +1 -0
  6. package/dist/adonisjs/index.d.cts +68 -0
  7. package/dist/adonisjs/index.d.ts +68 -0
  8. package/dist/adonisjs/index.js +684 -0
  9. package/dist/adonisjs/index.js.map +1 -0
  10. package/dist/express/index.cjs +789 -0
  11. package/dist/express/index.cjs.map +1 -0
  12. package/dist/express/index.d.cts +61 -0
  13. package/dist/express/index.d.ts +61 -0
  14. package/dist/express/index.js +763 -0
  15. package/dist/express/index.js.map +1 -0
  16. package/dist/fastify/index.cjs +660 -0
  17. package/dist/fastify/index.cjs.map +1 -0
  18. package/dist/fastify/index.d.cts +42 -0
  19. package/dist/fastify/index.d.ts +42 -0
  20. package/dist/fastify/index.js +635 -0
  21. package/dist/fastify/index.js.map +1 -0
  22. package/dist/hono/index.cjs +656 -0
  23. package/dist/hono/index.cjs.map +1 -0
  24. package/dist/hono/index.d.cts +44 -0
  25. package/dist/hono/index.d.ts +44 -0
  26. package/dist/hono/index.js +630 -0
  27. package/dist/hono/index.js.map +1 -0
  28. package/dist/index.cjs +720 -0
  29. package/dist/index.cjs.map +1 -0
  30. package/dist/index.d.cts +81 -0
  31. package/dist/index.d.ts +81 -0
  32. package/dist/index.js +686 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/koa/index.cjs +739 -0
  35. package/dist/koa/index.cjs.map +1 -0
  36. package/dist/koa/index.d.cts +68 -0
  37. package/dist/koa/index.d.ts +68 -0
  38. package/dist/koa/index.js +709 -0
  39. package/dist/koa/index.js.map +1 -0
  40. package/dist/nestjs/index.cjs +1117 -0
  41. package/dist/nestjs/index.cjs.map +1 -0
  42. package/dist/nestjs/index.d.cts +104 -0
  43. package/dist/nestjs/index.d.ts +104 -0
  44. package/dist/nestjs/index.js +1079 -0
  45. package/dist/nestjs/index.js.map +1 -0
  46. package/dist/server-fzkb-wJo.d.cts +194 -0
  47. package/dist/server-fzkb-wJo.d.ts +194 -0
  48. package/package.json +225 -0
@@ -0,0 +1,789 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/express/index.ts
22
+ var express_exports = {};
23
+ __export(express_exports, {
24
+ mcpTool: () => mcpTool,
25
+ mountMcp: () => mountMcp
26
+ });
27
+ module.exports = __toCommonJS(express_exports);
28
+
29
+ // src/core/dispatch.ts
30
+ function buildUrl(base, req) {
31
+ const url = new URL(base.replace(/\/+$/, "") + req.path);
32
+ for (const [k, v] of Object.entries(req.query)) {
33
+ for (const item of Array.isArray(v) ? v : [
34
+ v
35
+ ]) url.searchParams.append(k, item);
36
+ }
37
+ return url.toString();
38
+ }
39
+ __name(buildUrl, "buildUrl");
40
+ function createFetchDispatcher(opts) {
41
+ const doFetch = opts.fetch ?? fetch;
42
+ return async (req, ctx) => {
43
+ const base = typeof opts.baseUrl === "function" ? opts.baseUrl(ctx) : opts.baseUrl;
44
+ const res = await doFetch(buildUrl(base, req), {
45
+ method: req.method,
46
+ headers: req.headers,
47
+ body: req.body === void 0 || req.method === "GET" || req.method === "HEAD" ? void 0 : JSON.stringify(req.body),
48
+ signal: AbortSignal.timeout(opts.timeoutMs ?? 3e4)
49
+ });
50
+ const headers = {};
51
+ res.headers.forEach((v, k) => headers[k] = v);
52
+ return {
53
+ status: res.status,
54
+ headers,
55
+ body: await res.text()
56
+ };
57
+ };
58
+ }
59
+ __name(createFetchDispatcher, "createFetchDispatcher");
60
+
61
+ // src/core/marker.ts
62
+ var MCP_TOOL = /* @__PURE__ */ Symbol.for("mcp-expose.tool");
63
+ function markerOf(fn) {
64
+ return typeof fn === "function" ? fn[MCP_TOOL] : void 0;
65
+ }
66
+ __name(markerOf, "markerOf");
67
+ function mark(fn, opts) {
68
+ Object.defineProperty(fn, MCP_TOOL, {
69
+ value: opts,
70
+ enumerable: false
71
+ });
72
+ return fn;
73
+ }
74
+ __name(mark, "mark");
75
+
76
+ // src/core/node.ts
77
+ function normalizeHeaders(headers) {
78
+ const out = {};
79
+ for (const [k, v] of Object.entries(headers)) {
80
+ if (v === void 0) continue;
81
+ out[k.toLowerCase()] = Array.isArray(v) ? v.join(", ") : String(v);
82
+ }
83
+ return out;
84
+ }
85
+ __name(normalizeHeaders, "normalizeHeaders");
86
+ function readRawBody(req, limit = 4 * 1024 * 1024) {
87
+ return new Promise((resolve, reject) => {
88
+ let size = 0;
89
+ const chunks = [];
90
+ req.on("data", (c) => {
91
+ size += c.length;
92
+ if (size > limit) {
93
+ reject(new Error("Request body too large"));
94
+ req.destroy();
95
+ } else chunks.push(c);
96
+ });
97
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
98
+ req.on("error", reject);
99
+ });
100
+ }
101
+ __name(readRawBody, "readRawBody");
102
+ function loopbackBaseUrl(req) {
103
+ const socket = req.socket;
104
+ const port = socket.localPort;
105
+ const encrypted = socket.encrypted;
106
+ const bound = socket.server?.address();
107
+ let host = typeof bound === "object" && bound?.address || socket.localAddress || "127.0.0.1";
108
+ if (host.startsWith("::ffff:")) host = host.slice(7);
109
+ if (host === "::" || host === "0.0.0.0") host = "127.0.0.1";
110
+ if (host.includes(":")) host = `[${host}]`;
111
+ return `${encrypted ? "https" : "http"}://${host}:${port}`;
112
+ }
113
+ __name(loopbackBaseUrl, "loopbackBaseUrl");
114
+ function writeNodeResponse(res, out) {
115
+ res.statusCode = out.status;
116
+ for (const [k, v] of Object.entries(out.headers)) res.setHeader(k, v);
117
+ res.end(out.body);
118
+ }
119
+ __name(writeNodeResponse, "writeNodeResponse");
120
+
121
+ // src/core/schema.ts
122
+ function isStandard(s) {
123
+ return typeof s === "object" && s !== null && "~standard" in s;
124
+ }
125
+ __name(isStandard, "isStandard");
126
+ function toJsonSchema(schema) {
127
+ if (schema === void 0 || schema === null) return void 0;
128
+ let out;
129
+ if (isStandard(schema) && typeof schema["~standard"].jsonSchema?.input === "function") {
130
+ out = schema["~standard"].jsonSchema.input({
131
+ target: "draft-2020-12"
132
+ });
133
+ } else if (typeof schema.toJSONSchema === "function") {
134
+ out = schema.toJSONSchema();
135
+ } else if (isStandard(schema)) {
136
+ throw new Error("mcp-expose: this schema library does not support JSON Schema export. Pass a plain JSON Schema instead.");
137
+ } else {
138
+ out = schema;
139
+ }
140
+ const { $schema: _ignored, ...rest } = out;
141
+ return rest;
142
+ }
143
+ __name(toJsonSchema, "toJsonSchema");
144
+ function toValidator(schema) {
145
+ if (!isStandard(schema) || typeof schema["~standard"].validate !== "function") return void 0;
146
+ const validate = schema["~standard"].validate;
147
+ return async (value) => {
148
+ const result = await validate(value);
149
+ if (!result.issues) return {
150
+ ok: true,
151
+ value: result.value
152
+ };
153
+ const message = result.issues.map((i) => {
154
+ const path = (i.path ?? []).map((p) => typeof p === "object" ? String(p.key) : String(p)).join(".");
155
+ return path ? `${path}: ${i.message}` : i.message;
156
+ }).join("; ");
157
+ return {
158
+ ok: false,
159
+ message
160
+ };
161
+ };
162
+ }
163
+ __name(toValidator, "toValidator");
164
+ function checkRequired(schema, args) {
165
+ const missing = (schema.required ?? []).filter((k) => args[k] === void 0);
166
+ return missing.length ? `Missing required argument(s): ${missing.join(", ")}` : void 0;
167
+ }
168
+ __name(checkRequired, "checkRequired");
169
+
170
+ // src/core/route-tool.ts
171
+ var DEFAULT_FORWARD_HEADERS = [
172
+ "authorization",
173
+ "cookie",
174
+ "x-api-key",
175
+ "accept-language"
176
+ ];
177
+ var BODY_METHODS = /* @__PURE__ */ new Set([
178
+ "POST",
179
+ "PUT",
180
+ "PATCH"
181
+ ]);
182
+ var PARAM_RE = /:([A-Za-z0-9_]+)\??|\{([A-Za-z0-9_]+)\}/g;
183
+ function pathParams(path) {
184
+ return [
185
+ ...path.matchAll(PARAM_RE)
186
+ ].map((m) => m[1] ?? m[2]);
187
+ }
188
+ __name(pathParams, "pathParams");
189
+ function encodeSegment(name, value) {
190
+ const raw = String(value);
191
+ if (raw === "" || raw === "." || raw === "..") {
192
+ throw new Error(`Invalid value for path parameter "${name}": ${JSON.stringify(raw)}`);
193
+ }
194
+ return encodeURIComponent(raw);
195
+ }
196
+ __name(encodeSegment, "encodeSegment");
197
+ function interpolatePath(path, values) {
198
+ return path.replace(/\/:([A-Za-z0-9_]+)\?/g, (_, k) => values[k] === void 0 ? "" : `/${encodeSegment(k, values[k])}`).replace(PARAM_RE, (_, a, b) => {
199
+ const k = a ?? b;
200
+ return encodeSegment(k, values[k]);
201
+ });
202
+ }
203
+ __name(interpolatePath, "interpolatePath");
204
+ function joinPaths(...parts) {
205
+ const joined = parts.filter((p) => !!p && p !== "/").map((p) => p.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
206
+ return `/${joined}`;
207
+ }
208
+ __name(joinPaths, "joinPaths");
209
+ function toolNameFromRoute(method, path) {
210
+ const segments = path.split("/").filter(Boolean).map((s) => {
211
+ const m = /^(?::([A-Za-z0-9_]+)\??|\{([A-Za-z0-9_]+)\})$/.exec(s);
212
+ return m ? `by_${m[1] ?? m[2]}` : s;
213
+ });
214
+ return sanitizeToolName([
215
+ method.toLowerCase(),
216
+ ...segments
217
+ ].join("_"));
218
+ }
219
+ __name(toolNameFromRoute, "toolNameFromRoute");
220
+ function sanitizeToolName(name) {
221
+ return name.replace(/[^A-Za-z0-9_-]+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").slice(0, 64);
222
+ }
223
+ __name(sanitizeToolName, "sanitizeToolName");
224
+ function defaultAnnotations(method) {
225
+ switch (method) {
226
+ case "GET":
227
+ case "HEAD":
228
+ return {
229
+ readOnlyHint: true,
230
+ openWorldHint: false
231
+ };
232
+ case "DELETE":
233
+ return {
234
+ readOnlyHint: false,
235
+ destructiveHint: true,
236
+ idempotentHint: true,
237
+ openWorldHint: false
238
+ };
239
+ case "PUT":
240
+ return {
241
+ readOnlyHint: false,
242
+ destructiveHint: false,
243
+ idempotentHint: true,
244
+ openWorldHint: false
245
+ };
246
+ default:
247
+ return {
248
+ readOnlyHint: false,
249
+ destructiveHint: false,
250
+ openWorldHint: false
251
+ };
252
+ }
253
+ }
254
+ __name(defaultAnnotations, "defaultAnnotations");
255
+ function buildInputSchema(route, opts) {
256
+ const method = route.method.toUpperCase();
257
+ const params = pathParams(route.path);
258
+ const mapping = {
259
+ params,
260
+ query: /* @__PURE__ */ new Set(),
261
+ body: /* @__PURE__ */ new Set(),
262
+ wholeBody: false,
263
+ bodyMethod: BODY_METHODS.has(method)
264
+ };
265
+ if (opts.input) {
266
+ const schema2 = toJsonSchema(opts.input);
267
+ return {
268
+ schema: {
269
+ type: "object",
270
+ ...schema2
271
+ },
272
+ mapping
273
+ };
274
+ }
275
+ const properties = {};
276
+ const required = /* @__PURE__ */ new Set();
277
+ const paramSchema = toJsonSchema(opts.params);
278
+ for (const p of params) {
279
+ properties[p] = paramSchema?.properties?.[p] ?? {
280
+ type: "string",
281
+ description: `Path parameter "${p}"`
282
+ };
283
+ if (!route.path.includes(`:${p}?`)) required.add(p);
284
+ }
285
+ const querySchema = toJsonSchema(opts.query);
286
+ for (const [k, v] of Object.entries(querySchema?.properties ?? {})) {
287
+ properties[k] = v;
288
+ mapping.query.add(k);
289
+ }
290
+ for (const k of querySchema?.required ?? []) required.add(k);
291
+ const bodySchema = toJsonSchema(opts.body);
292
+ if (bodySchema) {
293
+ if (bodySchema.properties && (bodySchema.type === void 0 || bodySchema.type === "object")) {
294
+ for (const [k, v] of Object.entries(bodySchema.properties)) {
295
+ properties[k] = v;
296
+ mapping.body.add(k);
297
+ }
298
+ for (const k of bodySchema.required ?? []) required.add(k);
299
+ } else {
300
+ properties.body = bodySchema;
301
+ required.add("body");
302
+ mapping.wholeBody = true;
303
+ }
304
+ }
305
+ const schema = {
306
+ type: "object",
307
+ properties
308
+ };
309
+ if (required.size) schema.required = [
310
+ ...required
311
+ ];
312
+ return {
313
+ schema,
314
+ mapping
315
+ };
316
+ }
317
+ __name(buildInputSchema, "buildInputSchema");
318
+ function splitArgs(args, mapping) {
319
+ const pathValues = {};
320
+ const query = {};
321
+ let body;
322
+ for (const [k, v] of Object.entries(args)) {
323
+ if (v === void 0) continue;
324
+ if (mapping.params.includes(k)) {
325
+ pathValues[k] = v;
326
+ } else if (mapping.wholeBody && k === "body") {
327
+ body = v;
328
+ } else if (mapping.query.has(k) || !mapping.body.has(k) && !mapping.bodyMethod) {
329
+ query[k] = Array.isArray(v) ? v.map(String) : typeof v === "object" && v !== null ? JSON.stringify(v) : String(v);
330
+ } else {
331
+ body = {
332
+ ...body ?? {},
333
+ [k]: v
334
+ };
335
+ }
336
+ }
337
+ if (body === void 0 && mapping.bodyMethod) body = {};
338
+ return {
339
+ pathValues,
340
+ query,
341
+ body
342
+ };
343
+ }
344
+ __name(splitArgs, "splitArgs");
345
+ function responseToResult(res, maxChars) {
346
+ let text = res.body;
347
+ if (text.length > maxChars) text = `${text.slice(0, maxChars)}
348
+ \u2026[truncated ${text.length - maxChars} chars]`;
349
+ const isError = res.status >= 400;
350
+ const result = {
351
+ content: [
352
+ {
353
+ type: "text",
354
+ text: isError ? `HTTP ${res.status}
355
+ ${text}` : text || `HTTP ${res.status}`
356
+ }
357
+ ]
358
+ };
359
+ if (isError) {
360
+ result.isError = true;
361
+ return result;
362
+ }
363
+ if ((res.headers["content-type"] ?? "").includes("json") && text === res.body) {
364
+ try {
365
+ const parsed = JSON.parse(res.body);
366
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) result.structuredContent = parsed;
367
+ } catch {
368
+ }
369
+ }
370
+ return result;
371
+ }
372
+ __name(responseToResult, "responseToResult");
373
+ function pickForwardHeaders(ctx, names) {
374
+ const out = {};
375
+ for (const name of names) {
376
+ const v = ctx.headers[name.toLowerCase()];
377
+ if (v !== void 0) out[name.toLowerCase()] = v;
378
+ }
379
+ return out;
380
+ }
381
+ __name(pickForwardHeaders, "pickForwardHeaders");
382
+ function createRouteTool(route, opts, dispatch, serverOpts = {}) {
383
+ const method = route.method.toUpperCase();
384
+ const name = sanitizeToolName(opts.name ?? toolNameFromRoute(method, route.path));
385
+ const { schema, mapping } = buildInputSchema(route, opts);
386
+ const forward = serverOpts.forwardHeaders ?? DEFAULT_FORWARD_HEADERS;
387
+ const maxChars = serverOpts.maxResponseChars ?? 1e5;
388
+ return {
389
+ name,
390
+ title: opts.title,
391
+ description: opts.description ?? `${method} ${route.path}`,
392
+ inputSchema: schema,
393
+ annotations: {
394
+ ...defaultAnnotations(method),
395
+ ...opts.annotations
396
+ },
397
+ validate: toValidator(opts.input),
398
+ async handler(args, ctx) {
399
+ const missing = opts.input ? void 0 : checkRequired(schema, args);
400
+ if (missing) return {
401
+ isError: true,
402
+ content: [
403
+ {
404
+ type: "text",
405
+ text: missing
406
+ }
407
+ ]
408
+ };
409
+ const { pathValues, query, body } = splitArgs(args, mapping);
410
+ const headers = {
411
+ accept: "application/json",
412
+ ...pickForwardHeaders(ctx, forward),
413
+ ...opts.headers,
414
+ "x-mcp-tool": name
415
+ };
416
+ if (ctx.clientIp) headers["x-forwarded-for"] = ctx.clientIp;
417
+ if (body !== void 0) headers["content-type"] = "application/json";
418
+ let path;
419
+ try {
420
+ path = interpolatePath(route.path, pathValues);
421
+ } catch (err) {
422
+ return {
423
+ isError: true,
424
+ content: [
425
+ {
426
+ type: "text",
427
+ text: err.message
428
+ }
429
+ ]
430
+ };
431
+ }
432
+ const res = await dispatch({
433
+ method,
434
+ path,
435
+ query,
436
+ body,
437
+ headers
438
+ }, ctx);
439
+ return responseToResult(res, maxChars);
440
+ }
441
+ };
442
+ }
443
+ __name(createRouteTool, "createRouteTool");
444
+
445
+ // src/core/server.ts
446
+ var SUPPORTED_PROTOCOL_VERSIONS = [
447
+ "2025-11-25",
448
+ "2025-06-18",
449
+ "2025-03-26",
450
+ "2024-11-05"
451
+ ];
452
+ var LATEST_PROTOCOL_VERSION = SUPPORTED_PROTOCOL_VERSIONS[0];
453
+ var PARSE_ERROR = -32700;
454
+ var INVALID_REQUEST = -32600;
455
+ var METHOD_NOT_FOUND = -32601;
456
+ var INVALID_PARAMS = -32602;
457
+ var ok = /* @__PURE__ */ __name((id, result) => ({
458
+ jsonrpc: "2.0",
459
+ id,
460
+ result
461
+ }), "ok");
462
+ var fail = /* @__PURE__ */ __name((id, code, message) => ({
463
+ jsonrpc: "2.0",
464
+ id,
465
+ error: {
466
+ code,
467
+ message
468
+ }
469
+ }), "fail");
470
+ var McpServer = class {
471
+ static {
472
+ __name(this, "McpServer");
473
+ }
474
+ options;
475
+ tools = /* @__PURE__ */ new Map();
476
+ loaders = [];
477
+ constructor(options) {
478
+ this.options = options;
479
+ }
480
+ /** Register a tool. Throws on duplicate names. */
481
+ addTool(tool) {
482
+ if (this.tools.has(tool.name)) {
483
+ throw new Error(`mcp-expose: duplicate tool name "${tool.name}". Set an explicit \`name\`.`);
484
+ }
485
+ this.tools.set(tool.name, tool);
486
+ return this;
487
+ }
488
+ /** Register a function that adds tools lazily, right before the first MCP request is served. */
489
+ onLoad(loader) {
490
+ this.loaders.push(loader);
491
+ return this;
492
+ }
493
+ async listTools() {
494
+ await this.load();
495
+ return [
496
+ ...this.tools.values()
497
+ ];
498
+ }
499
+ async load() {
500
+ if (!this.loaders.length) return;
501
+ const loaders = this.loaders;
502
+ this.loaders = [];
503
+ for (const l of loaders) await l();
504
+ }
505
+ /** Handle one JSON-RPC message (or a batch). Returns null when no response is due. */
506
+ async handleMessage(message, ctx) {
507
+ if (Array.isArray(message)) {
508
+ const results = (await Promise.all(message.map((m) => this.handleOne(m, ctx)))).filter((r) => r !== null);
509
+ return results.length ? results : null;
510
+ }
511
+ return this.handleOne(message, ctx);
512
+ }
513
+ async handleOne(message, ctx) {
514
+ const msg = message;
515
+ if (!msg || typeof msg !== "object" || msg.jsonrpc !== "2.0") {
516
+ return fail(null, INVALID_REQUEST, "Invalid JSON-RPC message");
517
+ }
518
+ if (typeof msg.method !== "string" || msg.id === void 0) return null;
519
+ const id = msg.id;
520
+ const params = msg.params ?? {};
521
+ await this.load();
522
+ switch (msg.method) {
523
+ case "initialize": {
524
+ const requested = params.protocolVersion;
525
+ const protocolVersion = requested && SUPPORTED_PROTOCOL_VERSIONS.includes(requested) ? requested : LATEST_PROTOCOL_VERSION;
526
+ return ok(id, {
527
+ protocolVersion,
528
+ capabilities: {
529
+ tools: {
530
+ listChanged: false
531
+ }
532
+ },
533
+ serverInfo: {
534
+ name: this.options.name,
535
+ version: this.options.version ?? "1.0.0"
536
+ },
537
+ ...this.options.instructions ? {
538
+ instructions: this.options.instructions
539
+ } : {}
540
+ });
541
+ }
542
+ case "ping":
543
+ return ok(id, {});
544
+ case "tools/list":
545
+ return ok(id, {
546
+ tools: [
547
+ ...this.tools.values()
548
+ ].map((t) => ({
549
+ name: t.name,
550
+ ...t.title ? {
551
+ title: t.title
552
+ } : {},
553
+ description: t.description,
554
+ inputSchema: t.inputSchema,
555
+ ...t.annotations ? {
556
+ annotations: t.annotations
557
+ } : {}
558
+ }))
559
+ });
560
+ case "tools/call": {
561
+ const tool = this.tools.get(params.name);
562
+ if (!tool) return fail(id, INVALID_PARAMS, `Unknown tool: ${String(params.name)}`);
563
+ return ok(id, await this.callTool(tool, params.arguments ?? {}, ctx));
564
+ }
565
+ case "resources/list":
566
+ return ok(id, {
567
+ resources: []
568
+ });
569
+ case "prompts/list":
570
+ return ok(id, {
571
+ prompts: []
572
+ });
573
+ default:
574
+ return fail(id, METHOD_NOT_FOUND, `Method not found: ${msg.method}`);
575
+ }
576
+ }
577
+ async callTool(tool, args, ctx) {
578
+ try {
579
+ if (tool.validate) {
580
+ const v = await tool.validate(args);
581
+ if (!v.ok) return {
582
+ isError: true,
583
+ content: [
584
+ {
585
+ type: "text",
586
+ text: `Invalid arguments: ${v.message}`
587
+ }
588
+ ]
589
+ };
590
+ args = v.value;
591
+ }
592
+ return await tool.handler(args, ctx);
593
+ } catch (err) {
594
+ return {
595
+ isError: true,
596
+ content: [
597
+ {
598
+ type: "text",
599
+ text: `Tool error: ${err.message}`
600
+ }
601
+ ]
602
+ };
603
+ }
604
+ }
605
+ /** Check the Origin header (DNS rebinding protection). */
606
+ isOriginAllowed(origin) {
607
+ if (!origin) return true;
608
+ const allowed = this.options.allowedOrigins;
609
+ if (allowed === "*") return true;
610
+ return (allowed ?? []).includes(origin);
611
+ }
612
+ /**
613
+ * Framework-neutral Streamable HTTP handler. Adapters convert their request
614
+ * into McpHttpRequest, call this, and write the McpHttpResponse back.
615
+ */
616
+ async handleHttp(req, ctx) {
617
+ const json = {
618
+ "content-type": "application/json"
619
+ };
620
+ if (!this.isOriginAllowed(req.headers.origin)) {
621
+ return {
622
+ status: 403,
623
+ headers: json,
624
+ body: JSON.stringify(fail(null, INVALID_REQUEST, "Origin not allowed"))
625
+ };
626
+ }
627
+ if (req.method.toUpperCase() !== "POST") {
628
+ return {
629
+ status: 405,
630
+ headers: {
631
+ allow: "POST"
632
+ }
633
+ };
634
+ }
635
+ let body = req.body;
636
+ if (typeof body === "string" || body instanceof Uint8Array) {
637
+ try {
638
+ body = JSON.parse(typeof body === "string" ? body : new TextDecoder().decode(body));
639
+ } catch {
640
+ return {
641
+ status: 400,
642
+ headers: json,
643
+ body: JSON.stringify(fail(null, PARSE_ERROR, "Parse error"))
644
+ };
645
+ }
646
+ }
647
+ const response = await this.handleMessage(body, ctx);
648
+ if (response === null) return {
649
+ status: 202,
650
+ headers: {}
651
+ };
652
+ return {
653
+ status: 200,
654
+ headers: json,
655
+ body: JSON.stringify(response)
656
+ };
657
+ }
658
+ };
659
+
660
+ // src/express/discover.ts
661
+ function collectExpress3(routes, out) {
662
+ for (const list of Object.values(routes)) {
663
+ for (const r of list) {
664
+ const marker = r.callbacks.map(markerOf).find(Boolean);
665
+ if (marker && typeof r.path === "string") out.push({
666
+ method: r.method.toUpperCase(),
667
+ path: r.path,
668
+ options: marker
669
+ });
670
+ }
671
+ }
672
+ }
673
+ __name(collectExpress3, "collectExpress3");
674
+ function stackOf(app) {
675
+ let router = app._router;
676
+ if (!router) {
677
+ try {
678
+ router = app.router;
679
+ } catch {
680
+ router = void 0;
681
+ }
682
+ }
683
+ return router?.stack ?? [];
684
+ }
685
+ __name(stackOf, "stackOf");
686
+ function express4Prefix(layer) {
687
+ const re = layer.regexp;
688
+ if (!re) return void 0;
689
+ if (re.fast_slash) return "";
690
+ const m = /^\/\^((?:\\[.*+?^${}()|[\]\\/]|[^.*+?^${}()|[\]\\/])*)\\\/\?\(\?=\\\/\|\$\)\/i?$/.exec(re.toString());
691
+ return m ? m[1].replace(/\\(.)/g, "$1") : void 0;
692
+ }
693
+ __name(express4Prefix, "express4Prefix");
694
+ function collect(stack, prefix, routers, out, unresolved) {
695
+ for (const layer of stack) {
696
+ if (layer.route) {
697
+ const marker = layer.route.stack.map((l) => markerOf(l.handle)).find(Boolean);
698
+ if (!marker) continue;
699
+ const paths = Array.isArray(layer.route.path) ? layer.route.path : [
700
+ layer.route.path
701
+ ];
702
+ for (const method of Object.keys(layer.route.methods).filter((m) => m !== "_all")) {
703
+ for (const p of paths) out.push({
704
+ method: method.toUpperCase(),
705
+ path: joinPaths(prefix, p),
706
+ options: marker
707
+ });
708
+ }
709
+ } else if (layer.handle?.stack) {
710
+ const known = routers.get(layer.handle);
711
+ const sub = known ?? express4Prefix(layer);
712
+ if (sub === void 0) {
713
+ const inner = [];
714
+ collect(layer.handle.stack, "", routers, inner, unresolved);
715
+ if (inner.length) unresolved.push(...inner.map((f) => `${f.method} ${f.path}`));
716
+ continue;
717
+ }
718
+ collect(layer.handle.stack, joinPaths(prefix, sub), routers, out, unresolved);
719
+ }
720
+ }
721
+ }
722
+ __name(collect, "collect");
723
+ function discoverExpressRoutes(app, routers = {}) {
724
+ const stack = stackOf(app);
725
+ const byRouter = new Map(Object.entries(routers).map(([prefix, r]) => [
726
+ r,
727
+ prefix
728
+ ]));
729
+ const out = [];
730
+ const unresolved = [];
731
+ collect(stack, "", byRouter, out, unresolved);
732
+ if (!stack.length && app.routes && typeof app.routes === "object") collectExpress3(app.routes, out);
733
+ if (unresolved.length) {
734
+ throw new Error(`mcp-expose: cannot determine the mount path of a router containing MCP tools (${unresolved.join(", ")}). Pass it via the \`routers\` option, e.g. mountMcp(app, { routers: { '/api': apiRouter } }).`);
735
+ }
736
+ return out;
737
+ }
738
+ __name(discoverExpressRoutes, "discoverExpressRoutes");
739
+
740
+ // src/express/index.ts
741
+ function mcpTool(options = {}) {
742
+ return mark((_req, _res, next) => next(), options);
743
+ }
744
+ __name(mcpTool, "mcpTool");
745
+ function mountMcp(app, options) {
746
+ const path = options.path ?? "/mcp";
747
+ const server = new McpServer(options);
748
+ const dispatch = createFetchDispatcher({
749
+ baseUrl: options.baseUrl ?? ((ctx) => loopbackBaseUrl(ctx.raw))
750
+ });
751
+ for (const t of options.tools ?? []) server.addTool(t);
752
+ server.onLoad(() => {
753
+ const found = discoverExpressRoutes(app, options.routers);
754
+ for (const r of options.routes ?? []) found.push({
755
+ method: r.method,
756
+ path: r.path,
757
+ options: r
758
+ });
759
+ for (const f of found) server.addTool(createRouteTool({
760
+ method: f.method,
761
+ path: f.path
762
+ }, f.options, dispatch, options));
763
+ });
764
+ const handler = /* @__PURE__ */ __name((req, res, next) => {
765
+ (async () => {
766
+ const body = req.method === "POST" && req.body === void 0 ? await readRawBody(req) : req.body;
767
+ const ctx = {
768
+ headers: normalizeHeaders(req.headers),
769
+ clientIp: req.ip ?? req.socket.remoteAddress,
770
+ raw: req
771
+ };
772
+ const out = await server.handleHttp({
773
+ method: req.method ?? "POST",
774
+ headers: ctx.headers,
775
+ body
776
+ }, ctx);
777
+ writeNodeResponse(res, out);
778
+ })().catch(next);
779
+ }, "handler");
780
+ app.all.call(app, path, ...options.middleware ?? [], handler);
781
+ return server;
782
+ }
783
+ __name(mountMcp, "mountMcp");
784
+ // Annotate the CommonJS export names for ESM import in node:
785
+ 0 && (module.exports = {
786
+ mcpTool,
787
+ mountMcp
788
+ });
789
+ //# sourceMappingURL=index.cjs.map