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.
Files changed (389) hide show
  1. package/AGENTS.md +24 -0
  2. package/CLAUDE.md +24 -0
  3. package/ai-docs/package.json +2 -2
  4. package/ai-docs/src/52_graphql/10_basics.ts +111 -0
  5. package/ai-docs/src/52_graphql/20_testing.ts +45 -0
  6. package/ai-docs/src/52_graphql/30_batching.ts +116 -0
  7. package/ai-docs/src/52_graphql/40_aliased-batching.ts +122 -0
  8. package/ai-docs/src/52_graphql/50_errors-and-retries.ts +74 -0
  9. package/ai-docs/src/52_graphql/index.md +6 -0
  10. package/dist/Context.d.ts.map +1 -1
  11. package/dist/Context.js +12 -3
  12. package/dist/Context.js.map +1 -1
  13. package/dist/Crypto.d.ts +33 -5
  14. package/dist/Crypto.d.ts.map +1 -1
  15. package/dist/Crypto.js +8 -2
  16. package/dist/Crypto.js.map +1 -1
  17. package/dist/DateTime.d.ts +9 -6
  18. package/dist/DateTime.d.ts.map +1 -1
  19. package/dist/DateTime.js +9 -6
  20. package/dist/DateTime.js.map +1 -1
  21. package/dist/Duration.d.ts +6 -6
  22. package/dist/Duration.d.ts.map +1 -1
  23. package/dist/Duration.js +27 -8
  24. package/dist/Duration.js.map +1 -1
  25. package/dist/FileSystem.d.ts +44 -2
  26. package/dist/FileSystem.d.ts.map +1 -1
  27. package/dist/FileSystem.js.map +1 -1
  28. package/dist/RequestResolver.d.ts.map +1 -1
  29. package/dist/RequestResolver.js +4 -10
  30. package/dist/RequestResolver.js.map +1 -1
  31. package/dist/Scheduler.d.ts +9 -0
  32. package/dist/Scheduler.d.ts.map +1 -1
  33. package/dist/Scheduler.js +9 -0
  34. package/dist/Scheduler.js.map +1 -1
  35. package/dist/Schema.d.ts +481 -1
  36. package/dist/Schema.d.ts.map +1 -1
  37. package/dist/Schema.js +362 -4
  38. package/dist/Schema.js.map +1 -1
  39. package/dist/SchemaGetter.d.ts +4 -0
  40. package/dist/SchemaGetter.d.ts.map +1 -1
  41. package/dist/SchemaGetter.js +52 -33
  42. package/dist/SchemaGetter.js.map +1 -1
  43. package/dist/Sink.d.ts.map +1 -1
  44. package/dist/Sink.js +38 -17
  45. package/dist/Sink.js.map +1 -1
  46. package/dist/Stdio.d.ts +7 -1
  47. package/dist/Stdio.d.ts.map +1 -1
  48. package/dist/Stdio.js +1 -0
  49. package/dist/Stdio.js.map +1 -1
  50. package/dist/Stream.d.ts +22 -11
  51. package/dist/Stream.d.ts.map +1 -1
  52. package/dist/Stream.js +21 -6
  53. package/dist/Stream.js.map +1 -1
  54. package/dist/Utils.js +8 -3
  55. package/dist/Utils.js.map +1 -1
  56. package/dist/ai/AnthropicStructuredOutput.js +0 -2
  57. package/dist/ai/AnthropicStructuredOutput.js.map +1 -1
  58. package/dist/ai/Chat.d.ts +2 -2
  59. package/dist/ai/Chat.d.ts.map +1 -1
  60. package/dist/ai/LanguageModel.d.ts +38 -7
  61. package/dist/ai/LanguageModel.d.ts.map +1 -1
  62. package/dist/ai/LanguageModel.js +25 -7
  63. package/dist/ai/LanguageModel.js.map +1 -1
  64. package/dist/ai/McpServer.d.ts +61 -0
  65. package/dist/ai/McpServer.d.ts.map +1 -1
  66. package/dist/ai/McpServer.js +196 -44
  67. package/dist/ai/McpServer.js.map +1 -1
  68. package/dist/ai/Prompt.d.ts.map +1 -1
  69. package/dist/ai/Prompt.js +28 -5
  70. package/dist/ai/Prompt.js.map +1 -1
  71. package/dist/ai/Response.d.ts +4 -3
  72. package/dist/ai/Response.d.ts.map +1 -1
  73. package/dist/ai/Response.js.map +1 -1
  74. package/dist/ai/internal/mcpProtocol/v2026_07_28.js +5 -0
  75. package/dist/ai/internal/mcpProtocol/v2026_07_28.js.map +1 -1
  76. package/dist/ai/internal/mcpProtocol.js.map +1 -1
  77. package/dist/ai/internal/mcpRuntime.js +1 -0
  78. package/dist/ai/internal/mcpRuntime.js.map +1 -1
  79. package/dist/cli/CliOutput.d.ts +27 -0
  80. package/dist/cli/CliOutput.d.ts.map +1 -1
  81. package/dist/cli/CliOutput.js +33 -10
  82. package/dist/cli/CliOutput.js.map +1 -1
  83. package/dist/cli/Command.d.ts.map +1 -1
  84. package/dist/cli/Command.js +6 -3
  85. package/dist/cli/Command.js.map +1 -1
  86. package/dist/cli/Completions.d.ts +3 -3
  87. package/dist/cli/Completions.d.ts.map +1 -1
  88. package/dist/cli/Completions.js +8 -5
  89. package/dist/cli/Completions.js.map +1 -1
  90. package/dist/cli/GlobalFlag.d.ts +5 -4
  91. package/dist/cli/GlobalFlag.d.ts.map +1 -1
  92. package/dist/cli/GlobalFlag.js +3 -3
  93. package/dist/cli/GlobalFlag.js.map +1 -1
  94. package/dist/cli/Prompt.d.ts +17 -1
  95. package/dist/cli/Prompt.d.ts.map +1 -1
  96. package/dist/cli/Prompt.js +119 -114
  97. package/dist/cli/Prompt.js.map +1 -1
  98. package/dist/cli/internal/ansi.d.ts +0 -1
  99. package/dist/cli/internal/ansi.d.ts.map +1 -1
  100. package/dist/cli/internal/ansi.js +20 -3
  101. package/dist/cli/internal/ansi.js.map +1 -1
  102. package/dist/cli/internal/completions/powershell.d.ts +2 -0
  103. package/dist/cli/internal/completions/powershell.d.ts.map +1 -0
  104. package/dist/cli/internal/completions/powershell.js +273 -0
  105. package/dist/cli/internal/completions/powershell.js.map +1 -0
  106. package/dist/cli/internal/completions/zsh.js +3 -2
  107. package/dist/cli/internal/completions/zsh.js.map +1 -1
  108. package/dist/cli/internal/wizard.d.ts +3 -3
  109. package/dist/cli/internal/wizard.d.ts.map +1 -1
  110. package/dist/cli/internal/wizard.js +21 -18
  111. package/dist/cli/internal/wizard.js.map +1 -1
  112. package/dist/cluster/ClusterSchema.d.ts +28 -0
  113. package/dist/cluster/ClusterSchema.d.ts.map +1 -1
  114. package/dist/cluster/ClusterSchema.js +30 -0
  115. package/dist/cluster/ClusterSchema.js.map +1 -1
  116. package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  117. package/dist/cluster/ClusterWorkflowEngine.js +56 -26
  118. package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
  119. package/dist/cluster/MessageStorage.d.ts +18 -3
  120. package/dist/cluster/MessageStorage.d.ts.map +1 -1
  121. package/dist/cluster/MessageStorage.js +31 -11
  122. package/dist/cluster/MessageStorage.js.map +1 -1
  123. package/dist/cluster/Runners.d.ts.map +1 -1
  124. package/dist/cluster/Runners.js +8 -2
  125. package/dist/cluster/Runners.js.map +1 -1
  126. package/dist/cluster/Sharding.d.ts.map +1 -1
  127. package/dist/cluster/Sharding.js +187 -66
  128. package/dist/cluster/Sharding.js.map +1 -1
  129. package/dist/cluster/SqlMessageStorage.d.ts +59 -19
  130. package/dist/cluster/SqlMessageStorage.d.ts.map +1 -1
  131. package/dist/cluster/SqlMessageStorage.js +84 -55
  132. package/dist/cluster/SqlMessageStorage.js.map +1 -1
  133. package/dist/cluster/SqlRunnerStorage.d.ts +68 -19
  134. package/dist/cluster/SqlRunnerStorage.d.ts.map +1 -1
  135. package/dist/cluster/SqlRunnerStorage.js +191 -113
  136. package/dist/cluster/SqlRunnerStorage.js.map +1 -1
  137. package/dist/cluster/internal/entityManager.js +104 -37
  138. package/dist/cluster/internal/entityManager.js.map +1 -1
  139. package/dist/encoding/Sse.d.ts +6 -0
  140. package/dist/encoding/Sse.d.ts.map +1 -1
  141. package/dist/encoding/Sse.js +62 -70
  142. package/dist/encoding/Sse.js.map +1 -1
  143. package/dist/graphql/GraphQL.d.ts +349 -0
  144. package/dist/graphql/GraphQL.d.ts.map +1 -0
  145. package/dist/graphql/GraphQL.js +207 -0
  146. package/dist/graphql/GraphQL.js.map +1 -0
  147. package/dist/graphql/GraphQLClient.d.ts +562 -0
  148. package/dist/graphql/GraphQLClient.d.ts.map +1 -0
  149. package/dist/graphql/GraphQLClient.js +498 -0
  150. package/dist/graphql/GraphQLClient.js.map +1 -0
  151. package/dist/graphql/GraphQLClientError.d.ts +445 -0
  152. package/dist/graphql/GraphQLClientError.d.ts.map +1 -0
  153. package/dist/graphql/GraphQLClientError.js +479 -0
  154. package/dist/graphql/GraphQLClientError.js.map +1 -0
  155. package/dist/graphql/GraphQLGroup.d.ts +118 -0
  156. package/dist/graphql/GraphQLGroup.d.ts.map +1 -0
  157. package/dist/graphql/GraphQLGroup.js +109 -0
  158. package/dist/graphql/GraphQLGroup.js.map +1 -0
  159. package/dist/graphql/GraphQLMiddleware.d.ts +249 -0
  160. package/dist/graphql/GraphQLMiddleware.d.ts.map +1 -0
  161. package/dist/graphql/GraphQLMiddleware.js +130 -0
  162. package/dist/graphql/GraphQLMiddleware.js.map +1 -0
  163. package/dist/graphql/GraphQLProtocol.d.ts +731 -0
  164. package/dist/graphql/GraphQLProtocol.d.ts.map +1 -0
  165. package/dist/graphql/GraphQLProtocol.js +719 -0
  166. package/dist/graphql/GraphQLProtocol.js.map +1 -0
  167. package/dist/graphql/GraphQLResolver.d.ts +453 -0
  168. package/dist/graphql/GraphQLResolver.d.ts.map +1 -0
  169. package/dist/graphql/GraphQLResolver.js +552 -0
  170. package/dist/graphql/GraphQLResolver.js.map +1 -0
  171. package/dist/graphql/index.d.ts +40 -0
  172. package/dist/graphql/index.d.ts.map +1 -0
  173. package/dist/graphql/index.js +41 -0
  174. package/dist/graphql/index.js.map +1 -0
  175. package/dist/http/FindMyWay/internal/router.js +3 -6
  176. package/dist/http/FindMyWay/internal/router.js.map +1 -1
  177. package/dist/http/HttpClient.d.ts +21 -0
  178. package/dist/http/HttpClient.d.ts.map +1 -1
  179. package/dist/http/HttpClient.js +63 -12
  180. package/dist/http/HttpClient.js.map +1 -1
  181. package/dist/http/HttpEffect.d.ts.map +1 -1
  182. package/dist/http/HttpEffect.js +14 -7
  183. package/dist/http/HttpEffect.js.map +1 -1
  184. package/dist/http/HttpMiddleware.d.ts.map +1 -1
  185. package/dist/http/HttpMiddleware.js +31 -32
  186. package/dist/http/HttpMiddleware.js.map +1 -1
  187. package/dist/http/HttpRouter.d.ts +35 -0
  188. package/dist/http/HttpRouter.d.ts.map +1 -1
  189. package/dist/http/HttpRouter.js +35 -0
  190. package/dist/http/HttpRouter.js.map +1 -1
  191. package/dist/http/HttpServerResponse.d.ts +8 -6
  192. package/dist/http/HttpServerResponse.d.ts.map +1 -1
  193. package/dist/http/HttpServerResponse.js +27 -16
  194. package/dist/http/HttpServerResponse.js.map +1 -1
  195. package/dist/http/HttpTraceContext.d.ts.map +1 -1
  196. package/dist/http/HttpTraceContext.js +4 -2
  197. package/dist/http/HttpTraceContext.js.map +1 -1
  198. package/dist/http/Multipart.d.ts.map +1 -1
  199. package/dist/http/Multipart.js +15 -1
  200. package/dist/http/Multipart.js.map +1 -1
  201. package/dist/http-api/HttpApiScalar.d.ts +5 -0
  202. package/dist/http-api/HttpApiScalar.d.ts.map +1 -1
  203. package/dist/http-api/HttpApiScalar.js.map +1 -1
  204. package/dist/internal/dateTime.js +40 -35
  205. package/dist/internal/dateTime.js.map +1 -1
  206. package/dist/internal/effect.js +19 -9
  207. package/dist/internal/effect.js.map +1 -1
  208. package/dist/internal/graphql.d.ts +2 -0
  209. package/dist/internal/graphql.d.ts.map +1 -0
  210. package/dist/internal/graphql.js +195 -0
  211. package/dist/internal/graphql.js.map +1 -0
  212. package/dist/internal/graphqlHeaders.d.ts +4 -0
  213. package/dist/internal/graphqlHeaders.d.ts.map +1 -0
  214. package/dist/internal/graphqlHeaders.js +11 -0
  215. package/dist/internal/graphqlHeaders.js.map +1 -0
  216. package/dist/internal/request.js +20 -1
  217. package/dist/internal/request.js.map +1 -1
  218. package/dist/internal/schema/toCodeDocument.js +4 -1
  219. package/dist/internal/schema/toCodeDocument.js.map +1 -1
  220. package/dist/internal/schema/toJsonSchemaDocument.js +29 -5
  221. package/dist/internal/schema/toJsonSchemaDocument.js.map +1 -1
  222. package/dist/internal/version.js +1 -1
  223. package/dist/net/AddressResolver.d.ts +145 -0
  224. package/dist/net/AddressResolver.d.ts.map +1 -0
  225. package/dist/net/AddressResolver.js +114 -0
  226. package/dist/net/AddressResolver.js.map +1 -0
  227. package/dist/net/Dns.d.ts +596 -0
  228. package/dist/net/Dns.d.ts.map +1 -0
  229. package/dist/net/Dns.js +549 -0
  230. package/dist/net/Dns.js.map +1 -0
  231. package/dist/net/Host.d.ts +369 -0
  232. package/dist/net/Host.d.ts.map +1 -0
  233. package/dist/net/Host.js +397 -0
  234. package/dist/net/Host.js.map +1 -0
  235. package/dist/net/IpInterface.d.ts +76 -3
  236. package/dist/net/IpInterface.d.ts.map +1 -1
  237. package/dist/net/IpInterface.js +53 -4
  238. package/dist/net/IpInterface.js.map +1 -1
  239. package/dist/net/IpNetwork.d.ts +82 -8
  240. package/dist/net/IpNetwork.d.ts.map +1 -1
  241. package/dist/net/IpNetwork.js +60 -4
  242. package/dist/net/IpNetwork.js.map +1 -1
  243. package/dist/net/NetAddress.d.ts +396 -35
  244. package/dist/net/NetAddress.d.ts.map +1 -1
  245. package/dist/net/NetAddress.js +358 -34
  246. package/dist/net/NetAddress.js.map +1 -1
  247. package/dist/net/index.d.ts +15 -0
  248. package/dist/net/index.d.ts.map +1 -1
  249. package/dist/net/index.js +15 -0
  250. package/dist/net/index.js.map +1 -1
  251. package/dist/observability/OtlpMetrics.d.ts.map +1 -1
  252. package/dist/observability/OtlpMetrics.js +15 -21
  253. package/dist/observability/OtlpMetrics.js.map +1 -1
  254. package/dist/observability/PrometheusMetrics.js +1 -0
  255. package/dist/observability/PrometheusMetrics.js.map +1 -1
  256. package/dist/persistence/PersistedQueue.d.ts +32 -3
  257. package/dist/persistence/PersistedQueue.d.ts.map +1 -1
  258. package/dist/persistence/PersistedQueue.js +75 -16
  259. package/dist/persistence/PersistedQueue.js.map +1 -1
  260. package/dist/rpc/RpcClient.d.ts +22 -6
  261. package/dist/rpc/RpcClient.d.ts.map +1 -1
  262. package/dist/rpc/RpcClient.js +12 -5
  263. package/dist/rpc/RpcClient.js.map +1 -1
  264. package/dist/rpc/RpcServer.d.ts +6 -0
  265. package/dist/rpc/RpcServer.d.ts.map +1 -1
  266. package/dist/rpc/RpcServer.js +78 -29
  267. package/dist/rpc/RpcServer.js.map +1 -1
  268. package/dist/socket/DatagramSocket.d.ts +456 -0
  269. package/dist/socket/DatagramSocket.d.ts.map +1 -0
  270. package/dist/socket/DatagramSocket.js +535 -0
  271. package/dist/socket/DatagramSocket.js.map +1 -0
  272. package/dist/socket/Socket.d.ts +20 -1
  273. package/dist/socket/Socket.d.ts.map +1 -1
  274. package/dist/socket/Socket.js +9 -1
  275. package/dist/socket/Socket.js.map +1 -1
  276. package/dist/socket/index.d.ts +5 -0
  277. package/dist/socket/index.d.ts.map +1 -1
  278. package/dist/socket/index.js +5 -0
  279. package/dist/socket/index.js.map +1 -1
  280. package/dist/sql/Migrator.d.ts.map +1 -1
  281. package/dist/sql/Migrator.js +7 -2
  282. package/dist/sql/Migrator.js.map +1 -1
  283. package/dist/sql/SqlClient.d.ts +7 -1
  284. package/dist/sql/SqlClient.d.ts.map +1 -1
  285. package/dist/sql/SqlClient.js +31 -6
  286. package/dist/sql/SqlClient.js.map +1 -1
  287. package/dist/testing/TestCrypto.d.ts +73 -0
  288. package/dist/testing/TestCrypto.d.ts.map +1 -0
  289. package/dist/testing/TestCrypto.js +87 -0
  290. package/dist/testing/TestCrypto.js.map +1 -0
  291. package/dist/testing/index.d.ts +5 -0
  292. package/dist/testing/index.d.ts.map +1 -1
  293. package/dist/testing/index.js +5 -0
  294. package/dist/testing/index.js.map +1 -1
  295. package/dist/workflow/DurableDeferred.d.ts +54 -0
  296. package/dist/workflow/DurableDeferred.d.ts.map +1 -1
  297. package/dist/workflow/DurableDeferred.js +108 -50
  298. package/dist/workflow/DurableDeferred.js.map +1 -1
  299. package/dist/workflow/WorkflowEngine.d.ts +24 -2
  300. package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
  301. package/dist/workflow/WorkflowEngine.js +13 -3
  302. package/dist/workflow/WorkflowEngine.js.map +1 -1
  303. package/package.json +6 -3
  304. package/src/Context.ts +12 -3
  305. package/src/Crypto.ts +43 -9
  306. package/src/DateTime.ts +9 -6
  307. package/src/Duration.ts +36 -16
  308. package/src/FileSystem.ts +48 -2
  309. package/src/RequestResolver.ts +4 -10
  310. package/src/Scheduler.ts +9 -0
  311. package/src/Schema.ts +677 -5
  312. package/src/SchemaGetter.ts +47 -35
  313. package/src/Sink.ts +45 -18
  314. package/src/Stdio.ts +9 -2
  315. package/src/Stream.ts +44 -15
  316. package/src/Utils.ts +8 -5
  317. package/src/ai/AnthropicStructuredOutput.ts +2 -2
  318. package/src/ai/Chat.ts +2 -2
  319. package/src/ai/LanguageModel.ts +96 -24
  320. package/src/ai/McpServer.ts +288 -94
  321. package/src/ai/Prompt.ts +24 -10
  322. package/src/ai/Response.ts +6 -3
  323. package/src/ai/internal/mcpProtocol/v2026_07_28.ts +5 -0
  324. package/src/ai/internal/mcpProtocol.ts +3 -0
  325. package/src/ai/internal/mcpRuntime.ts +2 -0
  326. package/src/cli/CliOutput.ts +33 -16
  327. package/src/cli/Command.ts +4 -3
  328. package/src/cli/Completions.ts +9 -6
  329. package/src/cli/GlobalFlag.ts +19 -11
  330. package/src/cli/Prompt.ts +159 -149
  331. package/src/cli/internal/ansi.ts +26 -3
  332. package/src/cli/internal/completions/powershell.ts +322 -0
  333. package/src/cli/internal/completions/zsh.ts +4 -2
  334. package/src/cli/internal/wizard.ts +34 -26
  335. package/src/cluster/ClusterSchema.ts +32 -0
  336. package/src/cluster/ClusterWorkflowEngine.ts +64 -36
  337. package/src/cluster/MessageStorage.ts +45 -14
  338. package/src/cluster/Runners.ts +8 -2
  339. package/src/cluster/Sharding.ts +219 -58
  340. package/src/cluster/SqlMessageStorage.ts +118 -84
  341. package/src/cluster/SqlRunnerStorage.ts +224 -128
  342. package/src/cluster/internal/entityManager.ts +151 -55
  343. package/src/encoding/Sse.ts +69 -79
  344. package/src/graphql/GraphQL.ts +494 -0
  345. package/src/graphql/GraphQLClient.ts +912 -0
  346. package/src/graphql/GraphQLClientError.ts +542 -0
  347. package/src/graphql/GraphQLGroup.ts +167 -0
  348. package/src/graphql/GraphQLMiddleware.ts +253 -0
  349. package/src/graphql/GraphQLProtocol.ts +1286 -0
  350. package/src/graphql/GraphQLResolver.ts +1010 -0
  351. package/src/graphql/index.ts +48 -0
  352. package/src/http/FindMyWay/internal/router.ts +4 -7
  353. package/src/http/HttpClient.ts +110 -12
  354. package/src/http/HttpEffect.ts +11 -5
  355. package/src/http/HttpMiddleware.ts +33 -34
  356. package/src/http/HttpRouter.ts +35 -0
  357. package/src/http/HttpServerResponse.ts +28 -16
  358. package/src/http/HttpTraceContext.ts +8 -2
  359. package/src/http/Multipart.ts +17 -1
  360. package/src/http-api/HttpApiScalar.ts +5 -0
  361. package/src/internal/dateTime.ts +13 -10
  362. package/src/internal/effect.ts +22 -15
  363. package/src/internal/graphql.ts +263 -0
  364. package/src/internal/graphqlHeaders.ts +11 -0
  365. package/src/internal/request.ts +31 -3
  366. package/src/internal/schema/toCodeDocument.ts +7 -1
  367. package/src/internal/schema/toJsonSchemaDocument.ts +34 -2
  368. package/src/internal/version.ts +1 -1
  369. package/src/net/AddressResolver.ts +222 -0
  370. package/src/net/Dns.ts +1035 -0
  371. package/src/net/Host.ts +544 -0
  372. package/src/net/IpInterface.ts +117 -7
  373. package/src/net/IpNetwork.ts +116 -17
  374. package/src/net/NetAddress.ts +620 -76
  375. package/src/net/index.ts +18 -0
  376. package/src/observability/OtlpMetrics.ts +12 -23
  377. package/src/observability/PrometheusMetrics.ts +1 -0
  378. package/src/persistence/PersistedQueue.ts +146 -53
  379. package/src/rpc/RpcClient.ts +41 -19
  380. package/src/rpc/RpcServer.ts +115 -40
  381. package/src/socket/DatagramSocket.ts +919 -0
  382. package/src/socket/Socket.ts +28 -2
  383. package/src/socket/index.ts +6 -0
  384. package/src/sql/Migrator.ts +21 -5
  385. package/src/sql/SqlClient.ts +39 -15
  386. package/src/testing/TestCrypto.ts +94 -0
  387. package/src/testing/index.ts +6 -0
  388. package/src/workflow/DurableDeferred.ts +160 -47
  389. package/src/workflow/WorkflowEngine.ts +50 -5
@@ -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
- const pendingListChanges = new Set<string>()
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
- if (!pendingListChanges.has(message.tag)) {
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
- const tag = message.tag
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 handlers = yield* runtime.installHandlers({
811
- core: internalState.get(server)!.core,
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
- ...(!protocol.supportsNotifications ? {} : {
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
- // Ack & co are not part of FromServerEncoded, but the JSON-RPC
853
- // serializer encodes them symmetrically for reverse control flow
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 cancelled = requests?.get(key)?.cancelled
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
- const { notificationDelivery, notifications } = internalState.get(server)!
1235
- yield* Effect.acquireRelease(
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._tag === "LoggingMessage" || notification._tag === "Progress"
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
- const router = yield* HttpRouter.HttpRouter
1619
- const allowSessionTermination = options.allowSessionTermination === true &&
1620
- runtime.protocols.some((protocol) => protocol.runtime._tag === "Stateful")
1621
- const forbidden = Effect.succeed(HttpServerResponse.empty({ status: 403 }))
1622
- const withAllowedOrigin = (
1623
- handler: (request: HttpServerRequest.HttpServerRequest) => Effect.Effect<HttpServerResponse.HttpServerResponse>
1624
- ) =>
1625
- (request: HttpServerRequest.HttpServerRequest) =>
1626
- isAllowedMcpOrigin(request, options.allowedOrigins) ? handler(request) : forbidden
1627
- const methodNotAllowedResponse = Effect.succeed(HttpServerResponse.empty({
1628
- status: 405,
1629
- headers: { allow: allowSessionTermination ? "POST, DELETE" : "POST" }
1630
- }))
1631
- const methodNotAllowed = withAllowedOrigin(() => methodNotAllowedResponse)
1632
- for (const method of ["GET", "PUT", "PATCH", "OPTIONS"] as const) {
1633
- yield* router.add(method, options.path, methodNotAllowed)
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
- yield* router.add(
1636
- "DELETE",
1637
- options.path,
1638
- allowSessionTermination
1639
- ? withAllowedOrigin((request) => runtime.terminateHttpSession(request.headers))
1640
- : methodNotAllowed
1641
- )
1642
- yield* router.add("POST", options.path, (request) => {
1643
- if (!isAllowedMcpOrigin(request, options.allowedOrigins)) {
1644
- return Effect.succeed(HttpServerResponse.empty({ status: 403 }))
1645
- }
1646
- if (mcpMediaTypes(request.headers["content-type"])[0] !== "application/json") {
1647
- return Effect.succeed(HttpServerResponse.empty({ status: 415 }))
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 accepted = mcpMediaTypes(request.headers["accept"])
1650
- if (!accepted.includes("application/json") || !accepted.includes("text/event-stream")) {
1651
- return Effect.succeed(HttpServerResponse.empty({ status: 406 }))
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
- return Effect.gen(function*() {
1654
- const parsed = yield* Effect.result(
1655
- Effect.flatMap(request.text, Schema.decodeEffect(Schema.UnknownFromJsonString))
1851
+ if (admission.acknowledge) {
1852
+ return yield* Effect.catchCause(
1853
+ Effect.map(httpEffect, toResponse),
1854
+ () => Effect.succeed(HttpServerResponse.empty({ status: 202 }))
1656
1855
  )
1657
- const admission = runtime.admitHttp(request.headers, parsed)
1658
- if (admission._tag === "Rejected") {
1659
- return admission.response
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
- const response = Effect.map(httpEffect, (response) => {
1662
- // Completed responses may already contain notifications followed by the result.
1663
- const hasMultipleMessages = response.body._tag === "Uint8Array" &&
1664
- response.body.body.subarray(0, -1).includes(10)
1665
- return admission.isSubscription || response.body._tag === "Stream" || hasMultipleMessages
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
- return protocol
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.push(makePart("text", active))
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.push(makePart("reasoning", active))
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
- if (assistantParts.length === 0 && toolParts.length === 0) {
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 (assistantParts.length > 0) {
2282
- messages.push(makeMessage("assistant", { content: assistantParts }))
2295
+ if (assistantContent.length > 0) {
2296
+ messages.push(makeMessage("assistant", { content: assistantContent }))
2283
2297
  }
2284
2298
 
2285
2299
  if (toolParts.length > 0) {