@code-yeongyu/senpi 2026.10.25-4 → 2026.10.26-2

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 (260) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/bundle/chunks/{anthropic-messages-WBASBTIW.js → anthropic-messages-ODSFJCXR.js} +3 -3
  3. package/dist/bundle/chunks/{args-B444OQBK.js → args-SWKJAYQD.js} +1 -1
  4. package/dist/bundle/chunks/azure-openai-responses-REGXS4XO.js +2 -0
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +3 -3
  6. package/dist/bundle/chunks/chunk-2XF6W6F6.js +410 -0
  7. package/dist/bundle/chunks/{chunk-WDPQ6CZJ.js → chunk-4QVORFQ2.js} +1 -1
  8. package/dist/bundle/chunks/{chunk-6AOVUTUR.js → chunk-5W3DTM2J.js} +2 -2
  9. package/dist/bundle/chunks/{chunk-E6WYH3ZL.js → chunk-6GAMRAVM.js} +9 -7
  10. package/dist/bundle/chunks/{chunk-N7IN46Q5.js → chunk-7IZH2KQX.js} +9 -7
  11. package/dist/bundle/chunks/{chunk-7GZUFOM3.js → chunk-ARSENVDY.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-STMXIBXF.js → chunk-CNDPCYNT.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-XF3LJC3O.js → chunk-CYOHFREV.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-E6D4RIT7.js → chunk-DNDZPKIQ.js} +2 -2
  15. package/dist/bundle/chunks/{chunk-E4LBKOZG.js → chunk-EQNUL2WB.js} +4 -4
  16. package/dist/bundle/chunks/{chunk-2RB5EL4X.js → chunk-ESNSRUW4.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-4AOMNOGY.js → chunk-FQDARFHT.js} +1 -1
  18. package/dist/bundle/chunks/{chunk-BWA4FVFW.js → chunk-GPPOPK76.js} +3 -3
  19. package/dist/bundle/chunks/{chunk-SBGIXMX3.js → chunk-GWGNVYPB.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-KKD4CYCI.js → chunk-H4WJ4LY3.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-V2UYXTRR.js → chunk-HWTD4QF3.js} +1 -1
  22. package/dist/bundle/chunks/chunk-I2WFLKZ2.js +5 -0
  23. package/dist/bundle/chunks/{chunk-WD6WBYC5.js → chunk-IHKHCWWG.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-T36LJ6MW.js → chunk-IY4G44MN.js} +1 -1
  25. package/dist/bundle/chunks/{chunk-BZ7726QO.js → chunk-J2YAYTB2.js} +1 -1
  26. package/dist/bundle/chunks/{chunk-UFJ2K7Q7.js → chunk-JH7MVRNJ.js} +1 -1
  27. package/dist/bundle/chunks/chunk-KBSFTXAT.js +2 -0
  28. package/dist/bundle/chunks/{chunk-OLCDIITS.js → chunk-KRO63T4Q.js} +1 -1
  29. package/dist/bundle/chunks/{chunk-TTU2RNVX.js → chunk-KTDIIF2F.js} +1 -1
  30. package/dist/bundle/chunks/{chunk-KNVNKDER.js → chunk-L2O6K3SJ.js} +1 -1
  31. package/dist/bundle/chunks/{chunk-3BTN337S.js → chunk-NDNIMC5W.js} +1 -1
  32. package/dist/bundle/chunks/{chunk-ITGYN22E.js → chunk-ODRGPC5S.js} +1 -1
  33. package/dist/bundle/chunks/{chunk-4MOZXOJX.js → chunk-PDG4XWO2.js} +1 -1
  34. package/dist/bundle/chunks/{chunk-3QCYBOKN.js → chunk-PPECSSWX.js} +1 -1
  35. package/dist/bundle/chunks/{chunk-4P7WARQZ.js → chunk-PY4ZQHTN.js} +1 -1
  36. package/dist/bundle/chunks/{chunk-C2MQ4QXN.js → chunk-RPW3XQW3.js} +48 -42
  37. package/dist/bundle/chunks/{chunk-JMOUU75H.js → chunk-RQ5JTXUC.js} +1 -1
  38. package/dist/bundle/chunks/chunk-TQ67PB7U.js +13 -0
  39. package/dist/bundle/chunks/{chunk-5JJDR6XI.js → chunk-UJPG4ROT.js} +1 -1
  40. package/dist/bundle/chunks/{chunk-FHXR4ZAC.js → chunk-XM36IFP3.js} +1 -1
  41. package/dist/bundle/chunks/{chunk-PT6JJUN2.js → chunk-XMNPK472.js} +1 -1
  42. package/dist/bundle/chunks/{client-WTYEICLH.js → chunk-Y3TMJV3J.js} +3 -3
  43. package/dist/bundle/chunks/{chunk-GSSD3R5X.js → chunk-Z6BREXV3.js} +1 -1
  44. package/dist/bundle/chunks/{chunk-KXB5WIS5.js → chunk-Z6SAQ5O3.js} +1 -1
  45. package/dist/bundle/chunks/{chunk-LOIF6PHI.js → chunk-ZAUVY6ZC.js} +3 -3
  46. package/dist/bundle/chunks/{chunk-7CNOCJCN.js → chunk-ZP6JJDYV.js} +1 -1
  47. package/dist/bundle/chunks/cli-main-Q2GA53XM.js +3 -0
  48. package/dist/bundle/chunks/client-LOS6GSEW.js +2 -0
  49. package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-K5H6BJP3.js → cloudflare-workers-ai-system-one-WDHT7BBV.js} +1 -1
  50. package/dist/bundle/chunks/cursor-agent.js +3 -3
  51. package/dist/bundle/chunks/devin-agent.js +3 -3
  52. package/dist/bundle/chunks/dispatch-5R3TYAYB.js +9 -0
  53. package/dist/bundle/chunks/google-generative-ai-FHYFXKLA.js +2 -0
  54. package/dist/bundle/chunks/google-vertex-5DWE5U5K.js +2 -0
  55. package/dist/bundle/chunks/{help-fast-path-3Z26DQSV.js → help-fast-path-FETLIF3W.js} +1 -1
  56. package/dist/bundle/chunks/host-command-XEB7SHT3.js +2 -0
  57. package/dist/bundle/chunks/{host-lifecycle-3VHHIGDL.js → host-lifecycle-HX3EGRRW.js} +1 -1
  58. package/dist/bundle/chunks/host-lifecycle.js +2 -2
  59. package/dist/bundle/chunks/{host-session-command-56XF6ACS.js → host-session-command-FFN52LXJ.js} +1 -1
  60. package/dist/bundle/chunks/{host-socket-request-QL2WEZTN.js → host-socket-request-K4LCUOUD.js} +1 -1
  61. package/dist/bundle/chunks/interactive-mode-5DEKY3YW.js +2 -0
  62. package/dist/bundle/chunks/{mcp-presenter-bootstrap-XUSCQVRC.js → mcp-presenter-bootstrap-FOMKF4TU.js} +1 -1
  63. package/dist/bundle/chunks/{mistral-conversations-DH44NOYV.js → mistral-conversations-I2PI7DDF.js} +1 -1
  64. package/dist/bundle/chunks/{multi-session-host-Z2CW4SDM.js → multi-session-host-OPR5VBFO.js} +6 -7
  65. package/dist/bundle/chunks/{openai-codex-responses-SERAFKAS.js → openai-codex-responses-WSEX6SH7.js} +1 -1
  66. package/dist/bundle/chunks/{openai-completions-BZB5O45A.js → openai-completions-YCBHTBSS.js} +1 -1
  67. package/dist/bundle/chunks/openai-responses-GXNSQF4A.js +3 -0
  68. package/dist/bundle/chunks/{package-manager-cli-N55U6PEO.js → package-manager-cli-W55E3LHP.js} +1 -1
  69. package/dist/bundle/chunks/{pi-messages-VXATX45B.js → pi-messages-O6GISIRW.js} +2 -2
  70. package/dist/bundle/chunks/{role-JVSBNKGE.js → role-SHIZQKK4.js} +1 -1
  71. package/dist/bundle/chunks/{rotation-stream-CBGCBYHV.js → rotation-stream-2PCZWNKF.js} +1 -1
  72. package/dist/bundle/chunks/rpc-mode-IDUJTSYS.js +2 -0
  73. package/dist/bundle/chunks/{rpc-monitor-source-AE3C6EQV.js → rpc-monitor-source-DRZXJHTL.js} +1 -1
  74. package/dist/bundle/chunks/{schedule-command-VS7CZC2J.js → schedule-command-B7RRSANU.js} +1 -1
  75. package/dist/bundle/chunks/session-control-endpoint-LMZH5JLH.js +3 -0
  76. package/dist/bundle/chunks/{session-picker-E4KUJDLO.js → session-picker-DTGBOXDR.js} +1 -1
  77. package/dist/bundle/chunks/session-worker.js +214 -205
  78. package/dist/bundle/chunks/{typesafe-system-one-P75WJ767.js → typesafe-system-one-XGYJJVSE.js} +1 -1
  79. package/dist/bundle/cli.js +2 -2
  80. package/dist/bundle/host-supervisor.js +2 -2
  81. package/dist/bundle/index.js +1 -1
  82. package/dist/bundle/rpc-entry.js +1 -1
  83. package/dist/bundle/runtime-manifest.json +1 -1
  84. package/dist/cli/host-command-args.d.ts +1 -0
  85. package/dist/cli/host-command-args.js +5 -0
  86. package/dist/cli/host-command.js +7 -1
  87. package/dist/cli.js +21 -3
  88. package/dist/core/agent-session.d.ts +11 -1
  89. package/dist/core/agent-session.js +19 -2
  90. package/dist/core/extensions/builtin/compaction/index.js +7 -1
  91. package/dist/core/extensions/builtin/herdr/index.js +9 -5
  92. package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.d.ts +6 -0
  93. package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.js +25 -0
  94. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.d.ts +3 -3
  95. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +50 -19
  96. package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.js +4 -3
  97. package/dist/core/extensions/builtin/mcp/auth/oauth.js +19 -12
  98. package/dist/core/extensions/builtin/mcp/catalog-cache.d.ts +2 -1
  99. package/dist/core/extensions/builtin/mcp/catalog-cache.js +19 -6
  100. package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -2
  101. package/dist/core/extensions/builtin/mcp/catalog.js +20 -3
  102. package/dist/core/extensions/builtin/mcp/expose/call.d.ts +2 -1
  103. package/dist/core/extensions/builtin/mcp/expose/call.js +2 -1
  104. package/dist/core/extensions/builtin/mcp/expose/register.js +1 -1
  105. package/dist/core/extensions/builtin/mcp/expose/schema-compat.d.ts +2 -2
  106. package/dist/core/extensions/builtin/mcp/expose/schema-compat.js +11 -1
  107. package/dist/core/extensions/builtin/mcp/invocation.js +16 -2
  108. package/dist/core/extensions/builtin/mcp/modern/dispatch.js +1 -0
  109. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +4 -1
  110. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +33 -35
  111. package/dist/core/extensions/builtin/mcp/modern/request.d.ts +1 -1
  112. package/dist/core/extensions/builtin/mcp/modern/request.js +4 -1
  113. package/dist/core/extensions/builtin/mcp/modern/sdk-bridge.js +18 -3
  114. package/dist/core/extensions/builtin/mcp/modern/stdio-probe.js +23 -0
  115. package/dist/core/extensions/builtin/mcp/modern/subscriptions.d.ts +14 -0
  116. package/dist/core/extensions/builtin/mcp/modern/subscriptions.js +226 -0
  117. package/dist/core/extensions/builtin/mcp/modern/tool-schema.d.ts +13 -0
  118. package/dist/core/extensions/builtin/mcp/modern/tool-schema.js +75 -0
  119. package/dist/core/extensions/builtin/mcp/modern/tools.d.ts +107 -0
  120. package/dist/core/extensions/builtin/mcp/modern/tools.js +53 -0
  121. package/dist/core/extensions/builtin/mcp/modern/transport.d.ts +2 -0
  122. package/dist/core/extensions/builtin/mcp/modern/transport.js +4 -2
  123. package/dist/core/extensions/builtin/mcp/resources.js +4 -0
  124. package/dist/core/extensions/builtin/mcp/tool-client.d.ts +13 -0
  125. package/dist/core/extensions/builtin/mcp/tool-client.js +17 -0
  126. package/dist/core/extensions/builtin/mcp/tool-contracts.d.ts +19 -0
  127. package/dist/core/extensions/builtin/mcp/tool-contracts.js +2 -0
  128. package/dist/core/extensions/builtin/mcp/wrap.js +2 -2
  129. package/dist/core/extensions/builtin/permission-system/bash-command-segments.d.ts +37 -0
  130. package/dist/core/extensions/builtin/permission-system/bash-command-segments.js +899 -0
  131. package/dist/core/extensions/builtin/permission-system/parsers.d.ts +2 -0
  132. package/dist/core/extensions/builtin/permission-system/parsers.js +44 -10
  133. package/dist/core/extensions/builtin/terminal/extension-state.js +2 -1
  134. package/dist/core/extensions/builtin/vertex-model-garden/auth.d.ts +9 -0
  135. package/dist/core/extensions/builtin/vertex-model-garden/auth.js +17 -1
  136. package/dist/core/extensions/builtin/websearch/websearch/providers/google.d.ts +6 -0
  137. package/dist/core/extensions/builtin/websearch/websearch/providers/google.js +6 -1
  138. package/dist/core/extensions/types.d.ts +2 -0
  139. package/dist/core/lifecycle-children.d.ts +3 -1
  140. package/dist/core/lifecycle-children.js +79 -15
  141. package/dist/core/lifecycle-record.js +1 -24
  142. package/dist/core/lifecycle-session-binding.js +2 -1
  143. package/dist/core/lifecycle-wait-items.d.ts +17 -0
  144. package/dist/core/lifecycle-wait-items.js +72 -0
  145. package/dist/core/model-runtime.js +6 -1
  146. package/dist/core/provider-api-key-auth.d.ts +6 -1
  147. package/dist/core/provider-api-key-auth.js +11 -4
  148. package/dist/core/provider-composer.d.ts +8 -1
  149. package/dist/core/provider-composer.js +7 -3
  150. package/dist/core/sdk.js +4 -1
  151. package/dist/core/session-activity.js +2 -0
  152. package/dist/core/session-lifecycle-types.d.ts +20 -4
  153. package/dist/core/session-lifecycle.js +35 -4
  154. package/dist/core/structured-session-lane.d.ts +33 -0
  155. package/dist/core/structured-session-lane.js +62 -0
  156. package/dist/core/work-custody-record.d.ts +2 -1
  157. package/dist/core/work-custody-record.js +2 -0
  158. package/dist/core/work-obligations.d.ts +1 -1
  159. package/dist/core/work-obligations.js +8 -2
  160. package/dist/modes/interactive/session-control-commands.js +21 -4
  161. package/dist/modes/interactive/session-control-rpc-commands.d.ts +1 -1
  162. package/dist/modes/interactive/session-control-rpc-commands.js +19 -0
  163. package/dist/modes/json-event.d.ts +79 -1
  164. package/dist/modes/json-event.js +79 -1
  165. package/dist/modes/rpc/compact-content-index.d.ts +85 -0
  166. package/dist/modes/rpc/compact-content-index.js +254 -0
  167. package/dist/modes/rpc/compact-content-store.d.ts +59 -0
  168. package/dist/modes/rpc/compact-content-store.js +134 -0
  169. package/dist/modes/rpc/compact-index-pages.d.ts +12 -0
  170. package/dist/modes/rpc/compact-index-pages.js +31 -0
  171. package/dist/modes/rpc/compact-session-state.d.ts +83 -0
  172. package/dist/modes/rpc/compact-session-state.js +197 -0
  173. package/dist/modes/rpc/compact-spool-dir.d.ts +8 -0
  174. package/dist/modes/rpc/compact-spool-dir.js +94 -0
  175. package/dist/modes/rpc/compact-spool-storage.d.ts +53 -0
  176. package/dist/modes/rpc/compact-spool-storage.js +162 -0
  177. package/dist/modes/rpc/compact-storage-io.d.ts +28 -0
  178. package/dist/modes/rpc/compact-storage-io.js +25 -0
  179. package/dist/modes/rpc/compact-transition.d.ts +41 -0
  180. package/dist/modes/rpc/compact-transition.js +12 -0
  181. package/dist/modes/rpc/connection-handler.d.ts +4 -0
  182. package/dist/modes/rpc/connection-handler.js +51 -13
  183. package/dist/modes/rpc/connection-question-bridge.d.ts +2 -1
  184. package/dist/modes/rpc/connection-question-bridge.js +6 -2
  185. package/dist/modes/rpc/extension-ui-response.d.ts +7 -2
  186. package/dist/modes/rpc/extension-ui-response.js +27 -7
  187. package/dist/modes/rpc/get-snapshot.d.ts +18 -0
  188. package/dist/modes/rpc/get-snapshot.js +43 -0
  189. package/dist/modes/rpc/host-capabilities.js +3 -0
  190. package/dist/modes/rpc/host-ensure-start.js +6 -3
  191. package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
  192. package/dist/modes/rpc/host-ensure.js +1 -0
  193. package/dist/modes/rpc/host-handoff.d.ts +2 -0
  194. package/dist/modes/rpc/host-handover-request.d.ts +1 -1
  195. package/dist/modes/rpc/host-handover-request.js +4 -3
  196. package/dist/modes/rpc/host-handover-wire.js +6 -0
  197. package/dist/modes/rpc/host-idle-handover.d.ts +2 -0
  198. package/dist/modes/rpc/host-idle-handover.js +3 -1
  199. package/dist/modes/rpc/host-observe-request.js +1 -0
  200. package/dist/modes/rpc/host-runner.d.ts +1 -0
  201. package/dist/modes/rpc/host-runner.js +9 -7
  202. package/dist/modes/rpc/host-successor.js +4 -0
  203. package/dist/modes/rpc/json-normal-value.d.ts +6 -0
  204. package/dist/modes/rpc/json-normal-value.js +100 -0
  205. package/dist/modes/rpc/mcp-presenter-bootstrap.js +5 -0
  206. package/dist/modes/rpc/media-placeholders.d.ts +9 -3
  207. package/dist/modes/rpc/media-placeholders.js +12 -4
  208. package/dist/modes/rpc/rpc-host-command-types.d.ts +2 -0
  209. package/dist/modes/rpc/rpc-question-types.d.ts +1 -0
  210. package/dist/modes/rpc/rpc-read-response-types.d.ts +7 -0
  211. package/dist/modes/rpc/rpc-session-command-types.d.ts +6 -0
  212. package/dist/modes/rpc/rpc-session-state-types.d.ts +3 -0
  213. package/dist/modes/rpc/rpc-session-state.js +2 -0
  214. package/dist/modes/rpc/rpc-types.d.ts +1 -0
  215. package/dist/modes/rpc/security-presentations.js +2 -1
  216. package/dist/modes/rpc/session-attachment-gate.js +1 -0
  217. package/dist/modes/rpc/session-binding.js +30 -17
  218. package/dist/modes/rpc/session-command-activity.js +1 -0
  219. package/dist/modes/rpc/session-event-fanout.d.ts +1 -1
  220. package/dist/modes/rpc/session-event-fanout.js +17 -9
  221. package/dist/modes/rpc/session-event-writer.d.ts +2 -0
  222. package/dist/modes/rpc/session-event-writer.js +24 -5
  223. package/dist/modes/rpc/session-extension-ui-requests.d.ts +2 -1
  224. package/dist/modes/rpc/session-extension-ui-requests.js +15 -10
  225. package/dist/modes/rpc/session-held.js +1 -0
  226. package/dist/modes/rpc/session-record-stream.d.ts +3 -3
  227. package/dist/modes/rpc/session-record-stream.js +3 -3
  228. package/dist/modes/rpc/session-status-stream.d.ts +2 -1
  229. package/dist/modes/rpc/session-status-stream.js +8 -2
  230. package/dist/modes/rpc/session-status-subscriptions.d.ts +2 -1
  231. package/dist/modes/rpc/session-status-subscriptions.js +4 -0
  232. package/dist/modes/rpc/session-structured-lanes.d.ts +12 -0
  233. package/dist/modes/rpc/session-structured-lanes.js +24 -0
  234. package/dist/modes/rpc/session-worker-client.js +6 -5
  235. package/dist/modes/rpc/session-worker.js +5 -2
  236. package/dist/modes/rpc/snapshot-types.d.ts +29 -0
  237. package/dist/modes/rpc/snapshot-types.js +2 -0
  238. package/dist/modes/rpc/structured-session-records.d.ts +12 -0
  239. package/dist/modes/rpc/structured-session-records.js +88 -0
  240. package/dist/modes/rpc/ui-answer-ledger.d.ts +13 -0
  241. package/dist/modes/rpc/ui-answer-ledger.js +34 -0
  242. package/dist/senpi +21 -3
  243. package/docs/extensions.md +7 -1
  244. package/docs/mcp.md +20 -0
  245. package/docs/rpc.md +61 -2
  246. package/package.json +7 -6
  247. package/dist/bundle/chunks/azure-openai-responses-7I45DI72.js +0 -2
  248. package/dist/bundle/chunks/chunk-DMI55M2N.js +0 -2
  249. package/dist/bundle/chunks/chunk-DODH3N6U.js +0 -410
  250. package/dist/bundle/chunks/chunk-J37GRED2.js +0 -5
  251. package/dist/bundle/chunks/chunk-TBU6VYHN.js +0 -13
  252. package/dist/bundle/chunks/cli-main-YYH3IBC6.js +0 -3
  253. package/dist/bundle/chunks/dispatch-MXG5YWLV.js +0 -9
  254. package/dist/bundle/chunks/google-generative-ai-UM5Y7T6B.js +0 -2
  255. package/dist/bundle/chunks/google-vertex-OKU2VQHL.js +0 -2
  256. package/dist/bundle/chunks/host-command-TDLLHXVI.js +0 -2
  257. package/dist/bundle/chunks/interactive-mode-OEPULLZK.js +0 -2
  258. package/dist/bundle/chunks/openai-responses-TNHQ6SJS.js +0 -3
  259. package/dist/bundle/chunks/rpc-mode-ETXQWEB2.js +0 -2
  260. package/dist/bundle/chunks/session-control-endpoint-4LEIDIVI.js +0 -3
@@ -1,4 +1,4 @@
1
- import type { ImageContent, TextContent } from "@earendil-works/pi-ai";
1
+ import { type ImageContent, type TextContent } from "@earendil-works/pi-ai";
2
2
  import { type TSchema } from "typebox";
3
3
  import { buildMcpToolNames, type McpToolNameEntry } from "./naming.ts";
4
4
  import { collectAllPages, type McpListPage, type McpPaginationResult } from "./pagination.ts";
@@ -62,7 +62,7 @@ export type McpMappedToolResult = {
62
62
  content: McpMappedContentBlock[];
63
63
  };
64
64
  };
65
- export declare function convertJsonSchemaToTypeBox(schema: unknown): SchemaConversionResult;
65
+ export declare function convertJsonSchemaToTypeBox(schema: unknown, modern?: boolean): SchemaConversionResult;
66
66
  export declare function prepareOutputSchemaRetry(schema: unknown): OutputSchemaRetryResult;
67
67
  export declare function mapMcpToolResult(result: McpToolResultLike): McpMappedToolResult;
68
68
  //# sourceMappingURL=schema-compat.d.ts.map
@@ -1,9 +1,19 @@
1
+ import { registerToolArgumentValidator } from "@earendil-works/pi-ai";
1
2
  import { Type } from "typebox";
3
+ import { compileModernSchema } from "../modern/tool-schema.js";
2
4
  import { buildMcpToolNames } from "./naming.js";
3
5
  import { collectAllPages } from "./pagination.js";
4
6
  export { buildMcpToolNames, collectAllPages };
5
7
  const PERMISSIVE_OBJECT_SCHEMA = { type: "object", properties: {} };
6
- export function convertJsonSchemaToTypeBox(schema) {
8
+ export function convertJsonSchemaToTypeBox(schema, modern = false) {
9
+ if (modern && (typeof schema === "boolean" || isRecord(schema))) {
10
+ const validate = compileModernSchema(schema);
11
+ // TypeBox/tool APIs require an object container. allOf preserves the exact
12
+ // boolean constraint; provider-specific object adaptation happens in pi-ai.
13
+ const parameters = Type.Unsafe(typeof schema === "boolean" ? { allOf: [schema] } : structuredClone(schema));
14
+ registerToolArgumentValidator(parameters, validate);
15
+ return { schema: parameters, warnings: [] };
16
+ }
7
17
  const warnings = [];
8
18
  const resolved = resolveRefs(stripTopLevelSchemaKeys(schema), schema, new Set(), warnings);
9
19
  if (warnings.length > 0) {
@@ -3,6 +3,7 @@ import { Check } from "typebox/value";
3
3
  import { authorizeToolDispatch, hasDispatchAuthorizer } from "../permission-system/dispatch.js";
4
4
  import { getDispatchIdentity } from "../permission-system/dispatch-metadata.js";
5
5
  import { TimeoutError } from "./errors.js";
6
+ import { compileModernSchema, McpToolSchemaError } from "./modern/tool-schema.js";
6
7
  import { safeTimer } from "./wrap.js";
7
8
  export function createMcpInvocationResolver(scope) {
8
9
  const identity = (entry) => ({
@@ -13,6 +14,8 @@ export function createMcpInvocationResolver(scope) {
13
14
  entry.server,
14
15
  entry.tool,
15
16
  entry.schema,
17
+ entry.outputSchema,
18
+ entry.modern,
16
19
  entry.description,
17
20
  entry.annotations,
18
21
  ]),
@@ -66,7 +69,18 @@ export function createMcpInvocationResolver(scope) {
66
69
  if (current === undefined)
67
70
  return refuse("unavailable", "MCP tool is no longer available.");
68
71
  // Validate the current raw schema, never the coercing preflight schema.
69
- if (!Check(Type.Unsafe(current.schema), args)) {
72
+ let validate;
73
+ try {
74
+ validate = current.modern
75
+ ? compileModernSchema(current.schema)
76
+ : (value) => Check(Type.Unsafe(current.schema), value);
77
+ }
78
+ catch (error) {
79
+ if (!(error instanceof McpToolSchemaError))
80
+ throw error;
81
+ return refuse("schema_changed", `${error.code}: ${error.message}`, current);
82
+ }
83
+ if (!validate(args)) {
70
84
  return refuse("schema_changed", "Arguments do not match the current MCP tool schema.", current);
71
85
  }
72
86
  const preparedIdentity = identity(current);
@@ -107,7 +121,7 @@ export function createMcpInvocationResolver(scope) {
107
121
  const latest = scope.current(offered.tool);
108
122
  return (latest !== undefined &&
109
123
  identity(latest).metadata === preparedIdentity.metadata &&
110
- Check(Type.Unsafe(latest.schema), args) &&
124
+ validate(args) &&
111
125
  (operationFence === undefined || operationFence()) &&
112
126
  (permissionFence === undefined || permissionFence()));
113
127
  },
@@ -39,6 +39,7 @@ export async function materializeNegotiatedTransport(options, serverName, timeou
39
39
  clientInfo,
40
40
  clientCapabilities: {},
41
41
  protocolVersion: outcome.version,
42
+ logger,
42
43
  }), outcome.discovery, outcome.version);
43
44
  const version = outcome.version;
44
45
  logVersion(transport, () => version);
@@ -2,10 +2,13 @@
2
2
  export declare function encodeMcpHeaderValue(value: string): string;
3
3
  /** Configured headers must never supply parameter metadata absent from the request body. */
4
4
  export declare function clearMcpParameterHeaders(headers: Headers): void;
5
+ /** A local argument error, not a failed transport or an untrusted server diagnostic. */
6
+ export declare class ParameterHeaderValidationError extends Error {
7
+ }
5
8
  /** Connection-local catalog: never shared with another server or transport generation. */
6
9
  export declare class ModernParameterHeaders {
7
10
  #private;
8
- filterTools(tools: readonly unknown[], firstPage: boolean): unknown[];
11
+ filterTools(tools: readonly unknown[], firstPage: boolean, warn: (message: string) => void): unknown[];
9
12
  append(headers: Headers, name: unknown, args: unknown): void;
10
13
  }
11
14
  //# sourceMappingURL=parameter-headers.d.ts.map
@@ -18,22 +18,31 @@ export function clearMcpParameterHeaders(headers) {
18
18
  function parseParameterHeaders(schema) {
19
19
  const headers = [];
20
20
  const names = new Set();
21
+ let reason = "";
21
22
  const visit = (node, path) => {
23
+ if (Array.isArray(node))
24
+ return node.every((child) => visit(child, undefined));
22
25
  if (!isRecord(node))
23
26
  return true;
24
27
  if (Object.hasOwn(node, "x-mcp-header")) {
25
28
  const name = node["x-mcp-header"];
26
29
  const type = node.type;
27
- if (!path?.length ||
28
- typeof name !== "string" ||
29
- !/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(name) ||
30
- names.has(name.toLowerCase()) ||
31
- (type !== "string" && type !== "integer" && type !== "boolean"))
30
+ if (!path?.length)
31
+ reason = "x-mcp-header must be on a properties-only path";
32
+ else if (typeof name !== "string" || !/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(name))
33
+ reason = "x-mcp-header must be a nonempty HTTP field-name token";
34
+ else if (names.has(name.toLowerCase()))
35
+ reason = "x-mcp-header names must be case-insensitively unique";
36
+ else if (type !== "string" && type !== "integer" && type !== "boolean")
37
+ reason = "x-mcp-header type must be string, integer, or boolean";
38
+ else {
39
+ names.add(name.toLowerCase());
40
+ headers.push({ name, path, type });
41
+ }
42
+ if (reason)
32
43
  return false;
33
- names.add(name.toLowerCase());
34
- headers.push({ name, path, type });
35
44
  }
36
- // Only schema-valued keywords are traversed: examples/default/const are instance data.
45
+ // "Anywhere else" includes unknown keywords; instance-data keywords are not schemas.
37
46
  for (const [keyword, value] of Object.entries(node)) {
38
47
  if (["properties", "patternProperties", "$defs", "definitions", "dependentSchemas", "dependencies"].includes(keyword)) {
39
48
  if (!isRecord(value))
@@ -44,46 +53,31 @@ function parseParameterHeaders(schema) {
44
53
  return false;
45
54
  }
46
55
  }
47
- else if ([
48
- "items",
49
- "prefixItems",
50
- "allOf",
51
- "anyOf",
52
- "oneOf",
53
- "additionalItems",
54
- "additionalProperties",
55
- "unevaluatedItems",
56
- "unevaluatedProperties",
57
- "contains",
58
- "propertyNames",
59
- "not",
60
- "if",
61
- "then",
62
- "else",
63
- "contentSchema",
64
- ].includes(keyword)) {
65
- for (const child of Array.isArray(value) ? value : [value]) {
66
- if (!visit(child, undefined))
67
- return false;
68
- }
56
+ else if (!["examples", "default", "const", "enum"].includes(keyword)) {
57
+ if (!visit(value, undefined))
58
+ return false;
69
59
  }
70
60
  }
71
61
  return true;
72
62
  };
73
- return visit(schema, []) ? headers : undefined;
63
+ return visit(schema, []) ? headers : reason;
64
+ }
65
+ /** A local argument error, not a failed transport or an untrusted server diagnostic. */
66
+ export class ParameterHeaderValidationError extends Error {
74
67
  }
75
68
  /** Connection-local catalog: never shared with another server or transport generation. */
76
69
  export class ModernParameterHeaders {
77
70
  #tools = new Map();
78
- filterTools(tools, firstPage) {
71
+ filterTools(tools, firstPage, warn) {
79
72
  if (firstPage)
80
73
  this.#tools.clear();
81
74
  return tools.filter((tool) => {
82
75
  if (!isRecord(tool) || typeof tool.name !== "string")
83
76
  return true;
84
77
  const headers = parseParameterHeaders(tool.inputSchema);
85
- if (headers === undefined) {
78
+ if (typeof headers === "string") {
86
79
  this.#tools.delete(tool.name);
80
+ warn(`Rejected MCP tool ${JSON.stringify(tool.name)}: ${headers}`);
87
81
  return false;
88
82
  }
89
83
  this.#tools.set(tool.name, headers);
@@ -98,11 +92,15 @@ export class ModernParameterHeaders {
98
92
  for (const key of parameter.path) {
99
93
  value = isRecord(value) && Object.hasOwn(value, key) ? value[key] : undefined;
100
94
  }
95
+ if (value === undefined)
96
+ continue;
101
97
  const valid = (parameter.type === "string" && typeof value === "string") ||
102
98
  (parameter.type === "boolean" && typeof value === "boolean") ||
103
99
  (parameter.type === "integer" && typeof value === "number" && Number.isSafeInteger(value));
104
- if (valid)
105
- headers.set(`Mcp-Param-${parameter.name}`, encodeMcpHeaderValue(String(value)));
100
+ if (!valid) {
101
+ throw new ParameterHeaderValidationError(`MCP tool ${JSON.stringify(name)} argument ${JSON.stringify(parameter.path)} must be ${parameter.type === "integer" ? "a safe integer" : `a ${parameter.type}`}`);
102
+ }
103
+ headers.set(`Mcp-Param-${parameter.name}`, encodeMcpHeaderValue(String(value)));
106
104
  }
107
105
  }
108
106
  }
@@ -15,6 +15,6 @@ export type MrtrClock = McpClock;
15
15
  export declare const isModernMcpClient: (client: Client) => boolean;
16
16
  export declare const legacyMcpFormsAdvertised: (client: Client) => boolean | undefined;
17
17
  export declare function getModernDispatchGuard(params: unknown): ((credential?: string) => void) | undefined;
18
- /** One instance-local adapter. Captured SDK requests retain ID allocation and final output-schema validation. */
18
+ /** One instance-local adapter. Captured SDK requests retain ID allocation; the caller supplies its era's result contract. */
19
19
  export declare function installModernRequestAdapter(client: Client, protocol: ModernProtocolState, clock?: MrtrClock, readCredential?: () => string | undefined): void;
20
20
  //# sourceMappingURL=request.d.ts.map
@@ -7,6 +7,7 @@ import { isRecord, validateMrtrForm } from "./form-schema.js";
7
7
  import { McpMrtrError } from "./errors.js";
8
8
  import { queuedForm } from "./consent-queue.js";
9
9
  import { parseInputs } from "./inputs.js";
10
+ import { ParameterHeaderValidationError } from "./parameter-headers.js";
10
11
  export const MAX_MCP_MRTR_ROUNDS = 8;
11
12
  const capableMethods = new Set(["tools/call", "resources/read", "prompts/get"]);
12
13
  const capabilitiesKey = "io.modelcontextprotocol/clientCapabilities";
@@ -22,7 +23,7 @@ export function getModernDispatchGuard(params) {
22
23
  ? params[modernDispatchGuard]
23
24
  : undefined;
24
25
  }
25
- /** One instance-local adapter. Captured SDK requests retain ID allocation and final output-schema validation. */
26
+ /** One instance-local adapter. Captured SDK requests retain ID allocation; the caller supplies its era's result contract. */
26
27
  export function installModernRequestAdapter(client, protocol, clock = mcpElapsedClock, readCredential = () => undefined) {
27
28
  const sdkRequest = client.request.bind(client);
28
29
  states.set(client, protocol);
@@ -259,6 +260,8 @@ export function installModernRequestAdapter(client, protocol, clock = mcpElapsed
259
260
  throw error;
260
261
  if (error instanceof AuthError)
261
262
  throw error;
263
+ if (error instanceof ParameterHeaderValidationError)
264
+ throw error;
262
265
  const remoteCode = isRecord(error) && typeof error.code === "number" ? error.code : undefined;
263
266
  throw new McpMrtrError("MCP_MRTR_TRANSPORT_FAILED", remoteCode);
264
267
  }
@@ -1,5 +1,6 @@
1
1
  import { InitializeResultSchema, LATEST_PROTOCOL_VERSION, } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { ModernTransportError } from "./transport.js";
3
+ import { ModernSubscriptions } from "./subscriptions.js";
3
4
  /** The SDK's local `initialize` answer, built from validated discovery; discovery must support the real wire version. */
4
5
  export function modernInitializeResult(discovery, wireVersion) {
5
6
  if (!discovery.supportedVersions.includes(wireVersion)) {
@@ -22,16 +23,22 @@ export function normalizeModernResult(message) {
22
23
  export class ModernSdkBridge {
23
24
  #transport;
24
25
  #initializeResult;
26
+ #subscriptions;
25
27
  #initialized = false;
26
28
  constructor(transport, discovery, wireVersion) {
27
29
  this.#initializeResult = modernInitializeResult(discovery, wireVersion);
28
30
  this.#transport = transport;
31
+ this.#subscriptions = new ModernSubscriptions(transport.send.bind(transport), discovery.capabilities, true);
29
32
  }
30
33
  async start() {
31
34
  this.#transport.onmessage = (message, extra) => {
32
- this.onmessage?.(normalizeModernResult(message), extra);
35
+ if (this.#subscriptions.incoming(message))
36
+ this.onmessage?.(normalizeModernResult(message), extra);
37
+ };
38
+ this.#transport.onclose = () => {
39
+ this.#subscriptions.dispose();
40
+ this.onclose?.();
33
41
  };
34
- this.#transport.onclose = () => this.onclose?.();
35
42
  this.#transport.onerror = (error) => this.onerror?.(error);
36
43
  await this.#transport.start();
37
44
  }
@@ -46,9 +53,17 @@ export class ModernSdkBridge {
46
53
  }
47
54
  if ("method" in message && message.method === "notifications/initialized" && !("id" in message))
48
55
  return;
49
- await this.#transport.send(message, options);
56
+ this.#subscriptions.outgoing(message);
57
+ try {
58
+ await this.#transport.send(message, options);
59
+ }
60
+ catch (error) {
61
+ this.#subscriptions.failedSend(message);
62
+ throw error;
63
+ }
50
64
  }
51
65
  close() {
66
+ this.#subscriptions.dispose();
52
67
  return this.#transport.close();
53
68
  }
54
69
  }
@@ -3,6 +3,7 @@ import { reapProcessTree } from "../process-tree.js";
3
3
  import { discoveryReply, probeStdioEra, STDIO_PROBE_ID, StdioProbeSilence, } from "./era.js";
4
4
  import { modernInitializeResult, normalizeModernResult } from "./sdk-bridge.js";
5
5
  import { ModernTransportError } from "./transport.js";
6
+ import { ModernSubscriptions } from "./subscriptions.js";
6
7
  import { sanitizeModernMessage } from "./diagnostics.js";
7
8
  import { McpMrtrError } from "./errors.js";
8
9
  import { getModernDispatchGuard } from "./request.js";
@@ -186,6 +187,17 @@ export function probeStdioTransport(transport, options, logger, timeoutMs, serve
186
187
  const initializeResult = modernInitializeResult(discovery, version);
187
188
  const deliver = transport.onmessage;
188
189
  const live = new Set();
190
+ const subscriptions = new ModernSubscriptions((message, options) => transport.send(message, options), discovery.capabilities);
191
+ const end = transport.close.bind(transport);
192
+ transport.close = async () => {
193
+ subscriptions.dispose();
194
+ await end();
195
+ };
196
+ const onclose = transport.onclose;
197
+ transport.onclose = () => {
198
+ subscriptions.dispose();
199
+ onclose?.();
200
+ };
189
201
  let initialized = false;
190
202
  transport.onerror = () => {
191
203
  // A parser error may quote a wire body; never forward it.
@@ -204,6 +216,14 @@ export function probeStdioTransport(transport, options, logger, timeoutMs, serve
204
216
  }
205
217
  if ("id" in message && message.id !== undefined && !live.delete(message.id))
206
218
  return;
219
+ if (!subscriptions.incoming(message)) {
220
+ const id = "method" in message && message.method === "notifications/cancelled"
221
+ ? message.params?.requestId
222
+ : undefined;
223
+ if (typeof id === "string" || typeof id === "number")
224
+ live.delete(id);
225
+ return;
226
+ }
207
227
  if ("method" in message && message.method === "notifications/progress") {
208
228
  const token = message.params?.progressToken;
209
229
  if ((typeof token !== "string" && typeof token !== "number") || !live.has(token))
@@ -227,12 +247,14 @@ export function probeStdioTransport(transport, options, logger, timeoutMs, serve
227
247
  if (message.method === "notifications/initialized" && !("id" in message))
228
248
  return;
229
249
  if (message.method === "notifications/cancelled" && !("id" in message)) {
250
+ subscriptions.outgoing(message);
230
251
  const id = message.params?.requestId;
231
252
  if ((typeof id !== "string" && typeof id !== "number") || !live.delete(id))
232
253
  return;
233
254
  return send({ jsonrpc: "2.0", method: message.method, params: { requestId: id, reason: "MCP request canceled" } }, sendOptions);
234
255
  }
235
256
  getModernDispatchGuard(message.params)?.();
257
+ subscriptions.outgoing(message);
236
258
  const request = modernizeRequest("id" in message ? message.id : 0, message.method, options.clientInfo, options.clientCapabilities, version, message.params);
237
259
  if ("id" in message) {
238
260
  live.add(message.id);
@@ -241,6 +263,7 @@ export function probeStdioTransport(transport, options, logger, timeoutMs, serve
241
263
  }
242
264
  catch {
243
265
  live.delete(message.id);
266
+ subscriptions.failedSend(message);
244
267
  throw new McpMrtrError("MCP_MRTR_TRANSPORT_FAILED");
245
268
  }
246
269
  }
@@ -0,0 +1,14 @@
1
+ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
2
+ import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
3
+ import type { DiscoveryResult } from "./era.ts";
4
+ /** Connection-owned streams bypass the SDK's finite request timeout and MRTR consent lifecycle. */
5
+ export declare class ModernSubscriptions {
6
+ #private;
7
+ constructor(send: Transport["send"], capabilities: DiscoveryResult["capabilities"], sendCompletesStream?: boolean);
8
+ outgoing(message: JSONRPCMessage): void;
9
+ /** Returns true only for application messages that may reach the existing SDK notification handlers. */
10
+ incoming(message: JSONRPCMessage): boolean;
11
+ failedSend(message: JSONRPCMessage): void;
12
+ dispose(): void;
13
+ }
14
+ //# sourceMappingURL=subscriptions.d.ts.map
@@ -0,0 +1,226 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mcpElapsedClock } from "../wrap.js";
3
+ import { isRecord } from "./form-schema.js";
4
+ const subscriptionKey = "io.modelcontextprotocol/subscriptionId";
5
+ const listFilters = {
6
+ "notifications/tools/list_changed": "toolsListChanged",
7
+ "notifications/prompts/list_changed": "promptsListChanged",
8
+ "notifications/resources/list_changed": "resourcesListChanged",
9
+ };
10
+ /** Connection-owned streams bypass the SDK's finite request timeout and MRTR consent lifecycle. */
11
+ export class ModernSubscriptions {
12
+ #send;
13
+ #capabilities;
14
+ #sendCompletesStream;
15
+ #filter = {};
16
+ #resourceLists = new Map();
17
+ #resourceCursors = new Map();
18
+ #latestSweep;
19
+ #active;
20
+ #pending;
21
+ #timer;
22
+ #retry = 0;
23
+ #closed = false;
24
+ #started = false;
25
+ constructor(send, capabilities, sendCompletesStream = false) {
26
+ this.#send = send;
27
+ this.#capabilities = capabilities;
28
+ this.#sendCompletesStream = sendCompletesStream;
29
+ }
30
+ outgoing(message) {
31
+ if (!("method" in message))
32
+ return;
33
+ if (message.method === "notifications/cancelled") {
34
+ const id = message.params?.requestId;
35
+ if (typeof id === "string" || typeof id === "number")
36
+ this.#resourceLists.delete(id);
37
+ }
38
+ if (!("id" in message))
39
+ return;
40
+ if (message.method === "resources/list" && this.#supports("resources", "subscribe")) {
41
+ const cursor = message.params?.cursor;
42
+ if (cursor === undefined) {
43
+ const sweep = { uris: [] };
44
+ this.#latestSweep = sweep;
45
+ this.#resourceLists.set(message.id, sweep);
46
+ }
47
+ else if (typeof cursor === "string") {
48
+ const sweep = this.#resourceCursors.get(cursor);
49
+ this.#resourceCursors.delete(cursor);
50
+ if (sweep)
51
+ this.#resourceLists.set(message.id, sweep);
52
+ }
53
+ }
54
+ if (this.#started || !["tools/list", "prompts/list", "resources/list"].includes(message.method))
55
+ return;
56
+ this.#started = true;
57
+ // The builtin catalog uses all three lists. Never opt in to an undeclared feature.
58
+ if (this.#supports("tools", "listChanged"))
59
+ this.#filter.toolsListChanged = true;
60
+ if (this.#supports("prompts", "listChanged"))
61
+ this.#filter.promptsListChanged = true;
62
+ if (this.#supports("resources", "listChanged"))
63
+ this.#filter.resourcesListChanged = true;
64
+ this.#open();
65
+ }
66
+ /** Returns true only for application messages that may reach the existing SDK notification handlers. */
67
+ incoming(message) {
68
+ const sweep = "id" in message && message.id !== undefined ? this.#resourceLists.get(message.id) : undefined;
69
+ if ("id" in message && message.id !== undefined)
70
+ this.#resourceLists.delete(message.id);
71
+ if (sweep && "result" in message) {
72
+ const resources = message.result.resources;
73
+ if (Array.isArray(resources)) {
74
+ for (const resource of resources)
75
+ if (isRecord(resource) && typeof resource.uri === "string")
76
+ sweep.uris.push(resource.uri);
77
+ if (typeof message.result.nextCursor === "string" && message.result.nextCursor) {
78
+ this.#resourceCursors.set(message.result.nextCursor, sweep);
79
+ }
80
+ else if (sweep === this.#latestSweep) {
81
+ const uris = [...new Set(sweep.uris)].sort();
82
+ if (JSON.stringify(uris) !== JSON.stringify(this.#filter.resourceSubscriptions ?? [])) {
83
+ this.#filter.resourceSubscriptions = uris;
84
+ this.#open();
85
+ }
86
+ }
87
+ }
88
+ }
89
+ if ("id" in message) {
90
+ const stream = [this.#active, this.#pending].find((entry) => entry?.id === message.id);
91
+ if (!stream)
92
+ return true;
93
+ // A response ends the long-lived request; graceful server shutdown is not a retry trigger.
94
+ this.#retire(stream);
95
+ return false;
96
+ }
97
+ if (!("method" in message))
98
+ return true;
99
+ if (message.method === "notifications/cancelled") {
100
+ const stream = [this.#active, this.#pending].find((entry) => entry?.id === message.params?.requestId);
101
+ if (stream) {
102
+ this.#retire(stream);
103
+ return false;
104
+ }
105
+ }
106
+ const meta = message.params?._meta;
107
+ const id = isRecord(meta) ? meta[subscriptionKey] : undefined;
108
+ const acknowledged = message.method === "notifications/subscriptions/acknowledged";
109
+ const list = Object.hasOwn(listFilters, message.method);
110
+ const resource = message.method === "notifications/resources/updated";
111
+ if (!acknowledged && !list && !resource)
112
+ return true;
113
+ const active = [this.#active, this.#pending].find((entry) => entry?.id === id);
114
+ if (!active)
115
+ return false;
116
+ if (acknowledged) {
117
+ if (active.accepted)
118
+ return false;
119
+ if (!isRecord(message.params?.notifications)) {
120
+ this.#failed(active);
121
+ this.#cancel(active);
122
+ return false;
123
+ }
124
+ const offered = message.params.notifications;
125
+ const accepted = {};
126
+ for (const key of Object.values(listFilters))
127
+ if (active.requested[key] && offered[key] === true)
128
+ accepted[key] = true;
129
+ const uris = offered.resourceSubscriptions;
130
+ if (Array.isArray(uris))
131
+ accepted.resourceSubscriptions = active.requested.resourceSubscriptions?.filter((uri) => uris.includes(uri));
132
+ active.accepted = accepted;
133
+ const previous = this.#active;
134
+ this.#pending = undefined;
135
+ this.#active = active;
136
+ if (previous && previous !== active)
137
+ this.#cancel(previous);
138
+ if (!Object.values(accepted).some((value) => value === true) && !accepted.resourceSubscriptions?.length)
139
+ this.#cancel(active);
140
+ return false;
141
+ }
142
+ if (active !== this.#active || !active.accepted)
143
+ return false;
144
+ const deliver = resource
145
+ ? typeof message.params?.uri === "string" &&
146
+ active.accepted.resourceSubscriptions?.includes(message.params.uri) === true
147
+ : active.accepted[listFilters[message.method]] === true;
148
+ if (deliver)
149
+ this.#retry = 0;
150
+ return deliver;
151
+ }
152
+ failedSend(message) {
153
+ if ("id" in message && message.id !== undefined)
154
+ this.#resourceLists.delete(message.id);
155
+ }
156
+ dispose() {
157
+ this.#closed = true;
158
+ if (this.#timer)
159
+ mcpElapsedClock.cancel(this.#timer);
160
+ this.#timer = undefined;
161
+ if (this.#pending)
162
+ this.#cancel(this.#pending);
163
+ if (this.#active)
164
+ this.#cancel(this.#active);
165
+ this.#resourceLists.clear();
166
+ this.#resourceCursors.clear();
167
+ this.#latestSweep = undefined;
168
+ }
169
+ #supports(feature, flag) {
170
+ const capability = this.#capabilities[feature];
171
+ return isRecord(capability) && capability[flag] === true;
172
+ }
173
+ #retire(active) {
174
+ if (this.#active === active)
175
+ this.#active = undefined;
176
+ if (this.#pending === active)
177
+ this.#pending = undefined;
178
+ }
179
+ #cancel(active) {
180
+ this.#retire(active);
181
+ void this.#send({
182
+ jsonrpc: "2.0",
183
+ method: "notifications/cancelled",
184
+ params: { requestId: active.id },
185
+ }).catch(() => { });
186
+ }
187
+ #open() {
188
+ if (this.#closed || JSON.stringify((this.#pending ?? this.#active)?.requested) === JSON.stringify(this.#filter))
189
+ return;
190
+ if (this.#timer)
191
+ mcpElapsedClock.cancel(this.#timer);
192
+ this.#timer = undefined;
193
+ if (this.#pending)
194
+ this.#cancel(this.#pending);
195
+ if (!Object.values(this.#filter).some((value) => value === true) && !this.#filter.resourceSubscriptions?.length) {
196
+ if (this.#active)
197
+ this.#cancel(this.#active);
198
+ return;
199
+ }
200
+ const active = { id: `senpi-subscription-${randomUUID()}`, requested: structuredClone(this.#filter) };
201
+ this.#pending = active;
202
+ // HTTP send remains pending for the entire SSE lifetime. No caller awaits it.
203
+ void this.#send({
204
+ jsonrpc: "2.0",
205
+ id: active.id,
206
+ method: "subscriptions/listen",
207
+ params: { notifications: active.requested },
208
+ }).then(() => {
209
+ if (this.#sendCompletesStream)
210
+ this.#failed(active);
211
+ }, () => this.#failed(active));
212
+ }
213
+ #failed(active) {
214
+ if (this.#closed || (this.#active !== active && this.#pending !== active))
215
+ return;
216
+ this.#retire(active);
217
+ if (this.#pending || this.#timer)
218
+ return;
219
+ this.#timer = mcpElapsedClock.schedule(() => {
220
+ this.#timer = undefined;
221
+ this.#open();
222
+ }, Math.min(30_000, 1000 * 2 ** Math.min(this.#retry++, 5)));
223
+ this.#timer.unref?.();
224
+ }
225
+ }
226
+ //# sourceMappingURL=subscriptions.js.map
@@ -0,0 +1,13 @@
1
+ import type { ValidateFunction } from "ajv";
2
+ import type { McpJsonSchema, McpListedTool } from "../tool-contracts.ts";
3
+ type SchemaErrorCode = "MCP_SCHEMA_UNSUPPORTED_DIALECT" | "MCP_SCHEMA_INVALID" | "MCP_SCHEMA_LIMIT" | "MCP_OUTPUT_SCHEMA_MISMATCH";
4
+ export declare class McpToolSchemaError extends Error {
5
+ readonly code: SchemaErrorCode;
6
+ constructor(code: SchemaErrorCode, message: string);
7
+ }
8
+ /** No coercion/default insertion/removal, no network resolver, no shared $id namespace. */
9
+ export declare function compileModernSchema(schema: McpJsonSchema): ValidateFunction;
10
+ /** Reject one unusable definition, never its valid siblings; diagnostics contain no schema or arguments. */
11
+ export declare function hasSupportedModernToolSchemas(tool: Pick<McpListedTool, "name" | "inputSchema" | "outputSchema">): boolean;
12
+ export {};
13
+ //# sourceMappingURL=tool-schema.d.ts.map