apex-code 0.0.1-alpha.9 → 0.0.5
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/CHANGELOG.md +150 -2
- package/README.md +9 -8
- package/dist/cli/args.d.ts +25 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +43 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/mcp-command.d.ts +9 -0
- package/dist/cli/mcp-command.d.ts.map +1 -0
- package/dist/cli/mcp-command.js +53 -0
- package/dist/cli/mcp-command.js.map +1 -0
- package/dist/cli/project-trust.d.ts.map +1 -1
- package/dist/cli/project-trust.js +3 -1
- package/dist/cli/project-trust.js.map +1 -1
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +2 -1
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +94 -5
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +24 -5
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +3 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +157 -8
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +565 -22
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +7 -0
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/checkpoints/git-checkpoints.d.ts +39 -0
- package/dist/core/checkpoints/git-checkpoints.d.ts.map +1 -0
- package/dist/core/checkpoints/git-checkpoints.js +251 -0
- package/dist/core/checkpoints/git-checkpoints.js.map +1 -0
- package/dist/core/checkpoints/session-checkpoints.d.ts +23 -0
- package/dist/core/checkpoints/session-checkpoints.d.ts.map +1 -0
- package/dist/core/checkpoints/session-checkpoints.js +36 -0
- package/dist/core/checkpoints/session-checkpoints.js.map +1 -0
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +4 -3
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +5 -0
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +19 -5
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/delegation/runtime.d.ts +6 -5
- package/dist/core/delegation/runtime.d.ts.map +1 -1
- package/dist/core/delegation/runtime.js +4 -3
- package/dist/core/delegation/runtime.js.map +1 -1
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +5 -2
- package/dist/core/exec.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +8 -2
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +5 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +45 -1
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/source-runtime.d.ts +14 -0
- package/dist/core/extensions/source-runtime.d.ts.map +1 -0
- package/dist/core/extensions/source-runtime.js +56 -0
- package/dist/core/extensions/source-runtime.js.map +1 -0
- package/dist/core/extensions/types.d.ts +22 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/formatter-lifecycle.d.ts +69 -0
- package/dist/core/formatter-lifecycle.d.ts.map +1 -0
- package/dist/core/formatter-lifecycle.js +360 -0
- package/dist/core/formatter-lifecycle.js.map +1 -0
- package/dist/core/hooks/command-handler.d.ts +19 -0
- package/dist/core/hooks/command-handler.d.ts.map +1 -0
- package/dist/core/hooks/command-handler.js +112 -0
- package/dist/core/hooks/command-handler.js.map +1 -0
- package/dist/core/hooks/http-handler.d.ts +10 -0
- package/dist/core/hooks/http-handler.d.ts.map +1 -0
- package/dist/core/hooks/http-handler.js +36 -0
- package/dist/core/hooks/http-handler.js.map +1 -0
- package/dist/core/hooks/loader.d.ts +29 -0
- package/dist/core/hooks/loader.d.ts.map +1 -0
- package/dist/core/hooks/loader.js +139 -0
- package/dist/core/hooks/loader.js.map +1 -0
- package/dist/core/hooks/runtime.d.ts +13 -0
- package/dist/core/hooks/runtime.d.ts.map +1 -0
- package/dist/core/hooks/runtime.js +63 -0
- package/dist/core/hooks/runtime.js.map +1 -0
- package/dist/core/hooks/types.d.ts +83 -0
- package/dist/core/hooks/types.d.ts.map +1 -0
- package/dist/core/hooks/types.js +24 -0
- package/dist/core/hooks/types.js.map +1 -0
- package/dist/core/mcp/config.d.ts +16 -0
- package/dist/core/mcp/config.d.ts.map +1 -0
- package/dist/core/mcp/config.js +166 -0
- package/dist/core/mcp/config.js.map +1 -0
- package/dist/core/mcp/connector.d.ts +35 -0
- package/dist/core/mcp/connector.d.ts.map +1 -0
- package/dist/core/mcp/connector.js +105 -0
- package/dist/core/mcp/connector.js.map +1 -0
- package/dist/core/mcp/contract.d.ts +19 -0
- package/dist/core/mcp/contract.d.ts.map +1 -0
- package/dist/core/mcp/contract.js +89 -0
- package/dist/core/mcp/contract.js.map +1 -0
- package/dist/core/mcp/mcp-tool.d.ts +32 -0
- package/dist/core/mcp/mcp-tool.d.ts.map +1 -0
- package/dist/core/mcp/mcp-tool.js +171 -0
- package/dist/core/mcp/mcp-tool.js.map +1 -0
- package/dist/core/mcp/metadata-cache.d.ts +29 -0
- package/dist/core/mcp/metadata-cache.d.ts.map +1 -0
- package/dist/core/mcp/metadata-cache.js +99 -0
- package/dist/core/mcp/metadata-cache.js.map +1 -0
- package/dist/core/mcp/oauth/authorize.d.ts +21 -0
- package/dist/core/mcp/oauth/authorize.d.ts.map +1 -0
- package/dist/core/mcp/oauth/authorize.js +32 -0
- package/dist/core/mcp/oauth/authorize.js.map +1 -0
- package/dist/core/mcp/oauth/discover.d.ts +27 -0
- package/dist/core/mcp/oauth/discover.d.ts.map +1 -0
- package/dist/core/mcp/oauth/discover.js +116 -0
- package/dist/core/mcp/oauth/discover.js.map +1 -0
- package/dist/core/mcp/oauth/flow.d.ts +31 -0
- package/dist/core/mcp/oauth/flow.d.ts.map +1 -0
- package/dist/core/mcp/oauth/flow.js +175 -0
- package/dist/core/mcp/oauth/flow.js.map +1 -0
- package/dist/core/mcp/oauth/mcp-token.d.ts +51 -0
- package/dist/core/mcp/oauth/mcp-token.d.ts.map +1 -0
- package/dist/core/mcp/oauth/mcp-token.js +129 -0
- package/dist/core/mcp/oauth/mcp-token.js.map +1 -0
- package/dist/core/mcp/oauth/pkce.d.ts +12 -0
- package/dist/core/mcp/oauth/pkce.d.ts.map +1 -0
- package/dist/core/mcp/oauth/pkce.js +11 -0
- package/dist/core/mcp/oauth/pkce.js.map +1 -0
- package/dist/core/mcp/oauth/register.d.ts +16 -0
- package/dist/core/mcp/oauth/register.d.ts.map +1 -0
- package/dist/core/mcp/oauth/register.js +42 -0
- package/dist/core/mcp/oauth/register.js.map +1 -0
- package/dist/core/mcp/oauth/token-client.d.ts +33 -0
- package/dist/core/mcp/oauth/token-client.d.ts.map +1 -0
- package/dist/core/mcp/oauth/token-client.js +65 -0
- package/dist/core/mcp/oauth/token-client.js.map +1 -0
- package/dist/core/mcp/runtime.d.ts +20 -0
- package/dist/core/mcp/runtime.d.ts.map +1 -0
- package/dist/core/mcp/runtime.js +37 -0
- package/dist/core/mcp/runtime.js.map +1 -0
- package/dist/core/mcp/schema.d.ts +22 -0
- package/dist/core/mcp/schema.d.ts.map +1 -0
- package/dist/core/mcp/schema.js +20 -0
- package/dist/core/mcp/schema.js.map +1 -0
- package/dist/core/mcp/server-manager.d.ts +43 -0
- package/dist/core/mcp/server-manager.d.ts.map +1 -0
- package/dist/core/mcp/server-manager.js +142 -0
- package/dist/core/mcp/server-manager.js.map +1 -0
- package/dist/core/mcp/types.d.ts +100 -0
- package/dist/core/mcp/types.d.ts.map +1 -0
- package/dist/core/mcp/types.js +8 -0
- package/dist/core/mcp/types.js.map +1 -0
- package/dist/core/permissions/gate.d.ts.map +1 -1
- package/dist/core/permissions/gate.js +45 -4
- package/dist/core/permissions/gate.js.map +1 -1
- package/dist/core/permissions/operations.d.ts +20 -0
- package/dist/core/permissions/operations.d.ts.map +1 -0
- package/dist/core/permissions/operations.js +9 -0
- package/dist/core/permissions/operations.js.map +1 -0
- package/dist/core/permissions/policy-command.d.ts +42 -0
- package/dist/core/permissions/policy-command.d.ts.map +1 -0
- package/dist/core/permissions/policy-command.js +44 -0
- package/dist/core/permissions/policy-command.js.map +1 -0
- package/dist/core/permissions/responder.d.ts +49 -2
- package/dist/core/permissions/responder.d.ts.map +1 -1
- package/dist/core/permissions/responder.js +20 -8
- package/dist/core/permissions/responder.js.map +1 -1
- package/dist/core/permissions/rules.d.ts.map +1 -1
- package/dist/core/permissions/rules.js +11 -1
- package/dist/core/permissions/rules.js.map +1 -1
- package/dist/core/permissions/store.d.ts +18 -0
- package/dist/core/permissions/store.d.ts.map +1 -1
- package/dist/core/permissions/store.js +89 -8
- package/dist/core/permissions/store.js.map +1 -1
- package/dist/core/policy-executor.d.ts +47 -0
- package/dist/core/policy-executor.d.ts.map +1 -0
- package/dist/core/policy-executor.js +228 -0
- package/dist/core/policy-executor.js.map +1 -0
- package/dist/core/policy-loader.d.ts +72 -0
- package/dist/core/policy-loader.d.ts.map +1 -0
- package/dist/core/policy-loader.js +195 -0
- package/dist/core/policy-loader.js.map +1 -0
- package/dist/core/project-trust.d.ts +1 -1
- package/dist/core/project-trust.d.ts.map +1 -1
- package/dist/core/project-trust.js.map +1 -1
- package/dist/core/sandbox/bwrap-arguments.d.ts +41 -0
- package/dist/core/sandbox/bwrap-arguments.d.ts.map +1 -0
- package/dist/core/sandbox/bwrap-arguments.js +136 -0
- package/dist/core/sandbox/bwrap-arguments.js.map +1 -0
- package/dist/core/sandbox/child-entry.d.ts.map +1 -1
- package/dist/core/sandbox/child-entry.js +3 -1
- package/dist/core/sandbox/child-entry.js.map +1 -1
- package/dist/core/sandbox/cli-launch.d.ts +84 -4
- package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
- package/dist/core/sandbox/cli-launch.js +115 -16
- package/dist/core/sandbox/cli-launch.js.map +1 -1
- package/dist/core/sandbox/cli-supervisor.d.ts +2 -0
- package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
- package/dist/core/sandbox/cli-supervisor.js +50 -2
- package/dist/core/sandbox/cli-supervisor.js.map +1 -1
- package/dist/core/sandbox/full-access.d.ts +28 -0
- package/dist/core/sandbox/full-access.d.ts.map +1 -0
- package/dist/core/sandbox/full-access.js +63 -0
- package/dist/core/sandbox/full-access.js.map +1 -0
- package/dist/core/sandbox/git-identity.d.ts +57 -0
- package/dist/core/sandbox/git-identity.d.ts.map +1 -0
- package/dist/core/sandbox/git-identity.js +78 -0
- package/dist/core/sandbox/git-identity.js.map +1 -0
- package/dist/core/sandbox/host-approval.d.ts +42 -0
- package/dist/core/sandbox/host-approval.d.ts.map +1 -0
- package/dist/core/sandbox/host-approval.js +99 -0
- package/dist/core/sandbox/host-approval.js.map +1 -0
- package/dist/core/sandbox/linux-backend.d.ts +14 -0
- package/dist/core/sandbox/linux-backend.d.ts.map +1 -1
- package/dist/core/sandbox/linux-backend.js +158 -80
- package/dist/core/sandbox/linux-backend.js.map +1 -1
- package/dist/core/sandbox/macos-backend.d.ts +5 -0
- package/dist/core/sandbox/macos-backend.d.ts.map +1 -1
- package/dist/core/sandbox/macos-backend.js +164 -5
- package/dist/core/sandbox/macos-backend.js.map +1 -1
- package/dist/core/sandbox/network-proxy.d.ts +19 -0
- package/dist/core/sandbox/network-proxy.d.ts.map +1 -1
- package/dist/core/sandbox/network-proxy.js +93 -19
- package/dist/core/sandbox/network-proxy.js.map +1 -1
- package/dist/core/sandbox/policy.d.ts +8 -0
- package/dist/core/sandbox/policy.d.ts.map +1 -1
- package/dist/core/sandbox/policy.js +29 -12
- package/dist/core/sandbox/policy.js.map +1 -1
- package/dist/core/sandbox/profiles.d.ts +25 -0
- package/dist/core/sandbox/profiles.d.ts.map +1 -0
- package/dist/core/sandbox/profiles.js +23 -0
- package/dist/core/sandbox/profiles.js.map +1 -0
- package/dist/core/sandbox/rpc/command-client.d.ts +28 -0
- package/dist/core/sandbox/rpc/command-client.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/command-client.js +86 -0
- package/dist/core/sandbox/rpc/command-client.js.map +1 -0
- package/dist/core/sandbox/rpc/command-proxy.d.ts +48 -0
- package/dist/core/sandbox/rpc/command-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/command-proxy.js +120 -0
- package/dist/core/sandbox/rpc/command-proxy.js.map +1 -0
- package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -1
- package/dist/core/sandbox/rpc/credential-proxy.js +2 -96
- package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -1
- package/dist/core/sandbox/rpc/framing.d.ts +35 -0
- package/dist/core/sandbox/rpc/framing.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/framing.js +114 -0
- package/dist/core/sandbox/rpc/framing.js.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-helper.d.ts +47 -0
- package/dist/core/sandbox/rpc/git-credential-helper.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-helper.js +222 -0
- package/dist/core/sandbox/rpc/git-credential-helper.js.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.d.ts +66 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.js +209 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.js.map +1 -0
- package/dist/core/sandbox/supervisor-temp.d.ts +18 -0
- package/dist/core/sandbox/supervisor-temp.d.ts.map +1 -0
- package/dist/core/sandbox/supervisor-temp.js +21 -0
- package/dist/core/sandbox/supervisor-temp.js.map +1 -0
- package/dist/core/sandbox/supervisor.d.ts +12 -1
- package/dist/core/sandbox/supervisor.d.ts.map +1 -1
- package/dist/core/sandbox/supervisor.js +15 -1
- package/dist/core/sandbox/supervisor.js.map +1 -1
- package/dist/core/sandbox/terminal-handoff.d.ts +68 -0
- package/dist/core/sandbox/terminal-handoff.d.ts.map +1 -0
- package/dist/core/sandbox/terminal-handoff.js +221 -0
- package/dist/core/sandbox/terminal-handoff.js.map +1 -0
- package/dist/core/sandbox/terminal-size.d.ts +13 -0
- package/dist/core/sandbox/terminal-size.d.ts.map +1 -1
- package/dist/core/sandbox/terminal-size.js +23 -2
- package/dist/core/sandbox/terminal-size.js.map +1 -1
- package/dist/core/sdk.d.ts +22 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +53 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +13 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +32 -5
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +163 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +107 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +2 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tools/background-shell.d.ts +39 -0
- package/dist/core/tools/background-shell.d.ts.map +1 -0
- package/dist/core/tools/background-shell.js +81 -0
- package/dist/core/tools/background-shell.js.map +1 -0
- package/dist/core/tools/bash-command-segments.d.ts.map +1 -1
- package/dist/core/tools/bash-command-segments.js +2 -0
- package/dist/core/tools/bash-command-segments.js.map +1 -1
- package/dist/core/tools/bash.d.ts +30 -15
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +228 -12
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/contract-snapshot.d.ts +58 -0
- package/dist/core/tools/contract-snapshot.d.ts.map +1 -0
- package/dist/core/tools/contract-snapshot.js +51 -0
- package/dist/core/tools/contract-snapshot.js.map +1 -0
- package/dist/core/tools/contract.d.ts +53 -5
- package/dist/core/tools/contract.d.ts.map +1 -1
- package/dist/core/tools/contract.js +22 -2
- package/dist/core/tools/contract.js.map +1 -1
- package/dist/core/tools/delegate.d.ts +6 -4
- package/dist/core/tools/delegate.d.ts.map +1 -1
- package/dist/core/tools/delegate.js +21 -11
- package/dist/core/tools/delegate.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +48 -0
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +215 -3
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +8 -0
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +53 -7
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +5 -2
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +5 -2
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +10 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +3 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +5 -2
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/lsp.d.ts +19 -1
- package/dist/core/tools/lsp.d.ts.map +1 -1
- package/dist/core/tools/lsp.js +163 -10
- package/dist/core/tools/lsp.js.map +1 -1
- package/dist/core/tools/path-permission.d.ts +3 -0
- package/dist/core/tools/path-permission.d.ts.map +1 -1
- package/dist/core/tools/path-permission.js +16 -4
- package/dist/core/tools/path-permission.js.map +1 -1
- package/dist/core/tools/path-utils.d.ts +27 -2
- package/dist/core/tools/path-utils.d.ts.map +1 -1
- package/dist/core/tools/path-utils.js +221 -4
- package/dist/core/tools/path-utils.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +20 -11
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/test.d.ts +54 -9
- package/dist/core/tools/test.d.ts.map +1 -1
- package/dist/core/tools/test.js +239 -19
- package/dist/core/tools/test.js.map +1 -1
- package/dist/core/tools/write.d.ts +7 -0
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +64 -9
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/verification-lifecycle.d.ts +77 -0
- package/dist/core/verification-lifecycle.d.ts.map +1 -0
- package/dist/core/verification-lifecycle.js +146 -0
- package/dist/core/verification-lifecycle.js.map +1 -0
- package/dist/core/workspace/artifacts.d.ts +45 -0
- package/dist/core/workspace/artifacts.d.ts.map +1 -0
- package/dist/core/workspace/artifacts.js +165 -0
- package/dist/core/workspace/artifacts.js.map +1 -0
- package/dist/core/workspace/comparison.d.ts +27 -0
- package/dist/core/workspace/comparison.d.ts.map +1 -0
- package/dist/core/workspace/comparison.js +94 -0
- package/dist/core/workspace/comparison.js.map +1 -0
- package/dist/core/workspace/git-observer.d.ts +48 -0
- package/dist/core/workspace/git-observer.d.ts.map +1 -0
- package/dist/core/workspace/git-observer.js +346 -0
- package/dist/core/workspace/git-observer.js.map +1 -0
- package/dist/core/workspace/projection.d.ts +3 -0
- package/dist/core/workspace/projection.d.ts.map +1 -0
- package/dist/core/workspace/projection.js +56 -0
- package/dist/core/workspace/projection.js.map +1 -0
- package/dist/core/workspace/state.d.ts +133 -0
- package/dist/core/workspace/state.d.ts.map +1 -0
- package/dist/core/workspace/state.js +96 -0
- package/dist/core/workspace/state.js.map +1 -0
- package/dist/extensions/llama/client.d.ts +6 -0
- package/dist/extensions/llama/client.d.ts.map +1 -1
- package/dist/extensions/llama/client.js +7 -0
- package/dist/extensions/llama/client.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts +3 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +27 -6
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +3 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +41 -9
- package/dist/main.js.map +1 -1
- package/dist/modes/acp/server.d.ts +55 -0
- package/dist/modes/acp/server.d.ts.map +1 -0
- package/dist/modes/acp/server.js +244 -0
- package/dist/modes/acp/server.js.map +1 -0
- package/dist/modes/acp/translate.d.ts +37 -0
- package/dist/modes/acp/translate.d.ts.map +1 -0
- package/dist/modes/acp/translate.js +50 -0
- package/dist/modes/acp/translate.js.map +1 -0
- package/dist/modes/index.d.ts +1 -0
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +1 -0
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +29 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +182 -23
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +7 -0
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +20 -0
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +9 -4
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +20 -9
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +7 -1
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +7 -3
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +19 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +70 -10
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +6 -6
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/permission-preview.d.ts +15 -0
- package/dist/modes/interactive/components/permission-preview.d.ts.map +1 -0
- package/dist/modes/interactive/components/permission-preview.js +33 -0
- package/dist/modes/interactive/components/permission-preview.js.map +1 -0
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +2 -2
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +10 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +23 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +64 -10
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +34 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +231 -26
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +12 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +15 -5
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +27 -12
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +7 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +7 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +3 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +12 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +0 -6
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +6 -4
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +6 -4
- package/dist/utils/version-check.js.map +1 -1
- package/docs/compaction.md +4 -0
- package/docs/custom-provider.md +7 -7
- package/docs/environment-variables.md +1 -1
- package/docs/extensions.md +18 -0
- package/docs/keybindings.md +1 -1
- package/docs/quickstart.md +1 -1
- package/docs/rpc.md +23 -0
- package/docs/sdk.md +25 -0
- package/docs/settings.md +12 -4
- package/docs/terminal-setup.md +12 -0
- package/docs/usage.md +4 -3
- package/examples/extensions/built-in-tool-renderer.ts +8 -4
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/git-checkpoint.ts +24 -36
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/minimal-mode.ts +2 -2
- package/examples/extensions/permission-gate.ts +1 -1
- package/examples/extensions/plan-mode/index.ts +1 -1
- package/examples/extensions/rpc-demo.ts +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +194 -46
- package/package.json +14 -12
- package/dist/server/create-harness.d.ts +0 -26
- package/dist/server/create-harness.d.ts.map +0 -1
- package/dist/server/create-harness.js +0 -107
- package/dist/server/create-harness.js.map +0 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the ecosystem-standard `{ "mcpServers": { … } }` shape from a project
|
|
3
|
+
* `.mcp.json` and a global `mcp.json`, project winning per server name.
|
|
4
|
+
*
|
|
5
|
+
* Every failure degrades. A malformed file yields no servers and one diagnostic; a
|
|
6
|
+
* malformed entry drops itself and leaves its siblings intact. A config file is the
|
|
7
|
+
* first thing a user hand-edits, so a parse error must never take the session down.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { CONFIG_DIR_NAME } from "../../config.js";
|
|
13
|
+
import { ALL_CAPABILITIES } from "../tools/contract.js";
|
|
14
|
+
import { DEFAULT_IDLE_TIMEOUT_MINUTES, DEFAULT_LIFECYCLE, } from "./types.js";
|
|
15
|
+
const LIFECYCLES = ["lazy", "eager", "keep-alive", "lazy-keep-alive"];
|
|
16
|
+
export const PROJECT_CONFIG_FILENAME = ".mcp.json";
|
|
17
|
+
export function globalMcpConfigPath() {
|
|
18
|
+
return join(homedir(), CONFIG_DIR_NAME, "mcp.json");
|
|
19
|
+
}
|
|
20
|
+
function isRecord(value) {
|
|
21
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22
|
+
}
|
|
23
|
+
function stringRecord(value) {
|
|
24
|
+
if (!isRecord(value))
|
|
25
|
+
return {};
|
|
26
|
+
const out = {};
|
|
27
|
+
for (const [key, item] of Object.entries(value)) {
|
|
28
|
+
if (typeof item === "string")
|
|
29
|
+
out[key] = item;
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
function stringArray(value) {
|
|
34
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
35
|
+
}
|
|
36
|
+
function parseTransport(entry) {
|
|
37
|
+
const command = typeof entry.command === "string" ? entry.command : undefined;
|
|
38
|
+
const url = typeof entry.url === "string" ? entry.url : undefined;
|
|
39
|
+
if (command && url)
|
|
40
|
+
return "declares both command and url; exactly one is required";
|
|
41
|
+
if (command) {
|
|
42
|
+
return {
|
|
43
|
+
kind: "stdio",
|
|
44
|
+
command,
|
|
45
|
+
args: stringArray(entry.args),
|
|
46
|
+
env: stringRecord(entry.env),
|
|
47
|
+
cwd: typeof entry.cwd === "string" ? entry.cwd : undefined,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (url) {
|
|
51
|
+
return {
|
|
52
|
+
kind: "http",
|
|
53
|
+
url,
|
|
54
|
+
headers: stringRecord(entry.headers),
|
|
55
|
+
bearerTokenEnv: typeof entry.bearerTokenEnv === "string" ? entry.bearerTokenEnv : undefined,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return "declares neither command nor url";
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* An absent `capabilities` is the common case, not an edge case: a config pasted from
|
|
62
|
+
* another host carries no Apex-specific field. Those servers get the full set, which
|
|
63
|
+
* keeps them classified (never `UNCLASSIFIED`) without narrowing anything the user
|
|
64
|
+
* did not ask to narrow.
|
|
65
|
+
*/
|
|
66
|
+
function parseCapabilities(entry, report) {
|
|
67
|
+
if (!("capabilities" in entry))
|
|
68
|
+
return ALL_CAPABILITIES;
|
|
69
|
+
if (!Array.isArray(entry.capabilities)) {
|
|
70
|
+
report("capabilities is not an array; falling back to the full capability set");
|
|
71
|
+
return ALL_CAPABILITIES;
|
|
72
|
+
}
|
|
73
|
+
const resolved = new Set();
|
|
74
|
+
const unknown = [];
|
|
75
|
+
for (const candidate of entry.capabilities) {
|
|
76
|
+
if (typeof candidate === "string" && ALL_CAPABILITIES.has(candidate)) {
|
|
77
|
+
resolved.add(candidate);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
unknown.push(String(candidate));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (unknown.length > 0)
|
|
84
|
+
report(`unknown capabilities dropped: ${unknown.join(", ")}`);
|
|
85
|
+
return resolved;
|
|
86
|
+
}
|
|
87
|
+
function parseLifecycle(entry, report) {
|
|
88
|
+
const value = entry.lifecycle;
|
|
89
|
+
if (value === undefined)
|
|
90
|
+
return DEFAULT_LIFECYCLE;
|
|
91
|
+
if (typeof value === "string" && LIFECYCLES.includes(value))
|
|
92
|
+
return value;
|
|
93
|
+
report(`unknown lifecycle ${JSON.stringify(value)}; using ${DEFAULT_LIFECYCLE}`);
|
|
94
|
+
return DEFAULT_LIFECYCLE;
|
|
95
|
+
}
|
|
96
|
+
function parseIdleTimeout(entry) {
|
|
97
|
+
const value = entry.idleTimeout;
|
|
98
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : DEFAULT_IDLE_TIMEOUT_MINUTES;
|
|
99
|
+
}
|
|
100
|
+
function parseAuth(entry, transport, report) {
|
|
101
|
+
const value = entry.auth;
|
|
102
|
+
if (value === undefined)
|
|
103
|
+
return undefined;
|
|
104
|
+
if (transport.kind !== "http") {
|
|
105
|
+
report("auth is only supported on HTTP servers; ignoring");
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
if (value === "oauth")
|
|
109
|
+
return "oauth";
|
|
110
|
+
report(`unknown auth ${JSON.stringify(value)}; only "oauth" is supported; ignoring`);
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
function readServers(path, diagnostics) {
|
|
114
|
+
const servers = new Map();
|
|
115
|
+
if (!existsSync(path))
|
|
116
|
+
return servers;
|
|
117
|
+
let parsed;
|
|
118
|
+
try {
|
|
119
|
+
parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
diagnostics.push({
|
|
123
|
+
path,
|
|
124
|
+
server: undefined,
|
|
125
|
+
message: `could not be parsed as JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
126
|
+
});
|
|
127
|
+
return servers;
|
|
128
|
+
}
|
|
129
|
+
if (!isRecord(parsed) || !isRecord(parsed.mcpServers)) {
|
|
130
|
+
diagnostics.push({ path, server: undefined, message: "has no mcpServers object" });
|
|
131
|
+
return servers;
|
|
132
|
+
}
|
|
133
|
+
for (const [name, raw] of Object.entries(parsed.mcpServers)) {
|
|
134
|
+
if (!isRecord(raw)) {
|
|
135
|
+
diagnostics.push({ path, server: name, message: "entry is not an object" });
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const transport = parseTransport(raw);
|
|
139
|
+
if (typeof transport === "string") {
|
|
140
|
+
diagnostics.push({ path, server: name, message: transport });
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const report = (message) => diagnostics.push({ path, server: name, message });
|
|
144
|
+
servers.set(name, {
|
|
145
|
+
name,
|
|
146
|
+
transport,
|
|
147
|
+
auth: parseAuth(raw, transport, report),
|
|
148
|
+
capabilities: parseCapabilities(raw, report),
|
|
149
|
+
lifecycle: parseLifecycle(raw, report),
|
|
150
|
+
idleTimeoutMinutes: parseIdleTimeout(raw),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return servers;
|
|
154
|
+
}
|
|
155
|
+
export function loadMcpConfig(options = {}) {
|
|
156
|
+
const diagnostics = [];
|
|
157
|
+
const globalPath = options.globalPath ?? globalMcpConfigPath();
|
|
158
|
+
const servers = readServers(globalPath, diagnostics);
|
|
159
|
+
if (options.projectPath) {
|
|
160
|
+
for (const [name, config] of readServers(options.projectPath, diagnostics)) {
|
|
161
|
+
servers.set(name, config);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { servers, diagnostics };
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/core/mcp/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reads the ecosystem-standard `{ \"mcpServers\": { … } }` shape from a project\n * `.mcp.json` and a global `mcp.json`, project winning per server name.\n *\n * Every failure degrades. A malformed file yields no servers and one diagnostic; a\n * malformed entry drops itself and leaves its siblings intact. A config file is the\n * first thing a user hand-edits, so a parse error must never take the session down.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { CONFIG_DIR_NAME } from \"../../config.ts\";\nimport { ALL_CAPABILITIES, type Capability } from \"../tools/contract.ts\";\nimport {\n\tDEFAULT_IDLE_TIMEOUT_MINUTES,\n\tDEFAULT_LIFECYCLE,\n\ttype McpConfig,\n\ttype McpConfigDiagnostic,\n\ttype McpLifecycle,\n\ttype McpServerConfig,\n\ttype McpTransport,\n} from \"./types.ts\";\n\nconst LIFECYCLES: readonly McpLifecycle[] = [\"lazy\", \"eager\", \"keep-alive\", \"lazy-keep-alive\"];\n\nexport const PROJECT_CONFIG_FILENAME = \".mcp.json\";\n\nexport function globalMcpConfigPath(): string {\n\treturn join(homedir(), CONFIG_DIR_NAME, \"mcp.json\");\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction stringRecord(value: unknown): Record<string, string> {\n\tif (!isRecord(value)) return {};\n\tconst out: Record<string, string> = {};\n\tfor (const [key, item] of Object.entries(value)) {\n\t\tif (typeof item === \"string\") out[key] = item;\n\t}\n\treturn out;\n}\n\nfunction stringArray(value: unknown): string[] {\n\treturn Array.isArray(value) ? value.filter((item): item is string => typeof item === \"string\") : [];\n}\n\nfunction parseTransport(entry: Record<string, unknown>): McpTransport | string {\n\tconst command = typeof entry.command === \"string\" ? entry.command : undefined;\n\tconst url = typeof entry.url === \"string\" ? entry.url : undefined;\n\n\tif (command && url) return \"declares both command and url; exactly one is required\";\n\tif (command) {\n\t\treturn {\n\t\t\tkind: \"stdio\",\n\t\t\tcommand,\n\t\t\targs: stringArray(entry.args),\n\t\t\tenv: stringRecord(entry.env),\n\t\t\tcwd: typeof entry.cwd === \"string\" ? entry.cwd : undefined,\n\t\t};\n\t}\n\tif (url) {\n\t\treturn {\n\t\t\tkind: \"http\",\n\t\t\turl,\n\t\t\theaders: stringRecord(entry.headers),\n\t\t\tbearerTokenEnv: typeof entry.bearerTokenEnv === \"string\" ? entry.bearerTokenEnv : undefined,\n\t\t};\n\t}\n\treturn \"declares neither command nor url\";\n}\n\n/**\n * An absent `capabilities` is the common case, not an edge case: a config pasted from\n * another host carries no Apex-specific field. Those servers get the full set, which\n * keeps them classified (never `UNCLASSIFIED`) without narrowing anything the user\n * did not ask to narrow.\n */\nfunction parseCapabilities(entry: Record<string, unknown>, report: (message: string) => void): ReadonlySet<Capability> {\n\tif (!(\"capabilities\" in entry)) return ALL_CAPABILITIES;\n\tif (!Array.isArray(entry.capabilities)) {\n\t\treport(\"capabilities is not an array; falling back to the full capability set\");\n\t\treturn ALL_CAPABILITIES;\n\t}\n\n\tconst resolved = new Set<Capability>();\n\tconst unknown: string[] = [];\n\tfor (const candidate of entry.capabilities) {\n\t\tif (typeof candidate === \"string\" && ALL_CAPABILITIES.has(candidate as Capability)) {\n\t\t\tresolved.add(candidate as Capability);\n\t\t} else {\n\t\t\tunknown.push(String(candidate));\n\t\t}\n\t}\n\tif (unknown.length > 0) report(`unknown capabilities dropped: ${unknown.join(\", \")}`);\n\treturn resolved;\n}\n\nfunction parseLifecycle(entry: Record<string, unknown>, report: (message: string) => void): McpLifecycle {\n\tconst value = entry.lifecycle;\n\tif (value === undefined) return DEFAULT_LIFECYCLE;\n\tif (typeof value === \"string\" && LIFECYCLES.includes(value as McpLifecycle)) return value as McpLifecycle;\n\treport(`unknown lifecycle ${JSON.stringify(value)}; using ${DEFAULT_LIFECYCLE}`);\n\treturn DEFAULT_LIFECYCLE;\n}\n\nfunction parseIdleTimeout(entry: Record<string, unknown>): number {\n\tconst value = entry.idleTimeout;\n\treturn typeof value === \"number\" && Number.isFinite(value) && value >= 0 ? value : DEFAULT_IDLE_TIMEOUT_MINUTES;\n}\n\nfunction parseAuth(\n\tentry: Record<string, unknown>,\n\ttransport: McpTransport,\n\treport: (message: string) => void,\n): \"oauth\" | undefined {\n\tconst value = entry.auth;\n\tif (value === undefined) return undefined;\n\tif (transport.kind !== \"http\") {\n\t\treport(\"auth is only supported on HTTP servers; ignoring\");\n\t\treturn undefined;\n\t}\n\tif (value === \"oauth\") return \"oauth\";\n\treport(`unknown auth ${JSON.stringify(value)}; only \"oauth\" is supported; ignoring`);\n\treturn undefined;\n}\n\nfunction readServers(path: string, diagnostics: McpConfigDiagnostic[]): Map<string, McpServerConfig> {\n\tconst servers = new Map<string, McpServerConfig>();\n\tif (!existsSync(path)) return servers;\n\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(readFileSync(path, \"utf-8\"));\n\t} catch (error) {\n\t\tdiagnostics.push({\n\t\t\tpath,\n\t\t\tserver: undefined,\n\t\t\tmessage: `could not be parsed as JSON: ${error instanceof Error ? error.message : String(error)}`,\n\t\t});\n\t\treturn servers;\n\t}\n\n\tif (!isRecord(parsed) || !isRecord(parsed.mcpServers)) {\n\t\tdiagnostics.push({ path, server: undefined, message: \"has no mcpServers object\" });\n\t\treturn servers;\n\t}\n\n\tfor (const [name, raw] of Object.entries(parsed.mcpServers)) {\n\t\tif (!isRecord(raw)) {\n\t\t\tdiagnostics.push({ path, server: name, message: \"entry is not an object\" });\n\t\t\tcontinue;\n\t\t}\n\t\tconst transport = parseTransport(raw);\n\t\tif (typeof transport === \"string\") {\n\t\t\tdiagnostics.push({ path, server: name, message: transport });\n\t\t\tcontinue;\n\t\t}\n\t\tconst report = (message: string) => diagnostics.push({ path, server: name, message });\n\t\tservers.set(name, {\n\t\t\tname,\n\t\t\ttransport,\n\t\t\tauth: parseAuth(raw, transport, report),\n\t\t\tcapabilities: parseCapabilities(raw, report),\n\t\t\tlifecycle: parseLifecycle(raw, report),\n\t\t\tidleTimeoutMinutes: parseIdleTimeout(raw),\n\t\t});\n\t}\n\n\treturn servers;\n}\n\nexport function loadMcpConfig(options: { projectPath?: string; globalPath?: string } = {}): McpConfig {\n\tconst diagnostics: McpConfigDiagnostic[] = [];\n\tconst globalPath = options.globalPath ?? globalMcpConfigPath();\n\n\tconst servers = readServers(globalPath, diagnostics);\n\tif (options.projectPath) {\n\t\tfor (const [name, config] of readServers(options.projectPath, diagnostics)) {\n\t\t\tservers.set(name, config);\n\t\t}\n\t}\n\n\treturn { servers, diagnostics };\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MCP SDK lives here and nowhere else.
|
|
3
|
+
*
|
|
4
|
+
* Everything above this file talks to `McpConnection`, so the lifecycle logic, the
|
|
5
|
+
* proxy tool, and their tests never import the SDK or start a process. This module is
|
|
6
|
+
* the boundary where an external protocol becomes an internal type.
|
|
7
|
+
*/
|
|
8
|
+
import type { CredentialStore } from "@earendil-works/pi-ai";
|
|
9
|
+
import type { FetchLike } from "./oauth/discover.ts";
|
|
10
|
+
import type { McpConnection, McpConnector, McpServerConfig } from "./types.ts";
|
|
11
|
+
/**
|
|
12
|
+
* Authorization for one HTTP connection, in precedence order: a static configured
|
|
13
|
+
* header, then a named bearer env var, then the credential store's OAuth token.
|
|
14
|
+
* Later wins, because each later source is the more deliberately configured one.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveAuthorizationHeaders(server: McpServerConfig, oauthAuthorization?: string): Record<string, string>;
|
|
17
|
+
export declare function connectMcpServer(server: McpServerConfig, oauthAuthorization?: string): Promise<McpConnection>;
|
|
18
|
+
export interface SessionMcpConnectorOptions {
|
|
19
|
+
/**
|
|
20
|
+
* The session's credential store: `AuthStorage` on the host, `SandboxAuthStorage`
|
|
21
|
+
* in a sandboxed child (reads from the projection, writes through the supervisor
|
|
22
|
+
* channel). OAuth-configured servers without one fail closed.
|
|
23
|
+
*/
|
|
24
|
+
credentials?: CredentialStore;
|
|
25
|
+
now?: () => number;
|
|
26
|
+
fetch?: FetchLike;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The session connector: resolves OAuth tokens for servers configured with
|
|
30
|
+
* `auth: "oauth"`, and behaves exactly like `connectMcpServer` for everything else.
|
|
31
|
+
* A missing authorization throws before the transport is constructed, so no OAuth
|
|
32
|
+
* endpoint is ever contacted from a tool call path (spec 2026-09-01-mcp-oauth).
|
|
33
|
+
*/
|
|
34
|
+
export declare function createSessionMcpConnector(options?: SessionMcpConnectorOptions): McpConnector;
|
|
35
|
+
//# sourceMappingURL=connector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/connector.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAI7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAA6B,aAAa,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE1G;;;;GAIG;AACH,wBAAgB,2BAA2B,CAC1C,MAAM,EAAE,eAAe,EACvB,kBAAkB,CAAC,EAAE,MAAM,GACzB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB;AAwCD,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,eAAe,EAAE,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAqBnH;AAED,MAAM,WAAW,0BAA0B;IAC1C;;;;OAIG;IACH,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,0BAA+B,GAAG,YAAY,CAchG","sourcesContent":["/**\n * The MCP SDK lives here and nowhere else.\n *\n * Everything above this file talks to `McpConnection`, so the lifecycle logic, the\n * proxy tool, and their tests never import the SDK or start a process. This module is\n * the boundary where an external protocol becomes an internal type.\n */\n\nimport type { CredentialStore } from \"@earendil-works/pi-ai\";\nimport { Client, StreamableHTTPClientTransport } from \"@modelcontextprotocol/client\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/client/stdio\";\nimport { APP_NAME, VERSION } from \"../../config.ts\";\nimport type { FetchLike } from \"./oauth/discover.ts\";\nimport { authRequired, ensureFreshServerToken } from \"./oauth/mcp-token.ts\";\nimport type { CachedTool, McpCallResult, McpConnection, McpConnector, McpServerConfig } from \"./types.ts\";\n\n/**\n * Authorization for one HTTP connection, in precedence order: a static configured\n * header, then a named bearer env var, then the credential store's OAuth token.\n * Later wins, because each later source is the more deliberately configured one.\n */\nexport function resolveAuthorizationHeaders(\n\tserver: McpServerConfig,\n\toauthAuthorization?: string,\n): Record<string, string> {\n\tif (server.transport.kind !== \"http\") return {};\n\tconst headers = { ...server.transport.headers };\n\tconst token = server.transport.bearerTokenEnv ? process.env[server.transport.bearerTokenEnv] : undefined;\n\tif (token) headers.Authorization = `Bearer ${token}`;\n\tif (oauthAuthorization) headers.Authorization = oauthAuthorization;\n\treturn headers;\n}\n\nfunction createTransport(server: McpServerConfig, oauthAuthorization?: string) {\n\tif (server.transport.kind === \"stdio\") {\n\t\treturn new StdioClientTransport({\n\t\t\tcommand: server.transport.command,\n\t\t\targs: server.transport.args,\n\t\t\tenv: { ...process.env, ...server.transport.env } as Record<string, string>,\n\t\t\tcwd: server.transport.cwd,\n\t\t});\n\t}\n\n\treturn new StreamableHTTPClientTransport(new URL(server.transport.url), {\n\t\trequestInit: { headers: resolveAuthorizationHeaders(server, oauthAuthorization) },\n\t});\n}\n\n/**\n * Server content arrives as a heterogeneous array. Only text is carried forward; an\n * image or an embedded resource is named rather than dropped silently, so a caller\n * can tell the difference between \"returned nothing\" and \"returned something we do\n * not render yet\".\n */\nfunction toCallResult(raw: unknown): McpCallResult {\n\tconst result = raw as { content?: unknown; isError?: unknown };\n\tconst parts = Array.isArray(result?.content) ? result.content : [];\n\tconst content = parts.map((part) => {\n\t\tconst item = part as { type?: unknown; text?: unknown };\n\t\tif (item?.type === \"text\" && typeof item.text === \"string\") {\n\t\t\treturn { type: \"text\" as const, text: item.text };\n\t\t}\n\t\treturn { type: \"text\" as const, text: `[${String(item?.type ?? \"unknown\")} content omitted]` };\n\t});\n\n\treturn {\n\t\tcontent: content.length > 0 ? content : [{ type: \"text\" as const, text: \"(no content)\" }],\n\t\tisError: result?.isError === true,\n\t};\n}\n\nexport async function connectMcpServer(server: McpServerConfig, oauthAuthorization?: string): Promise<McpConnection> {\n\tconst client = new Client({ name: APP_NAME, version: VERSION });\n\tawait client.connect(createTransport(server, oauthAuthorization));\n\n\treturn {\n\t\tasync listTools(): Promise<CachedTool[]> {\n\t\t\tconst { tools } = await client.listTools();\n\t\t\treturn tools.map((tool) => ({\n\t\t\t\tserver: server.name,\n\t\t\t\tname: tool.name,\n\t\t\t\tdescription: tool.description ?? \"\",\n\t\t\t\tinputSchema: tool.inputSchema,\n\t\t\t}));\n\t\t},\n\t\tasync callTool(name: string, args: unknown): Promise<McpCallResult> {\n\t\t\treturn toCallResult(await client.callTool({ name, arguments: (args ?? {}) as Record<string, unknown> }));\n\t\t},\n\t\tasync close(): Promise<void> {\n\t\t\tawait client.close();\n\t\t},\n\t};\n}\n\nexport interface SessionMcpConnectorOptions {\n\t/**\n\t * The session's credential store: `AuthStorage` on the host, `SandboxAuthStorage`\n\t * in a sandboxed child (reads from the projection, writes through the supervisor\n\t * channel). OAuth-configured servers without one fail closed.\n\t */\n\tcredentials?: CredentialStore;\n\tnow?: () => number;\n\tfetch?: FetchLike;\n}\n\n/**\n * The session connector: resolves OAuth tokens for servers configured with\n * `auth: \"oauth\"`, and behaves exactly like `connectMcpServer` for everything else.\n * A missing authorization throws before the transport is constructed, so no OAuth\n * endpoint is ever contacted from a tool call path (spec 2026-09-01-mcp-oauth).\n */\nexport function createSessionMcpConnector(options: SessionMcpConnectorOptions = {}): McpConnector {\n\treturn async (server) => {\n\t\tif (server.transport.kind === \"http\" && server.auth === \"oauth\") {\n\t\t\tif (!options.credentials) throw authRequired(server.name);\n\t\t\tconst accessToken = await ensureFreshServerToken({\n\t\t\t\tserver,\n\t\t\t\tcredentials: options.credentials,\n\t\t\t\tnow: options.now,\n\t\t\t\tfetch: options.fetch,\n\t\t\t});\n\t\t\treturn connectMcpServer(server, `Bearer ${accessToken}`);\n\t\t}\n\t\treturn connectMcpServer(server);\n\t};\n}\n"]}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MCP SDK lives here and nowhere else.
|
|
3
|
+
*
|
|
4
|
+
* Everything above this file talks to `McpConnection`, so the lifecycle logic, the
|
|
5
|
+
* proxy tool, and their tests never import the SDK or start a process. This module is
|
|
6
|
+
* the boundary where an external protocol becomes an internal type.
|
|
7
|
+
*/
|
|
8
|
+
import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
|
9
|
+
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
|
10
|
+
import { APP_NAME, VERSION } from "../../config.js";
|
|
11
|
+
import { authRequired, ensureFreshServerToken } from "./oauth/mcp-token.js";
|
|
12
|
+
/**
|
|
13
|
+
* Authorization for one HTTP connection, in precedence order: a static configured
|
|
14
|
+
* header, then a named bearer env var, then the credential store's OAuth token.
|
|
15
|
+
* Later wins, because each later source is the more deliberately configured one.
|
|
16
|
+
*/
|
|
17
|
+
export function resolveAuthorizationHeaders(server, oauthAuthorization) {
|
|
18
|
+
if (server.transport.kind !== "http")
|
|
19
|
+
return {};
|
|
20
|
+
const headers = { ...server.transport.headers };
|
|
21
|
+
const token = server.transport.bearerTokenEnv ? process.env[server.transport.bearerTokenEnv] : undefined;
|
|
22
|
+
if (token)
|
|
23
|
+
headers.Authorization = `Bearer ${token}`;
|
|
24
|
+
if (oauthAuthorization)
|
|
25
|
+
headers.Authorization = oauthAuthorization;
|
|
26
|
+
return headers;
|
|
27
|
+
}
|
|
28
|
+
function createTransport(server, oauthAuthorization) {
|
|
29
|
+
if (server.transport.kind === "stdio") {
|
|
30
|
+
return new StdioClientTransport({
|
|
31
|
+
command: server.transport.command,
|
|
32
|
+
args: server.transport.args,
|
|
33
|
+
env: { ...process.env, ...server.transport.env },
|
|
34
|
+
cwd: server.transport.cwd,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return new StreamableHTTPClientTransport(new URL(server.transport.url), {
|
|
38
|
+
requestInit: { headers: resolveAuthorizationHeaders(server, oauthAuthorization) },
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Server content arrives as a heterogeneous array. Only text is carried forward; an
|
|
43
|
+
* image or an embedded resource is named rather than dropped silently, so a caller
|
|
44
|
+
* can tell the difference between "returned nothing" and "returned something we do
|
|
45
|
+
* not render yet".
|
|
46
|
+
*/
|
|
47
|
+
function toCallResult(raw) {
|
|
48
|
+
const result = raw;
|
|
49
|
+
const parts = Array.isArray(result?.content) ? result.content : [];
|
|
50
|
+
const content = parts.map((part) => {
|
|
51
|
+
const item = part;
|
|
52
|
+
if (item?.type === "text" && typeof item.text === "string") {
|
|
53
|
+
return { type: "text", text: item.text };
|
|
54
|
+
}
|
|
55
|
+
return { type: "text", text: `[${String(item?.type ?? "unknown")} content omitted]` };
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
content: content.length > 0 ? content : [{ type: "text", text: "(no content)" }],
|
|
59
|
+
isError: result?.isError === true,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export async function connectMcpServer(server, oauthAuthorization) {
|
|
63
|
+
const client = new Client({ name: APP_NAME, version: VERSION });
|
|
64
|
+
await client.connect(createTransport(server, oauthAuthorization));
|
|
65
|
+
return {
|
|
66
|
+
async listTools() {
|
|
67
|
+
const { tools } = await client.listTools();
|
|
68
|
+
return tools.map((tool) => ({
|
|
69
|
+
server: server.name,
|
|
70
|
+
name: tool.name,
|
|
71
|
+
description: tool.description ?? "",
|
|
72
|
+
inputSchema: tool.inputSchema,
|
|
73
|
+
}));
|
|
74
|
+
},
|
|
75
|
+
async callTool(name, args) {
|
|
76
|
+
return toCallResult(await client.callTool({ name, arguments: (args ?? {}) }));
|
|
77
|
+
},
|
|
78
|
+
async close() {
|
|
79
|
+
await client.close();
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The session connector: resolves OAuth tokens for servers configured with
|
|
85
|
+
* `auth: "oauth"`, and behaves exactly like `connectMcpServer` for everything else.
|
|
86
|
+
* A missing authorization throws before the transport is constructed, so no OAuth
|
|
87
|
+
* endpoint is ever contacted from a tool call path (spec 2026-09-01-mcp-oauth).
|
|
88
|
+
*/
|
|
89
|
+
export function createSessionMcpConnector(options = {}) {
|
|
90
|
+
return async (server) => {
|
|
91
|
+
if (server.transport.kind === "http" && server.auth === "oauth") {
|
|
92
|
+
if (!options.credentials)
|
|
93
|
+
throw authRequired(server.name);
|
|
94
|
+
const accessToken = await ensureFreshServerToken({
|
|
95
|
+
server,
|
|
96
|
+
credentials: options.credentials,
|
|
97
|
+
now: options.now,
|
|
98
|
+
fetch: options.fetch,
|
|
99
|
+
});
|
|
100
|
+
return connectMcpServer(server, `Bearer ${accessToken}`);
|
|
101
|
+
}
|
|
102
|
+
return connectMcpServer(server);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=connector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connector.js","sourceRoot":"","sources":["../../../src/core/mcp/connector.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The MCP SDK lives here and nowhere else.\n *\n * Everything above this file talks to `McpConnection`, so the lifecycle logic, the\n * proxy tool, and their tests never import the SDK or start a process. This module is\n * the boundary where an external protocol becomes an internal type.\n */\n\nimport type { CredentialStore } from \"@earendil-works/pi-ai\";\nimport { Client, StreamableHTTPClientTransport } from \"@modelcontextprotocol/client\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/client/stdio\";\nimport { APP_NAME, VERSION } from \"../../config.ts\";\nimport type { FetchLike } from \"./oauth/discover.ts\";\nimport { authRequired, ensureFreshServerToken } from \"./oauth/mcp-token.ts\";\nimport type { CachedTool, McpCallResult, McpConnection, McpConnector, McpServerConfig } from \"./types.ts\";\n\n/**\n * Authorization for one HTTP connection, in precedence order: a static configured\n * header, then a named bearer env var, then the credential store's OAuth token.\n * Later wins, because each later source is the more deliberately configured one.\n */\nexport function resolveAuthorizationHeaders(\n\tserver: McpServerConfig,\n\toauthAuthorization?: string,\n): Record<string, string> {\n\tif (server.transport.kind !== \"http\") return {};\n\tconst headers = { ...server.transport.headers };\n\tconst token = server.transport.bearerTokenEnv ? process.env[server.transport.bearerTokenEnv] : undefined;\n\tif (token) headers.Authorization = `Bearer ${token}`;\n\tif (oauthAuthorization) headers.Authorization = oauthAuthorization;\n\treturn headers;\n}\n\nfunction createTransport(server: McpServerConfig, oauthAuthorization?: string) {\n\tif (server.transport.kind === \"stdio\") {\n\t\treturn new StdioClientTransport({\n\t\t\tcommand: server.transport.command,\n\t\t\targs: server.transport.args,\n\t\t\tenv: { ...process.env, ...server.transport.env } as Record<string, string>,\n\t\t\tcwd: server.transport.cwd,\n\t\t});\n\t}\n\n\treturn new StreamableHTTPClientTransport(new URL(server.transport.url), {\n\t\trequestInit: { headers: resolveAuthorizationHeaders(server, oauthAuthorization) },\n\t});\n}\n\n/**\n * Server content arrives as a heterogeneous array. Only text is carried forward; an\n * image or an embedded resource is named rather than dropped silently, so a caller\n * can tell the difference between \"returned nothing\" and \"returned something we do\n * not render yet\".\n */\nfunction toCallResult(raw: unknown): McpCallResult {\n\tconst result = raw as { content?: unknown; isError?: unknown };\n\tconst parts = Array.isArray(result?.content) ? result.content : [];\n\tconst content = parts.map((part) => {\n\t\tconst item = part as { type?: unknown; text?: unknown };\n\t\tif (item?.type === \"text\" && typeof item.text === \"string\") {\n\t\t\treturn { type: \"text\" as const, text: item.text };\n\t\t}\n\t\treturn { type: \"text\" as const, text: `[${String(item?.type ?? \"unknown\")} content omitted]` };\n\t});\n\n\treturn {\n\t\tcontent: content.length > 0 ? content : [{ type: \"text\" as const, text: \"(no content)\" }],\n\t\tisError: result?.isError === true,\n\t};\n}\n\nexport async function connectMcpServer(server: McpServerConfig, oauthAuthorization?: string): Promise<McpConnection> {\n\tconst client = new Client({ name: APP_NAME, version: VERSION });\n\tawait client.connect(createTransport(server, oauthAuthorization));\n\n\treturn {\n\t\tasync listTools(): Promise<CachedTool[]> {\n\t\t\tconst { tools } = await client.listTools();\n\t\t\treturn tools.map((tool) => ({\n\t\t\t\tserver: server.name,\n\t\t\t\tname: tool.name,\n\t\t\t\tdescription: tool.description ?? \"\",\n\t\t\t\tinputSchema: tool.inputSchema,\n\t\t\t}));\n\t\t},\n\t\tasync callTool(name: string, args: unknown): Promise<McpCallResult> {\n\t\t\treturn toCallResult(await client.callTool({ name, arguments: (args ?? {}) as Record<string, unknown> }));\n\t\t},\n\t\tasync close(): Promise<void> {\n\t\t\tawait client.close();\n\t\t},\n\t};\n}\n\nexport interface SessionMcpConnectorOptions {\n\t/**\n\t * The session's credential store: `AuthStorage` on the host, `SandboxAuthStorage`\n\t * in a sandboxed child (reads from the projection, writes through the supervisor\n\t * channel). OAuth-configured servers without one fail closed.\n\t */\n\tcredentials?: CredentialStore;\n\tnow?: () => number;\n\tfetch?: FetchLike;\n}\n\n/**\n * The session connector: resolves OAuth tokens for servers configured with\n * `auth: \"oauth\"`, and behaves exactly like `connectMcpServer` for everything else.\n * A missing authorization throws before the transport is constructed, so no OAuth\n * endpoint is ever contacted from a tool call path (spec 2026-09-01-mcp-oauth).\n */\nexport function createSessionMcpConnector(options: SessionMcpConnectorOptions = {}): McpConnector {\n\treturn async (server) => {\n\t\tif (server.transport.kind === \"http\" && server.auth === \"oauth\") {\n\t\t\tif (!options.credentials) throw authRequired(server.name);\n\t\t\tconst accessToken = await ensureFreshServerToken({\n\t\t\t\tserver,\n\t\t\t\tcredentials: options.credentials,\n\t\t\t\tnow: options.now,\n\t\t\t\tfetch: options.fetch,\n\t\t\t});\n\t\t\treturn connectMcpServer(server, `Bearer ${accessToken}`);\n\t\t}\n\t\treturn connectMcpServer(server);\n\t};\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tool contract for the `mcp` proxy tool (ADR 0010).
|
|
3
|
+
*
|
|
4
|
+
* This module is the reason MCP is built into Apex Code rather than adopted as an
|
|
5
|
+
* extension. A tool registered from outside the repo cannot supply a contract, so it
|
|
6
|
+
* resolves `UNCLASSIFIED`: every capability, `ask` by default, and rule matching by
|
|
7
|
+
* exact serialized arguments. Under that fallback a second call with different
|
|
8
|
+
* arguments prompts again and no result is ever evictable.
|
|
9
|
+
*
|
|
10
|
+
* The grammar is owned here and nowhere else. The permission engine resolves
|
|
11
|
+
* precedence between rules and never learns what one means.
|
|
12
|
+
*/
|
|
13
|
+
import type { ToolContract } from "../tools/contract.ts";
|
|
14
|
+
import type { McpToolDetails, mcpToolSchema } from "./schema.ts";
|
|
15
|
+
import type { McpServerConfig } from "./types.ts";
|
|
16
|
+
/** Rule covering the metadata actions, which read the local cache and reach no server. */
|
|
17
|
+
export declare const METADATA_RULE = "Mcp(metadata)";
|
|
18
|
+
export declare function createMcpToolContract(servers: ReadonlyMap<string, McpServerConfig>): ToolContract<typeof mcpToolSchema, McpToolDetails>;
|
|
19
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,KAAK,EAAE,cAAc,EAAiB,aAAa,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,0FAA0F;AAC1F,eAAO,MAAM,aAAa,kBAAkB,CAAC;AA8D7C,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,GAC3C,YAAY,CAAC,OAAO,aAAa,EAAE,cAAc,CAAC,CAYpD","sourcesContent":["/**\n * The tool contract for the `mcp` proxy tool (ADR 0010).\n *\n * This module is the reason MCP is built into Apex Code rather than adopted as an\n * extension. A tool registered from outside the repo cannot supply a contract, so it\n * resolves `UNCLASSIFIED`: every capability, `ask` by default, and rule matching by\n * exact serialized arguments. Under that fallback a second call with different\n * arguments prompts again and no result is ever evictable.\n *\n * The grammar is owned here and nowhere else. The permission engine resolves\n * precedence between rules and never learns what one means.\n */\n\nimport type { Capability, ToolContract } from \"../tools/contract.ts\";\nimport { ALL_CAPABILITIES } from \"../tools/contract.ts\";\nimport type { McpToolDetails, McpToolParams, mcpToolSchema } from \"./schema.ts\";\nimport type { McpServerConfig } from \"./types.ts\";\n\n/** Rule covering the metadata actions, which read the local cache and reach no server. */\nexport const METADATA_RULE = \"Mcp(metadata)\";\n\nconst RULE_PATTERN = /^Mcp\\(([^():*\\s]+):([^():\\s]+)\\)$/;\n\nfunction isCall(params: McpToolParams): params is McpToolParams & { tool: string } {\n\treturn typeof params.tool === \"string\" && params.tool.length > 0;\n}\n\n/** `<server>:<tool>`, the form the model calls and the form a rule names. */\nfunction splitQualifiedName(qualified: string): { server: string; tool: string } | undefined {\n\tconst index = qualified.indexOf(\":\");\n\tif (index <= 0 || index === qualified.length - 1) return undefined;\n\treturn { server: qualified.slice(0, index), tool: qualified.slice(index + 1) };\n}\n\nfunction matches(ruleContent: string, params: McpToolParams): boolean {\n\tif (ruleContent === METADATA_RULE) return !isCall(params);\n\tif (!isCall(params)) return false;\n\n\tconst rule = RULE_PATTERN.exec(ruleContent);\n\tif (!rule) return false;\n\tconst [, ruleServer, ruleTool] = rule;\n\n\tconst target = splitQualifiedName(params.tool);\n\tif (!target) return false;\n\n\tif (ruleServer !== target.server) return false;\n\treturn ruleTool === \"*\" || ruleTool === target.tool;\n}\n\nfunction ruleForCall(params: McpToolParams): string | null {\n\tif (!isCall(params)) return METADATA_RULE;\n\tconst target = splitQualifiedName(params.tool);\n\treturn target ? `Mcp(${target.server}:${target.tool})` : null;\n}\n\nfunction describe(ruleContent: string): string {\n\tif (ruleContent === METADATA_RULE) return \"Searching and describing MCP tools (no server is contacted)\";\n\n\tconst rule = RULE_PATTERN.exec(ruleContent);\n\tif (!rule) return `Unrecognized MCP rule: ${ruleContent}`;\n\tconst [, server, tool] = rule;\n\treturn tool === \"*\"\n\t\t? `Calling any tool on the \"${server}\" MCP server`\n\t\t: `Calling \"${tool}\" on the \"${server}\" MCP server`;\n}\n\n/**\n * One proxy tool carries one capability set, and that set feeds the delegation\n * ceiling (ADR 0008) and mode resolution. The union across configured servers is the\n * only sound answer: narrower would understate what a call can do, and a fixed full\n * set would ignore what a user took the trouble to declare.\n */\nfunction unionCapabilities(servers: Iterable<McpServerConfig>): ReadonlySet<Capability> {\n\tconst union = new Set<Capability>();\n\tfor (const server of servers) {\n\t\tfor (const capability of server.capabilities) union.add(capability);\n\t\tif (union.size === ALL_CAPABILITIES.size) return ALL_CAPABILITIES;\n\t}\n\treturn union;\n}\n\nexport function createMcpToolContract(\n\tservers: ReadonlyMap<string, McpServerConfig>,\n): ToolContract<typeof mcpToolSchema, McpToolDetails> {\n\treturn {\n\t\tcapabilities: unionCapabilities(servers.values()),\n\t\tpermission: {\n\t\t\tdefaultBehavior: \"ask\",\n\t\t\tmatches,\n\t\t\tdescribe,\n\t\t\truleForCall,\n\t\t},\n\t\tcontext: { resultRecoverable: false, deferSchema: true },\n\t\tevidence: { emits: new Set(), capture: () => [] },\n\t};\n}\n"]}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tool contract for the `mcp` proxy tool (ADR 0010).
|
|
3
|
+
*
|
|
4
|
+
* This module is the reason MCP is built into Apex Code rather than adopted as an
|
|
5
|
+
* extension. A tool registered from outside the repo cannot supply a contract, so it
|
|
6
|
+
* resolves `UNCLASSIFIED`: every capability, `ask` by default, and rule matching by
|
|
7
|
+
* exact serialized arguments. Under that fallback a second call with different
|
|
8
|
+
* arguments prompts again and no result is ever evictable.
|
|
9
|
+
*
|
|
10
|
+
* The grammar is owned here and nowhere else. The permission engine resolves
|
|
11
|
+
* precedence between rules and never learns what one means.
|
|
12
|
+
*/
|
|
13
|
+
import { ALL_CAPABILITIES } from "../tools/contract.js";
|
|
14
|
+
/** Rule covering the metadata actions, which read the local cache and reach no server. */
|
|
15
|
+
export const METADATA_RULE = "Mcp(metadata)";
|
|
16
|
+
const RULE_PATTERN = /^Mcp\(([^():*\s]+):([^():\s]+)\)$/;
|
|
17
|
+
function isCall(params) {
|
|
18
|
+
return typeof params.tool === "string" && params.tool.length > 0;
|
|
19
|
+
}
|
|
20
|
+
/** `<server>:<tool>`, the form the model calls and the form a rule names. */
|
|
21
|
+
function splitQualifiedName(qualified) {
|
|
22
|
+
const index = qualified.indexOf(":");
|
|
23
|
+
if (index <= 0 || index === qualified.length - 1)
|
|
24
|
+
return undefined;
|
|
25
|
+
return { server: qualified.slice(0, index), tool: qualified.slice(index + 1) };
|
|
26
|
+
}
|
|
27
|
+
function matches(ruleContent, params) {
|
|
28
|
+
if (ruleContent === METADATA_RULE)
|
|
29
|
+
return !isCall(params);
|
|
30
|
+
if (!isCall(params))
|
|
31
|
+
return false;
|
|
32
|
+
const rule = RULE_PATTERN.exec(ruleContent);
|
|
33
|
+
if (!rule)
|
|
34
|
+
return false;
|
|
35
|
+
const [, ruleServer, ruleTool] = rule;
|
|
36
|
+
const target = splitQualifiedName(params.tool);
|
|
37
|
+
if (!target)
|
|
38
|
+
return false;
|
|
39
|
+
if (ruleServer !== target.server)
|
|
40
|
+
return false;
|
|
41
|
+
return ruleTool === "*" || ruleTool === target.tool;
|
|
42
|
+
}
|
|
43
|
+
function ruleForCall(params) {
|
|
44
|
+
if (!isCall(params))
|
|
45
|
+
return METADATA_RULE;
|
|
46
|
+
const target = splitQualifiedName(params.tool);
|
|
47
|
+
return target ? `Mcp(${target.server}:${target.tool})` : null;
|
|
48
|
+
}
|
|
49
|
+
function describe(ruleContent) {
|
|
50
|
+
if (ruleContent === METADATA_RULE)
|
|
51
|
+
return "Searching and describing MCP tools (no server is contacted)";
|
|
52
|
+
const rule = RULE_PATTERN.exec(ruleContent);
|
|
53
|
+
if (!rule)
|
|
54
|
+
return `Unrecognized MCP rule: ${ruleContent}`;
|
|
55
|
+
const [, server, tool] = rule;
|
|
56
|
+
return tool === "*"
|
|
57
|
+
? `Calling any tool on the "${server}" MCP server`
|
|
58
|
+
: `Calling "${tool}" on the "${server}" MCP server`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* One proxy tool carries one capability set, and that set feeds the delegation
|
|
62
|
+
* ceiling (ADR 0008) and mode resolution. The union across configured servers is the
|
|
63
|
+
* only sound answer: narrower would understate what a call can do, and a fixed full
|
|
64
|
+
* set would ignore what a user took the trouble to declare.
|
|
65
|
+
*/
|
|
66
|
+
function unionCapabilities(servers) {
|
|
67
|
+
const union = new Set();
|
|
68
|
+
for (const server of servers) {
|
|
69
|
+
for (const capability of server.capabilities)
|
|
70
|
+
union.add(capability);
|
|
71
|
+
if (union.size === ALL_CAPABILITIES.size)
|
|
72
|
+
return ALL_CAPABILITIES;
|
|
73
|
+
}
|
|
74
|
+
return union;
|
|
75
|
+
}
|
|
76
|
+
export function createMcpToolContract(servers) {
|
|
77
|
+
return {
|
|
78
|
+
capabilities: unionCapabilities(servers.values()),
|
|
79
|
+
permission: {
|
|
80
|
+
defaultBehavior: "ask",
|
|
81
|
+
matches,
|
|
82
|
+
describe,
|
|
83
|
+
ruleForCall,
|
|
84
|
+
},
|
|
85
|
+
context: { resultRecoverable: false, deferSchema: true },
|
|
86
|
+
evidence: { emits: new Set(), capture: () => [] },
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../../src/core/mcp/contract.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The tool contract for the `mcp` proxy tool (ADR 0010).\n *\n * This module is the reason MCP is built into Apex Code rather than adopted as an\n * extension. A tool registered from outside the repo cannot supply a contract, so it\n * resolves `UNCLASSIFIED`: every capability, `ask` by default, and rule matching by\n * exact serialized arguments. Under that fallback a second call with different\n * arguments prompts again and no result is ever evictable.\n *\n * The grammar is owned here and nowhere else. The permission engine resolves\n * precedence between rules and never learns what one means.\n */\n\nimport type { Capability, ToolContract } from \"../tools/contract.ts\";\nimport { ALL_CAPABILITIES } from \"../tools/contract.ts\";\nimport type { McpToolDetails, McpToolParams, mcpToolSchema } from \"./schema.ts\";\nimport type { McpServerConfig } from \"./types.ts\";\n\n/** Rule covering the metadata actions, which read the local cache and reach no server. */\nexport const METADATA_RULE = \"Mcp(metadata)\";\n\nconst RULE_PATTERN = /^Mcp\\(([^():*\\s]+):([^():\\s]+)\\)$/;\n\nfunction isCall(params: McpToolParams): params is McpToolParams & { tool: string } {\n\treturn typeof params.tool === \"string\" && params.tool.length > 0;\n}\n\n/** `<server>:<tool>`, the form the model calls and the form a rule names. */\nfunction splitQualifiedName(qualified: string): { server: string; tool: string } | undefined {\n\tconst index = qualified.indexOf(\":\");\n\tif (index <= 0 || index === qualified.length - 1) return undefined;\n\treturn { server: qualified.slice(0, index), tool: qualified.slice(index + 1) };\n}\n\nfunction matches(ruleContent: string, params: McpToolParams): boolean {\n\tif (ruleContent === METADATA_RULE) return !isCall(params);\n\tif (!isCall(params)) return false;\n\n\tconst rule = RULE_PATTERN.exec(ruleContent);\n\tif (!rule) return false;\n\tconst [, ruleServer, ruleTool] = rule;\n\n\tconst target = splitQualifiedName(params.tool);\n\tif (!target) return false;\n\n\tif (ruleServer !== target.server) return false;\n\treturn ruleTool === \"*\" || ruleTool === target.tool;\n}\n\nfunction ruleForCall(params: McpToolParams): string | null {\n\tif (!isCall(params)) return METADATA_RULE;\n\tconst target = splitQualifiedName(params.tool);\n\treturn target ? `Mcp(${target.server}:${target.tool})` : null;\n}\n\nfunction describe(ruleContent: string): string {\n\tif (ruleContent === METADATA_RULE) return \"Searching and describing MCP tools (no server is contacted)\";\n\n\tconst rule = RULE_PATTERN.exec(ruleContent);\n\tif (!rule) return `Unrecognized MCP rule: ${ruleContent}`;\n\tconst [, server, tool] = rule;\n\treturn tool === \"*\"\n\t\t? `Calling any tool on the \"${server}\" MCP server`\n\t\t: `Calling \"${tool}\" on the \"${server}\" MCP server`;\n}\n\n/**\n * One proxy tool carries one capability set, and that set feeds the delegation\n * ceiling (ADR 0008) and mode resolution. The union across configured servers is the\n * only sound answer: narrower would understate what a call can do, and a fixed full\n * set would ignore what a user took the trouble to declare.\n */\nfunction unionCapabilities(servers: Iterable<McpServerConfig>): ReadonlySet<Capability> {\n\tconst union = new Set<Capability>();\n\tfor (const server of servers) {\n\t\tfor (const capability of server.capabilities) union.add(capability);\n\t\tif (union.size === ALL_CAPABILITIES.size) return ALL_CAPABILITIES;\n\t}\n\treturn union;\n}\n\nexport function createMcpToolContract(\n\tservers: ReadonlyMap<string, McpServerConfig>,\n): ToolContract<typeof mcpToolSchema, McpToolDetails> {\n\treturn {\n\t\tcapabilities: unionCapabilities(servers.values()),\n\t\tpermission: {\n\t\t\tdefaultBehavior: \"ask\",\n\t\t\tmatches,\n\t\t\tdescribe,\n\t\t\truleForCall,\n\t\t},\n\t\tcontext: { resultRecoverable: false, deferSchema: true },\n\t\tevidence: { emits: new Set(), capture: () => [] },\n\t};\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One tool for every MCP server, instead of one tool per server tool.
|
|
3
|
+
*
|
|
4
|
+
* Registering each server tool individually costs 150-300 prompt tokens apiece, and
|
|
5
|
+
* two ordinary servers would exceed the whole static-prefix budget before a
|
|
6
|
+
* conversation starts. The proxy costs one announced name and this description,
|
|
7
|
+
* because its schema defers.
|
|
8
|
+
*
|
|
9
|
+
* `search` and `describe` read the disk cache and reach no server. Only a `tool` call
|
|
10
|
+
* connects, which is what makes discovery free and is why the two have separate
|
|
11
|
+
* permission rules (ADR 0025).
|
|
12
|
+
*/
|
|
13
|
+
import type { ApexToolDefinition } from "../tools/contract.ts";
|
|
14
|
+
import type { McpMetadataCache } from "./metadata-cache.ts";
|
|
15
|
+
import { type McpToolDetails, mcpToolSchema } from "./schema.ts";
|
|
16
|
+
import type { McpServerManager } from "./server-manager.ts";
|
|
17
|
+
import type { McpServerConfig } from "./types.ts";
|
|
18
|
+
export declare const MCP_TOOL_NAME = "mcp";
|
|
19
|
+
export interface McpToolOptions {
|
|
20
|
+
servers: ReadonlyMap<string, McpServerConfig>;
|
|
21
|
+
cache: McpMetadataCache;
|
|
22
|
+
manager: McpServerManager;
|
|
23
|
+
}
|
|
24
|
+
export declare function createMcpToolDefinition(options: McpToolOptions): ApexToolDefinition<typeof mcpToolSchema, McpToolDetails>;
|
|
25
|
+
export declare function createMcpTool(options: McpToolOptions): import("apex-code-agent-core").AgentTool<import("typebox").TObject<{
|
|
26
|
+
search: import("typebox").TOptional<import("typebox").TString>;
|
|
27
|
+
server: import("typebox").TOptional<import("typebox").TString>;
|
|
28
|
+
describe: import("typebox").TOptional<import("typebox").TString>;
|
|
29
|
+
tool: import("typebox").TOptional<import("typebox").TString>;
|
|
30
|
+
args: import("typebox").TOptional<import("typebox").TUnknown>;
|
|
31
|
+
}>, McpToolDetails>;
|
|
32
|
+
//# sourceMappingURL=mcp-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-tool.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/mcp-tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,OAAO,EAAE,KAAK,cAAc,EAAsB,aAAa,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAc,eAAe,EAAE,MAAM,YAAY,CAAC;AAE9D,eAAO,MAAM,aAAa,QAAQ,CAAC;AAwCnC,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC9C,KAAK,EAAE,gBAAgB,CAAC;IACxB,OAAO,EAAE,gBAAgB,CAAC;CAC1B;AAED,wBAAgB,uBAAuB,CACtC,OAAO,EAAE,cAAc,GACrB,kBAAkB,CAAC,OAAO,aAAa,EAAE,cAAc,CAAC,CA0I1D;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc;;;;;;oBAEpD","sourcesContent":["/**\n * One tool for every MCP server, instead of one tool per server tool.\n *\n * Registering each server tool individually costs 150-300 prompt tokens apiece, and\n * two ordinary servers would exceed the whole static-prefix budget before a\n * conversation starts. The proxy costs one announced name and this description,\n * because its schema defers.\n *\n * `search` and `describe` read the disk cache and reach no server. Only a `tool` call\n * connects, which is what makes discovery free and is why the two have separate\n * permission rules (ADR 0025).\n */\n\nimport type { AgentToolResult } from \"apex-code-agent-core\";\nimport type { ApexToolDefinition } from \"../tools/contract.ts\";\nimport { wrapToolDefinition } from \"../tools/tool-definition-wrapper.ts\";\nimport { createMcpToolContract } from \"./contract.ts\";\nimport type { McpMetadataCache } from \"./metadata-cache.ts\";\nimport { McpOAuthRefreshError, McpOAuthRequiredError } from \"./oauth/mcp-token.ts\";\nimport { type McpToolDetails, type McpToolParams, mcpToolSchema } from \"./schema.ts\";\nimport type { McpServerManager } from \"./server-manager.ts\";\nimport type { CachedTool, McpServerConfig } from \"./types.ts\";\n\nexport const MCP_TOOL_NAME = \"mcp\";\n\nconst MAX_RESULTS = 25;\n\nconst DESCRIPTION =\n\t\"Use tools provided by configured MCP servers. \" +\n\t'Search with {\"search\":\"keyword\"}, list one server with {\"server\":\"name\"}, ' +\n\t'see a tool\\'s full schema with {\"describe\":\"server:tool\"}, ' +\n\t'and call one with {\"tool\":\"server:tool\",\"args\":{...}}. ' +\n\t\"Search and describe read a local cache and never start a server, so discovery is free. \" +\n\t\"Always describe a tool before calling it for the first time.\";\n\nfunction text(body: string): AgentToolResult<McpToolDetails>[\"content\"] {\n\treturn [{ type: \"text\", text: body }];\n}\n\nfunction qualified(tool: CachedTool): string {\n\treturn `${tool.server}:${tool.name}`;\n}\n\n/** Substring scoring, weighted to the name, which is what the model searched for. */\nfunction score(tool: CachedTool, needle: string): number {\n\tconst name = qualified(tool).toLowerCase();\n\tconst description = tool.description.toLowerCase();\n\tif (name === needle) return 100;\n\tif (name.includes(needle)) return 50;\n\tif (description.includes(needle)) return 10;\n\treturn 0;\n}\n\nfunction renderList(tools: readonly CachedTool[]): string {\n\treturn tools.map((tool) => `${qualified(tool)}: ${tool.description}`).join(\"\\n\");\n}\n\nfunction nearest(tools: readonly CachedTool[], target: string): CachedTool[] {\n\tconst needle = target.toLowerCase();\n\tconst stem = needle.slice(0, Math.max(3, Math.floor(needle.length * 0.6)));\n\treturn tools.filter((tool) => qualified(tool).toLowerCase().includes(stem)).slice(0, 5);\n}\n\nexport interface McpToolOptions {\n\tservers: ReadonlyMap<string, McpServerConfig>;\n\tcache: McpMetadataCache;\n\tmanager: McpServerManager;\n}\n\nexport function createMcpToolDefinition(\n\toptions: McpToolOptions,\n): ApexToolDefinition<typeof mcpToolSchema, McpToolDetails> {\n\tconst { servers, cache, manager } = options;\n\n\treturn {\n\t\tname: MCP_TOOL_NAME,\n\t\tlabel: \"mcp\",\n\t\tdescription: DESCRIPTION,\n\t\tpromptSnippet: \"Call tools on configured MCP servers\",\n\t\tparameters: mcpToolSchema,\n\t\tcontract: createMcpToolContract(servers),\n\t\tasync execute(_id, params: McpToolParams): Promise<AgentToolResult<McpToolDetails>> {\n\t\t\tif (params.tool) return callTool(params.tool, params.args);\n\t\t\tif (params.describe) return describeTool(params.describe);\n\t\t\tif (params.search !== undefined) return searchTools(params.search, params.server);\n\t\t\tif (params.server) return listServer(params.server);\n\n\t\t\treturn {\n\t\t\t\tcontent: text('Provide one of \"search\", \"server\", \"describe\", or \"tool\".'),\n\t\t\t\tdetails: { action: \"search\", server: undefined, tool: undefined },\n\t\t\t};\n\t\t},\n\t};\n\n\tfunction searchTools(needle: string, serverFilter?: string): AgentToolResult<McpToolDetails> {\n\t\tconst pool = serverFilter ? cache.all().filter((tool) => tool.server === serverFilter) : cache.all();\n\t\tconst lowered = needle.toLowerCase();\n\t\tconst ranked = pool\n\t\t\t.map((tool) => ({ tool, rank: score(tool, lowered) }))\n\t\t\t.filter((entry) => entry.rank > 0)\n\t\t\t.sort((a, b) => b.rank - a.rank)\n\t\t\t.slice(0, MAX_RESULTS)\n\t\t\t.map((entry) => entry.tool);\n\n\t\treturn {\n\t\t\tcontent: text(\n\t\t\t\tranked.length > 0\n\t\t\t\t\t? renderList(ranked)\n\t\t\t\t\t: `No cached MCP tool matches \"${needle}\". Configured servers: ${[...servers.keys()].join(\", \") || \"none\"}.`,\n\t\t\t),\n\t\t\tdetails: { action: \"search\", server: serverFilter, tool: undefined },\n\t\t};\n\t}\n\n\tfunction listServer(name: string): AgentToolResult<McpToolDetails> {\n\t\tif (!servers.has(name)) {\n\t\t\treturn {\n\t\t\t\tcontent: text(\n\t\t\t\t\t`MCP server \"${name}\" is not configured. Configured servers: ${[...servers.keys()].join(\", \") || \"none\"}.`,\n\t\t\t\t),\n\t\t\t\tdetails: { action: \"list\", server: name, tool: undefined },\n\t\t\t};\n\t\t}\n\n\t\tconst tools = cache.all().filter((tool) => tool.server === name);\n\t\treturn {\n\t\t\tcontent: text(\n\t\t\t\ttools.length > 0\n\t\t\t\t\t? renderList(tools)\n\t\t\t\t\t: `No cached tools for \"${name}\" yet. Tools are cached the first time the server is called, so call a known tool on it, or set \"lifecycle\": \"eager\" for this server in .mcp.json to have them cached at startup.`,\n\t\t\t),\n\t\t\tdetails: { action: \"list\", server: name, tool: undefined },\n\t\t};\n\t}\n\n\tfunction describeTool(target: string): AgentToolResult<McpToolDetails> {\n\t\tconst found = cache.all().find((tool) => qualified(tool) === target);\n\t\tif (!found) {\n\t\t\tconst suggestions = nearest(cache.all(), target);\n\t\t\treturn {\n\t\t\t\tcontent: text(\n\t\t\t\t\tsuggestions.length > 0\n\t\t\t\t\t\t? `No cached tool named \"${target}\". Did you mean:\\n${renderList(suggestions)}`\n\t\t\t\t\t\t: `No cached tool named \"${target}\". Search with {\"search\":\"keyword\"}.`,\n\t\t\t\t),\n\t\t\t\tdetails: { action: \"describe\", server: undefined, tool: target },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tcontent: text(\n\t\t\t\t`${qualified(found)}\\n${found.description}\\n\\nInput schema:\\n${JSON.stringify(found.inputSchema, null, 2)}`,\n\t\t\t),\n\t\t\tdetails: { action: \"describe\", server: found.server, tool: target },\n\t\t};\n\t}\n\n\tasync function callTool(target: string, args: unknown): Promise<AgentToolResult<McpToolDetails>> {\n\t\tconst separator = target.indexOf(\":\");\n\t\tconst details: McpToolDetails = { action: \"call\", server: undefined, tool: target };\n\t\tif (separator <= 0) {\n\t\t\treturn { content: text(`Tool must be qualified as \"server:tool\". Received \"${target}\".`), details };\n\t\t}\n\n\t\tconst serverName = target.slice(0, separator);\n\t\tconst toolName = target.slice(separator + 1);\n\t\tdetails.server = serverName;\n\n\t\tif (!servers.has(serverName)) {\n\t\t\treturn {\n\t\t\t\tcontent: text(\n\t\t\t\t\t`MCP server \"${serverName}\" is not configured. Configured servers: ${[...servers.keys()].join(\", \") || \"none\"}.`,\n\t\t\t\t),\n\t\t\t\tdetails,\n\t\t\t};\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await manager.withConnection(serverName, async (connection) => {\n\t\t\t\t// An empty list is a fact about the server, not a failed read. Keeping the\n\t\t\t\t// previous entry would leave search advertising tools that no longer exist.\n\t\t\t\tconst fresh = await connection.listTools();\n\t\t\t\tcache.set(servers.get(serverName) as McpServerConfig, fresh);\n\t\t\t\tcache.save();\n\t\t\t\treturn connection.callTool(toolName, args);\n\t\t\t});\n\n\t\t\t// `AgentToolResult` has no error flag; a server-reported failure is marked in\n\t\t\t// the text so the model can see it failed without the turn being aborted.\n\t\t\tconst content = result.isError\n\t\t\t\t? text(`Tool reported an error:\\n${result.content.map((part) => part.text).join(\"\\n\")}`)\n\t\t\t\t: result.content;\n\t\t\treturn { content, details };\n\t\t} catch (error) {\n\t\t\t// OAuth failures already carry the one action that helps (`apex-code mcp auth\n\t\t\t// <server>`); the generic \"unavailable\" wrapper would bury it.\n\t\t\tif (error instanceof McpOAuthRequiredError || error instanceof McpOAuthRefreshError) {\n\t\t\t\treturn { content: text(error.message), details };\n\t\t\t}\n\t\t\t// A failed server is reported, not retried: the manager holds it in `failed`\n\t\t\t// with a backoff window so one broken server cannot cost every call a spawn.\n\t\t\treturn {\n\t\t\t\tcontent: text(\n\t\t\t\t\t`MCP server \"${serverName}\" is unavailable: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t),\n\t\t\t\tdetails,\n\t\t\t};\n\t\t}\n\t}\n}\n\nexport function createMcpTool(options: McpToolOptions) {\n\treturn wrapToolDefinition(createMcpToolDefinition(options));\n}\n"]}
|