@animalabs/agent-framework 0.22.2 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -4
- package/dist/src/agent.d.ts +108 -17
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +207 -67
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server-core.js +1 -1
- package/dist/src/api/mcp-server-core.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +5 -1
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/api/types.d.ts +1 -1
- package/dist/src/api/types.d.ts.map +1 -1
- package/dist/src/branch-names.d.ts +14 -0
- package/dist/src/branch-names.d.ts.map +1 -0
- package/dist/src/branch-names.js +22 -0
- package/dist/src/branch-names.js.map +1 -0
- package/dist/src/closest-names.d.ts +28 -0
- package/dist/src/closest-names.d.ts.map +1 -0
- package/dist/src/closest-names.js +98 -0
- package/dist/src/closest-names.js.map +1 -0
- package/dist/src/code-execution/index.d.ts +1 -1
- package/dist/src/code-execution/index.d.ts.map +1 -1
- package/dist/src/code-execution/index.js.map +1 -1
- package/dist/src/code-execution/py-runner.d.ts +50 -7
- package/dist/src/code-execution/py-runner.d.ts.map +1 -1
- package/dist/src/code-execution/py-runner.js +258 -90
- package/dist/src/code-execution/py-runner.js.map +1 -1
- package/dist/src/code-execution/runtime-py.d.ts +3 -2
- package/dist/src/code-execution/runtime-py.d.ts.map +1 -1
- package/dist/src/code-execution/runtime-py.js +94 -8
- package/dist/src/code-execution/runtime-py.js.map +1 -1
- package/dist/src/code-execution/tool-definition.d.ts +17 -1
- package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
- package/dist/src/code-execution/tool-definition.js +72 -6
- package/dist/src/code-execution/tool-definition.js.map +1 -1
- package/dist/src/config-checks.d.ts +28 -0
- package/dist/src/config-checks.d.ts.map +1 -0
- package/dist/src/config-checks.js +56 -0
- package/dist/src/config-checks.js.map +1 -0
- package/dist/src/deadline-timer.d.ts +21 -0
- package/dist/src/deadline-timer.d.ts.map +1 -0
- package/dist/src/deadline-timer.js +43 -0
- package/dist/src/deadline-timer.js.map +1 -0
- package/dist/src/error-message.d.ts +30 -0
- package/dist/src/error-message.d.ts.map +1 -0
- package/dist/src/error-message.js +50 -0
- package/dist/src/error-message.js.map +1 -0
- package/dist/src/framework.d.ts +1529 -165
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +8264 -1939
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +8 -0
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +36 -5
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/held-writes.d.ts +16 -0
- package/dist/src/held-writes.d.ts.map +1 -0
- package/dist/src/held-writes.js +56 -0
- package/dist/src/held-writes.js.map +1 -0
- package/dist/src/index.d.ts +10 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +11 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/capability-grant.d.ts +4 -0
- package/dist/src/mcpl/capability-grant.d.ts.map +1 -1
- package/dist/src/mcpl/capability-grant.js +1 -1
- package/dist/src/mcpl/capability-grant.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +233 -18
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +975 -386
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.d.ts +21 -1
- package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.js +91 -30
- package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts +19 -3
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +23 -8
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts +20 -0
- package/dist/src/mcpl/convert-block.d.ts.map +1 -0
- package/dist/src/mcpl/convert-block.js +88 -0
- package/dist/src/mcpl/convert-block.js.map +1 -0
- package/dist/src/mcpl/errors.d.ts +13 -0
- package/dist/src/mcpl/errors.d.ts.map +1 -1
- package/dist/src/mcpl/errors.js +28 -0
- package/dist/src/mcpl/errors.js.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts +23 -0
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.js +83 -72
- package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +114 -0
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -0
- package/dist/src/mcpl/inbound-source.js +83 -0
- package/dist/src/mcpl/inbound-source.js.map +1 -0
- package/dist/src/mcpl/index.d.ts +7 -1
- package/dist/src/mcpl/index.d.ts.map +1 -1
- package/dist/src/mcpl/index.js +5 -1
- package/dist/src/mcpl/index.js.map +1 -1
- package/dist/src/mcpl/inference-router.d.ts +19 -13
- package/dist/src/mcpl/inference-router.d.ts.map +1 -1
- package/dist/src/mcpl/inference-router.js +155 -87
- package/dist/src/mcpl/inference-router.js.map +1 -1
- package/dist/src/mcpl/log-value.d.ts +7 -0
- package/dist/src/mcpl/log-value.d.ts.map +1 -1
- package/dist/src/mcpl/log-value.js +9 -0
- package/dist/src/mcpl/log-value.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts +227 -0
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -0
- package/dist/src/mcpl/modern-connection.js +1088 -0
- package/dist/src/mcpl/modern-connection.js.map +1 -0
- package/dist/src/mcpl/prefix-rule.d.ts +18 -0
- package/dist/src/mcpl/prefix-rule.d.ts.map +1 -0
- package/dist/src/mcpl/prefix-rule.js +25 -0
- package/dist/src/mcpl/prefix-rule.js.map +1 -0
- package/dist/src/mcpl/protocol-family.d.ts +179 -0
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -0
- package/dist/src/mcpl/protocol-family.js +697 -0
- package/dist/src/mcpl/protocol-family.js.map +1 -0
- package/dist/src/mcpl/push-coalescer.d.ts +58 -17
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +93 -32
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/push-handler.d.ts +16 -9
- package/dist/src/mcpl/push-handler.d.ts.map +1 -1
- package/dist/src/mcpl/push-handler.js +18 -83
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/reference-fetcher.d.ts +76 -13
- package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
- package/dist/src/mcpl/reference-fetcher.js +275 -66
- package/dist/src/mcpl/reference-fetcher.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +98 -18
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +170 -32
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +406 -31
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +1331 -272
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/server-registry.d.ts +35 -6
- package/dist/src/mcpl/server-registry.d.ts.map +1 -1
- package/dist/src/mcpl/server-registry.js +63 -13
- package/dist/src/mcpl/server-registry.js.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.d.ts +36 -0
- package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -0
- package/dist/src/mcpl/skip-reply-wake.js +55 -0
- package/dist/src/mcpl/skip-reply-wake.js.map +1 -0
- package/dist/src/mcpl/tool-result-normalize.d.ts +54 -0
- package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -0
- package/dist/src/mcpl/tool-result-normalize.js +232 -0
- package/dist/src/mcpl/tool-result-normalize.js.map +1 -0
- package/dist/src/mcpl/transport.d.ts +104 -12
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +315 -85
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +76 -19
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/mcpl/visible-content.d.ts +16 -0
- package/dist/src/mcpl/visible-content.d.ts.map +1 -1
- package/dist/src/mcpl/visible-content.js +26 -0
- package/dist/src/mcpl/visible-content.js.map +1 -1
- package/dist/src/module-registry.d.ts +34 -6
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +119 -31
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/api/index.d.ts +1 -1
- package/dist/src/modules/api/index.d.ts.map +1 -1
- package/dist/src/modules/api/index.js +3 -2
- package/dist/src/modules/api/index.js.map +1 -1
- package/dist/src/modules/discord/discord-js-client.d.ts +43 -2
- package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
- package/dist/src/modules/discord/discord-js-client.js +286 -67
- package/dist/src/modules/discord/discord-js-client.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts +53 -5
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +712 -186
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/discord/refusal.d.ts +14 -0
- package/dist/src/modules/discord/refusal.d.ts.map +1 -0
- package/dist/src/modules/discord/refusal.js +14 -0
- package/dist/src/modules/discord/refusal.js.map +1 -0
- package/dist/src/modules/discord/types.d.ts +27 -4
- package/dist/src/modules/discord/types.d.ts.map +1 -1
- package/dist/src/modules/health/index.d.ts.map +1 -1
- package/dist/src/modules/health/index.js +8 -3
- package/dist/src/modules/health/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts +3 -1
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +359 -127
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/history/semantic.d.ts +28 -1
- package/dist/src/modules/history/semantic.d.ts.map +1 -1
- package/dist/src/modules/history/semantic.js +177 -32
- package/dist/src/modules/history/semantic.js.map +1 -1
- package/dist/src/modules/index.d.ts +1 -1
- package/dist/src/modules/index.d.ts.map +1 -1
- package/dist/src/modules/index.js +1 -1
- package/dist/src/modules/index.js.map +1 -1
- package/dist/src/modules/unknown-tool.d.ts +10 -0
- package/dist/src/modules/unknown-tool.d.ts.map +1 -0
- package/dist/src/modules/unknown-tool.js +17 -0
- package/dist/src/modules/unknown-tool.js.map +1 -0
- package/dist/src/modules/workspace/disk-agreement.d.ts +57 -4
- package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +75 -7
- package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
- package/dist/src/modules/workspace/effects.d.ts +66 -7
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +303 -46
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +313 -45
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +1404 -304
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +315 -16
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +727 -37
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts +58 -7
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +521 -56
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +28 -5
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/modules/workspace/watcher.d.ts +49 -2
- package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
- package/dist/src/modules/workspace/watcher.js +158 -29
- package/dist/src/modules/workspace/watcher.js.map +1 -1
- package/dist/src/prose-drafts.d.ts +240 -0
- package/dist/src/prose-drafts.d.ts.map +1 -0
- package/dist/src/prose-drafts.js +362 -0
- package/dist/src/prose-drafts.js.map +1 -0
- package/dist/src/prose-segments.d.ts +21 -2
- package/dist/src/prose-segments.d.ts.map +1 -1
- package/dist/src/prose-segments.js +46 -9
- package/dist/src/prose-segments.js.map +1 -1
- package/dist/src/recovery/offline-branch.d.ts +1 -0
- package/dist/src/recovery/offline-branch.d.ts.map +1 -1
- package/dist/src/recovery/offline-branch.js +3 -1
- package/dist/src/recovery/offline-branch.js.map +1 -1
- package/dist/src/safe-slice.d.ts +5 -1
- package/dist/src/safe-slice.d.ts.map +1 -1
- package/dist/src/safe-slice.js +9 -1
- package/dist/src/safe-slice.js.map +1 -1
- package/dist/src/silent-heartbeat.d.ts +112 -39
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +100 -29
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/tool-args.d.ts +40 -0
- package/dist/src/tool-args.d.ts.map +1 -0
- package/dist/src/tool-args.js +207 -0
- package/dist/src/tool-args.js.map +1 -0
- package/dist/src/tool-image-ledger.d.ts +87 -19
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +135 -32
- package/dist/src/tool-image-ledger.js.map +1 -1
- package/dist/src/tool-presentation.d.ts +9 -0
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +266 -62
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +7 -10
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tool-result-history.d.ts +32 -3
- package/dist/src/tool-result-history.d.ts.map +1 -1
- package/dist/src/tool-result-history.js +106 -19
- package/dist/src/tool-result-history.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +25 -1
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +260 -65
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/types/agent.d.ts +44 -22
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +25 -0
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +38 -7
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/inference-log.d.ts +1 -1
- package/dist/src/types/inference-log.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +77 -14
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +97 -8
- package/dist/src/types/trace.d.ts.map +1 -1
- package/dist/src/usage/usage-tracker.d.ts +5 -0
- package/dist/src/usage/usage-tracker.d.ts.map +1 -1
- package/dist/src/usage/usage-tracker.js +35 -8
- package/dist/src/usage/usage-tracker.js.map +1 -1
- package/package.json +5 -4
- package/src/agent.ts +257 -72
- package/src/api/mcp-server-core.ts +1 -1
- package/src/api/server.ts +4 -1
- package/src/api/types.ts +1 -1
- package/src/branch-names.ts +19 -0
- package/src/closest-names.ts +94 -0
- package/src/code-execution/index.ts +1 -0
- package/src/code-execution/py-runner.ts +304 -93
- package/src/code-execution/runtime-py.ts +94 -8
- package/src/code-execution/tool-definition.ts +85 -7
- package/src/config-checks.ts +50 -0
- package/src/deadline-timer.ts +44 -0
- package/src/error-message.ts +48 -0
- package/src/framework.ts +8625 -1984
- package/src/gate/event-gate.ts +38 -5
- package/src/held-writes.ts +59 -0
- package/src/index.ts +29 -5
- package/src/mcpl/capability-grant.ts +1 -1
- package/src/mcpl/channel-registry.ts +1095 -411
- package/src/mcpl/checkpoint-manager.ts +104 -29
- package/src/mcpl/conversation-router.ts +28 -8
- package/src/mcpl/convert-block.ts +105 -0
- package/src/mcpl/errors.ts +28 -0
- package/src/mcpl/hook-orchestrator.ts +84 -77
- package/src/mcpl/inbound-source.ts +156 -0
- package/src/mcpl/index.ts +23 -1
- package/src/mcpl/inference-router.ts +179 -103
- package/src/mcpl/log-value.ts +10 -0
- package/src/mcpl/modern-connection.ts +1182 -0
- package/src/mcpl/prefix-rule.ts +32 -0
- package/src/mcpl/protocol-family.ts +691 -0
- package/src/mcpl/push-coalescer.ts +110 -36
- package/src/mcpl/push-handler.ts +20 -90
- package/src/mcpl/reference-fetcher.ts +318 -67
- package/src/mcpl/references.ts +183 -34
- package/src/mcpl/server-connection.ts +1404 -280
- package/src/mcpl/server-registry.ts +63 -14
- package/src/mcpl/skip-reply-wake.ts +73 -0
- package/src/mcpl/tool-result-normalize.ts +294 -0
- package/src/mcpl/transport.ts +320 -76
- package/src/mcpl/types.ts +77 -19
- package/src/mcpl/visible-content.ts +28 -0
- package/src/module-registry.ts +126 -36
- package/src/modules/api/index.ts +3 -2
- package/src/modules/discord/discord-js-client.ts +306 -74
- package/src/modules/discord/index.ts +737 -201
- package/src/modules/discord/refusal.ts +13 -0
- package/src/modules/discord/types.ts +27 -6
- package/src/modules/health/index.ts +8 -3
- package/src/modules/history/index.ts +410 -120
- package/src/modules/history/semantic.ts +189 -32
- package/src/modules/index.ts +1 -1
- package/src/modules/unknown-tool.ts +16 -0
- package/src/modules/workspace/disk-agreement.ts +97 -8
- package/src/modules/workspace/effects.ts +291 -36
- package/src/modules/workspace/index.ts +1469 -321
- package/src/modules/workspace/observe.ts +784 -41
- package/src/modules/workspace/reconcile.ts +619 -64
- package/src/modules/workspace/types.ts +28 -5
- package/src/modules/workspace/watcher.ts +154 -30
- package/src/prose-drafts.ts +481 -0
- package/src/prose-segments.ts +51 -8
- package/src/recovery/offline-branch.ts +4 -1
- package/src/safe-slice.ts +9 -1
- package/src/silent-heartbeat.ts +184 -59
- package/src/tool-args.ts +213 -0
- package/src/tool-image-ledger.ts +153 -34
- package/src/tool-presentation.ts +233 -42
- package/src/tool-result-guard.ts +7 -8
- package/src/tool-result-history.ts +124 -20
- package/src/tune-out/coordinator.ts +259 -65
- package/src/types/agent.ts +44 -24
- package/src/types/events.ts +25 -0
- package/src/types/framework.ts +38 -7
- package/src/types/inference-log.ts +1 -1
- package/src/types/module.ts +79 -14
- package/src/types/trace.ts +103 -8
- package/src/usage/usage-tracker.ts +32 -8
- package/dist/test/activation-request-sanitize.test.d.ts +0 -2
- package/dist/test/activation-request-sanitize.test.d.ts.map +0 -1
- package/dist/test/activation-request-sanitize.test.js +0 -89
- package/dist/test/activation-request-sanitize.test.js.map +0 -1
- package/dist/test/agent-cache-ttl.test.d.ts +0 -2
- package/dist/test/agent-cache-ttl.test.d.ts.map +0 -1
- package/dist/test/agent-cache-ttl.test.js +0 -41
- package/dist/test/agent-cache-ttl.test.js.map +0 -1
- package/dist/test/agent-runtime-settings.test.d.ts +0 -2
- package/dist/test/agent-runtime-settings.test.d.ts.map +0 -1
- package/dist/test/agent-runtime-settings.test.js +0 -1598
- package/dist/test/agent-runtime-settings.test.js.map +0 -1
- package/dist/test/api-admin-gate.test.d.ts +0 -2
- package/dist/test/api-admin-gate.test.d.ts.map +0 -1
- package/dist/test/api-admin-gate.test.js +0 -116
- package/dist/test/api-admin-gate.test.js.map +0 -1
- package/dist/test/assistant-persist-refusal.test.d.ts +0 -2
- package/dist/test/assistant-persist-refusal.test.d.ts.map +0 -1
- package/dist/test/assistant-persist-refusal.test.js +0 -63
- package/dist/test/assistant-persist-refusal.test.js.map +0 -1
- package/dist/test/batched-wake-locus.test.d.ts +0 -2
- package/dist/test/batched-wake-locus.test.d.ts.map +0 -1
- package/dist/test/batched-wake-locus.test.js +0 -166
- package/dist/test/batched-wake-locus.test.js.map +0 -1
- package/dist/test/busy-agent-scheduler.test.d.ts +0 -2
- package/dist/test/busy-agent-scheduler.test.d.ts.map +0 -1
- package/dist/test/busy-agent-scheduler.test.js +0 -92
- package/dist/test/busy-agent-scheduler.test.js.map +0 -1
- package/dist/test/channel-incoming-targeting.test.d.ts +0 -2
- package/dist/test/channel-incoming-targeting.test.d.ts.map +0 -1
- package/dist/test/channel-incoming-targeting.test.js +0 -81
- package/dist/test/channel-incoming-targeting.test.js.map +0 -1
- package/dist/test/channel-label-history.test.d.ts +0 -2
- package/dist/test/channel-label-history.test.d.ts.map +0 -1
- package/dist/test/channel-label-history.test.js +0 -424
- package/dist/test/channel-label-history.test.js.map +0 -1
- package/dist/test/channel-lifecycle-provenance.test.d.ts +0 -2
- package/dist/test/channel-lifecycle-provenance.test.d.ts.map +0 -1
- package/dist/test/channel-lifecycle-provenance.test.js +0 -47
- package/dist/test/channel-lifecycle-provenance.test.js.map +0 -1
- package/dist/test/channel-registry-routing.test.d.ts +0 -2
- package/dist/test/channel-registry-routing.test.d.ts.map +0 -1
- package/dist/test/channel-registry-routing.test.js +0 -446
- package/dist/test/channel-registry-routing.test.js.map +0 -1
- package/dist/test/channel-registry-typing.test.d.ts +0 -2
- package/dist/test/channel-registry-typing.test.d.ts.map +0 -1
- package/dist/test/channel-registry-typing.test.js +0 -91
- package/dist/test/channel-registry-typing.test.js.map +0 -1
- package/dist/test/channels-changed-respond-first.test.d.ts +0 -2
- package/dist/test/channels-changed-respond-first.test.d.ts.map +0 -1
- package/dist/test/channels-changed-respond-first.test.js +0 -74
- package/dist/test/channels-changed-respond-first.test.js.map +0 -1
- package/dist/test/checkpoint-attribution.test.d.ts +0 -2
- package/dist/test/checkpoint-attribution.test.d.ts.map +0 -1
- package/dist/test/checkpoint-attribution.test.js +0 -200
- package/dist/test/checkpoint-attribution.test.js.map +0 -1
- package/dist/test/code-execution.test.d.ts +0 -11
- package/dist/test/code-execution.test.d.ts.map +0 -1
- package/dist/test/code-execution.test.js +0 -1272
- package/dist/test/code-execution.test.js.map +0 -1
- package/dist/test/component-defaults.test.d.ts +0 -2
- package/dist/test/component-defaults.test.d.ts.map +0 -1
- package/dist/test/component-defaults.test.js +0 -54
- package/dist/test/component-defaults.test.js.map +0 -1
- package/dist/test/conversation-fork-injection-scoping.test.d.ts +0 -2
- package/dist/test/conversation-fork-injection-scoping.test.d.ts.map +0 -1
- package/dist/test/conversation-fork-injection-scoping.test.js +0 -200
- package/dist/test/conversation-fork-injection-scoping.test.js.map +0 -1
- package/dist/test/conversation-router.test.d.ts +0 -2
- package/dist/test/conversation-router.test.d.ts.map +0 -1
- package/dist/test/conversation-router.test.js +0 -174
- package/dist/test/conversation-router.test.js.map +0 -1
- package/dist/test/conversation-routing.test.d.ts +0 -7
- package/dist/test/conversation-routing.test.d.ts.map +0 -1
- package/dist/test/conversation-routing.test.js +0 -621
- package/dist/test/conversation-routing.test.js.map +0 -1
- package/dist/test/deferred-wake-integrity.test.d.ts +0 -29
- package/dist/test/deferred-wake-integrity.test.d.ts.map +0 -1
- package/dist/test/deferred-wake-integrity.test.js +0 -320
- package/dist/test/deferred-wake-integrity.test.js.map +0 -1
- package/dist/test/discord-awareness-outbox.test.d.ts +0 -2
- package/dist/test/discord-awareness-outbox.test.d.ts.map +0 -1
- package/dist/test/discord-awareness-outbox.test.js +0 -371
- package/dist/test/discord-awareness-outbox.test.js.map +0 -1
- package/dist/test/discord-history-delete-window.test.d.ts +0 -2
- package/dist/test/discord-history-delete-window.test.d.ts.map +0 -1
- package/dist/test/discord-history-delete-window.test.js +0 -81
- package/dist/test/discord-history-delete-window.test.js.map +0 -1
- package/dist/test/discord-history-sync.test.d.ts +0 -2
- package/dist/test/discord-history-sync.test.d.ts.map +0 -1
- package/dist/test/discord-history-sync.test.js +0 -56
- package/dist/test/discord-history-sync.test.js.map +0 -1
- package/dist/test/discord-typing-keepalive.test.d.ts +0 -2
- package/dist/test/discord-typing-keepalive.test.d.ts.map +0 -1
- package/dist/test/discord-typing-keepalive.test.js +0 -77
- package/dist/test/discord-typing-keepalive.test.js.map +0 -1
- package/dist/test/ephemeral-settle.test.d.ts +0 -2
- package/dist/test/ephemeral-settle.test.d.ts.map +0 -1
- package/dist/test/ephemeral-settle.test.js +0 -354
- package/dist/test/ephemeral-settle.test.js.map +0 -1
- package/dist/test/event-gate-passthrough.test.d.ts +0 -15
- package/dist/test/event-gate-passthrough.test.d.ts.map +0 -1
- package/dist/test/event-gate-passthrough.test.js +0 -289
- package/dist/test/event-gate-passthrough.test.js.map +0 -1
- package/dist/test/event-gate-policy-mutation.test.d.ts +0 -2
- package/dist/test/event-gate-policy-mutation.test.d.ts.map +0 -1
- package/dist/test/event-gate-policy-mutation.test.js +0 -137
- package/dist/test/event-gate-policy-mutation.test.js.map +0 -1
- package/dist/test/event-gate-rate-limit.test.d.ts +0 -2
- package/dist/test/event-gate-rate-limit.test.d.ts.map +0 -1
- package/dist/test/event-gate-rate-limit.test.js +0 -627
- package/dist/test/event-gate-rate-limit.test.js.map +0 -1
- package/dist/test/event-gate-script.test.d.ts +0 -2
- package/dist/test/event-gate-script.test.d.ts.map +0 -1
- package/dist/test/event-gate-script.test.js +0 -95
- package/dist/test/event-gate-script.test.js.map +0 -1
- package/dist/test/event-gate-selfwake.test.d.ts +0 -15
- package/dist/test/event-gate-selfwake.test.d.ts.map +0 -1
- package/dist/test/event-gate-selfwake.test.js +0 -125
- package/dist/test/event-gate-selfwake.test.js.map +0 -1
- package/dist/test/event-gate-tags.test.d.ts +0 -2
- package/dist/test/event-gate-tags.test.d.ts.map +0 -1
- package/dist/test/event-gate-tags.test.js +0 -76
- package/dist/test/event-gate-tags.test.js.map +0 -1
- package/dist/test/event-gate.test.d.ts +0 -2
- package/dist/test/event-gate.test.d.ts.map +0 -1
- package/dist/test/event-gate.test.js +0 -919
- package/dist/test/event-gate.test.js.map +0 -1
- package/dist/test/explicit-prose-routing.test.d.ts +0 -11
- package/dist/test/explicit-prose-routing.test.d.ts.map +0 -1
- package/dist/test/explicit-prose-routing.test.js +0 -1715
- package/dist/test/explicit-prose-routing.test.js.map +0 -1
- package/dist/test/framework.test.d.ts +0 -5
- package/dist/test/framework.test.d.ts.map +0 -1
- package/dist/test/framework.test.js +0 -1048
- package/dist/test/framework.test.js.map +0 -1
- package/dist/test/gather-context-timeout.test.d.ts +0 -2
- package/dist/test/gather-context-timeout.test.d.ts.map +0 -1
- package/dist/test/gather-context-timeout.test.js +0 -120
- package/dist/test/gather-context-timeout.test.js.map +0 -1
- package/dist/test/health-snapshot.test.d.ts +0 -2
- package/dist/test/health-snapshot.test.d.ts.map +0 -1
- package/dist/test/health-snapshot.test.js +0 -74
- package/dist/test/health-snapshot.test.js.map +0 -1
- package/dist/test/helpers/coalescing-fixture.d.ts +0 -74
- package/dist/test/helpers/coalescing-fixture.d.ts.map +0 -1
- package/dist/test/helpers/coalescing-fixture.js +0 -152
- package/dist/test/helpers/coalescing-fixture.js.map +0 -1
- package/dist/test/helpers/coalescing-kill-child.d.ts +0 -2
- package/dist/test/helpers/coalescing-kill-child.d.ts.map +0 -1
- package/dist/test/helpers/coalescing-kill-child.js +0 -24
- package/dist/test/helpers/coalescing-kill-child.js.map +0 -1
- package/dist/test/helpers/mock-membrane.d.ts +0 -53
- package/dist/test/helpers/mock-membrane.d.ts.map +0 -1
- package/dist/test/helpers/mock-membrane.js +0 -168
- package/dist/test/helpers/mock-membrane.js.map +0 -1
- package/dist/test/helpers/pytc-stop-rows.d.ts +0 -11
- package/dist/test/helpers/pytc-stop-rows.d.ts.map +0 -1
- package/dist/test/helpers/pytc-stop-rows.js +0 -143
- package/dist/test/helpers/pytc-stop-rows.js.map +0 -1
- package/dist/test/helpers/quiesce-crash-child.d.ts +0 -2
- package/dist/test/helpers/quiesce-crash-child.d.ts.map +0 -1
- package/dist/test/helpers/quiesce-crash-child.js +0 -156
- package/dist/test/helpers/quiesce-crash-child.js.map +0 -1
- package/dist/test/hide-then-turn.test.d.ts +0 -2
- package/dist/test/hide-then-turn.test.d.ts.map +0 -1
- package/dist/test/hide-then-turn.test.js +0 -101
- package/dist/test/hide-then-turn.test.js.map +0 -1
- package/dist/test/history-module-overview.test.d.ts +0 -2
- package/dist/test/history-module-overview.test.d.ts.map +0 -1
- package/dist/test/history-module-overview.test.js +0 -711
- package/dist/test/history-module-overview.test.js.map +0 -1
- package/dist/test/history-module-realstore.test.d.ts +0 -2
- package/dist/test/history-module-realstore.test.d.ts.map +0 -1
- package/dist/test/history-module-realstore.test.js +0 -363
- package/dist/test/history-module-realstore.test.js.map +0 -1
- package/dist/test/history-module-semantic.test.d.ts +0 -2
- package/dist/test/history-module-semantic.test.d.ts.map +0 -1
- package/dist/test/history-module-semantic.test.js +0 -213
- package/dist/test/history-module-semantic.test.js.map +0 -1
- package/dist/test/history-module-ux.test.d.ts +0 -2
- package/dist/test/history-module-ux.test.d.ts.map +0 -1
- package/dist/test/history-module-ux.test.js +0 -304
- package/dist/test/history-module-ux.test.js.map +0 -1
- package/dist/test/history-module.test.d.ts +0 -2
- package/dist/test/history-module.test.d.ts.map +0 -1
- package/dist/test/history-module.test.js +0 -619
- package/dist/test/history-module.test.js.map +0 -1
- package/dist/test/history-semantic-review-findings.test.d.ts +0 -2
- package/dist/test/history-semantic-review-findings.test.d.ts.map +0 -1
- package/dist/test/history-semantic-review-findings.test.js +0 -338
- package/dist/test/history-semantic-review-findings.test.js.map +0 -1
- package/dist/test/history-semantic-sync-converges.test.d.ts +0 -2
- package/dist/test/history-semantic-sync-converges.test.d.ts.map +0 -1
- package/dist/test/history-semantic-sync-converges.test.js +0 -109
- package/dist/test/history-semantic-sync-converges.test.js.map +0 -1
- package/dist/test/host-quiesce.test.d.ts +0 -2
- package/dist/test/host-quiesce.test.d.ts.map +0 -1
- package/dist/test/host-quiesce.test.js +0 -988
- package/dist/test/host-quiesce.test.js.map +0 -1
- package/dist/test/inference-exhaustion-rewind.test.d.ts +0 -2
- package/dist/test/inference-exhaustion-rewind.test.d.ts.map +0 -1
- package/dist/test/inference-exhaustion-rewind.test.js +0 -387
- package/dist/test/inference-exhaustion-rewind.test.js.map +0 -1
- package/dist/test/inference-failure-bounds.test.d.ts +0 -2
- package/dist/test/inference-failure-bounds.test.d.ts.map +0 -1
- package/dist/test/inference-failure-bounds.test.js +0 -1202
- package/dist/test/inference-failure-bounds.test.js.map +0 -1
- package/dist/test/inference-failure-observability.test.d.ts +0 -2
- package/dist/test/inference-failure-observability.test.d.ts.map +0 -1
- package/dist/test/inference-failure-observability.test.js +0 -84
- package/dist/test/inference-failure-observability.test.js.map +0 -1
- package/dist/test/journal-tool.test.d.ts +0 -2
- package/dist/test/journal-tool.test.d.ts.map +0 -1
- package/dist/test/journal-tool.test.js +0 -33
- package/dist/test/journal-tool.test.js.map +0 -1
- package/dist/test/kv-unified-open-flight-retry.test.d.ts +0 -2
- package/dist/test/kv-unified-open-flight-retry.test.d.ts.map +0 -1
- package/dist/test/kv-unified-open-flight-retry.test.js +0 -189
- package/dist/test/kv-unified-open-flight-retry.test.js.map +0 -1
- package/dist/test/kv-unified-wiring.test.d.ts +0 -2
- package/dist/test/kv-unified-wiring.test.d.ts.map +0 -1
- package/dist/test/kv-unified-wiring.test.js +0 -95
- package/dist/test/kv-unified-wiring.test.js.map +0 -1
- package/dist/test/late-events-after-stop.test.d.ts +0 -2
- package/dist/test/late-events-after-stop.test.d.ts.map +0 -1
- package/dist/test/late-events-after-stop.test.js +0 -122
- package/dist/test/late-events-after-stop.test.js.map +0 -1
- package/dist/test/liveness-watchdog.test.d.ts +0 -2
- package/dist/test/liveness-watchdog.test.d.ts.map +0 -1
- package/dist/test/liveness-watchdog.test.js +0 -94
- package/dist/test/liveness-watchdog.test.js.map +0 -1
- package/dist/test/machine-close-provenance.test.d.ts +0 -2
- package/dist/test/machine-close-provenance.test.d.ts.map +0 -1
- package/dist/test/machine-close-provenance.test.js +0 -116
- package/dist/test/machine-close-provenance.test.js.map +0 -1
- package/dist/test/maintenance-timer.test.d.ts +0 -2
- package/dist/test/maintenance-timer.test.d.ts.map +0 -1
- package/dist/test/maintenance-timer.test.js +0 -1827
- package/dist/test/maintenance-timer.test.js.map +0 -1
- package/dist/test/mcp-server-bin.test.d.ts +0 -2
- package/dist/test/mcp-server-bin.test.d.ts.map +0 -1
- package/dist/test/mcp-server-bin.test.js +0 -142
- package/dist/test/mcp-server-bin.test.js.map +0 -1
- package/dist/test/mcpl-access-provider.test.d.ts +0 -2
- package/dist/test/mcpl-access-provider.test.d.ts.map +0 -1
- package/dist/test/mcpl-access-provider.test.js +0 -20
- package/dist/test/mcpl-access-provider.test.js.map +0 -1
- package/dist/test/mcpl-awareness-barrier.test.d.ts +0 -2
- package/dist/test/mcpl-awareness-barrier.test.d.ts.map +0 -1
- package/dist/test/mcpl-awareness-barrier.test.js +0 -769
- package/dist/test/mcpl-awareness-barrier.test.js.map +0 -1
- package/dist/test/mcpl-capability-grant.test.d.ts +0 -10
- package/dist/test/mcpl-capability-grant.test.d.ts.map +0 -1
- package/dist/test/mcpl-capability-grant.test.js +0 -111
- package/dist/test/mcpl-capability-grant.test.js.map +0 -1
- package/dist/test/mcpl-capability-scoping.test.d.ts +0 -2
- package/dist/test/mcpl-capability-scoping.test.d.ts.map +0 -1
- package/dist/test/mcpl-capability-scoping.test.js +0 -266
- package/dist/test/mcpl-capability-scoping.test.js.map +0 -1
- package/dist/test/mcpl-child-env.test.d.ts +0 -2
- package/dist/test/mcpl-child-env.test.d.ts.map +0 -1
- package/dist/test/mcpl-child-env.test.js +0 -158
- package/dist/test/mcpl-child-env.test.js.map +0 -1
- package/dist/test/mcpl-coalesced-content-parity.test.d.ts +0 -2
- package/dist/test/mcpl-coalesced-content-parity.test.d.ts.map +0 -1
- package/dist/test/mcpl-coalesced-content-parity.test.js +0 -532
- package/dist/test/mcpl-coalesced-content-parity.test.js.map +0 -1
- package/dist/test/mcpl-coalescing.test.d.ts +0 -2
- package/dist/test/mcpl-coalescing.test.d.ts.map +0 -1
- package/dist/test/mcpl-coalescing.test.js +0 -1125
- package/dist/test/mcpl-coalescing.test.js.map +0 -1
- package/dist/test/mcpl-config-diagnostics.test.d.ts +0 -2
- package/dist/test/mcpl-config-diagnostics.test.d.ts.map +0 -1
- package/dist/test/mcpl-config-diagnostics.test.js +0 -452
- package/dist/test/mcpl-config-diagnostics.test.js.map +0 -1
- package/dist/test/mcpl-empty-content.test.d.ts +0 -2
- package/dist/test/mcpl-empty-content.test.d.ts.map +0 -1
- package/dist/test/mcpl-empty-content.test.js +0 -368
- package/dist/test/mcpl-empty-content.test.js.map +0 -1
- package/dist/test/mcpl-gate-roundtrip.test.d.ts +0 -2
- package/dist/test/mcpl-gate-roundtrip.test.d.ts.map +0 -1
- package/dist/test/mcpl-gate-roundtrip.test.js +0 -285
- package/dist/test/mcpl-gate-roundtrip.test.js.map +0 -1
- package/dist/test/mcpl-list-status.test.d.ts +0 -2
- package/dist/test/mcpl-list-status.test.d.ts.map +0 -1
- package/dist/test/mcpl-list-status.test.js +0 -82
- package/dist/test/mcpl-list-status.test.js.map +0 -1
- package/dist/test/mcpl-manifest-changed.test.d.ts +0 -2
- package/dist/test/mcpl-manifest-changed.test.d.ts.map +0 -1
- package/dist/test/mcpl-manifest-changed.test.js +0 -178
- package/dist/test/mcpl-manifest-changed.test.js.map +0 -1
- package/dist/test/mcpl-push-convert.test.d.ts +0 -2
- package/dist/test/mcpl-push-convert.test.d.ts.map +0 -1
- package/dist/test/mcpl-push-convert.test.js +0 -42
- package/dist/test/mcpl-push-convert.test.js.map +0 -1
- package/dist/test/mcpl-reconnect-events.test.d.ts +0 -2
- package/dist/test/mcpl-reconnect-events.test.d.ts.map +0 -1
- package/dist/test/mcpl-reconnect-events.test.js +0 -144
- package/dist/test/mcpl-reconnect-events.test.js.map +0 -1
- package/dist/test/mcpl-reconnect-reregistration.test.d.ts +0 -2
- package/dist/test/mcpl-reconnect-reregistration.test.d.ts.map +0 -1
- package/dist/test/mcpl-reconnect-reregistration.test.js +0 -238
- package/dist/test/mcpl-reconnect-reregistration.test.js.map +0 -1
- package/dist/test/mcpl-reference-fetcher.test.d.ts +0 -2
- package/dist/test/mcpl-reference-fetcher.test.d.ts.map +0 -1
- package/dist/test/mcpl-reference-fetcher.test.js +0 -427
- package/dist/test/mcpl-reference-fetcher.test.js.map +0 -1
- package/dist/test/mcpl-reference-fields.test.d.ts +0 -2
- package/dist/test/mcpl-reference-fields.test.d.ts.map +0 -1
- package/dist/test/mcpl-reference-fields.test.js +0 -149
- package/dist/test/mcpl-reference-fields.test.js.map +0 -1
- package/dist/test/mcpl-rejection-log-lines.test.d.ts +0 -2
- package/dist/test/mcpl-rejection-log-lines.test.d.ts.map +0 -1
- package/dist/test/mcpl-rejection-log-lines.test.js +0 -349
- package/dist/test/mcpl-rejection-log-lines.test.js.map +0 -1
- package/dist/test/mcpl-request-timeout.test.d.ts +0 -2
- package/dist/test/mcpl-request-timeout.test.d.ts.map +0 -1
- package/dist/test/mcpl-request-timeout.test.js +0 -138
- package/dist/test/mcpl-request-timeout.test.js.map +0 -1
- package/dist/test/mcpl-runtime-lifecycle.test.d.ts +0 -7
- package/dist/test/mcpl-runtime-lifecycle.test.d.ts.map +0 -1
- package/dist/test/mcpl-runtime-lifecycle.test.js +0 -142
- package/dist/test/mcpl-runtime-lifecycle.test.js.map +0 -1
- package/dist/test/mcpl-stderr-tail.test.d.ts +0 -2
- package/dist/test/mcpl-stderr-tail.test.d.ts.map +0 -1
- package/dist/test/mcpl-stderr-tail.test.js +0 -210
- package/dist/test/mcpl-stderr-tail.test.js.map +0 -1
- package/dist/test/mcpl-tool-class-listing.test.d.ts +0 -2
- package/dist/test/mcpl-tool-class-listing.test.d.ts.map +0 -1
- package/dist/test/mcpl-tool-class-listing.test.js +0 -147
- package/dist/test/mcpl-tool-class-listing.test.js.map +0 -1
- package/dist/test/mcpl-tool-classes.test.d.ts +0 -7
- package/dist/test/mcpl-tool-classes.test.d.ts.map +0 -1
- package/dist/test/mcpl-tool-classes.test.js +0 -80
- package/dist/test/mcpl-tool-classes.test.js.map +0 -1
- package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts +0 -2
- package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts.map +0 -1
- package/dist/test/mcpl-tool-lifecycle-e2e.test.js +0 -237
- package/dist/test/mcpl-tool-lifecycle-e2e.test.js.map +0 -1
- package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts +0 -11
- package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts.map +0 -1
- package/dist/test/mcpl-tool-lifecycle-emitter.test.js +0 -412
- package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +0 -1
- package/dist/test/mcpl-tool-lifecycle-script.test.d.ts +0 -2
- package/dist/test/mcpl-tool-lifecycle-script.test.d.ts.map +0 -1
- package/dist/test/mcpl-tool-lifecycle-script.test.js +0 -288
- package/dist/test/mcpl-tool-lifecycle-script.test.js.map +0 -1
- package/dist/test/mcpl-tool-lifecycle.test.d.ts +0 -11
- package/dist/test/mcpl-tool-lifecycle.test.d.ts.map +0 -1
- package/dist/test/mcpl-tool-lifecycle.test.js +0 -270
- package/dist/test/mcpl-tool-lifecycle.test.js.map +0 -1
- package/dist/test/mcpl-transport-close.test.d.ts +0 -2
- package/dist/test/mcpl-transport-close.test.d.ts.map +0 -1
- package/dist/test/mcpl-transport-close.test.js +0 -373
- package/dist/test/mcpl-transport-close.test.js.map +0 -1
- package/dist/test/mcpl-transport-spawn.test.d.ts +0 -2
- package/dist/test/mcpl-transport-spawn.test.d.ts.map +0 -1
- package/dist/test/mcpl-transport-spawn.test.js +0 -58
- package/dist/test/mcpl-transport-spawn.test.js.map +0 -1
- package/dist/test/mcpl-ws-transport.test.d.ts +0 -2
- package/dist/test/mcpl-ws-transport.test.d.ts.map +0 -1
- package/dist/test/mcpl-ws-transport.test.js +0 -259
- package/dist/test/mcpl-ws-transport.test.js.map +0 -1
- package/dist/test/module-context-integration.test.d.ts +0 -18
- package/dist/test/module-context-integration.test.d.ts.map +0 -1
- package/dist/test/module-context-integration.test.js +0 -181
- package/dist/test/module-context-integration.test.js.map +0 -1
- package/dist/test/offline-recovery-branch.test.d.ts +0 -2
- package/dist/test/offline-recovery-branch.test.d.ts.map +0 -1
- package/dist/test/offline-recovery-branch.test.js +0 -570
- package/dist/test/offline-recovery-branch.test.js.map +0 -1
- package/dist/test/operator-surgery.test.d.ts +0 -2
- package/dist/test/operator-surgery.test.d.ts.map +0 -1
- package/dist/test/operator-surgery.test.js +0 -595
- package/dist/test/operator-surgery.test.js.map +0 -1
- package/dist/test/ops-alert.test.d.ts +0 -2
- package/dist/test/ops-alert.test.d.ts.map +0 -1
- package/dist/test/ops-alert.test.js +0 -153
- package/dist/test/ops-alert.test.js.map +0 -1
- package/dist/test/overbudget-drain-breaker.test.d.ts +0 -2
- package/dist/test/overbudget-drain-breaker.test.d.ts.map +0 -1
- package/dist/test/overbudget-drain-breaker.test.js +0 -198
- package/dist/test/overbudget-drain-breaker.test.js.map +0 -1
- package/dist/test/pack-installs-alone.test.d.ts +0 -2
- package/dist/test/pack-installs-alone.test.d.ts.map +0 -1
- package/dist/test/pack-installs-alone.test.js +0 -284
- package/dist/test/pack-installs-alone.test.js.map +0 -1
- package/dist/test/per-agent-delivery.test.d.ts +0 -2
- package/dist/test/per-agent-delivery.test.d.ts.map +0 -1
- package/dist/test/per-agent-delivery.test.js +0 -108
- package/dist/test/per-agent-delivery.test.js.map +0 -1
- package/dist/test/physical-window-restart.test.d.ts +0 -16
- package/dist/test/physical-window-restart.test.d.ts.map +0 -1
- package/dist/test/physical-window-restart.test.js +0 -487
- package/dist/test/physical-window-restart.test.js.map +0 -1
- package/dist/test/placed-reaction-baseline.test.d.ts +0 -2
- package/dist/test/placed-reaction-baseline.test.d.ts.map +0 -1
- package/dist/test/placed-reaction-baseline.test.js +0 -312
- package/dist/test/placed-reaction-baseline.test.js.map +0 -1
- package/dist/test/present-while-acting.test.d.ts +0 -19
- package/dist/test/present-while-acting.test.d.ts.map +0 -1
- package/dist/test/present-while-acting.test.js +0 -1649
- package/dist/test/present-while-acting.test.js.map +0 -1
- package/dist/test/prose-segments.test.d.ts +0 -2
- package/dist/test/prose-segments.test.d.ts.map +0 -1
- package/dist/test/prose-segments.test.js +0 -52
- package/dist/test/prose-segments.test.js.map +0 -1
- package/dist/test/prose-stream-router.test.d.ts +0 -2
- package/dist/test/prose-stream-router.test.d.ts.map +0 -1
- package/dist/test/prose-stream-router.test.js +0 -171
- package/dist/test/prose-stream-router.test.js.map +0 -1
- package/dist/test/provider-acceleration-cooldown.test.d.ts +0 -2
- package/dist/test/provider-acceleration-cooldown.test.d.ts.map +0 -1
- package/dist/test/provider-acceleration-cooldown.test.js +0 -435
- package/dist/test/provider-acceleration-cooldown.test.js.map +0 -1
- package/dist/test/provider-waits.test.d.ts +0 -2
- package/dist/test/provider-waits.test.d.ts.map +0 -1
- package/dist/test/provider-waits.test.js +0 -2259
- package/dist/test/provider-waits.test.js.map +0 -1
- package/dist/test/puppet-tool-call.test.d.ts +0 -2
- package/dist/test/puppet-tool-call.test.d.ts.map +0 -1
- package/dist/test/puppet-tool-call.test.js +0 -226
- package/dist/test/puppet-tool-call.test.js.map +0 -1
- package/dist/test/puppet-turn-reservation.test.d.ts +0 -2
- package/dist/test/puppet-turn-reservation.test.d.ts.map +0 -1
- package/dist/test/puppet-turn-reservation.test.js +0 -139
- package/dist/test/puppet-turn-reservation.test.js.map +0 -1
- package/dist/test/reaction-baseline-platform.test.d.ts +0 -2
- package/dist/test/reaction-baseline-platform.test.d.ts.map +0 -1
- package/dist/test/reaction-baseline-platform.test.js +0 -124
- package/dist/test/reaction-baseline-platform.test.js.map +0 -1
- package/dist/test/record-journal.test.d.ts +0 -6
- package/dist/test/record-journal.test.d.ts.map +0 -1
- package/dist/test/record-journal.test.js +0 -269
- package/dist/test/record-journal.test.js.map +0 -1
- package/dist/test/refusal-reaction-baseline.test.d.ts +0 -2
- package/dist/test/refusal-reaction-baseline.test.d.ts.map +0 -1
- package/dist/test/refusal-reaction-baseline.test.js +0 -81
- package/dist/test/refusal-reaction-baseline.test.js.map +0 -1
- package/dist/test/refusal-rewind.test.d.ts +0 -2
- package/dist/test/refusal-rewind.test.d.ts.map +0 -1
- package/dist/test/refusal-rewind.test.js +0 -117
- package/dist/test/refusal-rewind.test.js.map +0 -1
- package/dist/test/refusal-single-call.test.d.ts +0 -2
- package/dist/test/refusal-single-call.test.d.ts.map +0 -1
- package/dist/test/refusal-single-call.test.js +0 -177
- package/dist/test/refusal-single-call.test.js.map +0 -1
- package/dist/test/release-bump.test.d.ts +0 -2
- package/dist/test/release-bump.test.d.ts.map +0 -1
- package/dist/test/release-bump.test.js +0 -248
- package/dist/test/release-bump.test.js.map +0 -1
- package/dist/test/release-changelog.test.d.ts +0 -2
- package/dist/test/release-changelog.test.d.ts.map +0 -1
- package/dist/test/release-changelog.test.js +0 -159
- package/dist/test/release-changelog.test.js.map +0 -1
- package/dist/test/release-holds.test.d.ts +0 -2
- package/dist/test/release-holds.test.d.ts.map +0 -1
- package/dist/test/release-holds.test.js +0 -456
- package/dist/test/release-holds.test.js.map +0 -1
- package/dist/test/same-round-think-policy.test.d.ts +0 -2
- package/dist/test/same-round-think-policy.test.d.ts.map +0 -1
- package/dist/test/same-round-think-policy.test.js +0 -853
- package/dist/test/same-round-think-policy.test.js.map +0 -1
- package/dist/test/save-recent-image.test.d.ts +0 -26
- package/dist/test/save-recent-image.test.d.ts.map +0 -1
- package/dist/test/save-recent-image.test.js +0 -974
- package/dist/test/save-recent-image.test.js.map +0 -1
- package/dist/test/shutdown-errors.test.d.ts +0 -2
- package/dist/test/shutdown-errors.test.d.ts.map +0 -1
- package/dist/test/shutdown-errors.test.js +0 -662
- package/dist/test/shutdown-errors.test.js.map +0 -1
- package/dist/test/silent-heartbeat-rows.test.d.ts +0 -2
- package/dist/test/silent-heartbeat-rows.test.d.ts.map +0 -1
- package/dist/test/silent-heartbeat-rows.test.js +0 -451
- package/dist/test/silent-heartbeat-rows.test.js.map +0 -1
- package/dist/test/silent-heartbeat.test.d.ts +0 -2
- package/dist/test/silent-heartbeat.test.d.ts.map +0 -1
- package/dist/test/silent-heartbeat.test.js +0 -165
- package/dist/test/silent-heartbeat.test.js.map +0 -1
- package/dist/test/state-scaling-e2e.test.d.ts +0 -22
- package/dist/test/state-scaling-e2e.test.d.ts.map +0 -1
- package/dist/test/state-scaling-e2e.test.js +0 -206
- package/dist/test/state-scaling-e2e.test.js.map +0 -1
- package/dist/test/state-scaling.test.d.ts +0 -12
- package/dist/test/state-scaling.test.d.ts.map +0 -1
- package/dist/test/state-scaling.test.js +0 -251
- package/dist/test/state-scaling.test.js.map +0 -1
- package/dist/test/stop-with-host-operations.test.d.ts +0 -20
- package/dist/test/stop-with-host-operations.test.d.ts.map +0 -1
- package/dist/test/stop-with-host-operations.test.js +0 -1174
- package/dist/test/stop-with-host-operations.test.js.map +0 -1
- package/dist/test/stop-with-work-in-flight.test.d.ts +0 -23
- package/dist/test/stop-with-work-in-flight.test.d.ts.map +0 -1
- package/dist/test/stop-with-work-in-flight.test.js +0 -784
- package/dist/test/stop-with-work-in-flight.test.js.map +0 -1
- package/dist/test/surgery-marks-receipt.test.d.ts +0 -2
- package/dist/test/surgery-marks-receipt.test.d.ts.map +0 -1
- package/dist/test/surgery-marks-receipt.test.js +0 -540
- package/dist/test/surgery-marks-receipt.test.js.map +0 -1
- package/dist/test/thinking-token-stamp.test.d.ts +0 -12
- package/dist/test/thinking-token-stamp.test.d.ts.map +0 -1
- package/dist/test/thinking-token-stamp.test.js +0 -129
- package/dist/test/thinking-token-stamp.test.js.map +0 -1
- package/dist/test/timezone.test.d.ts +0 -2
- package/dist/test/timezone.test.d.ts.map +0 -1
- package/dist/test/timezone.test.js +0 -18
- package/dist/test/timezone.test.js.map +0 -1
- package/dist/test/tool-batch-hook.test.d.ts +0 -8
- package/dist/test/tool-batch-hook.test.d.ts.map +0 -1
- package/dist/test/tool-batch-hook.test.js +0 -275
- package/dist/test/tool-batch-hook.test.js.map +0 -1
- package/dist/test/tool-policy.test.d.ts +0 -2
- package/dist/test/tool-policy.test.d.ts.map +0 -1
- package/dist/test/tool-policy.test.js +0 -51
- package/dist/test/tool-policy.test.js.map +0 -1
- package/dist/test/tool-presentation.test.d.ts +0 -2
- package/dist/test/tool-presentation.test.d.ts.map +0 -1
- package/dist/test/tool-presentation.test.js +0 -268
- package/dist/test/tool-presentation.test.js.map +0 -1
- package/dist/test/tool-result-content.test.d.ts +0 -2
- package/dist/test/tool-result-content.test.d.ts.map +0 -1
- package/dist/test/tool-result-content.test.js +0 -37
- package/dist/test/tool-result-content.test.js.map +0 -1
- package/dist/test/tool-result-guard.test.d.ts +0 -2
- package/dist/test/tool-result-guard.test.d.ts.map +0 -1
- package/dist/test/tool-result-guard.test.js +0 -3729
- package/dist/test/tool-result-guard.test.js.map +0 -1
- package/dist/test/tool-result-history.test.d.ts +0 -7
- package/dist/test/tool-result-history.test.d.ts.map +0 -1
- package/dist/test/tool-result-history.test.js +0 -156
- package/dist/test/tool-result-history.test.js.map +0 -1
- package/dist/test/tool-result-spill.test.d.ts +0 -13
- package/dist/test/tool-result-spill.test.d.ts.map +0 -1
- package/dist/test/tool-result-spill.test.js +0 -586
- package/dist/test/tool-result-spill.test.js.map +0 -1
- package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts +0 -2
- package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts.map +0 -1
- package/dist/test/tool-wrapper-prose-guard-integration.test.js +0 -485
- package/dist/test/tool-wrapper-prose-guard-integration.test.js.map +0 -1
- package/dist/test/tool-wrapper-prose-guard.test.d.ts +0 -2
- package/dist/test/tool-wrapper-prose-guard.test.d.ts.map +0 -1
- package/dist/test/tool-wrapper-prose-guard.test.js +0 -34
- package/dist/test/tool-wrapper-prose-guard.test.js.map +0 -1
- package/dist/test/tools-observe-parser-conformance.test.d.ts +0 -2
- package/dist/test/tools-observe-parser-conformance.test.d.ts.map +0 -1
- package/dist/test/tools-observe-parser-conformance.test.js +0 -129
- package/dist/test/tools-observe-parser-conformance.test.js.map +0 -1
- package/dist/test/trunk-channel-routing.test.d.ts +0 -19
- package/dist/test/trunk-channel-routing.test.d.ts.map +0 -1
- package/dist/test/trunk-channel-routing.test.js +0 -652
- package/dist/test/trunk-channel-routing.test.js.map +0 -1
- package/dist/test/tune-out-e2e.test.d.ts +0 -2
- package/dist/test/tune-out-e2e.test.d.ts.map +0 -1
- package/dist/test/tune-out-e2e.test.js +0 -142
- package/dist/test/tune-out-e2e.test.js.map +0 -1
- package/dist/test/tune-out-gate-composition.test.d.ts +0 -2
- package/dist/test/tune-out-gate-composition.test.d.ts.map +0 -1
- package/dist/test/tune-out-gate-composition.test.js +0 -221
- package/dist/test/tune-out-gate-composition.test.js.map +0 -1
- package/dist/test/tune-out-lifecycle.test.d.ts +0 -2
- package/dist/test/tune-out-lifecycle.test.d.ts.map +0 -1
- package/dist/test/tune-out-lifecycle.test.js +0 -121
- package/dist/test/tune-out-lifecycle.test.js.map +0 -1
- package/dist/test/turn-trigger-provenance.test.d.ts +0 -2
- package/dist/test/turn-trigger-provenance.test.d.ts.map +0 -1
- package/dist/test/turn-trigger-provenance.test.js +0 -137
- package/dist/test/turn-trigger-provenance.test.js.map +0 -1
- package/dist/test/unconvertible-tool-result.test.d.ts +0 -2
- package/dist/test/unconvertible-tool-result.test.d.ts.map +0 -1
- package/dist/test/unconvertible-tool-result.test.js +0 -358
- package/dist/test/unconvertible-tool-result.test.js.map +0 -1
- package/dist/test/unsent-compiles.test.d.ts +0 -2
- package/dist/test/unsent-compiles.test.d.ts.map +0 -1
- package/dist/test/unsent-compiles.test.js +0 -139
- package/dist/test/unsent-compiles.test.js.map +0 -1
- package/dist/test/usage-accounting.test.d.ts +0 -2
- package/dist/test/usage-accounting.test.d.ts.map +0 -1
- package/dist/test/usage-accounting.test.js +0 -13
- package/dist/test/usage-accounting.test.js.map +0 -1
- package/dist/test/usage-tracker.test.d.ts +0 -2
- package/dist/test/usage-tracker.test.d.ts.map +0 -1
- package/dist/test/usage-tracker.test.js +0 -129
- package/dist/test/usage-tracker.test.js.map +0 -1
- package/dist/test/utils-surface.test.d.ts +0 -2
- package/dist/test/utils-surface.test.d.ts.map +0 -1
- package/dist/test/utils-surface.test.js +0 -145
- package/dist/test/utils-surface.test.js.map +0 -1
- package/dist/test/workspace-branch-continuation.test.d.ts +0 -15
- package/dist/test/workspace-branch-continuation.test.d.ts.map +0 -1
- package/dist/test/workspace-branch-continuation.test.js +0 -183
- package/dist/test/workspace-branch-continuation.test.js.map +0 -1
- package/dist/test/workspace-materialize-freshness.test.d.ts +0 -16
- package/dist/test/workspace-materialize-freshness.test.d.ts.map +0 -1
- package/dist/test/workspace-materialize-freshness.test.js +0 -252
- package/dist/test/workspace-materialize-freshness.test.js.map +0 -1
- package/dist/test/workspace-path-containment.test.d.ts +0 -19
- package/dist/test/workspace-path-containment.test.d.ts.map +0 -1
- package/dist/test/workspace-path-containment.test.js +0 -91
- package/dist/test/workspace-path-containment.test.js.map +0 -1
- package/dist/test/workspace-read-file-from-disk.test.d.ts +0 -17
- package/dist/test/workspace-read-file-from-disk.test.d.ts.map +0 -1
- package/dist/test/workspace-read-file-from-disk.test.js +0 -174
- package/dist/test/workspace-read-file-from-disk.test.js.map +0 -1
- package/dist/test/workspace-read-image.test.d.ts +0 -2
- package/dist/test/workspace-read-image.test.d.ts.map +0 -1
- package/dist/test/workspace-read-image.test.js +0 -597
- package/dist/test/workspace-read-image.test.js.map +0 -1
- package/dist/test/workspace-read-text.test.d.ts +0 -2
- package/dist/test/workspace-read-text.test.d.ts.map +0 -1
- package/dist/test/workspace-read-text.test.js +0 -101
- package/dist/test/workspace-read-text.test.js.map +0 -1
- package/dist/test/workspace-restore-order.test.d.ts +0 -16
- package/dist/test/workspace-restore-order.test.d.ts.map +0 -1
- package/dist/test/workspace-restore-order.test.js +0 -140
- package/dist/test/workspace-restore-order.test.js.map +0 -1
- package/dist/test/workspace-three-way.test.d.ts +0 -12
- package/dist/test/workspace-three-way.test.d.ts.map +0 -1
- package/dist/test/workspace-three-way.test.js +0 -4094
- package/dist/test/workspace-three-way.test.js.map +0 -1
- package/dist/test/workspace-watcher.test.d.ts +0 -2
- package/dist/test/workspace-watcher.test.d.ts.map +0 -1
- package/dist/test/workspace-watcher.test.js +0 -281
- package/dist/test/workspace-watcher.test.js.map +0 -1
- package/dist/test/xml-round-persist.test.d.ts +0 -19
- package/dist/test/xml-round-persist.test.d.ts.map +0 -1
- package/dist/test/xml-round-persist.test.js +0 -235
- package/dist/test/xml-round-persist.test.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,1182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModernMcpConnection: a server that speaks modern MCP (2026-07-28), reached
|
|
3
|
+
* over stdio or Streamable HTTP through the official SDK client.
|
|
4
|
+
*
|
|
5
|
+
* Responsibilities divide like this. The SDK owns the modern wire:
|
|
6
|
+
* `server/discover`, the per-request `_meta` envelope, `resultType`,
|
|
7
|
+
* continuation of `input_required` results, cancellation, list-change
|
|
8
|
+
* subscriptions, and the HTTP binding's headers, auth retry and
|
|
9
|
+
* header-mismatch handling. This class owns what Connectome decides:
|
|
10
|
+
* - **stdio** runs through Connectome's own spawner, so the child gets the
|
|
11
|
+
* same env allowlist, `inheritEnv` and stderr surfacing as a legacy server,
|
|
12
|
+
* and it starts once per connect. The SDK's own stdio transport starts a
|
|
13
|
+
* throwaway second copy for discovery.
|
|
14
|
+
* - **HTTP credentials** come from `token`/`accessProvider`: a cached bearer
|
|
15
|
+
* token, re-resolved once when the server answers 401.
|
|
16
|
+
* - **One deadline per tool call** (`requestTimeoutMs`, always positive)
|
|
17
|
+
* bounds every leg of the call and the pauses between them. At the
|
|
18
|
+
* deadline the SDK requests cancellation of a leg in flight; between
|
|
19
|
+
* continuation rounds nothing is in flight. Either way the outcome is
|
|
20
|
+
* unknown. Calls are never replayed. The protocol has no cancel for a
|
|
21
|
+
* call paused at `input_required`, so whatever the server holds for it,
|
|
22
|
+
* beyond the `requestState` it gave this client, stays until the server
|
|
23
|
+
* lets it go.
|
|
24
|
+
* - **Request outcomes from the wire.** A failed call is classified by what
|
|
25
|
+
* crossed Connectome's transport boundary, never by the error's class. The
|
|
26
|
+
* SDK raises the same error classes locally (an invalid outputSchema before
|
|
27
|
+
* dispatch, output validation after a successful result) as it does for
|
|
28
|
+
* server answers. A call's answer is the server's final one: an
|
|
29
|
+
* `input_required` answer asks for another round, so a call that ends
|
|
30
|
+
* after one has no answer.
|
|
31
|
+
* - **Lifetime.** On a lost transport the connection restarts with the same
|
|
32
|
+
* backoff settings as legacy, when `reconnect` is set, once the lost
|
|
33
|
+
* launch is reaped, so a second child never runs beside the first. A
|
|
34
|
+
* stdio child that stays alive but silently stops reading its stdin is
|
|
35
|
+
* no loss the pipes show: each call's deadline is the only bound on it.
|
|
36
|
+
* A server that shares no revision with this client is a configuration
|
|
37
|
+
* verdict, as for legacy (`McplProtocolVersionError`): it is never
|
|
38
|
+
* retried. List-change
|
|
39
|
+
* awareness is kept while the connection lives: a subscription the server
|
|
40
|
+
* advertises but that didn't open, or that ended, is reopened. Each connect
|
|
41
|
+
* is a generation, so a superseded client never touches a newer one.
|
|
42
|
+
* `close()` owns a connect in flight too.
|
|
43
|
+
*
|
|
44
|
+
* Nothing here is MCPL: a modern server has no grant, no planes and no
|
|
45
|
+
* server→host requests. The framework uses this connection only through its
|
|
46
|
+
* tool paths, and the MCPL machinery never sees it.
|
|
47
|
+
*/
|
|
48
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
49
|
+
import { EventEmitter } from 'node:events';
|
|
50
|
+
|
|
51
|
+
import {
|
|
52
|
+
Client,
|
|
53
|
+
InsufficientScopeError,
|
|
54
|
+
SdkError,
|
|
55
|
+
SdkErrorCode,
|
|
56
|
+
SdkHttpError,
|
|
57
|
+
StreamableHTTPClientTransport,
|
|
58
|
+
UnsupportedProtocolVersionError,
|
|
59
|
+
parseJSONRPCMessage,
|
|
60
|
+
type AuthProvider,
|
|
61
|
+
type CallToolResult,
|
|
62
|
+
type JSONRPCMessage,
|
|
63
|
+
type McpSubscription,
|
|
64
|
+
type MessageExtraInfo,
|
|
65
|
+
type Transport,
|
|
66
|
+
type TransportSendOptions,
|
|
67
|
+
} from '@modelcontextprotocol/client';
|
|
68
|
+
|
|
69
|
+
import { StdioTransport, type McplTransport } from './transport.js';
|
|
70
|
+
import { McplProtocolVersionError, McplRequestError, McplUnreapedLaunchError } from './server-connection.js';
|
|
71
|
+
import { LEGACY_MCP_PROTOCOL_VERSION, MAX_TIMER_MS, MODERN_MCP_PROTOCOL_VERSION, checkServerConfig, describeCredentialError, noteDialSent, redactServerText, serverTextRedactor } from './protocol-family.js';
|
|
72
|
+
import type { McplServerConfig, McpToolDefinition } from './types.js';
|
|
73
|
+
import { boundedForLog } from './errors.js';
|
|
74
|
+
import { logDetail, logValue, logValueRedacted } from './log-value.js';
|
|
75
|
+
|
|
76
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
|
|
77
|
+
/** Bound on establishing the connection (`server/discover`), like the legacy
|
|
78
|
+
* engine's initialize timeout. */
|
|
79
|
+
const CONNECT_TIMEOUT_MS = 30_000;
|
|
80
|
+
/** Inventory listing has its own bound: it is host work, not a resident's call. */
|
|
81
|
+
const LIST_TIMEOUT_MS = 30_000;
|
|
82
|
+
/** Startup stderr kept for the first listener. */
|
|
83
|
+
const STDERR_BACKLOG_LINES = 200;
|
|
84
|
+
|
|
85
|
+
/** A modern tool result as the framework consumes it. `structuredContent` is
|
|
86
|
+
* kept by presence: `false`, `0` and `null` are values, not absence. */
|
|
87
|
+
export interface ModernToolCallResult {
|
|
88
|
+
content: CallToolResult['content'];
|
|
89
|
+
isError?: boolean;
|
|
90
|
+
structuredContent?: unknown;
|
|
91
|
+
_meta?: Record<string, unknown>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* What crossed Connectome's transport boundary for one logical request: were
|
|
96
|
+
* any of its requests handed to the transport, and how did the server answer
|
|
97
|
+
* the most recent one? A logical call can take several legs: a
|
|
98
|
+
* header-mismatch retry, or the rounds of an `input_required` continuation.
|
|
99
|
+
* Only an error or a `complete` result is the server's final answer.
|
|
100
|
+
* `input-required` asks for another round, and a result of any other type,
|
|
101
|
+
* or of none (`unrecognized`), doesn't say the call finished.
|
|
102
|
+
*/
|
|
103
|
+
interface WireRecord {
|
|
104
|
+
readonly method: string;
|
|
105
|
+
handedOff: boolean;
|
|
106
|
+
currentId?: string | number;
|
|
107
|
+
/** The current leg's answer. */
|
|
108
|
+
answer: 'none' | 'error' | 'complete' | 'input-required' | 'unrecognized';
|
|
109
|
+
error?: { code: number; message: string; data?: unknown };
|
|
110
|
+
/** An earlier leg's answer asked for another round (or wasn't a complete
|
|
111
|
+
* result): the server may have acted on the call, whatever a later leg
|
|
112
|
+
* meets. A new leg's send never clears it. */
|
|
113
|
+
continued: boolean;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A send that Connectome's own transport refused before writing anything (its
|
|
118
|
+
* stdio line is closed): nothing of that request reached the server. `pipe`:
|
|
119
|
+
* the child's stdin or stdout had failed, so this host can no longer write to
|
|
120
|
+
* that server at all.
|
|
121
|
+
*/
|
|
122
|
+
class NotWrittenError extends Error {
|
|
123
|
+
readonly pipe: boolean;
|
|
124
|
+
constructor(message: string, pipe = false) {
|
|
125
|
+
super(message);
|
|
126
|
+
this.name = 'NotWrittenError';
|
|
127
|
+
this.pipe = pipe;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The logical request a send belongs to. The SDK hands each request to the
|
|
132
|
+
* transport inside the caller's async chain, so the scope reaches `send`. */
|
|
133
|
+
const wireScope = new AsyncLocalStorage<WireRecord>();
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The boundary between the SDK and the actual transport. It records, for the
|
|
137
|
+
* logical request in scope, each request of the observed method that it
|
|
138
|
+
* hands on, and the answer that comes back for it. Everything else passes
|
|
139
|
+
* through untouched: the SDK sees the inner transport's own members.
|
|
140
|
+
*/
|
|
141
|
+
class ObservedTransport implements Transport {
|
|
142
|
+
onclose?: () => void;
|
|
143
|
+
onerror?: (error: Error) => void;
|
|
144
|
+
onmessage?: <T extends JSONRPCMessage>(message: T, extra?: MessageExtraInfo) => void;
|
|
145
|
+
private readonly awaiting = new Map<string | number, WireRecord>();
|
|
146
|
+
|
|
147
|
+
constructor(private readonly inner: Transport) {}
|
|
148
|
+
|
|
149
|
+
get sessionId(): string | undefined {
|
|
150
|
+
return this.inner.sessionId;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
get hasPerRequestStream(): boolean | undefined {
|
|
154
|
+
return this.inner.hasPerRequestStream;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
setProtocolVersion(version: string): void {
|
|
158
|
+
this.inner.setProtocolVersion?.(version);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
setSupportedProtocolVersions(versions: string[]): void {
|
|
162
|
+
this.inner.setSupportedProtocolVersions?.(versions);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async start(): Promise<void> {
|
|
166
|
+
this.inner.onmessage = (message, extra) => {
|
|
167
|
+
// An answer to a request in scope is handed to the SDK inside that
|
|
168
|
+
// request's scope. The SDK drives an input_required continuation from
|
|
169
|
+
// this very callback, outside the caller's async chain, and the next
|
|
170
|
+
// leg's send must still be recorded against the right call.
|
|
171
|
+
const record = this.observe(message);
|
|
172
|
+
if (record) wireScope.run(record, () => this.onmessage?.(message, extra));
|
|
173
|
+
else this.onmessage?.(message, extra);
|
|
174
|
+
};
|
|
175
|
+
this.inner.onerror = (error) => this.onerror?.(error);
|
|
176
|
+
this.inner.onclose = () => this.onclose?.();
|
|
177
|
+
await this.inner.start();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async send(message: JSONRPCMessage, options?: TransportSendOptions): Promise<void> {
|
|
181
|
+
const record = wireScope.getStore();
|
|
182
|
+
let undo: (() => void) | undefined;
|
|
183
|
+
if (record && 'method' in message && 'id' in message && message.method === record.method) {
|
|
184
|
+
const prior = { handedOff: record.handedOff, currentId: record.currentId, answer: record.answer, error: record.error };
|
|
185
|
+
// Handed off from here on: past this point nothing proves it unsent,
|
|
186
|
+
// unless the transport says it wrote nothing.
|
|
187
|
+
record.handedOff = true;
|
|
188
|
+
record.currentId = message.id;
|
|
189
|
+
record.answer = 'none';
|
|
190
|
+
record.error = undefined;
|
|
191
|
+
this.awaiting.set(message.id, record);
|
|
192
|
+
undo = () => {
|
|
193
|
+
this.awaiting.delete(message.id);
|
|
194
|
+
Object.assign(record, prior);
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
await this.inner.send(message, options);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
// Refused before any write: this leg never reached the server, so the
|
|
201
|
+
// record is as it was before it. Any other throw (an HTTP send can fail
|
|
202
|
+
// after part of the request went out) leaves it handed off.
|
|
203
|
+
if (error instanceof NotWrittenError) undo?.();
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async close(): Promise<void> {
|
|
209
|
+
await this.inner.close();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Drop a finished request's entries (unanswered legs included). */
|
|
213
|
+
forget(record: WireRecord): void {
|
|
214
|
+
for (const [id, entry] of this.awaiting) if (entry === record) this.awaiting.delete(id);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Record an answer to a request in scope; returns its record when it
|
|
218
|
+
* answers that record's current leg. */
|
|
219
|
+
private observe(message: JSONRPCMessage): WireRecord | undefined {
|
|
220
|
+
if (typeof message !== 'object' || message === null) return undefined;
|
|
221
|
+
if (!('id' in message) || (!('result' in message) && !('error' in message))) return undefined;
|
|
222
|
+
const record = this.awaiting.get(message.id as string | number);
|
|
223
|
+
if (!record) return undefined;
|
|
224
|
+
this.awaiting.delete(message.id as string | number);
|
|
225
|
+
// A superseded leg's late answer says nothing about the current one.
|
|
226
|
+
if (record.currentId !== message.id) return undefined;
|
|
227
|
+
if ('error' in message) {
|
|
228
|
+
record.answer = 'error';
|
|
229
|
+
record.error = message.error as WireRecord['error'];
|
|
230
|
+
} else {
|
|
231
|
+
const result = (message as { result?: unknown }).result;
|
|
232
|
+
const type = typeof result === 'object' && result !== null ? (result as { resultType?: unknown }).resultType : undefined;
|
|
233
|
+
record.answer = type === 'complete' ? 'complete' : type === 'input_required' ? 'input-required' : 'unrecognized';
|
|
234
|
+
if (record.answer !== 'complete') record.continued = true;
|
|
235
|
+
}
|
|
236
|
+
return record;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* The diagnostic for a line that is JSON but not a JSON-RPC message: the
|
|
242
|
+
* line's first 200 characters, after the configuration's credentials are
|
|
243
|
+
* replaced, so a credential across the cut can't leave its first part.
|
|
244
|
+
*/
|
|
245
|
+
export function malformedLineDiagnostic(
|
|
246
|
+
config: McplServerConfig,
|
|
247
|
+
text: string,
|
|
248
|
+
error: unknown,
|
|
249
|
+
recorded: (text: unknown) => string = serverTextRedactor(config),
|
|
250
|
+
): string {
|
|
251
|
+
// The parser's message can quote the line, so it's redacted too.
|
|
252
|
+
const shown = recorded(text);
|
|
253
|
+
const why = recorded(error instanceof Error ? error.message.split('\n')[0] : error);
|
|
254
|
+
return `MCP server "${config.id}" sent a malformed JSON-RPC message (${shown.length > 200 ? `${shown.slice(0, 200)}…` : shown}): ${why}`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* SDK `Transport` over Connectome's stdio spawner. Framing is the same
|
|
259
|
+
* newline-delimited JSON-RPC as legacy. Lines that are not JSON are ignored,
|
|
260
|
+
* as the legacy engine ignores them. Because this is a custom transport, the
|
|
261
|
+
* SDK runs `server/discover` on this very process instead of starting a
|
|
262
|
+
* throwaway copy, and cancels with `notifications/cancelled`, which is the
|
|
263
|
+
* stdio binding's mechanism.
|
|
264
|
+
*/
|
|
265
|
+
class SpawnerStdioTransport implements Transport {
|
|
266
|
+
onclose?: () => void;
|
|
267
|
+
onerror?: (error: Error) => void;
|
|
268
|
+
onmessage?: <T extends JSONRPCMessage>(message: T, extra?: MessageExtraInfo) => void;
|
|
269
|
+
private line: McplTransport | null = null;
|
|
270
|
+
/** The spawn start() awaits, so a close() made meanwhile closes its child. */
|
|
271
|
+
private spawning: Promise<McplTransport> | null = null;
|
|
272
|
+
|
|
273
|
+
/** Built once for the transport: every malformed line uses it. */
|
|
274
|
+
private readonly recorded: (text: unknown) => string;
|
|
275
|
+
|
|
276
|
+
constructor(
|
|
277
|
+
private readonly config: McplServerConfig,
|
|
278
|
+
private readonly onStderr: (line: string) => void,
|
|
279
|
+
/** The child's pipe failed and it outlived SIGKILL too, so the line
|
|
280
|
+
* stays open, owning it (the line's 'unreaped'): `reason` names the
|
|
281
|
+
* pipe's error. */
|
|
282
|
+
private readonly onUnreaped: (reason: string) => void,
|
|
283
|
+
) {
|
|
284
|
+
this.recorded = serverTextRedactor(config);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async start(): Promise<void> {
|
|
288
|
+
// A command that can't be spawned rejects here, with its spawn error, so
|
|
289
|
+
// the connect fails with it rather than waiting out its discover probe.
|
|
290
|
+
this.spawning = StdioTransport.spawn(this.config);
|
|
291
|
+
const line = await this.spawning;
|
|
292
|
+
this.line = line;
|
|
293
|
+
line.on('line', (text: string) => {
|
|
294
|
+
// A line that isn't JSON is ignored, as the legacy engine ignores it.
|
|
295
|
+
// A line that is JSON but not a JSON-RPC message (`null`, an array, a
|
|
296
|
+
// bare value) is a diagnostic. It must never reach a handler that
|
|
297
|
+
// assumes the shape: thrown from this callback, it would end the host.
|
|
298
|
+
let parsed: unknown;
|
|
299
|
+
try {
|
|
300
|
+
parsed = JSON.parse(text);
|
|
301
|
+
} catch {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
let message: JSONRPCMessage;
|
|
305
|
+
try {
|
|
306
|
+
message = parseJSONRPCMessage(parsed);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
this.onerror?.(new Error(malformedLineDiagnostic(this.config, text, error, this.recorded)));
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
this.onmessage?.(message);
|
|
312
|
+
});
|
|
313
|
+
line.on('stderr', (text: string) => this.onStderr(text));
|
|
314
|
+
line.on('error', (error: Error) => this.onerror?.(error));
|
|
315
|
+
line.on('close', () => this.onclose?.());
|
|
316
|
+
line.on('unreaped', () => this.onUnreaped(`its stdio pipe failed with ${line.pipeFailure?.message ?? 'an error'}`));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async send(message: JSONRPCMessage): Promise<void> {
|
|
320
|
+
if (!this.line || this.line.closed) throw new NotWrittenError(`MCP server "${this.config.id}" stdio transport is closed`);
|
|
321
|
+
// Once the child's stdin or stdout has failed, the line is ending it and
|
|
322
|
+
// writes nothing more to it, so a message is refused here as not sent.
|
|
323
|
+
const failed = this.line.pipeFailure;
|
|
324
|
+
if (failed) throw new NotWrittenError(`MCP server "${this.config.id}" stdio pipe failed with ${failed.message}`, true);
|
|
325
|
+
this.line.writeLine(JSON.stringify(message));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async close(): Promise<void> {
|
|
329
|
+
// A close() during the spawn closes the child it yields. start()
|
|
330
|
+
// awaited the spawn first, so the line is wired by then, and its close
|
|
331
|
+
// ends what the SDK has in flight, as it does after the spawn.
|
|
332
|
+
if (!this.line && this.spawning) await this.spawning.catch(() => null);
|
|
333
|
+
await this.line?.close();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Connectome's credential plumbing as the SDK's minimal bearer seam: a
|
|
338
|
+
* cached token, fetched on first use and once again after a 401. */
|
|
339
|
+
function bearerAuth(config: McplServerConfig): AuthProvider | undefined {
|
|
340
|
+
const provider = config.accessProvider;
|
|
341
|
+
if (!provider && !config.token) return undefined;
|
|
342
|
+
let cached: string | undefined;
|
|
343
|
+
const resolve = async (): Promise<string | undefined> => {
|
|
344
|
+
if (provider) {
|
|
345
|
+
try {
|
|
346
|
+
const fresh = await provider();
|
|
347
|
+
if (fresh) {
|
|
348
|
+
// Sent in the Authorization header: the server's text is recorded
|
|
349
|
+
// with it replaced.
|
|
350
|
+
noteDialSent(config, { token: fresh });
|
|
351
|
+
return fresh;
|
|
352
|
+
}
|
|
353
|
+
} catch (error) {
|
|
354
|
+
// Same fallback as the WebSocket dial: use the static token, and let
|
|
355
|
+
// the server refuse a stale credential loudly. The provider's own
|
|
356
|
+
// text may quote the credential, so it isn't logged.
|
|
357
|
+
console.error(
|
|
358
|
+
`[mcp] server "${config.id}": access provider failed (${describeCredentialError(error)}) — using configured fallback`,
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return config.token;
|
|
363
|
+
};
|
|
364
|
+
return {
|
|
365
|
+
token: async () => (cached ??= await resolve()),
|
|
366
|
+
onUnauthorized: async () => {
|
|
367
|
+
cached = await resolve();
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** One connect generation's client and the boundary it talks through. */
|
|
373
|
+
interface Session {
|
|
374
|
+
readonly generation: number;
|
|
375
|
+
readonly client: Client;
|
|
376
|
+
readonly wire: ObservedTransport;
|
|
377
|
+
/** Its transport has closed, whether or not the session was adopted yet. */
|
|
378
|
+
lost: boolean;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export class ModernMcpConnection extends EventEmitter {
|
|
382
|
+
readonly id: string;
|
|
383
|
+
readonly family = 'modern' as const;
|
|
384
|
+
readonly transportKind: 'stdio' | 'http';
|
|
385
|
+
/** The pinned revision, once a connect has established it; null before. */
|
|
386
|
+
protocolVersion: string | null = null;
|
|
387
|
+
|
|
388
|
+
private session: Session | null = null;
|
|
389
|
+
/** A connect in flight, owned so `close()` can end it. */
|
|
390
|
+
private opening: { session: Session; done: Promise<void> } | null = null;
|
|
391
|
+
private generation = 0;
|
|
392
|
+
private closedByHost = false;
|
|
393
|
+
private reconnectEnabled: boolean;
|
|
394
|
+
private readonly reconnectIntervalMs: number;
|
|
395
|
+
private readonly reconnectMaxIntervalMs: number;
|
|
396
|
+
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
397
|
+
private reconnectAttempts = 0;
|
|
398
|
+
/** At most one pending reopen, and only ever for the current generation. */
|
|
399
|
+
private relisten: { generation: number; timer: ReturnType<typeof setTimeout> } | null = null;
|
|
400
|
+
private relistenAttempts = 0;
|
|
401
|
+
/**
|
|
402
|
+
* Generations whose cleanup could not confirm their child exited. While any
|
|
403
|
+
* is held, no replacement is started: launching another would orphan the
|
|
404
|
+
* first. close() retries them and reports what still fails.
|
|
405
|
+
*/
|
|
406
|
+
private unreaped: Array<{ session: Session; failure: Error }> = [];
|
|
407
|
+
/** A lost session's reap in flight: the reconnect follows it, and
|
|
408
|
+
* close() waits for it. */
|
|
409
|
+
private losing: { session: Session; done: Promise<void> } | null = null;
|
|
410
|
+
/** Child stderr from before anyone listens (the caller attaches only once
|
|
411
|
+
* connect() returns), delivered to the first listener. Bounded: the
|
|
412
|
+
* newest lines are kept. */
|
|
413
|
+
private stderrBacklog: string[] = [];
|
|
414
|
+
|
|
415
|
+
private constructor(private readonly config: McplServerConfig) {
|
|
416
|
+
super();
|
|
417
|
+
this.id = config.id;
|
|
418
|
+
const binding = checkServerConfig(config);
|
|
419
|
+
if (binding.family !== 'modern') {
|
|
420
|
+
throw new Error(`MCP server "${config.id}" is not configured for modern MCP; it belongs to McplServerConnection`);
|
|
421
|
+
}
|
|
422
|
+
this.transportKind = binding.transport === 'http' ? 'http' : 'stdio';
|
|
423
|
+
this.reconnectEnabled = config.reconnect === true;
|
|
424
|
+
this.reconnectIntervalMs = config.reconnectIntervalMs ?? 5000;
|
|
425
|
+
this.reconnectMaxIntervalMs = config.reconnectMaxIntervalMs ?? 300_000;
|
|
426
|
+
// An 'error' with no listener would throw out of an SDK callback and take
|
|
427
|
+
// the host down; out-of-band errors here are diagnostics, never fatal.
|
|
428
|
+
this.on('error', (error: Error) => {
|
|
429
|
+
if (this.listenerCount('error') === 1) console.error(`[mcp] ${this.id}: ${redactServerText(error.message, this.config)}`);
|
|
430
|
+
});
|
|
431
|
+
this.on('newListener', (event: string | symbol) => {
|
|
432
|
+
if (event !== 'stderr' || this.stderrBacklog.length === 0) return;
|
|
433
|
+
const pending = this.stderrBacklog;
|
|
434
|
+
this.stderrBacklog = [];
|
|
435
|
+
// After the listener is added (newListener fires just before).
|
|
436
|
+
queueMicrotask(() => { for (const line of pending) this.emit('stderr', { line }); });
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
private deliverStderr(line: string): void {
|
|
441
|
+
if (this.listenerCount('stderr') > 0) {
|
|
442
|
+
this.emit('stderr', { line });
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
this.stderrBacklog.push(line);
|
|
446
|
+
if (this.stderrBacklog.length > STDERR_BACKLOG_LINES) this.stderrBacklog.shift();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** The per-call deadline: `requestTimeoutMs`, validated positive. */
|
|
450
|
+
get requestTimeoutMs(): number {
|
|
451
|
+
return this.config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
get isConnected(): boolean {
|
|
455
|
+
return this.session !== null;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** Whether a lost connection will be re-established in the background. */
|
|
459
|
+
get willReconnect(): boolean {
|
|
460
|
+
return this.reconnectEnabled && !this.closedByHost && this.unreaped.length === 0;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** Whether close() has been called: closed for good, or closing. A
|
|
464
|
+
* connection lost on its own is not closed in this sense. */
|
|
465
|
+
get isClosed(): boolean {
|
|
466
|
+
return this.closedByHost;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** Whether a child this connection launched couldn't be confirmed exited.
|
|
470
|
+
* While it's true the connection owns that child, so whatever registered
|
|
471
|
+
* it keeps its id held; close() retries the reap. It's settled only once
|
|
472
|
+
* a reap has finished: while a lost session's reap or a close runs, it
|
|
473
|
+
* reads false, so read it once lossReaped() or close() has settled. */
|
|
474
|
+
get holdsUnreapedChild(): boolean {
|
|
475
|
+
return this.unreaped.length > 0;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** The reap of a session lost on its own, settled once it's done, whether
|
|
479
|
+
* or not it confirmed the child exited (holdsUnreapedChild says which);
|
|
480
|
+
* settled at once when no such reap is running. */
|
|
481
|
+
lossReaped(): Promise<void> {
|
|
482
|
+
return this.losing?.done ?? Promise.resolve();
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** A failed generation's cleanup couldn't reap its child: keep owning it,
|
|
486
|
+
* and halt reconnecting until close() settles it. */
|
|
487
|
+
private holdUnreaped(session: Session, failure: Error): void {
|
|
488
|
+
if (this.unreaped.some((held) => held.session === session)) return;
|
|
489
|
+
this.unreaped.push({ session, failure });
|
|
490
|
+
this.emit('error', new Error(
|
|
491
|
+
`MCP server "${this.id}": ${failure.message}. Not starting another launch while it may still be running; close the connection to retry the reap.`,
|
|
492
|
+
));
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** The halt, as a permanent failure for the reconnect events. */
|
|
496
|
+
private unreapedFailure(): string | null {
|
|
497
|
+
const held = this.unreaped[0];
|
|
498
|
+
if (!held) return null;
|
|
499
|
+
const halted = this.reconnectEnabled ? '; reconnecting is halted' : '';
|
|
500
|
+
return `the previous launch could not be reaped (${held.failure.message})${halted}`;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* A connection that hasn't started: configuration is validated (and
|
|
505
|
+
* throws), nothing is launched or dialed. An owner can register it, and
|
|
506
|
+
* wire its events, before {@link start}, so a connect in flight is
|
|
507
|
+
* already the owner's to close.
|
|
508
|
+
*/
|
|
509
|
+
static create(config: McplServerConfig): ModernMcpConnection {
|
|
510
|
+
return new ModernMcpConnection(config);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/** The first connect, shared by every call to start(). */
|
|
514
|
+
private starting: Promise<void> | null = null;
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* The first connect. Without `reconnect` a failure throws. With it, the
|
|
518
|
+
* connection keeps retrying in the background, as the legacy engine's
|
|
519
|
+
* stub does, and reports `connect-failed`, unless the failed launch
|
|
520
|
+
* couldn't be reaped: no retry can come then, so it throws too. A
|
|
521
|
+
* connection closed before or during its first connect resolves
|
|
522
|
+
* unconnected. A connection starts once:
|
|
523
|
+
* later calls return the first call's promise, so a second session never
|
|
524
|
+
* opens beside the first.
|
|
525
|
+
*/
|
|
526
|
+
start(): Promise<void> {
|
|
527
|
+
return (this.starting ??= this.firstConnect());
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
private async firstConnect(): Promise<void> {
|
|
531
|
+
try {
|
|
532
|
+
await this.open();
|
|
533
|
+
} catch (error) {
|
|
534
|
+
if (this.closedByHost) return;
|
|
535
|
+
// No common revision is a configuration fact, not an outage: a retry
|
|
536
|
+
// would restart the server to hear the same answer.
|
|
537
|
+
if (error instanceof McplProtocolVersionError) {
|
|
538
|
+
this.reconnectEnabled = false;
|
|
539
|
+
throw error;
|
|
540
|
+
}
|
|
541
|
+
// A launch that couldn't be reaped halts reconnecting, so no retry
|
|
542
|
+
// will come: the first connect fails, as one without reconnect does.
|
|
543
|
+
if (!this.reconnectEnabled || this.holdsUnreapedChild) throw error;
|
|
544
|
+
console.error(`MCP server "${this.id}" initial connect failed, will retry:`, redactServerText((error as Error).message, this.config));
|
|
545
|
+
this.reconnectAttempts = 1;
|
|
546
|
+
this.emit('connect-failed', { error: (error as Error).message, attempt: 0 });
|
|
547
|
+
this.scheduleReconnect();
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* {@link create} and {@link start} in one step. A first connect that fails
|
|
553
|
+
* and whose cleanup couldn't confirm its child exited throws
|
|
554
|
+
* {@link McplUnreapedLaunchError}, whose connection owns that launch, as
|
|
555
|
+
* the legacy engine's connect does: its close() retries the reap.
|
|
556
|
+
*/
|
|
557
|
+
static async connect(config: McplServerConfig): Promise<ModernMcpConnection> {
|
|
558
|
+
const connection = ModernMcpConnection.create(config);
|
|
559
|
+
try {
|
|
560
|
+
await connection.start();
|
|
561
|
+
} catch (error) {
|
|
562
|
+
const held = connection.unreaped[0];
|
|
563
|
+
if (held) throw new McplUnreapedLaunchError(connection, error instanceof Error ? error : new Error(String(error)), held.failure);
|
|
564
|
+
throw error;
|
|
565
|
+
}
|
|
566
|
+
return connection;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
private isCurrent(generation: number): boolean {
|
|
570
|
+
return generation === this.generation && !this.closedByHost;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** One connect generation: a fresh client over a fresh transport. */
|
|
574
|
+
private async open(): Promise<void> {
|
|
575
|
+
// Closed before this connect began: launch nothing.
|
|
576
|
+
if (this.closedByHost) return;
|
|
577
|
+
const generation = ++this.generation;
|
|
578
|
+
this.cancelRelisten();
|
|
579
|
+
// A new generation's subscription has failed nothing yet: its first
|
|
580
|
+
// reopen backs off from the base interval, not from the previous
|
|
581
|
+
// generation's failures.
|
|
582
|
+
this.relistenAttempts = 0;
|
|
583
|
+
const inner: Transport = this.transportKind === 'stdio'
|
|
584
|
+
? new SpawnerStdioTransport(
|
|
585
|
+
this.config,
|
|
586
|
+
(line) => { if (this.isCurrent(generation)) this.deliverStderr(line); },
|
|
587
|
+
// A child whose pipe failed and that outlived SIGKILL: a loss, whose
|
|
588
|
+
// reap finds the child still running and holds the launch, as for any
|
|
589
|
+
// launch it can't reap. During a connect there is no session to lose
|
|
590
|
+
// yet: the connect's own cleanup holds it, once the connect fails.
|
|
591
|
+
(reason) => { if (this.isCurrent(generation)) this.handleLost(generation, reason); },
|
|
592
|
+
)
|
|
593
|
+
: new StreamableHTTPClientTransport(new URL(this.config.url!), { authProvider: bearerAuth(this.config) });
|
|
594
|
+
const wire = new ObservedTransport(inner);
|
|
595
|
+
|
|
596
|
+
const client = new Client(
|
|
597
|
+
{ name: 'agent-framework', version: '1.0.0' },
|
|
598
|
+
{
|
|
599
|
+
versionNegotiation: { mode: { pin: MODERN_MCP_PROTOCOL_VERSION } },
|
|
600
|
+
// Nothing advertised: this client supplies no sampling, elicitation
|
|
601
|
+
// or roots, so a conforming server cannot require them.
|
|
602
|
+
capabilities: {},
|
|
603
|
+
listChanged: {
|
|
604
|
+
tools: {
|
|
605
|
+
autoRefresh: false,
|
|
606
|
+
debounceMs: 0,
|
|
607
|
+
onChanged: () => { if (this.isCurrent(generation)) this.emit('tools-list-changed'); },
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
);
|
|
612
|
+
const session: Session = { generation, client, wire, lost: false };
|
|
613
|
+
client.onerror = (error: Error) => {
|
|
614
|
+
// The SDK reports late responses to deadline-abandoned calls here
|
|
615
|
+
// ("unknown message ID"), and a list-change subscription it could not
|
|
616
|
+
// open at connect, among other out-of-band errors. They are emitted as
|
|
617
|
+
// this connection's 'error', so they reach the host's error listeners
|
|
618
|
+
// (and are logged when there are none). They never change a call's
|
|
619
|
+
// outcome, which was already reported.
|
|
620
|
+
if (this.isCurrent(generation)) this.emit('error', error);
|
|
621
|
+
};
|
|
622
|
+
client.onclose = () => {
|
|
623
|
+
// Marked before the session is adopted too: a close during the connect
|
|
624
|
+
// makes it a failed connect (see establish).
|
|
625
|
+
session.lost = true;
|
|
626
|
+
if (this.isCurrent(generation)) this.handleLost(generation, 'transport closed');
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// `done` settles only once this connect's own cleanup has finished, on
|
|
630
|
+
// every path: close() waits on it, and must not resolve while that
|
|
631
|
+
// cleanup could still find a child it can't reap.
|
|
632
|
+
let settle!: () => void;
|
|
633
|
+
this.opening = { session, done: new Promise<void>((resolve) => { settle = resolve; }) };
|
|
634
|
+
try {
|
|
635
|
+
await this.establish(session);
|
|
636
|
+
} finally {
|
|
637
|
+
if (this.opening?.session === session) this.opening = null;
|
|
638
|
+
settle();
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** Connect one generation's client, and adopt it if it's still current. */
|
|
643
|
+
private async establish(session: Session): Promise<void> {
|
|
644
|
+
try {
|
|
645
|
+
await session.client.connect(session.wire, { timeout: CONNECT_TIMEOUT_MS });
|
|
646
|
+
} catch (error) {
|
|
647
|
+
const failure = await ModernMcpConnection.closeSession(session);
|
|
648
|
+
if (failure) this.holdUnreaped(session, failure);
|
|
649
|
+
throw this.versionVerdict(error) ?? error;
|
|
650
|
+
}
|
|
651
|
+
if (!this.isCurrent(session.generation)) {
|
|
652
|
+
// Superseded or closed while connecting: this generation never serves.
|
|
653
|
+
const failure = await ModernMcpConnection.closeSession(session);
|
|
654
|
+
if (failure) this.holdUnreaped(session, failure);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
if (session.lost) {
|
|
658
|
+
// The transport closed while the client connected, such as a child
|
|
659
|
+
// that exited at the connect-time list-change subscribe: the SDK
|
|
660
|
+
// reports that subscribe's failure only as an error, and resolves the
|
|
661
|
+
// connect anyway (R1-2). Adopted, the session would read as connected
|
|
662
|
+
// with nothing behind it, and no later close would come to reconnect
|
|
663
|
+
// it. So the connect failed.
|
|
664
|
+
const failure = await ModernMcpConnection.closeSession(session);
|
|
665
|
+
if (failure) this.holdUnreaped(session, failure);
|
|
666
|
+
throw new Error(`MCP server "${this.id}" closed its connection while connecting`);
|
|
667
|
+
}
|
|
668
|
+
this.session = session;
|
|
669
|
+
this.protocolVersion = MODERN_MCP_PROTOCOL_VERSION;
|
|
670
|
+
this.reconnectAttempts = 0;
|
|
671
|
+
this.keepListening(session, session.client.autoOpenedSubscription);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* The server refused the pinned revision (no common one in its
|
|
676
|
+
* `server/discover` answer, or an unsupported-version error): a
|
|
677
|
+
* configuration verdict, as for the legacy engine, in the same type, so
|
|
678
|
+
* every reader treats both engines alike. Retrying can't change it.
|
|
679
|
+
*/
|
|
680
|
+
private versionVerdict(error: unknown): McplProtocolVersionError | null {
|
|
681
|
+
if (!(error instanceof UnsupportedProtocolVersionError)) return null;
|
|
682
|
+
const data = (error as { data?: unknown }).data as { supported?: unknown } | undefined;
|
|
683
|
+
const supported = Array.isArray(data?.supported) && data.supported.every((v) => typeof v === 'string')
|
|
684
|
+
? (data.supported as string[])
|
|
685
|
+
: null;
|
|
686
|
+
// Legacy MCP is reached over stdio or WebSocket. A stdio server's
|
|
687
|
+
// family is its configuration's choice; an http(s) url is modern by its
|
|
688
|
+
// scheme, so there is nothing to reconfigure on it.
|
|
689
|
+
const legacyHint = !supported?.includes(LEGACY_MCP_PROTOCOL_VERSION)
|
|
690
|
+
? ''
|
|
691
|
+
: this.transportKind === 'stdio'
|
|
692
|
+
? ` It speaks legacy MCP: configure it without protocol: 'modern'.`
|
|
693
|
+
: ` It speaks only legacy MCP, which this client reaches over stdio or WebSocket (ws://, wss://), not http(s).`;
|
|
694
|
+
return new McplProtocolVersionError(
|
|
695
|
+
`MCP server "${this.id}" does not support MCP ${MODERN_MCP_PROTOCOL_VERSION}` +
|
|
696
|
+
(supported ? ` (it supports: ${supported.join(', ') || 'none listed'}).` : ` (${error.message}).`) +
|
|
697
|
+
legacyHint,
|
|
698
|
+
'rejected',
|
|
699
|
+
null,
|
|
700
|
+
supported,
|
|
701
|
+
(error as { code?: unknown }).code === undefined ? undefined : Number((error as { code?: unknown }).code),
|
|
702
|
+
(error as { data?: unknown }).data,
|
|
703
|
+
MODERN_MCP_PROTOCOL_VERSION,
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Keep list-change awareness while this generation lives. The SDK opens the
|
|
709
|
+
* subscription at connect when the server advertises tool list changes. If
|
|
710
|
+
* that open failed (the SDK reports it through `onerror` and connects
|
|
711
|
+
* anyway), or the subscription later ends without our asking, it is
|
|
712
|
+
* reopened with backoff. Once it's back the inventory is refreshed, since
|
|
713
|
+
* changes may have been missed. A lost transport is the reconnect path's
|
|
714
|
+
* job, not this one's.
|
|
715
|
+
*/
|
|
716
|
+
private keepListening(session: Session, subscription: McpSubscription | undefined): void {
|
|
717
|
+
if (!session.client.getServerCapabilities()?.tools?.listChanged) return;
|
|
718
|
+
if (!subscription) {
|
|
719
|
+
this.scheduleRelisten(session.generation);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
void subscription.closed.then((reason) => {
|
|
723
|
+
if (reason === 'local' || this.session !== session || !this.isCurrent(session.generation)) return;
|
|
724
|
+
this.scheduleRelisten(session.generation);
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
private cancelRelisten(): void {
|
|
729
|
+
if (this.relisten) clearTimeout(this.relisten.timer);
|
|
730
|
+
this.relisten = null;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
private scheduleRelisten(generation: number): void {
|
|
734
|
+
if (!this.isCurrent(generation)) return;
|
|
735
|
+
if (this.relisten?.generation === generation) return;
|
|
736
|
+
this.cancelRelisten();
|
|
737
|
+
const timer = setTimeout(() => {
|
|
738
|
+
if (this.relisten?.timer === timer) this.relisten = null;
|
|
739
|
+
void this.reopenSubscription(generation);
|
|
740
|
+
}, this.backoffDelay(Math.max(1, this.relistenAttempts)));
|
|
741
|
+
timer.unref?.();
|
|
742
|
+
this.relisten = { generation, timer };
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
private async reopenSubscription(generation: number): Promise<void> {
|
|
746
|
+
const session = this.session;
|
|
747
|
+
if (!session || session.generation !== generation || !this.isCurrent(generation)) return;
|
|
748
|
+
try {
|
|
749
|
+
const subscription = await session.client.listen({ toolsListChanged: true }, { timeout: CONNECT_TIMEOUT_MS });
|
|
750
|
+
if (this.session !== session || !this.isCurrent(generation)) {
|
|
751
|
+
await subscription.close().catch(() => {});
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
this.relistenAttempts = 0;
|
|
755
|
+
this.keepListening(session, subscription);
|
|
756
|
+
this.emit('tools-list-changed');
|
|
757
|
+
} catch (error) {
|
|
758
|
+
if (this.session !== session || !this.isCurrent(generation)) return;
|
|
759
|
+
this.relistenAttempts++;
|
|
760
|
+
this.emit('error', new Error(`MCP server "${this.id}" could not reopen its list-change subscription: ${(error as Error).message}`));
|
|
761
|
+
this.scheduleRelisten(generation);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/** The transport went away (child exit, or a closed HTTP client). */
|
|
766
|
+
private handleLost(generation: number, reason: string): void {
|
|
767
|
+
const session = this.session;
|
|
768
|
+
if (session?.generation !== generation) return;
|
|
769
|
+
this.session = null;
|
|
770
|
+
this.cancelRelisten();
|
|
771
|
+
// Reap before any reconnect, so a second child never runs beside the
|
|
772
|
+
// first. A transport usually closes once its child has exited, but a
|
|
773
|
+
// loss read from a failed pipe can come while the child still runs
|
|
774
|
+
// (R1-1). A launch that can't be reaped halts reconnecting, as a failed
|
|
775
|
+
// reconnect's does, and is a permanent failure, as that one is: no
|
|
776
|
+
// later attempt will come. The reap starts, and is recorded, before
|
|
777
|
+
// 'close' is emitted, so a listener that throws can't skip it.
|
|
778
|
+
const done = ModernMcpConnection.closeSession(session).then((failure) => {
|
|
779
|
+
if (this.losing?.session === session) this.losing = null;
|
|
780
|
+
if (failure) {
|
|
781
|
+
this.holdUnreaped(session, failure);
|
|
782
|
+
// A close() the host made meanwhile reports the failure itself.
|
|
783
|
+
if (!this.closedByHost) {
|
|
784
|
+
this.emit('reconnect-failed', { error: `${reason}; ${this.unreapedFailure()}`, attempt: Math.max(1, this.reconnectAttempts), permanent: true });
|
|
785
|
+
}
|
|
786
|
+
} else if (this.willReconnect) this.scheduleReconnect();
|
|
787
|
+
});
|
|
788
|
+
this.losing = { session, done };
|
|
789
|
+
this.emit('close', { reason });
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/** The legacy engine's backoff: doubling from the base interval, capped,
|
|
793
|
+
* with ±25% jitter, and never past the timer limit (a longer delay would
|
|
794
|
+
* fire at once, turning the backoff into a tight loop). */
|
|
795
|
+
private backoffDelay(failures: number): number {
|
|
796
|
+
const exponent = Math.max(0, Math.min(failures - 1, 30));
|
|
797
|
+
const capped = Math.min(this.reconnectIntervalMs * 2 ** exponent, this.reconnectMaxIntervalMs);
|
|
798
|
+
return Math.min(capped * (0.75 + Math.random() * 0.5), MAX_TIMER_MS);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
private scheduleReconnect(): void {
|
|
802
|
+
if (!this.willReconnect || this.reconnectTimer) return;
|
|
803
|
+
this.reconnectTimer = setTimeout(() => {
|
|
804
|
+
this.reconnectTimer = null;
|
|
805
|
+
void this.attemptReconnect();
|
|
806
|
+
}, this.backoffDelay(this.reconnectAttempts));
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
private async attemptReconnect(): Promise<void> {
|
|
810
|
+
if (!this.willReconnect || this.session) return;
|
|
811
|
+
const attempt = Math.max(1, this.reconnectAttempts);
|
|
812
|
+
try {
|
|
813
|
+
await this.open();
|
|
814
|
+
if (!this.session) return;
|
|
815
|
+
console.error(`MCP server "${this.id}" reconnected`);
|
|
816
|
+
this.emit('reconnect', { attempts: attempt });
|
|
817
|
+
} catch (error) {
|
|
818
|
+
if (this.closedByHost) return;
|
|
819
|
+
this.reconnectAttempts = attempt + 1;
|
|
820
|
+
const halted = this.unreapedFailure();
|
|
821
|
+
if (halted) {
|
|
822
|
+
this.emit('reconnect-failed', { error: `${(error as Error).message}; ${halted}`, attempt, permanent: true });
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
// No common revision: every further attempt would hear the same
|
|
826
|
+
// answer. Stop; only the configuration can fix it.
|
|
827
|
+
if (error instanceof McplProtocolVersionError) {
|
|
828
|
+
this.reconnectEnabled = false;
|
|
829
|
+
this.emit('reconnect-failed', { error: error.message, attempt, permanent: true });
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
this.emit('reconnect-failed', { error: (error as Error).message, attempt });
|
|
833
|
+
this.scheduleReconnect();
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* The server's complete tool inventory. The SDK follows every page; one
|
|
839
|
+
* budget, {@link LIST_TIMEOUT_MS}, bounds the whole walk, not each page.
|
|
840
|
+
* Its failures use the request-outcome contract with `tools/list` as the
|
|
841
|
+
* observed method. A relist the SDK makes inside a tool call is auxiliary
|
|
842
|
+
* to that call, and leaves the call's evidence alone.
|
|
843
|
+
*/
|
|
844
|
+
async listTools(): Promise<McpToolDefinition[]> {
|
|
845
|
+
const { tools } = await this.request('tools/list', 'tools/list', (client) =>
|
|
846
|
+
client.listTools(undefined, {
|
|
847
|
+
cacheMode: 'refresh',
|
|
848
|
+
timeout: LIST_TIMEOUT_MS,
|
|
849
|
+
signal: AbortSignal.timeout(LIST_TIMEOUT_MS),
|
|
850
|
+
}), LIST_TIMEOUT_MS);
|
|
851
|
+
return tools.map((tool) => ({
|
|
852
|
+
name: tool.name,
|
|
853
|
+
...(tool.description !== undefined ? { description: tool.description } : {}),
|
|
854
|
+
inputSchema: tool.inputSchema as Record<string, unknown>,
|
|
855
|
+
...(tool._meta !== undefined ? { _meta: tool._meta as Record<string, unknown> } : {}),
|
|
856
|
+
}));
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Call a tool within the configured deadline. One budget covers the whole
|
|
861
|
+
* logical call: the SDK's per-leg timer and its `maxTotalTimeout` across
|
|
862
|
+
* continuation rounds, and an abort signal around any retry the SDK makes
|
|
863
|
+
* before dispatch (an auth refresh, or a header-mismatch relist).
|
|
864
|
+
*/
|
|
865
|
+
async callTool(name: string, args: Record<string, unknown>): Promise<ModernToolCallResult> {
|
|
866
|
+
const timeoutMs = this.requestTimeoutMs;
|
|
867
|
+
const result = await this.request('tools/call', `tools/call ${logValue(name)}`, (client) =>
|
|
868
|
+
client.callTool(
|
|
869
|
+
{ name, arguments: args },
|
|
870
|
+
{ timeout: timeoutMs, maxTotalTimeout: timeoutMs, signal: AbortSignal.timeout(timeoutMs) },
|
|
871
|
+
), timeoutMs);
|
|
872
|
+
return {
|
|
873
|
+
content: result.content ?? [],
|
|
874
|
+
...(result.isError !== undefined ? { isError: result.isError } : {}),
|
|
875
|
+
...('structuredContent' in result ? { structuredContent: result.structuredContent } : {}),
|
|
876
|
+
...(result._meta !== undefined ? { _meta: result._meta as Record<string, unknown> } : {}),
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/** Run one logical request in a wire scope, and classify its failure by
|
|
881
|
+
* what that scope saw. `what` names the request in the host's log: its
|
|
882
|
+
* method, and a tool call's tool as a log field. */
|
|
883
|
+
private async request<T>(method: string, what: string, run: (client: Client) => Promise<T>, timeoutMs?: number): Promise<T> {
|
|
884
|
+
const session = this.session;
|
|
885
|
+
if (!session) {
|
|
886
|
+
// Connectome refuses before anything reaches the SDK: provably not
|
|
887
|
+
// sent. In the agent's words, as the legacy engine's request on a
|
|
888
|
+
// closed connection: after this host's own close, only that holds;
|
|
889
|
+
// otherwise the connection is closed, and whether this host is
|
|
890
|
+
// reconnecting says what works from there.
|
|
891
|
+
throw new McplRequestError(
|
|
892
|
+
this.closedByHost
|
|
893
|
+
? `The request wasn't sent: this host closed its connection to the "${this.id}" server, so nothing ran. ` +
|
|
894
|
+
'If calling it again fails, ask whoever runs this host.'
|
|
895
|
+
: `The request wasn't sent: the connection to the "${this.id}" server is closed, so nothing ran. ${this.retryText()}`,
|
|
896
|
+
'not-sent',
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
const record: WireRecord = { method, handedOff: false, answer: 'none', continued: false };
|
|
900
|
+
try {
|
|
901
|
+
return await wireScope.run(record, () => run(session.client));
|
|
902
|
+
} catch (error) {
|
|
903
|
+
throw this.requestFailure(what, record, error, timeoutMs, session.lost);
|
|
904
|
+
} finally {
|
|
905
|
+
session.wire.forget(record);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* Close one generation: the transport first (during discovery the SDK
|
|
911
|
+
* hasn't attached it yet, so only closing it ends the probe), then the
|
|
912
|
+
* client. The transport's close is the reaping verdict, and its failure is
|
|
913
|
+
* returned, never swallowed. It never rejects, which lossReaped() and the
|
|
914
|
+
* framework's unguarded wait on it rely on.
|
|
915
|
+
*/
|
|
916
|
+
private static async closeSession(session: Session): Promise<Error | null> {
|
|
917
|
+
let failure: Error | null = null;
|
|
918
|
+
try {
|
|
919
|
+
await session.wire.close();
|
|
920
|
+
} catch (error) {
|
|
921
|
+
failure = error instanceof Error ? error : new Error(String(error));
|
|
922
|
+
}
|
|
923
|
+
await session.client.close().catch(() => { /* SDK teardown; the verdict is the transport's */ });
|
|
924
|
+
return failure;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/** The close in flight, which later calls wait behind. */
|
|
928
|
+
private closing: Promise<void> | null = null;
|
|
929
|
+
|
|
930
|
+
/** Set when the host closes a live session; cleared, with the one
|
|
931
|
+
* 'close', by the close that confirms its teardown. */
|
|
932
|
+
private closeIncomplete = false;
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Close for good: no reconnect, no reopened subscription, no connect left
|
|
936
|
+
* in flight. Awaits the in-flight connect's end, and rejects while a child
|
|
937
|
+
* can't be confirmed exited: with that failure itself, the transport's
|
|
938
|
+
* own error, or an AggregateError when there are several, as the MCPL
|
|
939
|
+
* engine does. Calls queue behind the one in flight, so none resolves
|
|
940
|
+
* while teardown is still pending. A call after a failed close re-checks
|
|
941
|
+
* what wasn't reaped. 'close' is emitted once, when the teardown of a
|
|
942
|
+
* session the host closed while live completes, not on a failed close.
|
|
943
|
+
*/
|
|
944
|
+
close(): Promise<void> {
|
|
945
|
+
// Closed from this call on: a connect or start() in the same turn sees
|
|
946
|
+
// it, and launches nothing.
|
|
947
|
+
this.closedByHost = true;
|
|
948
|
+
const previous = this.closing;
|
|
949
|
+
const next = (previous ? previous.catch(() => {}) : Promise.resolve()).then(() => this.closeStep());
|
|
950
|
+
this.closing = next;
|
|
951
|
+
return next;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
private async closeStep(): Promise<void> {
|
|
955
|
+
this.closedByHost = true;
|
|
956
|
+
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
957
|
+
this.reconnectTimer = null;
|
|
958
|
+
this.cancelRelisten();
|
|
959
|
+
const opening = this.opening;
|
|
960
|
+
const losing = this.losing;
|
|
961
|
+
const session = this.session;
|
|
962
|
+
this.session = null;
|
|
963
|
+
if (session) this.closeIncomplete = true;
|
|
964
|
+
// Every generation this connection still owns: a connect in flight, the
|
|
965
|
+
// live one, a lost one still being reaped, and any a failed cleanup
|
|
966
|
+
// couldn't reap.
|
|
967
|
+
const targets: Session[] = [];
|
|
968
|
+
if (opening) targets.push(opening.session);
|
|
969
|
+
if (session) targets.push(session);
|
|
970
|
+
if (losing) targets.push(losing.session);
|
|
971
|
+
for (const held of this.unreaped) if (!targets.includes(held.session)) targets.push(held.session);
|
|
972
|
+
this.unreaped = [];
|
|
973
|
+
const failures: Error[] = [];
|
|
974
|
+
for (const target of targets) {
|
|
975
|
+
// Closing the transport ends a probe in flight too: its connect then
|
|
976
|
+
// rejects and its own cleanup runs (sharing this verdict).
|
|
977
|
+
const failure = await ModernMcpConnection.closeSession(target);
|
|
978
|
+
if (failure) {
|
|
979
|
+
failures.push(failure);
|
|
980
|
+
if (!this.unreaped.some((held) => held.session === target)) this.unreaped.push({ session: target, failure });
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
// The connect's own cleanup, and a lost session's reap, close their
|
|
984
|
+
// generation again, after the close above; each `done` settles once it
|
|
985
|
+
// has. A child that either cleanup couldn't reap is this close's failure
|
|
986
|
+
// too.
|
|
987
|
+
for (const pending of [opening, losing]) {
|
|
988
|
+
if (!pending) continue;
|
|
989
|
+
await pending.done;
|
|
990
|
+
for (const held of this.unreaped) if (!failures.includes(held.failure)) failures.push(held.failure);
|
|
991
|
+
}
|
|
992
|
+
if (failures.length === 1) throw failures[0];
|
|
993
|
+
if (failures.length > 1) {
|
|
994
|
+
throw new AggregateError(failures, `MCP server "${this.id}" did not close cleanly: ${failures.map((f) => f.message).join('; ')}`);
|
|
995
|
+
}
|
|
996
|
+
if (this.closeIncomplete) {
|
|
997
|
+
this.closeIncomplete = false;
|
|
998
|
+
this.emit('close', { reason: 'closed by host' });
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/** What works from here after a request that didn't run, by whether this
|
|
1003
|
+
* host is reconnecting to the server, in the legacy engine's words. */
|
|
1004
|
+
private retryText(): string {
|
|
1005
|
+
return this.willReconnect
|
|
1006
|
+
? 'This host is reconnecting to it; try again later.'
|
|
1007
|
+
: "This host won't reconnect to it on its own. If you need it, ask whoever runs this host.";
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
* Map a failure onto the request-outcome contract by what crossed the
|
|
1012
|
+
* transport boundary for this request, claiming no more than that shows.
|
|
1013
|
+
* The rules apply in this order. The call's answer is the server's final
|
|
1014
|
+
* one: an error or a `complete` result.
|
|
1015
|
+
* - nothing handed to the transport: `not-sent`, whatever was raised (a
|
|
1016
|
+
* pre-dispatch validation, a signal already aborted, no transport, a send
|
|
1017
|
+
* Connectome's stdio line refused before writing, an HTTP refusal before
|
|
1018
|
+
* any request went out);
|
|
1019
|
+
* - the most recent leg answered with a JSON-RPC error: `error-response`
|
|
1020
|
+
* with that error's data;
|
|
1021
|
+
* - the most recent leg answered with a complete result: the server
|
|
1022
|
+
* finished, but what it returned can't be used (output validation).
|
|
1023
|
+
* That's a plain error, not a request outcome;
|
|
1024
|
+
* - the most recent leg answered without finishing the call: `no-response`,
|
|
1025
|
+
* since the server may have acted on what it received. An
|
|
1026
|
+
* `input_required` answer asks for another round, and the call can end
|
|
1027
|
+
* before that round is sent: at the deadline, when the connection is
|
|
1028
|
+
* gone, or when this client can't go on (input it can't supply, the
|
|
1029
|
+
* rounds cap). Nothing is in flight then, so nothing is cancelled. A
|
|
1030
|
+
* result of a type this client doesn't recognize ends the call where it
|
|
1031
|
+
* is;
|
|
1032
|
+
* - an HTTP 401/403 refusal of the current leg: `error-response`, the
|
|
1033
|
+
* status in `data.httpStatus` (the server answered at the HTTP layer,
|
|
1034
|
+
* outside the JSON-RPC code space). After an earlier leg
|
|
1035
|
+
* asked for another round it is `no-response` instead: the server may
|
|
1036
|
+
* already have acted on the call;
|
|
1037
|
+
* - handed off and unanswered: `no-response`. At the deadline, cancellation
|
|
1038
|
+
* was requested and the outcome is unknown.
|
|
1039
|
+
*
|
|
1040
|
+
* The message is the agent's, in the legacy engine's words where it has
|
|
1041
|
+
* words for the same outcome (#506), and in their shape where it hasn't:
|
|
1042
|
+
* what happened, whether anything took effect, and what works from there.
|
|
1043
|
+
* A request this host's close cut off, and one whose connection closed
|
|
1044
|
+
* under it (`lost`), read as the legacy engine's do. The method, the tool,
|
|
1045
|
+
* the JSON-RPC code, the HTTP status, the deadline in milliseconds and the
|
|
1046
|
+
* SDK's or transport's words are the operator's, so they go to one log
|
|
1047
|
+
* line, the server's text redacted. Where the effect is in doubt, a tool
|
|
1048
|
+
* call's message says to check it before trying again, and a listing's that
|
|
1049
|
+
* listing changes nothing on the server, so trying again is safe.
|
|
1050
|
+
*/
|
|
1051
|
+
private requestFailure(what: string, record: WireRecord, error: unknown, timeoutMs?: number, lost = false): Error {
|
|
1052
|
+
const server = `the "${this.id}" server`;
|
|
1053
|
+
const Server = `The "${this.id}" server`;
|
|
1054
|
+
const message = redactServerText(error instanceof Error ? error.message : String(error), this.config);
|
|
1055
|
+
const log = (line: string) => console.error(`MCP server "${this.id}" ${line}`);
|
|
1056
|
+
const listing = record.method === 'tools/list';
|
|
1057
|
+
// What a failure whose effect is in doubt says: for a tool call, that the
|
|
1058
|
+
// effect is unknown and to check it; for a listing, that it's safe to ask
|
|
1059
|
+
// again.
|
|
1060
|
+
const unknown = (said: string) => listing
|
|
1061
|
+
? `${said}. Listing changes nothing on the server, so it's safe to try again.`
|
|
1062
|
+
: `${said}, so whether the request took effect is unknown. Check its effect before trying again.`;
|
|
1063
|
+
if (!record.handedOff) {
|
|
1064
|
+
log(`${what} not sent: ${logDetail(message)}`);
|
|
1065
|
+
const pipe = error instanceof NotWrittenError && error.pipe;
|
|
1066
|
+
return new McplRequestError(
|
|
1067
|
+
pipe
|
|
1068
|
+
? `The request wasn't sent: this host can no longer write to ${server}, so nothing ran. ${this.retryText()}`
|
|
1069
|
+
// Before the wire, the cause can pass (the stdio line closing as
|
|
1070
|
+
// the connection is lost) or stay (a tool's outputSchema the SDK
|
|
1071
|
+
// can't compile), so the message claims neither.
|
|
1072
|
+
: `The request wasn't sent: this host couldn't send it to ${server}, so nothing ran. If calling it again fails, ask whoever runs this host.`,
|
|
1073
|
+
'not-sent',
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
if (record.answer === 'error' && record.error) {
|
|
1077
|
+
const { code, message: wireMessage, data } = record.error;
|
|
1078
|
+
// A JSON-RPC answer reaches the caller unredacted (redactServerText's
|
|
1079
|
+
// scope). The log is a record of the server's text, so the message is
|
|
1080
|
+
// redacted there, whatever JSON value it is, as every other line this
|
|
1081
|
+
// connection writes is.
|
|
1082
|
+
log(`answered ${what} with error ${logValue(code)}: ${logValueRedacted(wireMessage, (text) => redactServerText(text, this.config))}`);
|
|
1083
|
+
// The server's own words, bounded as errors.ts says. Only a string is
|
|
1084
|
+
// words: a message of any other JSON value reads as none.
|
|
1085
|
+
const said = typeof wireMessage === 'string' ? boundedForLog(wireMessage.trim()) : '';
|
|
1086
|
+
return new McplRequestError(
|
|
1087
|
+
said ? `${Server} answered with an error: ${said}` : unknown(`${Server} answered with an error without saying what went wrong`),
|
|
1088
|
+
'error-response',
|
|
1089
|
+
data,
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
if (record.answer === 'complete') {
|
|
1093
|
+
log(`answered ${what} with a result this host can't use: ${logDetail(message)}`);
|
|
1094
|
+
return new Error(unknown(`${Server} answered, but not in a form this host can read`));
|
|
1095
|
+
}
|
|
1096
|
+
const deadline = error instanceof SdkError && error.code === SdkErrorCode.RequestTimeout;
|
|
1097
|
+
const within = deadline && timeoutMs ? ` within ${timeoutMs}ms` : '';
|
|
1098
|
+
if (record.answer === 'input-required' || record.answer === 'unrecognized') {
|
|
1099
|
+
const reason = /[.!?]$/.test(message) ? message : `${message}.`;
|
|
1100
|
+
log(record.answer === 'unrecognized'
|
|
1101
|
+
? `did not finish ${what}${within}: its answer was not a complete result: ${logDetail(reason)}`
|
|
1102
|
+
: deadline
|
|
1103
|
+
? `did not finish ${what}${within}: it asked for another round (input_required), and the deadline passed before that round was sent`
|
|
1104
|
+
: `did not finish ${what}: it asked for another round (input_required), and the call ended before that round was sent: ${logDetail(reason)}`);
|
|
1105
|
+
return new McplRequestError(
|
|
1106
|
+
unknown(record.answer === 'unrecognized'
|
|
1107
|
+
? `${Server}'s answer wasn't a complete result`
|
|
1108
|
+
: `${Server} didn't finish the request: it asked for more from this host, and ${deadline ? 'the time ran out' : 'the call ended'} before this host sent it`),
|
|
1109
|
+
'no-response',
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
// An HTTP refusal of the credential: 401 or 403, however the SDK raised
|
|
1113
|
+
// it. It raises a plain 403 under another code than its step-up limit's,
|
|
1114
|
+
// and an insufficient_scope 403 as InsufficientScopeError when no OAuth
|
|
1115
|
+
// provider is configured. A 403 can refuse a valid credential that lacks
|
|
1116
|
+
// permission, so the message says the server refused this host access.
|
|
1117
|
+
const refusedWith = error instanceof SdkHttpError && (error.status === 401 || error.status === 403)
|
|
1118
|
+
? error.status
|
|
1119
|
+
: error instanceof InsufficientScopeError ? 403 : null;
|
|
1120
|
+
if (refusedWith !== null) {
|
|
1121
|
+
if (record.continued) {
|
|
1122
|
+
log(`refused a later round of ${what}: HTTP ${refusedWith} (${logDetail(message)})`);
|
|
1123
|
+
return new McplRequestError(
|
|
1124
|
+
listing
|
|
1125
|
+
? unknown(`${Server} refused this host access partway through the request, after asking for more`)
|
|
1126
|
+
: `${Server} refused this host access partway through the request, after asking for more, so whether it took effect is unknown. ` +
|
|
1127
|
+
'Check its effect before trying again; if calling it again fails, ask whoever runs this host.',
|
|
1128
|
+
'no-response',
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
log(`refused ${what}: HTTP ${refusedWith} (${logDetail(message)})`);
|
|
1132
|
+
// The response body (the SDK's `text`) is the server's text and can
|
|
1133
|
+
// echo the request's url, so it's left out of the data; the status stays.
|
|
1134
|
+
const { text: _body, ...data } = (error instanceof SdkHttpError && error.data && typeof error.data === 'object' ? error.data : {}) as Record<string, unknown>;
|
|
1135
|
+
return new McplRequestError(
|
|
1136
|
+
`${Server} refused this host access, so the request didn't run. If you need it, ask whoever runs this host.`,
|
|
1137
|
+
'error-response',
|
|
1138
|
+
{ ...data, httpStatus: refusedWith },
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
if (deadline) {
|
|
1142
|
+
log(`didn't answer ${what}${within}; cancellation requested`);
|
|
1143
|
+
const seconds = timeoutMs ? timeoutMs / 1000 : null;
|
|
1144
|
+
const late = seconds === null ? `${Server} didn't answer in time` : `${Server} didn't answer within ${seconds} second${seconds === 1 ? '' : 's'}`;
|
|
1145
|
+
return new McplRequestError(
|
|
1146
|
+
listing
|
|
1147
|
+
? `${late}, and this host asked it to cancel. Listing changes nothing on the server, so it's safe to try again.`
|
|
1148
|
+
: `${late}, so whether the request took effect is unknown: this host asked it to cancel, and it may still complete on the server. ` +
|
|
1149
|
+
'Check its effect before trying again, since repeating something that changes state may do it twice.',
|
|
1150
|
+
'no-response',
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
// Anything else after the request went out. An HTTP status other than
|
|
1154
|
+
// 401/403 is only in the SDK's error, not its message, so the log names it.
|
|
1155
|
+
log(`${what} failed${error instanceof SdkHttpError ? ` with HTTP ${error.status}` : ''}: ${logDetail(message)}`);
|
|
1156
|
+
if (this.closedByHost) {
|
|
1157
|
+
// This host's close cut it off: the legacy engine's words.
|
|
1158
|
+
return new McplRequestError(unknown(`${Server} didn't answer before this host closed its connection`), 'no-response');
|
|
1159
|
+
}
|
|
1160
|
+
if (lost) {
|
|
1161
|
+
// The connection closed under it: the legacy engine's words, with
|
|
1162
|
+
// whether this host is reconnecting.
|
|
1163
|
+
return new McplRequestError(
|
|
1164
|
+
listing
|
|
1165
|
+
? unknown(`The connection to ${server} closed before it answered`)
|
|
1166
|
+
: `The connection to ${server} closed before it answered, so whether the request took effect is unknown. ` +
|
|
1167
|
+
(this.willReconnect
|
|
1168
|
+
? 'Check its effect before trying again; this host is reconnecting to it.'
|
|
1169
|
+
: "This host won't reconnect to it on its own. If you need it, ask whoever runs this host."),
|
|
1170
|
+
'no-response',
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
// The connection may still stand, as after an HTTP error with no JSON-RPC
|
|
1174
|
+
// answer, so the message says nothing of reconnecting.
|
|
1175
|
+
return new McplRequestError(
|
|
1176
|
+
listing
|
|
1177
|
+
? `The request to ${server} failed before an answer came back. Listing changes nothing on the server, so it's safe to try again.`
|
|
1178
|
+
: `The request to ${server} failed before an answer came back, so whether it took effect is unknown. Check its effect before trying again.`,
|
|
1179
|
+
'no-response',
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
}
|