@code-yeongyu/senpi 2026.10.25-1 → 2026.10.25-3

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 (226) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/bundle/chunks/{args-JXULI4EH.js → args-B444OQBK.js} +1 -1
  3. package/dist/bundle/chunks/{auth-4NUNUSTF.js → auth-C77UQA55.js} +1 -1
  4. package/dist/bundle/chunks/{chunk-RHHBXIRN.js → chunk-4WZRVZNX.js} +1 -1
  5. package/dist/bundle/chunks/chunk-6O4SEW2W.js +5 -0
  6. package/dist/bundle/chunks/{chunk-Y5OTHMXO.js → chunk-7CNOCJCN.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-YDCRA5ES.js → chunk-7W7OJDIY.js} +4 -4
  8. package/dist/bundle/chunks/chunk-AX72VNRS.js +27 -0
  9. package/dist/bundle/chunks/{chunk-FWXMMMCV.js → chunk-C5UFJ7B3.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-7IRLFYNM.js → chunk-CNL6QSUB.js} +4 -4
  11. package/dist/bundle/chunks/{chunk-BTNYLNWZ.js → chunk-FFQOEC76.js} +5 -5
  12. package/dist/bundle/chunks/{chunk-VOBD6FQG.js → chunk-FHXR4ZAC.js} +1 -1
  13. package/dist/bundle/chunks/chunk-H3HNMY2W.js +13 -0
  14. package/dist/bundle/chunks/{chunk-BMYNMUVZ.js → chunk-HVJI5T75.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-EWOEVVQT.js → chunk-JA2R2ZSE.js} +1 -1
  16. package/dist/bundle/chunks/chunk-JPGT64RV.js +2 -0
  17. package/dist/bundle/chunks/chunk-KXB5WIS5.js +4 -0
  18. package/dist/bundle/chunks/chunk-L73UYAH5.js +3 -0
  19. package/dist/bundle/chunks/chunk-LTU6UFEX.js +2 -0
  20. package/dist/bundle/chunks/{chunk-EYBDBR7V.js → chunk-MJ5NNWX4.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-RVYSJBZ7.js → chunk-O3Q2ET6W.js} +2 -5
  22. package/dist/bundle/chunks/chunk-O5RYI3IW.js +2 -0
  23. package/dist/bundle/chunks/{chunk-SGDOZT6B.js → chunk-ODTGC6YU.js} +1 -1
  24. package/dist/bundle/chunks/chunk-P3M2PF6K.js +27 -0
  25. package/dist/bundle/chunks/chunk-SWJXRC2I.js +5 -0
  26. package/dist/bundle/chunks/{chunk-QH2HSCMX.js → chunk-TTU2RNVX.js} +3 -3
  27. package/dist/bundle/chunks/{chunk-PXKF2RHD.js → chunk-UFJ2K7Q7.js} +1 -1
  28. package/dist/bundle/chunks/{chunk-7UUOGZKR.js → chunk-UQNRW3VU.js} +1 -1
  29. package/dist/bundle/chunks/{chunk-U67ML6CQ.js → chunk-WD6WBYC5.js} +1 -1
  30. package/dist/bundle/chunks/chunk-WJOT3DFL.js +2 -0
  31. package/dist/bundle/chunks/{chunk-7WAJCESX.js → chunk-WLHNEUGD.js} +1 -1
  32. package/dist/bundle/chunks/chunk-YI7SPFDN.js +5 -0
  33. package/dist/bundle/chunks/{chunk-URBJS24R.js → chunk-YRF4I43T.js} +35 -35
  34. package/dist/bundle/chunks/chunk-Z3UYNSVR.js +3 -0
  35. package/dist/bundle/chunks/cli-main-HEKFFB5S.js +3 -0
  36. package/dist/bundle/chunks/{client-KWTLTKVV.js → client-WTYEICLH.js} +3 -3
  37. package/dist/bundle/chunks/dispatch-NYAULNVW.js +9 -0
  38. package/dist/bundle/chunks/{help-fast-path-ZHZGXAUG.js → help-fast-path-3Z26DQSV.js} +1 -1
  39. package/dist/bundle/chunks/host-command-Z3WPHJOW.js +2 -0
  40. package/dist/bundle/chunks/host-lifecycle-ONQIMWMX.js +3 -0
  41. package/dist/bundle/chunks/host-lifecycle.js +9 -7
  42. package/dist/bundle/chunks/host-session-command-J65ENSV5.js +20 -0
  43. package/dist/bundle/chunks/{host-socket-request-ZDJGSSZO.js → host-socket-request-3CWE4265.js} +1 -1
  44. package/dist/bundle/chunks/interactive-mode-DEPQC6TX.js +2 -0
  45. package/dist/bundle/chunks/mcp-presenter-bootstrap-XUSCQVRC.js +2 -0
  46. package/dist/bundle/chunks/{multi-session-host-M7HVUMRH.js → multi-session-host-YI2WZOET.js} +12 -9
  47. package/dist/bundle/chunks/{package-manager-cli-FSR5P6YL.js → package-manager-cli-GHZPJMRF.js} +1 -1
  48. package/dist/bundle/chunks/{role-M2MTVACJ.js → role-JVSBNKGE.js} +1 -1
  49. package/dist/bundle/chunks/{rotation-stream-U7YQMC7H.js → rotation-stream-ZPVCFOVD.js} +1 -1
  50. package/dist/bundle/chunks/rpc-mode-ORHBGBBV.js +2 -0
  51. package/dist/bundle/chunks/{rpc-monitor-source-OL5KU6NO.js → rpc-monitor-source-FFEQS744.js} +1 -1
  52. package/dist/bundle/chunks/{runtime-session-CLNODAYE.js → runtime-session-V5OO7ZRM.js} +1 -1
  53. package/dist/bundle/chunks/{schedule-command-XBW4EOG5.js → schedule-command-YWG6D6ZF.js} +1 -1
  54. package/dist/bundle/chunks/session-control-endpoint-36HAQSQO.js +3 -0
  55. package/dist/bundle/chunks/{session-picker-KQUFWDQC.js → session-picker-25H5KNYP.js} +1 -1
  56. package/dist/bundle/chunks/session-worker.js +285 -271
  57. package/dist/bundle/chunks/{stdio-IEC635SY.js → stdio-YXDKKKQZ.js} +1 -1
  58. package/dist/bundle/chunks/{streamableHttp-KJI5KE4K.js → streamableHttp-DKIBUWVY.js} +1 -1
  59. package/dist/bundle/chunks/{types-LLNV23LW.js → types-BJUEQ4II.js} +1 -1
  60. package/dist/bundle/chunks/zod-compat-RJDXCU7S.js +2 -0
  61. package/dist/bundle/cli.js +2 -2
  62. package/dist/bundle/host-supervisor.js +9 -7
  63. package/dist/bundle/index.js +1 -1
  64. package/dist/bundle/rpc-entry.js +1 -1
  65. package/dist/bundle/runtime-manifest.json +1 -1
  66. package/dist/cli/args.d.ts +2 -0
  67. package/dist/cli/args.js +9 -0
  68. package/dist/cli/host-command-args.d.ts +1 -0
  69. package/dist/cli/host-command-args.js +7 -1
  70. package/dist/cli/host-command.js +3 -1
  71. package/dist/core/agent-session.d.ts +1 -0
  72. package/dist/core/agent-session.js +50 -16
  73. package/dist/core/engine-turn-limit.d.ts +2 -0
  74. package/dist/core/engine-turn-limit.js +7 -1
  75. package/dist/core/extensions/builtin/anthropic-subscription/auth-attempt.js +5 -0
  76. package/dist/core/extensions/builtin/goal/index.js +1 -0
  77. package/dist/core/extensions/builtin/goal/lifecycle-facts.d.ts +3 -0
  78. package/dist/core/extensions/builtin/goal/stop-pause.js +2 -1
  79. package/dist/core/extensions/builtin/loop/session-hold.js +2 -1
  80. package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -1
  81. package/dist/core/extensions/builtin/mcp/catalog.js +2 -0
  82. package/dist/core/extensions/builtin/mcp/connection.d.ts +1 -0
  83. package/dist/core/extensions/builtin/mcp/connection.js +46 -1
  84. package/dist/core/extensions/builtin/mcp/elicitation.d.ts +6 -1
  85. package/dist/core/extensions/builtin/mcp/elicitation.js +56 -2
  86. package/dist/core/extensions/builtin/mcp/expose/call.js +6 -2
  87. package/dist/core/extensions/builtin/mcp/expose/session.d.ts +4 -1
  88. package/dist/core/extensions/builtin/mcp/expose/session.js +7 -0
  89. package/dist/core/extensions/builtin/mcp/health.js +5 -0
  90. package/dist/core/extensions/builtin/mcp/host-registry.js +7 -1
  91. package/dist/core/extensions/builtin/mcp/index.js +2 -0
  92. package/dist/core/extensions/builtin/mcp/modern/client.d.ts +5 -0
  93. package/dist/core/extensions/builtin/mcp/modern/client.js +15 -0
  94. package/dist/core/extensions/builtin/mcp/modern/consent-queue.d.ts +2 -0
  95. package/dist/core/extensions/builtin/mcp/modern/consent-queue.js +59 -0
  96. package/dist/core/extensions/builtin/mcp/modern/diagnostics.d.ts +4 -0
  97. package/dist/core/extensions/builtin/mcp/modern/diagnostics.js +39 -0
  98. package/dist/core/extensions/builtin/mcp/modern/dispatch.js +21 -11
  99. package/dist/core/extensions/builtin/mcp/modern/elicitation.d.ts +14 -0
  100. package/dist/core/extensions/builtin/mcp/modern/elicitation.js +145 -0
  101. package/dist/core/extensions/builtin/mcp/modern/errors.d.ts +21 -0
  102. package/dist/core/extensions/builtin/mcp/modern/errors.js +23 -0
  103. package/dist/core/extensions/builtin/mcp/modern/form-schema.d.ts +20 -0
  104. package/dist/core/extensions/builtin/mcp/modern/form-schema.js +153 -0
  105. package/dist/core/extensions/builtin/mcp/modern/http-fetch.d.ts +3 -1
  106. package/dist/core/extensions/builtin/mcp/modern/http-fetch.js +8 -2
  107. package/dist/core/extensions/builtin/mcp/modern/inputs.d.ts +11 -0
  108. package/dist/core/extensions/builtin/mcp/modern/inputs.js +65 -0
  109. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +11 -0
  110. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +109 -0
  111. package/dist/core/extensions/builtin/mcp/modern/request.d.ts +20 -0
  112. package/dist/core/extensions/builtin/mcp/modern/request.js +292 -0
  113. package/dist/core/extensions/builtin/mcp/modern/stdio-probe.d.ts +1 -1
  114. package/dist/core/extensions/builtin/mcp/modern/stdio-probe.js +54 -6
  115. package/dist/core/extensions/builtin/mcp/modern/transport.js +80 -14
  116. package/dist/core/extensions/builtin/mcp/modern/wire.js +11 -7
  117. package/dist/core/extensions/builtin/mcp/prompts.d.ts +5 -1
  118. package/dist/core/extensions/builtin/mcp/prompts.js +6 -1
  119. package/dist/core/extensions/builtin/mcp/request-origin.d.ts +47 -0
  120. package/dist/core/extensions/builtin/mcp/request-origin.js +59 -0
  121. package/dist/core/extensions/builtin/mcp/resources.d.ts +4 -1
  122. package/dist/core/extensions/builtin/mcp/resources.js +19 -4
  123. package/dist/core/extensions/builtin/mcp/sdk.lazy.d.ts +3 -1
  124. package/dist/core/extensions/builtin/mcp/sdk.lazy.js +3 -1
  125. package/dist/core/extensions/builtin/mcp/service-register.d.ts +4 -1
  126. package/dist/core/extensions/builtin/mcp/service-register.js +3 -0
  127. package/dist/core/extensions/builtin/mcp/service-types.d.ts +2 -1
  128. package/dist/core/extensions/builtin/mcp/service.d.ts +1 -0
  129. package/dist/core/extensions/builtin/mcp/service.js +79 -4
  130. package/dist/core/extensions/builtin/mcp/shared-connection.js +36 -2
  131. package/dist/core/extensions/builtin/mcp/shared-lease.d.ts +1 -0
  132. package/dist/core/extensions/builtin/mcp/shared-lease.js +9 -0
  133. package/dist/core/extensions/builtin/mcp/transport-sdk.d.ts +3 -2
  134. package/dist/core/extensions/builtin/mcp/transport-sdk.js +56 -25
  135. package/dist/core/extensions/builtin/mcp/wrap.d.ts +7 -0
  136. package/dist/core/extensions/builtin/mcp/wrap.js +6 -0
  137. package/dist/core/extensions/runner.js +3 -2
  138. package/dist/core/extensions/types.d.ts +2 -0
  139. package/dist/core/lifecycle-ledger.d.ts +1 -0
  140. package/dist/core/lifecycle-ledger.js +7 -2
  141. package/dist/core/lifecycle-record.js +3 -0
  142. package/dist/core/lifecycle-runtime-facts.js +3 -0
  143. package/dist/core/lifecycle-session-binding.d.ts +11 -1
  144. package/dist/core/lifecycle-session-binding.js +35 -4
  145. package/dist/core/lifecycle-stop.d.ts +41 -0
  146. package/dist/core/lifecycle-stop.js +92 -0
  147. package/dist/core/mcp-form-types.d.ts +89 -0
  148. package/dist/core/mcp-form-types.js +23 -0
  149. package/dist/core/mcp-mrtr-presentations.d.ts +15 -0
  150. package/dist/core/mcp-mrtr-presentations.js +17 -0
  151. package/dist/core/run-resume-turn.d.ts +1 -0
  152. package/dist/core/run-resume-turn.js +4 -3
  153. package/dist/core/session-lifecycle-types.d.ts +15 -0
  154. package/dist/core/session-lifecycle.d.ts +17 -1
  155. package/dist/core/session-lifecycle.js +40 -2
  156. package/dist/core/stop-resume-directive.d.ts +1 -1
  157. package/dist/core/stop-resume-directive.js +10 -1
  158. package/dist/main.js +5 -1
  159. package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
  160. package/dist/modes/interactive/components/extension-selector.js +4 -0
  161. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  162. package/dist/modes/interactive/interactive-mode.js +35 -8
  163. package/dist/modes/interactive/mcp-local-presenter.d.ts +5 -0
  164. package/dist/modes/interactive/mcp-local-presenter.js +109 -0
  165. package/dist/modes/interactive/pause-footer.d.ts +5 -0
  166. package/dist/modes/interactive/pause-footer.js +2 -0
  167. package/dist/modes/rpc/connection-handler.d.ts +1 -0
  168. package/dist/modes/rpc/connection-handler.js +7 -1
  169. package/dist/modes/rpc/host-capabilities.d.ts +1 -0
  170. package/dist/modes/rpc/host-capabilities.js +2 -1
  171. package/dist/modes/rpc/host-ensure-start.js +13 -2
  172. package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
  173. package/dist/modes/rpc/host-ensure.js +1 -0
  174. package/dist/modes/rpc/host-handoff.d.ts +2 -0
  175. package/dist/modes/rpc/host-handover-wire.d.ts +2 -0
  176. package/dist/modes/rpc/host-handover-wire.js +1 -0
  177. package/dist/modes/rpc/host-lifecycle-launch.d.ts +1 -0
  178. package/dist/modes/rpc/host-lifecycle-launch.js +43 -2
  179. package/dist/modes/rpc/host-reservations.js +3 -2
  180. package/dist/modes/rpc/host-runner.d.ts +2 -0
  181. package/dist/modes/rpc/host-runner.js +8 -6
  182. package/dist/modes/rpc/host-stalled-evidence.js +2 -1
  183. package/dist/modes/rpc/host-successor.js +11 -1
  184. package/dist/modes/rpc/mcp-form-dialog.d.ts +45 -0
  185. package/dist/modes/rpc/mcp-form-dialog.js +159 -0
  186. package/dist/modes/rpc/mcp-form-worker.d.ts +14 -0
  187. package/dist/modes/rpc/mcp-form-worker.js +167 -0
  188. package/dist/modes/rpc/mcp-presenter-authority.d.ts +53 -0
  189. package/dist/modes/rpc/mcp-presenter-authority.js +309 -0
  190. package/dist/modes/rpc/mcp-presenter-bootstrap.d.ts +8 -0
  191. package/dist/modes/rpc/mcp-presenter-bootstrap.js +111 -0
  192. package/dist/modes/rpc/mcp-presenter-control.d.ts +24 -0
  193. package/dist/modes/rpc/mcp-presenter-control.js +330 -0
  194. package/dist/modes/rpc/multi-session-host.d.ts +3 -0
  195. package/dist/modes/rpc/multi-session-host.js +36 -1
  196. package/dist/modes/rpc/session-binding.js +6 -1
  197. package/dist/modes/rpc/session-event-writer.d.ts +14 -0
  198. package/dist/modes/rpc/session-event-writer.js +128 -1
  199. package/dist/modes/rpc/session-router-routing.js +1 -0
  200. package/dist/modes/rpc/session-worker-client.d.ts +3 -0
  201. package/dist/modes/rpc/session-worker-client.js +89 -0
  202. package/dist/modes/rpc/session-worker-protocol.d.ts +41 -0
  203. package/dist/modes/rpc/session-worker.js +9 -0
  204. package/dist/modes/rpc/tool-child/group-ownership.js +4 -2
  205. package/dist/utils/process-identity.d.ts +7 -0
  206. package/dist/utils/process-identity.js +32 -5
  207. package/docs/extensions.md +5 -0
  208. package/docs/mcp.md +20 -3
  209. package/docs/rpc.md +111 -0
  210. package/package.json +6 -6
  211. package/dist/bundle/chunks/chunk-3VOG6WFU.js +0 -27
  212. package/dist/bundle/chunks/chunk-7ASLQ243.js +0 -2
  213. package/dist/bundle/chunks/chunk-CDRFFHLL.js +0 -4
  214. package/dist/bundle/chunks/chunk-HPCS4HM4.js +0 -27
  215. package/dist/bundle/chunks/chunk-II22E3CV.js +0 -3
  216. package/dist/bundle/chunks/chunk-J5P2VL3Y.js +0 -5
  217. package/dist/bundle/chunks/chunk-SCVRKIFK.js +0 -2
  218. package/dist/bundle/chunks/chunk-XUFUKDKJ.js +0 -5
  219. package/dist/bundle/chunks/cli-main-TABBP55A.js +0 -3
  220. package/dist/bundle/chunks/dispatch-DEP4PEDZ.js +0 -10
  221. package/dist/bundle/chunks/host-command-OQR4RDTM.js +0 -2
  222. package/dist/bundle/chunks/host-lifecycle-U36U7JTE.js +0 -3
  223. package/dist/bundle/chunks/host-session-command-IGB27ZPJ.js +0 -20
  224. package/dist/bundle/chunks/interactive-mode-DGQNSXUA.js +0 -2
  225. package/dist/bundle/chunks/rpc-mode-SZAJNKKL.js +0 -2
  226. package/dist/bundle/chunks/session-control-endpoint-WEF3XTJR.js +0 -3
@@ -0,0 +1,292 @@
1
+ import { beginLegacyMcpOperation, physicalMcpClient, takeMcpRequestOrigin, } from "../request-origin.js";
2
+ import { AuthError } from "../errors.js";
3
+ import { loadMcpSdkTypes, loadMcpSdkZodCompat } from "../sdk.lazy.js";
4
+ import { mcpElapsedClock } from "../wrap.js";
5
+ import { collectMrtrForm } from "./elicitation.js";
6
+ import { isRecord, validateMrtrForm } from "./form-schema.js";
7
+ import { McpMrtrError } from "./errors.js";
8
+ import { queuedForm } from "./consent-queue.js";
9
+ import { parseInputs } from "./inputs.js";
10
+ export const MAX_MCP_MRTR_ROUNDS = 8;
11
+ const capableMethods = new Set(["tools/call", "resources/read", "prompts/get"]);
12
+ const capabilitiesKey = "io.modelcontextprotocol/clientCapabilities";
13
+ /** Local dispatch guard survives SDK object spreads but is never JSON serialized. */
14
+ export const modernDispatchGuard = Symbol("mcp-modern-dispatch");
15
+ const states = new WeakMap();
16
+ export const isModernMcpClient = (client) => states.get(physicalMcpClient(client))?.version !== undefined;
17
+ export const legacyMcpFormsAdvertised = (client) => states.get(physicalMcpClient(client))?.legacyFormEligible;
18
+ const own = (value, key) => Object.hasOwn(value, key);
19
+ const byteSize = (value) => Buffer.byteLength(JSON.stringify(value), "utf8");
20
+ export function getModernDispatchGuard(params) {
21
+ return isRecord(params)
22
+ ? params[modernDispatchGuard]
23
+ : undefined;
24
+ }
25
+ /** One instance-local adapter. Captured SDK requests retain ID allocation and final output-schema validation. */
26
+ export function installModernRequestAdapter(client, protocol, clock = mcpElapsedClock, readCredential = () => undefined) {
27
+ const sdkRequest = client.request.bind(client);
28
+ states.set(client, protocol);
29
+ const requestWithMrtr = async (request, schema, options) => {
30
+ if (request.method === "initialize")
31
+ return sdkRequest(request, schema, options);
32
+ const { origin, sdkOptions } = takeMcpRequestOrigin(options, client);
33
+ if (protocol.version === undefined) {
34
+ const end = request.method === "tools/call" && origin
35
+ ? beginLegacyMcpOperation(client, origin, sdkOptions.signal)
36
+ : undefined;
37
+ try {
38
+ return await sdkRequest(request, schema, sdkOptions);
39
+ }
40
+ finally {
41
+ end?.();
42
+ }
43
+ }
44
+ if (own(request.params ?? {}, "requestState") || own(request.params ?? {}, "inputResponses") || options?.task)
45
+ throw new McpMrtrError("MCP_MRTR_INVALID_INPUT");
46
+ const params = structuredClone(request.params ?? {});
47
+ const start = clock.now();
48
+ const timeout = sdkOptions.timeout ?? 60_000;
49
+ const duration = Math.min(sdkOptions.maxTotalTimeout ?? Infinity, 9 * timeout + 300_000);
50
+ const deadline = start + duration;
51
+ const operation = new AbortController();
52
+ const credential = readCredential();
53
+ let authority;
54
+ let authorityCaptured = false;
55
+ const controller = origin?.connection;
56
+ const generation = controller?.generation;
57
+ let presentationLost = origin?.presentation === undefined || origin.presentation.signal.aborted;
58
+ let consentSpent = 0;
59
+ let continuation = {};
60
+ let failure;
61
+ const stop = (code) => {
62
+ failure ??= new McpMrtrError(code);
63
+ operation.abort(failure);
64
+ };
65
+ const onCallerAbort = () => stop("MCP_MRTR_CANCELLED");
66
+ const onOwnerAbort = () => stop("MCP_MRTR_CONTEXT_CHANGED");
67
+ const onPresentationLost = () => {
68
+ presentationLost = true;
69
+ };
70
+ const check = () => {
71
+ if (clock.now() >= deadline)
72
+ stop("MCP_MRTR_TIMEOUT");
73
+ if (origin &&
74
+ (!origin.isCurrent() ||
75
+ origin.signal.aborted ||
76
+ controller?.generation !== generation ||
77
+ controller?.state !== "connected"))
78
+ stop("MCP_MRTR_CONTEXT_CHANGED");
79
+ if (failure)
80
+ throw failure;
81
+ };
82
+ const resolveAuthority = async () => {
83
+ check();
84
+ if (origin) {
85
+ await origin.privateForm?.synchronize?.();
86
+ const snapshot = JSON.stringify(await interrupted(origin.resolveAuthority(operation.signal), operation.signal));
87
+ if (!authorityCaptured) {
88
+ authority = snapshot;
89
+ authorityCaptured = true;
90
+ }
91
+ else if (authority !== snapshot)
92
+ stop("MCP_MRTR_CONTEXT_CHANGED");
93
+ }
94
+ check();
95
+ };
96
+ const off = controller?.onStateChange(() => {
97
+ try {
98
+ check();
99
+ }
100
+ catch {
101
+ /* Abort reaches the active attempt and consent. */
102
+ }
103
+ });
104
+ sdkOptions.signal?.addEventListener("abort", onCallerAbort, { once: true });
105
+ origin?.signal.addEventListener("abort", onOwnerAbort, { once: true });
106
+ origin?.presentation?.signal.addEventListener("abort", onPresentationLost, { once: true });
107
+ const timer = clock.schedule(() => stop("MCP_MRTR_TIMEOUT"), Math.max(0, duration));
108
+ if (sdkOptions.signal?.aborted)
109
+ onCallerAbort();
110
+ if (origin?.signal.aborted)
111
+ onOwnerAbort();
112
+ try {
113
+ const { safeParse } = await interrupted(loadMcpSdkZodCompat(), operation.signal);
114
+ // Use the SDK's own Zod identity, including when npm installs another copy for this package.
115
+ const { ResultSchema } = await interrupted(loadMcpSdkTypes(), operation.signal);
116
+ for (let round = 0;; round++) {
117
+ await resolveAuthority();
118
+ const remaining = deadline - clock.now();
119
+ const attempt = new AbortController();
120
+ const forward = () => attempt.abort("MCP request canceled");
121
+ operation.signal.addEventListener("abort", forward, { once: true });
122
+ const metadata = isRecord(params._meta) ? params._meta : {};
123
+ const capabilities = origin?.privateFormsEnforced
124
+ ? origin.privateForm?.ready()
125
+ ? { elicitation: {} }
126
+ : {}
127
+ : !presentationLost && origin?.presentation
128
+ ? { elicitation: {} }
129
+ : {};
130
+ const outgoing = {
131
+ ...params,
132
+ ...continuation,
133
+ _meta: { ...metadata, [capabilitiesKey]: capabilities },
134
+ [modernDispatchGuard]: (snapshot) => {
135
+ check();
136
+ if ((snapshot ?? readCredential()) !== credential)
137
+ throw new McpMrtrError("MCP_MRTR_CONTEXT_CHANGED");
138
+ },
139
+ };
140
+ let result;
141
+ try {
142
+ check();
143
+ result = await sdkRequest({ method: request.method, params: outgoing }, ResultSchema, {
144
+ ...sdkOptions,
145
+ signal: attempt.signal,
146
+ timeout: Math.min(timeout, remaining),
147
+ maxTotalTimeout: remaining,
148
+ onprogress: sdkOptions.onprogress
149
+ ? (progress) => {
150
+ check();
151
+ if (!Number.isFinite(progress.progress) ||
152
+ (progress.total !== undefined && !Number.isFinite(progress.total)))
153
+ return;
154
+ sdkOptions.onprogress?.({
155
+ progress: progress.progress,
156
+ ...(progress.total === undefined ? {} : { total: progress.total }),
157
+ });
158
+ }
159
+ : undefined,
160
+ });
161
+ }
162
+ finally {
163
+ operation.signal.removeEventListener("abort", forward);
164
+ }
165
+ check();
166
+ const type = own(result, "resultType") ? result.resultType : "complete";
167
+ if (type === "complete") {
168
+ if (["requestState", "inputRequests", "inputResponses"].some((key) => own(result, key)))
169
+ throw new McpMrtrError("MCP_MRTR_INVALID_RESULT");
170
+ const parsed = safeParse(schema, result);
171
+ if (!parsed.success)
172
+ throw new McpMrtrError("MCP_MRTR_INVALID_RESULT");
173
+ return parsed.data;
174
+ }
175
+ if (type !== "input_required" || !capableMethods.has(request.method))
176
+ throw new McpMrtrError("MCP_MRTR_INVALID_RESULT");
177
+ if (round >= MAX_MCP_MRTR_ROUNDS)
178
+ throw new McpMrtrError("MCP_MRTR_ROUND_LIMIT");
179
+ const inputs = parseInputs(result);
180
+ continuation = own(result, "requestState") ? { requestState: result.requestState } : {};
181
+ if (inputs) {
182
+ const responses = Object.create(null);
183
+ let cancelled = false;
184
+ for (const [key, input] of inputs) {
185
+ await resolveAuthority();
186
+ if (input.params.mode === "url") {
187
+ throw new McpMrtrError("MCP_MRTR_UNSUPPORTED_INPUT");
188
+ }
189
+ else if (cancelled) {
190
+ // A human stopped this batch, not necessarily the caller's AbortSignal.
191
+ // Keep the cancellation code; never fabricate results for unshown inputs.
192
+ throw new McpMrtrError("MCP_MRTR_CANCELLED");
193
+ }
194
+ else if (consentSpent >= 300_000) {
195
+ throw new McpMrtrError("MCP_MRTR_PROCESSING_UNAVAILABLE");
196
+ }
197
+ else if (origin?.privateFormsEnforced) {
198
+ const presenter = origin.privateForm;
199
+ const schema = input.params.requestedSchema;
200
+ if (!presenter?.ready() || !validateMrtrForm(schema))
201
+ throw new McpMrtrError("MCP_MRTR_PROCESSING_UNAVAILABLE");
202
+ const began = clock.now();
203
+ const outcome = await presenter.present({
204
+ operationId: String(start),
205
+ inputId: key,
206
+ round,
207
+ server: { label: origin.serverLabel, source: origin.serverSource ?? "user" },
208
+ message: input.params.message,
209
+ schema,
210
+ operationSignal: operation.signal,
211
+ presentationSignal: presenter.signal,
212
+ deadlineMs: Math.max(0, 300_000 - consentSpent),
213
+ });
214
+ consentSpent += clock.now() - began;
215
+ check();
216
+ if (outcome.kind !== "result")
217
+ throw new McpMrtrError(outcome.kind === "operation_aborted"
218
+ ? "MCP_MRTR_CANCELLED"
219
+ : "MCP_MRTR_PROCESSING_UNAVAILABLE");
220
+ responses[key] = outcome.result;
221
+ cancelled = outcome.result.action !== "accept";
222
+ }
223
+ else if (presentationLost || !origin?.presentation) {
224
+ throw new McpMrtrError("MCP_MRTR_PROCESSING_UNAVAILABLE");
225
+ }
226
+ else {
227
+ const consent = new AbortController();
228
+ const began = clock.now();
229
+ const consentTimer = clock.schedule(() => consent.abort(), Math.max(0, 300_000 - consentSpent));
230
+ const signal = AbortSignal.any([operation.signal, origin.presentation.signal, consent.signal]);
231
+ let presented = false;
232
+ try {
233
+ const answer = await interrupted(queuedForm(origin.binding, signal, () => {
234
+ presented = true;
235
+ return collectMrtrForm(origin.presentation, origin.serverLabel, { message: input.params.message, requestedSchema: input.params.requestedSchema }, signal);
236
+ }), operation.signal);
237
+ check();
238
+ if (!presented)
239
+ throw new McpMrtrError("MCP_MRTR_PROCESSING_UNAVAILABLE");
240
+ responses[key] = answer;
241
+ cancelled = answer.action !== "accept";
242
+ }
243
+ finally {
244
+ consentSpent += clock.now() - began;
245
+ clock.cancel(consentTimer);
246
+ consent.abort();
247
+ }
248
+ }
249
+ }
250
+ if (byteSize(responses) > 512 * 1024)
251
+ throw new McpMrtrError("MCP_MRTR_INPUT_LIMIT");
252
+ continuation.inputResponses = responses;
253
+ }
254
+ }
255
+ }
256
+ catch (error) {
257
+ check();
258
+ if (error instanceof McpMrtrError)
259
+ throw error;
260
+ if (error instanceof AuthError)
261
+ throw error;
262
+ const remoteCode = isRecord(error) && typeof error.code === "number" ? error.code : undefined;
263
+ throw new McpMrtrError("MCP_MRTR_TRANSPORT_FAILED", remoteCode);
264
+ }
265
+ finally {
266
+ continuation = {};
267
+ clock.cancel(timer);
268
+ off?.();
269
+ sdkOptions.signal?.removeEventListener("abort", onCallerAbort);
270
+ origin?.signal.removeEventListener("abort", onOwnerAbort);
271
+ origin?.presentation?.signal.removeEventListener("abort", onPresentationLost);
272
+ }
273
+ };
274
+ // Preserve the SDK method itself until negotiation positively selects modern.
275
+ protocol.activateRequests = () => {
276
+ client.request = requestWithMrtr;
277
+ };
278
+ // Default legacy clients retain the SDK method and its original admission behavior.
279
+ // Enforced legacy calls need private operation ownership before the first tools/call.
280
+ if (protocol.privateForms || protocol.version !== undefined)
281
+ protocol.activateRequests();
282
+ }
283
+ function interrupted(work, signal) {
284
+ return new Promise((resolve, reject) => {
285
+ const onabort = () => reject(signal.reason);
286
+ signal.addEventListener("abort", onabort, { once: true });
287
+ if (signal.aborted)
288
+ onabort();
289
+ void work.then(resolve, reject).finally(() => signal.removeEventListener("abort", onabort));
290
+ });
291
+ }
292
+ //# sourceMappingURL=request.js.map
@@ -10,5 +10,5 @@ export declare const STDIO_PROBE_MS = 2000;
10
10
  * keeps the same process for the legacy handshake; a discovery reply that arrives late still upgrades it to modern.
11
11
  * Only a child that exited or wrote non-JSON to stdout is replaced by a fresh process for the legacy handshake.
12
12
  */
13
- export declare function probeStdioTransport(transport: Transport, options: ProbeOptions, logger: McpLogger, timeoutMs: number, serverName: string, onModern?: (version: string) => void, probeMs?: number): void;
13
+ export declare function probeStdioTransport(transport: Transport, options: ProbeOptions, logger: McpLogger, timeoutMs: number, serverName: string, onModern?: (version: string) => void, probeMs?: number, onLegacy?: () => void): void;
14
14
  //# sourceMappingURL=stdio-probe.d.ts.map
@@ -3,6 +3,9 @@ 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 { sanitizeModernMessage } from "./diagnostics.js";
7
+ import { McpMrtrError } from "./errors.js";
8
+ import { getModernDispatchGuard } from "./request.js";
6
9
  import { MODERN_PROTOCOL_VERSION, modernizeRequest } from "./wire.js";
7
10
  /** How long a stdio child gets to answer discovery (at most a quarter of the connect timeout) before it is treated as legacy. */
8
11
  export const STDIO_PROBE_MS = 2_000;
@@ -13,7 +16,7 @@ export const STDIO_PROBE_MS = 2_000;
13
16
  * keeps the same process for the legacy handshake; a discovery reply that arrives late still upgrades it to modern.
14
17
  * Only a child that exited or wrote non-JSON to stdout is replaced by a fresh process for the legacy handshake.
15
18
  */
16
- export function probeStdioTransport(transport, options, logger, timeoutMs, serverName, onModern, probeMs = STDIO_PROBE_MS) {
19
+ export function probeStdioTransport(transport, options, logger, timeoutMs, serverName, onModern, probeMs = STDIO_PROBE_MS, onLegacy) {
17
20
  const start = transport.start.bind(transport);
18
21
  const close = transport.close.bind(transport);
19
22
  const send = transport.send.bind(transport);
@@ -31,6 +34,8 @@ export function probeStdioTransport(transport, options, logger, timeoutMs, serve
31
34
  await restart();
32
35
  else if (outcome.silent)
33
36
  awaitLateDiscovery();
37
+ if (!outcome.silent)
38
+ onLegacy?.();
34
39
  return;
35
40
  }
36
41
  if (outcome.kind === "indeterminate" || !outcome.discovery) {
@@ -161,8 +166,10 @@ export function probeStdioTransport(transport, options, logger, timeoutMs, serve
161
166
  }, extra);
162
167
  return;
163
168
  }
164
- if (isInitializeReply)
169
+ if (isInitializeReply) {
165
170
  settle();
171
+ onLegacy?.();
172
+ }
166
173
  deliver?.(message, extra);
167
174
  };
168
175
  transport.send = async (message, sendOptions) => {
@@ -178,8 +185,34 @@ export function probeStdioTransport(transport, options, logger, timeoutMs, serve
178
185
  function speakModern(discovery, version) {
179
186
  const initializeResult = modernInitializeResult(discovery, version);
180
187
  const deliver = transport.onmessage;
188
+ const live = new Set();
181
189
  let initialized = false;
182
- transport.onmessage = (message, extra) => deliver?.(normalizeModernResult(message), extra);
190
+ transport.onerror = () => {
191
+ // A parser error may quote a wire body; never forward it.
192
+ const failure = new McpMrtrError("MCP_MRTR_TRANSPORT_FAILED");
193
+ logger.warn(failure.message);
194
+ void transport.close();
195
+ };
196
+ transport.onmessage = (message, extra) => {
197
+ if ("id" in message && "method" in message) {
198
+ void send({
199
+ jsonrpc: "2.0",
200
+ id: message.id,
201
+ error: { code: -32601, message: "Unsupported MCP method." },
202
+ }).catch(() => { });
203
+ return;
204
+ }
205
+ if ("id" in message && message.id !== undefined && !live.delete(message.id))
206
+ return;
207
+ if ("method" in message && message.method === "notifications/progress") {
208
+ const token = message.params?.progressToken;
209
+ if ((typeof token !== "string" && typeof token !== "number") || !live.has(token))
210
+ return;
211
+ }
212
+ const safe = sanitizeModernMessage(message);
213
+ if (safe)
214
+ deliver?.(normalizeModernResult(safe), extra);
215
+ };
183
216
  transport.send = async (message, sendOptions) => {
184
217
  if (!("method" in message))
185
218
  return send(message, sendOptions);
@@ -188,14 +221,29 @@ export function probeStdioTransport(transport, options, logger, timeoutMs, serve
188
221
  throw new ModernTransportError("Modern MCP stdio has already initialized");
189
222
  }
190
223
  initialized = true;
191
- transport.onmessage?.({ jsonrpc: "2.0", id: message.id, result: initializeResult });
224
+ deliver?.({ jsonrpc: "2.0", id: message.id, result: initializeResult });
192
225
  return;
193
226
  }
194
227
  if (message.method === "notifications/initialized" && !("id" in message))
195
228
  return;
229
+ if (message.method === "notifications/cancelled" && !("id" in message)) {
230
+ const id = message.params?.requestId;
231
+ if ((typeof id !== "string" && typeof id !== "number") || !live.delete(id))
232
+ return;
233
+ return send({ jsonrpc: "2.0", method: message.method, params: { requestId: id, reason: "MCP request canceled" } }, sendOptions);
234
+ }
235
+ getModernDispatchGuard(message.params)?.();
196
236
  const request = modernizeRequest("id" in message ? message.id : 0, message.method, options.clientInfo, options.clientCapabilities, version, message.params);
197
- if ("id" in message)
198
- return send(request, sendOptions);
237
+ if ("id" in message) {
238
+ live.add(message.id);
239
+ try {
240
+ return await send(request, sendOptions);
241
+ }
242
+ catch {
243
+ live.delete(message.id);
244
+ throw new McpMrtrError("MCP_MRTR_TRANSPORT_FAILED");
245
+ }
246
+ }
199
247
  const { id: _id, ...notification } = request;
200
248
  return send(notification, sendOptions);
201
249
  };
@@ -1,5 +1,10 @@
1
1
  import { JSONRPCMessageSchema } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { deriveModernHeaders, MODERN_PROTOCOL_VERSION, modernizeRequest } from "./wire.js";
3
+ import { AuthError } from "../errors.js";
4
+ import { sanitizeModernMessage } from "./diagnostics.js";
5
+ import { McpMrtrError } from "./errors.js";
6
+ import { getModernDispatchGuard, modernDispatchGuard } from "./request.js";
7
+ import { clearMcpParameterHeaders, ModernParameterHeaders } from "./parameter-headers.js";
3
8
  export class ModernTransportError extends Error {
4
9
  constructor(message) {
5
10
  super(message);
@@ -10,6 +15,9 @@ export class ModernTransportError extends Error {
10
15
  export class ModernHttpTransport {
11
16
  #options;
12
17
  #abort = new AbortController();
18
+ #attempts = new Map();
19
+ #toolLists = new Map();
20
+ #parameterHeaders = new ModernParameterHeaders();
13
21
  #started = false;
14
22
  #closed = false;
15
23
  constructor(options) {
@@ -30,9 +38,24 @@ export class ModernHttpTransport {
30
38
  async send(message) {
31
39
  if (!this.#started || this.#closed)
32
40
  throw new ModernTransportError("Modern MCP transport is not open");
41
+ if ("method" in message && message.method === "notifications/cancelled") {
42
+ const id = message.params?.requestId;
43
+ if (typeof id === "string" || typeof id === "number")
44
+ this.#attempts.get(id)?.abort();
45
+ return;
46
+ }
47
+ const id = "method" in message && "id" in message ? message.id : undefined;
48
+ if (id !== undefined && "method" in message && message.method === "tools/list") {
49
+ this.#toolLists.set(id, message.params?.cursor === undefined);
50
+ }
51
+ const controller = new AbortController();
52
+ if (id !== undefined)
53
+ this.#attempts.set(id, controller);
54
+ const signal = AbortSignal.any([this.#abort.signal, controller.signal]);
33
55
  try {
34
56
  const version = this.#options.protocolVersion ?? MODERN_PROTOCOL_VERSION;
35
57
  const headers = new Headers(this.#options.headers);
58
+ clearMcpParameterHeaders(headers);
36
59
  // Auth headers pass through, but protocol routing is exclusively derived from the body.
37
60
  for (const name of ["Mcp-Session-Id", "Mcp-Name", "Mcp-Method", "MCP-Protocol-Version"])
38
61
  headers.delete(name);
@@ -43,6 +66,9 @@ export class ModernHttpTransport {
43
66
  const request = modernizeRequest("id" in message ? message.id : 0, message.method, this.#options.clientInfo, this.#options.clientCapabilities, version, message.params);
44
67
  for (const [name, value] of Object.entries(deriveModernHeaders(request)))
45
68
  headers.set(name, value);
69
+ if (request.method === "tools/call") {
70
+ this.#parameterHeaders.append(headers, request.params.name, request.params.arguments);
71
+ }
46
72
  if ("id" in message) {
47
73
  body = request;
48
74
  }
@@ -55,13 +81,24 @@ export class ModernHttpTransport {
55
81
  // JSON-RPC replies have no method or params; do not turn them into requests.
56
82
  headers.set("MCP-Protocol-Version", version);
57
83
  }
58
- const response = await this.#options.fetch(this.#options.url, {
84
+ const guard = "method" in message ? getModernDispatchGuard(message.params) : undefined;
85
+ guard?.();
86
+ const init = {
59
87
  method: "POST",
60
88
  headers,
61
89
  body: JSON.stringify(body),
62
- signal: this.#abort.signal,
63
- });
64
- this.#abort.signal.throwIfAborted();
90
+ signal,
91
+ [modernDispatchGuard]: guard,
92
+ };
93
+ const response = await this.#options.fetch(this.#options.url, init);
94
+ signal.throwIfAborted();
95
+ if (response.status === 401 || response.status === 403) {
96
+ await response.body?.cancel();
97
+ throw new AuthError("MCP server needs authentication; use /mcp auth-start.", {
98
+ phase: "auth",
99
+ retriable: false,
100
+ });
101
+ }
65
102
  if (response.status === 202 || response.status === 204)
66
103
  return;
67
104
  const mediaType = response.headers.get("Content-Type")?.split(";")[0].trim().toLowerCase();
@@ -71,35 +108,64 @@ export class ModernHttpTransport {
71
108
  if (!response.ok && !("error" in incoming)) {
72
109
  throw new ModernTransportError(`Modern MCP POST failed with HTTP ${response.status}`);
73
110
  }
74
- this.#abort.signal.throwIfAborted();
75
- this.onmessage?.(incoming);
111
+ signal.throwIfAborted();
112
+ this.#deliver(incoming, id);
76
113
  }
77
114
  else if (response.ok && mediaType === "text/event-stream" && response.body) {
78
- await this.#consumeSse(response.body, "method" in message && "id" in message ? message.id : undefined);
115
+ await this.#consumeSse(response.body, id, signal);
79
116
  }
80
117
  else {
81
118
  throw new ModernTransportError(`Modern MCP POST failed with HTTP ${response.status} (${mediaType ?? "no content type"})`);
82
119
  }
83
120
  }
84
121
  catch (error) {
85
- const failure = error instanceof Error ? error : new ModernTransportError(String(error));
122
+ const failure = error instanceof McpMrtrError || error instanceof AuthError
123
+ ? error
124
+ : new McpMrtrError("MCP_MRTR_TRANSPORT_FAILED");
86
125
  this.onerror?.(failure);
87
126
  throw failure;
88
127
  }
128
+ finally {
129
+ if (id !== undefined)
130
+ this.#attempts.delete(id);
131
+ if (id !== undefined)
132
+ this.#toolLists.delete(id);
133
+ }
134
+ }
135
+ #deliver(incoming, expected) {
136
+ if ("id" in incoming && (expected === undefined || incoming.id !== expected))
137
+ throw new McpMrtrError("MCP_MRTR_INVALID_RESULT");
138
+ if ("id" in incoming && "result" in incoming && this.#toolLists.has(incoming.id)) {
139
+ const result = incoming.result;
140
+ if (Array.isArray(result.tools)) {
141
+ incoming = {
142
+ ...incoming,
143
+ result: {
144
+ ...result,
145
+ tools: this.#parameterHeaders.filterTools(result.tools, this.#toolLists.get(incoming.id) === true),
146
+ },
147
+ };
148
+ }
149
+ }
150
+ const safe = sanitizeModernMessage(incoming);
151
+ if (safe)
152
+ this.onmessage?.(safe);
89
153
  }
90
- async #consumeSse(stream, requestId) {
154
+ async #consumeSse(stream, requestId, signal) {
91
155
  const reader = stream.getReader();
92
156
  const decoder = new TextDecoder();
93
157
  const aborted = Promise.withResolvers();
94
- const onabort = () => aborted.reject(this.#abort.signal.reason);
95
- this.#abort.signal.addEventListener("abort", onabort, { once: true });
158
+ const onabort = () => aborted.reject(signal.reason);
159
+ signal.addEventListener("abort", onabort, { once: true });
160
+ if (signal.aborted)
161
+ onabort();
96
162
  let buffer = "";
97
163
  let data = [];
98
164
  let event = "";
99
165
  try {
100
166
  while (true) {
101
167
  const { value, done } = await Promise.race([reader.read(), aborted.promise]);
102
- this.#abort.signal.throwIfAborted();
168
+ signal.throwIfAborted();
103
169
  buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
104
170
  while (true) {
105
171
  const match = /[\r\n]/.exec(buffer);
@@ -114,7 +180,7 @@ export class ModernHttpTransport {
114
180
  if (line === "") {
115
181
  if (data.length > 0 && (event === "" || event === "message")) {
116
182
  const incoming = JSONRPCMessageSchema.parse(JSON.parse(data.join("\n")));
117
- this.onmessage?.(incoming);
183
+ this.#deliver(incoming, requestId);
118
184
  if ("id" in incoming && !("method" in incoming) && incoming.id === requestId)
119
185
  return;
120
186
  }
@@ -139,7 +205,7 @@ export class ModernHttpTransport {
139
205
  }
140
206
  }
141
207
  finally {
142
- this.#abort.signal.removeEventListener("abort", onabort);
208
+ signal.removeEventListener("abort", onabort);
143
209
  await reader.cancel();
144
210
  reader.releaseLock();
145
211
  }
@@ -1,4 +1,4 @@
1
- import { Buffer } from "node:buffer";
1
+ import { encodeMcpHeaderValue } from "./parameter-headers.js";
2
2
  export const MODERN_PROTOCOL_VERSION = "2026-07-28";
3
3
  export const MODERN_ERROR_CODES = {
4
4
  HeaderMismatch: -32020,
@@ -24,9 +24,17 @@ export function buildModernRequest(id, method, clientInfo, clientCapabilities, p
24
24
  /** Rewrite an outgoing SDK request or notification into the modern wire shape at the negotiated version. */
25
25
  export function modernizeRequest(id, method, clientInfo, clientCapabilities, version, params) {
26
26
  const built = buildModernRequest(id, method, clientInfo, clientCapabilities, params);
27
+ const capabilities = params?._meta?.["io.modelcontextprotocol/clientCapabilities"];
27
28
  return {
28
29
  ...built,
29
- params: { ...built.params, _meta: { ...built.params._meta, "io.modelcontextprotocol/protocolVersion": version } },
30
+ params: {
31
+ ...built.params,
32
+ _meta: {
33
+ ...built.params._meta,
34
+ "io.modelcontextprotocol/clientCapabilities": isRecord(capabilities) ? capabilities : clientCapabilities,
35
+ "io.modelcontextprotocol/protocolVersion": version,
36
+ },
37
+ },
30
38
  };
31
39
  }
32
40
  /** Derive protocol headers from the same request that will be serialized. */
@@ -41,11 +49,7 @@ export function deriveModernHeaders(request) {
41
49
  ? request.params.name
42
50
  : undefined;
43
51
  if (typeof name === "string") {
44
- // Encode the sentinel itself too, so a literal name cannot be mistaken for encoded text.
45
- headers["Mcp-Name"] =
46
- /^[\x20-\x7E]*$/.test(name) && !name.startsWith("base64:")
47
- ? name
48
- : `base64:${Buffer.from(name, "utf8").toString("base64url")}`;
52
+ headers["Mcp-Name"] = encodeMcpHeaderValue(name);
49
53
  }
50
54
  return headers;
51
55
  }
@@ -1,8 +1,12 @@
1
- import type { ExtensionAPI } from "../../types.ts";
1
+ import type { ExtensionAPI, ExtensionUIContext } from "../../types.ts";
2
2
  import type { McpCachedServerCatalog } from "./catalog-cache.ts";
3
3
  import type { ServerConnection } from "./connection.ts";
4
4
  import type { McpService } from "./service.ts";
5
+ import { type McpRequestOrigin } from "./request-origin.ts";
5
6
  export interface McpPromptServer {
7
+ readonly ensureFresh?: () => Promise<void>;
8
+ readonly requestOrigin?: () => McpRequestOrigin | undefined;
9
+ readonly bindPresentation?: (ui: ExtensionUIContext) => void;
6
10
  readonly server: string;
7
11
  readonly connection: ServerConnection;
8
12
  readonly requestTimeoutMs?: number;
@@ -10,6 +10,8 @@
10
10
  // last-wins, and prompts removed server-side keep a stale command until the
11
11
  // next session — invoking one surfaces the server error as a notice.
12
12
  import { createMcpLogger } from "./log.js";
13
+ import { mcpRequestOptions } from "./request-origin.js";
14
+ import { ensureMcpToolCallConnection } from "./health.js";
13
15
  const registeredCommandNames = new Set();
14
16
  /** Test seam: forget which commands were registered (module-level last-wins). */
15
17
  export function resetMcpPromptCommandsForTests() {
@@ -34,10 +36,13 @@ export function registerMcpPromptCommands(serviceOrPi, piOrServers, serversArg)
34
36
  description: prompt.description ?? `MCP prompt ${prompt.name} from ${server.server}`,
35
37
  handler: async (_args, ctx) => {
36
38
  try {
39
+ server.bindPresentation?.(ctx.ui);
37
40
  const collected = await collectPromptArguments(prompt.arguments ?? [], ctx);
38
41
  if (collected === undefined)
39
42
  return;
40
- const result = await server.connection.client.getPrompt({ arguments: collected, name: prompt.name }, { timeout: server.requestTimeoutMs });
43
+ if (server.requestOrigin)
44
+ await ensureMcpToolCallConnection(server.connection, server.ensureFresh);
45
+ const result = await server.connection.client.getPrompt({ arguments: collected, name: prompt.name }, mcpRequestOptions(server.requestOrigin?.(), { timeout: server.requestTimeoutMs }));
41
46
  ctx.ui.setEditorText(flattenPromptMessages(result.messages));
42
47
  }
43
48
  catch (error) {