@rekog/mcp-nest 2.0.0-alpha.2 → 2.0.0-alpha.4
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/mcp/decorators/mcp-controller.decorator.d.ts.map +1 -1
- package/dist/mcp/decorators/mcp-controller.decorator.js.map +1 -1
- package/dist/mcp/transport/transports/streamable-http.transport.d.ts.map +1 -1
- package/dist/mcp/transport/transports/streamable-http.transport.js +1 -2
- package/dist/mcp/transport/transports/streamable-http.transport.js.map +1 -1
- package/package.json +1 -1
- package/src/mcp/decorators/mcp-controller.decorator.ts +1 -4
- package/src/mcp/transport/transports/streamable-http.transport.ts +1 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-controller.decorator.d.ts","sourceRoot":"","sources":["../../../src/mcp/decorators/mcp-controller.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,iBAAiB,EAElB,MAAM,gBAAgB,CAAC;AAkBxB,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAM7D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"mcp-controller.decorator.d.ts","sourceRoot":"","sources":["../../../src/mcp/decorators/mcp-controller.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,iBAAiB,EAElB,MAAM,gBAAgB,CAAC;AAkBxB,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAM7D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA4DD,wBAAgB,aAAa,CAC3B,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,oBAAoB,GACzD,cAAc,CA8BhB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-controller.decorator.js","sourceRoot":"","sources":["../../../src/mcp/decorators/mcp-controller.decorator.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"mcp-controller.decorator.js","sourceRoot":"","sources":["../../../src/mcp/decorators/mcp-controller.decorator.ts"],"names":[],"mappings":";;AA0FA,sCAgCC;AA1HD,2CAKwB;AACxB,2CAA0D;AAC1D,kFAG8C;AAS9C,MAAM,uBAAuB,GAAG,8BAA8B,CAAC;AAC/D,MAAM,kBAAkB,GAAG,yBAAyB,CAAC;AAyBrD,SAAS,wBAAwB,CAAC,MAAgB,EAAE,WAAmB;IACrE,MAAM,KAAK,GAAI,MAAkD,CAAC,SAAS,CAAC;IAC5E,IAAI,CAAC,KAAK;QAAE,OAAO;IACnB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,IAAI,GAAG,KAAK,aAAa;YAAE,SAAS;QACpC,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,OAAO,EAAE,KAAK,UAAU;YAAE,SAAS;QACvC,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,uBAAuB,EAAE,EAAE,CAEjD,CAAC;QACd,IAAI,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YAC7B,OAAO,CAAC,cAAc,CAAC,kBAAkB,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;AACH,CAAC;AA+BD,SAAgB,aAAa,CAC3B,eAA0D;IAI1D,IAAI,MAA0B,CAAC;IAC/B,IAAI,iBAAiB,GACnB,eAAe,CAAC;IAClB,IACE,eAAe;QACf,OAAO,eAAe,KAAK,QAAQ;QACnC,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC;QAC/B,QAAQ,IAAI,eAAe,EAC3B,CAAC;QACD,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,eAAe,CAAC;QACxD,MAAM,GAAG,UAAU,CAAC;QACpB,iBAAiB,GAAG,IAAI,CAAC;IAC3B,CAAC;IAED,OAAO,CAAC,MAAgB,EAAE,EAAE;QAC1B,IAAA,wBAAe,EACb,IAAA,mBAAU,EAAC,iBAAsC,CAAC,EAClD,IAAA,oBAAW,EAAC,uCAA2B,EAAE,IAAI,CAAC,EAC9C,IAAA,oBAAW,EAAC,sDAA4B,EAAE,MAAM,CAAC,CAClD,CAAC,MAAM,CAAC,CAAC;QAIV,IAAI,MAAM,EAAE,CAAC;YACX,wBAAwB,CAAC,MAAM,EAAE,IAAA,yCAAe,EAAC,MAAM,CAAC,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import {\n applyDecorators,\n Controller,\n ControllerOptions,\n SetMetadata,\n} from '@nestjs/common';\nimport { MCP_CONTROLLER_METADATA_KEY } from './constants';\nimport {\n MCP_SERVER_NAME_METADATA_KEY,\n mcpTransportFor,\n} from '../transport/mcp-transport.constants';\n\n/**\n * NestJS-internal metadata keys written by `@MessagePattern`. They are NOT\n * re-exported from the `@nestjs/microservices` package entry, so we reference\n * the string literals directly (see `@nestjs/microservices/constants`:\n * `PATTERN_EXTRAS_METADATA` / `TRANSPORT_METADATA`). The multi-server e2e test\n * guards against these drifting on a NestJS bump.\n */\nconst PATTERN_EXTRAS_METADATA = 'microservices:pattern_extras';\nconst TRANSPORT_METADATA = 'microservices:transport';\n\n/** Options for {@link McpController}: standard `@Controller` options plus a server name. */\nexport interface McpControllerOptions extends ControllerOptions {\n /**\n * Logical MCP server name. The controller's MCP methods bind ONLY to a\n * connected `McpStrategy({ server: <name> })`. Omit to use the default shared\n * server (binds to a plain `McpStrategy()`).\n */\n server?: string;\n}\n\n/**\n * Re-tags every MCP capability method on `target`'s prototype with the given\n * transport id, overriding the default {@link MCP_TRANSPORT} that the\n * `@Tool`/`@Resource`/`@Prompt` method decorators wrote.\n *\n * This works because class decorators run AFTER the method decorators, and both\n * `@MessagePattern` and this walk reach the SAME function reference via\n * `prototype[key]` — so re-defining the transport metadata on it wins. We\n * recognize MCP methods by the `mcpType` marker stored in their pattern extras.\n *\n * Note: `Object.getOwnPropertyNames` only sees methods declared on this class,\n * not ones inherited from a base class.\n */\nfunction retagMcpMethodsTransport(target: Function, transportId: symbol): void {\n const proto = (target as { prototype?: Record<string, unknown> }).prototype;\n if (!proto) return;\n for (const key of Object.getOwnPropertyNames(proto)) {\n if (key === 'constructor') continue;\n const fn = proto[key];\n if (typeof fn !== 'function') continue;\n const extras = Reflect.getMetadata(PATTERN_EXTRAS_METADATA, fn) as\n | { mcpType?: unknown }\n | undefined;\n if (extras && extras.mcpType) {\n Reflect.defineMetadata(TRANSPORT_METADATA, transportId, fn);\n }\n }\n}\n\n/**\n * Marks a class as an MCP capability controller.\n *\n * NestJS only scans classes registered in a module's `controllers` array for\n * microservice (`@MessagePattern`) handlers, so MCP tool/resource/prompt classes\n * MUST be controllers. `@McpController` composes the standard `@Controller()`\n * decorator (so the framework discovers and binds the `@Tool`/`@Resource`/\n * `@Prompt` handlers) and adds a marker the {@link McpStrategy} uses to recognize\n * MCP controllers.\n *\n * Because `@Tool`/`@Resource`/`@Prompt` do not add HTTP route decorators, methods\n * on an `@McpController` are NOT exposed as HTTP routes — they are reachable only\n * through the MCP transport.\n *\n * To run multiple isolated MCP servers in one app, assign a controller to a\n * named server with `@McpController({ server: 'admin' })`; its tools/resources/\n * prompts then bind ONLY to a matching `McpStrategy({ server: 'admin' })`.\n *\n * @example\n * ```typescript\n * @McpController()\n * export class GreetingTool {\n * @Tool({ name: 'hello', description: 'Say hello', parameters: z.object({ name: z.string() }) })\n * sayHello(@Payload() { name }: { name: string }, @Ctx() ctx: McpContext) {\n * return { content: [{ type: 'text', text: `Hello, ${name}!` }] };\n * }\n * }\n * ```\n */\nexport function McpController(\n prefixOrOptions?: string | string[] | McpControllerOptions,\n): ClassDecorator {\n // Extract the MCP-specific `server` name (if any) and forward the remaining\n // standard controller options to `@Controller`.\n let server: string | undefined;\n let controllerOptions: string | string[] | ControllerOptions | undefined =\n prefixOrOptions;\n if (\n prefixOrOptions &&\n typeof prefixOrOptions === 'object' &&\n !Array.isArray(prefixOrOptions) &&\n 'server' in prefixOrOptions\n ) {\n const { server: serverName, ...rest } = prefixOrOptions;\n server = serverName;\n controllerOptions = rest;\n }\n\n return (target: Function) => {\n applyDecorators(\n Controller(controllerOptions as ControllerOptions),\n SetMetadata(MCP_CONTROLLER_METADATA_KEY, true),\n SetMetadata(MCP_SERVER_NAME_METADATA_KEY, server),\n )(target);\n // A named server re-tags this controller's MCP methods with that server's\n // transport id, so NestJS routes them only to the matching strategy. An\n // unnamed controller keeps the default MCP_TRANSPORT (single-server) behavior.\n if (server) {\n retagMcpMethodsTransport(target, mcpTransportFor(server));\n }\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamable-http.transport.d.ts","sourceRoot":"","sources":["../../../../src/mcp/transport/transports/streamable-http.transport.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,MAAM,WAAW,8BAA8B;IAgB7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAclB,YAAY,CAAC,EAAE,OAAO,CAAC;IAWvB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;IAmBlC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAOD,qBAAa,uBAAwB,YAAW,YAAY;IAC1D,QAAQ,CAAC,IAAI,EAAG,iBAAiB,CAAU;IAE3C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;IACvC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAU;IAC7C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAe;IAElD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAU;IAE3C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAU;IAEvC,OAAO,CAAC,eAAe,CAAS;IAEhC,OAAO,CAAC,QAAQ,CAAC,UAAU,CACtB;IACL,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,GAAG,CAAC,CAAsB;gBAEtB,OAAO,GAAE,8BAAmC;
|
|
1
|
+
{"version":3,"file":"streamable-http.transport.d.ts","sourceRoot":"","sources":["../../../../src/mcp/transport/transports/streamable-http.transport.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,MAAM,WAAW,8BAA8B;IAgB7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAclB,YAAY,CAAC,EAAE,OAAO,CAAC;IAWvB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;IAmBlC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAOD,qBAAa,uBAAwB,YAAW,YAAY;IAC1D,QAAQ,CAAC,IAAI,EAAG,iBAAiB,CAAU;IAE3C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;IACvC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAU;IAC7C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAe;IAElD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAU;IAE3C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAU;IAEvC,OAAO,CAAC,eAAe,CAAS;IAEhC,OAAO,CAAC,QAAQ,CAAC,UAAU,CACtB;IACL,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,GAAG,CAAC,CAAsB;gBAEtB,OAAO,GAAE,8BAAmC;IAuBxD,IAAI,YAAY,IAAI,cAAc,CAOjC;IAED,KAAK,CAAC,GAAG,EAAE,mBAAmB,GAAG,IAAI;IA4C/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAMd,UAAU;YAwBV,eAAe;YA2Bf,cAAc;YA+Dd,SAAS;YA+BT,YAAY;YA+BZ,cAAc;CAY7B"}
|
|
@@ -14,8 +14,7 @@ class StreamableHttpTransport {
|
|
|
14
14
|
this.endpoint = ensureLeadingSlash(options.endpoint ?? 'mcp');
|
|
15
15
|
this.endpointExplicit = options.endpoint !== undefined;
|
|
16
16
|
this.statefulMode = options.statefulMode ?? false;
|
|
17
|
-
this.enableJsonResponse =
|
|
18
|
-
options.enableJsonResponse ?? !this.statefulMode;
|
|
17
|
+
this.enableJsonResponse = options.enableJsonResponse ?? !this.statefulMode;
|
|
19
18
|
this.sessionIdGenerator =
|
|
20
19
|
options.sessionIdGenerator ?? (() => (0, crypto_1.randomUUID)());
|
|
21
20
|
this.mountOption = options.mount;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamable-http.transport.js","sourceRoot":"","sources":["../../../../src/mcp/transport/transports/streamable-http.transport.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from 'crypto';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport { HttpAdapterFactory } from '../../adapters/http-adapter.factory';\nimport { HttpResponse } from '../../interfaces/http-adapter.interface';\nimport { McpTransport, McpTransportContext } from '../mcp-transport.interface';\nimport type { McpHttpHandler } from '../mcp-http-handler';\nimport { readJsonBody } from './read-body';\n\nexport interface StreamableHttpTransportOptions {\n /**\n * Path of the transport's **self-mounted** route.\n *\n * This ONLY applies when the transport self-mounts (no controller owns the\n * route). A self-mounted route is registered directly on the HTTP adapter,\n * OUTSIDE Nest's routing pipeline — so it does not pick up\n * `app.setGlobalPrefix(...)`, URI versioning, guards, or interceptors. Use\n * `endpoint` for a trivial path change on a no-auth server.\n *\n * When you bring your own controller, the path comes from your\n * `@Controller(...)` decorator (and global prefix / versioning apply normally)\n * — `endpoint` is ignored. The transport logs a warning if you set it anyway.\n *\n * @default '/mcp'\n */\n endpoint?: string;\n /**\n * Enable session management (a long-lived MCP server per session, identified\n * by the `mcp-session-id` header, with `GET`/`DELETE` support for SSE streams\n * and session teardown).\n *\n * Left off (the default), the transport is **stateless**: every request is\n * self-contained, a fresh server is created and torn down per request, and\n * `GET`/`DELETE` return `405`. Stateless is the simplest mode and the right\n * default for most servers; turn this on only when you need server-initiated\n * streaming/notifications tied to a session.\n *\n * @default false (stateless)\n */\n statefulMode?: boolean;\n /**\n * Return a single JSON response instead of opening an SSE stream.\n *\n * When unset, this **follows the session mode**: `true` in stateless mode (so\n * a plain POST gets a JSON reply with no stream to manage) and `false` in\n * stateful mode (SSE, so server-initiated messages can flow). Set it\n * explicitly to override that pairing.\n *\n * @default `!statefulMode` (JSON when stateless, SSE when stateful)\n */\n enableJsonResponse?: boolean;\n /** Custom session id generator (stateful mode). */\n sessionIdGenerator?: () => string;\n /**\n * Whether the transport mounts its own `POST`/`GET`/`DELETE` routes on the\n * Nest HTTP adapter.\n *\n * Leave it unset (the default) for **auto-detection**: the transport\n * self-mounts UNLESS something has read {@link httpHandlers} — which happens\n * exactly when you wire a bring-your-own `@Controller` (e.g. via\n * `{ provide: MCP_HTTP_HANDLER, useValue: transport.httpHandlers }`). So\n * providing a controller automatically suppresses self-mounting; doing\n * nothing keeps the zero-config self-mount. This read happens at module\n * definition time, before the transport starts, so the timing is reliable.\n *\n * Set it explicitly to override the heuristic:\n * - `true`: always self-mount (bypasses the Nest pipeline — no guards).\n * - `false`: never self-mount (you own the route via a controller).\n *\n * @default undefined (auto: self-mount unless `httpHandlers` was accessed)\n */\n mount?: boolean;\n}\n\n/**\n * Streamable-HTTP transport. Mounts `POST`/`GET`/`DELETE` on the Nest HTTP server\n * and delegates to the MCP SDK `StreamableHTTPServerTransport`. Supports both the\n * stateless (one server per request) and stateful (session-managed) modes.\n */\nexport class StreamableHttpTransport implements McpTransport {\n readonly kind = 'streamable-http' as const;\n\n private readonly endpoint: string;\n private readonly statefulMode: boolean;\n private readonly enableJsonResponse: boolean;\n private readonly sessionIdGenerator: () => string;\n /** Whether `endpoint` was set explicitly (vs defaulted) — for the ignored-option warning. */\n private readonly endpointExplicit: boolean;\n /** Explicit `mount` override; `undefined` means auto-detect. */\n private readonly mountOption?: boolean;\n /** Set the first time {@link httpHandlers} is read — implies a BYO controller. */\n private handlersClaimed = false;\n\n private readonly transports: Record<string, StreamableHTTPServerTransport> =\n {};\n private readonly servers: Record<string, McpServer> = {};\n private ctx?: McpTransportContext;\n\n constructor(options: StreamableHttpTransportOptions = {}) {\n this.endpoint = ensureLeadingSlash(options.endpoint ?? 'mcp');\n this.endpointExplicit = options.endpoint !== undefined;\n this.statefulMode = options.statefulMode ?? false;\n // Default follows the session mode: JSON in stateless, SSE in stateful.\n this.enableJsonResponse =\n options.enableJsonResponse ?? !this.statefulMode;\n this.sessionIdGenerator =\n options.sessionIdGenerator ?? (() => randomUUID());\n this.mountOption = options.mount;\n }\n\n /**\n * The HTTP verb handlers, for bring-your-own-controller setups. Provide this\n * under `MCP_HTTP_HANDLER` and delegate to it from a `@Controller` (see\n * {@link StreamableHttpController}). The returned functions are bound to this\n * transport and read their context lazily, so it is safe to grab this getter\n * at module-construction time — before `start()` has run.\n *\n * Reading this getter also marks the route as claimed, so the transport\n * auto-disables its own self-mount (unless `mount` was set explicitly). That\n * is how `{ provide: MCP_HTTP_HANDLER, useValue: transport.httpHandlers }`\n * suppresses self-mounting without any extra flag.\n */\n get httpHandlers(): McpHttpHandler {\n this.handlersClaimed = true;\n return {\n handlePost: (req: unknown, res: unknown) => this.handlePost(req, res),\n handleGet: (req: unknown, res: unknown) => this.handleGet(req, res),\n handleDelete: (req: unknown, res: unknown) => this.handleDelete(req, res),\n };\n }\n\n start(ctx: McpTransportContext): void {\n // The context (server factory, request-handler binding, logger) is always\n // needed — the handlers read it lazily whether we self-mount or a user\n // controller calls them.\n this.ctx = ctx;\n\n // Auto-detect: self-mount unless a controller claimed the route by reading\n // `httpHandlers`. An explicit `mount` option overrides the heuristic.\n const shouldMount = this.mountOption ?? !this.handlersClaimed;\n\n if (!shouldMount) {\n // The path now comes from the user's @Controller(...), so a self-mount\n // `endpoint` would be silently ignored. Don't let that pass quietly.\n if (this.endpointExplicit) {\n ctx.logger.warn(\n `StreamableHttpTransport: \\`endpoint: '${this.endpoint}'\\` is ignored because a controller owns the route. Set the path on your @Controller(...) decorator instead (it also picks up global prefix and versioning).`,\n );\n }\n ctx.logger.log(\n `MCP streamable-http transport ready (${this.statefulMode ? 'stateful' : 'stateless'}, self-mount disabled — a controller owns the route)`,\n );\n return;\n }\n\n if (!ctx.httpAdapter) {\n throw new Error(\n 'StreamableHttpTransport requires an HTTP adapter to self-mount. Pass it via new McpStrategy({ httpAdapter }) or strategy.setHttpAdapter(app.getHttpAdapter()) — or mount your own controller (provide MCP_HTTP_HANDLER with transport.httpHandlers).',\n );\n }\n const adapter = ctx.httpAdapter as unknown as {\n post(path: string, handler: (req: any, res: any) => unknown): unknown;\n get(path: string, handler: (req: any, res: any) => unknown): unknown;\n delete(path: string, handler: (req: any, res: any) => unknown): unknown;\n };\n\n adapter.post(this.endpoint, (req, res) => this.handlePost(req, res));\n adapter.get(this.endpoint, (req, res) => this.handleGet(req, res));\n adapter.delete(this.endpoint, (req, res) => this.handleDelete(req, res));\n\n ctx.logger.log(\n `MCP streamable-http transport mounted at ${this.endpoint} (${this.statefulMode ? 'stateful' : 'stateless'})`,\n );\n }\n\n async close(): Promise<void> {\n for (const sessionId of Object.keys(this.transports)) {\n await this.cleanupSession(sessionId);\n }\n }\n\n private async handlePost(req: any, res: any): Promise<void> {\n const adapter = HttpAdapterFactory.getAdapter(req, res);\n const adaptedReq = adapter.adaptRequest(req);\n const adaptedRes = adapter.adaptResponse(res);\n const body = await readJsonBody(adaptedReq);\n\n try {\n if (this.statefulMode) {\n await this.handleStateful(adaptedReq, adaptedRes, body);\n } else {\n await this.handleStateless(adaptedReq, adaptedRes, body);\n }\n } catch (error) {\n this.ctx!.logger.error('Error handling MCP request', error as Error);\n if (!adaptedRes.headersSent) {\n adaptedRes.status(500).json({\n jsonrpc: '2.0',\n error: { code: -32603, message: 'Internal server error' },\n id: null,\n });\n }\n }\n }\n\n private async handleStateless(\n req: ReturnType<\n ReturnType<typeof HttpAdapterFactory.getAdapter>['adaptRequest']\n >,\n res: HttpResponse,\n body: unknown,\n ): Promise<void> {\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: this.enableJsonResponse,\n });\n const server = this.ctx!.createServer();\n await server.connect(transport);\n this.ctx!.bindRequestHandlers(\n server,\n { transport: this.kind, stateless: true },\n req.raw,\n );\n\n res.raw.on('finish', () => {\n void transport.close();\n void server.close();\n });\n\n await transport.handleRequest(req.raw, res.raw, body);\n }\n\n private async handleStateful(\n req: ReturnType<\n ReturnType<typeof HttpAdapterFactory.getAdapter>['adaptRequest']\n >,\n res: HttpResponse,\n body: unknown,\n ): Promise<void> {\n const sessionId = req.headers['mcp-session-id'] as string | undefined;\n\n if (!sessionId && isInitializeRequest(body)) {\n const server = this.ctx!.createServer();\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: this.sessionIdGenerator,\n enableJsonResponse: this.enableJsonResponse,\n onsessioninitialized: (sid: string) => {\n this.transports[sid] = transport;\n this.servers[sid] = server;\n },\n onsessionclosed: (sid: string) => {\n void this.cleanupSession(sid);\n },\n });\n await server.connect(transport);\n this.ctx!.bindRequestHandlers(\n server,\n { transport: this.kind, stateless: false },\n req.raw,\n );\n await transport.handleRequest(req.raw, res.raw, body);\n return;\n }\n\n if (sessionId) {\n const transport = this.transports[sessionId];\n const server = this.servers[sessionId];\n if (!transport || !server) {\n res.status(404).json({\n jsonrpc: '2.0',\n error: { code: -32001, message: 'Session not found' },\n id: null,\n });\n return;\n }\n // Re-bind so the per-request auth context is current.\n this.ctx!.bindRequestHandlers(\n server,\n { transport: this.kind, stateless: false, sessionId },\n req.raw,\n );\n await transport.handleRequest(req.raw, res.raw, body);\n return;\n }\n\n res.status(400).json({\n jsonrpc: '2.0',\n error: {\n code: -32000,\n message: 'Bad Request: Mcp-Session-Id header is required',\n },\n id: null,\n });\n }\n\n private async handleGet(req: any, res: any): Promise<void> {\n const adapter = HttpAdapterFactory.getAdapter(req, res);\n const adaptedReq = adapter.adaptRequest(req);\n const adaptedRes = adapter.adaptResponse(res);\n\n if (!this.statefulMode) {\n adaptedRes.status(405).json({\n jsonrpc: '2.0',\n error: {\n code: -32000,\n message: 'Method not allowed in stateless mode',\n },\n id: null,\n });\n return;\n }\n const sessionId = adaptedReq.headers['mcp-session-id'] as\n | string\n | undefined;\n const transport = sessionId ? this.transports[sessionId] : undefined;\n if (!transport) {\n adaptedRes.status(404).json({\n jsonrpc: '2.0',\n error: { code: -32001, message: 'Session not found' },\n id: null,\n });\n return;\n }\n await transport.handleRequest(adaptedReq.raw, adaptedRes.raw);\n }\n\n private async handleDelete(req: any, res: any): Promise<void> {\n const adapter = HttpAdapterFactory.getAdapter(req, res);\n const adaptedReq = adapter.adaptRequest(req);\n const adaptedRes = adapter.adaptResponse(res);\n\n if (!this.statefulMode) {\n adaptedRes.status(405).json({\n jsonrpc: '2.0',\n error: {\n code: -32000,\n message: 'Method not allowed in stateless mode',\n },\n id: null,\n });\n return;\n }\n const sessionId = adaptedReq.headers['mcp-session-id'] as\n | string\n | undefined;\n const transport = sessionId ? this.transports[sessionId] : undefined;\n if (!transport) {\n adaptedRes.status(404).json({\n jsonrpc: '2.0',\n error: { code: -32001, message: 'Session not found' },\n id: null,\n });\n return;\n }\n await transport.handleRequest(adaptedReq.raw, adaptedRes.raw);\n }\n\n private async cleanupSession(sessionId: string): Promise<void> {\n const transport = this.transports[sessionId];\n const server = this.servers[sessionId];\n delete this.transports[sessionId];\n delete this.servers[sessionId];\n try {\n await transport?.close();\n await server?.close();\n } catch {\n // best-effort cleanup\n }\n }\n}\n\nfunction isInitializeRequest(body: unknown): boolean {\n const isInit = (msg: unknown): boolean =>\n typeof msg === 'object' &&\n msg !== null &&\n 'method' in msg &&\n (msg as { method?: unknown }).method === 'initialize';\n return Array.isArray(body) ? body.some(isInit) : isInit(body);\n}\n\nfunction ensureLeadingSlash(endpoint: string): string {\n const trimmed = endpoint.trim();\n return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"streamable-http.transport.js","sourceRoot":"","sources":["../../../../src/mcp/transport/transports/streamable-http.transport.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from 'crypto';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport { HttpAdapterFactory } from '../../adapters/http-adapter.factory';\nimport { HttpResponse } from '../../interfaces/http-adapter.interface';\nimport { McpTransport, McpTransportContext } from '../mcp-transport.interface';\nimport type { McpHttpHandler } from '../mcp-http-handler';\nimport { readJsonBody } from './read-body';\n\nexport interface StreamableHttpTransportOptions {\n /**\n * Path of the transport's **self-mounted** route.\n *\n * This ONLY applies when the transport self-mounts (no controller owns the\n * route). A self-mounted route is registered directly on the HTTP adapter,\n * OUTSIDE Nest's routing pipeline — so it does not pick up\n * `app.setGlobalPrefix(...)`, URI versioning, guards, or interceptors. Use\n * `endpoint` for a trivial path change on a no-auth server.\n *\n * When you bring your own controller, the path comes from your\n * `@Controller(...)` decorator (and global prefix / versioning apply normally)\n * — `endpoint` is ignored. The transport logs a warning if you set it anyway.\n *\n * @default '/mcp'\n */\n endpoint?: string;\n /**\n * Enable session management (a long-lived MCP server per session, identified\n * by the `mcp-session-id` header, with `GET`/`DELETE` support for SSE streams\n * and session teardown).\n *\n * Left off (the default), the transport is **stateless**: every request is\n * self-contained, a fresh server is created and torn down per request, and\n * `GET`/`DELETE` return `405`. Stateless is the simplest mode and the right\n * default for most servers; turn this on only when you need server-initiated\n * streaming/notifications tied to a session.\n *\n * @default false (stateless)\n */\n statefulMode?: boolean;\n /**\n * Return a single JSON response instead of opening an SSE stream.\n *\n * When unset, this **follows the session mode**: `true` in stateless mode (so\n * a plain POST gets a JSON reply with no stream to manage) and `false` in\n * stateful mode (SSE, so server-initiated messages can flow). Set it\n * explicitly to override that pairing.\n *\n * @default `!statefulMode` (JSON when stateless, SSE when stateful)\n */\n enableJsonResponse?: boolean;\n /** Custom session id generator (stateful mode). */\n sessionIdGenerator?: () => string;\n /**\n * Whether the transport mounts its own `POST`/`GET`/`DELETE` routes on the\n * Nest HTTP adapter.\n *\n * Leave it unset (the default) for **auto-detection**: the transport\n * self-mounts UNLESS something has read {@link httpHandlers} — which happens\n * exactly when you wire a bring-your-own `@Controller` (e.g. via\n * `{ provide: MCP_HTTP_HANDLER, useValue: transport.httpHandlers }`). So\n * providing a controller automatically suppresses self-mounting; doing\n * nothing keeps the zero-config self-mount. This read happens at module\n * definition time, before the transport starts, so the timing is reliable.\n *\n * Set it explicitly to override the heuristic:\n * - `true`: always self-mount (bypasses the Nest pipeline — no guards).\n * - `false`: never self-mount (you own the route via a controller).\n *\n * @default undefined (auto: self-mount unless `httpHandlers` was accessed)\n */\n mount?: boolean;\n}\n\n/**\n * Streamable-HTTP transport. Mounts `POST`/`GET`/`DELETE` on the Nest HTTP server\n * and delegates to the MCP SDK `StreamableHTTPServerTransport`. Supports both the\n * stateless (one server per request) and stateful (session-managed) modes.\n */\nexport class StreamableHttpTransport implements McpTransport {\n readonly kind = 'streamable-http' as const;\n\n private readonly endpoint: string;\n private readonly statefulMode: boolean;\n private readonly enableJsonResponse: boolean;\n private readonly sessionIdGenerator: () => string;\n /** Whether `endpoint` was set explicitly (vs defaulted) — for the ignored-option warning. */\n private readonly endpointExplicit: boolean;\n /** Explicit `mount` override; `undefined` means auto-detect. */\n private readonly mountOption?: boolean;\n /** Set the first time {@link httpHandlers} is read — implies a BYO controller. */\n private handlersClaimed = false;\n\n private readonly transports: Record<string, StreamableHTTPServerTransport> =\n {};\n private readonly servers: Record<string, McpServer> = {};\n private ctx?: McpTransportContext;\n\n constructor(options: StreamableHttpTransportOptions = {}) {\n this.endpoint = ensureLeadingSlash(options.endpoint ?? 'mcp');\n this.endpointExplicit = options.endpoint !== undefined;\n this.statefulMode = options.statefulMode ?? false;\n // Default follows the session mode: JSON in stateless, SSE in stateful.\n this.enableJsonResponse = options.enableJsonResponse ?? !this.statefulMode;\n this.sessionIdGenerator =\n options.sessionIdGenerator ?? (() => randomUUID());\n this.mountOption = options.mount;\n }\n\n /**\n * The HTTP verb handlers, for bring-your-own-controller setups. Provide this\n * under `MCP_HTTP_HANDLER` and delegate to it from a `@Controller` (see\n * {@link StreamableHttpController}). The returned functions are bound to this\n * transport and read their context lazily, so it is safe to grab this getter\n * at module-construction time — before `start()` has run.\n *\n * Reading this getter also marks the route as claimed, so the transport\n * auto-disables its own self-mount (unless `mount` was set explicitly). That\n * is how `{ provide: MCP_HTTP_HANDLER, useValue: transport.httpHandlers }`\n * suppresses self-mounting without any extra flag.\n */\n get httpHandlers(): McpHttpHandler {\n this.handlersClaimed = true;\n return {\n handlePost: (req: unknown, res: unknown) => this.handlePost(req, res),\n handleGet: (req: unknown, res: unknown) => this.handleGet(req, res),\n handleDelete: (req: unknown, res: unknown) => this.handleDelete(req, res),\n };\n }\n\n start(ctx: McpTransportContext): void {\n // The context (server factory, request-handler binding, logger) is always\n // needed — the handlers read it lazily whether we self-mount or a user\n // controller calls them.\n this.ctx = ctx;\n\n // Auto-detect: self-mount unless a controller claimed the route by reading\n // `httpHandlers`. An explicit `mount` option overrides the heuristic.\n const shouldMount = this.mountOption ?? !this.handlersClaimed;\n\n if (!shouldMount) {\n // The path now comes from the user's @Controller(...), so a self-mount\n // `endpoint` would be silently ignored. Don't let that pass quietly.\n if (this.endpointExplicit) {\n ctx.logger.warn(\n `StreamableHttpTransport: \\`endpoint: '${this.endpoint}'\\` is ignored because a controller owns the route. Set the path on your @Controller(...) decorator instead (it also picks up global prefix and versioning).`,\n );\n }\n ctx.logger.log(\n `MCP streamable-http transport ready (${this.statefulMode ? 'stateful' : 'stateless'}, self-mount disabled — a controller owns the route)`,\n );\n return;\n }\n\n if (!ctx.httpAdapter) {\n throw new Error(\n 'StreamableHttpTransport requires an HTTP adapter to self-mount. Pass it via new McpStrategy({ httpAdapter }) or strategy.setHttpAdapter(app.getHttpAdapter()) — or mount your own controller (provide MCP_HTTP_HANDLER with transport.httpHandlers).',\n );\n }\n const adapter = ctx.httpAdapter as unknown as {\n post(path: string, handler: (req: any, res: any) => unknown): unknown;\n get(path: string, handler: (req: any, res: any) => unknown): unknown;\n delete(path: string, handler: (req: any, res: any) => unknown): unknown;\n };\n\n adapter.post(this.endpoint, (req, res) => this.handlePost(req, res));\n adapter.get(this.endpoint, (req, res) => this.handleGet(req, res));\n adapter.delete(this.endpoint, (req, res) => this.handleDelete(req, res));\n\n ctx.logger.log(\n `MCP streamable-http transport mounted at ${this.endpoint} (${this.statefulMode ? 'stateful' : 'stateless'})`,\n );\n }\n\n async close(): Promise<void> {\n for (const sessionId of Object.keys(this.transports)) {\n await this.cleanupSession(sessionId);\n }\n }\n\n private async handlePost(req: any, res: any): Promise<void> {\n const adapter = HttpAdapterFactory.getAdapter(req, res);\n const adaptedReq = adapter.adaptRequest(req);\n const adaptedRes = adapter.adaptResponse(res);\n const body = await readJsonBody(adaptedReq);\n\n try {\n if (this.statefulMode) {\n await this.handleStateful(adaptedReq, adaptedRes, body);\n } else {\n await this.handleStateless(adaptedReq, adaptedRes, body);\n }\n } catch (error) {\n this.ctx!.logger.error('Error handling MCP request', error as Error);\n if (!adaptedRes.headersSent) {\n adaptedRes.status(500).json({\n jsonrpc: '2.0',\n error: { code: -32603, message: 'Internal server error' },\n id: null,\n });\n }\n }\n }\n\n private async handleStateless(\n req: ReturnType<\n ReturnType<typeof HttpAdapterFactory.getAdapter>['adaptRequest']\n >,\n res: HttpResponse,\n body: unknown,\n ): Promise<void> {\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: this.enableJsonResponse,\n });\n const server = this.ctx!.createServer();\n await server.connect(transport);\n this.ctx!.bindRequestHandlers(\n server,\n { transport: this.kind, stateless: true },\n req.raw,\n );\n\n res.raw.on('finish', () => {\n void transport.close();\n void server.close();\n });\n\n await transport.handleRequest(req.raw, res.raw, body);\n }\n\n private async handleStateful(\n req: ReturnType<\n ReturnType<typeof HttpAdapterFactory.getAdapter>['adaptRequest']\n >,\n res: HttpResponse,\n body: unknown,\n ): Promise<void> {\n const sessionId = req.headers['mcp-session-id'] as string | undefined;\n\n if (!sessionId && isInitializeRequest(body)) {\n const server = this.ctx!.createServer();\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: this.sessionIdGenerator,\n enableJsonResponse: this.enableJsonResponse,\n onsessioninitialized: (sid: string) => {\n this.transports[sid] = transport;\n this.servers[sid] = server;\n },\n onsessionclosed: (sid: string) => {\n void this.cleanupSession(sid);\n },\n });\n await server.connect(transport);\n this.ctx!.bindRequestHandlers(\n server,\n { transport: this.kind, stateless: false },\n req.raw,\n );\n await transport.handleRequest(req.raw, res.raw, body);\n return;\n }\n\n if (sessionId) {\n const transport = this.transports[sessionId];\n const server = this.servers[sessionId];\n if (!transport || !server) {\n res.status(404).json({\n jsonrpc: '2.0',\n error: { code: -32001, message: 'Session not found' },\n id: null,\n });\n return;\n }\n // Re-bind so the per-request auth context is current.\n this.ctx!.bindRequestHandlers(\n server,\n { transport: this.kind, stateless: false, sessionId },\n req.raw,\n );\n await transport.handleRequest(req.raw, res.raw, body);\n return;\n }\n\n res.status(400).json({\n jsonrpc: '2.0',\n error: {\n code: -32000,\n message: 'Bad Request: Mcp-Session-Id header is required',\n },\n id: null,\n });\n }\n\n private async handleGet(req: any, res: any): Promise<void> {\n const adapter = HttpAdapterFactory.getAdapter(req, res);\n const adaptedReq = adapter.adaptRequest(req);\n const adaptedRes = adapter.adaptResponse(res);\n\n if (!this.statefulMode) {\n adaptedRes.status(405).json({\n jsonrpc: '2.0',\n error: {\n code: -32000,\n message: 'Method not allowed in stateless mode',\n },\n id: null,\n });\n return;\n }\n const sessionId = adaptedReq.headers['mcp-session-id'] as\n | string\n | undefined;\n const transport = sessionId ? this.transports[sessionId] : undefined;\n if (!transport) {\n adaptedRes.status(404).json({\n jsonrpc: '2.0',\n error: { code: -32001, message: 'Session not found' },\n id: null,\n });\n return;\n }\n await transport.handleRequest(adaptedReq.raw, adaptedRes.raw);\n }\n\n private async handleDelete(req: any, res: any): Promise<void> {\n const adapter = HttpAdapterFactory.getAdapter(req, res);\n const adaptedReq = adapter.adaptRequest(req);\n const adaptedRes = adapter.adaptResponse(res);\n\n if (!this.statefulMode) {\n adaptedRes.status(405).json({\n jsonrpc: '2.0',\n error: {\n code: -32000,\n message: 'Method not allowed in stateless mode',\n },\n id: null,\n });\n return;\n }\n const sessionId = adaptedReq.headers['mcp-session-id'] as\n | string\n | undefined;\n const transport = sessionId ? this.transports[sessionId] : undefined;\n if (!transport) {\n adaptedRes.status(404).json({\n jsonrpc: '2.0',\n error: { code: -32001, message: 'Session not found' },\n id: null,\n });\n return;\n }\n await transport.handleRequest(adaptedReq.raw, adaptedRes.raw);\n }\n\n private async cleanupSession(sessionId: string): Promise<void> {\n const transport = this.transports[sessionId];\n const server = this.servers[sessionId];\n delete this.transports[sessionId];\n delete this.servers[sessionId];\n try {\n await transport?.close();\n await server?.close();\n } catch {\n // best-effort cleanup\n }\n }\n}\n\nfunction isInitializeRequest(body: unknown): boolean {\n const isInit = (msg: unknown): boolean =>\n typeof msg === 'object' &&\n msg !== null &&\n 'method' in msg &&\n (msg as { method?: unknown }).method === 'initialize';\n return Array.isArray(body) ? body.some(isInit) : isInit(body);\n}\n\nfunction ensureLeadingSlash(endpoint: string): string {\n const trimmed = endpoint.trim();\n return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -43,10 +43,7 @@ export interface McpControllerOptions extends ControllerOptions {
|
|
|
43
43
|
* Note: `Object.getOwnPropertyNames` only sees methods declared on this class,
|
|
44
44
|
* not ones inherited from a base class.
|
|
45
45
|
*/
|
|
46
|
-
function retagMcpMethodsTransport(
|
|
47
|
-
target: Function,
|
|
48
|
-
transportId: symbol,
|
|
49
|
-
): void {
|
|
46
|
+
function retagMcpMethodsTransport(target: Function, transportId: symbol): void {
|
|
50
47
|
const proto = (target as { prototype?: Record<string, unknown> }).prototype;
|
|
51
48
|
if (!proto) return;
|
|
52
49
|
for (const key of Object.getOwnPropertyNames(proto)) {
|
|
@@ -101,8 +101,7 @@ export class StreamableHttpTransport implements McpTransport {
|
|
|
101
101
|
this.endpointExplicit = options.endpoint !== undefined;
|
|
102
102
|
this.statefulMode = options.statefulMode ?? false;
|
|
103
103
|
// Default follows the session mode: JSON in stateless, SSE in stateful.
|
|
104
|
-
this.enableJsonResponse =
|
|
105
|
-
options.enableJsonResponse ?? !this.statefulMode;
|
|
104
|
+
this.enableJsonResponse = options.enableJsonResponse ?? !this.statefulMode;
|
|
106
105
|
this.sessionIdGenerator =
|
|
107
106
|
options.sessionIdGenerator ?? (() => randomUUID());
|
|
108
107
|
this.mountOption = options.mount;
|