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

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 (198) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/bundle/chunks/{anthropic-messages-WBASBTIW.js → anthropic-messages-M235FJQV.js} +3 -3
  3. package/dist/bundle/chunks/{args-B444OQBK.js → args-FBNJOJSR.js} +1 -1
  4. package/dist/bundle/chunks/azure-openai-responses-YQRTQ7H6.js +2 -0
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +3 -3
  6. package/dist/bundle/chunks/{chunk-4MOZXOJX.js → chunk-3A3XCLNH.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-E6D4RIT7.js → chunk-3LPWILUZ.js} +2 -2
  8. package/dist/bundle/chunks/{chunk-3BTN337S.js → chunk-3WP5RFQR.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-2RB5EL4X.js → chunk-5FQMNHXV.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-STMXIBXF.js → chunk-5KZH6EYQ.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-4P7WARQZ.js → chunk-5LWUUXMH.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-5JJDR6XI.js → chunk-6U3TBFBA.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-BWA4FVFW.js → chunk-7KOUQSBX.js} +2 -2
  14. package/dist/bundle/chunks/{chunk-7CNOCJCN.js → chunk-ADQAPDPV.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-ITGYN22E.js → chunk-CDQPA4FP.js} +1 -1
  16. package/dist/bundle/chunks/{chunk-WDPQ6CZJ.js → chunk-DMFAVE5N.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-XF3LJC3O.js → chunk-DYWIFF2Q.js} +1 -1
  18. package/dist/bundle/chunks/{chunk-C2MQ4QXN.js → chunk-EZP6AYW2.js} +35 -29
  19. package/dist/bundle/chunks/chunk-GQJYNFMO.js +2 -0
  20. package/dist/bundle/chunks/{chunk-SBGIXMX3.js → chunk-GWGNVYPB.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-KKD4CYCI.js → chunk-H4WJ4LY3.js} +1 -1
  22. package/dist/bundle/chunks/{chunk-V2UYXTRR.js → chunk-HWTD4QF3.js} +1 -1
  23. package/dist/bundle/chunks/{chunk-WD6WBYC5.js → chunk-I7QWTWVZ.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-BZ7726QO.js → chunk-J2YAYTB2.js} +1 -1
  25. package/dist/bundle/chunks/{chunk-TTU2RNVX.js → chunk-J5FCH7TI.js} +1 -1
  26. package/dist/bundle/chunks/chunk-JFQUWEN7.js +13 -0
  27. package/dist/bundle/chunks/{chunk-KNVNKDER.js → chunk-L2O6K3SJ.js} +1 -1
  28. package/dist/bundle/chunks/{chunk-4AOMNOGY.js → chunk-LO5MYLGF.js} +1 -1
  29. package/dist/bundle/chunks/{chunk-N7IN46Q5.js → chunk-MSHRIZGT.js} +8 -6
  30. package/dist/bundle/chunks/{chunk-T36LJ6MW.js → chunk-MY4ZD3EW.js} +1 -1
  31. package/dist/bundle/chunks/{chunk-UFJ2K7Q7.js → chunk-OQ3WDMB3.js} +1 -1
  32. package/dist/bundle/chunks/{chunk-7GZUFOM3.js → chunk-PH3JDRB2.js} +1 -1
  33. package/dist/bundle/chunks/{chunk-E6WYH3ZL.js → chunk-QREKAXKX.js} +7 -5
  34. package/dist/bundle/chunks/{chunk-3QCYBOKN.js → chunk-QYFOHFPS.js} +1 -1
  35. package/dist/bundle/chunks/{chunk-JMOUU75H.js → chunk-SOXQ42VS.js} +1 -1
  36. package/dist/bundle/chunks/{chunk-FHXR4ZAC.js → chunk-UDM7VO6O.js} +1 -1
  37. package/dist/bundle/chunks/{chunk-OLCDIITS.js → chunk-UW6M3EXY.js} +1 -1
  38. package/dist/bundle/chunks/chunk-VICDRG3J.js +410 -0
  39. package/dist/bundle/chunks/chunk-VL7O5PO4.js +5 -0
  40. package/dist/bundle/chunks/{chunk-PT6JJUN2.js → chunk-XMNPK472.js} +1 -1
  41. package/dist/bundle/chunks/{chunk-GSSD3R5X.js → chunk-XQ45JMGN.js} +1 -1
  42. package/dist/bundle/chunks/{client-WTYEICLH.js → chunk-Y3TMJV3J.js} +3 -3
  43. package/dist/bundle/chunks/{chunk-KXB5WIS5.js → chunk-Z6SAQ5O3.js} +1 -1
  44. package/dist/bundle/chunks/{chunk-LOIF6PHI.js → chunk-ZAUVY6ZC.js} +3 -3
  45. package/dist/bundle/chunks/{chunk-E4LBKOZG.js → chunk-ZLIVZQDY.js} +4 -4
  46. package/dist/bundle/chunks/{chunk-6AOVUTUR.js → chunk-ZXHLH4MW.js} +2 -2
  47. package/dist/bundle/chunks/cli-main-55EQA5PS.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-ZJQGIIRC.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-C2IBP5VV.js +9 -0
  53. package/dist/bundle/chunks/google-generative-ai-5G4RIQA6.js +2 -0
  54. package/dist/bundle/chunks/google-vertex-ALD4P27O.js +2 -0
  55. package/dist/bundle/chunks/{help-fast-path-3Z26DQSV.js → help-fast-path-PEIKGCKZ.js} +1 -1
  56. package/dist/bundle/chunks/host-command-657VCQ34.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-OV5XDOJK.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-MLXSYK2H.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-RAE4K2XY.js} +1 -1
  64. package/dist/bundle/chunks/{multi-session-host-Z2CW4SDM.js → multi-session-host-774MACJU.js} +5 -6
  65. package/dist/bundle/chunks/{openai-codex-responses-SERAFKAS.js → openai-codex-responses-3CW4R7TG.js} +1 -1
  66. package/dist/bundle/chunks/{openai-completions-BZB5O45A.js → openai-completions-5UIJXQUF.js} +1 -1
  67. package/dist/bundle/chunks/openai-responses-2BO44MRW.js +3 -0
  68. package/dist/bundle/chunks/{package-manager-cli-N55U6PEO.js → package-manager-cli-MZHVCX2S.js} +1 -1
  69. package/dist/bundle/chunks/{pi-messages-VXATX45B.js → pi-messages-RF6GER4R.js} +2 -2
  70. package/dist/bundle/chunks/{role-JVSBNKGE.js → role-ETSFSDTS.js} +1 -1
  71. package/dist/bundle/chunks/{rotation-stream-CBGCBYHV.js → rotation-stream-ZMQB4GPO.js} +1 -1
  72. package/dist/bundle/chunks/rpc-mode-UJXOIZCW.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-KA7LFFNK.js} +1 -1
  75. package/dist/bundle/chunks/session-control-endpoint-MFWXUR7Z.js +3 -0
  76. package/dist/bundle/chunks/{session-picker-E4KUJDLO.js → session-picker-BP33NRSL.js} +1 -1
  77. package/dist/bundle/chunks/session-worker.js +199 -190
  78. package/dist/bundle/chunks/{typesafe-system-one-P75WJ767.js → typesafe-system-one-GNNZ5O5U.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/extensions/builtin/mcp/catalog-cache.d.ts +2 -1
  89. package/dist/core/extensions/builtin/mcp/catalog-cache.js +19 -6
  90. package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -2
  91. package/dist/core/extensions/builtin/mcp/catalog.js +20 -3
  92. package/dist/core/extensions/builtin/mcp/expose/call.d.ts +2 -1
  93. package/dist/core/extensions/builtin/mcp/expose/call.js +2 -1
  94. package/dist/core/extensions/builtin/mcp/expose/register.js +1 -1
  95. package/dist/core/extensions/builtin/mcp/expose/schema-compat.d.ts +2 -2
  96. package/dist/core/extensions/builtin/mcp/expose/schema-compat.js +11 -1
  97. package/dist/core/extensions/builtin/mcp/invocation.js +16 -2
  98. package/dist/core/extensions/builtin/mcp/modern/request.d.ts +1 -1
  99. package/dist/core/extensions/builtin/mcp/modern/request.js +1 -1
  100. package/dist/core/extensions/builtin/mcp/modern/sdk-bridge.js +18 -3
  101. package/dist/core/extensions/builtin/mcp/modern/stdio-probe.js +23 -0
  102. package/dist/core/extensions/builtin/mcp/modern/subscriptions.d.ts +14 -0
  103. package/dist/core/extensions/builtin/mcp/modern/subscriptions.js +226 -0
  104. package/dist/core/extensions/builtin/mcp/modern/tool-schema.d.ts +13 -0
  105. package/dist/core/extensions/builtin/mcp/modern/tool-schema.js +75 -0
  106. package/dist/core/extensions/builtin/mcp/modern/tools.d.ts +107 -0
  107. package/dist/core/extensions/builtin/mcp/modern/tools.js +53 -0
  108. package/dist/core/extensions/builtin/mcp/resources.js +4 -0
  109. package/dist/core/extensions/builtin/mcp/tool-client.d.ts +13 -0
  110. package/dist/core/extensions/builtin/mcp/tool-client.js +17 -0
  111. package/dist/core/extensions/builtin/mcp/tool-contracts.d.ts +19 -0
  112. package/dist/core/extensions/builtin/mcp/tool-contracts.js +2 -0
  113. package/dist/core/extensions/builtin/mcp/wrap.js +2 -2
  114. package/dist/core/extensions/builtin/permission-system/bash-command-segments.d.ts +37 -0
  115. package/dist/core/extensions/builtin/permission-system/bash-command-segments.js +899 -0
  116. package/dist/core/extensions/builtin/permission-system/parsers.d.ts +2 -0
  117. package/dist/core/extensions/builtin/permission-system/parsers.js +44 -10
  118. package/dist/modes/interactive/session-control-commands.js +21 -4
  119. package/dist/modes/interactive/session-control-rpc-commands.d.ts +1 -1
  120. package/dist/modes/interactive/session-control-rpc-commands.js +19 -0
  121. package/dist/modes/rpc/compact-content-store.d.ts +96 -0
  122. package/dist/modes/rpc/compact-content-store.js +269 -0
  123. package/dist/modes/rpc/compact-session-state.d.ts +111 -0
  124. package/dist/modes/rpc/compact-session-state.js +206 -0
  125. package/dist/modes/rpc/compact-spool-dir.d.ts +5 -0
  126. package/dist/modes/rpc/compact-spool-dir.js +61 -0
  127. package/dist/modes/rpc/compact-storage-io.d.ts +27 -0
  128. package/dist/modes/rpc/compact-storage-io.js +19 -0
  129. package/dist/modes/rpc/connection-handler.d.ts +2 -0
  130. package/dist/modes/rpc/connection-handler.js +39 -7
  131. package/dist/modes/rpc/connection-question-bridge.d.ts +2 -1
  132. package/dist/modes/rpc/connection-question-bridge.js +6 -2
  133. package/dist/modes/rpc/extension-ui-response.d.ts +7 -2
  134. package/dist/modes/rpc/extension-ui-response.js +27 -7
  135. package/dist/modes/rpc/get-snapshot.d.ts +18 -0
  136. package/dist/modes/rpc/get-snapshot.js +43 -0
  137. package/dist/modes/rpc/host-capabilities.js +3 -0
  138. package/dist/modes/rpc/host-ensure-start.js +1 -0
  139. package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
  140. package/dist/modes/rpc/host-ensure.js +1 -0
  141. package/dist/modes/rpc/host-handoff.d.ts +2 -0
  142. package/dist/modes/rpc/host-handover-request.d.ts +1 -1
  143. package/dist/modes/rpc/host-handover-request.js +4 -3
  144. package/dist/modes/rpc/host-handover-wire.js +6 -0
  145. package/dist/modes/rpc/host-idle-handover.d.ts +2 -0
  146. package/dist/modes/rpc/host-idle-handover.js +3 -1
  147. package/dist/modes/rpc/host-observe-request.js +1 -0
  148. package/dist/modes/rpc/host-runner.d.ts +1 -0
  149. package/dist/modes/rpc/host-runner.js +9 -7
  150. package/dist/modes/rpc/host-successor.js +4 -0
  151. package/dist/modes/rpc/json-normal-value.d.ts +6 -0
  152. package/dist/modes/rpc/json-normal-value.js +100 -0
  153. package/dist/modes/rpc/mcp-presenter-bootstrap.js +5 -0
  154. package/dist/modes/rpc/media-placeholders.d.ts +9 -3
  155. package/dist/modes/rpc/media-placeholders.js +12 -4
  156. package/dist/modes/rpc/rpc-host-command-types.d.ts +2 -0
  157. package/dist/modes/rpc/rpc-question-types.d.ts +1 -0
  158. package/dist/modes/rpc/rpc-read-response-types.d.ts +7 -0
  159. package/dist/modes/rpc/rpc-session-command-types.d.ts +6 -0
  160. package/dist/modes/rpc/rpc-session-state-types.d.ts +3 -0
  161. package/dist/modes/rpc/rpc-session-state.js +2 -0
  162. package/dist/modes/rpc/rpc-types.d.ts +1 -0
  163. package/dist/modes/rpc/session-attachment-gate.js +1 -0
  164. package/dist/modes/rpc/session-binding.js +21 -18
  165. package/dist/modes/rpc/session-command-activity.js +1 -0
  166. package/dist/modes/rpc/session-event-fanout.d.ts +1 -1
  167. package/dist/modes/rpc/session-event-fanout.js +17 -9
  168. package/dist/modes/rpc/session-event-writer.js +22 -5
  169. package/dist/modes/rpc/session-extension-ui-requests.d.ts +2 -1
  170. package/dist/modes/rpc/session-extension-ui-requests.js +15 -10
  171. package/dist/modes/rpc/session-held.js +1 -0
  172. package/dist/modes/rpc/session-status-stream.d.ts +2 -1
  173. package/dist/modes/rpc/session-status-stream.js +8 -2
  174. package/dist/modes/rpc/session-status-subscriptions.d.ts +2 -1
  175. package/dist/modes/rpc/session-status-subscriptions.js +4 -0
  176. package/dist/modes/rpc/session-worker.js +5 -2
  177. package/dist/modes/rpc/snapshot-types.d.ts +29 -0
  178. package/dist/modes/rpc/snapshot-types.js +2 -0
  179. package/dist/modes/rpc/ui-answer-ledger.d.ts +13 -0
  180. package/dist/modes/rpc/ui-answer-ledger.js +34 -0
  181. package/dist/senpi +21 -3
  182. package/docs/mcp.md +20 -0
  183. package/docs/rpc.md +44 -0
  184. package/package.json +7 -6
  185. package/dist/bundle/chunks/azure-openai-responses-7I45DI72.js +0 -2
  186. package/dist/bundle/chunks/chunk-DMI55M2N.js +0 -2
  187. package/dist/bundle/chunks/chunk-DODH3N6U.js +0 -410
  188. package/dist/bundle/chunks/chunk-J37GRED2.js +0 -5
  189. package/dist/bundle/chunks/chunk-TBU6VYHN.js +0 -13
  190. package/dist/bundle/chunks/cli-main-YYH3IBC6.js +0 -3
  191. package/dist/bundle/chunks/dispatch-MXG5YWLV.js +0 -9
  192. package/dist/bundle/chunks/google-generative-ai-UM5Y7T6B.js +0 -2
  193. package/dist/bundle/chunks/google-vertex-OKU2VQHL.js +0 -2
  194. package/dist/bundle/chunks/host-command-TDLLHXVI.js +0 -2
  195. package/dist/bundle/chunks/interactive-mode-OEPULLZK.js +0 -2
  196. package/dist/bundle/chunks/openai-responses-TNHQ6SJS.js +0 -3
  197. package/dist/bundle/chunks/rpc-mode-ETXQWEB2.js +0 -2
  198. package/dist/bundle/chunks/session-control-endpoint-4LEIDIVI.js +0 -3
@@ -6,6 +6,9 @@ import { redactResolvedReferenceData } from "../../../config-reference-redaction
6
6
  import { lockWithLossHandling } from "../../../file-lock.js";
7
7
  import { isEphemeralIdentity } from "./config-identity-key.js";
8
8
  import { collectAllPages } from "./expose/pagination.js";
9
+ import { listMcpTools } from "./tool-client.js";
10
+ import { isModernMcpClient } from "./modern/request.js";
11
+ import { hasSupportedModernToolSchemas } from "./modern/tool-schema.js";
9
12
  const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
10
13
  const EMPTY_CACHE = { version: 1, servers: {} };
11
14
  export function getMcpCatalogCachePath(agentDir = getAgentDir()) {
@@ -36,7 +39,7 @@ export function cachedCatalogNeedsRefresh(cached, now = Date.now()) {
36
39
  return now - cached.fetchedAt > CACHE_TTL_MS;
37
40
  }
38
41
  export async function collectServerCatalogForCache(connection, config, configHash, credentialIdentity) {
39
- const tools = await collectAllPages((cursor) => connection.client.listTools(cursor === undefined ? {} : { cursor }, {
42
+ const tools = await collectAllPages((cursor) => listMcpTools(connection.client, cursor === undefined ? {} : { cursor }, {
40
43
  timeout: config.requestTimeoutMs,
41
44
  }));
42
45
  const resources = await collectOptionalPages((cursor) => connection.client.listResources(cursor === undefined ? {} : { cursor }, {
@@ -50,6 +53,7 @@ export async function collectServerCatalogForCache(connection, config, configHas
50
53
  }));
51
54
  return {
52
55
  configHash,
56
+ ...(isModernMcpClient(connection.client) ? { modern: true } : {}),
53
57
  credentialIdentity,
54
58
  fetchedAt: Date.now(),
55
59
  instructions: connection.client.getInstructions(),
@@ -128,7 +132,7 @@ function normalizeCachedServer(value) {
128
132
  if (!isRecord(value) || typeof value.configHash !== "string" || typeof value.fetchedAt !== "number") {
129
133
  return undefined;
130
134
  }
131
- const tools = normalizeTools(value.tools);
135
+ const tools = normalizeTools(value.tools, value.modern === true);
132
136
  if (tools === undefined)
133
137
  return undefined;
134
138
  const resources = Array.isArray(value.resources) ? value.resources : [];
@@ -139,6 +143,7 @@ function normalizeCachedServer(value) {
139
143
  const instructions = typeof value.instructions === "string" ? value.instructions : undefined;
140
144
  return {
141
145
  configHash: value.configHash,
146
+ ...(value.modern === true ? { modern: true } : {}),
142
147
  credentialIdentity: typeof value.credentialIdentity === "string" ? value.credentialIdentity : undefined,
143
148
  fetchedAt: value.fetchedAt,
144
149
  instructions,
@@ -148,19 +153,27 @@ function normalizeCachedServer(value) {
148
153
  tools,
149
154
  };
150
155
  }
151
- function normalizeTools(value) {
156
+ function normalizeTools(value, modern) {
152
157
  if (!Array.isArray(value))
153
158
  return undefined;
154
159
  const tools = [];
155
160
  for (const item of value) {
156
- if (!isRecord(item) || typeof item.name !== "string" || !isRecord(item.inputSchema))
161
+ if (!isRecord(item) || typeof item.name !== "string")
157
162
  return undefined;
158
- tools.push({
163
+ if (!isRecord(item.inputSchema) && !(modern && typeof item.inputSchema === "boolean"))
164
+ return undefined;
165
+ const tool = {
166
+ ...(modern ? item : {}),
159
167
  annotations: isRecord(item.annotations) ? item.annotations : undefined,
160
168
  description: typeof item.description === "string" ? item.description : undefined,
161
169
  inputSchema: item.inputSchema,
170
+ ...(modern && (isRecord(item.outputSchema) || typeof item.outputSchema === "boolean")
171
+ ? { outputSchema: item.outputSchema }
172
+ : {}),
162
173
  name: item.name,
163
- });
174
+ };
175
+ if (!modern || hasSupportedModernToolSchemas(tool))
176
+ tools.push(tool);
164
177
  }
165
178
  return tools;
166
179
  }
@@ -1,4 +1,4 @@
1
- import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
1
+ import type { McpListedTool as ListedTool } from "./tool-contracts.ts";
2
2
  import type { McpCachedServerCatalog } from "./catalog-cache.ts";
3
3
  import type { McpServerDeclaredConfig, McpSettings } from "./config-schema.ts";
4
4
  import type { ServerConnection } from "./connection.ts";
@@ -6,12 +6,13 @@ import type { McpOutputArtifacts } from "./guard/output-guard.ts";
6
6
  import type { McpEnsureFreshAuth } from "./health.ts";
7
7
  import type { McpInvocationResolver } from "./invocation.ts";
8
8
  import type { McpRequestOrigin } from "./request-origin.ts";
9
- type ListedTool = Awaited<ReturnType<Client["listTools"]>>["tools"][number];
10
9
  export interface McpToolCatalogEntry {
11
10
  requestOrigin?: () => McpRequestOrigin | undefined;
12
11
  server: string;
13
12
  tool: string;
14
13
  schema: ListedTool["inputSchema"];
14
+ outputSchema?: ListedTool["outputSchema"];
15
+ modern?: boolean;
15
16
  description?: string;
16
17
  annotations?: ListedTool["annotations"];
17
18
  requestTimeoutMs: number;
@@ -1,6 +1,9 @@
1
+ import { listMcpTools } from "./tool-client.js";
2
+ import { isModernMcpClient } from "./modern/request.js";
3
+ import { hasSupportedModernToolSchemas } from "./modern/tool-schema.js";
1
4
  import { collectAllPages } from "./expose/pagination.js";
2
5
  export async function collectToolCatalog(server, connection, config, options = {}) {
3
- const result = await collectAllPages((cursor) => connection.client.listTools(cursor === undefined ? {} : { cursor }, { timeout: config.requestTimeoutMs }));
6
+ const result = await collectAllPages((cursor) => listMcpTools(connection.client, cursor === undefined ? {} : { cursor }, { timeout: config.requestTimeoutMs }));
4
7
  return result.items.map((tool) => ({
5
8
  requestOrigin: options.requestOrigin,
6
9
  annotations: tool.annotations,
@@ -13,6 +16,8 @@ export async function collectToolCatalog(server, connection, config, options = {
13
16
  outputGuard: options.outputGuard,
14
17
  requestTimeoutMs: config.requestTimeoutMs,
15
18
  schema: tool.inputSchema,
19
+ outputSchema: tool.outputSchema,
20
+ modern: isModernMcpClient(connection.client),
16
21
  server,
17
22
  tool: tool.name,
18
23
  }));
@@ -21,13 +26,23 @@ export async function collectToolCatalog(server, connection, config, options = {
21
26
  * equal identities register identical definitions. */
22
27
  export function mcpRegistrationIdentity(tools, catalog) {
23
28
  return JSON.stringify([
24
- tools.map((entry) => [entry.tool, entry.description, entry.schema, entry.annotations]),
29
+ tools.map((entry) => [
30
+ entry.tool,
31
+ entry.description,
32
+ entry.schema,
33
+ entry.outputSchema,
34
+ entry.modern,
35
+ entry.annotations,
36
+ ]),
25
37
  catalog?.resources ?? [],
26
38
  catalog?.prompts ?? [],
27
39
  ]);
28
40
  }
29
41
  export function cachedToolsToCatalogEntries(server, catalog, connection, requestTimeoutMs, ensureConnected, options = {}) {
30
- return catalog.tools.map((tool) => ({
42
+ const modern = connection.state === "connected" ? isModernMcpClient(connection.client) || undefined : catalog.modern;
43
+ return catalog.tools
44
+ .filter((tool) => !modern || hasSupportedModernToolSchemas(tool))
45
+ .map((tool) => ({
31
46
  requestOrigin: options.requestOrigin,
32
47
  annotations: tool.annotations,
33
48
  agentDir: options.agentDir,
@@ -40,6 +55,8 @@ export function cachedToolsToCatalogEntries(server, catalog, connection, request
40
55
  outputGuard: options.outputGuard,
41
56
  requestTimeoutMs,
42
57
  schema: tool.inputSchema,
58
+ outputSchema: tool.outputSchema,
59
+ modern,
43
60
  server,
44
61
  tool: tool.name,
45
62
  }));
@@ -1,10 +1,11 @@
1
1
  import type { McpToolCatalogEntry } from "../catalog.ts";
2
2
  import type { McpInvocation, McpInvocationError } from "../invocation.ts";
3
3
  import type { McpToolDefinition } from "./register.ts";
4
+ import type { McpToolResult } from "../tool-contracts.ts";
4
5
  export declare function callMcpTool(entry: McpToolCatalogEntry, args: Record<string, unknown>, signal: AbortSignal | undefined, onUpdate: Parameters<McpToolDefinition["execute"]>[3], label: string, invocation: McpInvocation | undefined): Promise<{
5
6
  kind: "called";
6
7
  entry: McpToolCatalogEntry;
7
- result: Awaited<ReturnType<McpToolCatalogEntry["connection"]["client"]["callTool"]>>;
8
+ result: McpToolResult;
8
9
  } | {
9
10
  kind: "refused";
10
11
  error: McpInvocationError;
@@ -4,6 +4,7 @@ import { ensureMcpToolCallConnection, withMcpRetriableFailedSendRetry, withMcpSe
4
4
  import { runMcpConnectionLifecycleCall } from "../idle.js";
5
5
  import { mcpRequestOptions } from "../request-origin.js";
6
6
  import { McpMrtrError } from "../modern/errors.js";
7
+ import { callMcpToolRequest } from "../tool-client.js";
7
8
  export async function callMcpTool(entry, args, signal, onUpdate, label, invocation) {
8
9
  try {
9
10
  return await runMcpConnectionLifecycleCall(entry.connection, () => withMcpSessionExpiryRetry(entry.connection, async () => {
@@ -31,7 +32,7 @@ export async function callMcpTool(entry, args, signal, onUpdate, label, invocati
31
32
  if (prepared !== undefined && !prepared.isCurrent())
32
33
  continue;
33
34
  const current = prepared?.entry ?? entry;
34
- const result = await current.connection.client.callTool({ name: current.tool, arguments: args }, undefined, mcpRequestOptions(current.requestOrigin?.(), {
35
+ const result = await callMcpToolRequest(current.connection.client, { name: current.tool, arguments: args }, current.outputSchema, mcpRequestOptions(current.requestOrigin?.(), {
35
36
  onprogress: (progress) => {
36
37
  onUpdate?.(redactResolvedReferenceData({
37
38
  content: [{ type: "text", text: formatProgress(label, progress) }],
@@ -18,7 +18,7 @@ export function buildMcpToolDefinitions(entries, warn) {
18
18
  return mapMcpCatalogNames(entries, warn).map(({ entry, name }) => createMcpToolDefinition(entry, name));
19
19
  }
20
20
  function createMcpToolDefinition(entry, name) {
21
- const converted = convertJsonSchemaToTypeBox(entry.schema);
21
+ const converted = convertJsonSchemaToTypeBox(entry.schema, entry.modern);
22
22
  const label = `${entry.server}/${entry.tool}`;
23
23
  registerDispatchIdentity(converted.schema, () => entry.invocation?.identity(entry));
24
24
  return {
@@ -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
  },
@@ -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
@@ -22,7 +22,7 @@ export function getModernDispatchGuard(params) {
22
22
  ? params[modernDispatchGuard]
23
23
  : undefined;
24
24
  }
25
- /** One instance-local adapter. Captured SDK requests retain ID allocation and final output-schema validation. */
25
+ /** One instance-local adapter. Captured SDK requests retain ID allocation; the caller supplies its era's result contract. */
26
26
  export function installModernRequestAdapter(client, protocol, clock = mcpElapsedClock, readCredential = () => undefined) {
27
27
  const sdkRequest = client.request.bind(client);
28
28
  states.set(client, protocol);
@@ -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