@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.
- package/LICENSE +18 -0
- package/README.md +96 -2
- package/dist/adapters/auth-middleware.d.ts +68 -0
- package/dist/adapters/auth-middleware.d.ts.map +1 -0
- package/dist/adapters/auth-middleware.js +161 -0
- package/dist/adapters/auth-middleware.js.map +1 -0
- package/dist/adapters/compatibility.d.ts +20 -0
- package/dist/adapters/compatibility.d.ts.map +1 -0
- package/dist/adapters/compatibility.js +57 -0
- package/dist/adapters/compatibility.js.map +1 -0
- package/dist/adapters/host-context.consumer.d.ts +12 -0
- package/dist/adapters/host-context.consumer.d.ts.map +1 -0
- package/dist/adapters/host-context.consumer.js +10 -0
- package/dist/adapters/host-context.consumer.js.map +1 -0
- package/dist/adapters/host-context.d.ts +32 -0
- package/dist/adapters/host-context.d.ts.map +1 -0
- package/dist/adapters/host-context.js +17 -0
- package/dist/adapters/host-context.js.map +1 -0
- package/dist/adapters/index.d.ts +12 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +13 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/mcp/ambient-envelope.d.ts +26 -0
- package/dist/adapters/mcp/ambient-envelope.d.ts.map +1 -0
- package/dist/adapters/mcp/ambient-envelope.js +70 -0
- package/dist/adapters/mcp/ambient-envelope.js.map +1 -0
- package/dist/adapters/mcp/catalog.d.ts +15 -0
- package/dist/adapters/mcp/catalog.d.ts.map +1 -0
- package/dist/adapters/mcp/catalog.js +26 -0
- package/dist/adapters/mcp/catalog.js.map +1 -0
- package/dist/adapters/mcp/connection-identity.d.ts +81 -0
- package/dist/adapters/mcp/connection-identity.d.ts.map +1 -0
- package/dist/adapters/mcp/connection-identity.js +114 -0
- package/dist/adapters/mcp/connection-identity.js.map +1 -0
- package/dist/adapters/mcp/context.d.ts +137 -0
- package/dist/adapters/mcp/context.d.ts.map +1 -0
- package/dist/adapters/mcp/context.js +230 -0
- package/dist/adapters/mcp/context.js.map +1 -0
- package/dist/adapters/mcp/formatters.d.ts +41 -0
- package/dist/adapters/mcp/formatters.d.ts.map +1 -0
- package/dist/adapters/mcp/formatters.js +102 -0
- package/dist/adapters/mcp/formatters.js.map +1 -0
- package/dist/adapters/mcp/from-api-endpoint.d.ts +59 -0
- package/dist/adapters/mcp/from-api-endpoint.d.ts.map +1 -0
- package/dist/adapters/mcp/from-api-endpoint.js +206 -0
- package/dist/adapters/mcp/from-api-endpoint.js.map +1 -0
- package/dist/adapters/mcp/generation-contract.d.ts +5 -0
- package/dist/adapters/mcp/generation-contract.d.ts.map +1 -0
- package/dist/adapters/mcp/generation-contract.js +34 -0
- package/dist/adapters/mcp/generation-contract.js.map +1 -0
- package/dist/adapters/mcp/host-registry.d.ts +159 -0
- package/dist/adapters/mcp/host-registry.d.ts.map +1 -0
- package/dist/adapters/mcp/host-registry.js +237 -0
- package/dist/adapters/mcp/host-registry.js.map +1 -0
- package/dist/adapters/mcp/index.d.ts +12 -0
- package/dist/adapters/mcp/index.d.ts.map +1 -0
- package/dist/adapters/mcp/index.js +17 -0
- package/dist/adapters/mcp/index.js.map +1 -0
- package/dist/adapters/mcp/isolated-notice.d.ts +111 -0
- package/dist/adapters/mcp/isolated-notice.d.ts.map +1 -0
- package/dist/adapters/mcp/isolated-notice.js +217 -0
- package/dist/adapters/mcp/isolated-notice.js.map +1 -0
- package/dist/adapters/mcp/json-rpc.d.ts +16 -0
- package/dist/adapters/mcp/json-rpc.d.ts.map +1 -0
- package/dist/adapters/mcp/json-rpc.js +32 -0
- package/dist/adapters/mcp/json-rpc.js.map +1 -0
- package/dist/adapters/mcp/local-origin.d.ts +18 -0
- package/dist/adapters/mcp/local-origin.d.ts.map +1 -0
- package/dist/adapters/mcp/local-origin.js +37 -0
- package/dist/adapters/mcp/local-origin.js.map +1 -0
- package/dist/adapters/mcp/logging/mcp-cloud.descriptor.d.ts +11 -0
- package/dist/adapters/mcp/logging/mcp-cloud.descriptor.d.ts.map +1 -0
- package/dist/adapters/mcp/logging/mcp-cloud.descriptor.js +21 -0
- package/dist/adapters/mcp/logging/mcp-cloud.descriptor.js.map +1 -0
- package/dist/adapters/mcp/logging/mcp-http.descriptor.d.ts +10 -0
- package/dist/adapters/mcp/logging/mcp-http.descriptor.d.ts.map +1 -0
- package/dist/adapters/mcp/logging/mcp-http.descriptor.js +20 -0
- package/dist/adapters/mcp/logging/mcp-http.descriptor.js.map +1 -0
- package/dist/adapters/mcp/logging/mcp-server.descriptor.d.ts +9 -0
- package/dist/adapters/mcp/logging/mcp-server.descriptor.d.ts.map +1 -0
- package/dist/adapters/mcp/logging/mcp-server.descriptor.js +25 -0
- package/dist/adapters/mcp/logging/mcp-server.descriptor.js.map +1 -0
- package/dist/adapters/mcp/logging/mcp-stdio.descriptor.d.ts +10 -0
- package/dist/adapters/mcp/logging/mcp-stdio.descriptor.d.ts.map +1 -0
- package/dist/adapters/mcp/logging/mcp-stdio.descriptor.js +20 -0
- package/dist/adapters/mcp/logging/mcp-stdio.descriptor.js.map +1 -0
- package/dist/adapters/mcp/mcp-http-session.d.ts +140 -0
- package/dist/adapters/mcp/mcp-http-session.d.ts.map +1 -0
- package/dist/adapters/mcp/mcp-http-session.js +797 -0
- package/dist/adapters/mcp/mcp-http-session.js.map +1 -0
- package/dist/adapters/mcp/mcp-wire-name.d.ts +9 -0
- package/dist/adapters/mcp/mcp-wire-name.d.ts.map +1 -0
- package/dist/adapters/mcp/mcp-wire-name.js +52 -0
- package/dist/adapters/mcp/mcp-wire-name.js.map +1 -0
- package/dist/adapters/mcp/projection-doctor.d.ts +101 -0
- package/dist/adapters/mcp/projection-doctor.d.ts.map +1 -0
- package/dist/adapters/mcp/projection-doctor.js +285 -0
- package/dist/adapters/mcp/projection-doctor.js.map +1 -0
- package/dist/adapters/mcp/projection.d.ts +36 -0
- package/dist/adapters/mcp/projection.d.ts.map +1 -0
- package/dist/adapters/mcp/projection.js +136 -0
- package/dist/adapters/mcp/projection.js.map +1 -0
- package/dist/adapters/mcp/prompts/continuity-wake.prompt.descriptor.d.ts +113 -0
- package/dist/adapters/mcp/prompts/continuity-wake.prompt.descriptor.d.ts.map +1 -0
- package/dist/adapters/mcp/prompts/continuity-wake.prompt.descriptor.js +15 -0
- package/dist/adapters/mcp/prompts/continuity-wake.prompt.descriptor.js.map +1 -0
- package/dist/adapters/mcp/prompts/empty-tool-kind.prompt.descriptor.d.ts +113 -0
- package/dist/adapters/mcp/prompts/empty-tool-kind.prompt.descriptor.d.ts.map +1 -0
- package/dist/adapters/mcp/prompts/empty-tool-kind.prompt.descriptor.js +18 -0
- package/dist/adapters/mcp/prompts/empty-tool-kind.prompt.descriptor.js.map +1 -0
- package/dist/adapters/mcp/prompts/index.d.ts +113 -0
- package/dist/adapters/mcp/prompts/index.d.ts.map +1 -0
- package/dist/adapters/mcp/prompts/index.js +24 -0
- package/dist/adapters/mcp/prompts/index.js.map +1 -0
- package/dist/adapters/mcp/prompts/isolated-runtime.prompt.descriptor.d.ts +114 -0
- package/dist/adapters/mcp/prompts/isolated-runtime.prompt.descriptor.d.ts.map +1 -0
- package/dist/adapters/mcp/prompts/isolated-runtime.prompt.descriptor.js +37 -0
- package/dist/adapters/mcp/prompts/isolated-runtime.prompt.descriptor.js.map +1 -0
- package/dist/adapters/mcp/prompts/local-tools.prompt.descriptor.d.ts +113 -0
- package/dist/adapters/mcp/prompts/local-tools.prompt.descriptor.d.ts.map +1 -0
- package/dist/adapters/mcp/prompts/local-tools.prompt.descriptor.js +20 -0
- package/dist/adapters/mcp/prompts/local-tools.prompt.descriptor.js.map +1 -0
- package/dist/adapters/mcp/prompts/max-tools.prompt.descriptor.d.ts +113 -0
- package/dist/adapters/mcp/prompts/max-tools.prompt.descriptor.d.ts.map +1 -0
- package/dist/adapters/mcp/prompts/max-tools.prompt.descriptor.js +20 -0
- package/dist/adapters/mcp/prompts/max-tools.prompt.descriptor.js.map +1 -0
- package/dist/adapters/mcp/prompts/private-runtime.prompt.descriptor.d.ts +114 -0
- package/dist/adapters/mcp/prompts/private-runtime.prompt.descriptor.d.ts.map +1 -0
- package/dist/adapters/mcp/prompts/private-runtime.prompt.descriptor.js +21 -0
- package/dist/adapters/mcp/prompts/private-runtime.prompt.descriptor.js.map +1 -0
- package/dist/adapters/mcp/prompts/registration-failure.prompt.descriptor.d.ts +113 -0
- package/dist/adapters/mcp/prompts/registration-failure.prompt.descriptor.d.ts.map +1 -0
- package/dist/adapters/mcp/prompts/registration-failure.prompt.descriptor.js +19 -0
- package/dist/adapters/mcp/prompts/registration-failure.prompt.descriptor.js.map +1 -0
- package/dist/adapters/mcp/prompts/required-checklist.prompt.descriptor.d.ts +113 -0
- package/dist/adapters/mcp/prompts/required-checklist.prompt.descriptor.d.ts.map +1 -0
- package/dist/adapters/mcp/prompts/required-checklist.prompt.descriptor.js +18 -0
- package/dist/adapters/mcp/prompts/required-checklist.prompt.descriptor.js.map +1 -0
- package/dist/adapters/mcp/prompts/required-tools-missing.prompt.descriptor.d.ts +113 -0
- package/dist/adapters/mcp/prompts/required-tools-missing.prompt.descriptor.d.ts.map +1 -0
- package/dist/adapters/mcp/prompts/required-tools-missing.prompt.descriptor.js +19 -0
- package/dist/adapters/mcp/prompts/required-tools-missing.prompt.descriptor.js.map +1 -0
- package/dist/adapters/mcp/prompts/unknown-client.prompt.descriptor.d.ts +113 -0
- package/dist/adapters/mcp/prompts/unknown-client.prompt.descriptor.d.ts.map +1 -0
- package/dist/adapters/mcp/prompts/unknown-client.prompt.descriptor.js +18 -0
- package/dist/adapters/mcp/prompts/unknown-client.prompt.descriptor.js.map +1 -0
- package/dist/adapters/mcp/request-context.d.ts +28 -0
- package/dist/adapters/mcp/request-context.d.ts.map +1 -0
- package/dist/adapters/mcp/request-context.js +49 -0
- package/dist/adapters/mcp/request-context.js.map +1 -0
- package/dist/adapters/mcp/roster-announcer.d.ts +3 -0
- package/dist/adapters/mcp/roster-announcer.d.ts.map +1 -0
- package/dist/adapters/mcp/roster-announcer.js +22 -0
- package/dist/adapters/mcp/roster-announcer.js.map +1 -0
- package/dist/adapters/mcp/sampling-context.d.ts +12 -0
- package/dist/adapters/mcp/sampling-context.d.ts.map +1 -0
- package/dist/adapters/mcp/sampling-context.js +30 -0
- package/dist/adapters/mcp/sampling-context.js.map +1 -0
- package/dist/adapters/mcp/sampling.d.ts +17 -0
- package/dist/adapters/mcp/sampling.d.ts.map +1 -0
- package/dist/adapters/mcp/sampling.js +43 -0
- package/dist/adapters/mcp/sampling.js.map +1 -0
- package/dist/adapters/mcp/schemas/index.d.ts +37 -0
- package/dist/adapters/mcp/schemas/index.d.ts.map +1 -0
- package/dist/adapters/mcp/schemas/index.js +5 -0
- package/dist/adapters/mcp/schemas/index.js.map +1 -0
- package/dist/adapters/mcp/schemas/tool-selection.d.ts +12 -0
- package/dist/adapters/mcp/schemas/tool-selection.d.ts.map +1 -0
- package/dist/adapters/mcp/schemas/tool-selection.js +2 -0
- package/dist/adapters/mcp/schemas/tool-selection.js.map +1 -0
- package/dist/adapters/mcp/serve-identity.d.ts +64 -0
- package/dist/adapters/mcp/serve-identity.d.ts.map +1 -0
- package/dist/adapters/mcp/serve-identity.js +220 -0
- package/dist/adapters/mcp/serve-identity.js.map +1 -0
- package/dist/adapters/mcp/server.consumer.d.ts +5 -0
- package/dist/adapters/mcp/server.consumer.d.ts.map +1 -0
- package/dist/adapters/mcp/server.consumer.js +300 -0
- package/dist/adapters/mcp/server.consumer.js.map +1 -0
- package/dist/adapters/mcp/server.d.ts +56 -0
- package/dist/adapters/mcp/server.d.ts.map +1 -0
- package/dist/adapters/mcp/server.js +1423 -0
- package/dist/adapters/mcp/server.js.map +1 -0
- package/dist/adapters/mcp/stale-body.consumer.d.ts +85 -0
- package/dist/adapters/mcp/stale-body.consumer.d.ts.map +1 -0
- package/dist/adapters/mcp/stale-body.consumer.js +183 -0
- package/dist/adapters/mcp/stale-body.consumer.js.map +1 -0
- package/dist/adapters/mcp/startup-refusal.d.ts +8 -0
- package/dist/adapters/mcp/startup-refusal.d.ts.map +1 -0
- package/dist/adapters/mcp/startup-refusal.js +141 -0
- package/dist/adapters/mcp/startup-refusal.js.map +1 -0
- package/dist/adapters/mcp/stdio-relay.d.ts +232 -0
- package/dist/adapters/mcp/stdio-relay.d.ts.map +1 -0
- package/dist/adapters/mcp/stdio-relay.js +1169 -0
- package/dist/adapters/mcp/stdio-relay.js.map +1 -0
- package/dist/adapters/mcp/tool-audit-live.d.ts +109 -0
- package/dist/adapters/mcp/tool-audit-live.d.ts.map +1 -0
- package/dist/adapters/mcp/tool-audit-live.js +538 -0
- package/dist/adapters/mcp/tool-audit-live.js.map +1 -0
- package/dist/adapters/mcp/tool-audit.d.ts +44 -0
- package/dist/adapters/mcp/tool-audit.d.ts.map +1 -0
- package/dist/adapters/mcp/tool-audit.js +168 -0
- package/dist/adapters/mcp/tool-audit.js.map +1 -0
- package/dist/adapters/mcp/tool-catalog.d.ts +179 -0
- package/dist/adapters/mcp/tool-catalog.d.ts.map +1 -0
- package/dist/adapters/mcp/tool-catalog.js +330 -0
- package/dist/adapters/mcp/tool-catalog.js.map +1 -0
- package/dist/adapters/mcp/tool-selection.d.ts +77 -0
- package/dist/adapters/mcp/tool-selection.d.ts.map +1 -0
- package/dist/adapters/mcp/tool-selection.js +266 -0
- package/dist/adapters/mcp/tool-selection.js.map +1 -0
- package/dist/adapters/mcp/transport-registry.d.ts +5 -0
- package/dist/adapters/mcp/transport-registry.d.ts.map +1 -0
- package/dist/adapters/mcp/transport-registry.js +13 -0
- package/dist/adapters/mcp/transport-registry.js.map +1 -0
- package/dist/adapters/mcp/trust-tag.d.ts +69 -0
- package/dist/adapters/mcp/trust-tag.d.ts.map +1 -0
- package/dist/adapters/mcp/trust-tag.js +120 -0
- package/dist/adapters/mcp/trust-tag.js.map +1 -0
- package/dist/adapters/mcp/vibes-types.d.ts +115 -0
- package/dist/adapters/mcp/vibes-types.d.ts.map +1 -0
- package/dist/adapters/mcp/vibes-types.js +7 -0
- package/dist/adapters/mcp/vibes-types.js.map +1 -0
- package/dist/adapters/mcp/web-handler.d.ts +49 -0
- package/dist/adapters/mcp/web-handler.d.ts.map +1 -0
- package/dist/adapters/mcp/web-handler.js +129 -0
- package/dist/adapters/mcp/web-handler.js.map +1 -0
- package/dist/adapters/session-id.d.ts +3 -0
- package/dist/adapters/session-id.d.ts.map +1 -0
- package/dist/adapters/session-id.js +42 -0
- package/dist/adapters/session-id.js.map +1 -0
- package/dist/adapters/transport-bun-http.d.ts +39 -0
- package/dist/adapters/transport-bun-http.d.ts.map +1 -0
- package/dist/adapters/transport-bun-http.js +42 -0
- package/dist/adapters/transport-bun-http.js.map +1 -0
- package/dist/adapters/types.d.ts +135 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/adapters/web-compatibility.d.ts +6 -0
- package/dist/adapters/web-compatibility.d.ts.map +1 -0
- package/dist/adapters/web-compatibility.js +13 -0
- package/dist/adapters/web-compatibility.js.map +1 -0
- package/dist/adapters/zod-mcp.d.ts +139 -0
- package/dist/adapters/zod-mcp.d.ts.map +1 -0
- package/dist/adapters/zod-mcp.js +617 -0
- package/dist/adapters/zod-mcp.js.map +1 -0
- package/dist/cli/mcp/audit/dev.audit.tools.descriptor.d.ts +15 -0
- package/dist/cli/mcp/audit/dev.audit.tools.descriptor.d.ts.map +1 -0
- package/dist/cli/mcp/audit/dev.audit.tools.descriptor.js +58 -0
- package/dist/cli/mcp/audit/dev.audit.tools.descriptor.js.map +1 -0
- package/dist/cli/mcp/audit/dev.audit.tools.impl.consumer.d.ts +6 -0
- package/dist/cli/mcp/audit/dev.audit.tools.impl.consumer.d.ts.map +1 -0
- package/dist/cli/mcp/audit/dev.audit.tools.impl.consumer.js +180 -0
- package/dist/cli/mcp/audit/dev.audit.tools.impl.consumer.js.map +1 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.restart.descriptor.d.ts +22 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.restart.descriptor.d.ts.map +1 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.restart.descriptor.js +19 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.restart.descriptor.js.map +1 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.restart.impl.consumer.d.ts +23 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.restart.impl.consumer.d.ts.map +1 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.restart.impl.consumer.js +41 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.restart.impl.consumer.js.map +1 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.serve.descriptor.d.ts +75 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.serve.descriptor.d.ts.map +1 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.serve.descriptor.js +84 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.serve.descriptor.js.map +1 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.start.descriptor.d.ts +35 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.start.descriptor.d.ts.map +1 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.start.descriptor.js +28 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.start.descriptor.js.map +1 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.start.impl.consumer.d.ts +11 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.start.impl.consumer.d.ts.map +1 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.start.impl.consumer.js +47 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.start.impl.consumer.js.map +1 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.stop.descriptor.d.ts +18 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.stop.descriptor.d.ts.map +1 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.stop.descriptor.js +18 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.stop.descriptor.js.map +1 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.stop.impl.consumer.d.ts +12 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.stop.impl.consumer.d.ts.map +1 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.stop.impl.consumer.js +17 -0
- package/dist/cli/mcp/lifecycle/dev.mcp.stop.impl.consumer.js.map +1 -0
- package/dist/cli/mcp/management/dev.mcp.install.descriptor.d.ts +41 -0
- package/dist/cli/mcp/management/dev.mcp.install.descriptor.d.ts.map +1 -0
- package/dist/cli/mcp/management/dev.mcp.install.descriptor.js +49 -0
- package/dist/cli/mcp/management/dev.mcp.install.descriptor.js.map +1 -0
- package/dist/cli/mcp/management/dev.mcp.install.impl.consumer.d.ts +16 -0
- package/dist/cli/mcp/management/dev.mcp.install.impl.consumer.d.ts.map +1 -0
- package/dist/cli/mcp/management/dev.mcp.install.impl.consumer.js +244 -0
- package/dist/cli/mcp/management/dev.mcp.install.impl.consumer.js.map +1 -0
- package/dist/cli/mcp/management/dev.mcp.list.descriptor.d.ts +22 -0
- package/dist/cli/mcp/management/dev.mcp.list.descriptor.d.ts.map +1 -0
- package/dist/cli/mcp/management/dev.mcp.list.descriptor.js +24 -0
- package/dist/cli/mcp/management/dev.mcp.list.descriptor.js.map +1 -0
- package/dist/cli/mcp/management/dev.mcp.list.impl.consumer.d.ts +29 -0
- package/dist/cli/mcp/management/dev.mcp.list.impl.consumer.d.ts.map +1 -0
- package/dist/cli/mcp/management/dev.mcp.list.impl.consumer.js +127 -0
- package/dist/cli/mcp/management/dev.mcp.list.impl.consumer.js.map +1 -0
- package/dist/cli/mcp/management/dev.mcp.status.descriptor.d.ts +48 -0
- package/dist/cli/mcp/management/dev.mcp.status.descriptor.d.ts.map +1 -0
- package/dist/cli/mcp/management/dev.mcp.status.descriptor.js +56 -0
- package/dist/cli/mcp/management/dev.mcp.status.descriptor.js.map +1 -0
- package/dist/cli/mcp/management/dev.mcp.status.impl.consumer.d.ts +9 -0
- package/dist/cli/mcp/management/dev.mcp.status.impl.consumer.d.ts.map +1 -0
- package/dist/cli/mcp/management/dev.mcp.status.impl.consumer.js +253 -0
- package/dist/cli/mcp/management/dev.mcp.status.impl.consumer.js.map +1 -0
- package/dist/cli/mcp/management/install-helpers.d.ts +26 -0
- package/dist/cli/mcp/management/install-helpers.d.ts.map +1 -0
- package/dist/cli/mcp/management/install-helpers.js +95 -0
- package/dist/cli/mcp/management/install-helpers.js.map +1 -0
- package/dist/cli/mcp/management/process-audit.impl.consumer.d.ts +38 -0
- package/dist/cli/mcp/management/process-audit.impl.consumer.d.ts.map +1 -0
- package/dist/cli/mcp/management/process-audit.impl.consumer.js +145 -0
- package/dist/cli/mcp/management/process-audit.impl.consumer.js.map +1 -0
- package/dist/cli/mcp/management/rules-debt-summary.impl.consumer.d.ts +21 -0
- package/dist/cli/mcp/management/rules-debt-summary.impl.consumer.d.ts.map +1 -0
- package/dist/cli/mcp/management/rules-debt-summary.impl.consumer.js +82 -0
- package/dist/cli/mcp/management/rules-debt-summary.impl.consumer.js.map +1 -0
- package/dist/cli/mcp/management/status-doctor.d.ts +22 -0
- package/dist/cli/mcp/management/status-doctor.d.ts.map +1 -0
- package/dist/cli/mcp/management/status-doctor.js +153 -0
- package/dist/cli/mcp/management/status-doctor.js.map +1 -0
- package/dist/cli/mcp/schemas/status-doctor.d.ts +75 -0
- package/dist/cli/mcp/schemas/status-doctor.d.ts.map +1 -0
- package/dist/cli/mcp/schemas/status-doctor.js +2 -0
- package/dist/cli/mcp/schemas/status-doctor.js.map +1 -0
- package/dist/cli/mcp/support/host-port.d.ts +31 -0
- package/dist/cli/mcp/support/host-port.d.ts.map +1 -0
- package/dist/cli/mcp/support/host-port.js +31 -0
- package/dist/cli/mcp/support/host-port.js.map +1 -0
- package/dist/cli/mcp/support/server-process.impl.consumer.d.ts +24 -0
- package/dist/cli/mcp/support/server-process.impl.consumer.d.ts.map +1 -0
- package/dist/cli/mcp/support/server-process.impl.consumer.js +86 -0
- package/dist/cli/mcp/support/server-process.impl.consumer.js.map +1 -0
- package/dist/cli/mcp/support/status-doctor.d.ts +33 -0
- package/dist/cli/mcp/support/status-doctor.d.ts.map +1 -0
- package/dist/cli/mcp/support/status-doctor.js +104 -0
- package/dist/cli/mcp/support/status-doctor.js.map +1 -0
- package/dist/cli/print.d.ts +12 -0
- package/dist/cli/print.d.ts.map +1 -0
- package/dist/cli/print.js +25 -0
- package/dist/cli/print.js.map +1 -0
- package/dist/cli/surface-mcp/client-connection-identity.consumer.d.ts +19 -0
- package/dist/cli/surface-mcp/client-connection-identity.consumer.d.ts.map +1 -0
- package/dist/cli/surface-mcp/client-connection-identity.consumer.js +53 -0
- package/dist/cli/surface-mcp/client-connection-identity.consumer.js.map +1 -0
- package/dist/cli/surface-mcp/dev.mcp.serve.impl.cloud.d.ts +7 -0
- package/dist/cli/surface-mcp/dev.mcp.serve.impl.cloud.d.ts.map +1 -0
- package/dist/cli/surface-mcp/dev.mcp.serve.impl.cloud.js +345 -0
- package/dist/cli/surface-mcp/dev.mcp.serve.impl.cloud.js.map +1 -0
- package/dist/cli/surface-mcp/dev.mcp.serve.impl.consumer.d.ts +12 -0
- package/dist/cli/surface-mcp/dev.mcp.serve.impl.consumer.d.ts.map +1 -0
- package/dist/cli/surface-mcp/dev.mcp.serve.impl.consumer.js +657 -0
- package/dist/cli/surface-mcp/dev.mcp.serve.impl.consumer.js.map +1 -0
- package/dist/cli/surface-mcp/local-dev-flag.d.ts +24 -0
- package/dist/cli/surface-mcp/local-dev-flag.d.ts.map +1 -0
- package/dist/cli/surface-mcp/local-dev-flag.js +37 -0
- package/dist/cli/surface-mcp/local-dev-flag.js.map +1 -0
- package/dist/cli/surface-mcp/shared-host-startup.d.ts +14 -0
- package/dist/cli/surface-mcp/shared-host-startup.d.ts.map +1 -0
- package/dist/cli/surface-mcp/shared-host-startup.js +46 -0
- package/dist/cli/surface-mcp/shared-host-startup.js.map +1 -0
- package/dist/cli/surface-mcp/stdio-relay-transport.d.ts +2 -0
- package/dist/cli/surface-mcp/stdio-relay-transport.d.ts.map +1 -0
- package/dist/cli/surface-mcp/stdio-relay-transport.js +21 -0
- package/dist/cli/surface-mcp/stdio-relay-transport.js.map +1 -0
- package/dist/cli/surface-mcp/teardown.d.ts +17 -0
- package/dist/cli/surface-mcp/teardown.d.ts.map +1 -0
- package/dist/cli/surface-mcp/teardown.js +28 -0
- package/dist/cli/surface-mcp/teardown.js.map +1 -0
- package/dist/client.d.ts +49 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +131 -0
- package/dist/client.js.map +1 -0
- package/dist/config/load.d.ts +6 -0
- package/dist/config/load.d.ts.map +1 -0
- package/dist/config/load.js +18 -0
- package/dist/config/load.js.map +1 -0
- package/dist/config/mcp.config.descriptor.d.ts +108 -0
- package/dist/config/mcp.config.descriptor.d.ts.map +1 -0
- package/dist/config/mcp.config.descriptor.js +63 -0
- package/dist/config/mcp.config.descriptor.js.map +1 -0
- package/dist/docs/mcp.docs.descriptor.d.ts +4 -0
- package/dist/docs/mcp.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/mcp.docs.descriptor.js +44 -0
- package/dist/docs/mcp.docs.descriptor.js.map +1 -0
- package/dist/docs/prompts.docs.descriptor.d.ts +4 -0
- package/dist/docs/prompts.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/prompts.docs.descriptor.js +130 -0
- package/dist/docs/prompts.docs.descriptor.js.map +1 -0
- package/dist/docs/resources.docs.descriptor.d.ts +4 -0
- package/dist/docs/resources.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/resources.docs.descriptor.js +390 -0
- package/dist/docs/resources.docs.descriptor.js.map +1 -0
- package/dist/docs/server.docs.descriptor.d.ts +4 -0
- package/dist/docs/server.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/server.docs.descriptor.js +383 -0
- package/dist/docs/server.docs.descriptor.js.map +1 -0
- package/dist/docs/tools.docs.descriptor.d.ts +4 -0
- package/dist/docs/tools.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/tools.docs.descriptor.js +316 -0
- package/dist/docs/tools.docs.descriptor.js.map +1 -0
- package/dist/harness-identity.descriptors.d.ts +11 -0
- package/dist/harness-identity.descriptors.d.ts.map +1 -0
- package/dist/harness-identity.descriptors.js +41 -0
- package/dist/harness-identity.descriptors.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/kinds/harness-identity.kind.d.ts +3 -0
- package/dist/kinds/harness-identity.kind.d.ts.map +1 -0
- package/dist/kinds/harness-identity.kind.js +16 -0
- package/dist/kinds/harness-identity.kind.js.map +1 -0
- package/dist/kinds/sampling.kind.d.ts +15 -0
- package/dist/kinds/sampling.kind.d.ts.map +1 -0
- package/dist/kinds/sampling.kind.js +24 -0
- package/dist/kinds/sampling.kind.js.map +1 -0
- package/dist/lifecycle-repeat.d.ts +23 -0
- package/dist/lifecycle-repeat.d.ts.map +1 -0
- package/dist/lifecycle-repeat.js +21 -0
- package/dist/lifecycle-repeat.js.map +1 -0
- package/dist/mcp/dev.mcp.descriptor.d.ts +18 -0
- package/dist/mcp/dev.mcp.descriptor.d.ts.map +1 -0
- package/dist/mcp/dev.mcp.descriptor.js +18 -0
- package/dist/mcp/dev.mcp.descriptor.js.map +1 -0
- package/dist/mcp/index.d.ts +20 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +19 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/mcp.plugin.d.ts +9 -0
- package/dist/mcp/mcp.plugin.d.ts.map +1 -0
- package/dist/mcp/mcp.plugin.js +59 -0
- package/dist/mcp/mcp.plugin.js.map +1 -0
- package/dist/mcp-client.plugin.d.ts +4 -0
- package/dist/mcp-client.plugin.d.ts.map +1 -0
- package/dist/mcp-client.plugin.js +13 -0
- package/dist/mcp-client.plugin.js.map +1 -0
- package/dist/mcp.client.descriptor.d.ts +27 -0
- package/dist/mcp.client.descriptor.d.ts.map +1 -0
- package/dist/mcp.client.descriptor.js +7 -0
- package/dist/mcp.client.descriptor.js.map +1 -0
- package/dist/mcp.consumer.plugin.d.ts +4 -0
- package/dist/mcp.consumer.plugin.d.ts.map +1 -0
- package/dist/mcp.consumer.plugin.js +19 -0
- package/dist/mcp.consumer.plugin.js.map +1 -0
- package/dist/mcp.runtime.plugin.d.ts +10 -0
- package/dist/mcp.runtime.plugin.d.ts.map +1 -0
- package/dist/mcp.runtime.plugin.js +40 -0
- package/dist/mcp.runtime.plugin.js.map +1 -0
- package/dist/root-state.d.ts +3 -0
- package/dist/root-state.d.ts.map +1 -0
- package/dist/root-state.js +3 -0
- package/dist/root-state.js.map +1 -0
- package/dist/schemas/api-endpoint.d.ts +12 -0
- package/dist/schemas/api-endpoint.d.ts.map +1 -0
- package/dist/schemas/api-endpoint.js +11 -0
- package/dist/schemas/api-endpoint.js.map +1 -0
- package/dist/schemas/audit.d.ts +172 -0
- package/dist/schemas/audit.d.ts.map +1 -0
- package/dist/schemas/audit.js +82 -0
- package/dist/schemas/audit.js.map +1 -0
- package/dist/schemas/harness-identity.d.ts +41 -0
- package/dist/schemas/harness-identity.d.ts.map +1 -0
- package/dist/schemas/harness-identity.js +19 -0
- package/dist/schemas/harness-identity.js.map +1 -0
- package/dist/schemas/host-adapters.d.ts +14 -0
- package/dist/schemas/host-adapters.d.ts.map +1 -0
- package/dist/schemas/host-adapters.js +7 -0
- package/dist/schemas/host-adapters.js.map +1 -0
- package/dist/schemas/projection.d.ts +10 -0
- package/dist/schemas/projection.d.ts.map +1 -0
- package/dist/schemas/projection.js +9 -0
- package/dist/schemas/projection.js.map +1 -0
- package/dist/schemas/tool.d.ts +8 -0
- package/dist/schemas/tool.d.ts.map +1 -0
- package/dist/schemas/tool.js +8 -0
- package/dist/schemas/tool.js.map +1 -0
- package/dist/surface-mcp/build-provenance.d.ts +67 -0
- package/dist/surface-mcp/build-provenance.d.ts.map +1 -0
- package/dist/surface-mcp/build-provenance.js +287 -0
- package/dist/surface-mcp/build-provenance.js.map +1 -0
- package/dist/surface-mcp/crash-reporting.d.ts +28 -0
- package/dist/surface-mcp/crash-reporting.d.ts.map +1 -0
- package/dist/surface-mcp/crash-reporting.js +41 -0
- package/dist/surface-mcp/crash-reporting.js.map +1 -0
- package/dist/surface-mcp/executable-freshness-watch.d.ts +89 -0
- package/dist/surface-mcp/executable-freshness-watch.d.ts.map +1 -0
- package/dist/surface-mcp/executable-freshness-watch.js +225 -0
- package/dist/surface-mcp/executable-freshness-watch.js.map +1 -0
- package/dist/surface-mcp/host-provenance-banner.d.ts +49 -0
- package/dist/surface-mcp/host-provenance-banner.d.ts.map +1 -0
- package/dist/surface-mcp/host-provenance-banner.js +135 -0
- package/dist/surface-mcp/host-provenance-banner.js.map +1 -0
- package/dist/surface-mcp/index.d.ts +51 -0
- package/dist/surface-mcp/index.d.ts.map +1 -0
- package/dist/surface-mcp/index.js +92 -0
- package/dist/surface-mcp/index.js.map +1 -0
- package/dist/surface-mcp/local-tools-bootstrap.d.ts +36 -0
- package/dist/surface-mcp/local-tools-bootstrap.d.ts.map +1 -0
- package/dist/surface-mcp/local-tools-bootstrap.js +43 -0
- package/dist/surface-mcp/local-tools-bootstrap.js.map +1 -0
- package/dist/surface-mcp/mcp-serve.plugin.d.ts +9 -0
- package/dist/surface-mcp/mcp-serve.plugin.d.ts.map +1 -0
- package/dist/surface-mcp/mcp-serve.plugin.js +24 -0
- package/dist/surface-mcp/mcp-serve.plugin.js.map +1 -0
- package/dist/surface-mcp/project-config.d.ts +35 -0
- package/dist/surface-mcp/project-config.d.ts.map +1 -0
- package/dist/surface-mcp/project-config.js +74 -0
- package/dist/surface-mcp/project-config.js.map +1 -0
- package/dist/surface-mcp/serve-lifecycle.d.ts +102 -0
- package/dist/surface-mcp/serve-lifecycle.d.ts.map +1 -0
- package/dist/surface-mcp/serve-lifecycle.js +343 -0
- package/dist/surface-mcp/serve-lifecycle.js.map +1 -0
- package/dist/surface-mcp/session-bridge.d.ts +18 -0
- package/dist/surface-mcp/session-bridge.d.ts.map +1 -0
- package/dist/surface-mcp/session-bridge.js +26 -0
- package/dist/surface-mcp/session-bridge.js.map +1 -0
- package/dist/surface-mcp/subscriptions/session.bound.workspace.descriptor.d.ts +13 -0
- package/dist/surface-mcp/subscriptions/session.bound.workspace.descriptor.d.ts.map +1 -0
- package/dist/surface-mcp/subscriptions/session.bound.workspace.descriptor.js +13 -0
- package/dist/surface-mcp/subscriptions/session.bound.workspace.descriptor.js.map +1 -0
- package/dist/surface-mcp/subscriptions/session.bound.workspace.impl.consumer.d.ts +11 -0
- package/dist/surface-mcp/subscriptions/session.bound.workspace.impl.consumer.d.ts.map +1 -0
- package/dist/surface-mcp/subscriptions/session.bound.workspace.impl.consumer.js +36 -0
- package/dist/surface-mcp/subscriptions/session.bound.workspace.impl.consumer.js.map +1 -0
- package/dist/surface-mcp/workspace-warning.d.ts +16 -0
- package/dist/surface-mcp/workspace-warning.d.ts.map +1 -0
- package/dist/surface-mcp/workspace-warning.js +44 -0
- package/dist/surface-mcp/workspace-warning.js.map +1 -0
- package/dist/svelte/index.d.ts +12 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +11 -0
- package/dist/svelte/mcp-svelte.plugin.d.ts +10 -0
- package/dist/svelte/mcp-svelte.plugin.d.ts.map +1 -0
- package/dist/svelte/mcp-svelte.plugin.js +24 -0
- package/dist/svelte/tool-widgets/VibesContextToolCard.svelte +91 -0
- package/dist/svelte/tool-widgets/VibesContextToolCard.svelte.d.ts +9 -0
- package/dist/svelte/tool-widgets/VibesContextToolCard.svelte.d.ts.map +1 -0
- package/dist/svelte/tool-widgets/index.d.ts +3 -0
- package/dist/svelte/tool-widgets/index.d.ts.map +1 -0
- package/dist/svelte/tool-widgets/index.js +2 -0
- package/dist/svelte/tool-widgets/summary.d.ts +48 -0
- package/dist/svelte/tool-widgets/summary.d.ts.map +1 -0
- package/dist/svelte/tool-widgets/summary.js +117 -0
- package/dist/tools/vibes-context/schemas.d.ts +85 -0
- package/dist/tools/vibes-context/schemas.d.ts.map +1 -0
- package/dist/tools/vibes-context/schemas.js +76 -0
- package/dist/tools/vibes-context/schemas.js.map +1 -0
- package/dist/tools/vibes-context/vibes-context.descriptor.d.ts +4 -0
- package/dist/tools/vibes-context/vibes-context.descriptor.d.ts.map +1 -0
- package/dist/tools/vibes-context/vibes-context.descriptor.js +30 -0
- package/dist/tools/vibes-context/vibes-context.descriptor.js.map +1 -0
- package/dist/tools/vibes-context/vibes-context.impl.d.ts +34 -0
- package/dist/tools/vibes-context/vibes-context.impl.d.ts.map +1 -0
- package/dist/tools/vibes-context/vibes-context.impl.js +207 -0
- package/dist/tools/vibes-context/vibes-context.impl.js.map +1 -0
- package/dist/tui/index.d.ts +4 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +4 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/mcp-health.d.ts +7 -0
- package/dist/tui/mcp-health.d.ts.map +1 -0
- package/dist/tui/mcp-health.js +16 -0
- package/dist/tui/mcp-health.js.map +1 -0
- package/dist/tui/mcp-tui.plugin.d.ts +3 -0
- package/dist/tui/mcp-tui.plugin.d.ts.map +1 -0
- package/dist/tui/mcp-tui.plugin.js +18 -0
- package/dist/tui/mcp-tui.plugin.js.map +1 -0
- package/dist/tui/mcp.tui-app.descriptor.d.ts +4 -0
- package/dist/tui/mcp.tui-app.descriptor.d.ts.map +1 -0
- package/dist/tui/mcp.tui-app.descriptor.js +19 -0
- package/dist/tui/mcp.tui-app.descriptor.js.map +1 -0
- package/dist/tui/mcp.tui-app.impl.consumer.d.ts +4 -0
- package/dist/tui/mcp.tui-app.impl.consumer.d.ts.map +1 -0
- package/dist/tui/mcp.tui-app.impl.consumer.js +48 -0
- package/dist/tui/mcp.tui-app.impl.consumer.js.map +1 -0
- package/dist/tui/mcp.tui-state.descriptor.d.ts +8 -0
- package/dist/tui/mcp.tui-state.descriptor.d.ts.map +1 -0
- package/dist/tui/mcp.tui-state.descriptor.js +37 -0
- package/dist/tui/mcp.tui-state.descriptor.js.map +1 -0
- package/dist/tui/mcp.tui-state.impl.consumer.d.ts +5 -0
- package/dist/tui/mcp.tui-state.impl.consumer.d.ts.map +1 -0
- package/dist/tui/mcp.tui-state.impl.consumer.js +97 -0
- package/dist/tui/mcp.tui-state.impl.consumer.js.map +1 -0
- package/dist/tui/mcp.tui-state.key.d.ts +45 -0
- package/dist/tui/mcp.tui-state.key.d.ts.map +1 -0
- package/dist/tui/mcp.tui-state.key.js +25 -0
- package/dist/tui/mcp.tui-state.key.js.map +1 -0
- package/dist/types.core.d.ts +25 -0
- package/dist/types.core.d.ts.map +1 -0
- package/dist/types.core.js +8 -0
- package/dist/types.core.js.map +1 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/types.registry.d.ts +93 -0
- package/dist/types.registry.d.ts.map +1 -0
- package/dist/types.registry.js +32 -0
- package/dist/types.registry.js.map +1 -0
- 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
|