@animalabs/agent-framework 0.26.1 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/README.md +57 -7
  2. package/dist/src/agent.d.ts +263 -22
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +531 -54
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/server.d.ts.map +1 -1
  7. package/dist/src/api/server.js +1 -2
  8. package/dist/src/api/server.js.map +1 -1
  9. package/dist/src/auto-continue.d.ts +33 -0
  10. package/dist/src/auto-continue.d.ts.map +1 -0
  11. package/dist/src/auto-continue.js +35 -0
  12. package/dist/src/auto-continue.js.map +1 -0
  13. package/dist/src/code-execution/isolate/scripts.d.ts +11 -0
  14. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
  15. package/dist/src/code-execution/isolate/scripts.js +5 -1
  16. package/dist/src/code-execution/isolate/scripts.js.map +1 -1
  17. package/dist/src/framework.d.ts +829 -156
  18. package/dist/src/framework.d.ts.map +1 -1
  19. package/dist/src/framework.js +3906 -1008
  20. package/dist/src/framework.js.map +1 -1
  21. package/dist/src/gate/event-gate.d.ts +309 -46
  22. package/dist/src/gate/event-gate.d.ts.map +1 -1
  23. package/dist/src/gate/event-gate.js +2117 -395
  24. package/dist/src/gate/event-gate.js.map +1 -1
  25. package/dist/src/gate/index.d.ts +2 -2
  26. package/dist/src/gate/index.d.ts.map +1 -1
  27. package/dist/src/gate/index.js +1 -1
  28. package/dist/src/gate/index.js.map +1 -1
  29. package/dist/src/gate/recipe-reconcile.d.ts +103 -0
  30. package/dist/src/gate/recipe-reconcile.d.ts.map +1 -0
  31. package/dist/src/gate/recipe-reconcile.js +325 -0
  32. package/dist/src/gate/recipe-reconcile.js.map +1 -0
  33. package/dist/src/gate/types.d.ts +68 -8
  34. package/dist/src/gate/types.d.ts.map +1 -1
  35. package/dist/src/index.d.ts +6 -4
  36. package/dist/src/index.d.ts.map +1 -1
  37. package/dist/src/index.js +5 -2
  38. package/dist/src/index.js.map +1 -1
  39. package/dist/src/mcpl/channel-registry.d.ts +433 -113
  40. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  41. package/dist/src/mcpl/channel-registry.js +1152 -434
  42. package/dist/src/mcpl/channel-registry.js.map +1 -1
  43. package/dist/src/mcpl/conversation-router.d.ts +16 -8
  44. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  45. package/dist/src/mcpl/conversation-router.js +29 -15
  46. package/dist/src/mcpl/conversation-router.js.map +1 -1
  47. package/dist/src/mcpl/convert-block.d.ts.map +1 -1
  48. package/dist/src/mcpl/convert-block.js +14 -2
  49. package/dist/src/mcpl/convert-block.js.map +1 -1
  50. package/dist/src/mcpl/hook-orchestrator.d.ts +7 -1
  51. package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
  52. package/dist/src/mcpl/hook-orchestrator.js +32 -7
  53. package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
  54. package/dist/src/mcpl/index.d.ts +5 -1
  55. package/dist/src/mcpl/index.d.ts.map +1 -1
  56. package/dist/src/mcpl/index.js +4 -0
  57. package/dist/src/mcpl/index.js.map +1 -1
  58. package/dist/src/mcpl/inference-router.d.ts +14 -2
  59. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  60. package/dist/src/mcpl/inference-router.js +73 -28
  61. package/dist/src/mcpl/inference-router.js.map +1 -1
  62. package/dist/src/mcpl/prose-grammar.d.ts +4 -0
  63. package/dist/src/mcpl/prose-grammar.d.ts.map +1 -1
  64. package/dist/src/mcpl/prose-grammar.js +6 -0
  65. package/dist/src/mcpl/prose-grammar.js.map +1 -1
  66. package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
  67. package/dist/src/mcpl/protocol-family.js +3 -0
  68. package/dist/src/mcpl/protocol-family.js.map +1 -1
  69. package/dist/src/mcpl/push-coalescer.d.ts +20 -2
  70. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  71. package/dist/src/mcpl/push-coalescer.js +33 -11
  72. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  73. package/dist/src/mcpl/reference-fetcher.d.ts +63 -42
  74. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  75. package/dist/src/mcpl/reference-fetcher.js +177 -145
  76. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  77. package/dist/src/mcpl/references.d.ts +380 -46
  78. package/dist/src/mcpl/references.d.ts.map +1 -1
  79. package/dist/src/mcpl/references.js +737 -78
  80. package/dist/src/mcpl/references.js.map +1 -1
  81. package/dist/src/mcpl/server-connection.d.ts +82 -3
  82. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  83. package/dist/src/mcpl/server-connection.js +132 -7
  84. package/dist/src/mcpl/server-connection.js.map +1 -1
  85. package/dist/src/mcpl/tool-lifecycle.d.ts +11 -0
  86. package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
  87. package/dist/src/mcpl/tool-lifecycle.js +21 -0
  88. package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
  89. package/dist/src/mcpl/tool-presence.d.ts +259 -0
  90. package/dist/src/mcpl/tool-presence.d.ts.map +1 -0
  91. package/dist/src/mcpl/tool-presence.js +628 -0
  92. package/dist/src/mcpl/tool-presence.js.map +1 -0
  93. package/dist/src/mcpl/tool-result-normalize.d.ts +9 -3
  94. package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -1
  95. package/dist/src/mcpl/tool-result-normalize.js +14 -6
  96. package/dist/src/mcpl/tool-result-normalize.js.map +1 -1
  97. package/dist/src/mcpl/tool-signature.d.ts +39 -0
  98. package/dist/src/mcpl/tool-signature.d.ts.map +1 -0
  99. package/dist/src/mcpl/tool-signature.js +292 -0
  100. package/dist/src/mcpl/tool-signature.js.map +1 -0
  101. package/dist/src/mcpl/toolset-host.d.ts +200 -0
  102. package/dist/src/mcpl/toolset-host.d.ts.map +1 -0
  103. package/dist/src/mcpl/toolset-host.js +621 -0
  104. package/dist/src/mcpl/toolset-host.js.map +1 -0
  105. package/dist/src/mcpl/toolsets-module.d.ts +80 -0
  106. package/dist/src/mcpl/toolsets-module.d.ts.map +1 -0
  107. package/dist/src/mcpl/toolsets-module.js +431 -0
  108. package/dist/src/mcpl/toolsets-module.js.map +1 -0
  109. package/dist/src/mcpl/transport.d.ts +2 -2
  110. package/dist/src/mcpl/transport.d.ts.map +1 -1
  111. package/dist/src/mcpl/transport.js +3 -2
  112. package/dist/src/mcpl/transport.js.map +1 -1
  113. package/dist/src/mcpl/types.d.ts +21 -1
  114. package/dist/src/mcpl/types.d.ts.map +1 -1
  115. package/dist/src/mcpl/types.js.map +1 -1
  116. package/dist/src/mcpl/visible-content.d.ts +7 -9
  117. package/dist/src/mcpl/visible-content.d.ts.map +1 -1
  118. package/dist/src/mcpl/visible-content.js +21 -9
  119. package/dist/src/mcpl/visible-content.js.map +1 -1
  120. package/dist/src/module-registry.d.ts +23 -8
  121. package/dist/src/module-registry.d.ts.map +1 -1
  122. package/dist/src/module-registry.js +68 -15
  123. package/dist/src/module-registry.js.map +1 -1
  124. package/dist/src/modules/discord/index.js +1 -1
  125. package/dist/src/modules/discord/index.js.map +1 -1
  126. package/dist/src/modules/history/index.d.ts +2 -1
  127. package/dist/src/modules/history/index.d.ts.map +1 -1
  128. package/dist/src/modules/history/index.js +51 -17
  129. package/dist/src/modules/history/index.js.map +1 -1
  130. package/dist/src/modules/history/semantic.d.ts +108 -6
  131. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  132. package/dist/src/modules/history/semantic.js +273 -57
  133. package/dist/src/modules/history/semantic.js.map +1 -1
  134. package/dist/src/modules/workspace/direct-write.d.ts +181 -0
  135. package/dist/src/modules/workspace/direct-write.d.ts.map +1 -0
  136. package/dist/src/modules/workspace/direct-write.js +384 -0
  137. package/dist/src/modules/workspace/direct-write.js.map +1 -0
  138. package/dist/src/modules/workspace/edit-text.d.ts +26 -12
  139. package/dist/src/modules/workspace/edit-text.d.ts.map +1 -1
  140. package/dist/src/modules/workspace/edit-text.js +58 -28
  141. package/dist/src/modules/workspace/edit-text.js.map +1 -1
  142. package/dist/src/modules/workspace/effects.d.ts +10 -0
  143. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  144. package/dist/src/modules/workspace/effects.js +1 -1
  145. package/dist/src/modules/workspace/effects.js.map +1 -1
  146. package/dist/src/modules/workspace/index.d.ts +246 -27
  147. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  148. package/dist/src/modules/workspace/index.js +1216 -236
  149. package/dist/src/modules/workspace/index.js.map +1 -1
  150. package/dist/src/modules/workspace/read-page.d.ts +41 -0
  151. package/dist/src/modules/workspace/read-page.d.ts.map +1 -0
  152. package/dist/src/modules/workspace/read-page.js +213 -0
  153. package/dist/src/modules/workspace/read-page.js.map +1 -0
  154. package/dist/src/modules/workspace/result-text.d.ts +67 -0
  155. package/dist/src/modules/workspace/result-text.d.ts.map +1 -0
  156. package/dist/src/modules/workspace/result-text.js +94 -0
  157. package/dist/src/modules/workspace/result-text.js.map +1 -0
  158. package/dist/src/modules/workspace/text.d.ts +29 -12
  159. package/dist/src/modules/workspace/text.d.ts.map +1 -1
  160. package/dist/src/modules/workspace/text.js +35 -33
  161. package/dist/src/modules/workspace/text.js.map +1 -1
  162. package/dist/src/modules/workspace/types.d.ts +41 -17
  163. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  164. package/dist/src/modules/workspace/types.js.map +1 -1
  165. package/dist/src/sent-rows.d.ts +59 -0
  166. package/dist/src/sent-rows.d.ts.map +1 -0
  167. package/dist/src/sent-rows.js +145 -0
  168. package/dist/src/sent-rows.js.map +1 -0
  169. package/dist/src/silent-heartbeat.d.ts +17 -10
  170. package/dist/src/silent-heartbeat.d.ts.map +1 -1
  171. package/dist/src/silent-heartbeat.js.map +1 -1
  172. package/dist/src/stable-hash.d.ts +3 -0
  173. package/dist/src/stable-hash.d.ts.map +1 -0
  174. package/dist/src/stable-hash.js +15 -0
  175. package/dist/src/stable-hash.js.map +1 -0
  176. package/dist/src/stored-injections.d.ts +99 -0
  177. package/dist/src/stored-injections.d.ts.map +1 -0
  178. package/dist/src/stored-injections.js +106 -0
  179. package/dist/src/stored-injections.js.map +1 -0
  180. package/dist/src/text-as-stored.d.ts +4 -3
  181. package/dist/src/text-as-stored.d.ts.map +1 -1
  182. package/dist/src/text-as-stored.js +4 -3
  183. package/dist/src/text-as-stored.js.map +1 -1
  184. package/dist/src/thinking-drops.d.ts +81 -0
  185. package/dist/src/thinking-drops.d.ts.map +1 -0
  186. package/dist/src/thinking-drops.js +103 -0
  187. package/dist/src/thinking-drops.js.map +1 -0
  188. package/dist/src/tool-image-ledger.d.ts +9 -8
  189. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  190. package/dist/src/tool-image-ledger.js +9 -8
  191. package/dist/src/tool-image-ledger.js.map +1 -1
  192. package/dist/src/tool-presentation.d.ts.map +1 -1
  193. package/dist/src/tool-presentation.js +3 -1
  194. package/dist/src/tool-presentation.js.map +1 -1
  195. package/dist/src/tool-result-guard.d.ts +4 -2
  196. package/dist/src/tool-result-guard.d.ts.map +1 -1
  197. package/dist/src/tool-result-guard.js +21 -6
  198. package/dist/src/tool-result-guard.js.map +1 -1
  199. package/dist/src/tool-result-history.d.ts +186 -21
  200. package/dist/src/tool-result-history.d.ts.map +1 -1
  201. package/dist/src/tool-result-history.js +428 -89
  202. package/dist/src/tool-result-history.js.map +1 -1
  203. package/dist/src/tune-out/coordinator.d.ts +17 -9
  204. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  205. package/dist/src/tune-out/coordinator.js +84 -37
  206. package/dist/src/tune-out/coordinator.js.map +1 -1
  207. package/dist/src/types/agent.d.ts +186 -7
  208. package/dist/src/types/agent.d.ts.map +1 -1
  209. package/dist/src/types/events.d.ts +19 -2
  210. package/dist/src/types/events.d.ts.map +1 -1
  211. package/dist/src/types/framework.d.ts +24 -3
  212. package/dist/src/types/framework.d.ts.map +1 -1
  213. package/dist/src/types/module.d.ts +90 -7
  214. package/dist/src/types/module.d.ts.map +1 -1
  215. package/dist/src/types/trace.d.ts +57 -2
  216. package/dist/src/types/trace.d.ts.map +1 -1
  217. package/package.json +4 -3
  218. package/src/agent.ts +593 -60
  219. package/src/api/server.ts +1 -2
  220. package/src/auto-continue.ts +42 -0
  221. package/src/code-execution/isolate/scripts.ts +11 -3
  222. package/src/framework.ts +4004 -1026
  223. package/src/gate/event-gate.ts +2315 -389
  224. package/src/gate/index.ts +2 -1
  225. package/src/gate/recipe-reconcile.ts +355 -0
  226. package/src/gate/types.ts +70 -8
  227. package/src/index.ts +7 -4
  228. package/src/mcpl/channel-registry.ts +1220 -467
  229. package/src/mcpl/conversation-router.ts +34 -16
  230. package/src/mcpl/convert-block.ts +14 -1
  231. package/src/mcpl/hook-orchestrator.ts +33 -6
  232. package/src/mcpl/index.ts +6 -1
  233. package/src/mcpl/inference-router.ts +79 -34
  234. package/src/mcpl/prose-grammar.ts +7 -0
  235. package/src/mcpl/protocol-family.ts +2 -0
  236. package/src/mcpl/push-coalescer.ts +51 -13
  237. package/src/mcpl/reference-fetcher.ts +201 -162
  238. package/src/mcpl/references.ts +852 -78
  239. package/src/mcpl/server-connection.ts +145 -7
  240. package/src/mcpl/tool-lifecycle.ts +21 -0
  241. package/src/mcpl/tool-presence.ts +650 -0
  242. package/src/mcpl/tool-result-normalize.ts +18 -6
  243. package/src/mcpl/tool-signature.ts +285 -0
  244. package/src/mcpl/toolset-host.ts +637 -0
  245. package/src/mcpl/toolsets-module.ts +451 -0
  246. package/src/mcpl/transport.ts +3 -2
  247. package/src/mcpl/types.ts +22 -1
  248. package/src/mcpl/visible-content.ts +22 -9
  249. package/src/module-registry.ts +77 -24
  250. package/src/modules/discord/index.ts +1 -1
  251. package/src/modules/history/index.ts +48 -18
  252. package/src/modules/history/semantic.ts +308 -54
  253. package/src/modules/workspace/direct-write.ts +425 -0
  254. package/src/modules/workspace/edit-text.ts +82 -35
  255. package/src/modules/workspace/effects.ts +1 -1
  256. package/src/modules/workspace/index.ts +1274 -241
  257. package/src/modules/workspace/read-page.ts +222 -0
  258. package/src/modules/workspace/result-text.ts +119 -0
  259. package/src/modules/workspace/text.ts +58 -32
  260. package/src/modules/workspace/types.ts +41 -17
  261. package/src/sent-rows.ts +162 -0
  262. package/src/silent-heartbeat.ts +17 -10
  263. package/src/stable-hash.ts +14 -0
  264. package/src/stored-injections.ts +131 -0
  265. package/src/text-as-stored.ts +4 -3
  266. package/src/thinking-drops.ts +141 -0
  267. package/src/tool-image-ledger.ts +9 -8
  268. package/src/tool-presentation.ts +3 -1
  269. package/src/tool-result-guard.ts +19 -6
  270. package/src/tool-result-history.ts +488 -88
  271. package/src/tune-out/coordinator.ts +80 -37
  272. package/src/types/agent.ts +189 -7
  273. package/src/types/events.ts +19 -2
  274. package/src/types/framework.ts +21 -3
  275. package/src/types/module.ts +86 -7
  276. package/src/types/trace.ts +58 -2
@@ -219,6 +219,41 @@ function protocolVersionRejection(config: McplServerConfig, error: { code: numbe
219
219
 
220
220
  const errorOf = (value: unknown): Error => (value instanceof Error ? value : new Error(String(value)));
221
221
 
222
+ /**
223
+ * The RFC-014 members of a server's advertisement (`toolHints`, `toolsets`,
224
+ * `surface`), read from it as the server sent it. They are declarations, not
225
+ * §6.2 capabilities: the host's capability mask, which reads every member as
226
+ * a capability, never applies to them. Null when the server advertised none
227
+ * of them, or no MCPL at all.
228
+ */
229
+ export function toolsetDeclarationsOf(advertised: unknown): Record<string, unknown> | null {
230
+ if (advertised === null || typeof advertised !== 'object' || Array.isArray(advertised)) return null;
231
+ const { toolHints, toolsets, surface } = advertised as Record<string, unknown>;
232
+ if (toolHints === undefined && toolsets === undefined && surface === undefined) return null;
233
+ return {
234
+ ...(toolHints !== undefined ? { toolHints } : {}),
235
+ ...(toolsets !== undefined ? { toolsets } : {}),
236
+ ...(surface !== undefined ? { surface } : {}),
237
+ };
238
+ }
239
+
240
+ /** What the RFC-014 read tap is handed as the connection reads a message
241
+ * that can carry hint changes (see McplServerConnection.setReadTap). */
242
+ export type HintRead =
243
+ /** A response to a request that can carry hints (RFC-014 §6.1 carrier 1).
244
+ * `awaited`: whoever sent it still waits for it; false for a result that
245
+ * arrives after its timeout. A JSON-RPC error carries no state and isn't
246
+ * tapped. */
247
+ | { kind: 'result'; method: string; params: Record<string, unknown>; result: unknown; awaited: boolean }
248
+ /** A push/event (carrier 2), as read, before the plane gate or the grant
249
+ * admit or refuse it. */
250
+ | { kind: 'push'; params: unknown }
251
+ /** A standalone tools/hints (carrier 3). */
252
+ | { kind: 'hints'; params: unknown };
253
+
254
+ /** Requests whose results can carry hint state (RFC-014 §6.1, §4.1). */
255
+ const HINTED_METHODS: ReadonlySet<string> = new Set(['tools/call', 'toolsets/open', 'toolsets/close', 'toolsets/list', 'tools/list']);
256
+
222
257
  /**
223
258
  * A handshake that failed, and whose cleanup couldn't confirm that the child
224
259
  * exited. The transport goes to whoever owns the launch.
@@ -404,6 +439,46 @@ export class McplServerConnection extends EventEmitter {
404
439
  * the epoch their opening went to, never across a reconnect. */
405
440
  transportEpoch = 0;
406
441
 
442
+ /**
443
+ * RFC-014: the server's `toolHints`, `toolsets` and `surface` members as it
444
+ * advertised them at the last handshake (toolsetDeclarationsOf), or null.
445
+ * Read by the host's toolset controller; they grant nothing.
446
+ */
447
+ toolsetDeclarations: Record<string, unknown> | null = null;
448
+
449
+ /**
450
+ * RFC-014 §6.1: hint changes apply as each message carrying them is read,
451
+ * in rev order (arrival order on stdio and WebSocket), whatever becomes of
452
+ * the message: a push the plane holds or the grant refuses, a result its
453
+ * caller stopped waiting for. The tap sees every such message of the
454
+ * current transport, synchronously, before anything routes it; a late
455
+ * answer's request is remembered past its timeout (hintedRequests) so its
456
+ * hints still apply. Null (the default) taps nothing, and the connection
457
+ * behaves as it always has.
458
+ */
459
+ private readTap: ((read: HintRead, epoch: number) => void) | null = null;
460
+ /** The hinted requests sent on this transport, by id, kept past a timeout
461
+ * so a late result is still tapped; bounded. Cleared at each boundary,
462
+ * since the next transport numbers its requests afresh. */
463
+ private hintedRequests = new Map<string | number, { method: string; params: Record<string, unknown> }>();
464
+
465
+ /** Install (or, with null, remove) the RFC-014 read tap (see readTap). */
466
+ setReadTap(tap: ((read: HintRead, epoch: number) => void) | null): void {
467
+ this.readTap = tap;
468
+ if (!tap) this.hintedRequests.clear();
469
+ }
470
+
471
+ /** Hand a read to the tap, contained: a tap that throws is logged, and the
472
+ * message goes on its way as if there were none. */
473
+ private tap(read: HintRead): void {
474
+ if (!this.readTap) return;
475
+ try {
476
+ this.readTap(read, this.transportEpoch);
477
+ } catch (error) {
478
+ console.error(`[mcpl] ${this.id}: the tool-hints tap failed: ${logDetail(describeCause(error))}`);
479
+ }
480
+ }
481
+
407
482
  /**
408
483
  * §17 manifest tracking (host side of RFC-003): what this host actually
409
484
  * fetched and acted on — never the server's announcement log (§17.10:
@@ -454,6 +529,8 @@ export class McplServerConnection extends EventEmitter {
454
529
  // (connect-failed, reconnect-failed), the server's stderr, and
455
530
  // orphaned-response audits.
456
531
  this.replaceHeld(this.keepAcrossBoundary(this.bufferedEvents));
532
+ // The next transport numbers its requests afresh (RFC-014's tap).
533
+ this.hintedRequests.clear();
457
534
  // RFC-007 §6.5: a filter is per connection epoch — after a reconnect the
458
535
  // server sends it again, and until then receives metadata only.
459
536
  this.toolObserveFilter = null;
@@ -1116,9 +1193,10 @@ export class McplServerConnection extends EventEmitter {
1116
1193
  owner.holdUnreaped(error.transport, error.cleanup);
1117
1194
  throw new McplUnreapedLaunchError(owner, error.failure, error.cleanup);
1118
1195
  }
1119
- const { transport, capabilities, droppedCapabilities, mcpToolsAdvertised, protocolVersion } = established;
1196
+ const { transport, capabilities, droppedCapabilities, mcpToolsAdvertised, protocolVersion, toolsetDeclarations } = established;
1120
1197
 
1121
1198
  const connection = new McplServerConnection(config.id, capabilities, transport);
1199
+ connection.toolsetDeclarations = toolsetDeclarations;
1122
1200
  connection.droppedCapabilities = droppedCapabilities;
1123
1201
  connection.mcpToolsAdvertised = mcpToolsAdvertised;
1124
1202
  connection.protocolVersion = protocolVersion;
@@ -1172,7 +1250,7 @@ export class McplServerConnection extends EventEmitter {
1172
1250
  config: McplServerConfig,
1173
1251
  hostCapabilities: McplHostCapabilities,
1174
1252
  signal?: AbortSignal,
1175
- ): Promise<{ transport: McplTransport; capabilities: McplCapabilities | null; droppedCapabilities: ReadonlySet<string>; mcpToolsAdvertised: boolean; protocolVersion: string }> {
1253
+ ): Promise<{ transport: McplTransport; capabilities: McplCapabilities | null; droppedCapabilities: ReadonlySet<string>; mcpToolsAdvertised: boolean; protocolVersion: string; toolsetDeclarations: Record<string, unknown> | null }> {
1176
1254
  const closedWhileConnecting = () => new Error(`MCPL server "${config.id}" was closed while connecting`);
1177
1255
  if (signal?.aborted) throw closedWhileConnecting();
1178
1256
  let transport: McplTransport;
@@ -1362,7 +1440,10 @@ export class McplServerConnection extends EventEmitter {
1362
1440
  // listener of its own, in the continuation of this return, before any
1363
1441
  // I/O callback can emit.
1364
1442
  transport.off('error', logAfterSettled);
1365
- return { transport, capabilities: scoped, droppedCapabilities: new Set(dropped), mcpToolsAdvertised, protocolVersion };
1443
+ return {
1444
+ transport, capabilities: scoped, droppedCapabilities: new Set(dropped), mcpToolsAdvertised, protocolVersion,
1445
+ toolsetDeclarations: toolsetDeclarationsOf(rawCapabilities),
1446
+ };
1366
1447
  } catch (err) {
1367
1448
  // Never leak the child / socket if the handshake fails. A cleanup that
1368
1449
  // can't confirm the child exited is part of the failure, never
@@ -1538,7 +1619,7 @@ export class McplServerConnection extends EventEmitter {
1538
1619
  }
1539
1620
 
1540
1621
  /** Send `channels/open` request and await result. It keeps a deadline as
1541
- * channels/publish does: a delivery opens a closed channel before it
1622
+ * channels/publish does: a delivery can open a closed channel before it
1542
1623
  * publishes, so an open that is never answered would hold the delivery,
1543
1624
  * and the turn that waits for it, for good. */
1544
1625
  sendChannelsOpen(params: ChannelsOpenParams): Promise<ChannelsOpenResult> {
@@ -1662,18 +1743,39 @@ export class McplServerConnection extends EventEmitter {
1662
1743
  throw new Error(`MCPL server "${this.id}" tools/list did not finish within ${MAX_TOOL_LIST_PAGES} pages`);
1663
1744
  }
1664
1745
 
1665
- /** Send `tools/call` and return the result. Optionally includes state/checkpoint for stateful tools. */
1746
+ /** Send `tools/call` and return the result. Optionally includes
1747
+ * state/checkpoint for stateful tools, and `_meta` (RFC-014 §5.5: the
1748
+ * instance a call is for rides in `_meta["mcpl/instance"]`). */
1666
1749
  sendToolsCall(
1667
1750
  name: string,
1668
1751
  args: Record<string, unknown>,
1669
1752
  stateParams?: { state?: unknown; checkpoint?: string },
1753
+ meta?: Record<string, unknown>,
1670
1754
  ): Promise<McpToolCallResult> {
1671
1755
  const params: Record<string, unknown> = { name, arguments: args };
1672
1756
  if (stateParams?.state !== undefined) params.state = stateParams.state;
1673
1757
  if (stateParams?.checkpoint !== undefined) params.checkpoint = stateParams.checkpoint;
1758
+ if (meta !== undefined) params._meta = meta;
1674
1759
  return this.sendRequest('tools/call', params) as Promise<McpToolCallResult>;
1675
1760
  }
1676
1761
 
1762
+ /** RFC-014 §5.2 `toolsets/open` (Host → Server). The server's refusal
1763
+ * rejects, as any error answer does. */
1764
+ sendToolsetsOpen(params: { toolset: string; instance?: string; options?: Record<string, unknown> }, timeoutMs?: number): Promise<Record<string, unknown> | null> {
1765
+ return this.sendRequest('toolsets/open', { ...params }, timeoutMs === undefined ? {} : { timeoutMs }) as Promise<Record<string, unknown> | null>;
1766
+ }
1767
+
1768
+ /** RFC-014 §5.2 `toolsets/close` (Host → Server). */
1769
+ sendToolsetsClose(params: { toolset: string; instance?: string }, timeoutMs?: number): Promise<Record<string, unknown> | null> {
1770
+ return this.sendRequest('toolsets/close', { ...params }, timeoutMs === undefined ? {} : { timeoutMs }) as Promise<Record<string, unknown> | null>;
1771
+ }
1772
+
1773
+ /** RFC-014 §5.3 `toolsets/list` (Host → Server): what the principal has
1774
+ * open, and the full hint state of its dynamic tools. */
1775
+ sendToolsetsList(timeoutMs?: number): Promise<Record<string, unknown> | null> {
1776
+ return this.sendRequest('toolsets/list', {}, timeoutMs === undefined ? {} : { timeoutMs }) as Promise<Record<string, unknown> | null>;
1777
+ }
1778
+
1677
1779
  /**
1678
1780
  * Send a tools/call with a mandatory request-local deadline. This is used by
1679
1781
  * durable external-side-effect accounting, whose safety bound must remain in
@@ -1940,7 +2042,7 @@ export class McplServerConnection extends EventEmitter {
1940
2042
  if (!permanent) this.scheduleReconnect();
1941
2043
  return;
1942
2044
  }
1943
- const { transport, capabilities, droppedCapabilities, mcpToolsAdvertised, protocolVersion } = established;
2045
+ const { transport, capabilities, droppedCapabilities, mcpToolsAdvertised, protocolVersion, toolsetDeclarations } = established;
1944
2046
 
1945
2047
  if (signal.aborted) {
1946
2048
  // Closed by the host while the handshake finished: this launch never
@@ -1968,6 +2070,7 @@ export class McplServerConnection extends EventEmitter {
1968
2070
  this.resetPolicyForTransportBoundary();
1969
2071
  this.transport = transport;
1970
2072
  this.capabilities = capabilities;
2073
+ this.toolsetDeclarations = toolsetDeclarations;
1971
2074
  this.droppedCapabilities = droppedCapabilities;
1972
2075
  this.mcpToolsAdvertised = mcpToolsAdvertised;
1973
2076
  this.protocolVersion = protocolVersion;
@@ -2112,6 +2215,12 @@ export class McplServerConnection extends EventEmitter {
2112
2215
  pending.timer.unref?.();
2113
2216
  }
2114
2217
  this.pendingRequests.set(id, pending);
2218
+ // RFC-014: a request whose result can carry hints is remembered past
2219
+ // its timeout, so a late result's hints still apply (readTap).
2220
+ if (this.readTap && HINTED_METHODS.has(method)) {
2221
+ this.hintedRequests.set(id, { method, params });
2222
+ if (this.hintedRequests.size > 1000) this.hintedRequests.delete(this.hintedRequests.keys().next().value!);
2223
+ }
2115
2224
  try {
2116
2225
  this.transport!.writeLine(line);
2117
2226
  } catch (err) {
@@ -2120,6 +2229,7 @@ export class McplServerConnection extends EventEmitter {
2120
2229
  // method, id and the transport's words are the operator's, logged
2121
2230
  // after the refusal, as refused() logs.
2122
2231
  this.pendingRequests.delete(id);
2232
+ this.hintedRequests.delete(id);
2123
2233
  if (pending.timer) clearTimeout(pending.timer);
2124
2234
  reject(new McplRequestError(
2125
2235
  `The request wasn't sent: this host couldn't write it to the "${this.id}" server, so nothing ran. Try again later.`,
@@ -2258,7 +2368,12 @@ export class McplServerConnection extends EventEmitter {
2258
2368
  private dispatchMessage(msg: JsonRpcRequest | JsonRpcResponse, epoch: number): void {
2259
2369
  // Is this a response to one of our outbound requests?
2260
2370
  if ('id' in msg && msg.id != null && !('method' in msg)) {
2261
- this.handleResponse(msg as JsonRpcResponse, epoch !== this.transportEpoch);
2371
+ const late = epoch !== this.transportEpoch;
2372
+ // RFC-014: its hints apply as it's read, whether or not anyone still
2373
+ // waits for it. A dead transport's revs are its own, so a response
2374
+ // read after its boundary is never tapped.
2375
+ if (!late) this.tapResponse(msg as JsonRpcResponse);
2376
+ this.handleResponse(msg as JsonRpcResponse, late);
2262
2377
  return;
2263
2378
  }
2264
2379
 
@@ -2282,6 +2397,17 @@ export class McplServerConnection extends EventEmitter {
2282
2397
  return;
2283
2398
  }
2284
2399
 
2400
+ // RFC-014 §6.1: a push's hints apply as it's read, before the plane gate
2401
+ // holds it or the grant refuses it. A standalone tools/hints is consumed
2402
+ // here when a tap listens (it needs no grant), and answered if it came as
2403
+ // a request; with no tap it stays a method this host doesn't handle.
2404
+ if (request.method === 'push/event') this.tap({ kind: 'push', params: request.params });
2405
+ else if (request.method === 'tools/hints' && this.readTap) {
2406
+ this.tap({ kind: 'hints', params: request.params });
2407
+ if (request.id != null) this.sendResponse(request.id, {});
2408
+ return;
2409
+ }
2410
+
2285
2411
  const eventName = typeof request.method === 'string' && Object.hasOwn(McplServerConnection.METHOD_TO_EVENT, request.method)
2286
2412
  ? McplServerConnection.METHOD_TO_EVENT[request.method] : undefined;
2287
2413
 
@@ -2427,6 +2553,18 @@ export class McplServerConnection extends EventEmitter {
2427
2553
  return true;
2428
2554
  }
2429
2555
 
2556
+ /** Tap a response to a hinted request (RFC-014): its result's hints, or a
2557
+ * list's statement, apply now, awaited or not. */
2558
+ private tapResponse(response: JsonRpcResponse): void {
2559
+ if (!this.readTap) return;
2560
+ const sent = this.hintedRequests.get(response.id);
2561
+ if (!sent) return;
2562
+ this.hintedRequests.delete(response.id);
2563
+ const result = response.result;
2564
+ if (response.error || result === null || typeof result !== 'object' || Array.isArray(result)) return;
2565
+ this.tap({ kind: 'result', method: sent.method, params: sent.params, result, awaited: this.pendingRequests.has(response.id) });
2566
+ }
2567
+
2430
2568
  /**
2431
2569
  * Handle a JSON-RPC response by resolving/rejecting the corresponding pending request.
2432
2570
  */
@@ -705,6 +705,27 @@ export class ToolLifecycleEmitter {
705
705
  });
706
706
  }
707
707
 
708
+ /**
709
+ * A call the host routes to another tool before it executes it: RFC-014's
710
+ * `toolsets--act`, which calls a server's dynamic tool. Until it opens, the
711
+ * call is described as that tool, with that tool's arguments, class and
712
+ * provider, so observers see (and narrowing, filters and class rules apply
713
+ * to) what actually runs, as for a direct call of it. Its ids stay.
714
+ */
715
+ retarget(agentName: string, callId: string, call: { name: string; input: unknown }): void {
716
+ const tracked = this.calls.get(callKey(agentName, callId));
717
+ if (!tracked || tracked.opened) return;
718
+ const described = this.host.describe(call.name);
719
+ const { serverId: _serverId, serverTool: _serverTool, ...kept } = tracked.call;
720
+ tracked.call = {
721
+ ...kept,
722
+ tool: call.name,
723
+ class: described.class,
724
+ ...(described.serverId !== undefined ? { serverId: described.serverId, serverTool: described.serverTool } : {}),
725
+ input: call.input,
726
+ };
727
+ }
728
+
708
729
  /**
709
730
  * The host refused the call before executing it — its provider is gone,
710
731
  * host tool policy denies it, a conversation-bound agent reached outside