@rekog/mcp-nest 1.9.10 → 2.0.0-alpha.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/README.md +76 -29
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/decorators/constants.d.ts +1 -0
- package/dist/mcp/decorators/constants.d.ts.map +1 -1
- package/dist/mcp/decorators/constants.js +2 -1
- package/dist/mcp/decorators/constants.js.map +1 -1
- package/dist/mcp/decorators/index.d.ts +3 -1
- package/dist/mcp/decorators/index.d.ts.map +1 -1
- package/dist/mcp/decorators/index.js +3 -1
- package/dist/mcp/decorators/index.js.map +1 -1
- package/dist/mcp/decorators/mcp-controller.decorator.d.ts +6 -0
- package/dist/mcp/decorators/mcp-controller.decorator.d.ts.map +1 -0
- package/dist/mcp/decorators/mcp-controller.decorator.js +43 -0
- package/dist/mcp/decorators/mcp-controller.decorator.js.map +1 -0
- package/dist/mcp/decorators/mcp-message-pattern.d.ts +3 -0
- package/dist/mcp/decorators/mcp-message-pattern.d.ts.map +1 -0
- package/dist/mcp/decorators/mcp-message-pattern.js +13 -0
- package/dist/mcp/decorators/mcp-message-pattern.js.map +1 -0
- package/dist/mcp/decorators/prompt.decorator.d.ts +1 -1
- package/dist/mcp/decorators/prompt.decorator.d.ts.map +1 -1
- package/dist/mcp/decorators/prompt.decorator.js +2 -1
- package/dist/mcp/decorators/prompt.decorator.js.map +1 -1
- package/dist/mcp/decorators/public.decorator.js.map +1 -1
- package/dist/mcp/decorators/raw-request.decorator.d.ts +2 -0
- package/dist/mcp/decorators/raw-request.decorator.d.ts.map +1 -0
- package/dist/mcp/decorators/raw-request.decorator.js +6 -0
- package/dist/mcp/decorators/raw-request.decorator.js.map +1 -0
- package/dist/mcp/decorators/resource-template.decorator.d.ts +1 -1
- package/dist/mcp/decorators/resource-template.decorator.d.ts.map +1 -1
- package/dist/mcp/decorators/resource-template.decorator.js +2 -1
- package/dist/mcp/decorators/resource-template.decorator.js.map +1 -1
- package/dist/mcp/decorators/resource.decorator.d.ts +1 -1
- package/dist/mcp/decorators/resource.decorator.d.ts.map +1 -1
- package/dist/mcp/decorators/resource.decorator.js +2 -1
- package/dist/mcp/decorators/resource.decorator.js.map +1 -1
- package/dist/mcp/decorators/tool.decorator.d.ts +1 -3
- package/dist/mcp/decorators/tool.decorator.d.ts.map +1 -1
- package/dist/mcp/decorators/tool.decorator.js +2 -1
- package/dist/mcp/decorators/tool.decorator.js.map +1 -1
- package/dist/mcp/filters/mcp-exception.filter.d.ts +6 -0
- package/dist/mcp/filters/mcp-exception.filter.d.ts.map +1 -0
- package/dist/mcp/filters/mcp-exception.filter.js +26 -0
- package/dist/mcp/filters/mcp-exception.filter.js.map +1 -0
- package/dist/mcp/index.d.ts +11 -7
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +11 -7
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/interfaces/authenticated-user.interface.d.ts +9 -0
- package/dist/mcp/interfaces/authenticated-user.interface.d.ts.map +1 -0
- package/dist/{authz/interfaces/request-with-user.js → mcp/interfaces/authenticated-user.interface.js} +1 -1
- package/dist/mcp/interfaces/authenticated-user.interface.js.map +1 -0
- package/dist/mcp/interfaces/dynamic-prompt.interface.d.ts +1 -1
- package/dist/mcp/interfaces/dynamic-prompt.interface.d.ts.map +1 -1
- package/dist/mcp/interfaces/dynamic-prompt.interface.js.map +1 -1
- package/dist/mcp/interfaces/dynamic-resource.interface.d.ts +1 -1
- package/dist/mcp/interfaces/dynamic-resource.interface.d.ts.map +1 -1
- package/dist/mcp/interfaces/dynamic-resource.interface.js.map +1 -1
- package/dist/mcp/interfaces/dynamic-tool.interface.d.ts +1 -1
- package/dist/mcp/interfaces/dynamic-tool.interface.d.ts.map +1 -1
- package/dist/mcp/interfaces/dynamic-tool.interface.js.map +1 -1
- package/dist/mcp/interfaces/index.d.ts +2 -2
- package/dist/mcp/interfaces/index.d.ts.map +1 -1
- package/dist/mcp/interfaces/index.js +0 -3
- package/dist/mcp/interfaces/index.js.map +1 -1
- package/dist/mcp/services/tool-authorization.service.d.ts +7 -5
- package/dist/mcp/services/tool-authorization.service.d.ts.map +1 -1
- package/dist/mcp/services/tool-authorization.service.js +9 -6
- package/dist/mcp/services/tool-authorization.service.js.map +1 -1
- package/dist/mcp/transport/mcp-context.d.ts +34 -0
- package/dist/mcp/transport/mcp-context.d.ts.map +1 -0
- package/dist/mcp/transport/mcp-context.js +74 -0
- package/dist/mcp/transport/mcp-context.js.map +1 -0
- package/dist/mcp/transport/mcp-http-handler.d.ts +7 -0
- package/dist/mcp/transport/mcp-http-handler.d.ts.map +1 -0
- package/dist/mcp/transport/mcp-http-handler.js +5 -0
- package/dist/mcp/transport/mcp-http-handler.js.map +1 -0
- package/dist/mcp/transport/mcp-server-options.interface.d.ts +23 -0
- package/dist/mcp/transport/mcp-server-options.interface.d.ts.map +1 -0
- package/dist/{authz/providers/oauth-provider.interface.js → mcp/transport/mcp-server-options.interface.js} +1 -1
- package/dist/mcp/transport/mcp-server-options.interface.js.map +1 -0
- package/dist/mcp/transport/mcp-transport.constants.d.ts +19 -0
- package/dist/mcp/transport/mcp-transport.constants.d.ts.map +1 -0
- package/dist/mcp/transport/mcp-transport.constants.js +21 -0
- package/dist/mcp/transport/mcp-transport.constants.js.map +1 -0
- package/dist/mcp/transport/mcp-transport.interface.d.ts +17 -0
- package/dist/mcp/transport/mcp-transport.interface.d.ts.map +1 -0
- package/dist/{authz/interfaces/oauth-common.interface.js → mcp/transport/mcp-transport.interface.js} +1 -1
- package/dist/mcp/transport/mcp-transport.interface.js.map +1 -0
- package/dist/mcp/transport/mcp.strategy.d.ts +56 -0
- package/dist/mcp/transport/mcp.strategy.d.ts.map +1 -0
- package/dist/mcp/transport/mcp.strategy.js +446 -0
- package/dist/mcp/transport/mcp.strategy.js.map +1 -0
- package/dist/mcp/transport/resource-matcher.d.ts +13 -0
- package/dist/mcp/transport/resource-matcher.d.ts.map +1 -0
- package/dist/mcp/transport/resource-matcher.js +83 -0
- package/dist/mcp/transport/resource-matcher.js.map +1 -0
- package/dist/mcp/transport/streamable-http.controller.d.ts +16 -0
- package/dist/mcp/transport/streamable-http.controller.d.ts.map +1 -0
- package/dist/mcp/transport/streamable-http.controller.js +98 -0
- package/dist/mcp/transport/streamable-http.controller.js.map +1 -0
- package/dist/mcp/transport/transports/index.d.ts +3 -0
- package/dist/mcp/transport/transports/index.d.ts.map +1 -0
- package/dist/{authz → mcp/transport/transports}/index.js +2 -10
- package/dist/mcp/transport/transports/index.js.map +1 -0
- package/dist/mcp/transport/transports/read-body.d.ts +3 -0
- package/dist/mcp/transport/transports/read-body.d.ts.map +1 -0
- package/dist/mcp/transport/transports/read-body.js +32 -0
- package/dist/mcp/transport/transports/read-body.js.map +1 -0
- package/dist/mcp/transport/transports/stdio.transport.d.ts +9 -0
- package/dist/mcp/transport/transports/stdio.transport.d.ts.map +1 -0
- package/dist/mcp/transport/transports/stdio.transport.js +27 -0
- package/dist/mcp/transport/transports/stdio.transport.js.map +1 -0
- package/dist/mcp/transport/transports/streamable-http.transport.d.ts +33 -0
- package/dist/mcp/transport/transports/streamable-http.transport.d.ts.map +1 -0
- package/dist/mcp/transport/transports/streamable-http.transport.js +216 -0
- package/dist/mcp/transport/transports/streamable-http.transport.js.map +1 -0
- package/dist/mcp/utils/mcp-logger.factory.d.ts +6 -2
- package/dist/mcp/utils/mcp-logger.factory.d.ts.map +1 -1
- package/dist/mcp/utils/mcp-logger.factory.js.map +1 -1
- package/package.json +3 -66
- package/src/index.ts +0 -1
- package/src/mcp/decorators/constants.ts +1 -0
- package/src/mcp/decorators/index.ts +3 -1
- package/src/mcp/decorators/mcp-controller.decorator.ts +126 -0
- package/src/mcp/decorators/mcp-message-pattern.ts +38 -0
- package/src/mcp/decorators/prompt.decorator.ts +6 -2
- package/src/mcp/decorators/public.decorator.ts +3 -3
- package/src/mcp/decorators/raw-request.decorator.ts +32 -0
- package/src/mcp/decorators/resource-template.decorator.ts +6 -2
- package/src/mcp/decorators/resource.decorator.ts +6 -2
- package/src/mcp/decorators/tool.decorator.ts +6 -3
- package/src/mcp/filters/mcp-exception.filter.ts +47 -0
- package/src/mcp/index.ts +13 -7
- package/src/mcp/interfaces/authenticated-user.interface.ts +22 -0
- package/src/mcp/interfaces/dynamic-prompt.interface.ts +1 -1
- package/src/mcp/interfaces/dynamic-resource.interface.ts +1 -1
- package/src/mcp/interfaces/dynamic-tool.interface.ts +2 -2
- package/src/mcp/interfaces/index.ts +3 -7
- package/src/mcp/services/tool-authorization.service.ts +52 -72
- package/src/mcp/transport/mcp-context.ts +138 -0
- package/src/mcp/transport/mcp-http-handler.ts +32 -0
- package/src/mcp/transport/mcp-server-options.interface.ts +75 -0
- package/src/mcp/transport/mcp-transport.constants.ts +99 -0
- package/src/mcp/transport/mcp-transport.interface.ts +50 -0
- package/src/mcp/transport/mcp.strategy.ts +752 -0
- package/src/mcp/transport/resource-matcher.ts +107 -0
- package/src/mcp/transport/streamable-http.controller.ts +114 -0
- package/src/mcp/transport/transports/index.ts +2 -0
- package/src/mcp/transport/transports/read-body.ts +39 -0
- package/src/mcp/transport/transports/stdio.transport.ts +35 -0
- package/src/mcp/transport/transports/streamable-http.transport.ts +384 -0
- package/src/mcp/utils/mcp-logger.factory.spec.ts +8 -22
- package/src/mcp/utils/mcp-logger.factory.ts +13 -2
- package/dist/authz/guards/jwt-auth.guard.d.ts +0 -19
- package/dist/authz/guards/jwt-auth.guard.d.ts.map +0 -1
- package/dist/authz/guards/jwt-auth.guard.js +0 -108
- package/dist/authz/guards/jwt-auth.guard.js.map +0 -1
- package/dist/authz/index.d.ts +0 -11
- package/dist/authz/index.d.ts.map +0 -1
- package/dist/authz/index.js.map +0 -1
- package/dist/authz/interfaces/oauth-common.interface.d.ts +0 -21
- package/dist/authz/interfaces/oauth-common.interface.d.ts.map +0 -1
- package/dist/authz/interfaces/oauth-common.interface.js.map +0 -1
- package/dist/authz/interfaces/request-with-user.d.ts +0 -13
- package/dist/authz/interfaces/request-with-user.d.ts.map +0 -1
- package/dist/authz/interfaces/request-with-user.js.map +0 -1
- package/dist/authz/mcp-oauth.controller.d.ts +0 -81
- package/dist/authz/mcp-oauth.controller.d.ts.map +0 -1
- package/dist/authz/mcp-oauth.controller.js +0 -540
- package/dist/authz/mcp-oauth.controller.js.map +0 -1
- package/dist/authz/mcp-oauth.module.d.ts +0 -12
- package/dist/authz/mcp-oauth.module.d.ts.map +0 -1
- package/dist/authz/mcp-oauth.module.js +0 -271
- package/dist/authz/mcp-oauth.module.js.map +0 -1
- package/dist/authz/providers/azure-ad.provider.d.ts +0 -3
- package/dist/authz/providers/azure-ad.provider.d.ts.map +0 -1
- package/dist/authz/providers/azure-ad.provider.js +0 -37
- package/dist/authz/providers/azure-ad.provider.js.map +0 -1
- package/dist/authz/providers/github.provider.d.ts +0 -3
- package/dist/authz/providers/github.provider.d.ts.map +0 -1
- package/dist/authz/providers/github.provider.js +0 -24
- package/dist/authz/providers/github.provider.js.map +0 -1
- package/dist/authz/providers/google.provider.d.ts +0 -3
- package/dist/authz/providers/google.provider.d.ts.map +0 -1
- package/dist/authz/providers/google.provider.js +0 -25
- package/dist/authz/providers/google.provider.js.map +0 -1
- package/dist/authz/providers/oauth-provider.interface.d.ts +0 -107
- package/dist/authz/providers/oauth-provider.interface.d.ts.map +0 -1
- package/dist/authz/providers/oauth-provider.interface.js.map +0 -1
- package/dist/authz/services/client.service.d.ts +0 -12
- package/dist/authz/services/client.service.d.ts.map +0 -1
- package/dist/authz/services/client.service.js +0 -81
- package/dist/authz/services/client.service.js.map +0 -1
- package/dist/authz/services/jwt-token.service.d.ts +0 -37
- package/dist/authz/services/jwt-token.service.d.ts.map +0 -1
- package/dist/authz/services/jwt-token.service.js +0 -182
- package/dist/authz/services/jwt-token.service.js.map +0 -1
- package/dist/authz/services/oauth-strategy.service.d.ts +0 -13
- package/dist/authz/services/oauth-strategy.service.d.ts.map +0 -1
- package/dist/authz/services/oauth-strategy.service.js +0 -71
- package/dist/authz/services/oauth-strategy.service.js.map +0 -1
- package/dist/authz/stores/memory-store.service.d.ts +0 -27
- package/dist/authz/stores/memory-store.service.d.ts.map +0 -1
- package/dist/authz/stores/memory-store.service.js +0 -107
- package/dist/authz/stores/memory-store.service.js.map +0 -1
- package/dist/authz/stores/oauth-store.interface.d.ts +0 -61
- package/dist/authz/stores/oauth-store.interface.d.ts.map +0 -1
- package/dist/authz/stores/oauth-store.interface.js +0 -5
- package/dist/authz/stores/oauth-store.interface.js.map +0 -1
- package/dist/authz/stores/typeorm/constants.d.ts +0 -3
- package/dist/authz/stores/typeorm/constants.d.ts.map +0 -1
- package/dist/authz/stores/typeorm/constants.js +0 -6
- package/dist/authz/stores/typeorm/constants.js.map +0 -1
- package/dist/authz/stores/typeorm/entities/authorization-code.entity.d.ts +0 -15
- package/dist/authz/stores/typeorm/entities/authorization-code.entity.d.ts.map +0 -1
- package/dist/authz/stores/typeorm/entities/authorization-code.entity.js +0 -69
- package/dist/authz/stores/typeorm/entities/authorization-code.entity.js.map +0 -1
- package/dist/authz/stores/typeorm/entities/index.d.ts +0 -5
- package/dist/authz/stores/typeorm/entities/index.d.ts.map +0 -1
- package/dist/authz/stores/typeorm/entities/index.js +0 -12
- package/dist/authz/stores/typeorm/entities/index.js.map +0 -1
- package/dist/authz/stores/typeorm/entities/oauth-client.entity.d.ts +0 -17
- package/dist/authz/stores/typeorm/entities/oauth-client.entity.d.ts.map +0 -1
- package/dist/authz/stores/typeorm/entities/oauth-client.entity.js +0 -77
- package/dist/authz/stores/typeorm/entities/oauth-client.entity.js.map +0 -1
- package/dist/authz/stores/typeorm/entities/oauth-session.entity.d.ts +0 -14
- package/dist/authz/stores/typeorm/entities/oauth-session.entity.d.ts.map +0 -1
- package/dist/authz/stores/typeorm/entities/oauth-session.entity.js +0 -65
- package/dist/authz/stores/typeorm/entities/oauth-session.entity.js.map +0 -1
- package/dist/authz/stores/typeorm/entities/user-profile.entity.d.ts +0 -13
- package/dist/authz/stores/typeorm/entities/user-profile.entity.d.ts.map +0 -1
- package/dist/authz/stores/typeorm/entities/user-profile.entity.js +0 -62
- package/dist/authz/stores/typeorm/entities/user-profile.entity.js.map +0 -1
- package/dist/authz/stores/typeorm/typeorm-store.service.d.ts +0 -28
- package/dist/authz/stores/typeorm/typeorm-store.service.d.ts.map +0 -1
- package/dist/authz/stores/typeorm/typeorm-store.service.js +0 -138
- package/dist/authz/stores/typeorm/typeorm-store.service.js.map +0 -1
- package/dist/mcp/constants/feature-registration.constants.d.ts +0 -7
- package/dist/mcp/constants/feature-registration.constants.d.ts.map +0 -1
- package/dist/mcp/constants/feature-registration.constants.js +0 -5
- package/dist/mcp/constants/feature-registration.constants.js.map +0 -1
- package/dist/mcp/decorators/tool-guards.decorator.d.ts +0 -12
- package/dist/mcp/decorators/tool-guards.decorator.d.ts.map +0 -1
- package/dist/mcp/decorators/tool-guards.decorator.js +0 -13
- package/dist/mcp/decorators/tool-guards.decorator.js.map +0 -1
- package/dist/mcp/interfaces/mcp-options.interface.d.ts +0 -53
- package/dist/mcp/interfaces/mcp-options.interface.d.ts.map +0 -1
- package/dist/mcp/interfaces/mcp-options.interface.js +0 -10
- package/dist/mcp/interfaces/mcp-options.interface.js.map +0 -1
- package/dist/mcp/mcp.module.d.ts +0 -15
- package/dist/mcp/mcp.module.d.ts.map +0 -1
- package/dist/mcp/mcp.module.js +0 -248
- package/dist/mcp/mcp.module.js.map +0 -1
- package/dist/mcp/services/handlers/mcp-handler.base.d.ts +0 -117
- package/dist/mcp/services/handlers/mcp-handler.base.d.ts.map +0 -1
- package/dist/mcp/services/handlers/mcp-handler.base.js +0 -125
- package/dist/mcp/services/handlers/mcp-handler.base.js.map +0 -1
- package/dist/mcp/services/handlers/mcp-prompts.handler.d.ts +0 -12
- package/dist/mcp/services/handlers/mcp-prompts.handler.d.ts.map +0 -1
- package/dist/mcp/services/handlers/mcp-prompts.handler.js +0 -98
- package/dist/mcp/services/handlers/mcp-prompts.handler.js.map +0 -1
- package/dist/mcp/services/handlers/mcp-resources.handler.d.ts +0 -13
- package/dist/mcp/services/handlers/mcp-resources.handler.d.ts.map +0 -1
- package/dist/mcp/services/handlers/mcp-resources.handler.js +0 -117
- package/dist/mcp/services/handlers/mcp-resources.handler.js.map +0 -1
- package/dist/mcp/services/handlers/mcp-tools.handler.d.ts +0 -22
- package/dist/mcp/services/handlers/mcp-tools.handler.d.ts.map +0 -1
- package/dist/mcp/services/handlers/mcp-tools.handler.js +0 -258
- package/dist/mcp/services/handlers/mcp-tools.handler.js.map +0 -1
- package/dist/mcp/services/mcp-dynamic-registry.service.d.ts +0 -26
- package/dist/mcp/services/mcp-dynamic-registry.service.d.ts.map +0 -1
- package/dist/mcp/services/mcp-dynamic-registry.service.js +0 -133
- package/dist/mcp/services/mcp-dynamic-registry.service.js.map +0 -1
- package/dist/mcp/services/mcp-executor.service.d.ts +0 -15
- package/dist/mcp/services/mcp-executor.service.d.ts.map +0 -1
- package/dist/mcp/services/mcp-executor.service.js +0 -47
- package/dist/mcp/services/mcp-executor.service.js.map +0 -1
- package/dist/mcp/services/mcp-registry-discovery.service.d.ts +0 -63
- package/dist/mcp/services/mcp-registry-discovery.service.d.ts.map +0 -1
- package/dist/mcp/services/mcp-registry-discovery.service.js +0 -397
- package/dist/mcp/services/mcp-registry-discovery.service.js.map +0 -1
- package/dist/mcp/services/mcp-sse.service.d.ts +0 -20
- package/dist/mcp/services/mcp-sse.service.d.ts.map +0 -1
- package/dist/mcp/services/mcp-sse.service.js +0 -91
- package/dist/mcp/services/mcp-sse.service.js.map +0 -1
- package/dist/mcp/services/mcp-streamable-http.service.d.ts +0 -32
- package/dist/mcp/services/mcp-streamable-http.service.d.ts.map +0 -1
- package/dist/mcp/services/mcp-streamable-http.service.js +0 -327
- package/dist/mcp/services/mcp-streamable-http.service.js.map +0 -1
- package/dist/mcp/services/sse-ping.service.d.ts +0 -23
- package/dist/mcp/services/sse-ping.service.d.ts.map +0 -1
- package/dist/mcp/services/sse-ping.service.js +0 -99
- package/dist/mcp/services/sse-ping.service.js.map +0 -1
- package/dist/mcp/transport/sse.controller.factory.d.ts +0 -14
- package/dist/mcp/transport/sse.controller.factory.d.ts.map +0 -1
- package/dist/mcp/transport/sse.controller.factory.js +0 -67
- package/dist/mcp/transport/sse.controller.factory.js.map +0 -1
- package/dist/mcp/transport/stdio.service.d.ts +0 -14
- package/dist/mcp/transport/stdio.service.d.ts.map +0 -1
- package/dist/mcp/transport/stdio.service.js +0 -66
- package/dist/mcp/transport/stdio.service.js.map +0 -1
- package/dist/mcp/transport/streamable-http.controller.factory.d.ts +0 -14
- package/dist/mcp/transport/streamable-http.controller.factory.d.ts.map +0 -1
- package/dist/mcp/transport/streamable-http.controller.factory.js +0 -74
- package/dist/mcp/transport/streamable-http.controller.factory.js.map +0 -1
- package/dist/mcp/utils/capabilities-builder.d.ts +0 -5
- package/dist/mcp/utils/capabilities-builder.d.ts.map +0 -1
- package/dist/mcp/utils/capabilities-builder.js +0 -25
- package/dist/mcp/utils/capabilities-builder.js.map +0 -1
- package/dist/mcp/utils/mcp-server.factory.d.ts +0 -6
- package/dist/mcp/utils/mcp-server.factory.d.ts.map +0 -1
- package/dist/mcp/utils/mcp-server.factory.js +0 -22
- package/dist/mcp/utils/mcp-server.factory.js.map +0 -1
- package/src/authz/guards/jwt-auth.guard.ts +0 -127
- package/src/authz/index.ts +0 -10
- package/src/authz/interfaces/oauth-common.interface.ts +0 -21
- package/src/authz/interfaces/request-with-user.ts +0 -16
- package/src/authz/mcp-oauth.controller.ts +0 -860
- package/src/authz/mcp-oauth.module.ts +0 -384
- package/src/authz/providers/azure-ad.provider.ts +0 -40
- package/src/authz/providers/github.provider.ts +0 -22
- package/src/authz/providers/google.provider.ts +0 -23
- package/src/authz/providers/oauth-provider.interface.ts +0 -147
- package/src/authz/services/client.service.ts +0 -125
- package/src/authz/services/jwt-token.service.spec.ts +0 -67
- package/src/authz/services/jwt-token.service.ts +0 -188
- package/src/authz/services/oauth-strategy.service.ts +0 -64
- package/src/authz/stores/memory-store.service.spec.ts +0 -475
- package/src/authz/stores/memory-store.service.ts +0 -141
- package/src/authz/stores/oauth-store.interface.ts +0 -131
- package/src/authz/stores/typeorm/README.md +0 -140
- package/src/authz/stores/typeorm/constants.ts +0 -6
- package/src/authz/stores/typeorm/entities/authorization-code.entity.ts +0 -41
- package/src/authz/stores/typeorm/entities/index.ts +0 -4
- package/src/authz/stores/typeorm/entities/oauth-client.entity.ts +0 -53
- package/src/authz/stores/typeorm/entities/oauth-session.entity.ts +0 -38
- package/src/authz/stores/typeorm/entities/user-profile.entity.ts +0 -46
- package/src/authz/stores/typeorm/typeorm-store.service.spec.ts +0 -494
- package/src/authz/stores/typeorm/typeorm-store.service.ts +0 -165
- package/src/mcp/constants/feature-registration.constants.ts +0 -24
- package/src/mcp/decorators/tool-guards.decorator.ts +0 -82
- package/src/mcp/interfaces/mcp-options.interface.ts +0 -95
- package/src/mcp/mcp.module.ts +0 -349
- package/src/mcp/services/handlers/mcp-handler.base.ts +0 -203
- package/src/mcp/services/handlers/mcp-prompts.handler.ts +0 -143
- package/src/mcp/services/handlers/mcp-resources.handler.ts +0 -183
- package/src/mcp/services/handlers/mcp-tools.handler.spec.ts +0 -41
- package/src/mcp/services/handlers/mcp-tools.handler.ts +0 -436
- package/src/mcp/services/mcp-dynamic-registry.service.ts +0 -236
- package/src/mcp/services/mcp-executor.service.ts +0 -64
- package/src/mcp/services/mcp-registry-discovery.service.spec.ts +0 -306
- package/src/mcp/services/mcp-registry-discovery.service.ts +0 -849
- package/src/mcp/services/mcp-sse.service.ts +0 -124
- package/src/mcp/services/mcp-streamable-http.service.ts +0 -472
- package/src/mcp/services/sse-ping.service.ts +0 -144
- package/src/mcp/transport/custom-decorator.spec.ts +0 -75
- package/src/mcp/transport/sse.controller.factory.ts +0 -84
- package/src/mcp/transport/stdio.service.ts +0 -75
- package/src/mcp/transport/streamable-http.controller.factory.ts +0 -80
- package/src/mcp/utils/capabilities-builder.ts +0 -43
- package/src/mcp/utils/mcp-server.factory.ts +0 -33
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { match } from 'path-to-regexp';
|
|
2
|
+
|
|
3
|
+
/** Strips the scheme (`mcp://foo` -> `foo`). */
|
|
4
|
+
function stripScheme(uri: string): string {
|
|
5
|
+
return uri.includes('://') ? uri.split('://')[1] : uri;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* RFC 6570 template -> path-to-regexp (v8) path:
|
|
10
|
+
* - drop the `{?query}` form,
|
|
11
|
+
* - turn a catch-all `{path*}` into a wildcard `*path` (matches one or more
|
|
12
|
+
* segments across `/`),
|
|
13
|
+
* - turn a single `{p}` into `:p` (one segment).
|
|
14
|
+
*
|
|
15
|
+
* The catch-all rule MUST run before the single-param rule: `{(\w+)}` can't
|
|
16
|
+
* match `{path*}` (the `*` sits between `\w+` and `}`), which is the bug that
|
|
17
|
+
* previously left `{path*}` untouched so it never matched.
|
|
18
|
+
*/
|
|
19
|
+
function convertTemplate(template: string): string {
|
|
20
|
+
if (!template) return template;
|
|
21
|
+
return template
|
|
22
|
+
.replace(/\{\?[^}]+\}/g, '')
|
|
23
|
+
.replace(/\{(\w+)\*\}/g, '*$1')
|
|
24
|
+
.replace(/\{(\w+)\}/g, ':$1');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* path-to-regexp v8 returns wildcard (`*path`) params as an array of segments,
|
|
29
|
+
* while named (`:id`) params come back as strings. Join wildcard arrays with
|
|
30
|
+
* `/` so a `{path*}` template hands the handler a single string
|
|
31
|
+
* (e.g. `docs/readme.md`), preserving the documented contract.
|
|
32
|
+
*/
|
|
33
|
+
function flattenParams(
|
|
34
|
+
params: Partial<Record<string, string | string[]>>,
|
|
35
|
+
): Record<string, string> {
|
|
36
|
+
const out: Record<string, string> = {};
|
|
37
|
+
for (const [key, value] of Object.entries(params)) {
|
|
38
|
+
if (value === undefined) continue;
|
|
39
|
+
out[key] = Array.isArray(value) ? value.join('/') : value;
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function extractTemplateQueryParams(template: string): string[] {
|
|
45
|
+
const m = template.match(/\{\?([^}]+)\}/);
|
|
46
|
+
return m ? m[1].split(',').map((p) => p.trim()) : [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseQueryString(uri: string): Record<string, string> {
|
|
50
|
+
const i = uri.indexOf('?');
|
|
51
|
+
if (i === -1) return {};
|
|
52
|
+
const params: Record<string, string> = {};
|
|
53
|
+
for (const pair of uri.substring(i + 1).split('&')) {
|
|
54
|
+
const [k, v] = pair.split('=');
|
|
55
|
+
if (k) params[decodeURIComponent(k)] = v ? decodeURIComponent(v) : '';
|
|
56
|
+
}
|
|
57
|
+
return params;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function stripQueryString(uri: string): string {
|
|
61
|
+
const i = uri.indexOf('?');
|
|
62
|
+
return i === -1 ? uri : uri.substring(0, i);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Finds a static resource whose URI matches `inputUri`, extracting any path params. */
|
|
66
|
+
export function matchResourceByUri<T extends { uri?: string }>(
|
|
67
|
+
resources: T[],
|
|
68
|
+
inputUri: string,
|
|
69
|
+
): { resource: T; params: Record<string, string> } | undefined {
|
|
70
|
+
const input = stripScheme(inputUri);
|
|
71
|
+
for (const resource of resources) {
|
|
72
|
+
if (!resource.uri) continue;
|
|
73
|
+
const matcher = match(convertTemplate(stripScheme(resource.uri)), {
|
|
74
|
+
decode: decodeURIComponent,
|
|
75
|
+
});
|
|
76
|
+
const result = matcher(input);
|
|
77
|
+
if (result) {
|
|
78
|
+
return { resource, params: flattenParams(result.params) };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Finds a resource template matching `inputUri`, extracting path + declared query params. */
|
|
85
|
+
export function matchResourceTemplateByUri<T extends { uriTemplate?: string }>(
|
|
86
|
+
templates: T[],
|
|
87
|
+
inputUri: string,
|
|
88
|
+
): { template: T; params: Record<string, string> } | undefined {
|
|
89
|
+
const input = stripQueryString(stripScheme(inputUri));
|
|
90
|
+
const inputQuery = parseQueryString(inputUri);
|
|
91
|
+
for (const template of templates) {
|
|
92
|
+
if (!template.uriTemplate) continue;
|
|
93
|
+
const matcher = match(convertTemplate(stripScheme(template.uriTemplate)), {
|
|
94
|
+
decode: decodeURIComponent,
|
|
95
|
+
});
|
|
96
|
+
const result = matcher(input);
|
|
97
|
+
if (result) {
|
|
98
|
+
const params = flattenParams(result.params);
|
|
99
|
+
const query: Record<string, string> = {};
|
|
100
|
+
for (const name of extractTemplateQueryParams(template.uriTemplate)) {
|
|
101
|
+
if (inputQuery[name] !== undefined) query[name] = inputQuery[name];
|
|
102
|
+
}
|
|
103
|
+
return { template, params: { ...params, ...query } };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Delete, Get, Inject, Post, Req, Res } from '@nestjs/common';
|
|
2
|
+
import { MCP_HTTP_HANDLER, type McpHttpHandler } from './mcp-http-handler';
|
|
3
|
+
|
|
4
|
+
/** Anything that can hand out MCP HTTP verb handlers — i.e. a transport. */
|
|
5
|
+
export interface McpHttpHandlerSource {
|
|
6
|
+
readonly httpHandlers: McpHttpHandler;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Build a base controller bound DIRECTLY to a specific transport — the
|
|
11
|
+
* legible way to wire a bring-your-own MCP controller.
|
|
12
|
+
*
|
|
13
|
+
* Instead of providing handlers under a DI token and hoping the right one
|
|
14
|
+
* resolves (which gets confusing with multiple servers), the controller simply
|
|
15
|
+
* names the transport it serves. The binding is a concrete reference you can
|
|
16
|
+
* click through to:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* @Controller('weather/mcp')
|
|
20
|
+
* @UseGuards(WeatherAuthGuard)
|
|
21
|
+
* export class WeatherMcpController extends McpHttpControllerFor(weatherTransport) {}
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* No `MCP_HTTP_HANDLER` provider, no module-local resolution to reason about: a
|
|
25
|
+
* reader sees `weatherTransport`, jumps to where it's declared, and finds it in
|
|
26
|
+
* `weatherStrategy({ server: 'weather' })` next to the weather tools. One hop.
|
|
27
|
+
*
|
|
28
|
+
* Reading `source.httpHandlers` here (at class-definition / import time) also
|
|
29
|
+
* marks the route as claimed, so the transport auto-disables its own self-mount.
|
|
30
|
+
*
|
|
31
|
+
* You still own the subclass decorators — `@Controller(path)`, `@UseGuards`,
|
|
32
|
+
* `@UseInterceptors`, `@Version`, etc. all compose normally; the library only
|
|
33
|
+
* owns the `POST`/`GET`/`DELETE` wiring.
|
|
34
|
+
*/
|
|
35
|
+
export function McpHttpControllerFor(source: McpHttpHandlerSource) {
|
|
36
|
+
const handlers = source.httpHandlers;
|
|
37
|
+
|
|
38
|
+
abstract class BoundMcpHttpController {
|
|
39
|
+
@Post()
|
|
40
|
+
post(@Req() req: unknown, @Res() res: unknown): Promise<void> | void {
|
|
41
|
+
return handlers.handlePost(req, res);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@Get()
|
|
45
|
+
get(@Req() req: unknown, @Res() res: unknown): Promise<void> | void {
|
|
46
|
+
return handlers.handleGet(req, res);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@Delete()
|
|
50
|
+
delete(@Req() req: unknown, @Res() res: unknown): Promise<void> | void {
|
|
51
|
+
return handlers.handleDelete(req, res);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return BoundMcpHttpController;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Abstract base controller for the streamable-HTTP MCP endpoint.
|
|
60
|
+
*
|
|
61
|
+
* Extend it to mount the MCP route as a real NestJS controller — so guards,
|
|
62
|
+
* interceptors, exception filters and versioning apply the normal Nest way,
|
|
63
|
+
* while the library keeps ownership of the `POST`/`GET`/`DELETE` wiring
|
|
64
|
+
* (session handling, SSE streaming, raw-body reading). You never reimplement the
|
|
65
|
+
* verbs; you just decorate your subclass:
|
|
66
|
+
*
|
|
67
|
+
* ```ts
|
|
68
|
+
* @Controller('mcp')
|
|
69
|
+
* @UseGuards(MyAuthGuard) // ← authenticate the whole MCP surface, once
|
|
70
|
+
* export class McpController extends StreamableHttpController {}
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* Wiring is a single line — provide the transport's handlers under
|
|
74
|
+
* {@link MCP_HTTP_HANDLER} so this base can inject them:
|
|
75
|
+
*
|
|
76
|
+
* ```ts
|
|
77
|
+
* { provide: MCP_HTTP_HANDLER, useValue: mcpTransport.httpHandlers }
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* Reading `httpHandlers` there also auto-disables the transport's own
|
|
81
|
+
* self-mount, so you do NOT need `{ mount: false }` — your controller owns the
|
|
82
|
+
* route and there is no double-registration. (Pass `mount` explicitly only to
|
|
83
|
+
* override that.) The path lives on YOUR `@Controller(...)` decorator, not on
|
|
84
|
+
* the transport.
|
|
85
|
+
*
|
|
86
|
+
* Implementation notes:
|
|
87
|
+
* - `@Controller()` must be on the concrete subclass; Nest registers the route
|
|
88
|
+
* handlers it finds on the prototype chain, including these inherited ones.
|
|
89
|
+
* - The handler is injected via a property (not the constructor) so subclasses
|
|
90
|
+
* need no constructor and no `super(...)` call — and so it survives NestJS's
|
|
91
|
+
* inherited-constructor-metadata quirks.
|
|
92
|
+
* - `@Res()` puts Nest in manual-response mode: the SDK transport writes the
|
|
93
|
+
* response (and SSE stream) directly, so Nest must not serialize a return
|
|
94
|
+
* value.
|
|
95
|
+
*/
|
|
96
|
+
export abstract class StreamableHttpController {
|
|
97
|
+
@Inject(MCP_HTTP_HANDLER)
|
|
98
|
+
protected readonly mcpHandler!: McpHttpHandler;
|
|
99
|
+
|
|
100
|
+
@Post()
|
|
101
|
+
post(@Req() req: unknown, @Res() res: unknown): Promise<void> | void {
|
|
102
|
+
return this.mcpHandler.handlePost(req, res);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@Get()
|
|
106
|
+
get(@Req() req: unknown, @Res() res: unknown): Promise<void> | void {
|
|
107
|
+
return this.mcpHandler.handleGet(req, res);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@Delete()
|
|
111
|
+
delete(@Req() req: unknown, @Res() res: unknown): Promise<void> | void {
|
|
112
|
+
return this.mcpHandler.handleDelete(req, res);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { HttpRequest } from '../../interfaces/http-adapter.interface';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the JSON request body for an MCP HTTP request.
|
|
5
|
+
*
|
|
6
|
+
* Prefers an already-parsed body (`adaptedReq.body`) when a framework body-parser
|
|
7
|
+
* ran (e.g. Fastify parses before route handlers). Otherwise reads the raw Node
|
|
8
|
+
* stream — the case for Express, where MCP routes are mounted before NestJS adds
|
|
9
|
+
* its body-parser middleware, so `req.body` is undefined and the stream is intact.
|
|
10
|
+
* Reading once here also lets the transport inspect the payload (e.g. to detect an
|
|
11
|
+
* `initialize` request) before handing off to the SDK.
|
|
12
|
+
*/
|
|
13
|
+
export async function readJsonBody(adaptedReq: HttpRequest): Promise<unknown> {
|
|
14
|
+
if (adaptedReq.body !== undefined) {
|
|
15
|
+
return adaptedReq.body;
|
|
16
|
+
}
|
|
17
|
+
const rawReq = adaptedReq.raw;
|
|
18
|
+
if (rawReq?.body !== undefined) {
|
|
19
|
+
return rawReq.body;
|
|
20
|
+
}
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
let data = '';
|
|
23
|
+
rawReq.on('data', (chunk: Buffer | string) => {
|
|
24
|
+
data += chunk.toString();
|
|
25
|
+
});
|
|
26
|
+
rawReq.on('end', () => {
|
|
27
|
+
if (!data) {
|
|
28
|
+
resolve(undefined);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
resolve(JSON.parse(data));
|
|
33
|
+
} catch {
|
|
34
|
+
resolve(undefined);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
rawReq.on('error', () => resolve(undefined));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { McpTransport, McpTransportContext } from '../mcp-transport.interface';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* stdio transport. Starts a single long-lived MCP server bound to the process'
|
|
7
|
+
* stdin/stdout. Intended for CLI-style MCP servers launched by an MCP client.
|
|
8
|
+
*
|
|
9
|
+
* No HTTP adapter is required. As a persistent, session-aware connection, stdio
|
|
10
|
+
* supports progress notifications and server-side logging.
|
|
11
|
+
*/
|
|
12
|
+
export class StdioTransport implements McpTransport {
|
|
13
|
+
readonly kind = 'stdio' as const;
|
|
14
|
+
|
|
15
|
+
private server?: McpServer;
|
|
16
|
+
private transport?: StdioServerTransport;
|
|
17
|
+
|
|
18
|
+
async start(ctx: McpTransportContext): Promise<void> {
|
|
19
|
+
this.server = ctx.createServer();
|
|
20
|
+
ctx.bindRequestHandlers(this.server, {
|
|
21
|
+
transport: this.kind,
|
|
22
|
+
stateless: false,
|
|
23
|
+
});
|
|
24
|
+
this.transport = new StdioServerTransport();
|
|
25
|
+
await this.server.connect(this.transport);
|
|
26
|
+
ctx.logger.log('MCP stdio transport started');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async close(): Promise<void> {
|
|
30
|
+
await this.transport?.close();
|
|
31
|
+
await this.server?.close();
|
|
32
|
+
this.transport = undefined;
|
|
33
|
+
this.server = undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
4
|
+
import { HttpAdapterFactory } from '../../adapters/http-adapter.factory';
|
|
5
|
+
import { HttpResponse } from '../../interfaces/http-adapter.interface';
|
|
6
|
+
import { McpTransport, McpTransportContext } from '../mcp-transport.interface';
|
|
7
|
+
import type { McpHttpHandler } from '../mcp-http-handler';
|
|
8
|
+
import { readJsonBody } from './read-body';
|
|
9
|
+
|
|
10
|
+
export interface StreamableHttpTransportOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Path of the transport's **self-mounted** route.
|
|
13
|
+
*
|
|
14
|
+
* This ONLY applies when the transport self-mounts (no controller owns the
|
|
15
|
+
* route). A self-mounted route is registered directly on the HTTP adapter,
|
|
16
|
+
* OUTSIDE Nest's routing pipeline — so it does not pick up
|
|
17
|
+
* `app.setGlobalPrefix(...)`, URI versioning, guards, or interceptors. Use
|
|
18
|
+
* `endpoint` for a trivial path change on a no-auth server.
|
|
19
|
+
*
|
|
20
|
+
* When you bring your own controller, the path comes from your
|
|
21
|
+
* `@Controller(...)` decorator (and global prefix / versioning apply normally)
|
|
22
|
+
* — `endpoint` is ignored. The transport logs a warning if you set it anyway.
|
|
23
|
+
*
|
|
24
|
+
* @default '/mcp'
|
|
25
|
+
*/
|
|
26
|
+
endpoint?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Enable session management (a long-lived MCP server per session, identified
|
|
29
|
+
* by the `mcp-session-id` header, with `GET`/`DELETE` support for SSE streams
|
|
30
|
+
* and session teardown).
|
|
31
|
+
*
|
|
32
|
+
* Left off (the default), the transport is **stateless**: every request is
|
|
33
|
+
* self-contained, a fresh server is created and torn down per request, and
|
|
34
|
+
* `GET`/`DELETE` return `405`. Stateless is the simplest mode and the right
|
|
35
|
+
* default for most servers; turn this on only when you need server-initiated
|
|
36
|
+
* streaming/notifications tied to a session.
|
|
37
|
+
*
|
|
38
|
+
* @default false (stateless)
|
|
39
|
+
*/
|
|
40
|
+
statefulMode?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Return a single JSON response instead of opening an SSE stream.
|
|
43
|
+
*
|
|
44
|
+
* When unset, this **follows the session mode**: `true` in stateless mode (so
|
|
45
|
+
* a plain POST gets a JSON reply with no stream to manage) and `false` in
|
|
46
|
+
* stateful mode (SSE, so server-initiated messages can flow). Set it
|
|
47
|
+
* explicitly to override that pairing.
|
|
48
|
+
*
|
|
49
|
+
* @default `!statefulMode` (JSON when stateless, SSE when stateful)
|
|
50
|
+
*/
|
|
51
|
+
enableJsonResponse?: boolean;
|
|
52
|
+
/** Custom session id generator (stateful mode). */
|
|
53
|
+
sessionIdGenerator?: () => string;
|
|
54
|
+
/**
|
|
55
|
+
* Whether the transport mounts its own `POST`/`GET`/`DELETE` routes on the
|
|
56
|
+
* Nest HTTP adapter.
|
|
57
|
+
*
|
|
58
|
+
* Leave it unset (the default) for **auto-detection**: the transport
|
|
59
|
+
* self-mounts UNLESS something has read {@link httpHandlers} — which happens
|
|
60
|
+
* exactly when you wire a bring-your-own `@Controller` (e.g. via
|
|
61
|
+
* `{ provide: MCP_HTTP_HANDLER, useValue: transport.httpHandlers }`). So
|
|
62
|
+
* providing a controller automatically suppresses self-mounting; doing
|
|
63
|
+
* nothing keeps the zero-config self-mount. This read happens at module
|
|
64
|
+
* definition time, before the transport starts, so the timing is reliable.
|
|
65
|
+
*
|
|
66
|
+
* Set it explicitly to override the heuristic:
|
|
67
|
+
* - `true`: always self-mount (bypasses the Nest pipeline — no guards).
|
|
68
|
+
* - `false`: never self-mount (you own the route via a controller).
|
|
69
|
+
*
|
|
70
|
+
* @default undefined (auto: self-mount unless `httpHandlers` was accessed)
|
|
71
|
+
*/
|
|
72
|
+
mount?: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Streamable-HTTP transport. Mounts `POST`/`GET`/`DELETE` on the Nest HTTP server
|
|
77
|
+
* and delegates to the MCP SDK `StreamableHTTPServerTransport`. Supports both the
|
|
78
|
+
* stateless (one server per request) and stateful (session-managed) modes.
|
|
79
|
+
*/
|
|
80
|
+
export class StreamableHttpTransport implements McpTransport {
|
|
81
|
+
readonly kind = 'streamable-http' as const;
|
|
82
|
+
|
|
83
|
+
private readonly endpoint: string;
|
|
84
|
+
private readonly statefulMode: boolean;
|
|
85
|
+
private readonly enableJsonResponse: boolean;
|
|
86
|
+
private readonly sessionIdGenerator: () => string;
|
|
87
|
+
/** Whether `endpoint` was set explicitly (vs defaulted) — for the ignored-option warning. */
|
|
88
|
+
private readonly endpointExplicit: boolean;
|
|
89
|
+
/** Explicit `mount` override; `undefined` means auto-detect. */
|
|
90
|
+
private readonly mountOption?: boolean;
|
|
91
|
+
/** Set the first time {@link httpHandlers} is read — implies a BYO controller. */
|
|
92
|
+
private handlersClaimed = false;
|
|
93
|
+
|
|
94
|
+
private readonly transports: Record<string, StreamableHTTPServerTransport> =
|
|
95
|
+
{};
|
|
96
|
+
private readonly servers: Record<string, McpServer> = {};
|
|
97
|
+
private ctx?: McpTransportContext;
|
|
98
|
+
|
|
99
|
+
constructor(options: StreamableHttpTransportOptions = {}) {
|
|
100
|
+
this.endpoint = ensureLeadingSlash(options.endpoint ?? 'mcp');
|
|
101
|
+
this.endpointExplicit = options.endpoint !== undefined;
|
|
102
|
+
this.statefulMode = options.statefulMode ?? false;
|
|
103
|
+
// Default follows the session mode: JSON in stateless, SSE in stateful.
|
|
104
|
+
this.enableJsonResponse =
|
|
105
|
+
options.enableJsonResponse ?? !this.statefulMode;
|
|
106
|
+
this.sessionIdGenerator =
|
|
107
|
+
options.sessionIdGenerator ?? (() => randomUUID());
|
|
108
|
+
this.mountOption = options.mount;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The HTTP verb handlers, for bring-your-own-controller setups. Provide this
|
|
113
|
+
* under `MCP_HTTP_HANDLER` and delegate to it from a `@Controller` (see
|
|
114
|
+
* {@link StreamableHttpController}). The returned functions are bound to this
|
|
115
|
+
* transport and read their context lazily, so it is safe to grab this getter
|
|
116
|
+
* at module-construction time — before `start()` has run.
|
|
117
|
+
*
|
|
118
|
+
* Reading this getter also marks the route as claimed, so the transport
|
|
119
|
+
* auto-disables its own self-mount (unless `mount` was set explicitly). That
|
|
120
|
+
* is how `{ provide: MCP_HTTP_HANDLER, useValue: transport.httpHandlers }`
|
|
121
|
+
* suppresses self-mounting without any extra flag.
|
|
122
|
+
*/
|
|
123
|
+
get httpHandlers(): McpHttpHandler {
|
|
124
|
+
this.handlersClaimed = true;
|
|
125
|
+
return {
|
|
126
|
+
handlePost: (req: unknown, res: unknown) => this.handlePost(req, res),
|
|
127
|
+
handleGet: (req: unknown, res: unknown) => this.handleGet(req, res),
|
|
128
|
+
handleDelete: (req: unknown, res: unknown) => this.handleDelete(req, res),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
start(ctx: McpTransportContext): void {
|
|
133
|
+
// The context (server factory, request-handler binding, logger) is always
|
|
134
|
+
// needed — the handlers read it lazily whether we self-mount or a user
|
|
135
|
+
// controller calls them.
|
|
136
|
+
this.ctx = ctx;
|
|
137
|
+
|
|
138
|
+
// Auto-detect: self-mount unless a controller claimed the route by reading
|
|
139
|
+
// `httpHandlers`. An explicit `mount` option overrides the heuristic.
|
|
140
|
+
const shouldMount = this.mountOption ?? !this.handlersClaimed;
|
|
141
|
+
|
|
142
|
+
if (!shouldMount) {
|
|
143
|
+
// The path now comes from the user's @Controller(...), so a self-mount
|
|
144
|
+
// `endpoint` would be silently ignored. Don't let that pass quietly.
|
|
145
|
+
if (this.endpointExplicit) {
|
|
146
|
+
ctx.logger.warn(
|
|
147
|
+
`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).`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
ctx.logger.log(
|
|
151
|
+
`MCP streamable-http transport ready (${this.statefulMode ? 'stateful' : 'stateless'}, self-mount disabled — a controller owns the route)`,
|
|
152
|
+
);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!ctx.httpAdapter) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
'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).',
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
const adapter = ctx.httpAdapter as unknown as {
|
|
162
|
+
post(path: string, handler: (req: any, res: any) => unknown): unknown;
|
|
163
|
+
get(path: string, handler: (req: any, res: any) => unknown): unknown;
|
|
164
|
+
delete(path: string, handler: (req: any, res: any) => unknown): unknown;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
adapter.post(this.endpoint, (req, res) => this.handlePost(req, res));
|
|
168
|
+
adapter.get(this.endpoint, (req, res) => this.handleGet(req, res));
|
|
169
|
+
adapter.delete(this.endpoint, (req, res) => this.handleDelete(req, res));
|
|
170
|
+
|
|
171
|
+
ctx.logger.log(
|
|
172
|
+
`MCP streamable-http transport mounted at ${this.endpoint} (${this.statefulMode ? 'stateful' : 'stateless'})`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async close(): Promise<void> {
|
|
177
|
+
for (const sessionId of Object.keys(this.transports)) {
|
|
178
|
+
await this.cleanupSession(sessionId);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private async handlePost(req: any, res: any): Promise<void> {
|
|
183
|
+
const adapter = HttpAdapterFactory.getAdapter(req, res);
|
|
184
|
+
const adaptedReq = adapter.adaptRequest(req);
|
|
185
|
+
const adaptedRes = adapter.adaptResponse(res);
|
|
186
|
+
const body = await readJsonBody(adaptedReq);
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
if (this.statefulMode) {
|
|
190
|
+
await this.handleStateful(adaptedReq, adaptedRes, body);
|
|
191
|
+
} else {
|
|
192
|
+
await this.handleStateless(adaptedReq, adaptedRes, body);
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
this.ctx!.logger.error('Error handling MCP request', error as Error);
|
|
196
|
+
if (!adaptedRes.headersSent) {
|
|
197
|
+
adaptedRes.status(500).json({
|
|
198
|
+
jsonrpc: '2.0',
|
|
199
|
+
error: { code: -32603, message: 'Internal server error' },
|
|
200
|
+
id: null,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private async handleStateless(
|
|
207
|
+
req: ReturnType<
|
|
208
|
+
ReturnType<typeof HttpAdapterFactory.getAdapter>['adaptRequest']
|
|
209
|
+
>,
|
|
210
|
+
res: HttpResponse,
|
|
211
|
+
body: unknown,
|
|
212
|
+
): Promise<void> {
|
|
213
|
+
const transport = new StreamableHTTPServerTransport({
|
|
214
|
+
sessionIdGenerator: undefined,
|
|
215
|
+
enableJsonResponse: this.enableJsonResponse,
|
|
216
|
+
});
|
|
217
|
+
const server = this.ctx!.createServer();
|
|
218
|
+
await server.connect(transport);
|
|
219
|
+
this.ctx!.bindRequestHandlers(
|
|
220
|
+
server,
|
|
221
|
+
{ transport: this.kind, stateless: true },
|
|
222
|
+
req.raw,
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
res.raw.on('finish', () => {
|
|
226
|
+
void transport.close();
|
|
227
|
+
void server.close();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
await transport.handleRequest(req.raw, res.raw, body);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private async handleStateful(
|
|
234
|
+
req: ReturnType<
|
|
235
|
+
ReturnType<typeof HttpAdapterFactory.getAdapter>['adaptRequest']
|
|
236
|
+
>,
|
|
237
|
+
res: HttpResponse,
|
|
238
|
+
body: unknown,
|
|
239
|
+
): Promise<void> {
|
|
240
|
+
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
241
|
+
|
|
242
|
+
if (!sessionId && isInitializeRequest(body)) {
|
|
243
|
+
const server = this.ctx!.createServer();
|
|
244
|
+
const transport = new StreamableHTTPServerTransport({
|
|
245
|
+
sessionIdGenerator: this.sessionIdGenerator,
|
|
246
|
+
enableJsonResponse: this.enableJsonResponse,
|
|
247
|
+
onsessioninitialized: (sid: string) => {
|
|
248
|
+
this.transports[sid] = transport;
|
|
249
|
+
this.servers[sid] = server;
|
|
250
|
+
},
|
|
251
|
+
onsessionclosed: (sid: string) => {
|
|
252
|
+
void this.cleanupSession(sid);
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
await server.connect(transport);
|
|
256
|
+
this.ctx!.bindRequestHandlers(
|
|
257
|
+
server,
|
|
258
|
+
{ transport: this.kind, stateless: false },
|
|
259
|
+
req.raw,
|
|
260
|
+
);
|
|
261
|
+
await transport.handleRequest(req.raw, res.raw, body);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (sessionId) {
|
|
266
|
+
const transport = this.transports[sessionId];
|
|
267
|
+
const server = this.servers[sessionId];
|
|
268
|
+
if (!transport || !server) {
|
|
269
|
+
res.status(404).json({
|
|
270
|
+
jsonrpc: '2.0',
|
|
271
|
+
error: { code: -32001, message: 'Session not found' },
|
|
272
|
+
id: null,
|
|
273
|
+
});
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
// Re-bind so the per-request auth context is current.
|
|
277
|
+
this.ctx!.bindRequestHandlers(
|
|
278
|
+
server,
|
|
279
|
+
{ transport: this.kind, stateless: false, sessionId },
|
|
280
|
+
req.raw,
|
|
281
|
+
);
|
|
282
|
+
await transport.handleRequest(req.raw, res.raw, body);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
res.status(400).json({
|
|
287
|
+
jsonrpc: '2.0',
|
|
288
|
+
error: {
|
|
289
|
+
code: -32000,
|
|
290
|
+
message: 'Bad Request: Mcp-Session-Id header is required',
|
|
291
|
+
},
|
|
292
|
+
id: null,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private async handleGet(req: any, res: any): Promise<void> {
|
|
297
|
+
const adapter = HttpAdapterFactory.getAdapter(req, res);
|
|
298
|
+
const adaptedReq = adapter.adaptRequest(req);
|
|
299
|
+
const adaptedRes = adapter.adaptResponse(res);
|
|
300
|
+
|
|
301
|
+
if (!this.statefulMode) {
|
|
302
|
+
adaptedRes.status(405).json({
|
|
303
|
+
jsonrpc: '2.0',
|
|
304
|
+
error: {
|
|
305
|
+
code: -32000,
|
|
306
|
+
message: 'Method not allowed in stateless mode',
|
|
307
|
+
},
|
|
308
|
+
id: null,
|
|
309
|
+
});
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const sessionId = adaptedReq.headers['mcp-session-id'] as
|
|
313
|
+
| string
|
|
314
|
+
| undefined;
|
|
315
|
+
const transport = sessionId ? this.transports[sessionId] : undefined;
|
|
316
|
+
if (!transport) {
|
|
317
|
+
adaptedRes.status(404).json({
|
|
318
|
+
jsonrpc: '2.0',
|
|
319
|
+
error: { code: -32001, message: 'Session not found' },
|
|
320
|
+
id: null,
|
|
321
|
+
});
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
await transport.handleRequest(adaptedReq.raw, adaptedRes.raw);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private async handleDelete(req: any, res: any): Promise<void> {
|
|
328
|
+
const adapter = HttpAdapterFactory.getAdapter(req, res);
|
|
329
|
+
const adaptedReq = adapter.adaptRequest(req);
|
|
330
|
+
const adaptedRes = adapter.adaptResponse(res);
|
|
331
|
+
|
|
332
|
+
if (!this.statefulMode) {
|
|
333
|
+
adaptedRes.status(405).json({
|
|
334
|
+
jsonrpc: '2.0',
|
|
335
|
+
error: {
|
|
336
|
+
code: -32000,
|
|
337
|
+
message: 'Method not allowed in stateless mode',
|
|
338
|
+
},
|
|
339
|
+
id: null,
|
|
340
|
+
});
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const sessionId = adaptedReq.headers['mcp-session-id'] as
|
|
344
|
+
| string
|
|
345
|
+
| undefined;
|
|
346
|
+
const transport = sessionId ? this.transports[sessionId] : undefined;
|
|
347
|
+
if (!transport) {
|
|
348
|
+
adaptedRes.status(404).json({
|
|
349
|
+
jsonrpc: '2.0',
|
|
350
|
+
error: { code: -32001, message: 'Session not found' },
|
|
351
|
+
id: null,
|
|
352
|
+
});
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
await transport.handleRequest(adaptedReq.raw, adaptedRes.raw);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private async cleanupSession(sessionId: string): Promise<void> {
|
|
359
|
+
const transport = this.transports[sessionId];
|
|
360
|
+
const server = this.servers[sessionId];
|
|
361
|
+
delete this.transports[sessionId];
|
|
362
|
+
delete this.servers[sessionId];
|
|
363
|
+
try {
|
|
364
|
+
await transport?.close();
|
|
365
|
+
await server?.close();
|
|
366
|
+
} catch {
|
|
367
|
+
// best-effort cleanup
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function isInitializeRequest(body: unknown): boolean {
|
|
373
|
+
const isInit = (msg: unknown): boolean =>
|
|
374
|
+
typeof msg === 'object' &&
|
|
375
|
+
msg !== null &&
|
|
376
|
+
'method' in msg &&
|
|
377
|
+
(msg as { method?: unknown }).method === 'initialize';
|
|
378
|
+
return Array.isArray(body) ? body.some(isInit) : isInit(body);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function ensureLeadingSlash(endpoint: string): string {
|
|
382
|
+
const trimmed = endpoint.trim();
|
|
383
|
+
return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
384
|
+
}
|