@cyanheads/mcp-ts-core 0.11.5 → 0.12.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/AGENTS.md +27 -23
- package/CLAUDE.md +27 -23
- package/README.md +10 -10
- package/biome.json +1 -1
- package/changelog/0.12.x/0.12.0.md +96 -0
- package/dist/cli/init.js +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/config/index.d.ts +0 -18
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +0 -25
- package/dist/config/index.js.map +1 -1
- package/dist/core/app.d.ts +5 -7
- package/dist/core/app.d.ts.map +1 -1
- package/dist/core/app.js +18 -27
- package/dist/core/app.js.map +1 -1
- package/dist/core/context.d.ts +90 -57
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +32 -33
- package/dist/core/context.js.map +1 -1
- package/dist/core/gcPressure.d.ts.map +1 -1
- package/dist/core/gcPressure.js +3 -4
- package/dist/core/gcPressure.js.map +1 -1
- package/dist/core/index.d.ts +6 -7
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +10 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/serverManifest.d.ts +1 -3
- package/dist/core/serverManifest.d.ts.map +1 -1
- package/dist/core/serverManifest.js +9 -4
- package/dist/core/serverManifest.js.map +1 -1
- package/dist/core/worker.d.ts.map +1 -1
- package/dist/core/worker.js +32 -35
- package/dist/core/worker.js.map +1 -1
- package/dist/logs/combined.log +10 -22
- package/dist/logs/error.log +6 -18
- package/dist/mcp-server/inputRequired.d.ts +52 -0
- package/dist/mcp-server/inputRequired.d.ts.map +1 -0
- package/dist/mcp-server/inputRequired.js +76 -0
- package/dist/mcp-server/inputRequired.js.map +1 -0
- package/dist/mcp-server/notifications.d.ts +33 -31
- package/dist/mcp-server/notifications.d.ts.map +1 -1
- package/dist/mcp-server/notifications.js +34 -26
- package/dist/mcp-server/notifications.js.map +1 -1
- package/dist/mcp-server/prompts/prompt-registration.d.ts +2 -2
- package/dist/mcp-server/prompts/prompt-registration.d.ts.map +1 -1
- package/dist/mcp-server/prompts/prompt-registration.js +15 -5
- package/dist/mcp-server/prompts/prompt-registration.js.map +1 -1
- package/dist/mcp-server/prompts/utils/promptDefinition.d.ts +1 -1
- package/dist/mcp-server/prompts/utils/promptDefinition.d.ts.map +1 -1
- package/dist/mcp-server/resources/resource-registration.d.ts +3 -3
- package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -1
- package/dist/mcp-server/resources/resource-registration.js +8 -15
- package/dist/mcp-server/resources/resource-registration.js.map +1 -1
- package/dist/mcp-server/resources/resourceSubscriptions.d.ts +45 -0
- package/dist/mcp-server/resources/resourceSubscriptions.d.ts.map +1 -0
- package/dist/mcp-server/resources/resourceSubscriptions.js +51 -0
- package/dist/mcp-server/resources/resourceSubscriptions.js.map +1 -0
- package/dist/mcp-server/resources/utils/resourceDefinition.d.ts +3 -5
- package/dist/mcp-server/resources/utils/resourceDefinition.d.ts.map +1 -1
- package/dist/mcp-server/resources/utils/resourceDefinition.js +1 -1
- package/dist/mcp-server/resources/utils/resourceDefinition.js.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts +8 -15
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +21 -22
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
- package/dist/mcp-server/server.d.ts +12 -21
- package/dist/mcp-server/server.d.ts.map +1 -1
- package/dist/mcp-server/server.js +38 -28
- package/dist/mcp-server/server.js.map +1 -1
- package/dist/mcp-server/tools/tool-registration.d.ts +11 -33
- package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -1
- package/dist/mcp-server/tools/tool-registration.js +17 -243
- package/dist/mcp-server/tools/tool-registration.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts +1 -3
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolDefinition.js +48 -1
- package/dist/mcp-server/tools/utils/toolDefinition.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +46 -20
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +131 -25
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/dist/mcp-server/transports/ITransport.d.ts +2 -2
- package/dist/mcp-server/transports/ITransport.d.ts.map +1 -1
- package/dist/mcp-server/transports/auth/authFactory.js +1 -1
- package/dist/mcp-server/transports/auth/authFactory.js.map +1 -1
- package/dist/mcp-server/transports/auth/lib/authTypes.d.ts +1 -1
- package/dist/mcp-server/transports/auth/lib/authTypes.d.ts.map +1 -1
- package/dist/mcp-server/transports/auth/lib/authUtils.d.ts.map +1 -1
- package/dist/mcp-server/transports/auth/lib/authUtils.js +5 -13
- package/dist/mcp-server/transports/auth/lib/authUtils.js.map +1 -1
- package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js +5 -11
- package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js.map +1 -1
- package/dist/mcp-server/transports/auth/strategies/oauthStrategy.d.ts.map +1 -1
- package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js +9 -22
- package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js.map +1 -1
- package/dist/mcp-server/transports/heartbeat.d.ts.map +1 -1
- package/dist/mcp-server/transports/heartbeat.js +4 -8
- package/dist/mcp-server/transports/heartbeat.js.map +1 -1
- package/dist/mcp-server/transports/http/httpErrorHandler.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/httpErrorHandler.js +6 -24
- package/dist/mcp-server/transports/http/httpErrorHandler.js.map +1 -1
- package/dist/mcp-server/transports/http/httpServer.d.ts +3 -3
- package/dist/mcp-server/transports/http/httpServer.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/httpServer.js +15 -32
- package/dist/mcp-server/transports/http/httpServer.js.map +1 -1
- package/dist/mcp-server/transports/http/httpTransport.d.ts +25 -8
- package/dist/mcp-server/transports/http/httpTransport.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/httpTransport.js +156 -373
- package/dist/mcp-server/transports/http/httpTransport.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/assets/styles.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/assets/styles.js +0 -1
- package/dist/mcp-server/transports/http/landing-page/assets/styles.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/handler.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/handler.js +4 -8
- package/dist/mcp-server/transports/http/landing-page/handler.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/tools.js +0 -2
- package/dist/mcp-server/transports/http/landing-page/sections/tools.js.map +1 -1
- package/dist/mcp-server/transports/http/protectedResourceMetadata.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/protectedResourceMetadata.js +2 -6
- package/dist/mcp-server/transports/http/protectedResourceMetadata.js.map +1 -1
- package/dist/mcp-server/transports/http/robotsTxt.js +2 -2
- package/dist/mcp-server/transports/http/robotsTxt.js.map +1 -1
- package/dist/mcp-server/transports/http/serverCard.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/serverCard.js +2 -6
- package/dist/mcp-server/transports/http/serverCard.js.map +1 -1
- package/dist/mcp-server/transports/http/sessionStore.d.ts +46 -66
- package/dist/mcp-server/transports/http/sessionStore.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/sessionStore.js +140 -238
- package/dist/mcp-server/transports/http/sessionStore.js.map +1 -1
- package/dist/mcp-server/transports/manager.d.ts +12 -4
- package/dist/mcp-server/transports/manager.d.ts.map +1 -1
- package/dist/mcp-server/transports/manager.js +35 -33
- package/dist/mcp-server/transports/manager.js.map +1 -1
- package/dist/mcp-server/transports/stdio/stdioTransport.d.ts +24 -31
- package/dist/mcp-server/transports/stdio/stdioTransport.d.ts.map +1 -1
- package/dist/mcp-server/transports/stdio/stdioTransport.js +23 -38
- package/dist/mcp-server/transports/stdio/stdioTransport.js.map +1 -1
- package/dist/mcp-server/types.d.ts +25 -0
- package/dist/mcp-server/types.d.ts.map +1 -0
- package/dist/mcp-server/types.js +10 -0
- package/dist/mcp-server/types.js.map +1 -0
- package/dist/services/canvas/core/CanvasInstance.d.ts +2 -2
- package/dist/services/canvas/core/CanvasInstance.d.ts.map +1 -1
- package/dist/services/canvas/core/CanvasInstance.js.map +1 -1
- package/dist/services/canvas/core/CanvasRegistry.d.ts +4 -4
- package/dist/services/canvas/core/CanvasRegistry.d.ts.map +1 -1
- package/dist/services/canvas/core/CanvasRegistry.js +8 -18
- package/dist/services/canvas/core/CanvasRegistry.js.map +1 -1
- package/dist/services/canvas/core/DataCanvas.d.ts +5 -5
- package/dist/services/canvas/core/DataCanvas.d.ts.map +1 -1
- package/dist/services/canvas/core/DataCanvas.js +2 -3
- package/dist/services/canvas/core/DataCanvas.js.map +1 -1
- package/dist/services/canvas/core/IDataCanvasProvider.d.ts +11 -11
- package/dist/services/canvas/core/IDataCanvasProvider.d.ts.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts +11 -11
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +4 -10
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
- package/dist/services/llm/providers/openrouter.provider.d.ts.map +1 -1
- package/dist/services/llm/providers/openrouter.provider.js +2 -5
- package/dist/services/llm/providers/openrouter.provider.js.map +1 -1
- package/dist/services/mirror/core/defineMirror.js +1 -1
- package/dist/services/mirror/core/defineMirror.js.map +1 -1
- package/dist/services/mirror/types.d.ts +5 -5
- package/dist/services/mirror/types.d.ts.map +1 -1
- package/dist/services/speech/providers/elevenlabs.provider.js +1 -1
- package/dist/services/speech/providers/elevenlabs.provider.js.map +1 -1
- package/dist/services/speech/providers/whisper.provider.js +1 -1
- package/dist/services/speech/providers/whisper.provider.js.map +1 -1
- package/dist/storage/core/StorageService.d.ts +1 -1
- package/dist/storage/core/StorageService.d.ts.map +1 -1
- package/dist/storage/core/StorageService.js +12 -20
- package/dist/storage/core/StorageService.js.map +1 -1
- package/dist/storage/core/storageFactory.d.ts.map +1 -1
- package/dist/storage/core/storageFactory.js.map +1 -1
- package/dist/storage/core/storageValidation.d.ts +1 -1
- package/dist/storage/core/storageValidation.d.ts.map +1 -1
- package/dist/storage/core/storageValidation.js +2 -2
- package/dist/storage/core/storageValidation.js.map +1 -1
- package/dist/storage/providers/cloudflare/d1Provider.d.ts +1 -1
- package/dist/storage/providers/cloudflare/d1Provider.d.ts.map +1 -1
- package/dist/storage/providers/cloudflare/d1Provider.js +4 -12
- package/dist/storage/providers/cloudflare/d1Provider.js.map +1 -1
- package/dist/storage/providers/cloudflare/kvProvider.d.ts +1 -1
- package/dist/storage/providers/cloudflare/kvProvider.d.ts.map +1 -1
- package/dist/storage/providers/cloudflare/kvProvider.js +3 -8
- package/dist/storage/providers/cloudflare/kvProvider.js.map +1 -1
- package/dist/storage/providers/cloudflare/r2Provider.d.ts +1 -1
- package/dist/storage/providers/cloudflare/r2Provider.d.ts.map +1 -1
- package/dist/storage/providers/cloudflare/r2Provider.js +3 -8
- package/dist/storage/providers/cloudflare/r2Provider.js.map +1 -1
- package/dist/storage/providers/inMemory/inMemoryProvider.d.ts +1 -1
- package/dist/storage/providers/inMemory/inMemoryProvider.d.ts.map +1 -1
- package/dist/storage/providers/inMemory/inMemoryProvider.js +2 -4
- package/dist/storage/providers/inMemory/inMemoryProvider.js.map +1 -1
- package/dist/testing/index.d.ts +35 -12
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +53 -38
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/vitest.d.ts +1 -1
- package/dist/testing/vitest.d.ts.map +1 -1
- package/dist/types-global/errors.d.ts +28 -15
- package/dist/types-global/errors.d.ts.map +1 -1
- package/dist/types-global/errors.js +8 -1
- package/dist/types-global/errors.js.map +1 -1
- package/dist/utils/formatting/diffFormatter.d.ts +5 -5
- package/dist/utils/formatting/diffFormatter.d.ts.map +1 -1
- package/dist/utils/formatting/diffFormatter.js +7 -20
- package/dist/utils/formatting/diffFormatter.js.map +1 -1
- package/dist/utils/formatting/tableFormatter.d.ts +3 -3
- package/dist/utils/formatting/tableFormatter.d.ts.map +1 -1
- package/dist/utils/formatting/tableFormatter.js +6 -17
- package/dist/utils/formatting/tableFormatter.js.map +1 -1
- package/dist/utils/formatting/treeFormatter.d.ts +3 -3
- package/dist/utils/formatting/treeFormatter.d.ts.map +1 -1
- package/dist/utils/formatting/treeFormatter.js +5 -18
- package/dist/utils/formatting/treeFormatter.js.map +1 -1
- package/dist/utils/index.d.ts +4 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.d.ts +1 -1
- package/dist/utils/internal/error-handler/errorHandler.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.js +23 -11
- package/dist/utils/internal/error-handler/errorHandler.js.map +1 -1
- package/dist/utils/internal/error-handler/types.d.ts +10 -13
- package/dist/utils/internal/error-handler/types.d.ts.map +1 -1
- package/dist/utils/internal/logger.d.ts +3 -3
- package/dist/utils/internal/logger.d.ts.map +1 -1
- package/dist/utils/internal/logger.js +11 -5
- package/dist/utils/internal/logger.js.map +1 -1
- package/dist/utils/internal/performance.d.ts +1 -1
- package/dist/utils/internal/performance.d.ts.map +1 -1
- package/dist/utils/internal/performance.js +57 -13
- package/dist/utils/internal/performance.js.map +1 -1
- package/dist/utils/internal/requestContext.d.ts +79 -55
- package/dist/utils/internal/requestContext.d.ts.map +1 -1
- package/dist/utils/internal/requestContext.js +77 -32
- package/dist/utils/internal/requestContext.js.map +1 -1
- package/dist/utils/metrics/tokenCounter.d.ts +3 -3
- package/dist/utils/metrics/tokenCounter.d.ts.map +1 -1
- package/dist/utils/metrics/tokenCounter.js.map +1 -1
- package/dist/utils/network/fetchWithTimeout.d.ts +2 -2
- package/dist/utils/network/fetchWithTimeout.d.ts.map +1 -1
- package/dist/utils/network/fetchWithTimeout.js +8 -24
- package/dist/utils/network/fetchWithTimeout.js.map +1 -1
- package/dist/utils/network/retry.d.ts +2 -2
- package/dist/utils/network/retry.d.ts.map +1 -1
- package/dist/utils/overflow/outlineOnOverflow.d.ts +1 -1
- package/dist/utils/overflow/outlineOnOverflow.d.ts.map +1 -1
- package/dist/utils/pagination/pagination.d.ts +3 -3
- package/dist/utils/pagination/pagination.d.ts.map +1 -1
- package/dist/utils/pagination/pagination.js +3 -3
- package/dist/utils/pagination/pagination.js.map +1 -1
- package/dist/utils/parsing/csvParser.d.ts +2 -2
- package/dist/utils/parsing/csvParser.d.ts.map +1 -1
- package/dist/utils/parsing/csvParser.js +4 -8
- package/dist/utils/parsing/csvParser.js.map +1 -1
- package/dist/utils/parsing/dateParser.d.ts +3 -3
- package/dist/utils/parsing/dateParser.d.ts.map +1 -1
- package/dist/utils/parsing/dateParser.js +3 -2
- package/dist/utils/parsing/dateParser.js.map +1 -1
- package/dist/utils/parsing/frontmatterParser.d.ts +2 -2
- package/dist/utils/parsing/frontmatterParser.d.ts.map +1 -1
- package/dist/utils/parsing/frontmatterParser.js +7 -10
- package/dist/utils/parsing/frontmatterParser.js.map +1 -1
- package/dist/utils/parsing/htmlExtractor.d.ts +2 -2
- package/dist/utils/parsing/htmlExtractor.d.ts.map +1 -1
- package/dist/utils/parsing/htmlExtractor.js +6 -11
- package/dist/utils/parsing/htmlExtractor.js.map +1 -1
- package/dist/utils/parsing/jsonParser.d.ts +2 -2
- package/dist/utils/parsing/jsonParser.d.ts.map +1 -1
- package/dist/utils/parsing/jsonParser.js +4 -8
- package/dist/utils/parsing/jsonParser.js.map +1 -1
- package/dist/utils/parsing/pdfParser.d.ts +10 -10
- package/dist/utils/parsing/pdfParser.d.ts.map +1 -1
- package/dist/utils/parsing/pdfParser.js +26 -89
- package/dist/utils/parsing/pdfParser.js.map +1 -1
- package/dist/utils/parsing/xmlParser.d.ts +2 -2
- package/dist/utils/parsing/xmlParser.d.ts.map +1 -1
- package/dist/utils/parsing/xmlParser.js +4 -8
- package/dist/utils/parsing/xmlParser.js.map +1 -1
- package/dist/utils/parsing/yamlParser.d.ts +2 -2
- package/dist/utils/parsing/yamlParser.d.ts.map +1 -1
- package/dist/utils/parsing/yamlParser.js +4 -8
- package/dist/utils/parsing/yamlParser.js.map +1 -1
- package/dist/utils/scheduling/scheduler.d.ts.map +1 -1
- package/dist/utils/scheduling/scheduler.js +5 -6
- package/dist/utils/scheduling/scheduler.js.map +1 -1
- package/dist/utils/security/rateLimiter.d.ts +2 -2
- package/dist/utils/security/rateLimiter.d.ts.map +1 -1
- package/dist/utils/security/rateLimiter.js +1 -1
- package/dist/utils/security/rateLimiter.js.map +1 -1
- package/dist/utils/telemetry/attributes.d.ts +23 -7
- package/dist/utils/telemetry/attributes.d.ts.map +1 -1
- package/dist/utils/telemetry/attributes.js +23 -10
- package/dist/utils/telemetry/attributes.js.map +1 -1
- package/dist/utils/telemetry/trace.d.ts +3 -3
- package/dist/utils/telemetry/trace.d.ts.map +1 -1
- package/dist/utils/telemetry/trace.js +4 -2
- package/dist/utils/telemetry/trace.js.map +1 -1
- package/package.json +22 -26
- package/scripts/lint-mcp.ts +1 -3
- package/scripts/lint-packaging.ts +1 -1
- package/skills/add-service/SKILL.md +2 -2
- package/skills/add-test/SKILL.md +42 -20
- package/skills/add-tool/SKILL.md +68 -19
- package/skills/api-config/SKILL.md +1 -11
- package/skills/api-context/SKILL.md +175 -111
- package/skills/api-linter/SKILL.md +2 -2
- package/skills/api-telemetry/SKILL.md +8 -12
- package/skills/api-testing/SKILL.md +53 -52
- package/skills/code-simplifier/SKILL.md +2 -2
- package/skills/design-mcp-server/SKILL.md +31 -17
- package/skills/field-test/SKILL.md +4 -2
- package/skills/polish-docs-meta/SKILL.md +1 -1
- package/skills/polish-docs-meta/references/agent-protocol.md +2 -2
- package/skills/polish-docs-meta/references/readme.md +1 -1
- package/skills/release-and-publish/SKILL.md +3 -1
- package/skills/report-issue-framework/SKILL.md +2 -2
- package/skills/report-issue-local/SKILL.md +2 -2
- package/skills/security-pass/SKILL.md +16 -14
- package/templates/AGENTS.md +6 -6
- package/templates/CLAUDE.md +6 -6
- package/templates/tests/resources/echo.resource.test.ts +16 -9
- package/dist/mcp-server/elicitation.d.ts +0 -64
- package/dist/mcp-server/elicitation.d.ts.map +0 -1
- package/dist/mcp-server/elicitation.js +0 -81
- package/dist/mcp-server/elicitation.js.map +0 -1
- package/dist/mcp-server/protocolSession.d.ts +0 -34
- package/dist/mcp-server/protocolSession.d.ts.map +0 -1
- package/dist/mcp-server/protocolSession.js +0 -13
- package/dist/mcp-server/protocolSession.js.map +0 -1
- package/dist/mcp-server/tasks/core/evictingTaskMessageQueue.d.ts +0 -53
- package/dist/mcp-server/tasks/core/evictingTaskMessageQueue.d.ts.map +0 -1
- package/dist/mcp-server/tasks/core/evictingTaskMessageQueue.js +0 -65
- package/dist/mcp-server/tasks/core/evictingTaskMessageQueue.js.map +0 -1
- package/dist/mcp-server/tasks/core/sessionAwareTaskStore.d.ts +0 -70
- package/dist/mcp-server/tasks/core/sessionAwareTaskStore.d.ts.map +0 -1
- package/dist/mcp-server/tasks/core/sessionAwareTaskStore.js +0 -130
- package/dist/mcp-server/tasks/core/sessionAwareTaskStore.js.map +0 -1
- package/dist/mcp-server/tasks/core/storageBackedTaskStore.d.ts +0 -109
- package/dist/mcp-server/tasks/core/storageBackedTaskStore.d.ts.map +0 -1
- package/dist/mcp-server/tasks/core/storageBackedTaskStore.js +0 -209
- package/dist/mcp-server/tasks/core/storageBackedTaskStore.js.map +0 -1
- package/dist/mcp-server/tasks/core/taskManager.d.ts +0 -91
- package/dist/mcp-server/tasks/core/taskManager.d.ts.map +0 -1
- package/dist/mcp-server/tasks/core/taskManager.js +0 -210
- package/dist/mcp-server/tasks/core/taskManager.js.map +0 -1
- package/dist/mcp-server/tasks/core/taskTypes.d.ts +0 -18
- package/dist/mcp-server/tasks/core/taskTypes.d.ts.map +0 -1
- package/dist/mcp-server/tasks/core/taskTypes.js +0 -20
- package/dist/mcp-server/tasks/core/taskTypes.js.map +0 -1
- package/dist/mcp-server/tasks/utils/taskToolDefinition.d.ts +0 -101
- package/dist/mcp-server/tasks/utils/taskToolDefinition.d.ts.map +0 -1
- package/dist/mcp-server/tasks/utils/taskToolDefinition.js +0 -14
- package/dist/mcp-server/tasks/utils/taskToolDefinition.js.map +0 -1
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Worker-safe Hono application factory for the HTTP MCP transport.
|
|
3
|
-
* Builds the routing, auth, session, and
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `node:http` via tree-shaking.
|
|
3
|
+
* Builds the routing, auth, session, and serving wiring using only web-standard
|
|
4
|
+
* APIs. The Node-only `serve()` start path lives in `httpServer.ts` so the
|
|
5
|
+
* Worker bundle drops `@hono/node-server` and `node:http` via tree-shaking.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Serving is split by session mode:
|
|
8
|
+
*
|
|
9
|
+
* - **stateless** — one `createMcpHandler(factory)` answers both protocol eras
|
|
10
|
+
* per request (its default `legacy: 'stateless'` posture).
|
|
11
|
+
* - **stateful / auto** — `isLegacyRequest(request)` splits the traffic. 2025-era
|
|
12
|
+
* requests go to a sessionful `WebStandardStreamableHTTPServerTransport` with
|
|
13
|
+
* one persistent `McpServer` per `Mcp-Session-Id`; 2026-07-28 requests go to a
|
|
14
|
+
* strict (`legacy: 'reject'`) modern handler, which is per-request by
|
|
15
|
+
* construction — that revision has no session.
|
|
16
|
+
*
|
|
17
|
+
* The sessionful arm is load-bearing rather than legacy courtesy: the SDK's
|
|
18
|
+
* multi-round-trip legacy shim needs a live session, so dropping it would break
|
|
19
|
+
* interactive tools for every 2025 client that is not on stdio.
|
|
20
|
+
*
|
|
21
|
+
* @see {@link https://modelcontextprotocol.io/specification/2026-07-28/basic/transports | MCP Transports}
|
|
10
22
|
* @module src/mcp-server/transports/http/httpTransport
|
|
11
23
|
*/
|
|
12
|
-
import {
|
|
13
|
-
import { isJSONRPCErrorResponse, isJSONRPCRequest, isJSONRPCResultResponse, } from '@modelcontextprotocol/sdk/types.js';
|
|
24
|
+
import { createMcpHandler, isJsonContentType, isLegacyRequest, WebStandardStreamableHTTPServerTransport, } from '@modelcontextprotocol/server';
|
|
14
25
|
import { Hono } from 'hono';
|
|
15
26
|
import { cors } from 'hono/cors';
|
|
16
27
|
import { config } from '../../../config/index.js';
|
|
@@ -23,151 +34,18 @@ import { protectedResourceMetadataHandler } from '../../../mcp-server/transports
|
|
|
23
34
|
import { createRobotsTxtHandler } from '../../../mcp-server/transports/http/robotsTxt.js';
|
|
24
35
|
import { createServerCardHandler } from '../../../mcp-server/transports/http/serverCard.js';
|
|
25
36
|
import { generateSecureSessionId } from '../../../mcp-server/transports/http/sessionIdUtils.js';
|
|
26
|
-
import { SessionStore, } from '../../../mcp-server/transports/http/sessionStore.js';
|
|
37
|
+
import { closeConnection, SessionStore, } from '../../../mcp-server/transports/http/sessionStore.js';
|
|
27
38
|
import { logger } from '../../../utils/internal/logger.js';
|
|
28
|
-
import { requestContextService } from '../../../utils/internal/requestContext.js';
|
|
29
|
-
import {
|
|
30
|
-
/**
|
|
31
|
-
* Extends the base StreamableHTTPTransport to include a session ID.
|
|
32
|
-
*/
|
|
33
|
-
class McpSessionTransport extends StreamableHTTPTransport {
|
|
34
|
-
sessionStore;
|
|
35
|
-
sessionId;
|
|
36
|
-
pendingServerRequests = new Set();
|
|
37
|
-
constructor(sessionId, sessionStore) {
|
|
38
|
-
super();
|
|
39
|
-
this.sessionStore = sessionStore;
|
|
40
|
-
this.sessionId = sessionId;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Rewrites outbound server-request IDs into the durable session namespace.
|
|
44
|
-
* Every per-request SDK Server starts its request counter at zero; without
|
|
45
|
-
* this bridge concurrent elicitations collide and a response POST reaches a
|
|
46
|
-
* different Server instance than the one holding the response handler.
|
|
47
|
-
*/
|
|
48
|
-
async send(message, options) {
|
|
49
|
-
if (!isJSONRPCRequest(message) || !this.sessionStore) {
|
|
50
|
-
await super.send(message, options);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
const registration = this.sessionStore.registerServerRequest(this.sessionId, (response) => {
|
|
54
|
-
this.settleServerRequest(registration);
|
|
55
|
-
this.onmessage?.({ ...response, id: message.id });
|
|
56
|
-
});
|
|
57
|
-
if (!registration)
|
|
58
|
-
throw new Error('Cannot send a server request after its session ended.');
|
|
59
|
-
this.pendingServerRequests.add(registration);
|
|
60
|
-
try {
|
|
61
|
-
await super.send({ ...message, id: registration.requestId }, options);
|
|
62
|
-
}
|
|
63
|
-
catch (error) {
|
|
64
|
-
this.settleServerRequest(registration);
|
|
65
|
-
throw error;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
async close() {
|
|
69
|
-
for (const registration of this.pendingServerRequests)
|
|
70
|
-
registration.unregister();
|
|
71
|
-
this.pendingServerRequests.clear();
|
|
72
|
-
await super.close();
|
|
73
|
-
}
|
|
74
|
-
/** Drops a route from both the session registry and this transport's pending set. */
|
|
75
|
-
settleServerRequest(registration) {
|
|
76
|
-
if (!registration)
|
|
77
|
-
return;
|
|
78
|
-
registration.unregister();
|
|
79
|
-
this.pendingServerRequests.delete(registration);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
/** Max time (ms) we wait for per-request transport/server close to complete
|
|
83
|
-
* before giving up and logging a failure. Silent hangs here accumulate
|
|
84
|
-
* retained closures — bounded wait surfaces the issue as a metric. */
|
|
85
|
-
const PER_REQUEST_CLOSE_TIMEOUT_MS = 5_000;
|
|
39
|
+
import { requestContextService, withExtra, } from '../../../utils/internal/requestContext.js';
|
|
40
|
+
import { createObservableGauge } from '../../../utils/telemetry/metrics.js';
|
|
86
41
|
/** Matches loopback origins (http(s)://localhost|127.0.0.1|[::1] with optional port).
|
|
87
42
|
* Used as the fail-closed default for the Origin guard when no explicit
|
|
88
43
|
* MCP_ALLOWED_ORIGINS is configured — an unauthenticated MCP server must not
|
|
89
|
-
* accept browser Origin headers from arbitrary hosts (
|
|
90
|
-
*
|
|
44
|
+
* accept browser Origin headers from arbitrary hosts (DNS rebinding
|
|
45
|
+
* protection). The SDK's `validateOriginHeader` matches bare hostnames from a
|
|
46
|
+
* fixed list and so cannot express "any loopback port", which is exactly the
|
|
47
|
+
* dev-server case this guard exists for. */
|
|
91
48
|
const LOOPBACK_ORIGIN_RE = /^https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/i;
|
|
92
|
-
let closeFailureCounter;
|
|
93
|
-
function getCloseFailureCounter() {
|
|
94
|
-
closeFailureCounter ??= createCounter('mcp.http.close_failures', 'Per-request HTTP close failures (transport or server close threw or timed out)', '{failures}');
|
|
95
|
-
return closeFailureCounter;
|
|
96
|
-
}
|
|
97
|
-
// ---------------------------------------------------------------------------
|
|
98
|
-
// Finalization-lag diagnostic (issue #50)
|
|
99
|
-
//
|
|
100
|
-
// The HTTP transport constructs a fresh `McpServer` + `McpSessionTransport`
|
|
101
|
-
// per request (SDK 1.26 GHSA-345p-7cg4-v4c7). Production heap graphs show
|
|
102
|
-
// linear growth proportional to request volume, which suggests some of
|
|
103
|
-
// these per-request instances are being retained past their scope.
|
|
104
|
-
//
|
|
105
|
-
// Compare `mcp.http.per_request.created_total` against
|
|
106
|
-
// `mcp.http.per_request.finalized_total` (both tagged with `kind`) to
|
|
107
|
-
// detect retention. A persistent gap that grows with traffic is the
|
|
108
|
-
// signature of the leak; a gap that closes within minutes is just GC
|
|
109
|
-
// timing.
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
|
-
let perRequestCreatedCounter;
|
|
112
|
-
let perRequestFinalizedCounter;
|
|
113
|
-
function getPerRequestCreatedCounter() {
|
|
114
|
-
perRequestCreatedCounter ??= createCounter('mcp.http.per_request.created', 'Per-request McpServer/McpSessionTransport instances created', '{instances}');
|
|
115
|
-
return perRequestCreatedCounter;
|
|
116
|
-
}
|
|
117
|
-
function getPerRequestFinalizedCounter() {
|
|
118
|
-
perRequestFinalizedCounter ??= createCounter('mcp.http.per_request.finalized', 'Per-request McpServer/McpSessionTransport instances reclaimed by GC', '{instances}');
|
|
119
|
-
return perRequestFinalizedCounter;
|
|
120
|
-
}
|
|
121
|
-
/** Lazy so the registry only exists when HTTP transport is active. The
|
|
122
|
-
* FinalizationRegistry callback itself does not run during this process's
|
|
123
|
-
* exit teardown — so we deliberately don't try to drain it. */
|
|
124
|
-
let perRequestFinalizationRegistry;
|
|
125
|
-
function getPerRequestFinalizationRegistry() {
|
|
126
|
-
perRequestFinalizationRegistry ??= new FinalizationRegistry((kind) => {
|
|
127
|
-
getPerRequestFinalizedCounter().add(1, { kind });
|
|
128
|
-
});
|
|
129
|
-
return perRequestFinalizationRegistry;
|
|
130
|
-
}
|
|
131
|
-
function trackPerRequestInstance(obj, kind) {
|
|
132
|
-
getPerRequestCreatedCounter().add(1, { kind });
|
|
133
|
-
getPerRequestFinalizationRegistry().register(obj, kind);
|
|
134
|
-
}
|
|
135
|
-
/** Await a promise with a bounded timeout. Rejects with the timeout error
|
|
136
|
-
* if the promise doesn't settle first. */
|
|
137
|
-
function withTimeout(p, ms, label) {
|
|
138
|
-
let timer;
|
|
139
|
-
return Promise.race([
|
|
140
|
-
p,
|
|
141
|
-
new Promise((_, reject) => {
|
|
142
|
-
timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
143
|
-
}),
|
|
144
|
-
]).finally(() => {
|
|
145
|
-
if (timer !== undefined)
|
|
146
|
-
clearTimeout(timer);
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
/** Closes the per-request transport and server under a bounded timeout.
|
|
150
|
-
* Runs the two closes in parallel so one hanging close doesn't delay the
|
|
151
|
-
* other, and records each failure to `mcp.http.close_failures` with
|
|
152
|
-
* `surface` and `trigger` tags. */
|
|
153
|
-
async function closePerRequestInstances(transport, server, sessionId, requestContext, trigger) {
|
|
154
|
-
const tasks = [['transport', () => transport.close()]];
|
|
155
|
-
if (server)
|
|
156
|
-
tasks.push(['server', () => server.close()]);
|
|
157
|
-
await Promise.all(tasks.map(async ([surface, close]) => {
|
|
158
|
-
try {
|
|
159
|
-
await withTimeout(close(), PER_REQUEST_CLOSE_TIMEOUT_MS, `${surface}.close`);
|
|
160
|
-
}
|
|
161
|
-
catch (err) {
|
|
162
|
-
getCloseFailureCounter().add(1, { surface, trigger });
|
|
163
|
-
logger.warning(`Failed to close ${surface} (trigger=${trigger})`, {
|
|
164
|
-
...requestContext,
|
|
165
|
-
sessionId,
|
|
166
|
-
error: err instanceof Error ? err.message : String(err),
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
}));
|
|
170
|
-
}
|
|
171
49
|
/** Reads at most one stream chunk beyond `maxBytes`, cancelling as soon as the
|
|
172
50
|
* limit is crossed. Fetch body chunks are not size-bounded by the consumer, so
|
|
173
51
|
* the final chunk may itself be larger than the remaining allowance. */
|
|
@@ -218,10 +96,7 @@ async function readBodyWithinLimit(request, maxBytes) {
|
|
|
218
96
|
*/
|
|
219
97
|
export async function createHttpApp(serverFactory, parentContext, manifest) {
|
|
220
98
|
const app = new Hono();
|
|
221
|
-
const transportContext = {
|
|
222
|
-
...parentContext,
|
|
223
|
-
component: 'HttpTransportSetup',
|
|
224
|
-
};
|
|
99
|
+
const transportContext = withExtra(parentContext, { component: 'HttpTransportSetup' });
|
|
225
100
|
// Initialize session store for stateful mode.
|
|
226
101
|
// 'auto' resolves to stateful for HTTP (per MCP spec conformance).
|
|
227
102
|
const isStateful = config.mcpSessionMode === 'stateful' || config.mcpSessionMode === 'auto';
|
|
@@ -298,13 +173,12 @@ export async function createHttpApp(serverFactory, parentContext, manifest) {
|
|
|
298
173
|
if (!isAllowed) {
|
|
299
174
|
const requestContext = requestContextService.createRequestContext({
|
|
300
175
|
operation: 'HttpOriginGuard',
|
|
301
|
-
component: 'HttpTransport',
|
|
176
|
+
additionalContext: { component: 'HttpTransport' },
|
|
302
177
|
});
|
|
303
|
-
logger.warning('Rejected request with invalid Origin header', {
|
|
304
|
-
...requestContext,
|
|
178
|
+
logger.warning('Rejected request with invalid Origin header', withExtra(requestContext, {
|
|
305
179
|
origin,
|
|
306
180
|
allowedOrigins: explicitOrigins ?? 'loopback-only',
|
|
307
|
-
});
|
|
181
|
+
}));
|
|
308
182
|
return c.json({ error: 'Invalid origin. DNS rebinding protection.' }, 403);
|
|
309
183
|
}
|
|
310
184
|
}
|
|
@@ -329,13 +203,9 @@ export async function createHttpApp(serverFactory, parentContext, manifest) {
|
|
|
329
203
|
const rejectOversized = (bytes) => {
|
|
330
204
|
const requestContext = requestContextService.createRequestContext({
|
|
331
205
|
operation: 'HttpBodyLimit',
|
|
332
|
-
component: 'HttpTransport',
|
|
333
|
-
});
|
|
334
|
-
logger.warning('Rejected request exceeding body size limit', {
|
|
335
|
-
...requestContext,
|
|
336
|
-
bytes,
|
|
337
|
-
maxBodyBytes,
|
|
206
|
+
additionalContext: { component: 'HttpTransport' },
|
|
338
207
|
});
|
|
208
|
+
logger.warning('Rejected request exceeding body size limit', withExtra(requestContext, { bytes, maxBodyBytes }));
|
|
339
209
|
return c.json({
|
|
340
210
|
error: `Request body exceeds the ${maxBodyBytes}-byte limit (configurable via MCP_HTTP_MAX_BODY_BYTES).`,
|
|
341
211
|
}, 413);
|
|
@@ -488,246 +358,159 @@ export async function createHttpApp(serverFactory, parentContext, manifest) {
|
|
|
488
358
|
subject: authInfo.subject,
|
|
489
359
|
}).filter(([, v]) => v != null));
|
|
490
360
|
}
|
|
491
|
-
//
|
|
492
|
-
//
|
|
493
|
-
//
|
|
494
|
-
|
|
361
|
+
// DELETE terminates a session. Ownership is validated here, before the SDK
|
|
362
|
+
// transport is allowed to tear the session down; the transport itself owns
|
|
363
|
+
// the spec-shaped response and fires `onsessionclosed`.
|
|
364
|
+
// https://modelcontextprotocol.io/specification/2026-07-28/basic/transports
|
|
365
|
+
app.delete(config.mcpHttpEndpointPath, async (c, next) => {
|
|
495
366
|
const requestContext = requestContextService.createRequestContext({
|
|
496
367
|
operation: 'HttpSessionTermination',
|
|
497
|
-
component: 'HttpTransport',
|
|
368
|
+
additionalContext: { component: 'HttpTransport' },
|
|
498
369
|
});
|
|
499
370
|
const sessionId = c.req.header('mcp-session-id');
|
|
371
|
+
// Stateless mode has no session to terminate — the stateless handler
|
|
372
|
+
// answers with the spec's 405.
|
|
373
|
+
if (!sessionStore)
|
|
374
|
+
return await next();
|
|
500
375
|
if (!sessionId) {
|
|
501
376
|
logger.warning('DELETE request without session ID', requestContext);
|
|
502
377
|
return c.json({ error: 'Mcp-Session-Id header required' }, 400);
|
|
503
378
|
}
|
|
504
|
-
logger.info('Session termination requested', {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
});
|
|
508
|
-
// For stateless mode or if session management is disabled, return 405
|
|
509
|
-
if (config.mcpSessionMode === 'stateless' || !sessionStore) {
|
|
510
|
-
return c.json({ error: 'Session termination not supported in stateless mode' }, 405);
|
|
511
|
-
}
|
|
512
|
-
// SECURITY: Validate session ownership before termination
|
|
513
|
-
const sessionIdentity = extractSessionIdentity();
|
|
514
|
-
if (!sessionStore.isValidForIdentity(sessionId, sessionIdentity)) {
|
|
379
|
+
logger.info('Session termination requested', { ...requestContext, sessionId });
|
|
380
|
+
// SECURITY: validate session ownership before termination.
|
|
381
|
+
if (!sessionStore.isValidForIdentity(sessionId, extractSessionIdentity())) {
|
|
515
382
|
logger.warning('Session termination rejected - ownership validation failed', {
|
|
516
383
|
...requestContext,
|
|
517
384
|
sessionId,
|
|
518
|
-
requestTenant: sessionIdentity?.tenantId,
|
|
519
|
-
requestClient: sessionIdentity?.clientId,
|
|
520
385
|
});
|
|
521
386
|
return c.json({ error: 'Session not found or access denied' }, 404);
|
|
522
387
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
388
|
+
const connection = sessionStore.getConnection(sessionId);
|
|
389
|
+
if (!connection) {
|
|
390
|
+
// Identity-valid but no live connection (already torn down). Drop the
|
|
391
|
+
// record and answer exactly as the transport does below — one status and
|
|
392
|
+
// one body shape for one operation.
|
|
393
|
+
await sessionStore.terminate(sessionId);
|
|
394
|
+
return c.body(null, 200);
|
|
395
|
+
}
|
|
396
|
+
return await connection.transport.handleRequest(c.req.raw);
|
|
530
397
|
});
|
|
398
|
+
// -------------------------------------------------------------------------
|
|
531
399
|
// JSON-RPC over HTTP (Streamable)
|
|
400
|
+
// -------------------------------------------------------------------------
|
|
401
|
+
/** Validated auth info for pass-through to the SDK's `ctx.http.authInfo`. */
|
|
402
|
+
const currentAuthInfo = () => authContext.getStore()?.authInfo;
|
|
403
|
+
// The modern (2026-07-28) leg. In stateless mode the same handler also serves
|
|
404
|
+
// 2025-era traffic per request (`legacy: 'stateless'`, the default); in
|
|
405
|
+
// stateful mode it is strict and the sessionful arm below owns 2025.
|
|
406
|
+
const handler = createMcpHandler(serverFactory, {
|
|
407
|
+
...(isStateful && { legacy: 'reject' }),
|
|
408
|
+
onerror: (error) => {
|
|
409
|
+
logger.debug(`MCP handler reported: ${error.message}`, transportContext);
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
/** Serves one 2025-era request on the sessionful arm. */
|
|
413
|
+
const handleLegacySessionful = async (store, request, parsedBody, requestContext) => {
|
|
414
|
+
const authInfo = currentAuthInfo();
|
|
415
|
+
const identity = extractSessionIdentity();
|
|
416
|
+
const providedSessionId = request.headers.get('mcp-session-id') ?? undefined;
|
|
417
|
+
const options = {
|
|
418
|
+
...(parsedBody !== undefined && { parsedBody }),
|
|
419
|
+
...(authInfo && { authInfo }),
|
|
420
|
+
};
|
|
421
|
+
if (providedSessionId) {
|
|
422
|
+
// SECURITY: identity binding is checked before the session is reachable,
|
|
423
|
+
// so a stolen session ID alone cannot resume someone else's session.
|
|
424
|
+
if (!store.isValidForIdentity(providedSessionId, identity)) {
|
|
425
|
+
logger.warning('Session validation failed - invalid or hijacked session', {
|
|
426
|
+
...requestContext,
|
|
427
|
+
sessionId: providedSessionId,
|
|
428
|
+
});
|
|
429
|
+
return Response.json({ error: 'Session not found or expired' }, { status: 404 });
|
|
430
|
+
}
|
|
431
|
+
const connection = store.getConnection(providedSessionId);
|
|
432
|
+
if (!connection) {
|
|
433
|
+
return Response.json({ error: 'Session not found or expired' }, { status: 404 });
|
|
434
|
+
}
|
|
435
|
+
return await connection.transport.handleRequest(request, options);
|
|
436
|
+
}
|
|
437
|
+
// No session yet: this is an `initialize` (anything else is rejected by the
|
|
438
|
+
// SDK transport with the spec's 400). Capacity is checked before the
|
|
439
|
+
// instance is built so a saturated server never allocates one.
|
|
440
|
+
store.assertCapacity();
|
|
441
|
+
const server = await serverFactory({
|
|
442
|
+
era: 'legacy',
|
|
443
|
+
...(authInfo && { authInfo }),
|
|
444
|
+
requestInfo: request,
|
|
445
|
+
});
|
|
446
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
447
|
+
sessionIdGenerator: generateSecureSessionId,
|
|
448
|
+
onsessioninitialized: (sessionId) => {
|
|
449
|
+
store.register(sessionId, { server, transport }, identity);
|
|
450
|
+
logger.debug('Session initialized', { ...requestContext, sessionId });
|
|
451
|
+
},
|
|
452
|
+
onsessionclosed: (sessionId) => store.terminate(sessionId),
|
|
453
|
+
});
|
|
454
|
+
await server.connect(transport);
|
|
455
|
+
try {
|
|
456
|
+
const response = await transport.handleRequest(request, options);
|
|
457
|
+
// A handshake the transport refused never mints a session, so the
|
|
458
|
+
// instance pair it would have owned has no owner — close it here.
|
|
459
|
+
if (!transport.sessionId)
|
|
460
|
+
await closeConnection({ server, transport });
|
|
461
|
+
return response;
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
await closeConnection({ server, transport });
|
|
465
|
+
throw error;
|
|
466
|
+
}
|
|
467
|
+
};
|
|
532
468
|
app.all(config.mcpHttpEndpointPath, async (c) => {
|
|
533
469
|
const requestContext = requestContextService.createRequestContext({
|
|
534
470
|
operation: 'HttpRpcRequest',
|
|
535
|
-
component: 'HttpTransport',
|
|
536
|
-
});
|
|
537
|
-
const protocolVersion = c.req.header('mcp-protocol-version') ?? '2025-03-26';
|
|
538
|
-
logger.debug('Handling MCP request.', {
|
|
539
|
-
...requestContext,
|
|
540
|
-
path: c.req.path,
|
|
541
|
-
method: c.req.method,
|
|
542
|
-
protocolVersion,
|
|
471
|
+
additionalContext: { component: 'HttpTransport' },
|
|
543
472
|
});
|
|
544
|
-
|
|
545
|
-
//
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
supportedVersions,
|
|
553
|
-
});
|
|
554
|
-
return c.json({
|
|
555
|
-
error: 'Unsupported MCP protocol version',
|
|
556
|
-
protocolVersion,
|
|
557
|
-
supportedVersions,
|
|
558
|
-
}, 400);
|
|
473
|
+
logger.debug('Handling MCP request.', withExtra(requestContext, { path: c.req.path, method: c.req.method }));
|
|
474
|
+
// Media type before body: a POST whose Content-Type is not JSON is a 415,
|
|
475
|
+
// not a parse error, and the SDK answers it that way. Parsing first turns
|
|
476
|
+
// even a well-formed body into a misleading `-32700`, so hand those
|
|
477
|
+
// straight to the handler and let it produce the canonical response.
|
|
478
|
+
if (c.req.method === 'POST' && !isJsonContentType(c.req.header('content-type'))) {
|
|
479
|
+
const authInfo = currentAuthInfo();
|
|
480
|
+
return await handler.fetch(c.req.raw, { ...(authInfo && { authInfo }) });
|
|
559
481
|
}
|
|
560
|
-
|
|
561
|
-
//
|
|
562
|
-
//
|
|
563
|
-
|
|
564
|
-
let parsedRpcBody;
|
|
565
|
-
let rpcMessages = [];
|
|
482
|
+
// Read the POST body once, from the cache the body-limit guard may have
|
|
483
|
+
// seeded. Every downstream consumer takes it as `parsedBody` because the raw
|
|
484
|
+
// stream is not guaranteed re-readable after that guard runs.
|
|
485
|
+
let parsedBody;
|
|
566
486
|
if (c.req.method === 'POST') {
|
|
567
487
|
try {
|
|
568
|
-
|
|
569
|
-
parsedRpcBody = rawBody;
|
|
570
|
-
const messages = Array.isArray(rawBody) ? rawBody : [rawBody];
|
|
571
|
-
rpcMessages = messages.filter((message) => typeof message === 'object' && message !== null);
|
|
488
|
+
parsedBody = await c.req.json();
|
|
572
489
|
}
|
|
573
490
|
catch {
|
|
574
|
-
// Malformed
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const initializeMessage = rpcMessages.find((message) => message.method === 'initialize');
|
|
578
|
-
const declaredCapabilities = initializeMessage?.params?.capabilities;
|
|
579
|
-
const initializeCapabilities = typeof declaredCapabilities === 'object' && declaredCapabilities !== null
|
|
580
|
-
? declaredCapabilities
|
|
581
|
-
: undefined;
|
|
582
|
-
// Extract identity from auth context (if auth is enabled)
|
|
583
|
-
// This MUST happen before session validation for security
|
|
584
|
-
const sessionIdentity = extractSessionIdentity();
|
|
585
|
-
// MCP Spec 2025-06-18: Return 404 for invalid/terminated sessions
|
|
586
|
-
// https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#session-management
|
|
587
|
-
// SECURITY: Validate session WITH identity binding to prevent hijacking
|
|
588
|
-
if (sessionStore &&
|
|
589
|
-
providedSessionId &&
|
|
590
|
-
!sessionStore.isValidForIdentity(providedSessionId, sessionIdentity)) {
|
|
591
|
-
logger.warning('Session validation failed - invalid or hijacked session', {
|
|
592
|
-
...requestContext,
|
|
593
|
-
sessionId: providedSessionId,
|
|
594
|
-
requestTenant: sessionIdentity?.tenantId,
|
|
595
|
-
requestClient: sessionIdentity?.clientId,
|
|
596
|
-
});
|
|
597
|
-
return c.json({ error: 'Session not found or expired' }, 404);
|
|
598
|
-
}
|
|
599
|
-
// MCP Spec 2025-06-18: stateful sessions require Mcp-Session-Id on all
|
|
600
|
-
// non-initialize requests. Without this gate, the SDK processes the RPC
|
|
601
|
-
// on a fresh per-request McpServer (no protocol state to reject against)
|
|
602
|
-
// and the session store mints an uninitialized session on response.ok.
|
|
603
|
-
if (sessionStore && !providedSessionId && !initializeMessage) {
|
|
604
|
-
logger.warning('Rejected non-initialize request without session in stateful mode', {
|
|
605
|
-
...requestContext,
|
|
606
|
-
method: c.req.method,
|
|
607
|
-
});
|
|
608
|
-
return c.json({
|
|
609
|
-
error: 'Bad Request: Mcp-Session-Id header is required. Send an initialize request first.',
|
|
610
|
-
}, 400);
|
|
611
|
-
}
|
|
612
|
-
const sessionId = providedSessionId ?? generateSecureSessionId();
|
|
613
|
-
// A response to a server-initiated request arrives on a new POST and would
|
|
614
|
-
// otherwise be delivered to a fresh SDK Server with no matching response
|
|
615
|
-
// handler. Route recognized responses through the durable session registry
|
|
616
|
-
// and strip them before normal SDK dispatch. An emptied batch still goes to
|
|
617
|
-
// the transport rather than short-circuiting here: it owns the Accept and
|
|
618
|
-
// Content-Type checks, and returns the same 202 every other
|
|
619
|
-
// response/notification POST does.
|
|
620
|
-
let transportBody = parsedRpcBody;
|
|
621
|
-
if (sessionStore && parsedRpcBody !== undefined) {
|
|
622
|
-
const messages = Array.isArray(parsedRpcBody) ? parsedRpcBody : [parsedRpcBody];
|
|
623
|
-
const unrouted = messages.filter((message) => {
|
|
624
|
-
if (!isJSONRPCResultResponse(message) && !isJSONRPCErrorResponse(message))
|
|
625
|
-
return true;
|
|
626
|
-
return !sessionStore.routeServerResponse(sessionId, message);
|
|
627
|
-
});
|
|
628
|
-
if (unrouted.length !== messages.length)
|
|
629
|
-
transportBody = unrouted;
|
|
630
|
-
}
|
|
631
|
-
// The cancellation notification is handled by a different SDK Server
|
|
632
|
-
// instance than the original request. Mirror it into the durable session
|
|
633
|
-
// registry before passing it to the SDK so the original handler's merged
|
|
634
|
-
// `ctx.signal` is aborted without sharing Server instances across clients.
|
|
635
|
-
if (sessionStore) {
|
|
636
|
-
for (const message of rpcMessages) {
|
|
637
|
-
if (message.method !== 'notifications/cancelled')
|
|
638
|
-
continue;
|
|
639
|
-
const params = message.params;
|
|
640
|
-
const requestId = params?.requestId;
|
|
641
|
-
if (typeof requestId !== 'string' && typeof requestId !== 'number')
|
|
642
|
-
continue;
|
|
643
|
-
const reason = params?.reason;
|
|
644
|
-
sessionStore.cancelRequest(sessionId, requestId, typeof reason === 'string' ? reason : undefined);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
const transport = new McpSessionTransport(sessionId, sessionStore);
|
|
648
|
-
trackPerRequestInstance(transport, 'transport');
|
|
649
|
-
let server;
|
|
650
|
-
let closePromise;
|
|
651
|
-
const closeOnce = (trigger) => {
|
|
652
|
-
closePromise ??= closePerRequestInstances(transport, server, sessionId, requestContext, trigger);
|
|
653
|
-
return closePromise;
|
|
654
|
-
};
|
|
655
|
-
const handleRpc = async () => {
|
|
656
|
-
// SDK 1.26.0: Protocol.connect() throws if already connected.
|
|
657
|
-
// Create a fresh McpServer per request to prevent cross-client data leaks.
|
|
658
|
-
// See GHSA-345p-7cg4-v4c7.
|
|
659
|
-
server = await serverFactory(sessionStore?.createProtocolSessionHooks(sessionId));
|
|
660
|
-
trackPerRequestInstance(server, 'server');
|
|
661
|
-
await server.connect(transport);
|
|
662
|
-
const response = await transport.handleRequest(c, transportBody);
|
|
663
|
-
if (response) {
|
|
664
|
-
// Only register the session in the store AFTER a successful response.
|
|
665
|
-
// This avoids minting sessions for requests that fail protocol
|
|
666
|
-
// validation (e.g. tools/list without prior initialize).
|
|
667
|
-
if (sessionStore && response.ok) {
|
|
668
|
-
sessionStore.getOrCreate(sessionId, sessionIdentity);
|
|
669
|
-
if (initializeCapabilities) {
|
|
670
|
-
sessionStore.setClientCapabilities(sessionId, initializeCapabilities);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
// MCP Spec 2025-06-18: For stateful sessions, return Mcp-Session-Id header
|
|
674
|
-
// in InitializeResponse (and all subsequent responses).
|
|
675
|
-
// 'auto' resolves to stateful for HTTP, matching the SessionStore creation above.
|
|
676
|
-
if (isStateful && response.ok) {
|
|
677
|
-
response.headers.set('Mcp-Session-Id', sessionId);
|
|
678
|
-
logger.debug('Added Mcp-Session-Id header to response', {
|
|
679
|
-
...requestContext,
|
|
680
|
-
sessionId,
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
// Non-SSE responses (POST/202): close immediately via queueMicrotask.
|
|
684
|
-
// SSE streams must stay open until the client disconnects — closing now
|
|
685
|
-
// would abort the ReadableStream before Hono can write to it (see
|
|
686
|
-
// GHSA-345p-7cg4-v4c7 above) — so cleanup is bound to the request's
|
|
687
|
-
// AbortSignal instead. `@hono/mcp` `stream.onAbort` only clears its
|
|
688
|
-
// internal stream map; it never fires `transport.close()` or `onclose`,
|
|
689
|
-
// and real clients almost always disconnect ungracefully (no DELETE).
|
|
690
|
-
// Without this hook every SSE GET leaks its per-request McpServer +
|
|
691
|
-
// Transport pair (issue #50). `c.req.raw.signal` aborts on client
|
|
692
|
-
// disconnect across runtimes (`@hono/node-server`, modern Bun, Workers).
|
|
693
|
-
const isSSE = response.headers.get('content-type')?.includes('text/event-stream');
|
|
694
|
-
if (!isSSE) {
|
|
695
|
-
queueMicrotask(() => {
|
|
696
|
-
void closeOnce('success');
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
else {
|
|
700
|
-
const sseCleanup = () => {
|
|
701
|
-
void closeOnce('sse-abort');
|
|
702
|
-
};
|
|
703
|
-
const reqSignal = c.req.raw.signal;
|
|
704
|
-
if (reqSignal.aborted) {
|
|
705
|
-
queueMicrotask(sseCleanup);
|
|
706
|
-
}
|
|
707
|
-
else {
|
|
708
|
-
reqSignal.addEventListener('abort', sseCleanup, { once: true });
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
return response;
|
|
491
|
+
// Malformed JSON never reaches classification — answer the spec's parse
|
|
492
|
+
// error directly rather than letting an unreadable body reach a transport.
|
|
493
|
+
return c.json({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } }, 400);
|
|
712
494
|
}
|
|
713
|
-
queueMicrotask(() => {
|
|
714
|
-
void closeOnce('success');
|
|
715
|
-
});
|
|
716
|
-
return c.body(null, 204);
|
|
717
|
-
};
|
|
718
|
-
// Auth context is already populated by the middleware's authContext.run().
|
|
719
|
-
// ALS propagates through all async continuations in this handler.
|
|
720
|
-
try {
|
|
721
|
-
return await handleRpc();
|
|
722
495
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
// closure also protects against any cleanup already scheduled elsewhere.
|
|
726
|
-
await closeOnce('error');
|
|
727
|
-
throw err instanceof Error ? err : new Error(String(err));
|
|
496
|
+
if (sessionStore && (await isLegacyRequest(c.req.raw, parsedBody))) {
|
|
497
|
+
return await handleLegacySessionful(sessionStore, c.req.raw, parsedBody, requestContext);
|
|
728
498
|
}
|
|
499
|
+
const authInfo = currentAuthInfo();
|
|
500
|
+
return await handler.fetch(c.req.raw, {
|
|
501
|
+
...(parsedBody !== undefined && { parsedBody }),
|
|
502
|
+
...(authInfo && { authInfo }),
|
|
503
|
+
});
|
|
729
504
|
});
|
|
730
505
|
logger.info('Hono application setup complete.', transportContext);
|
|
731
|
-
return {
|
|
506
|
+
return {
|
|
507
|
+
app,
|
|
508
|
+
handler,
|
|
509
|
+
sessionStore,
|
|
510
|
+
close: async () => {
|
|
511
|
+
await handler.close();
|
|
512
|
+
await sessionStore?.destroy();
|
|
513
|
+
},
|
|
514
|
+
};
|
|
732
515
|
}
|
|
733
516
|
//# sourceMappingURL=httpTransport.js.map
|