effect 4.0.2 → 4.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +24 -0
- package/CLAUDE.md +24 -0
- package/ai-docs/package.json +2 -2
- package/ai-docs/src/52_graphql/10_basics.ts +111 -0
- package/ai-docs/src/52_graphql/20_testing.ts +45 -0
- package/ai-docs/src/52_graphql/30_batching.ts +116 -0
- package/ai-docs/src/52_graphql/40_aliased-batching.ts +122 -0
- package/ai-docs/src/52_graphql/50_errors-and-retries.ts +74 -0
- package/ai-docs/src/52_graphql/index.md +6 -0
- package/dist/Context.d.ts.map +1 -1
- package/dist/Context.js +12 -3
- package/dist/Context.js.map +1 -1
- package/dist/Crypto.d.ts +33 -5
- package/dist/Crypto.d.ts.map +1 -1
- package/dist/Crypto.js +8 -2
- package/dist/Crypto.js.map +1 -1
- package/dist/DateTime.d.ts +9 -6
- package/dist/DateTime.d.ts.map +1 -1
- package/dist/DateTime.js +9 -6
- package/dist/DateTime.js.map +1 -1
- package/dist/Duration.d.ts +6 -6
- package/dist/Duration.d.ts.map +1 -1
- package/dist/Duration.js +27 -8
- package/dist/Duration.js.map +1 -1
- package/dist/FileSystem.d.ts +44 -2
- package/dist/FileSystem.d.ts.map +1 -1
- package/dist/FileSystem.js.map +1 -1
- package/dist/RequestResolver.d.ts.map +1 -1
- package/dist/RequestResolver.js +4 -10
- package/dist/RequestResolver.js.map +1 -1
- package/dist/Scheduler.d.ts +9 -0
- package/dist/Scheduler.d.ts.map +1 -1
- package/dist/Scheduler.js +9 -0
- package/dist/Scheduler.js.map +1 -1
- package/dist/Schema.d.ts +481 -1
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +362 -4
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaGetter.d.ts +4 -0
- package/dist/SchemaGetter.d.ts.map +1 -1
- package/dist/SchemaGetter.js +52 -33
- package/dist/SchemaGetter.js.map +1 -1
- package/dist/Sink.d.ts.map +1 -1
- package/dist/Sink.js +38 -17
- package/dist/Sink.js.map +1 -1
- package/dist/Stdio.d.ts +7 -1
- package/dist/Stdio.d.ts.map +1 -1
- package/dist/Stdio.js +1 -0
- package/dist/Stdio.js.map +1 -1
- package/dist/Stream.d.ts +22 -11
- package/dist/Stream.d.ts.map +1 -1
- package/dist/Stream.js +21 -6
- package/dist/Stream.js.map +1 -1
- package/dist/Utils.js +8 -3
- package/dist/Utils.js.map +1 -1
- package/dist/ai/AnthropicStructuredOutput.js +0 -2
- package/dist/ai/AnthropicStructuredOutput.js.map +1 -1
- package/dist/ai/Chat.d.ts +2 -2
- package/dist/ai/Chat.d.ts.map +1 -1
- package/dist/ai/LanguageModel.d.ts +38 -7
- package/dist/ai/LanguageModel.d.ts.map +1 -1
- package/dist/ai/LanguageModel.js +25 -7
- package/dist/ai/LanguageModel.js.map +1 -1
- package/dist/ai/McpServer.d.ts +61 -0
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +196 -44
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/Prompt.d.ts.map +1 -1
- package/dist/ai/Prompt.js +28 -5
- package/dist/ai/Prompt.js.map +1 -1
- package/dist/ai/Response.d.ts +4 -3
- package/dist/ai/Response.d.ts.map +1 -1
- package/dist/ai/Response.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2026_07_28.js +5 -0
- package/dist/ai/internal/mcpProtocol/v2026_07_28.js.map +1 -1
- package/dist/ai/internal/mcpProtocol.js.map +1 -1
- package/dist/ai/internal/mcpRuntime.js +1 -0
- package/dist/ai/internal/mcpRuntime.js.map +1 -1
- package/dist/cli/CliOutput.d.ts +27 -0
- package/dist/cli/CliOutput.d.ts.map +1 -1
- package/dist/cli/CliOutput.js +33 -10
- package/dist/cli/CliOutput.js.map +1 -1
- package/dist/cli/Command.d.ts.map +1 -1
- package/dist/cli/Command.js +6 -3
- package/dist/cli/Command.js.map +1 -1
- package/dist/cli/Completions.d.ts +3 -3
- package/dist/cli/Completions.d.ts.map +1 -1
- package/dist/cli/Completions.js +8 -5
- package/dist/cli/Completions.js.map +1 -1
- package/dist/cli/GlobalFlag.d.ts +5 -4
- package/dist/cli/GlobalFlag.d.ts.map +1 -1
- package/dist/cli/GlobalFlag.js +3 -3
- package/dist/cli/GlobalFlag.js.map +1 -1
- package/dist/cli/Prompt.d.ts +17 -1
- package/dist/cli/Prompt.d.ts.map +1 -1
- package/dist/cli/Prompt.js +119 -114
- package/dist/cli/Prompt.js.map +1 -1
- package/dist/cli/internal/ansi.d.ts +0 -1
- package/dist/cli/internal/ansi.d.ts.map +1 -1
- package/dist/cli/internal/ansi.js +20 -3
- package/dist/cli/internal/ansi.js.map +1 -1
- package/dist/cli/internal/completions/powershell.d.ts +2 -0
- package/dist/cli/internal/completions/powershell.d.ts.map +1 -0
- package/dist/cli/internal/completions/powershell.js +273 -0
- package/dist/cli/internal/completions/powershell.js.map +1 -0
- package/dist/cli/internal/completions/zsh.js +3 -2
- package/dist/cli/internal/completions/zsh.js.map +1 -1
- package/dist/cli/internal/wizard.d.ts +3 -3
- package/dist/cli/internal/wizard.d.ts.map +1 -1
- package/dist/cli/internal/wizard.js +21 -18
- package/dist/cli/internal/wizard.js.map +1 -1
- package/dist/cluster/ClusterSchema.d.ts +28 -0
- package/dist/cluster/ClusterSchema.d.ts.map +1 -1
- package/dist/cluster/ClusterSchema.js +30 -0
- package/dist/cluster/ClusterSchema.js.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.js +56 -26
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/MessageStorage.d.ts +18 -3
- package/dist/cluster/MessageStorage.d.ts.map +1 -1
- package/dist/cluster/MessageStorage.js +31 -11
- package/dist/cluster/MessageStorage.js.map +1 -1
- package/dist/cluster/Runners.d.ts.map +1 -1
- package/dist/cluster/Runners.js +8 -2
- package/dist/cluster/Runners.js.map +1 -1
- package/dist/cluster/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js +187 -66
- package/dist/cluster/Sharding.js.map +1 -1
- package/dist/cluster/SqlMessageStorage.d.ts +59 -19
- package/dist/cluster/SqlMessageStorage.d.ts.map +1 -1
- package/dist/cluster/SqlMessageStorage.js +84 -55
- package/dist/cluster/SqlMessageStorage.js.map +1 -1
- package/dist/cluster/SqlRunnerStorage.d.ts +68 -19
- package/dist/cluster/SqlRunnerStorage.d.ts.map +1 -1
- package/dist/cluster/SqlRunnerStorage.js +191 -113
- package/dist/cluster/SqlRunnerStorage.js.map +1 -1
- package/dist/cluster/internal/entityManager.js +104 -37
- package/dist/cluster/internal/entityManager.js.map +1 -1
- package/dist/encoding/Sse.d.ts +6 -0
- package/dist/encoding/Sse.d.ts.map +1 -1
- package/dist/encoding/Sse.js +62 -70
- package/dist/encoding/Sse.js.map +1 -1
- package/dist/graphql/GraphQL.d.ts +349 -0
- package/dist/graphql/GraphQL.d.ts.map +1 -0
- package/dist/graphql/GraphQL.js +207 -0
- package/dist/graphql/GraphQL.js.map +1 -0
- package/dist/graphql/GraphQLClient.d.ts +562 -0
- package/dist/graphql/GraphQLClient.d.ts.map +1 -0
- package/dist/graphql/GraphQLClient.js +498 -0
- package/dist/graphql/GraphQLClient.js.map +1 -0
- package/dist/graphql/GraphQLClientError.d.ts +445 -0
- package/dist/graphql/GraphQLClientError.d.ts.map +1 -0
- package/dist/graphql/GraphQLClientError.js +479 -0
- package/dist/graphql/GraphQLClientError.js.map +1 -0
- package/dist/graphql/GraphQLGroup.d.ts +118 -0
- package/dist/graphql/GraphQLGroup.d.ts.map +1 -0
- package/dist/graphql/GraphQLGroup.js +109 -0
- package/dist/graphql/GraphQLGroup.js.map +1 -0
- package/dist/graphql/GraphQLMiddleware.d.ts +249 -0
- package/dist/graphql/GraphQLMiddleware.d.ts.map +1 -0
- package/dist/graphql/GraphQLMiddleware.js +130 -0
- package/dist/graphql/GraphQLMiddleware.js.map +1 -0
- package/dist/graphql/GraphQLProtocol.d.ts +731 -0
- package/dist/graphql/GraphQLProtocol.d.ts.map +1 -0
- package/dist/graphql/GraphQLProtocol.js +719 -0
- package/dist/graphql/GraphQLProtocol.js.map +1 -0
- package/dist/graphql/GraphQLResolver.d.ts +453 -0
- package/dist/graphql/GraphQLResolver.d.ts.map +1 -0
- package/dist/graphql/GraphQLResolver.js +552 -0
- package/dist/graphql/GraphQLResolver.js.map +1 -0
- package/dist/graphql/index.d.ts +40 -0
- package/dist/graphql/index.d.ts.map +1 -0
- package/dist/graphql/index.js +41 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/http/FindMyWay/internal/router.js +3 -6
- package/dist/http/FindMyWay/internal/router.js.map +1 -1
- package/dist/http/HttpClient.d.ts +21 -0
- package/dist/http/HttpClient.d.ts.map +1 -1
- package/dist/http/HttpClient.js +63 -12
- package/dist/http/HttpClient.js.map +1 -1
- package/dist/http/HttpEffect.d.ts.map +1 -1
- package/dist/http/HttpEffect.js +14 -7
- package/dist/http/HttpEffect.js.map +1 -1
- package/dist/http/HttpMiddleware.d.ts.map +1 -1
- package/dist/http/HttpMiddleware.js +31 -32
- package/dist/http/HttpMiddleware.js.map +1 -1
- package/dist/http/HttpRouter.d.ts +35 -0
- package/dist/http/HttpRouter.d.ts.map +1 -1
- package/dist/http/HttpRouter.js +35 -0
- package/dist/http/HttpRouter.js.map +1 -1
- package/dist/http/HttpServerResponse.d.ts +8 -6
- package/dist/http/HttpServerResponse.d.ts.map +1 -1
- package/dist/http/HttpServerResponse.js +27 -16
- package/dist/http/HttpServerResponse.js.map +1 -1
- package/dist/http/HttpTraceContext.d.ts.map +1 -1
- package/dist/http/HttpTraceContext.js +4 -2
- package/dist/http/HttpTraceContext.js.map +1 -1
- package/dist/http/Multipart.d.ts.map +1 -1
- package/dist/http/Multipart.js +15 -1
- package/dist/http/Multipart.js.map +1 -1
- package/dist/http-api/HttpApiScalar.d.ts +5 -0
- package/dist/http-api/HttpApiScalar.d.ts.map +1 -1
- package/dist/http-api/HttpApiScalar.js.map +1 -1
- package/dist/internal/dateTime.js +40 -35
- package/dist/internal/dateTime.js.map +1 -1
- package/dist/internal/effect.js +19 -9
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/graphql.d.ts +2 -0
- package/dist/internal/graphql.d.ts.map +1 -0
- package/dist/internal/graphql.js +195 -0
- package/dist/internal/graphql.js.map +1 -0
- package/dist/internal/graphqlHeaders.d.ts +4 -0
- package/dist/internal/graphqlHeaders.d.ts.map +1 -0
- package/dist/internal/graphqlHeaders.js +11 -0
- package/dist/internal/graphqlHeaders.js.map +1 -0
- package/dist/internal/request.js +20 -1
- package/dist/internal/request.js.map +1 -1
- package/dist/internal/schema/toCodeDocument.js +4 -1
- package/dist/internal/schema/toCodeDocument.js.map +1 -1
- package/dist/internal/schema/toJsonSchemaDocument.js +29 -5
- package/dist/internal/schema/toJsonSchemaDocument.js.map +1 -1
- package/dist/internal/version.js +1 -1
- package/dist/net/AddressResolver.d.ts +145 -0
- package/dist/net/AddressResolver.d.ts.map +1 -0
- package/dist/net/AddressResolver.js +114 -0
- package/dist/net/AddressResolver.js.map +1 -0
- package/dist/net/Dns.d.ts +596 -0
- package/dist/net/Dns.d.ts.map +1 -0
- package/dist/net/Dns.js +549 -0
- package/dist/net/Dns.js.map +1 -0
- package/dist/net/Host.d.ts +369 -0
- package/dist/net/Host.d.ts.map +1 -0
- package/dist/net/Host.js +397 -0
- package/dist/net/Host.js.map +1 -0
- package/dist/net/IpInterface.d.ts +76 -3
- package/dist/net/IpInterface.d.ts.map +1 -1
- package/dist/net/IpInterface.js +53 -4
- package/dist/net/IpInterface.js.map +1 -1
- package/dist/net/IpNetwork.d.ts +82 -8
- package/dist/net/IpNetwork.d.ts.map +1 -1
- package/dist/net/IpNetwork.js +60 -4
- package/dist/net/IpNetwork.js.map +1 -1
- package/dist/net/NetAddress.d.ts +396 -35
- package/dist/net/NetAddress.d.ts.map +1 -1
- package/dist/net/NetAddress.js +358 -34
- package/dist/net/NetAddress.js.map +1 -1
- package/dist/net/index.d.ts +15 -0
- package/dist/net/index.d.ts.map +1 -1
- package/dist/net/index.js +15 -0
- package/dist/net/index.js.map +1 -1
- package/dist/observability/OtlpMetrics.d.ts.map +1 -1
- package/dist/observability/OtlpMetrics.js +15 -21
- package/dist/observability/OtlpMetrics.js.map +1 -1
- package/dist/observability/PrometheusMetrics.js +1 -0
- package/dist/observability/PrometheusMetrics.js.map +1 -1
- package/dist/persistence/PersistedQueue.d.ts +32 -3
- package/dist/persistence/PersistedQueue.d.ts.map +1 -1
- package/dist/persistence/PersistedQueue.js +75 -16
- package/dist/persistence/PersistedQueue.js.map +1 -1
- package/dist/rpc/RpcClient.d.ts +22 -6
- package/dist/rpc/RpcClient.d.ts.map +1 -1
- package/dist/rpc/RpcClient.js +12 -5
- package/dist/rpc/RpcClient.js.map +1 -1
- package/dist/rpc/RpcServer.d.ts +6 -0
- package/dist/rpc/RpcServer.d.ts.map +1 -1
- package/dist/rpc/RpcServer.js +78 -29
- package/dist/rpc/RpcServer.js.map +1 -1
- package/dist/socket/DatagramSocket.d.ts +456 -0
- package/dist/socket/DatagramSocket.d.ts.map +1 -0
- package/dist/socket/DatagramSocket.js +535 -0
- package/dist/socket/DatagramSocket.js.map +1 -0
- package/dist/socket/Socket.d.ts +20 -1
- package/dist/socket/Socket.d.ts.map +1 -1
- package/dist/socket/Socket.js +9 -1
- package/dist/socket/Socket.js.map +1 -1
- package/dist/socket/index.d.ts +5 -0
- package/dist/socket/index.d.ts.map +1 -1
- package/dist/socket/index.js +5 -0
- package/dist/socket/index.js.map +1 -1
- package/dist/sql/Migrator.d.ts.map +1 -1
- package/dist/sql/Migrator.js +7 -2
- package/dist/sql/Migrator.js.map +1 -1
- package/dist/sql/SqlClient.d.ts +7 -1
- package/dist/sql/SqlClient.d.ts.map +1 -1
- package/dist/sql/SqlClient.js +31 -6
- package/dist/sql/SqlClient.js.map +1 -1
- package/dist/testing/TestCrypto.d.ts +73 -0
- package/dist/testing/TestCrypto.d.ts.map +1 -0
- package/dist/testing/TestCrypto.js +87 -0
- package/dist/testing/TestCrypto.js.map +1 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/workflow/DurableDeferred.d.ts +54 -0
- package/dist/workflow/DurableDeferred.d.ts.map +1 -1
- package/dist/workflow/DurableDeferred.js +108 -50
- package/dist/workflow/DurableDeferred.js.map +1 -1
- package/dist/workflow/WorkflowEngine.d.ts +24 -2
- package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
- package/dist/workflow/WorkflowEngine.js +13 -3
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/package.json +6 -3
- package/src/Context.ts +12 -3
- package/src/Crypto.ts +43 -9
- package/src/DateTime.ts +9 -6
- package/src/Duration.ts +36 -16
- package/src/FileSystem.ts +48 -2
- package/src/RequestResolver.ts +4 -10
- package/src/Scheduler.ts +9 -0
- package/src/Schema.ts +677 -5
- package/src/SchemaGetter.ts +47 -35
- package/src/Sink.ts +45 -18
- package/src/Stdio.ts +9 -2
- package/src/Stream.ts +44 -15
- package/src/Utils.ts +8 -5
- package/src/ai/AnthropicStructuredOutput.ts +2 -2
- package/src/ai/Chat.ts +2 -2
- package/src/ai/LanguageModel.ts +96 -24
- package/src/ai/McpServer.ts +288 -94
- package/src/ai/Prompt.ts +24 -10
- package/src/ai/Response.ts +6 -3
- package/src/ai/internal/mcpProtocol/v2026_07_28.ts +5 -0
- package/src/ai/internal/mcpProtocol.ts +3 -0
- package/src/ai/internal/mcpRuntime.ts +2 -0
- package/src/cli/CliOutput.ts +33 -16
- package/src/cli/Command.ts +4 -3
- package/src/cli/Completions.ts +9 -6
- package/src/cli/GlobalFlag.ts +19 -11
- package/src/cli/Prompt.ts +159 -149
- package/src/cli/internal/ansi.ts +26 -3
- package/src/cli/internal/completions/powershell.ts +322 -0
- package/src/cli/internal/completions/zsh.ts +4 -2
- package/src/cli/internal/wizard.ts +34 -26
- package/src/cluster/ClusterSchema.ts +32 -0
- package/src/cluster/ClusterWorkflowEngine.ts +64 -36
- package/src/cluster/MessageStorage.ts +45 -14
- package/src/cluster/Runners.ts +8 -2
- package/src/cluster/Sharding.ts +219 -58
- package/src/cluster/SqlMessageStorage.ts +118 -84
- package/src/cluster/SqlRunnerStorage.ts +224 -128
- package/src/cluster/internal/entityManager.ts +151 -55
- package/src/encoding/Sse.ts +69 -79
- package/src/graphql/GraphQL.ts +494 -0
- package/src/graphql/GraphQLClient.ts +912 -0
- package/src/graphql/GraphQLClientError.ts +542 -0
- package/src/graphql/GraphQLGroup.ts +167 -0
- package/src/graphql/GraphQLMiddleware.ts +253 -0
- package/src/graphql/GraphQLProtocol.ts +1286 -0
- package/src/graphql/GraphQLResolver.ts +1010 -0
- package/src/graphql/index.ts +48 -0
- package/src/http/FindMyWay/internal/router.ts +4 -7
- package/src/http/HttpClient.ts +110 -12
- package/src/http/HttpEffect.ts +11 -5
- package/src/http/HttpMiddleware.ts +33 -34
- package/src/http/HttpRouter.ts +35 -0
- package/src/http/HttpServerResponse.ts +28 -16
- package/src/http/HttpTraceContext.ts +8 -2
- package/src/http/Multipart.ts +17 -1
- package/src/http-api/HttpApiScalar.ts +5 -0
- package/src/internal/dateTime.ts +13 -10
- package/src/internal/effect.ts +22 -15
- package/src/internal/graphql.ts +263 -0
- package/src/internal/graphqlHeaders.ts +11 -0
- package/src/internal/request.ts +31 -3
- package/src/internal/schema/toCodeDocument.ts +7 -1
- package/src/internal/schema/toJsonSchemaDocument.ts +34 -2
- package/src/internal/version.ts +1 -1
- package/src/net/AddressResolver.ts +222 -0
- package/src/net/Dns.ts +1035 -0
- package/src/net/Host.ts +544 -0
- package/src/net/IpInterface.ts +117 -7
- package/src/net/IpNetwork.ts +116 -17
- package/src/net/NetAddress.ts +620 -76
- package/src/net/index.ts +18 -0
- package/src/observability/OtlpMetrics.ts +12 -23
- package/src/observability/PrometheusMetrics.ts +1 -0
- package/src/persistence/PersistedQueue.ts +146 -53
- package/src/rpc/RpcClient.ts +41 -19
- package/src/rpc/RpcServer.ts +115 -40
- package/src/socket/DatagramSocket.ts +919 -0
- package/src/socket/Socket.ts +28 -2
- package/src/socket/index.ts +6 -0
- package/src/sql/Migrator.ts +21 -5
- package/src/sql/SqlClient.ts +39 -15
- package/src/testing/TestCrypto.ts +94 -0
- package/src/testing/index.ts +6 -0
- package/src/workflow/DurableDeferred.ts +160 -47
- package/src/workflow/WorkflowEngine.ts +50 -5
package/src/ai/McpServer.ts
CHANGED
|
@@ -94,6 +94,7 @@ type CompletionContext = typeof Complete.payloadSchema.Type["context"]
|
|
|
94
94
|
|
|
95
95
|
interface QueuedServerNotification {
|
|
96
96
|
readonly notification: McpCore.ServerNotification
|
|
97
|
+
listChangeRevision?: number | undefined
|
|
97
98
|
readonly targetClientId?: number | undefined
|
|
98
99
|
readonly delivered: Deferred.Deferred<void>
|
|
99
100
|
readonly requestContext?: McpRequestContext["Service"] | undefined
|
|
@@ -103,8 +104,19 @@ interface QueuedServerNotification {
|
|
|
103
104
|
const internalState = new WeakMap<object, {
|
|
104
105
|
readonly core: McpCore.McpCore
|
|
105
106
|
readonly notifications: Queue.Dequeue<QueuedServerNotification>
|
|
106
|
-
readonly notificationDelivery: { consumers: number }
|
|
107
|
+
readonly notificationDelivery: { consumers: number; listChangeRevision: number; requestEndpoints: number }
|
|
107
108
|
}>()
|
|
109
|
+
|
|
110
|
+
// Request-scoped HTTP servers deliver a request's own notifications through
|
|
111
|
+
// that request's server instead of the shared notification queue.
|
|
112
|
+
const RequestNotificationQueue = Context.Reference<Queue.Queue<QueuedServerNotification> | undefined>(
|
|
113
|
+
"effect/ai/McpServer/RequestNotificationQueue",
|
|
114
|
+
{ defaultValue: () => undefined }
|
|
115
|
+
)
|
|
116
|
+
// Progress and logs belong to the request that emitted them.
|
|
117
|
+
const isRequestNotification = (notification: McpCore.ServerNotification) =>
|
|
118
|
+
notification._tag === "Progress" || notification._tag === "LoggingMessage"
|
|
119
|
+
|
|
108
120
|
type ServerExtensions = NonNullable<ServerCapabilities["extensions"]>
|
|
109
121
|
type ServerNotificationRequest<
|
|
110
122
|
R extends Rpc.Any = RpcGroup.Rpcs<typeof BroadcastServerNotificationRpcs>
|
|
@@ -321,8 +333,11 @@ export class McpServer extends Context.Service<McpServer, {
|
|
|
321
333
|
readonly annotations: Context.Context<never>
|
|
322
334
|
}> = []
|
|
323
335
|
const notificationsQueue = yield* Queue.make<QueuedServerNotification>()
|
|
324
|
-
const notificationDelivery = { consumers: 0 }
|
|
325
|
-
|
|
336
|
+
const notificationDelivery = { consumers: 0, listChangeRevision: 0, requestEndpoints: 0 }
|
|
337
|
+
// With only request-scoped endpoints, no long-lived server ever takes from the shared queue.
|
|
338
|
+
const withoutSharedConsumer = () =>
|
|
339
|
+
notificationDelivery.consumers === 0 && notificationDelivery.requestEndpoints > 0
|
|
340
|
+
const pendingListChanges = new Map<string, QueuedServerNotification>()
|
|
326
341
|
const dispatcher = (yield* Scheduler).makeDispatcher()
|
|
327
342
|
const notifications = yield* RpcClient.makeNoSerialization(BroadcastServerNotificationRpcs, {
|
|
328
343
|
spanPrefix: "McpServer/Notifications",
|
|
@@ -337,36 +352,52 @@ export class McpServer extends Context.Service<McpServer, {
|
|
|
337
352
|
if (notification === undefined) {
|
|
338
353
|
return Effect.void
|
|
339
354
|
}
|
|
340
|
-
const queued = {
|
|
355
|
+
const queued: QueuedServerNotification = {
|
|
341
356
|
notification,
|
|
342
357
|
delivered,
|
|
343
358
|
requestContext: Context.getOrUndefined(fiber.context, McpRequestContext),
|
|
344
359
|
requestHeaders: Context.getOrUndefined(fiber.context, HttpServerRequest.HttpServerRequest)?.headers
|
|
345
360
|
}
|
|
361
|
+
// write resumes the caller synchronously, so defer it until delivery completes.
|
|
362
|
+
const acknowledge = Effect.suspend(() =>
|
|
363
|
+
notifications.write({
|
|
364
|
+
clientId: 0,
|
|
365
|
+
requestId: message.id,
|
|
366
|
+
_tag: "Exit",
|
|
367
|
+
exit: Exit.void
|
|
368
|
+
})
|
|
369
|
+
)
|
|
370
|
+
// A request-scoped endpoint delivers its request's own progress and logs on that request's response.
|
|
371
|
+
const requestQueue = Context.get(fiber.context, RequestNotificationQueue)
|
|
372
|
+
if (
|
|
373
|
+
requestQueue !== undefined && queued.requestContext !== undefined && isRequestNotification(notification)
|
|
374
|
+
) {
|
|
375
|
+
Queue.offerUnsafe(requestQueue, queued)
|
|
376
|
+
return Effect.andThen(Deferred.await(delivered), acknowledge)
|
|
377
|
+
}
|
|
378
|
+
if (withoutSharedConsumer()) {
|
|
379
|
+
return acknowledge
|
|
380
|
+
}
|
|
346
381
|
let enqueued = false
|
|
347
382
|
if (message.tag.includes("list_changed")) {
|
|
348
|
-
|
|
383
|
+
const revision = ++notificationDelivery.listChangeRevision
|
|
384
|
+
const pending = pendingListChanges.get(message.tag)
|
|
385
|
+
if (pending !== undefined) {
|
|
386
|
+
// A coalesced change may be newer than an intervening subscription.
|
|
387
|
+
pending.listChangeRevision = revision
|
|
388
|
+
} else {
|
|
389
|
+
queued.listChangeRevision = revision
|
|
349
390
|
enqueued = true
|
|
350
|
-
|
|
391
|
+
pendingListChanges.set(message.tag, queued)
|
|
351
392
|
dispatcher.scheduleTask(() => {
|
|
352
|
-
Queue.offerUnsafe(notificationsQueue, queued)
|
|
353
393
|
pendingListChanges.delete(message.tag)
|
|
394
|
+
Queue.offerUnsafe(notificationsQueue, queued)
|
|
354
395
|
}, 0)
|
|
355
|
-
pendingListChanges.add(tag)
|
|
356
396
|
}
|
|
357
397
|
} else {
|
|
358
398
|
enqueued = true
|
|
359
399
|
Queue.offerUnsafe(notificationsQueue, queued)
|
|
360
400
|
}
|
|
361
|
-
// write resumes the caller synchronously, so defer it until delivery completes.
|
|
362
|
-
const acknowledge = Effect.suspend(() =>
|
|
363
|
-
notifications.write({
|
|
364
|
-
clientId: 0,
|
|
365
|
-
requestId: message.id,
|
|
366
|
-
_tag: "Exit",
|
|
367
|
-
exit: Exit.void
|
|
368
|
-
})
|
|
369
|
-
)
|
|
370
401
|
return enqueued && queued.requestContext !== undefined && notificationDelivery.consumers > 0
|
|
371
402
|
? Effect.andThen(Deferred.await(delivered), acknowledge)
|
|
372
403
|
: acknowledge
|
|
@@ -377,7 +408,7 @@ export class McpServer extends Context.Service<McpServer, {
|
|
|
377
408
|
const service = McpServer.of({
|
|
378
409
|
notifications: notifications.client,
|
|
379
410
|
notifyElicitationComplete: ({ clientId, elicitationId }) =>
|
|
380
|
-
Deferred.make<void>().pipe(
|
|
411
|
+
withoutSharedConsumer() ? Effect.void : Deferred.make<void>().pipe(
|
|
381
412
|
Effect.flatMap((delivered) =>
|
|
382
413
|
Queue.offer(notificationsQueue, {
|
|
383
414
|
notification: McpCore.ServerNotification.ElicitationComplete({ elicitationId }),
|
|
@@ -651,12 +682,14 @@ const MCP_SESSION_ID_HEADER = "mcp-session-id"
|
|
|
651
682
|
const MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version"
|
|
652
683
|
const MCP_INVALID_BATCH_METHOD = "invalid/json-rpc-batch"
|
|
653
684
|
const cancelledResponses = new WeakMap<object, string | number>()
|
|
685
|
+
const httpPostResponses = new WeakMap<HttpServerRequest.HttpServerRequest, { expected: number }>()
|
|
654
686
|
const requestKey = (requestId: string | number): string => `${typeof requestId}:${requestId}`
|
|
655
687
|
|
|
656
688
|
interface ActiveRequest {
|
|
657
689
|
readonly requestId: RpcMessage.RequestId
|
|
658
690
|
readonly prepared: McpRuntime.PreparedRequest
|
|
659
691
|
readonly cancelled: boolean
|
|
692
|
+
readonly httpPost: { expected: number } | undefined
|
|
660
693
|
}
|
|
661
694
|
|
|
662
695
|
class McpClientKey extends Data.Class<{
|
|
@@ -716,9 +749,14 @@ const runWithRuntime = Effect.fnUntraced(function*(
|
|
|
716
749
|
readonly websiteUrl?: string | undefined
|
|
717
750
|
readonly icons?: ReadonlyArray<McpSchema.Icon> | undefined
|
|
718
751
|
readonly extensions?: ServerExtensions | undefined
|
|
752
|
+
readonly allowSubscriptions?: boolean | undefined
|
|
719
753
|
},
|
|
720
754
|
runtime: McpRuntime.ServerRuntimeShape,
|
|
721
|
-
transport: "custom" | "http" | "stdio"
|
|
755
|
+
transport: "custom" | "http" | "stdio",
|
|
756
|
+
requestScoped?: {
|
|
757
|
+
readonly notifications: Queue.Dequeue<QueuedServerNotification>
|
|
758
|
+
readonly handlers: Context.Context<never>
|
|
759
|
+
}
|
|
722
760
|
) {
|
|
723
761
|
const protocol = yield* RpcServer.Protocol
|
|
724
762
|
const server = yield* McpServer
|
|
@@ -807,10 +845,12 @@ const runWithRuntime = Effect.fnUntraced(function*(
|
|
|
807
845
|
})
|
|
808
846
|
})
|
|
809
847
|
let writeFromClient!: (clientId: number, message: RpcMessage.FromClientEncoded) => Effect.Effect<void>
|
|
810
|
-
const
|
|
811
|
-
|
|
848
|
+
const { core, notificationDelivery, notifications } = internalState.get(server)!
|
|
849
|
+
const handlers = requestScoped?.handlers ?? (yield* runtime.installHandlers({
|
|
850
|
+
core,
|
|
812
851
|
subscribeServerNotifications: PubSub.subscribe(serverNotifications),
|
|
813
|
-
|
|
852
|
+
getListChangeRevision: () => notificationDelivery.listChangeRevision,
|
|
853
|
+
...(!protocol.supportsNotifications || options.allowSubscriptions === false ? {} : {
|
|
814
854
|
sendNotification,
|
|
815
855
|
markSubscriptionCancelled: (clientId: number, requestId: RpcMessage.RequestId) =>
|
|
816
856
|
Effect.sync(() => {
|
|
@@ -820,7 +860,7 @@ const runWithRuntime = Effect.fnUntraced(function*(
|
|
|
820
860
|
}),
|
|
821
861
|
defaultLogLevel,
|
|
822
862
|
serverInfo: options
|
|
823
|
-
})
|
|
863
|
+
}))
|
|
824
864
|
|
|
825
865
|
const clients = yield* RcMap.make({
|
|
826
866
|
lookup: Effect.fnUntraced(function*(key: McpClientKey) {
|
|
@@ -848,10 +888,13 @@ const runWithRuntime = Effect.fnUntraced(function*(
|
|
|
848
888
|
if (request._tag === "Interrupt") {
|
|
849
889
|
// A cancelled reverse request may never receive a reply.
|
|
850
890
|
removeReverseRequestClient(requestKey(request.requestId), key)
|
|
891
|
+
return sendNotification(key.profile.protocolVersion, key.clientId, {
|
|
892
|
+
tag: "notifications/cancelled",
|
|
893
|
+
payload: { requestId: request.requestId }
|
|
894
|
+
})
|
|
851
895
|
}
|
|
852
|
-
//
|
|
853
|
-
|
|
854
|
-
return protocol.send(key.clientId, request as any)
|
|
896
|
+
// Effect RPC control messages are not part of the MCP protocol.
|
|
897
|
+
return Effect.void
|
|
855
898
|
},
|
|
856
899
|
supportsAck: true,
|
|
857
900
|
supportsTransferables: false,
|
|
@@ -950,12 +993,13 @@ const runWithRuntime = Effect.fnUntraced(function*(
|
|
|
950
993
|
if (response._tag === "Exit") {
|
|
951
994
|
const requests = activeRequests.get(clientId)
|
|
952
995
|
const key = requestKey(response.requestId)
|
|
953
|
-
const
|
|
996
|
+
const active = requests?.get(key)
|
|
954
997
|
if (requests !== undefined && requests.delete(key) && requests.size === 0) {
|
|
955
998
|
activeRequests.delete(clientId)
|
|
956
999
|
}
|
|
957
|
-
if (cancelled === true) {
|
|
1000
|
+
if (active?.cancelled === true) {
|
|
958
1001
|
if (transport === "custom") return Effect.void
|
|
1002
|
+
if (active.httpPost !== undefined) active.httpPost.expected--
|
|
959
1003
|
cancelledResponses.set(response, response.requestId)
|
|
960
1004
|
return protocol.send(clientId, response)
|
|
961
1005
|
}
|
|
@@ -1003,6 +1047,11 @@ const runWithRuntime = Effect.fnUntraced(function*(
|
|
|
1003
1047
|
}
|
|
1004
1048
|
switch (request._tag) {
|
|
1005
1049
|
case "Request": {
|
|
1050
|
+
if (httpRequest !== undefined && request.isNotification !== true) {
|
|
1051
|
+
const httpPost = httpPostResponses.get(httpRequest) ?? { expected: 0 }
|
|
1052
|
+
httpPost.expected++
|
|
1053
|
+
httpPostResponses.set(httpRequest, httpPost)
|
|
1054
|
+
}
|
|
1006
1055
|
const headers = isHttp
|
|
1007
1056
|
? Context.getUnsafe(
|
|
1008
1057
|
Fiber.getCurrent()!.context,
|
|
@@ -1138,7 +1187,8 @@ const runWithRuntime = Effect.fnUntraced(function*(
|
|
|
1138
1187
|
requests.set(requestKey(request.id), {
|
|
1139
1188
|
requestId: RpcMessage.RequestId(request.id),
|
|
1140
1189
|
prepared,
|
|
1141
|
-
cancelled: false
|
|
1190
|
+
cancelled: false,
|
|
1191
|
+
httpPost: httpRequest === undefined ? undefined : httpPostResponses.get(httpRequest)
|
|
1142
1192
|
})
|
|
1143
1193
|
activeRequests.set(clientId, requests)
|
|
1144
1194
|
}
|
|
@@ -1231,22 +1281,23 @@ const runWithRuntime = Effect.fnUntraced(function*(
|
|
|
1231
1281
|
})
|
|
1232
1282
|
}
|
|
1233
1283
|
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
Effect.sync(() => {
|
|
1237
|
-
notificationDelivery.consumers++
|
|
1238
|
-
}),
|
|
1239
|
-
() =>
|
|
1284
|
+
if (requestScoped === undefined) {
|
|
1285
|
+
yield* Effect.acquireRelease(
|
|
1240
1286
|
Effect.sync(() => {
|
|
1241
|
-
notificationDelivery.consumers
|
|
1242
|
-
})
|
|
1243
|
-
|
|
1287
|
+
notificationDelivery.consumers++
|
|
1288
|
+
}),
|
|
1289
|
+
() =>
|
|
1290
|
+
Effect.sync(() => {
|
|
1291
|
+
notificationDelivery.consumers--
|
|
1292
|
+
})
|
|
1293
|
+
)
|
|
1294
|
+
}
|
|
1244
1295
|
const notificationTails = new Map<number, Deferred.Deferred<void>>()
|
|
1245
|
-
yield* Queue.take(notifications).pipe(
|
|
1296
|
+
yield* Queue.take(requestScoped?.notifications ?? notifications).pipe(
|
|
1246
1297
|
Effect.flatMap(Effect.fnUntraced(function*(queued) {
|
|
1247
|
-
const { delivered, notification, targetClientId, requestContext, requestHeaders } = queued
|
|
1298
|
+
const { delivered, listChangeRevision, notification, targetClientId, requestContext, requestHeaders } = queued
|
|
1248
1299
|
if (McpProtocolInternal.isSubscriptionServerNotification(notification)) {
|
|
1249
|
-
yield* PubSub.publish(serverNotifications, { notification, targetClientId })
|
|
1300
|
+
yield* PubSub.publish(serverNotifications, { notification, targetClientId, listChangeRevision })
|
|
1250
1301
|
}
|
|
1251
1302
|
const clientIds = yield* patchedProtocol.clientIds
|
|
1252
1303
|
for (const clientId of clientStates.keys()) {
|
|
@@ -1256,7 +1307,7 @@ const runWithRuntime = Effect.fnUntraced(function*(
|
|
|
1256
1307
|
runtime.disconnect(clientId)
|
|
1257
1308
|
}
|
|
1258
1309
|
}
|
|
1259
|
-
const requestNotification = notification
|
|
1310
|
+
const requestNotification = isRequestNotification(notification)
|
|
1260
1311
|
const deliveryClientIds = requestNotification && requestContext !== undefined
|
|
1261
1312
|
? [requestContext.clientId]
|
|
1262
1313
|
: targetClientId === undefined
|
|
@@ -1422,6 +1473,7 @@ const layerWithRuntime = (options: {
|
|
|
1422
1473
|
readonly websiteUrl?: string | undefined
|
|
1423
1474
|
readonly icons?: ReadonlyArray<McpSchema.Icon> | undefined
|
|
1424
1475
|
readonly extensions?: ServerExtensions | undefined
|
|
1476
|
+
readonly allowSubscriptions?: boolean | undefined
|
|
1425
1477
|
}, transport: "custom" | "http" | "stdio"): Layer.Layer<
|
|
1426
1478
|
McpServer | McpServerClient,
|
|
1427
1479
|
never,
|
|
@@ -1568,11 +1620,25 @@ const mcpStdioSerialization = (
|
|
|
1568
1620
|
* holding a session id can end that session, so authenticate requests in the
|
|
1569
1621
|
* surrounding router.
|
|
1570
1622
|
*
|
|
1623
|
+
* Set `allowSubscriptions: false` to disable `v2026_07_28` subscriptions.
|
|
1624
|
+
* Discovery advertises `listChanged: false`, and `subscriptions/listen` returns
|
|
1625
|
+
* Method not found. Request-scoped progress and logs, legacy protocols, and
|
|
1626
|
+
* default subscription behavior are unchanged.
|
|
1627
|
+
*
|
|
1628
|
+
* The layer runs one long-lived MCP server that every POST shares. For
|
|
1629
|
+
* serverless hosts where execution must belong to each request, use
|
|
1630
|
+
* `layerHttpRequestScoped` instead.
|
|
1631
|
+
*
|
|
1571
1632
|
* `layerHttp` always implements the single-endpoint Streamable HTTP topology.
|
|
1572
1633
|
* Using `v2024_11_05` here is a custom compatibility transport for that
|
|
1573
1634
|
* revision's schema. It does not implement the historical two-endpoint
|
|
1574
1635
|
* HTTP+SSE transport, GET SSE, event resumption, or session expiry.
|
|
1575
1636
|
*
|
|
1637
|
+
* **Gotchas**
|
|
1638
|
+
*
|
|
1639
|
+
* This layer starts a scoped server fiber. For sharing it across Cloudflare
|
|
1640
|
+
* Workers requests, see `HttpRouter.toWebHandler` for setup and limitations.
|
|
1641
|
+
*
|
|
1576
1642
|
* @see {@link layerStdio} for exposing the server over stdio
|
|
1577
1643
|
* @see {@link layer} for the base MCP server layer without a transport protocol
|
|
1578
1644
|
*
|
|
@@ -1592,6 +1658,7 @@ export const layerHttp = (options: {
|
|
|
1592
1658
|
readonly extensions?: ServerExtensions | undefined
|
|
1593
1659
|
readonly allowedOrigins?: ReadonlyArray<string> | undefined
|
|
1594
1660
|
readonly allowSessionTermination?: boolean | undefined
|
|
1661
|
+
readonly allowSubscriptions?: boolean | undefined
|
|
1595
1662
|
}): Layer.Layer<McpServer | McpServerClient, Cause.IllegalArgumentError, HttpRouter.HttpRouter> => {
|
|
1596
1663
|
const runtime = McpRuntime.layer(options.protocols)
|
|
1597
1664
|
return layerWithRuntime(options, "http").pipe(
|
|
@@ -1601,6 +1668,105 @@ export const layerHttp = (options: {
|
|
|
1601
1668
|
)
|
|
1602
1669
|
}
|
|
1603
1670
|
|
|
1671
|
+
/**
|
|
1672
|
+
* Registers a request-scoped Streamable HTTP MCP endpoint at `options.path`.
|
|
1673
|
+
*
|
|
1674
|
+
* **When to use**
|
|
1675
|
+
*
|
|
1676
|
+
* Use when serving on serverless hosts such as Cloudflare Workers, where execution must
|
|
1677
|
+
* belong to the HTTP request rather than a shared long-lived server.
|
|
1678
|
+
*
|
|
1679
|
+
* **Details**
|
|
1680
|
+
*
|
|
1681
|
+
* Each POST runs its own server in the request scope, with no background
|
|
1682
|
+
* fiber, timer, or queue consumer left after the response body completes.
|
|
1683
|
+
* Only sessionless protocols such as `v2026_07_28` are accepted. Tools,
|
|
1684
|
+
* resources, prompts, completions, multi round-trip input requests, and the
|
|
1685
|
+
* request's own progress and log notifications are supported. Legacy reverse
|
|
1686
|
+
* requests through `McpServer.elicit` are not available.
|
|
1687
|
+
*
|
|
1688
|
+
* Discovery advertises `listChanged: false`, `subscriptions/listen` returns
|
|
1689
|
+
* Method not found, and DELETE returns `405`. Request-scoped clients receive
|
|
1690
|
+
* no broadcast notifications such as list changes. Those still reach clients
|
|
1691
|
+
* of a `layerHttp` endpoint sharing the same `McpServer`, and are dropped when
|
|
1692
|
+
* no such endpoint exists. Use `layerHttp` on a long-lived host for sessions,
|
|
1693
|
+
* subscriptions, or legacy reverse requests.
|
|
1694
|
+
*
|
|
1695
|
+
* HTTP admission, origin checks, and JSON-RPC errors follow `layerHttp`.
|
|
1696
|
+
* Configuring a sessionful protocol through an untyped caller fails layer
|
|
1697
|
+
* construction with `IllegalArgumentError`.
|
|
1698
|
+
*
|
|
1699
|
+
* @see {@link layerHttp} for the long-lived HTTP transport and HTTP admission policy
|
|
1700
|
+
*
|
|
1701
|
+
* @stability unstable
|
|
1702
|
+
* @category layers
|
|
1703
|
+
* @since 4.0.0
|
|
1704
|
+
*/
|
|
1705
|
+
export const layerHttpRequestScoped = (options: {
|
|
1706
|
+
readonly name: string
|
|
1707
|
+
readonly version: string
|
|
1708
|
+
readonly instructions?: string | undefined
|
|
1709
|
+
readonly description?: string | undefined
|
|
1710
|
+
readonly websiteUrl?: string | undefined
|
|
1711
|
+
readonly icons?: ReadonlyArray<McpSchema.Icon> | undefined
|
|
1712
|
+
readonly path: HttpRouter.PathInput
|
|
1713
|
+
readonly protocols: Arr.NonEmptyReadonlyArray<
|
|
1714
|
+
McpProtocol.ProtocolAdapter<McpProtocol.ProtocolVersion, McpProtocol.StatelessRuntimeDescriptor>
|
|
1715
|
+
>
|
|
1716
|
+
readonly extensions?: ServerExtensions | undefined
|
|
1717
|
+
readonly allowedOrigins?: ReadonlyArray<string> | undefined
|
|
1718
|
+
}): Layer.Layer<McpServer | McpServerClient, Cause.IllegalArgumentError, HttpRouter.HttpRouter> =>
|
|
1719
|
+
Layer.effectDiscard(Effect.gen(function*() {
|
|
1720
|
+
const runtime = yield* McpRuntime.ServerRuntime
|
|
1721
|
+
const stateful = runtime.protocols.find((protocol) => protocol.runtime._tag === "Stateful")
|
|
1722
|
+
if (stateful !== undefined) {
|
|
1723
|
+
return yield* new Cause.IllegalArgumentError(
|
|
1724
|
+
`McpServer.layerHttpRequestScoped does not support the sessionful MCP protocol ${stateful.protocolVersion}`
|
|
1725
|
+
)
|
|
1726
|
+
}
|
|
1727
|
+
const server = yield* McpServer
|
|
1728
|
+
const { core, notificationDelivery } = internalState.get(server)!
|
|
1729
|
+
// Subscriptions are disabled, so handlers hold no per-request state and
|
|
1730
|
+
// read registrations from the shared core on every call. Request services
|
|
1731
|
+
// still take precedence over the layer context captured here.
|
|
1732
|
+
const handlers = yield* (runtime.installHandlers({
|
|
1733
|
+
core,
|
|
1734
|
+
subscribeServerNotifications: Effect.die("Subscriptions are disabled for request-scoped MCP servers"),
|
|
1735
|
+
getListChangeRevision: () => notificationDelivery.listChangeRevision,
|
|
1736
|
+
defaultLogLevel: yield* CurrentLogLevel,
|
|
1737
|
+
serverInfo: options
|
|
1738
|
+
}) as Effect.Effect<Context.Context<never>>)
|
|
1739
|
+
yield* Effect.acquireRelease(
|
|
1740
|
+
Effect.sync(() => {
|
|
1741
|
+
notificationDelivery.requestEndpoints++
|
|
1742
|
+
}),
|
|
1743
|
+
() =>
|
|
1744
|
+
Effect.sync(() => {
|
|
1745
|
+
notificationDelivery.requestEndpoints--
|
|
1746
|
+
})
|
|
1747
|
+
)
|
|
1748
|
+
// Each POST runs its own RPC server in the request scope, so no fiber,
|
|
1749
|
+
// timer, or queue consumer outlives the request that created it.
|
|
1750
|
+
yield* addMcpHttpRoutes(
|
|
1751
|
+
options,
|
|
1752
|
+
Effect.gen(function*() {
|
|
1753
|
+
const { httpEffect, protocol } = yield* RpcServer.makeProtocolWithHttpEffect().pipe(
|
|
1754
|
+
Effect.provideService(RpcSerialization.RpcSerialization, mcpHttpSerialization)
|
|
1755
|
+
)
|
|
1756
|
+
const notifications = yield* Queue.make<QueuedServerNotification>()
|
|
1757
|
+
yield* runWithRuntime(options, runtime, "http", { notifications, handlers }).pipe(
|
|
1758
|
+
Effect.provideService(RpcServer.Protocol, protocol),
|
|
1759
|
+
Effect.provideService(McpServer, server),
|
|
1760
|
+
Effect.forkScoped({ startImmediately: true })
|
|
1761
|
+
)
|
|
1762
|
+
return yield* Effect.provideService(httpEffect, RequestNotificationQueue, notifications)
|
|
1763
|
+
})
|
|
1764
|
+
)
|
|
1765
|
+
})).pipe(
|
|
1766
|
+
Layer.provideMerge(McpServer.layer),
|
|
1767
|
+
Layer.provide(McpRuntime.layer(options.protocols))
|
|
1768
|
+
)
|
|
1769
|
+
|
|
1604
1770
|
const layerMcpProtocolHttp = (options: {
|
|
1605
1771
|
readonly path: HttpRouter.PathInput
|
|
1606
1772
|
readonly allowedOrigins?: ReadonlyArray<string> | undefined
|
|
@@ -1611,68 +1777,96 @@ const layerMcpProtocolHttp = (options: {
|
|
|
1611
1777
|
McpRuntime.ServerRuntime | HttpRouter.HttpRouter
|
|
1612
1778
|
> =>
|
|
1613
1779
|
Layer.effect(RpcServer.Protocol)(Effect.gen(function*() {
|
|
1614
|
-
const runtime = yield* McpRuntime.ServerRuntime
|
|
1615
1780
|
const { httpEffect, protocol } = yield* RpcServer.makeProtocolWithHttpEffect().pipe(
|
|
1616
1781
|
Effect.provideService(RpcSerialization.RpcSerialization, mcpHttpSerialization)
|
|
1617
1782
|
)
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1783
|
+
yield* addMcpHttpRoutes(options, httpEffect)
|
|
1784
|
+
return protocol
|
|
1785
|
+
}))
|
|
1786
|
+
|
|
1787
|
+
const addMcpHttpRoutes = Effect.fnUntraced(function*(
|
|
1788
|
+
options: {
|
|
1789
|
+
readonly path: HttpRouter.PathInput
|
|
1790
|
+
readonly allowedOrigins?: ReadonlyArray<string> | undefined
|
|
1791
|
+
readonly allowSessionTermination?: boolean | undefined
|
|
1792
|
+
},
|
|
1793
|
+
httpEffect: Effect.Effect<
|
|
1794
|
+
HttpServerResponse.HttpServerResponse,
|
|
1795
|
+
never,
|
|
1796
|
+
Scope.Scope | HttpServerRequest.HttpServerRequest
|
|
1797
|
+
>
|
|
1798
|
+
) {
|
|
1799
|
+
const runtime = yield* McpRuntime.ServerRuntime
|
|
1800
|
+
const router = yield* HttpRouter.HttpRouter
|
|
1801
|
+
const allowSessionTermination = options.allowSessionTermination === true &&
|
|
1802
|
+
runtime.protocols.some((protocol) => protocol.runtime._tag === "Stateful")
|
|
1803
|
+
const forbidden = Effect.succeed(HttpServerResponse.empty({ status: 403 }))
|
|
1804
|
+
const withAllowedOrigin = (
|
|
1805
|
+
handler: (request: HttpServerRequest.HttpServerRequest) => Effect.Effect<HttpServerResponse.HttpServerResponse>
|
|
1806
|
+
) =>
|
|
1807
|
+
(request: HttpServerRequest.HttpServerRequest) =>
|
|
1808
|
+
isAllowedMcpOrigin(request, options.allowedOrigins) ? handler(request) : forbidden
|
|
1809
|
+
const methodNotAllowedResponse = Effect.succeed(HttpServerResponse.empty({
|
|
1810
|
+
status: 405,
|
|
1811
|
+
headers: { allow: allowSessionTermination ? "POST, DELETE" : "POST" }
|
|
1812
|
+
}))
|
|
1813
|
+
const methodNotAllowed = withAllowedOrigin(() => methodNotAllowedResponse)
|
|
1814
|
+
for (const method of ["GET", "PUT", "PATCH", "OPTIONS"] as const) {
|
|
1815
|
+
yield* router.add(method, options.path, methodNotAllowed)
|
|
1816
|
+
}
|
|
1817
|
+
yield* router.add(
|
|
1818
|
+
"DELETE",
|
|
1819
|
+
options.path,
|
|
1820
|
+
allowSessionTermination
|
|
1821
|
+
? withAllowedOrigin((request) => runtime.terminateHttpSession(request.headers))
|
|
1822
|
+
: methodNotAllowed
|
|
1823
|
+
)
|
|
1824
|
+
yield* router.add("POST", options.path, (request) => {
|
|
1825
|
+
if (!isAllowedMcpOrigin(request, options.allowedOrigins)) {
|
|
1826
|
+
return Effect.succeed(HttpServerResponse.empty({ status: 403 }))
|
|
1634
1827
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1828
|
+
if (mcpMediaTypes(request.headers["content-type"])[0] !== "application/json") {
|
|
1829
|
+
return Effect.succeed(HttpServerResponse.empty({ status: 415 }))
|
|
1830
|
+
}
|
|
1831
|
+
const accepted = mcpMediaTypes(request.headers["accept"])
|
|
1832
|
+
if (!accepted.includes("application/json") || !accepted.includes("text/event-stream")) {
|
|
1833
|
+
return Effect.succeed(HttpServerResponse.empty({ status: 406 }))
|
|
1834
|
+
}
|
|
1835
|
+
return Effect.gen(function*() {
|
|
1836
|
+
const parsed = yield* Effect.result(
|
|
1837
|
+
Effect.flatMap(request.text, Schema.decodeEffect(Schema.UnknownFromJsonString))
|
|
1838
|
+
)
|
|
1839
|
+
const admission = runtime.admitHttp(request.headers, parsed)
|
|
1840
|
+
if (admission._tag === "Rejected") {
|
|
1841
|
+
return admission.response
|
|
1648
1842
|
}
|
|
1649
|
-
const
|
|
1650
|
-
|
|
1651
|
-
|
|
1843
|
+
const toResponse = (response: HttpServerResponse.HttpServerResponse) => {
|
|
1844
|
+
// Completed responses may already contain notifications followed by the result.
|
|
1845
|
+
const hasMultipleMessages = response.body._tag === "Uint8Array" &&
|
|
1846
|
+
response.body.body.subarray(0, -1).includes(10)
|
|
1847
|
+
return admission.isSubscription || response.body._tag === "Stream" || hasMultipleMessages
|
|
1848
|
+
? toServerSentEvents(response, admission.isSubscription)
|
|
1849
|
+
: response
|
|
1652
1850
|
}
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
Effect.
|
|
1851
|
+
if (admission.acknowledge) {
|
|
1852
|
+
return yield* Effect.catchCause(
|
|
1853
|
+
Effect.map(httpEffect, toResponse),
|
|
1854
|
+
() => Effect.succeed(HttpServerResponse.empty({ status: 202 }))
|
|
1656
1855
|
)
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1856
|
+
}
|
|
1857
|
+
return yield* Effect.flatMap(httpEffect, (response) => {
|
|
1858
|
+
if (response.body._tag !== "Uint8Array" || response.body.body.length > 0) {
|
|
1859
|
+
return Effect.succeed(toResponse(response))
|
|
1660
1860
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
? toServerSentEvents(response, admission.isSubscription)
|
|
1667
|
-
: response
|
|
1668
|
-
})
|
|
1669
|
-
return yield* admission.acknowledge
|
|
1670
|
-
? Effect.catchCause(response, () => Effect.succeed(HttpServerResponse.empty({ status: 202 })))
|
|
1671
|
-
: response
|
|
1861
|
+
// Only fully cancelled request POSTs may return an empty SSE response.
|
|
1862
|
+
// https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server
|
|
1863
|
+
return httpPostResponses.get(request)?.expected === 0
|
|
1864
|
+
? Effect.succeed(HttpServerResponse.stream(Stream.empty, { contentType: "text/event-stream" }))
|
|
1865
|
+
: Effect.die(new Error("MCP request ended without writing its response"))
|
|
1672
1866
|
})
|
|
1673
1867
|
})
|
|
1674
|
-
|
|
1675
|
-
|
|
1868
|
+
})
|
|
1869
|
+
})
|
|
1676
1870
|
|
|
1677
1871
|
const mcpHttpSerialization: RpcSerialization.RpcSerialization["Service"] = (() => {
|
|
1678
1872
|
const serialization = mcpJsonRpcSerialization()
|
package/src/ai/Prompt.ts
CHANGED
|
@@ -2159,11 +2159,11 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
|
|
|
2159
2159
|
return empty
|
|
2160
2160
|
}
|
|
2161
2161
|
|
|
2162
|
-
const assistantParts: Array<AssistantMessagePart> = []
|
|
2162
|
+
const assistantParts: Array<AssistantMessagePart | undefined> = []
|
|
2163
2163
|
const toolParts: Array<ToolMessagePart> = []
|
|
2164
2164
|
|
|
2165
|
-
const activeTextDeltas = new Map<string, { text: string; options: ProviderOptions }>()
|
|
2166
|
-
const activeReasoningDeltas = new Map<string, { text: string; options: ProviderOptions }>()
|
|
2165
|
+
const activeTextDeltas = new Map<string, { index: number; text: string; options: ProviderOptions }>()
|
|
2166
|
+
const activeReasoningDeltas = new Map<string, { index: number; text: string; options: ProviderOptions }>()
|
|
2167
2167
|
|
|
2168
2168
|
for (const part of parts) {
|
|
2169
2169
|
switch (part.type) {
|
|
@@ -2175,7 +2175,8 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
|
|
|
2175
2175
|
|
|
2176
2176
|
// Text Parts (streaming)
|
|
2177
2177
|
case "text-start": {
|
|
2178
|
-
activeTextDeltas.set(part.id, { text: "", options: part.metadata })
|
|
2178
|
+
activeTextDeltas.set(part.id, { index: assistantParts.length, text: "", options: part.metadata })
|
|
2179
|
+
assistantParts.push(undefined)
|
|
2179
2180
|
break
|
|
2180
2181
|
}
|
|
2181
2182
|
case "text-delta": {
|
|
@@ -2190,7 +2191,8 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
|
|
|
2190
2191
|
if (activeTextDeltas.has(part.id)) {
|
|
2191
2192
|
const active = activeTextDeltas.get(part.id)!
|
|
2192
2193
|
active.options = mergeOptions(active.options, part.metadata)
|
|
2193
|
-
assistantParts.
|
|
2194
|
+
assistantParts[active.index] = makePart("text", { text: active.text, options: active.options })
|
|
2195
|
+
activeTextDeltas.delete(part.id)
|
|
2194
2196
|
}
|
|
2195
2197
|
break
|
|
2196
2198
|
}
|
|
@@ -2203,7 +2205,8 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
|
|
|
2203
2205
|
|
|
2204
2206
|
// Reasoning Parts (streaming)
|
|
2205
2207
|
case "reasoning-start": {
|
|
2206
|
-
activeReasoningDeltas.set(part.id, { text: "", options: part.metadata })
|
|
2208
|
+
activeReasoningDeltas.set(part.id, { index: assistantParts.length, text: "", options: part.metadata })
|
|
2209
|
+
assistantParts.push(undefined)
|
|
2207
2210
|
break
|
|
2208
2211
|
}
|
|
2209
2212
|
case "reasoning-delta": {
|
|
@@ -2218,7 +2221,8 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
|
|
|
2218
2221
|
if (activeReasoningDeltas.has(part.id)) {
|
|
2219
2222
|
const active = activeReasoningDeltas.get(part.id)!
|
|
2220
2223
|
active.options = mergeOptions(active.options, part.metadata)
|
|
2221
|
-
assistantParts.
|
|
2224
|
+
assistantParts[active.index] = makePart("reasoning", { text: active.text, options: active.options })
|
|
2225
|
+
activeReasoningDeltas.delete(part.id)
|
|
2222
2226
|
}
|
|
2223
2227
|
break
|
|
2224
2228
|
}
|
|
@@ -2272,14 +2276,24 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
|
|
|
2272
2276
|
}
|
|
2273
2277
|
}
|
|
2274
2278
|
|
|
2275
|
-
|
|
2279
|
+
// Keep partial text from unended blocks. Unended reasoning is dropped since it
|
|
2280
|
+
// may lack the signature or encrypted content providers require for replay.
|
|
2281
|
+
for (const active of activeTextDeltas.values()) {
|
|
2282
|
+
if (active.text.length > 0) {
|
|
2283
|
+
assistantParts[active.index] = makePart("text", { text: active.text, options: active.options })
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
const assistantContent = assistantParts.filter((part) => part !== undefined)
|
|
2288
|
+
|
|
2289
|
+
if (assistantContent.length === 0 && toolParts.length === 0) {
|
|
2276
2290
|
return empty
|
|
2277
2291
|
}
|
|
2278
2292
|
|
|
2279
2293
|
const messages: Array<Message> = []
|
|
2280
2294
|
|
|
2281
|
-
if (
|
|
2282
|
-
messages.push(makeMessage("assistant", { content:
|
|
2295
|
+
if (assistantContent.length > 0) {
|
|
2296
|
+
messages.push(makeMessage("assistant", { content: assistantContent }))
|
|
2283
2297
|
}
|
|
2284
2298
|
|
|
2285
2299
|
if (toolParts.length > 0) {
|