@vibesdotdev/mcp 0.0.0-stage → 1.0.0-rc.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 (602) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +96 -2
  3. package/dist/adapters/auth-middleware.d.ts +68 -0
  4. package/dist/adapters/auth-middleware.d.ts.map +1 -0
  5. package/dist/adapters/auth-middleware.js +161 -0
  6. package/dist/adapters/auth-middleware.js.map +1 -0
  7. package/dist/adapters/compatibility.d.ts +20 -0
  8. package/dist/adapters/compatibility.d.ts.map +1 -0
  9. package/dist/adapters/compatibility.js +57 -0
  10. package/dist/adapters/compatibility.js.map +1 -0
  11. package/dist/adapters/host-context.consumer.d.ts +12 -0
  12. package/dist/adapters/host-context.consumer.d.ts.map +1 -0
  13. package/dist/adapters/host-context.consumer.js +10 -0
  14. package/dist/adapters/host-context.consumer.js.map +1 -0
  15. package/dist/adapters/host-context.d.ts +32 -0
  16. package/dist/adapters/host-context.d.ts.map +1 -0
  17. package/dist/adapters/host-context.js +17 -0
  18. package/dist/adapters/host-context.js.map +1 -0
  19. package/dist/adapters/index.d.ts +12 -0
  20. package/dist/adapters/index.d.ts.map +1 -0
  21. package/dist/adapters/index.js +13 -0
  22. package/dist/adapters/index.js.map +1 -0
  23. package/dist/adapters/mcp/ambient-envelope.d.ts +26 -0
  24. package/dist/adapters/mcp/ambient-envelope.d.ts.map +1 -0
  25. package/dist/adapters/mcp/ambient-envelope.js +70 -0
  26. package/dist/adapters/mcp/ambient-envelope.js.map +1 -0
  27. package/dist/adapters/mcp/catalog.d.ts +15 -0
  28. package/dist/adapters/mcp/catalog.d.ts.map +1 -0
  29. package/dist/adapters/mcp/catalog.js +26 -0
  30. package/dist/adapters/mcp/catalog.js.map +1 -0
  31. package/dist/adapters/mcp/connection-identity.d.ts +81 -0
  32. package/dist/adapters/mcp/connection-identity.d.ts.map +1 -0
  33. package/dist/adapters/mcp/connection-identity.js +114 -0
  34. package/dist/adapters/mcp/connection-identity.js.map +1 -0
  35. package/dist/adapters/mcp/context.d.ts +137 -0
  36. package/dist/adapters/mcp/context.d.ts.map +1 -0
  37. package/dist/adapters/mcp/context.js +230 -0
  38. package/dist/adapters/mcp/context.js.map +1 -0
  39. package/dist/adapters/mcp/formatters.d.ts +41 -0
  40. package/dist/adapters/mcp/formatters.d.ts.map +1 -0
  41. package/dist/adapters/mcp/formatters.js +102 -0
  42. package/dist/adapters/mcp/formatters.js.map +1 -0
  43. package/dist/adapters/mcp/from-api-endpoint.d.ts +59 -0
  44. package/dist/adapters/mcp/from-api-endpoint.d.ts.map +1 -0
  45. package/dist/adapters/mcp/from-api-endpoint.js +206 -0
  46. package/dist/adapters/mcp/from-api-endpoint.js.map +1 -0
  47. package/dist/adapters/mcp/generation-contract.d.ts +5 -0
  48. package/dist/adapters/mcp/generation-contract.d.ts.map +1 -0
  49. package/dist/adapters/mcp/generation-contract.js +34 -0
  50. package/dist/adapters/mcp/generation-contract.js.map +1 -0
  51. package/dist/adapters/mcp/host-registry.d.ts +159 -0
  52. package/dist/adapters/mcp/host-registry.d.ts.map +1 -0
  53. package/dist/adapters/mcp/host-registry.js +237 -0
  54. package/dist/adapters/mcp/host-registry.js.map +1 -0
  55. package/dist/adapters/mcp/index.d.ts +12 -0
  56. package/dist/adapters/mcp/index.d.ts.map +1 -0
  57. package/dist/adapters/mcp/index.js +17 -0
  58. package/dist/adapters/mcp/index.js.map +1 -0
  59. package/dist/adapters/mcp/isolated-notice.d.ts +111 -0
  60. package/dist/adapters/mcp/isolated-notice.d.ts.map +1 -0
  61. package/dist/adapters/mcp/isolated-notice.js +217 -0
  62. package/dist/adapters/mcp/isolated-notice.js.map +1 -0
  63. package/dist/adapters/mcp/json-rpc.d.ts +16 -0
  64. package/dist/adapters/mcp/json-rpc.d.ts.map +1 -0
  65. package/dist/adapters/mcp/json-rpc.js +32 -0
  66. package/dist/adapters/mcp/json-rpc.js.map +1 -0
  67. package/dist/adapters/mcp/local-origin.d.ts +18 -0
  68. package/dist/adapters/mcp/local-origin.d.ts.map +1 -0
  69. package/dist/adapters/mcp/local-origin.js +37 -0
  70. package/dist/adapters/mcp/local-origin.js.map +1 -0
  71. package/dist/adapters/mcp/logging/mcp-cloud.descriptor.d.ts +11 -0
  72. package/dist/adapters/mcp/logging/mcp-cloud.descriptor.d.ts.map +1 -0
  73. package/dist/adapters/mcp/logging/mcp-cloud.descriptor.js +21 -0
  74. package/dist/adapters/mcp/logging/mcp-cloud.descriptor.js.map +1 -0
  75. package/dist/adapters/mcp/logging/mcp-http.descriptor.d.ts +10 -0
  76. package/dist/adapters/mcp/logging/mcp-http.descriptor.d.ts.map +1 -0
  77. package/dist/adapters/mcp/logging/mcp-http.descriptor.js +20 -0
  78. package/dist/adapters/mcp/logging/mcp-http.descriptor.js.map +1 -0
  79. package/dist/adapters/mcp/logging/mcp-server.descriptor.d.ts +9 -0
  80. package/dist/adapters/mcp/logging/mcp-server.descriptor.d.ts.map +1 -0
  81. package/dist/adapters/mcp/logging/mcp-server.descriptor.js +25 -0
  82. package/dist/adapters/mcp/logging/mcp-server.descriptor.js.map +1 -0
  83. package/dist/adapters/mcp/logging/mcp-stdio.descriptor.d.ts +10 -0
  84. package/dist/adapters/mcp/logging/mcp-stdio.descriptor.d.ts.map +1 -0
  85. package/dist/adapters/mcp/logging/mcp-stdio.descriptor.js +20 -0
  86. package/dist/adapters/mcp/logging/mcp-stdio.descriptor.js.map +1 -0
  87. package/dist/adapters/mcp/mcp-http-session.d.ts +140 -0
  88. package/dist/adapters/mcp/mcp-http-session.d.ts.map +1 -0
  89. package/dist/adapters/mcp/mcp-http-session.js +797 -0
  90. package/dist/adapters/mcp/mcp-http-session.js.map +1 -0
  91. package/dist/adapters/mcp/mcp-wire-name.d.ts +9 -0
  92. package/dist/adapters/mcp/mcp-wire-name.d.ts.map +1 -0
  93. package/dist/adapters/mcp/mcp-wire-name.js +52 -0
  94. package/dist/adapters/mcp/mcp-wire-name.js.map +1 -0
  95. package/dist/adapters/mcp/projection-doctor.d.ts +101 -0
  96. package/dist/adapters/mcp/projection-doctor.d.ts.map +1 -0
  97. package/dist/adapters/mcp/projection-doctor.js +285 -0
  98. package/dist/adapters/mcp/projection-doctor.js.map +1 -0
  99. package/dist/adapters/mcp/projection.d.ts +36 -0
  100. package/dist/adapters/mcp/projection.d.ts.map +1 -0
  101. package/dist/adapters/mcp/projection.js +136 -0
  102. package/dist/adapters/mcp/projection.js.map +1 -0
  103. package/dist/adapters/mcp/prompts/continuity-wake.prompt.descriptor.d.ts +113 -0
  104. package/dist/adapters/mcp/prompts/continuity-wake.prompt.descriptor.d.ts.map +1 -0
  105. package/dist/adapters/mcp/prompts/continuity-wake.prompt.descriptor.js +15 -0
  106. package/dist/adapters/mcp/prompts/continuity-wake.prompt.descriptor.js.map +1 -0
  107. package/dist/adapters/mcp/prompts/empty-tool-kind.prompt.descriptor.d.ts +113 -0
  108. package/dist/adapters/mcp/prompts/empty-tool-kind.prompt.descriptor.d.ts.map +1 -0
  109. package/dist/adapters/mcp/prompts/empty-tool-kind.prompt.descriptor.js +18 -0
  110. package/dist/adapters/mcp/prompts/empty-tool-kind.prompt.descriptor.js.map +1 -0
  111. package/dist/adapters/mcp/prompts/index.d.ts +113 -0
  112. package/dist/adapters/mcp/prompts/index.d.ts.map +1 -0
  113. package/dist/adapters/mcp/prompts/index.js +24 -0
  114. package/dist/adapters/mcp/prompts/index.js.map +1 -0
  115. package/dist/adapters/mcp/prompts/isolated-runtime.prompt.descriptor.d.ts +114 -0
  116. package/dist/adapters/mcp/prompts/isolated-runtime.prompt.descriptor.d.ts.map +1 -0
  117. package/dist/adapters/mcp/prompts/isolated-runtime.prompt.descriptor.js +37 -0
  118. package/dist/adapters/mcp/prompts/isolated-runtime.prompt.descriptor.js.map +1 -0
  119. package/dist/adapters/mcp/prompts/local-tools.prompt.descriptor.d.ts +113 -0
  120. package/dist/adapters/mcp/prompts/local-tools.prompt.descriptor.d.ts.map +1 -0
  121. package/dist/adapters/mcp/prompts/local-tools.prompt.descriptor.js +20 -0
  122. package/dist/adapters/mcp/prompts/local-tools.prompt.descriptor.js.map +1 -0
  123. package/dist/adapters/mcp/prompts/max-tools.prompt.descriptor.d.ts +113 -0
  124. package/dist/adapters/mcp/prompts/max-tools.prompt.descriptor.d.ts.map +1 -0
  125. package/dist/adapters/mcp/prompts/max-tools.prompt.descriptor.js +20 -0
  126. package/dist/adapters/mcp/prompts/max-tools.prompt.descriptor.js.map +1 -0
  127. package/dist/adapters/mcp/prompts/private-runtime.prompt.descriptor.d.ts +114 -0
  128. package/dist/adapters/mcp/prompts/private-runtime.prompt.descriptor.d.ts.map +1 -0
  129. package/dist/adapters/mcp/prompts/private-runtime.prompt.descriptor.js +21 -0
  130. package/dist/adapters/mcp/prompts/private-runtime.prompt.descriptor.js.map +1 -0
  131. package/dist/adapters/mcp/prompts/registration-failure.prompt.descriptor.d.ts +113 -0
  132. package/dist/adapters/mcp/prompts/registration-failure.prompt.descriptor.d.ts.map +1 -0
  133. package/dist/adapters/mcp/prompts/registration-failure.prompt.descriptor.js +19 -0
  134. package/dist/adapters/mcp/prompts/registration-failure.prompt.descriptor.js.map +1 -0
  135. package/dist/adapters/mcp/prompts/required-checklist.prompt.descriptor.d.ts +113 -0
  136. package/dist/adapters/mcp/prompts/required-checklist.prompt.descriptor.d.ts.map +1 -0
  137. package/dist/adapters/mcp/prompts/required-checklist.prompt.descriptor.js +18 -0
  138. package/dist/adapters/mcp/prompts/required-checklist.prompt.descriptor.js.map +1 -0
  139. package/dist/adapters/mcp/prompts/required-tools-missing.prompt.descriptor.d.ts +113 -0
  140. package/dist/adapters/mcp/prompts/required-tools-missing.prompt.descriptor.d.ts.map +1 -0
  141. package/dist/adapters/mcp/prompts/required-tools-missing.prompt.descriptor.js +19 -0
  142. package/dist/adapters/mcp/prompts/required-tools-missing.prompt.descriptor.js.map +1 -0
  143. package/dist/adapters/mcp/prompts/unknown-client.prompt.descriptor.d.ts +113 -0
  144. package/dist/adapters/mcp/prompts/unknown-client.prompt.descriptor.d.ts.map +1 -0
  145. package/dist/adapters/mcp/prompts/unknown-client.prompt.descriptor.js +18 -0
  146. package/dist/adapters/mcp/prompts/unknown-client.prompt.descriptor.js.map +1 -0
  147. package/dist/adapters/mcp/request-context.d.ts +28 -0
  148. package/dist/adapters/mcp/request-context.d.ts.map +1 -0
  149. package/dist/adapters/mcp/request-context.js +49 -0
  150. package/dist/adapters/mcp/request-context.js.map +1 -0
  151. package/dist/adapters/mcp/roster-announcer.d.ts +3 -0
  152. package/dist/adapters/mcp/roster-announcer.d.ts.map +1 -0
  153. package/dist/adapters/mcp/roster-announcer.js +22 -0
  154. package/dist/adapters/mcp/roster-announcer.js.map +1 -0
  155. package/dist/adapters/mcp/sampling-context.d.ts +12 -0
  156. package/dist/adapters/mcp/sampling-context.d.ts.map +1 -0
  157. package/dist/adapters/mcp/sampling-context.js +30 -0
  158. package/dist/adapters/mcp/sampling-context.js.map +1 -0
  159. package/dist/adapters/mcp/sampling.d.ts +17 -0
  160. package/dist/adapters/mcp/sampling.d.ts.map +1 -0
  161. package/dist/adapters/mcp/sampling.js +43 -0
  162. package/dist/adapters/mcp/sampling.js.map +1 -0
  163. package/dist/adapters/mcp/schemas/index.d.ts +37 -0
  164. package/dist/adapters/mcp/schemas/index.d.ts.map +1 -0
  165. package/dist/adapters/mcp/schemas/index.js +5 -0
  166. package/dist/adapters/mcp/schemas/index.js.map +1 -0
  167. package/dist/adapters/mcp/schemas/tool-selection.d.ts +12 -0
  168. package/dist/adapters/mcp/schemas/tool-selection.d.ts.map +1 -0
  169. package/dist/adapters/mcp/schemas/tool-selection.js +2 -0
  170. package/dist/adapters/mcp/schemas/tool-selection.js.map +1 -0
  171. package/dist/adapters/mcp/serve-identity.d.ts +64 -0
  172. package/dist/adapters/mcp/serve-identity.d.ts.map +1 -0
  173. package/dist/adapters/mcp/serve-identity.js +220 -0
  174. package/dist/adapters/mcp/serve-identity.js.map +1 -0
  175. package/dist/adapters/mcp/server.consumer.d.ts +5 -0
  176. package/dist/adapters/mcp/server.consumer.d.ts.map +1 -0
  177. package/dist/adapters/mcp/server.consumer.js +300 -0
  178. package/dist/adapters/mcp/server.consumer.js.map +1 -0
  179. package/dist/adapters/mcp/server.d.ts +56 -0
  180. package/dist/adapters/mcp/server.d.ts.map +1 -0
  181. package/dist/adapters/mcp/server.js +1423 -0
  182. package/dist/adapters/mcp/server.js.map +1 -0
  183. package/dist/adapters/mcp/stale-body.consumer.d.ts +85 -0
  184. package/dist/adapters/mcp/stale-body.consumer.d.ts.map +1 -0
  185. package/dist/adapters/mcp/stale-body.consumer.js +183 -0
  186. package/dist/adapters/mcp/stale-body.consumer.js.map +1 -0
  187. package/dist/adapters/mcp/startup-refusal.d.ts +8 -0
  188. package/dist/adapters/mcp/startup-refusal.d.ts.map +1 -0
  189. package/dist/adapters/mcp/startup-refusal.js +141 -0
  190. package/dist/adapters/mcp/startup-refusal.js.map +1 -0
  191. package/dist/adapters/mcp/stdio-relay.d.ts +232 -0
  192. package/dist/adapters/mcp/stdio-relay.d.ts.map +1 -0
  193. package/dist/adapters/mcp/stdio-relay.js +1169 -0
  194. package/dist/adapters/mcp/stdio-relay.js.map +1 -0
  195. package/dist/adapters/mcp/tool-audit-live.d.ts +109 -0
  196. package/dist/adapters/mcp/tool-audit-live.d.ts.map +1 -0
  197. package/dist/adapters/mcp/tool-audit-live.js +538 -0
  198. package/dist/adapters/mcp/tool-audit-live.js.map +1 -0
  199. package/dist/adapters/mcp/tool-audit.d.ts +44 -0
  200. package/dist/adapters/mcp/tool-audit.d.ts.map +1 -0
  201. package/dist/adapters/mcp/tool-audit.js +168 -0
  202. package/dist/adapters/mcp/tool-audit.js.map +1 -0
  203. package/dist/adapters/mcp/tool-catalog.d.ts +179 -0
  204. package/dist/adapters/mcp/tool-catalog.d.ts.map +1 -0
  205. package/dist/adapters/mcp/tool-catalog.js +330 -0
  206. package/dist/adapters/mcp/tool-catalog.js.map +1 -0
  207. package/dist/adapters/mcp/tool-selection.d.ts +77 -0
  208. package/dist/adapters/mcp/tool-selection.d.ts.map +1 -0
  209. package/dist/adapters/mcp/tool-selection.js +266 -0
  210. package/dist/adapters/mcp/tool-selection.js.map +1 -0
  211. package/dist/adapters/mcp/transport-registry.d.ts +5 -0
  212. package/dist/adapters/mcp/transport-registry.d.ts.map +1 -0
  213. package/dist/adapters/mcp/transport-registry.js +13 -0
  214. package/dist/adapters/mcp/transport-registry.js.map +1 -0
  215. package/dist/adapters/mcp/trust-tag.d.ts +69 -0
  216. package/dist/adapters/mcp/trust-tag.d.ts.map +1 -0
  217. package/dist/adapters/mcp/trust-tag.js +120 -0
  218. package/dist/adapters/mcp/trust-tag.js.map +1 -0
  219. package/dist/adapters/mcp/vibes-types.d.ts +115 -0
  220. package/dist/adapters/mcp/vibes-types.d.ts.map +1 -0
  221. package/dist/adapters/mcp/vibes-types.js +7 -0
  222. package/dist/adapters/mcp/vibes-types.js.map +1 -0
  223. package/dist/adapters/mcp/web-handler.d.ts +49 -0
  224. package/dist/adapters/mcp/web-handler.d.ts.map +1 -0
  225. package/dist/adapters/mcp/web-handler.js +129 -0
  226. package/dist/adapters/mcp/web-handler.js.map +1 -0
  227. package/dist/adapters/session-id.d.ts +3 -0
  228. package/dist/adapters/session-id.d.ts.map +1 -0
  229. package/dist/adapters/session-id.js +42 -0
  230. package/dist/adapters/session-id.js.map +1 -0
  231. package/dist/adapters/transport-bun-http.d.ts +39 -0
  232. package/dist/adapters/transport-bun-http.d.ts.map +1 -0
  233. package/dist/adapters/transport-bun-http.js +42 -0
  234. package/dist/adapters/transport-bun-http.js.map +1 -0
  235. package/dist/adapters/types.d.ts +135 -0
  236. package/dist/adapters/types.d.ts.map +1 -0
  237. package/dist/adapters/types.js +2 -0
  238. package/dist/adapters/types.js.map +1 -0
  239. package/dist/adapters/web-compatibility.d.ts +6 -0
  240. package/dist/adapters/web-compatibility.d.ts.map +1 -0
  241. package/dist/adapters/web-compatibility.js +13 -0
  242. package/dist/adapters/web-compatibility.js.map +1 -0
  243. package/dist/adapters/zod-mcp.d.ts +139 -0
  244. package/dist/adapters/zod-mcp.d.ts.map +1 -0
  245. package/dist/adapters/zod-mcp.js +617 -0
  246. package/dist/adapters/zod-mcp.js.map +1 -0
  247. package/dist/cli/mcp/audit/dev.audit.tools.descriptor.d.ts +15 -0
  248. package/dist/cli/mcp/audit/dev.audit.tools.descriptor.d.ts.map +1 -0
  249. package/dist/cli/mcp/audit/dev.audit.tools.descriptor.js +58 -0
  250. package/dist/cli/mcp/audit/dev.audit.tools.descriptor.js.map +1 -0
  251. package/dist/cli/mcp/audit/dev.audit.tools.impl.consumer.d.ts +6 -0
  252. package/dist/cli/mcp/audit/dev.audit.tools.impl.consumer.d.ts.map +1 -0
  253. package/dist/cli/mcp/audit/dev.audit.tools.impl.consumer.js +180 -0
  254. package/dist/cli/mcp/audit/dev.audit.tools.impl.consumer.js.map +1 -0
  255. package/dist/cli/mcp/lifecycle/dev.mcp.restart.descriptor.d.ts +22 -0
  256. package/dist/cli/mcp/lifecycle/dev.mcp.restart.descriptor.d.ts.map +1 -0
  257. package/dist/cli/mcp/lifecycle/dev.mcp.restart.descriptor.js +19 -0
  258. package/dist/cli/mcp/lifecycle/dev.mcp.restart.descriptor.js.map +1 -0
  259. package/dist/cli/mcp/lifecycle/dev.mcp.restart.impl.consumer.d.ts +23 -0
  260. package/dist/cli/mcp/lifecycle/dev.mcp.restart.impl.consumer.d.ts.map +1 -0
  261. package/dist/cli/mcp/lifecycle/dev.mcp.restart.impl.consumer.js +41 -0
  262. package/dist/cli/mcp/lifecycle/dev.mcp.restart.impl.consumer.js.map +1 -0
  263. package/dist/cli/mcp/lifecycle/dev.mcp.serve.descriptor.d.ts +75 -0
  264. package/dist/cli/mcp/lifecycle/dev.mcp.serve.descriptor.d.ts.map +1 -0
  265. package/dist/cli/mcp/lifecycle/dev.mcp.serve.descriptor.js +84 -0
  266. package/dist/cli/mcp/lifecycle/dev.mcp.serve.descriptor.js.map +1 -0
  267. package/dist/cli/mcp/lifecycle/dev.mcp.start.descriptor.d.ts +35 -0
  268. package/dist/cli/mcp/lifecycle/dev.mcp.start.descriptor.d.ts.map +1 -0
  269. package/dist/cli/mcp/lifecycle/dev.mcp.start.descriptor.js +28 -0
  270. package/dist/cli/mcp/lifecycle/dev.mcp.start.descriptor.js.map +1 -0
  271. package/dist/cli/mcp/lifecycle/dev.mcp.start.impl.consumer.d.ts +11 -0
  272. package/dist/cli/mcp/lifecycle/dev.mcp.start.impl.consumer.d.ts.map +1 -0
  273. package/dist/cli/mcp/lifecycle/dev.mcp.start.impl.consumer.js +47 -0
  274. package/dist/cli/mcp/lifecycle/dev.mcp.start.impl.consumer.js.map +1 -0
  275. package/dist/cli/mcp/lifecycle/dev.mcp.stop.descriptor.d.ts +18 -0
  276. package/dist/cli/mcp/lifecycle/dev.mcp.stop.descriptor.d.ts.map +1 -0
  277. package/dist/cli/mcp/lifecycle/dev.mcp.stop.descriptor.js +18 -0
  278. package/dist/cli/mcp/lifecycle/dev.mcp.stop.descriptor.js.map +1 -0
  279. package/dist/cli/mcp/lifecycle/dev.mcp.stop.impl.consumer.d.ts +12 -0
  280. package/dist/cli/mcp/lifecycle/dev.mcp.stop.impl.consumer.d.ts.map +1 -0
  281. package/dist/cli/mcp/lifecycle/dev.mcp.stop.impl.consumer.js +17 -0
  282. package/dist/cli/mcp/lifecycle/dev.mcp.stop.impl.consumer.js.map +1 -0
  283. package/dist/cli/mcp/management/dev.mcp.install.descriptor.d.ts +41 -0
  284. package/dist/cli/mcp/management/dev.mcp.install.descriptor.d.ts.map +1 -0
  285. package/dist/cli/mcp/management/dev.mcp.install.descriptor.js +49 -0
  286. package/dist/cli/mcp/management/dev.mcp.install.descriptor.js.map +1 -0
  287. package/dist/cli/mcp/management/dev.mcp.install.impl.consumer.d.ts +16 -0
  288. package/dist/cli/mcp/management/dev.mcp.install.impl.consumer.d.ts.map +1 -0
  289. package/dist/cli/mcp/management/dev.mcp.install.impl.consumer.js +244 -0
  290. package/dist/cli/mcp/management/dev.mcp.install.impl.consumer.js.map +1 -0
  291. package/dist/cli/mcp/management/dev.mcp.list.descriptor.d.ts +22 -0
  292. package/dist/cli/mcp/management/dev.mcp.list.descriptor.d.ts.map +1 -0
  293. package/dist/cli/mcp/management/dev.mcp.list.descriptor.js +24 -0
  294. package/dist/cli/mcp/management/dev.mcp.list.descriptor.js.map +1 -0
  295. package/dist/cli/mcp/management/dev.mcp.list.impl.consumer.d.ts +29 -0
  296. package/dist/cli/mcp/management/dev.mcp.list.impl.consumer.d.ts.map +1 -0
  297. package/dist/cli/mcp/management/dev.mcp.list.impl.consumer.js +127 -0
  298. package/dist/cli/mcp/management/dev.mcp.list.impl.consumer.js.map +1 -0
  299. package/dist/cli/mcp/management/dev.mcp.status.descriptor.d.ts +48 -0
  300. package/dist/cli/mcp/management/dev.mcp.status.descriptor.d.ts.map +1 -0
  301. package/dist/cli/mcp/management/dev.mcp.status.descriptor.js +56 -0
  302. package/dist/cli/mcp/management/dev.mcp.status.descriptor.js.map +1 -0
  303. package/dist/cli/mcp/management/dev.mcp.status.impl.consumer.d.ts +9 -0
  304. package/dist/cli/mcp/management/dev.mcp.status.impl.consumer.d.ts.map +1 -0
  305. package/dist/cli/mcp/management/dev.mcp.status.impl.consumer.js +253 -0
  306. package/dist/cli/mcp/management/dev.mcp.status.impl.consumer.js.map +1 -0
  307. package/dist/cli/mcp/management/install-helpers.d.ts +26 -0
  308. package/dist/cli/mcp/management/install-helpers.d.ts.map +1 -0
  309. package/dist/cli/mcp/management/install-helpers.js +95 -0
  310. package/dist/cli/mcp/management/install-helpers.js.map +1 -0
  311. package/dist/cli/mcp/management/process-audit.impl.consumer.d.ts +38 -0
  312. package/dist/cli/mcp/management/process-audit.impl.consumer.d.ts.map +1 -0
  313. package/dist/cli/mcp/management/process-audit.impl.consumer.js +145 -0
  314. package/dist/cli/mcp/management/process-audit.impl.consumer.js.map +1 -0
  315. package/dist/cli/mcp/management/rules-debt-summary.impl.consumer.d.ts +21 -0
  316. package/dist/cli/mcp/management/rules-debt-summary.impl.consumer.d.ts.map +1 -0
  317. package/dist/cli/mcp/management/rules-debt-summary.impl.consumer.js +82 -0
  318. package/dist/cli/mcp/management/rules-debt-summary.impl.consumer.js.map +1 -0
  319. package/dist/cli/mcp/management/status-doctor.d.ts +22 -0
  320. package/dist/cli/mcp/management/status-doctor.d.ts.map +1 -0
  321. package/dist/cli/mcp/management/status-doctor.js +153 -0
  322. package/dist/cli/mcp/management/status-doctor.js.map +1 -0
  323. package/dist/cli/mcp/schemas/status-doctor.d.ts +75 -0
  324. package/dist/cli/mcp/schemas/status-doctor.d.ts.map +1 -0
  325. package/dist/cli/mcp/schemas/status-doctor.js +2 -0
  326. package/dist/cli/mcp/schemas/status-doctor.js.map +1 -0
  327. package/dist/cli/mcp/support/host-port.d.ts +31 -0
  328. package/dist/cli/mcp/support/host-port.d.ts.map +1 -0
  329. package/dist/cli/mcp/support/host-port.js +31 -0
  330. package/dist/cli/mcp/support/host-port.js.map +1 -0
  331. package/dist/cli/mcp/support/server-process.impl.consumer.d.ts +24 -0
  332. package/dist/cli/mcp/support/server-process.impl.consumer.d.ts.map +1 -0
  333. package/dist/cli/mcp/support/server-process.impl.consumer.js +86 -0
  334. package/dist/cli/mcp/support/server-process.impl.consumer.js.map +1 -0
  335. package/dist/cli/mcp/support/status-doctor.d.ts +33 -0
  336. package/dist/cli/mcp/support/status-doctor.d.ts.map +1 -0
  337. package/dist/cli/mcp/support/status-doctor.js +104 -0
  338. package/dist/cli/mcp/support/status-doctor.js.map +1 -0
  339. package/dist/cli/print.d.ts +12 -0
  340. package/dist/cli/print.d.ts.map +1 -0
  341. package/dist/cli/print.js +25 -0
  342. package/dist/cli/print.js.map +1 -0
  343. package/dist/cli/surface-mcp/client-connection-identity.consumer.d.ts +19 -0
  344. package/dist/cli/surface-mcp/client-connection-identity.consumer.d.ts.map +1 -0
  345. package/dist/cli/surface-mcp/client-connection-identity.consumer.js +53 -0
  346. package/dist/cli/surface-mcp/client-connection-identity.consumer.js.map +1 -0
  347. package/dist/cli/surface-mcp/dev.mcp.serve.impl.cloud.d.ts +7 -0
  348. package/dist/cli/surface-mcp/dev.mcp.serve.impl.cloud.d.ts.map +1 -0
  349. package/dist/cli/surface-mcp/dev.mcp.serve.impl.cloud.js +345 -0
  350. package/dist/cli/surface-mcp/dev.mcp.serve.impl.cloud.js.map +1 -0
  351. package/dist/cli/surface-mcp/dev.mcp.serve.impl.consumer.d.ts +12 -0
  352. package/dist/cli/surface-mcp/dev.mcp.serve.impl.consumer.d.ts.map +1 -0
  353. package/dist/cli/surface-mcp/dev.mcp.serve.impl.consumer.js +657 -0
  354. package/dist/cli/surface-mcp/dev.mcp.serve.impl.consumer.js.map +1 -0
  355. package/dist/cli/surface-mcp/local-dev-flag.d.ts +24 -0
  356. package/dist/cli/surface-mcp/local-dev-flag.d.ts.map +1 -0
  357. package/dist/cli/surface-mcp/local-dev-flag.js +37 -0
  358. package/dist/cli/surface-mcp/local-dev-flag.js.map +1 -0
  359. package/dist/cli/surface-mcp/shared-host-startup.d.ts +14 -0
  360. package/dist/cli/surface-mcp/shared-host-startup.d.ts.map +1 -0
  361. package/dist/cli/surface-mcp/shared-host-startup.js +46 -0
  362. package/dist/cli/surface-mcp/shared-host-startup.js.map +1 -0
  363. package/dist/cli/surface-mcp/stdio-relay-transport.d.ts +2 -0
  364. package/dist/cli/surface-mcp/stdio-relay-transport.d.ts.map +1 -0
  365. package/dist/cli/surface-mcp/stdio-relay-transport.js +21 -0
  366. package/dist/cli/surface-mcp/stdio-relay-transport.js.map +1 -0
  367. package/dist/cli/surface-mcp/teardown.d.ts +17 -0
  368. package/dist/cli/surface-mcp/teardown.d.ts.map +1 -0
  369. package/dist/cli/surface-mcp/teardown.js +28 -0
  370. package/dist/cli/surface-mcp/teardown.js.map +1 -0
  371. package/dist/client.d.ts +49 -0
  372. package/dist/client.d.ts.map +1 -0
  373. package/dist/client.js +131 -0
  374. package/dist/client.js.map +1 -0
  375. package/dist/config/load.d.ts +6 -0
  376. package/dist/config/load.d.ts.map +1 -0
  377. package/dist/config/load.js +18 -0
  378. package/dist/config/load.js.map +1 -0
  379. package/dist/config/mcp.config.descriptor.d.ts +108 -0
  380. package/dist/config/mcp.config.descriptor.d.ts.map +1 -0
  381. package/dist/config/mcp.config.descriptor.js +63 -0
  382. package/dist/config/mcp.config.descriptor.js.map +1 -0
  383. package/dist/docs/mcp.docs.descriptor.d.ts +4 -0
  384. package/dist/docs/mcp.docs.descriptor.d.ts.map +1 -0
  385. package/dist/docs/mcp.docs.descriptor.js +44 -0
  386. package/dist/docs/mcp.docs.descriptor.js.map +1 -0
  387. package/dist/docs/prompts.docs.descriptor.d.ts +4 -0
  388. package/dist/docs/prompts.docs.descriptor.d.ts.map +1 -0
  389. package/dist/docs/prompts.docs.descriptor.js +130 -0
  390. package/dist/docs/prompts.docs.descriptor.js.map +1 -0
  391. package/dist/docs/resources.docs.descriptor.d.ts +4 -0
  392. package/dist/docs/resources.docs.descriptor.d.ts.map +1 -0
  393. package/dist/docs/resources.docs.descriptor.js +390 -0
  394. package/dist/docs/resources.docs.descriptor.js.map +1 -0
  395. package/dist/docs/server.docs.descriptor.d.ts +4 -0
  396. package/dist/docs/server.docs.descriptor.d.ts.map +1 -0
  397. package/dist/docs/server.docs.descriptor.js +383 -0
  398. package/dist/docs/server.docs.descriptor.js.map +1 -0
  399. package/dist/docs/tools.docs.descriptor.d.ts +4 -0
  400. package/dist/docs/tools.docs.descriptor.d.ts.map +1 -0
  401. package/dist/docs/tools.docs.descriptor.js +316 -0
  402. package/dist/docs/tools.docs.descriptor.js.map +1 -0
  403. package/dist/harness-identity.descriptors.d.ts +11 -0
  404. package/dist/harness-identity.descriptors.d.ts.map +1 -0
  405. package/dist/harness-identity.descriptors.js +41 -0
  406. package/dist/harness-identity.descriptors.js.map +1 -0
  407. package/dist/index.d.ts +36 -0
  408. package/dist/index.d.ts.map +1 -0
  409. package/dist/index.js +36 -0
  410. package/dist/index.js.map +1 -0
  411. package/dist/kinds/harness-identity.kind.d.ts +3 -0
  412. package/dist/kinds/harness-identity.kind.d.ts.map +1 -0
  413. package/dist/kinds/harness-identity.kind.js +16 -0
  414. package/dist/kinds/harness-identity.kind.js.map +1 -0
  415. package/dist/kinds/sampling.kind.d.ts +15 -0
  416. package/dist/kinds/sampling.kind.d.ts.map +1 -0
  417. package/dist/kinds/sampling.kind.js +24 -0
  418. package/dist/kinds/sampling.kind.js.map +1 -0
  419. package/dist/lifecycle-repeat.d.ts +23 -0
  420. package/dist/lifecycle-repeat.d.ts.map +1 -0
  421. package/dist/lifecycle-repeat.js +21 -0
  422. package/dist/lifecycle-repeat.js.map +1 -0
  423. package/dist/mcp/dev.mcp.descriptor.d.ts +18 -0
  424. package/dist/mcp/dev.mcp.descriptor.d.ts.map +1 -0
  425. package/dist/mcp/dev.mcp.descriptor.js +18 -0
  426. package/dist/mcp/dev.mcp.descriptor.js.map +1 -0
  427. package/dist/mcp/index.d.ts +20 -0
  428. package/dist/mcp/index.d.ts.map +1 -0
  429. package/dist/mcp/index.js +19 -0
  430. package/dist/mcp/index.js.map +1 -0
  431. package/dist/mcp/mcp.plugin.d.ts +9 -0
  432. package/dist/mcp/mcp.plugin.d.ts.map +1 -0
  433. package/dist/mcp/mcp.plugin.js +59 -0
  434. package/dist/mcp/mcp.plugin.js.map +1 -0
  435. package/dist/mcp-client.plugin.d.ts +4 -0
  436. package/dist/mcp-client.plugin.d.ts.map +1 -0
  437. package/dist/mcp-client.plugin.js +13 -0
  438. package/dist/mcp-client.plugin.js.map +1 -0
  439. package/dist/mcp.client.descriptor.d.ts +27 -0
  440. package/dist/mcp.client.descriptor.d.ts.map +1 -0
  441. package/dist/mcp.client.descriptor.js +7 -0
  442. package/dist/mcp.client.descriptor.js.map +1 -0
  443. package/dist/mcp.consumer.plugin.d.ts +4 -0
  444. package/dist/mcp.consumer.plugin.d.ts.map +1 -0
  445. package/dist/mcp.consumer.plugin.js +19 -0
  446. package/dist/mcp.consumer.plugin.js.map +1 -0
  447. package/dist/mcp.runtime.plugin.d.ts +10 -0
  448. package/dist/mcp.runtime.plugin.d.ts.map +1 -0
  449. package/dist/mcp.runtime.plugin.js +40 -0
  450. package/dist/mcp.runtime.plugin.js.map +1 -0
  451. package/dist/root-state.d.ts +3 -0
  452. package/dist/root-state.d.ts.map +1 -0
  453. package/dist/root-state.js +3 -0
  454. package/dist/root-state.js.map +1 -0
  455. package/dist/schemas/api-endpoint.d.ts +12 -0
  456. package/dist/schemas/api-endpoint.d.ts.map +1 -0
  457. package/dist/schemas/api-endpoint.js +11 -0
  458. package/dist/schemas/api-endpoint.js.map +1 -0
  459. package/dist/schemas/audit.d.ts +172 -0
  460. package/dist/schemas/audit.d.ts.map +1 -0
  461. package/dist/schemas/audit.js +82 -0
  462. package/dist/schemas/audit.js.map +1 -0
  463. package/dist/schemas/harness-identity.d.ts +41 -0
  464. package/dist/schemas/harness-identity.d.ts.map +1 -0
  465. package/dist/schemas/harness-identity.js +19 -0
  466. package/dist/schemas/harness-identity.js.map +1 -0
  467. package/dist/schemas/host-adapters.d.ts +14 -0
  468. package/dist/schemas/host-adapters.d.ts.map +1 -0
  469. package/dist/schemas/host-adapters.js +7 -0
  470. package/dist/schemas/host-adapters.js.map +1 -0
  471. package/dist/schemas/projection.d.ts +10 -0
  472. package/dist/schemas/projection.d.ts.map +1 -0
  473. package/dist/schemas/projection.js +9 -0
  474. package/dist/schemas/projection.js.map +1 -0
  475. package/dist/schemas/tool.d.ts +8 -0
  476. package/dist/schemas/tool.d.ts.map +1 -0
  477. package/dist/schemas/tool.js +8 -0
  478. package/dist/schemas/tool.js.map +1 -0
  479. package/dist/surface-mcp/build-provenance.d.ts +67 -0
  480. package/dist/surface-mcp/build-provenance.d.ts.map +1 -0
  481. package/dist/surface-mcp/build-provenance.js +287 -0
  482. package/dist/surface-mcp/build-provenance.js.map +1 -0
  483. package/dist/surface-mcp/crash-reporting.d.ts +28 -0
  484. package/dist/surface-mcp/crash-reporting.d.ts.map +1 -0
  485. package/dist/surface-mcp/crash-reporting.js +41 -0
  486. package/dist/surface-mcp/crash-reporting.js.map +1 -0
  487. package/dist/surface-mcp/executable-freshness-watch.d.ts +89 -0
  488. package/dist/surface-mcp/executable-freshness-watch.d.ts.map +1 -0
  489. package/dist/surface-mcp/executable-freshness-watch.js +225 -0
  490. package/dist/surface-mcp/executable-freshness-watch.js.map +1 -0
  491. package/dist/surface-mcp/host-provenance-banner.d.ts +49 -0
  492. package/dist/surface-mcp/host-provenance-banner.d.ts.map +1 -0
  493. package/dist/surface-mcp/host-provenance-banner.js +135 -0
  494. package/dist/surface-mcp/host-provenance-banner.js.map +1 -0
  495. package/dist/surface-mcp/index.d.ts +51 -0
  496. package/dist/surface-mcp/index.d.ts.map +1 -0
  497. package/dist/surface-mcp/index.js +92 -0
  498. package/dist/surface-mcp/index.js.map +1 -0
  499. package/dist/surface-mcp/local-tools-bootstrap.d.ts +36 -0
  500. package/dist/surface-mcp/local-tools-bootstrap.d.ts.map +1 -0
  501. package/dist/surface-mcp/local-tools-bootstrap.js +43 -0
  502. package/dist/surface-mcp/local-tools-bootstrap.js.map +1 -0
  503. package/dist/surface-mcp/mcp-serve.plugin.d.ts +9 -0
  504. package/dist/surface-mcp/mcp-serve.plugin.d.ts.map +1 -0
  505. package/dist/surface-mcp/mcp-serve.plugin.js +24 -0
  506. package/dist/surface-mcp/mcp-serve.plugin.js.map +1 -0
  507. package/dist/surface-mcp/project-config.d.ts +35 -0
  508. package/dist/surface-mcp/project-config.d.ts.map +1 -0
  509. package/dist/surface-mcp/project-config.js +74 -0
  510. package/dist/surface-mcp/project-config.js.map +1 -0
  511. package/dist/surface-mcp/serve-lifecycle.d.ts +102 -0
  512. package/dist/surface-mcp/serve-lifecycle.d.ts.map +1 -0
  513. package/dist/surface-mcp/serve-lifecycle.js +343 -0
  514. package/dist/surface-mcp/serve-lifecycle.js.map +1 -0
  515. package/dist/surface-mcp/session-bridge.d.ts +18 -0
  516. package/dist/surface-mcp/session-bridge.d.ts.map +1 -0
  517. package/dist/surface-mcp/session-bridge.js +26 -0
  518. package/dist/surface-mcp/session-bridge.js.map +1 -0
  519. package/dist/surface-mcp/subscriptions/session.bound.workspace.descriptor.d.ts +13 -0
  520. package/dist/surface-mcp/subscriptions/session.bound.workspace.descriptor.d.ts.map +1 -0
  521. package/dist/surface-mcp/subscriptions/session.bound.workspace.descriptor.js +13 -0
  522. package/dist/surface-mcp/subscriptions/session.bound.workspace.descriptor.js.map +1 -0
  523. package/dist/surface-mcp/subscriptions/session.bound.workspace.impl.consumer.d.ts +11 -0
  524. package/dist/surface-mcp/subscriptions/session.bound.workspace.impl.consumer.d.ts.map +1 -0
  525. package/dist/surface-mcp/subscriptions/session.bound.workspace.impl.consumer.js +36 -0
  526. package/dist/surface-mcp/subscriptions/session.bound.workspace.impl.consumer.js.map +1 -0
  527. package/dist/surface-mcp/workspace-warning.d.ts +16 -0
  528. package/dist/surface-mcp/workspace-warning.d.ts.map +1 -0
  529. package/dist/surface-mcp/workspace-warning.js +44 -0
  530. package/dist/surface-mcp/workspace-warning.js.map +1 -0
  531. package/dist/svelte/index.d.ts +12 -0
  532. package/dist/svelte/index.d.ts.map +1 -0
  533. package/dist/svelte/index.js +11 -0
  534. package/dist/svelte/mcp-svelte.plugin.d.ts +10 -0
  535. package/dist/svelte/mcp-svelte.plugin.d.ts.map +1 -0
  536. package/dist/svelte/mcp-svelte.plugin.js +24 -0
  537. package/dist/svelte/tool-widgets/VibesContextToolCard.svelte +91 -0
  538. package/dist/svelte/tool-widgets/VibesContextToolCard.svelte.d.ts +9 -0
  539. package/dist/svelte/tool-widgets/VibesContextToolCard.svelte.d.ts.map +1 -0
  540. package/dist/svelte/tool-widgets/index.d.ts +3 -0
  541. package/dist/svelte/tool-widgets/index.d.ts.map +1 -0
  542. package/dist/svelte/tool-widgets/index.js +2 -0
  543. package/dist/svelte/tool-widgets/summary.d.ts +48 -0
  544. package/dist/svelte/tool-widgets/summary.d.ts.map +1 -0
  545. package/dist/svelte/tool-widgets/summary.js +117 -0
  546. package/dist/tools/vibes-context/schemas.d.ts +85 -0
  547. package/dist/tools/vibes-context/schemas.d.ts.map +1 -0
  548. package/dist/tools/vibes-context/schemas.js +76 -0
  549. package/dist/tools/vibes-context/schemas.js.map +1 -0
  550. package/dist/tools/vibes-context/vibes-context.descriptor.d.ts +4 -0
  551. package/dist/tools/vibes-context/vibes-context.descriptor.d.ts.map +1 -0
  552. package/dist/tools/vibes-context/vibes-context.descriptor.js +30 -0
  553. package/dist/tools/vibes-context/vibes-context.descriptor.js.map +1 -0
  554. package/dist/tools/vibes-context/vibes-context.impl.d.ts +34 -0
  555. package/dist/tools/vibes-context/vibes-context.impl.d.ts.map +1 -0
  556. package/dist/tools/vibes-context/vibes-context.impl.js +207 -0
  557. package/dist/tools/vibes-context/vibes-context.impl.js.map +1 -0
  558. package/dist/tui/index.d.ts +4 -0
  559. package/dist/tui/index.d.ts.map +1 -0
  560. package/dist/tui/index.js +4 -0
  561. package/dist/tui/index.js.map +1 -0
  562. package/dist/tui/mcp-health.d.ts +7 -0
  563. package/dist/tui/mcp-health.d.ts.map +1 -0
  564. package/dist/tui/mcp-health.js +16 -0
  565. package/dist/tui/mcp-health.js.map +1 -0
  566. package/dist/tui/mcp-tui.plugin.d.ts +3 -0
  567. package/dist/tui/mcp-tui.plugin.d.ts.map +1 -0
  568. package/dist/tui/mcp-tui.plugin.js +18 -0
  569. package/dist/tui/mcp-tui.plugin.js.map +1 -0
  570. package/dist/tui/mcp.tui-app.descriptor.d.ts +4 -0
  571. package/dist/tui/mcp.tui-app.descriptor.d.ts.map +1 -0
  572. package/dist/tui/mcp.tui-app.descriptor.js +19 -0
  573. package/dist/tui/mcp.tui-app.descriptor.js.map +1 -0
  574. package/dist/tui/mcp.tui-app.impl.consumer.d.ts +4 -0
  575. package/dist/tui/mcp.tui-app.impl.consumer.d.ts.map +1 -0
  576. package/dist/tui/mcp.tui-app.impl.consumer.js +48 -0
  577. package/dist/tui/mcp.tui-app.impl.consumer.js.map +1 -0
  578. package/dist/tui/mcp.tui-state.descriptor.d.ts +8 -0
  579. package/dist/tui/mcp.tui-state.descriptor.d.ts.map +1 -0
  580. package/dist/tui/mcp.tui-state.descriptor.js +37 -0
  581. package/dist/tui/mcp.tui-state.descriptor.js.map +1 -0
  582. package/dist/tui/mcp.tui-state.impl.consumer.d.ts +5 -0
  583. package/dist/tui/mcp.tui-state.impl.consumer.d.ts.map +1 -0
  584. package/dist/tui/mcp.tui-state.impl.consumer.js +97 -0
  585. package/dist/tui/mcp.tui-state.impl.consumer.js.map +1 -0
  586. package/dist/tui/mcp.tui-state.key.d.ts +45 -0
  587. package/dist/tui/mcp.tui-state.key.d.ts.map +1 -0
  588. package/dist/tui/mcp.tui-state.key.js +25 -0
  589. package/dist/tui/mcp.tui-state.key.js.map +1 -0
  590. package/dist/types.core.d.ts +25 -0
  591. package/dist/types.core.d.ts.map +1 -0
  592. package/dist/types.core.js +8 -0
  593. package/dist/types.core.js.map +1 -0
  594. package/dist/types.d.ts +71 -0
  595. package/dist/types.d.ts.map +1 -0
  596. package/dist/types.js +2 -0
  597. package/dist/types.js.map +1 -0
  598. package/dist/types.registry.d.ts +93 -0
  599. package/dist/types.registry.d.ts.map +1 -0
  600. package/dist/types.registry.js +32 -0
  601. package/dist/types.registry.js.map +1 -0
  602. package/package.json +323 -4
@@ -0,0 +1,1169 @@
1
+ import { loadPurposePresetOverrides } from '@vibesdotdev/tools/config/load';
2
+ import { runtimePurposePresets, purposePresetPolicyKey } from '@vibesdotdev/tools/schemas/presets/registry';
3
+ /**
4
+ * Relay a stdio MCP connection to the workspace's shared host.
5
+ *
6
+ * WHAT THIS SAVES, measured on this machine 2026-08-21:
7
+ *
8
+ * 10 `vibes dev mcp serve` processes, ALL serving one workspace, 51.3 GB
9
+ * resident. Eight had byte-identical arguments. Every one had booted its own
10
+ * full runtime to answer the same questions about the same files.
11
+ *
12
+ * Each client spawns its own stdio server, and each stdio server booted its
13
+ * own full MCP runtime, ~6 GB. Nothing was wrong with any individual decision;
14
+ * the cost only exists in the sum, which no single process is in a position to
15
+ * see.
16
+ *
17
+ * THE RELAY MUST SHORT-CIRCUIT BEFORE THE SERVE BODY. A relay that boots a
18
+ * runtime and then forwards has moved the work without removing it — it would
19
+ * look like a fix in every log line and change nothing on the memory graph.
20
+ *
21
+ * OPEN (RC02): `dev mcp serve` is a serve command, so the CLI boots its serve
22
+ * host through the one entry — compose and discover — BEFORE the
23
+ * action that calls this relay runs (rc02-rc03 §2.3). The relay still skips
24
+ * the adapter, session, hot-reload and indexing work, but no longer the host
25
+ * boot itself. Recovering that saving is a design decision (the relay is not a
26
+ * serve host). Relay activation is deferred by its descriptor so autonomous
27
+ * work belongs to the remote serving body, not to each forwarding process.
28
+ *
29
+ * TRANSPARENT PASSTHROUGH, deliberately. This bridges raw JSON-RPC messages
30
+ * rather than instantiating a Client/Server pair and re-implementing methods.
31
+ * A proxy that enumerates the methods it understands silently breaks every
32
+ * method added after it was written, and the failure looks like the feature
33
+ * simply not existing. Forwarding whole messages means new MCP methods, and
34
+ * capabilities this file has never heard of, work with no change here.
35
+ */
36
+ import { CallToolRequestSchema, ErrorCode, InitializeRequestSchema, JSONRPCErrorSchema, ListToolsRequestSchema, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js';
37
+ import z from 'zod/v4';
38
+ import { CapabilityError, CaughtFailureSchema, getRuntimeStdinEvents, isCapabilityError, revealLocalAttestation, valueFreeFailure } from '@vibesdotdev/kernel';
39
+ import { isLifecycleDeadline, RpcConnector } from '@vibesdotdev/kernel-rpc';
40
+ import { LOCAL_ATTESTATION_CLAIM_MISSING, readLocalAttestation } from '@vibesdotdev/secrets/attestation';
41
+ import { mcpRelayAttestationPath, readHostClaim } from "./host-registry.js";
42
+ import { mcpCatalogAttachKey, resolveMcpPreset } from "./tool-selection.js";
43
+ import { connectionIdentityMeta } from "./connection-identity.js";
44
+ import { createMcpWireNameMap, harnessNeedsSafeMcpNames, mcpWireToolName } from "./mcp-wire-name.js";
45
+ function messageRequestId(message) {
46
+ return 'method' in message && 'id' in message ? message.id : undefined;
47
+ }
48
+ function messageResponseId(message) {
49
+ return 'method' in message ? undefined : message.id;
50
+ }
51
+ function cancellationRequestId(message) {
52
+ if (!('method' in message) || message.method !== 'notifications/cancelled')
53
+ return undefined;
54
+ const parsed = JSONRPCErrorSchema.shape.id.safeParse(message.params?.requestId);
55
+ return parsed.success ? parsed.data : undefined;
56
+ }
57
+ function cloneMessage(message) {
58
+ return structuredClone(message);
59
+ }
60
+ function withCancellationRequestId(message, requestId) {
61
+ const translated = cloneMessage(message);
62
+ if (!('method' in translated))
63
+ throw new CapabilityError('Cancellation must be a JSON-RPC notification or request', 'INVALID_INPUT');
64
+ translated.params = { ...translated.params, requestId };
65
+ return translated;
66
+ }
67
+ /**
68
+ * HTTP statuses on which the host has REFUSED one request while keeping the
69
+ * transport session intact. 400/401/403/404 stay "reconnect": they mean the
70
+ * session itself is unusable (bad session id, auth, evicted).
71
+ */
72
+ const HOST_REFUSAL_STATUSES = new Set([405, 406, 409, 413, 415, 422, 429]);
73
+ const TransportHostErrorSchema = z.object({
74
+ error: JSONRPCErrorSchema.shape.error,
75
+ id: JSONRPCErrorSchema.shape.id.optional()
76
+ });
77
+ /** Parse the transport's JSON refusal while retaining failed decoding. */
78
+ function decodeHostRefusalBody(text) {
79
+ try {
80
+ const parsed = z.union([
81
+ TransportHostErrorSchema,
82
+ z.tuple([TransportHostErrorSchema]).rest(z.unknown()).transform((batch) => batch[0])
83
+ ]).safeParse(JSON.parse(text));
84
+ return { response: parsed.success ? parsed.data : null };
85
+ }
86
+ catch (cause) {
87
+ return { failure: CaughtFailureSchema.parse(cause) };
88
+ }
89
+ }
90
+ /** Decode the host's own error envelope, including the request it refused. */
91
+ function readHostProtocolError(error, log) {
92
+ const transportFailure = z.object({ code: z.number(), message: z.string() }).safeParse(error);
93
+ if (!transportFailure.success)
94
+ return null;
95
+ const { code: status, message } = transportFailure.data;
96
+ if (!HOST_REFUSAL_STATUSES.has(status) && status !== 400)
97
+ return null;
98
+ const start = message.indexOf('{');
99
+ if (start < 0)
100
+ return null;
101
+ const decoded = decodeHostRefusalBody(message.slice(start));
102
+ if ('failure' in decoded) {
103
+ log.debug('host refusal body is not JSON; not classified', { failure: valueFreeFailure(decoded.failure) });
104
+ return null;
105
+ }
106
+ return decoded.response ? { status, ...decoded.response } : null;
107
+ }
108
+ /**
109
+ * The host is up but no longer holds this relay's HTTP session: it answered
110
+ * 404, the protocol's "start a new session" (a session it reclaimed), or 400
111
+ * "No valid session ID provided" (an id it never knew). That is not a lost
112
+ * host: the relay opens a fresh session, replays its state and resends the
113
+ * calls in flight.
114
+ */
115
+ export function isHostSessionExpired(error) {
116
+ // The 400 envelope carries `id: null`, which the refusal decoder (request ids only) does not accept.
117
+ const parsed = HostSessionExpiredSchema.safeParse(error);
118
+ return parsed.success && (parsed.data.code === 404 || /no valid session id/i.test(parsed.data.message));
119
+ }
120
+ const HostSessionExpiredSchema = z.object({ code: z.union([z.literal(400), z.literal(404)]), message: z.string() });
121
+ function requestRefusal(response, beforeInitialize) {
122
+ if (!response)
123
+ return null;
124
+ // A request sent before initialization has no HTTP session to invalidate.
125
+ // Its correlated 400 is a protocol answer; established-session 400s,
126
+ // authentication failures, and evicted sessions keep their loss behavior.
127
+ return HOST_REFUSAL_STATUSES.has(response.status) || (response.status === 400 && beforeInitialize)
128
+ ? response.error
129
+ : null;
130
+ }
131
+ /** The host's protocol refusal, or null when the transport session was lost. */
132
+ export function hostRefusalFromTransportError(error, log, beforeInitialize = false) {
133
+ return requestRefusal(readHostProtocolError(CaughtFailureSchema.parse(error), log), beforeInitialize);
134
+ }
135
+ const HOST_LOSS_TEXT = {
136
+ restarted: {
137
+ error: 'The shared Vibes MCP host restarted before this request completed. Retry it.',
138
+ notice: 'The shared Vibes MCP host restarted; this relay is reconnecting.',
139
+ reason: 'host-restarted'
140
+ },
141
+ unresponsive: {
142
+ error: 'The shared Vibes MCP host stopped answering before this request completed; it did not restart. Retry it.',
143
+ notice: 'The shared Vibes MCP host stopped answering (same process, no restart); this relay is reconnecting.',
144
+ reason: 'host-unresponsive'
145
+ }
146
+ };
147
+ function hostLossError(id, loss) {
148
+ return {
149
+ jsonrpc: '2.0',
150
+ id,
151
+ error: {
152
+ code: -32000,
153
+ message: HOST_LOSS_TEXT[loss].error,
154
+ data: { retryable: true, reason: HOST_LOSS_TEXT[loss].reason }
155
+ }
156
+ };
157
+ }
158
+ /** {@link observeHostLoss} for a handler that must finish its loss handling: a claim read failure is returned, never thrown. */
159
+ async function observeHostLossOrFailure(options, attached) {
160
+ try {
161
+ return { loss: await observeHostLoss(options, attached) };
162
+ }
163
+ catch (error) {
164
+ return { loss: 'unresponsive', error: CaughtFailureSchema.parse(error) };
165
+ }
166
+ }
167
+ /**
168
+ * Whether the host the relay attached to (`attached`, its claim at attach) was
169
+ * replaced: the advertised claim is gone or names another pid or start. With
170
+ * no claim at attach (an explicit host URL) a replacement cannot be observed.
171
+ */
172
+ export async function observeHostLoss(options, attached) {
173
+ if (!attached)
174
+ return 'unresponsive';
175
+ const observed = await resolveHostUrl(options);
176
+ return !observed.url || observed.claim?.pid !== attached.pid || observed.claim?.startedAt !== attached.startedAt
177
+ ? 'restarted'
178
+ : 'unresponsive';
179
+ }
180
+ function isInitializedNotification(message) {
181
+ return ('method' in message ? message.method : undefined) === 'notifications/initialized';
182
+ }
183
+ function advertisesToolListChanges(message) {
184
+ if (!('result' in message))
185
+ return false;
186
+ const parsed = z.object({ capabilities: z.object({ tools: z.object({ listChanged: z.boolean().optional() }).optional() }).optional() }).safeParse(message.result);
187
+ return parsed.success && parsed.data.capabilities?.tools?.listChanged === true;
188
+ }
189
+ function isSessionBindingRequest(message) {
190
+ if (!('method' in message) || message.method !== 'tools/call' || message.params?.name !== 'session')
191
+ return false;
192
+ return z.union([
193
+ z.object({ action: z.literal('init') }),
194
+ z.object({ action: z.literal('status'), sessionId: z.string(), participantId: z.string() })
195
+ ]).safeParse(message.params.arguments).success;
196
+ }
197
+ function readDurableSessionBindReceipt(message) {
198
+ if (!('result' in message))
199
+ return undefined;
200
+ const parsed = z.object({
201
+ sessionId: z.string().optional(),
202
+ session: z.object({ id: z.string().optional() }).optional(),
203
+ participant: z.object({ id: z.string().optional() }).optional()
204
+ }).safeParse(message.result.structuredContent);
205
+ if (!parsed.success)
206
+ return undefined;
207
+ const structured = parsed.data;
208
+ const sessionId = structured?.sessionId ?? structured?.session?.id;
209
+ const participantId = structured?.participant?.id;
210
+ return sessionId !== undefined && participantId !== undefined
211
+ ? { sessionId, participantId }
212
+ : undefined;
213
+ }
214
+ function durableSessionRestoreRequest(receipt) {
215
+ return {
216
+ jsonrpc: '2.0',
217
+ id: 'replaced-by-relay',
218
+ method: 'tools/call',
219
+ params: {
220
+ name: 'session',
221
+ arguments: {
222
+ action: 'status',
223
+ sessionId: receipt.sessionId,
224
+ participantId: receipt.participantId,
225
+ receiptOnly: true
226
+ }
227
+ }
228
+ };
229
+ }
230
+ function isErrorResponse(message) {
231
+ return 'error' in message;
232
+ }
233
+ function isToolErrorResponse(message) {
234
+ return 'result' in message && message.result.isError === true;
235
+ }
236
+ function relayStateNotification(level, message) {
237
+ return {
238
+ jsonrpc: '2.0',
239
+ method: 'notifications/message',
240
+ params: { level, logger: 'vibes.shared-relay', data: message }
241
+ };
242
+ }
243
+ function toolsListChangedNotification() {
244
+ return { jsonrpc: '2.0', method: 'notifications/tools/list_changed' };
245
+ }
246
+ /**
247
+ * Whether the host at `mcpUrl` answers `/health` within `timeoutMs`. An
248
+ * unanswered or failing probe is `false`: this is a liveness sample for a
249
+ * pending call, never a verdict on its own (a slow event loop is not a dead
250
+ * host), and every non-answer is logged with its cause.
251
+ */
252
+ async function probeMcpHost(lifecycle, mcpUrl, timeoutMs) {
253
+ const healthUrl = new URL(mcpUrl);
254
+ healthUrl.pathname = '/health';
255
+ healthUrl.search = '';
256
+ return await lifecycle.wait(async (signal) => {
257
+ const response = await fetch(healthUrl, { method: 'GET', headers: { accept: 'application/json' }, signal });
258
+ if (!response.ok)
259
+ return false;
260
+ const body = z.object({ ok: z.boolean() }).safeParse(await response.json());
261
+ return body.success && body.data.ok;
262
+ }, { timeoutMs, reason: 'mcp-shared-host-health-probe' });
263
+ }
264
+ /** A failed observation remains a sample, never proof that the host died. */
265
+ async function healthProbeOutcome(probe) {
266
+ try {
267
+ return { healthy: await probe() };
268
+ }
269
+ catch (cause) {
270
+ return { healthy: false, failure: CaughtFailureSchema.parse(cause) };
271
+ }
272
+ }
273
+ /**
274
+ * Decide whether a host is available, WITHOUT connecting.
275
+ *
276
+ * Split out from the connect path so the decision is testable on its own — the
277
+ * interesting cases here (dead pid, wrong workspace, no claim) are precisely the
278
+ * ones that are awkward to stage against a live socket.
279
+ */
280
+ /**
281
+ * The catalog id a client's identity resolves to, through the same resolver
282
+ * the host used when it wrote its claim. `undefined` when the client named no
283
+ * preset — it takes whatever the host serves.
284
+ */
285
+ export function canonicalWantedPreset(identity, customPresets) {
286
+ if (identity.preset === undefined)
287
+ return undefined;
288
+ return resolveMcpPreset(identity.client, identity.preset, customPresets).presetId;
289
+ }
290
+ /** A host claim's catalog id in canonical form (`mcp:`-prefixed), or undefined. */
291
+ export function canonicalServedPreset(presetId) {
292
+ const raw = presetId?.trim();
293
+ if (!raw)
294
+ return undefined;
295
+ return raw.startsWith('mcp:') || raw === 'mcp' ? raw : `mcp:${raw}`;
296
+ }
297
+ export async function resolveHostUrl(options) {
298
+ if (options.hostUrl)
299
+ return { relayed: false, url: options.hostUrl };
300
+ const lookup = await (options.lookup ?? ((root) => readHostClaim(options.runtime, root, options.roots, options.processProbe, options.logger)))(options.workspaceRoot);
301
+ if (lookup.status === 'none')
302
+ return { relayed: false, reason: 'no-host' };
303
+ if (lookup.status === 'stale') {
304
+ return { relayed: false, reason: 'stale-host', staleReason: lookup.reason, claim: lookup.claim };
305
+ }
306
+ // The host bakes ONE catalog at boot. Attaching a client that asked for a
307
+ // different one does not fail loudly — it hands that agent the host's tools
308
+ // and looks like success. Refuse, and let the caller run isolated with a
309
+ // reason it can state. Remove this only when the host scopes catalogs per
310
+ // connection; until then a match is the only case we can serve honestly.
311
+ //
312
+ // Compare CANONICAL catalog ids. The host writes the id tool-selection
313
+ // resolved for it (`mcp:full`); a client carries the raw flag value
314
+ // (`--client full` → preset `full`). Comparing those strings directly never
315
+ // matched, so every client booted its own ~6GB runtime while the claim said
316
+ // the host was serving exactly what they asked for.
317
+ const customPresets = runtimePurposePresets(options.runtime, await loadPurposePresetOverrides(options.runtime));
318
+ const wanted = canonicalWantedPreset(options.identity, customPresets);
319
+ const served = canonicalServedPreset(lookup.claim.presetId);
320
+ const wantedKey = mcpCatalogAttachKey(wanted, customPresets);
321
+ // New hosts advertise their selected identity. A relay cannot establish it
322
+ // by resolving the host's id through its own, possibly different composition.
323
+ let servedKey = lookup.claim.catalogAttachKey;
324
+ if (servedKey === undefined) {
325
+ if (lookup.claim.presetPolicyKey === purposePresetPolicyKey(customPresets)) {
326
+ servedKey = mcpCatalogAttachKey(served, customPresets);
327
+ }
328
+ else if (lookup.claim.presetPolicyKey === undefined && wantedKey === mcpCatalogAttachKey(wanted)) {
329
+ servedKey = mcpCatalogAttachKey(served);
330
+ }
331
+ }
332
+ if (wanted !== undefined && wantedKey !== servedKey) {
333
+ return {
334
+ relayed: false,
335
+ reason: 'identity-mismatch',
336
+ // `hostPreset` stays undefined when the claim carries no presetId: that
337
+ // host predates catalog advertising, and the message layer says so
338
+ // rather than inventing a catalog it disagrees with.
339
+ mismatch: { hostPreset: served ?? lookup.claim.presetId, wantedPreset: wanted },
340
+ claim: lookup.claim
341
+ };
342
+ }
343
+ return { relayed: false, url: lookup.url, claim: lookup.claim };
344
+ }
345
+ /**
346
+ * The `Authorization` this relay presents to the shared host it found through
347
+ * its claim (RC08 U7): the host start's local attestation, read fresh on every
348
+ * attach so a restarted host's rotated secret is picked up. No claim is no
349
+ * attestation (the host then verifies no identity for this connection); an
350
+ * insecure or invalid claim fails the attach. An explicit `hostUrl` is never
351
+ * sent the secret: it is not the host the claim names.
352
+ */
353
+ async function relayAuthorization(options) {
354
+ if (options.hostUrl)
355
+ return undefined;
356
+ if (options.readAttestation)
357
+ return options.readAttestation();
358
+ const observed = await readLocalAttestation(mcpRelayAttestationPath(options.workspaceRoot, options.roots), options.runtime)
359
+ .then((token) => ({ token }), (cause) => ({ failure: CaughtFailureSchema.parse(cause) }));
360
+ if ('failure' in observed) {
361
+ if (isCapabilityError(observed.failure) && observed.failure.reason === LOCAL_ATTESTATION_CLAIM_MISSING)
362
+ return undefined;
363
+ throw observed.failure;
364
+ }
365
+ return `Bearer ${revealLocalAttestation(observed.token)}`;
366
+ }
367
+ /**
368
+ * The identity a relayed client asked for, as `initialize` params.
369
+ *
370
+ * The host serves many clients, and they do NOT all want the same tools — on
371
+ * this machine eight clients asked for `full` and one asked for `cursor`. If the
372
+ * relay does not carry identity, every client silently inherits whichever
373
+ * catalog the host happened to boot with. That is not a crash; it is an agent
374
+ * quietly holding the wrong tools, which is worse.
375
+ */
376
+ export function relayInitializeMeta(identity) {
377
+ const meta = connectionIdentityMeta({
378
+ agentId: identity.agent, harness: identity.harness,
379
+ harnessSessionId: identity.harnessSessionId, agentSystemId: identity.agentSystemId,
380
+ projectIds: identity.projectIds, home: identity.home
381
+ });
382
+ if (identity.preset)
383
+ meta['vibes/preset'] = identity.preset;
384
+ if (identity.client)
385
+ meta['vibes/client'] = identity.client;
386
+ if (identity.toolPatterns?.length)
387
+ meta['vibes/tools'] = identity.toolPatterns;
388
+ return meta;
389
+ }
390
+ /** Stamp relay identity onto an outbound `initialize` request, in place. */
391
+ export function applyRelayIdentity(message, identity) {
392
+ if (!('method' in message) || message.method !== 'initialize')
393
+ return message;
394
+ const candidate = message;
395
+ const meta = relayInitializeMeta(identity);
396
+ if (Object.keys(meta).length === 0)
397
+ return message;
398
+ candidate.params = candidate.params ?? {};
399
+ const existing = candidate.params._meta ?? {};
400
+ // Capability/catalog hints remain client-overridable, but the stable body
401
+ // identity comes from the process environment that launched this relay. A
402
+ // stale authored value cannot retarget durable Session custody.
403
+ candidate.params._meta = { ...meta, ...existing };
404
+ if (identity.harnessSessionId)
405
+ candidate.params._meta['vibes/harnessSessionId'] = identity.harnessSessionId;
406
+ return message;
407
+ }
408
+ /**
409
+ * Attach this process's stdio to the shared host and pipe until it closes.
410
+ *
411
+ * Returns `{relayed:false}` when there is no usable host. It NEVER throws for a
412
+ * missing host: it reports WHAT it found (`reason`, `staleReason`, `mismatch`,
413
+ * `claim`, `url`) and leaves the decision to the caller. Today that caller is
414
+ * `startStdioMcp`, which turns the receipt into a specific error — never a
415
+ * silent private runtime — so every field here is a sentence someone reads.
416
+ */
417
+ export async function relayStdioToHost(options) {
418
+ const log = options.logger;
419
+ const resolved = await resolveHostUrl(options);
420
+ if (!resolved.url)
421
+ return resolved;
422
+ const customPresets = runtimePurposePresets(options.runtime, await loadPurposePresetOverrides(options.runtime));
423
+ // GUARDED. These are dynamic imports of SDK transports, and the client-side
424
+ // transport is used NOWHERE else in shipped code — only in tests, which are
425
+ // not bundled. On a compiled consumer binary there is therefore no proof the
426
+ // module resolves at all. An unguarded throw here would propagate out of the
427
+ // serve command and take down an MCP server that was perfectly capable of
428
+ // running isolated.
429
+ // Admit the EOF owner before any SDK transport can acquire a session.
430
+ const clientInput = options.clientInput;
431
+ const stdinEvents = clientInput || options.transports ? undefined : await getRuntimeStdinEvents(options.runtime);
432
+ let transports = options.transports;
433
+ if (!transports) {
434
+ try {
435
+ const [server, client] = await Promise.all([
436
+ import('@modelcontextprotocol/sdk/server/stdio.js'),
437
+ import('@modelcontextprotocol/sdk/client/streamableHttp.js')
438
+ ]);
439
+ transports = {
440
+ createUpstream: (url, authorization) => new client.StreamableHTTPClientTransport(url, authorization ? { requestInit: { headers: { authorization } } } : undefined),
441
+ createDownstream: () => new server.StdioServerTransport()
442
+ };
443
+ }
444
+ catch (error) {
445
+ const failure = CaughtFailureSchema.parse(error);
446
+ log.debug('relay transports unavailable', { failure: valueFreeFailure(failure) });
447
+ return {
448
+ relayed: false,
449
+ reason: 'transport-unavailable',
450
+ failure,
451
+ url: resolved.url,
452
+ claim: resolved.claim
453
+ };
454
+ }
455
+ }
456
+ const initialUpstream = transports.createUpstream(new URL(resolved.url), await relayAuthorization(options));
457
+ try {
458
+ await initialUpstream.start();
459
+ }
460
+ catch (error) {
461
+ const failure = CaughtFailureSchema.parse(error);
462
+ log.debug('relay host claim verified but the socket did not answer', { url: resolved.url, failure: valueFreeFailure(failure) });
463
+ const cleanup = await closeUpstream(initialUpstream).catch((cause) => CaughtFailureSchema.parse(cause));
464
+ const attachmentFailure = cleanup ? new AggregateError([failure, cleanup], 'The initial MCP attachment and cleanup failed.', { cause: failure }) : failure;
465
+ // The claim verified but the socket did not answer — the host is shutting
466
+ // down, or wedged. Report which process, rather than strand the client.
467
+ return { relayed: false, reason: 'unreachable', failure: attachmentFailure, url: resolved.url, claim: resolved.claim };
468
+ }
469
+ const downstream = transports.createDownstream();
470
+ let upstream = initialUpstream;
471
+ let upstreamUrl = new URL(resolved.url);
472
+ let upstreamClaim = resolved.claim;
473
+ /** How the host was last lost: what a request arriving mid-reconnect is told. */
474
+ let lastHostLoss = 'restarted';
475
+ /** Set while the relay re-opens a session the host evicted; calls arriving meanwhile wait on it. */
476
+ let reinitializing;
477
+ let upstreamReady = true;
478
+ let upstreamGeneration = 0;
479
+ let internalSequence = 0;
480
+ let hostRequestSequence = 0;
481
+ let initializeRequest;
482
+ let initializedNotification;
483
+ let sessionBindReceipt;
484
+ let bindingReceiptMissing = false;
485
+ let supportsToolListChanges = false;
486
+ let protocolInitialized = false;
487
+ let nameClient = options.identity.harness ?? options.identity.client;
488
+ let toolNames = new Map();
489
+ const hostRequestIdPrefix = `vibes-relay-host-${crypto.randomUUID()}-`;
490
+ const healthProbeIntervalMs = options.reconnect?.healthProbeIntervalMs ?? 10_000;
491
+ const healthProbeTimeoutMs = options.reconnect?.healthProbeTimeoutMs ?? 2_000;
492
+ const bindRestoreJitterWindowMs = options.reconnect?.bindRestoreJitterWindowMs ?? 60_000;
493
+ const recoveryRandom = options.reconnect?.random ?? Math.random;
494
+ const pendingClientRequests = new Map();
495
+ /** A failing health probe is reported once per relay, not on every interval. */
496
+ let probeFailureReported = false;
497
+ const pendingHostRequests = new Map();
498
+ const internalRequests = new Map();
499
+ const rejectInternal = (reason) => {
500
+ for (const waiter of internalRequests.values()) {
501
+ waiter.timer();
502
+ waiter.reject(reason);
503
+ }
504
+ internalRequests.clear();
505
+ };
506
+ const retireHostRequests = (candidate) => {
507
+ for (const [id, pending] of pendingHostRequests) {
508
+ if (pending.upstream === candidate)
509
+ pendingHostRequests.delete(id);
510
+ }
511
+ };
512
+ const isRelayHostRequestId = (id) => {
513
+ const parsed = z.string().safeParse(id);
514
+ return parsed.success && parsed.data.startsWith(hostRequestIdPrefix);
515
+ };
516
+ // Assigned at the bottom of this function, once the client pipe is watched.
517
+ let detachClientInputWatch = () => { };
518
+ /** Owns terminal settlement and failure routing for each upstream generation. */
519
+ class RelayFailureOwner {
520
+ closing = false;
521
+ shutdownFailure;
522
+ clientCancellation = new CapabilityError('The downstream MCP client disconnected.', 'CANCELLED', { reason: 'mcp-client-disconnected' });
523
+ signalDone = () => { };
524
+ signalFailed = () => { };
525
+ done = new Promise((resolve, reject) => {
526
+ this.signalDone = resolve;
527
+ this.signalFailed = reject;
528
+ });
529
+ generations = new WeakMap();
530
+ close() {
531
+ if (this.closing)
532
+ return;
533
+ this.closing = true;
534
+ detachClientInputWatch();
535
+ rejectInternal(this.clientCancellation);
536
+ for (const pending of pendingClientRequests.values())
537
+ pending.timer();
538
+ pendingClientRequests.clear();
539
+ pendingHostRequests.clear();
540
+ const current = upstream;
541
+ upstream = undefined;
542
+ upstreamUrl = undefined;
543
+ upstreamReady = false;
544
+ const connectorOwned = connector.connection;
545
+ const disposed = connector.dispose();
546
+ const upstreamClosed = current && current !== connectorOwned ? closeUpstream(current) : Promise.resolve();
547
+ const downstreamClosed = downstream.close();
548
+ void Promise.allSettled([disposed, upstreamClosed, downstreamClosed]).then((outcomes) => {
549
+ const failures = outcomes.flatMap((outcome) => outcome.status === 'rejected' ? [CaughtFailureSchema.parse(outcome.reason)] : [])
550
+ .filter((failure) => failure !== this.clientCancellation);
551
+ if (this.shutdownFailure)
552
+ failures.push(this.shutdownFailure);
553
+ if (failures.length)
554
+ this.signalFailed(new AggregateError(failures, 'Closing the MCP relay failed.'));
555
+ else
556
+ this.signalDone();
557
+ });
558
+ }
559
+ fail(cause) {
560
+ if (this.closing)
561
+ return;
562
+ this.shutdownFailure ??= cause;
563
+ log.warn('Closing the client relay after a transport failure', { failure: valueFreeFailure(cause) });
564
+ this.close();
565
+ }
566
+ bindDownstream() {
567
+ downstream.onerror = (cause) => this.fail(cause);
568
+ }
569
+ bindUpstream(candidate, generation, lost, expired) {
570
+ this.generations.set(candidate, { generation, lost, expired });
571
+ candidate.onerror = (cause) => this.transportFailed(candidate, cause);
572
+ }
573
+ transportFailed(candidate, cause) {
574
+ const registered = this.generations.get(candidate);
575
+ if (!registered || this.closing || upstream !== candidate || upstreamGeneration !== registered.generation)
576
+ return;
577
+ const response = readHostProtocolError(cause, log);
578
+ const pending = response?.id !== undefined ? pendingClientRequests.get(response.id) : undefined;
579
+ // The SDK reports each failed send here before rejecting the send.
580
+ // A correlated refusal remains with that send's client-response owner.
581
+ if (requestRefusal(response, pending?.beforeInitialize === true))
582
+ return;
583
+ if (isHostSessionExpired(cause))
584
+ registered.expired();
585
+ else
586
+ registered.lost(cause);
587
+ }
588
+ }
589
+ const failureOwner = new RelayFailureOwner();
590
+ const done = failureOwner.done;
591
+ const closeRelay = () => failureOwner.close();
592
+ const closeAfterFailure = (cause) => failureOwner.fail(cause);
593
+ const failInterruptedClientRequests = (loss) => {
594
+ for (const [id, pending] of pendingClientRequests) {
595
+ pending.timer();
596
+ void downstream.send(hostLossError(id, loss)).catch((cause) => CaughtFailureSchema.parse(cause)).then((failure) => { if (failure)
597
+ closeAfterFailure(failure); });
598
+ }
599
+ pendingClientRequests.clear();
600
+ };
601
+ const sendInternal = async (message) => {
602
+ const target = upstream;
603
+ if (!target)
604
+ throw new Error('The shared MCP host is reconnecting.');
605
+ const id = `vibes-relay-${++internalSequence}`;
606
+ const request = cloneMessage(message);
607
+ if (!('id' in request))
608
+ throw new CapabilityError('Relay initialization must carry a request id', 'INVALID_INPUT');
609
+ request.id = id;
610
+ return await new Promise((resolve, reject) => {
611
+ const timer = options.lifecycle.timeout(() => {
612
+ internalRequests.delete(id);
613
+ reject(new Error('The shared MCP host did not answer relay reinitialization.'));
614
+ }, 15_000);
615
+ internalRequests.set(id, { resolve, reject, timer });
616
+ void target.send(request).catch((cause) => CaughtFailureSchema.parse(cause)).then((error) => {
617
+ if (!error)
618
+ return;
619
+ const waiter = internalRequests.get(id);
620
+ if (!waiter) {
621
+ log.debug('Retired relay reinitialization send failed', { failure: valueFreeFailure(error) });
622
+ return;
623
+ }
624
+ waiter.timer();
625
+ internalRequests.delete(id);
626
+ waiter.reject(error instanceof Error ? error : new CapabilityError('Relay reinitialization send failed.', 'UNAVAILABLE', { cause: error }));
627
+ });
628
+ });
629
+ };
630
+ const forwardHostMessage = (message, candidate, generation) => {
631
+ const responseId = messageResponseId(message);
632
+ if (responseId !== undefined) {
633
+ const internal = internalRequests.get(responseId);
634
+ if (internal) {
635
+ internal.timer();
636
+ internalRequests.delete(responseId);
637
+ if (isErrorResponse(message)) {
638
+ internal.reject(new Error('The shared MCP host rejected relay reinitialization.'));
639
+ }
640
+ else {
641
+ internal.resolve(message);
642
+ }
643
+ return;
644
+ }
645
+ const pending = pendingClientRequests.get(responseId);
646
+ if (pending) {
647
+ pending.timer();
648
+ pendingClientRequests.delete(responseId);
649
+ const request = pending.request;
650
+ if ('method' in request && request.method === 'tools/list' && 'result' in message && harnessNeedsSafeMcpNames(nameClient, customPresets)) {
651
+ const hostResult = message.result;
652
+ const project = () => {
653
+ try {
654
+ const list = ListToolsResultSchema.parse(hostResult);
655
+ const page = ListToolsRequestSchema.parse(request);
656
+ const previousNames = page.params?.cursor ? [...toolNames.values()] : [];
657
+ const names = createMcpWireNameMap(new Set([...previousNames, ...list.tools.map(tool => tool.name)]), nameClient, customPresets);
658
+ return { names, result: { ...hostResult, tools: list.tools.map(tool => ({ ...tool, name: mcpWireToolName(tool.name, nameClient, customPresets) })) } };
659
+ }
660
+ catch (cause) {
661
+ return { failure: CaughtFailureSchema.parse(cause) };
662
+ }
663
+ };
664
+ const projection = project();
665
+ if ('failure' in projection) {
666
+ log.error('Shared MCP host tool-name projection failed', { failure: valueFreeFailure(projection.failure) });
667
+ void downstream.send({ jsonrpc: '2.0', id: responseId, error: { code: ErrorCode.InternalError, message: 'The shared MCP host advertised invalid or ambiguous tool metadata.' } })
668
+ .catch((cause) => CaughtFailureSchema.parse(cause)).then((failure) => { if (failure)
669
+ closeAfterFailure(failure); });
670
+ return;
671
+ }
672
+ message = { ...message, result: projection.result };
673
+ toolNames = projection.names;
674
+ }
675
+ if (('method' in request ? request.method : undefined) === 'initialize') {
676
+ supportsToolListChanges = advertisesToolListChanges(message);
677
+ protocolInitialized = !isErrorResponse(message);
678
+ }
679
+ if (isSessionBindingRequest(request) &&
680
+ !isErrorResponse(message) &&
681
+ !isToolErrorResponse(message)) {
682
+ const receipt = readDurableSessionBindReceipt(message);
683
+ sessionBindReceipt = receipt;
684
+ bindingReceiptMissing = !receipt;
685
+ }
686
+ }
687
+ }
688
+ const requestId = messageRequestId(message);
689
+ if (requestId !== undefined) {
690
+ // JSON-RPC ids are unique only within one endpoint generation. A host
691
+ // restart can reuse an id while the downstream client is still answering
692
+ // the old request, so never expose host ids directly across the relay.
693
+ const relayRequestId = `${hostRequestIdPrefix}${generation}-${++hostRequestSequence}`;
694
+ pendingHostRequests.set(relayRequestId, {
695
+ generation,
696
+ upstream: candidate,
697
+ originalId: requestId
698
+ });
699
+ const relayedRequest = cloneMessage(message);
700
+ if (!('id' in relayedRequest))
701
+ throw new CapabilityError('Host request must carry a request id', 'INVALID_INPUT');
702
+ relayedRequest.id = relayRequestId;
703
+ void downstream.send(relayedRequest).catch((cause) => CaughtFailureSchema.parse(cause)).then((failure) => { if (failure)
704
+ closeAfterFailure(failure); });
705
+ return;
706
+ }
707
+ const cancelledId = cancellationRequestId(message);
708
+ if (cancelledId !== undefined) {
709
+ for (const [relayRequestId, pending] of pendingHostRequests) {
710
+ if (pending.upstream === candidate &&
711
+ pending.generation === generation &&
712
+ pending.originalId === cancelledId) {
713
+ void downstream
714
+ .send(withCancellationRequestId(message, relayRequestId))
715
+ .catch((cause) => CaughtFailureSchema.parse(cause)).then((failure) => { if (failure)
716
+ closeAfterFailure(failure); });
717
+ return;
718
+ }
719
+ }
720
+ // The request already completed or belongs to a retired generation.
721
+ // Forwarding the host-local id could cancel an unrelated client request.
722
+ return;
723
+ }
724
+ void downstream.send(message).catch((cause) => CaughtFailureSchema.parse(cause)).then((failure) => { if (failure)
725
+ closeAfterFailure(failure); });
726
+ };
727
+ const installUpstreamHandlers = (candidate, generation) => {
728
+ candidate.onmessage = (message) => {
729
+ // A response can already be queued when close/error fires. Once that
730
+ // generation is retired, forwarding it would double-answer a request:
731
+ // first host-restarted, then a late success from the dead host.
732
+ if (failureOwner.closing || upstream !== candidate || upstreamGeneration !== generation)
733
+ return;
734
+ forwardHostMessage(message, candidate, generation);
735
+ };
736
+ const lost = (failure) => {
737
+ if (failureOwner.closing || upstream !== candidate || upstreamGeneration !== generation)
738
+ return;
739
+ if (failure)
740
+ log.warn('Shared MCP host transport failed; retiring its connection', { failure: valueFreeFailure(failure) });
741
+ // Retire the lost generation at once, so nothing more is forwarded to it.
742
+ retireHostRequests(candidate);
743
+ toolNames.clear();
744
+ upstream = undefined;
745
+ upstreamUrl = undefined;
746
+ upstreamReady = false;
747
+ upstreamLost.get(candidate)?.();
748
+ // The connector closes a connection it opened when it re-attaches.
749
+ if (connector.connection !== candidate)
750
+ void closeUpstream(candidate).catch((cause) => CaughtFailureSchema.parse(cause)).then((failure) => { if (failure)
751
+ closeAfterFailure(failure); });
752
+ // Which loss it was is read from the claim (an async read), then reported.
753
+ void (async () => {
754
+ const observed = await observeHostLossOrFailure(options, upstreamClaim);
755
+ // An unreadable claim proves no restart; the loss is still handled below.
756
+ if (observed.error !== undefined)
757
+ log.warn('Could not read the shared MCP host claim after losing its connection', { failure: valueFreeFailure(observed.error) });
758
+ const loss = observed.loss;
759
+ lastHostLoss = loss;
760
+ rejectInternal(new Error(HOST_LOSS_TEXT[loss].notice));
761
+ failInterruptedClientRequests(loss);
762
+ void downstream
763
+ .send(relayStateNotification('warning', HOST_LOSS_TEXT[loss].notice))
764
+ .catch((cause) => CaughtFailureSchema.parse(cause)).then((failure) => { if (failure)
765
+ closeAfterFailure(failure); });
766
+ if (failureOwner.closing)
767
+ return;
768
+ // One reconnect: concurrent losses join the connector's in-flight attempt.
769
+ await connector.connect();
770
+ })().catch((cause) => CaughtFailureSchema.parse(cause)).then((error) => {
771
+ if (!error)
772
+ return;
773
+ if (failureOwner.closing)
774
+ return;
775
+ log.warn('Shared MCP host relay stopped reconnecting', { failure: valueFreeFailure(CaughtFailureSchema.parse(error)) });
776
+ });
777
+ };
778
+ /**
779
+ * The host dropped this relay's session but is serving: open a fresh
780
+ * session (re-initialize and replay), then resend every call that was in
781
+ * flight. Calls that arrive meanwhile wait for it. Only a failed
782
+ * re-initialization fails them.
783
+ */
784
+ /** The fresh session's attach: null once it serves, else the failure, returned for the caller to handle. */
785
+ const connectOutcome = async () => {
786
+ try {
787
+ await connector.connect();
788
+ return null;
789
+ }
790
+ catch (error) {
791
+ return CaughtFailureSchema.parse(error);
792
+ }
793
+ };
794
+ const sessionExpired = () => {
795
+ if (failureOwner.closing || upstream !== candidate || upstreamGeneration !== generation)
796
+ return;
797
+ // The calls in flight stay pending (their health probes disarmed) until the fresh session answers or fails.
798
+ for (const pending of pendingClientRequests.values())
799
+ pending.timer();
800
+ const held = [...pendingClientRequests.keys()];
801
+ retireHostRequests(candidate);
802
+ toolNames.clear();
803
+ upstream = undefined;
804
+ upstreamUrl = undefined;
805
+ upstreamReady = false;
806
+ rejectInternal(new Error('The shared MCP host no longer holds this relay session.'));
807
+ upstreamLost.get(candidate)?.();
808
+ if (connector.connection !== candidate)
809
+ void closeUpstream(candidate).catch((cause) => CaughtFailureSchema.parse(cause)).then((failure) => { if (failure)
810
+ closeAfterFailure(failure); });
811
+ log.info('Shared MCP host no longer holds this relay session; re-initializing', { inFlight: held.length });
812
+ const reattach = async () => {
813
+ const failure = await connectOutcome();
814
+ reinitializing = undefined;
815
+ if (failure) {
816
+ if (failureOwner.closing)
817
+ return;
818
+ // Re-initialization failed: the held calls fail now, typed, and the failure is logged.
819
+ log.warn('Shared MCP host relay could not re-initialize its session', { error: failure });
820
+ lastHostLoss = 'unresponsive';
821
+ failInterruptedClientRequests('unresponsive');
822
+ return;
823
+ }
824
+ for (const id of held) {
825
+ const pending = pendingClientRequests.get(id);
826
+ if (!pending)
827
+ continue;
828
+ pendingClientRequests.delete(id);
829
+ downstream.onmessage?.(pending.request);
830
+ }
831
+ };
832
+ reinitializing = reattach();
833
+ };
834
+ candidate.onclose = lost;
835
+ failureOwner.bindUpstream(candidate, generation, lost, sessionExpired);
836
+ };
837
+ const replayClientState = async (signal) => {
838
+ if (!initializeRequest)
839
+ return;
840
+ const initializeResponse = await sendInternal(applyRelayIdentity(cloneMessage(initializeRequest), options.identity));
841
+ supportsToolListChanges = advertisesToolListChanges(initializeResponse);
842
+ if (initializedNotification) {
843
+ await upstream?.send(cloneMessage(initializedNotification));
844
+ }
845
+ if (bindingReceiptMissing) {
846
+ throw new Error('The prior session bind did not return a durable replay receipt.');
847
+ }
848
+ let bindingRestored = false;
849
+ if (sessionBindReceipt) {
850
+ // A host restart wakes every long-lived relay at once. Initialize is
851
+ // intentionally cheap, but restoring a durable Session binding crosses
852
+ // participant/session/presence storage. Without admission jitter, 100+
853
+ // bodies hit SQLite in the same tick, starve /health, and make PM recycle
854
+ // the otherwise-correct host forever. Spread only the restore — the
855
+ // original authored `session init` is never replayed.
856
+ const restoreDelayMs = Math.floor(Math.max(0, Math.min(1, recoveryRandom())) * bindRestoreJitterWindowMs);
857
+ await options.lifecycle.delay(restoreDelayMs, signal);
858
+ if (failureOwner.closing || !upstream)
859
+ return;
860
+ const bindResponse = await sendInternal(durableSessionRestoreRequest(sessionBindReceipt));
861
+ if (isToolErrorResponse(bindResponse)) {
862
+ // The host is UP — it just answered a tool call — and it refuses
863
+ // this receipt: the durable (participant, session) pair no longer
864
+ // validates there (session deleted, participant unmapped, session
865
+ // archived). Retrying the same receipt forever wedges the relay
866
+ // behind host-restarted errors — including for `session init`,
867
+ // the one call that could mint a clean pair. Drop the stale
868
+ // receipt, tell the body, and serve unbound; the next init
869
+ // rebinds durably.
870
+ sessionBindReceipt = undefined;
871
+ bindingReceiptMissing = false;
872
+ await downstream
873
+ .send(relayStateNotification('warning', 'The shared Vibes MCP host no longer recognizes this session binding; run session { action: "init" } to mint a fresh one.'))
874
+ .catch((cause) => CaughtFailureSchema.parse(cause)).then((failure) => { if (failure)
875
+ closeAfterFailure(failure); });
876
+ }
877
+ const restored = sessionBindReceipt
878
+ ? readDurableSessionBindReceipt(bindResponse)
879
+ : undefined;
880
+ if (sessionBindReceipt) {
881
+ if (!restored ||
882
+ restored.sessionId !== sessionBindReceipt.sessionId ||
883
+ restored.participantId !== sessionBindReceipt.participantId) {
884
+ throw new Error('The shared MCP host restored a different session binding.');
885
+ }
886
+ bindingRestored = true;
887
+ }
888
+ }
889
+ if (supportsToolListChanges) {
890
+ await downstream.send(toolsListChangedNotification());
891
+ }
892
+ await downstream.send(relayStateNotification('info', bindingRestored
893
+ ? 'The shared Vibes MCP host reconnected; tools and session binding were restored.'
894
+ : 'The shared Vibes MCP host is available again; the prior session binding was dropped — run session { action: "init" } to rebind.'));
895
+ };
896
+ /** The transport owner joins closure and preserves its failure. */
897
+ async function closeUpstream(candidate) {
898
+ await candidate.close();
899
+ }
900
+ // A retired upstream's end, for the connector's `closed`: resolved in `lost()`.
901
+ const upstreamLost = new WeakMap();
902
+ const upstreamEnded = new WeakMap();
903
+ /**
904
+ * Re-attach after the host is lost (RC07: runtime-rpc's one reconnect state
905
+ * machine). One attempt at a time; a failed attempt holds off on the
906
+ * runtime's lifecycle, doubling to `maxDelayMs`; the relay's disposal cancels
907
+ * both. An attempt re-reads the host claim (a restarted host rotates its
908
+ * relay secret), attaches, and replays this client's state before the
909
+ * upstream serves calls again.
910
+ */
911
+ const connector = new RpcConnector({
912
+ lifecycle: options.lifecycle,
913
+ reason: 'mcp-shared-host-relay',
914
+ reconnect: {
915
+ initialDelayMs: options.reconnect?.initialDelayMs ?? 100,
916
+ maxDelayMs: options.reconnect?.maxDelayMs ?? 2_000
917
+ },
918
+ live: (candidate) => upstream === candidate,
919
+ closed: (candidate) => upstreamEnded.get(candidate) ?? Promise.resolve(),
920
+ close: closeUpstream,
921
+ open: async (signal) => {
922
+ const next = await resolveHostUrl(options);
923
+ if (!next.url)
924
+ throw new Error('No shared MCP host is advertised for this workspace yet.');
925
+ const candidate = transports.createUpstream(new URL(next.url), await relayAuthorization(options));
926
+ try {
927
+ await candidate.start();
928
+ signal.throwIfAborted();
929
+ upstream = candidate;
930
+ upstreamUrl = new URL(next.url);
931
+ upstreamClaim = next.claim;
932
+ upstreamReady = false;
933
+ upstreamGeneration += 1;
934
+ upstreamEnded.set(candidate, new Promise((resolve) => upstreamLost.set(candidate, resolve)));
935
+ installUpstreamHandlers(candidate, upstreamGeneration);
936
+ await replayClientState(signal);
937
+ // A loss during the replay (the restore jitter outlasts a cut stream)
938
+ // retired this candidate: fail the attempt so the connector dials again,
939
+ // rather than serve on a connection that is gone.
940
+ if (upstream !== candidate)
941
+ throw new Error('The shared MCP host connection closed while this relay restored its state.');
942
+ upstreamReady = true;
943
+ return candidate;
944
+ }
945
+ catch (error) {
946
+ const failure = CaughtFailureSchema.parse(error);
947
+ retireHostRequests(candidate);
948
+ if (upstream === candidate) {
949
+ upstream = undefined;
950
+ upstreamUrl = undefined;
951
+ upstreamReady = false;
952
+ }
953
+ upstreamLost.get(candidate)?.();
954
+ rejectInternal(new Error('The candidate shared MCP host could not restore relay state.'));
955
+ const cleanup = await closeUpstream(candidate).catch((cause) => CaughtFailureSchema.parse(cause));
956
+ if (cleanup)
957
+ throw new AggregateError([failure, cleanup], 'The MCP relay candidate and its cleanup failed.', { cause: failure });
958
+ throw failure;
959
+ }
960
+ }
961
+ });
962
+ // client -> host
963
+ downstream.onmessage = (message) => {
964
+ if (('method' in message ? message.method : undefined) === 'initialize') {
965
+ initializeRequest = cloneMessage(message);
966
+ if (!nameClient) {
967
+ const initialize = InitializeRequestSchema.safeParse(message);
968
+ if (initialize.success)
969
+ nameClient = initialize.data.params.clientInfo.name;
970
+ }
971
+ }
972
+ if (isInitializedNotification(message)) {
973
+ initializedNotification = cloneMessage(message);
974
+ }
975
+ const cancelledId = cancellationRequestId(message);
976
+ const hostCancellationId = z.string().safeParse(cancelledId);
977
+ if (hostCancellationId.success && isRelayHostRequestId(hostCancellationId.data)) {
978
+ const pending = pendingHostRequests.get(hostCancellationId.data);
979
+ if (!pending ||
980
+ upstream !== pending.upstream ||
981
+ upstreamGeneration !== pending.generation) {
982
+ return;
983
+ }
984
+ void pending.upstream
985
+ .send(withCancellationRequestId(message, pending.originalId))
986
+ .catch((cause) => CaughtFailureSchema.parse(cause)).then((error) => {
987
+ if (!error)
988
+ return;
989
+ log.debug('relay upstream cancellation send failed', {
990
+ error: error instanceof Error ? error.message : String(error)
991
+ });
992
+ if (upstream === pending.upstream &&
993
+ upstreamGeneration === pending.generation) {
994
+ pending.upstream.onclose?.();
995
+ }
996
+ });
997
+ return;
998
+ }
999
+ const responseId = messageResponseId(message);
1000
+ if (responseId !== undefined) {
1001
+ const hostResponseId = z.string().safeParse(responseId);
1002
+ if (hostResponseId.success) {
1003
+ const pending = pendingHostRequests.get(hostResponseId.data);
1004
+ if (pending) {
1005
+ pendingHostRequests.delete(hostResponseId.data);
1006
+ if (upstream !== pending.upstream || upstreamGeneration !== pending.generation)
1007
+ return;
1008
+ const restoredResponse = cloneMessage(message);
1009
+ if (!('id' in restoredResponse))
1010
+ throw new CapabilityError('Client response must carry a request id', 'INVALID_INPUT');
1011
+ restoredResponse.id = pending.originalId;
1012
+ void pending.upstream.send(restoredResponse).catch((cause) => CaughtFailureSchema.parse(cause)).then((error) => {
1013
+ if (!error)
1014
+ return;
1015
+ log.debug('relay upstream response send failed', {
1016
+ error: error instanceof Error ? error.message : String(error)
1017
+ });
1018
+ if (upstream === pending.upstream &&
1019
+ upstreamGeneration === pending.generation) {
1020
+ pending.upstream.onclose?.();
1021
+ }
1022
+ });
1023
+ return;
1024
+ }
1025
+ }
1026
+ // A reply to a retired host generation must die with that generation.
1027
+ // Forwarding it to the replacement can satisfy an unrelated request that
1028
+ // reused the same original id.
1029
+ if (isRelayHostRequestId(responseId))
1030
+ return;
1031
+ }
1032
+ const requestId = messageRequestId(message);
1033
+ const beforeInitialize = !protocolInitialized && 'method' in message && message.method !== 'initialize';
1034
+ const target = upstreamReady ? upstream : undefined;
1035
+ const targetUrl = upstreamReady ? upstreamUrl : undefined;
1036
+ if (!target || !targetUrl) {
1037
+ // A session re-initialization is in flight: this call waits for it.
1038
+ if (reinitializing) {
1039
+ const queued = cloneMessage(message);
1040
+ void reinitializing.then(() => downstream.onmessage?.(queued));
1041
+ return;
1042
+ }
1043
+ if (requestId !== undefined) {
1044
+ void downstream.send(hostLossError(requestId, lastHostLoss)).catch((cause) => CaughtFailureSchema.parse(cause)).then((failure) => { if (failure)
1045
+ closeAfterFailure(failure); });
1046
+ }
1047
+ return;
1048
+ }
1049
+ if (requestId !== undefined) {
1050
+ const request = cloneMessage(message);
1051
+ const armHealthProbe = () => options.lifecycle.timeout(() => {
1052
+ void (async () => {
1053
+ const pending = pendingClientRequests.get(requestId);
1054
+ if (!pending || pending.request !== request || upstream !== target)
1055
+ return;
1056
+ const sample = await healthProbeOutcome(() => options.probeHost
1057
+ ? options.probeHost(new URL(targetUrl))
1058
+ : probeMcpHost(options.lifecycle, new URL(targetUrl), healthProbeTimeoutMs));
1059
+ if (sample.failure !== undefined && !probeFailureReported) {
1060
+ probeFailureReported = true;
1061
+ if (!isLifecycleDeadline(sample.failure))
1062
+ log.warn('Shared MCP host health probe failed; treating the host as unhealthy', { failure: valueFreeFailure(sample.failure) });
1063
+ }
1064
+ const current = pendingClientRequests.get(requestId);
1065
+ if (!current || current.request !== request || upstream !== target)
1066
+ return;
1067
+ if (!sample.healthy) {
1068
+ const observed = await observeHostLossOrFailure(options, upstreamClaim);
1069
+ if ('error' in observed && observed.error !== undefined)
1070
+ log.warn('Could not read the shared MCP host claim after a failed health probe', { failure: valueFreeFailure(observed.error) });
1071
+ if (observed.loss === 'restarted') {
1072
+ target.onclose?.();
1073
+ return;
1074
+ }
1075
+ // A slow event loop is not proof of a restarted process.
1076
+ // Preserve pending calls and the host's sampling requests.
1077
+ if (!current.healthWarned) {
1078
+ current.healthWarned = true;
1079
+ void downstream.send(relayStateNotification('warning', 'The shared MCP host is slow to answer health probes; existing calls remain attached.')).catch((cause) => CaughtFailureSchema.parse(cause)).then((failure) => { if (failure)
1080
+ closeAfterFailure(failure); });
1081
+ }
1082
+ }
1083
+ current.timer = armHealthProbe();
1084
+ })();
1085
+ }, healthProbeIntervalMs);
1086
+ pendingClientRequests.set(requestId, { request, beforeInitialize, timer: armHealthProbe() });
1087
+ }
1088
+ let outbound = cloneMessage(message);
1089
+ if (harnessNeedsSafeMcpNames(nameClient, customPresets)) {
1090
+ const call = CallToolRequestSchema.safeParse(message);
1091
+ if (call.success) {
1092
+ const canonicalName = toolNames.get(call.data.params.name);
1093
+ if (canonicalName && 'method' in outbound)
1094
+ outbound = { ...outbound, params: { ...call.data.params, name: canonicalName } };
1095
+ }
1096
+ // Malformed requests still reach the host's SDK validation unchanged.
1097
+ }
1098
+ void target
1099
+ .send(applyRelayIdentity(outbound, options.identity))
1100
+ .catch((cause) => CaughtFailureSchema.parse(cause)).then((error) => {
1101
+ if (!error)
1102
+ return;
1103
+ // A host that ANSWERS is not a host that died. The streamable-HTTP
1104
+ // transport throws on a non-2xx POST even when the body is a
1105
+ // JSON-RPC error the host wrote on purpose (2026-09-06: `session
1106
+ // init` from an identity-less body → 409 -32004 "Legacy session
1107
+ // restore requires a stable participant or harness session
1108
+ // identity"). Treating that as loss failed the call with
1109
+ // "host restarted", reconnected, and dropped the binding — every
1110
+ // time, while /health answered 200. Hand the host's own error to the
1111
+ // client under the client's id instead.
1112
+ const refusal = requestId !== undefined ? hostRefusalFromTransportError(error, log, beforeInitialize) : null;
1113
+ if (refusal && upstream === target) {
1114
+ const pending = pendingClientRequests.get(requestId);
1115
+ if (pending) {
1116
+ pending.timer();
1117
+ pendingClientRequests.delete(requestId);
1118
+ }
1119
+ void downstream
1120
+ .send({ jsonrpc: '2.0', id: requestId, error: refusal })
1121
+ .catch((cause) => CaughtFailureSchema.parse(cause)).then((failure) => { if (failure)
1122
+ closeAfterFailure(failure); });
1123
+ return;
1124
+ }
1125
+ if (upstream === target)
1126
+ target.onclose?.();
1127
+ });
1128
+ };
1129
+ // host -> client
1130
+ installUpstreamHandlers(initialUpstream, upstreamGeneration);
1131
+ downstream.onclose = closeRelay;
1132
+ failureOwner.bindDownstream();
1133
+ await downstream.start();
1134
+ // END OF CLIENT INPUT IS THE END OF THIS PROCESS'S JOB.
1135
+ //
1136
+ // `StdioServerTransport` subscribes to `data` and `error` on stdin and to
1137
+ // nothing else, so a client that closes the pipe never fires `onclose`,
1138
+ // `done` never resolves, and `startStdioMcp` never returns. Measured
1139
+ // 2026-08-30 against the shipping binary: `initialize` answered, `tools/list`
1140
+ // returned 96 tools, then the body was still alive 123 s after stdin EOF
1141
+ // (exit code null) while SIGTERM killed it in 144 ms. Every MCP client that
1142
+ // disconnects by closing stdio — the normal way — left an orphan holding a
1143
+ // live upstream HTTP session against the shared host.
1144
+ //
1145
+ // This relay, not the transport, owns the process's stdio, so it owns the
1146
+ // EOF. Watch it only when nobody injected transports (i.e. this really is
1147
+ // process stdio) or when a falsifier supplied its own pipe.
1148
+ const onClientEof = () => closeRelay();
1149
+ if (clientInput) {
1150
+ clientInput.once('end', onClientEof);
1151
+ clientInput.once('close', onClientEof);
1152
+ detachClientInputWatch = () => {
1153
+ clientInput.off('end', onClientEof);
1154
+ clientInput.off('close', onClientEof);
1155
+ };
1156
+ }
1157
+ else if (stdinEvents) {
1158
+ const detachEnd = stdinEvents.subscribeStdin('end', onClientEof);
1159
+ const detachClose = stdinEvents.subscribeStdin('close', onClientEof);
1160
+ detachClientInputWatch = () => {
1161
+ detachEnd();
1162
+ detachClose();
1163
+ };
1164
+ }
1165
+ if (failureOwner.closing)
1166
+ detachClientInputWatch();
1167
+ return { relayed: true, url: resolved.url, done };
1168
+ }
1169
+ //# sourceMappingURL=stdio-relay.js.map