@chances-ai/engine 24.0.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/dist/agents/discover.d.ts +30 -0
- package/dist/agents/discover.d.ts.map +1 -0
- package/dist/agents/discover.js +183 -0
- package/dist/agents/discover.js.map +1 -0
- package/dist/agents/index.d.ts +20 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +52 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/parse.d.ts +61 -0
- package/dist/agents/parse.d.ts.map +1 -0
- package/dist/agents/parse.js +527 -0
- package/dist/agents/parse.js.map +1 -0
- package/dist/agents/types.d.ts +52 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +8 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/ai/adapters/ai-sdk-stream.d.ts +19 -0
- package/dist/ai/adapters/ai-sdk-stream.d.ts.map +1 -0
- package/dist/ai/adapters/ai-sdk-stream.js +125 -0
- package/dist/ai/adapters/ai-sdk-stream.js.map +1 -0
- package/dist/ai/adapters/ai-sdk.d.ts +56 -0
- package/dist/ai/adapters/ai-sdk.d.ts.map +1 -0
- package/dist/ai/adapters/ai-sdk.js +112 -0
- package/dist/ai/adapters/ai-sdk.js.map +1 -0
- package/dist/ai/adapters/mock.d.ts +13 -0
- package/dist/ai/adapters/mock.d.ts.map +1 -0
- package/dist/ai/adapters/mock.js +54 -0
- package/dist/ai/adapters/mock.js.map +1 -0
- package/dist/ai/adapters/openai-compatible.d.ts +23 -0
- package/dist/ai/adapters/openai-compatible.d.ts.map +1 -0
- package/dist/ai/adapters/openai-compatible.js +45 -0
- package/dist/ai/adapters/openai-compatible.js.map +1 -0
- package/dist/ai/cost.d.ts +3 -0
- package/dist/ai/cost.d.ts.map +1 -0
- package/dist/ai/cost.js +5 -0
- package/dist/ai/cost.js.map +1 -0
- package/dist/ai/index.d.ts +12 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +11 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/known-models.d.ts +20 -0
- package/dist/ai/known-models.d.ts.map +1 -0
- package/dist/ai/known-models.js +129 -0
- package/dist/ai/known-models.js.map +1 -0
- package/dist/ai/registry.d.ts +12 -0
- package/dist/ai/registry.d.ts.map +1 -0
- package/dist/ai/registry.js +24 -0
- package/dist/ai/registry.js.map +1 -0
- package/dist/ai/retry.d.ts +11 -0
- package/dist/ai/retry.d.ts.map +1 -0
- package/dist/ai/retry.js +14 -0
- package/dist/ai/retry.js.map +1 -0
- package/dist/ai/router.d.ts +25 -0
- package/dist/ai/router.d.ts.map +1 -0
- package/dist/ai/router.js +36 -0
- package/dist/ai/router.js.map +1 -0
- package/dist/ai/setup.d.ts +23 -0
- package/dist/ai/setup.d.ts.map +1 -0
- package/dist/ai/setup.js +47 -0
- package/dist/ai/setup.js.map +1 -0
- package/dist/ai/summarizer.d.ts +24 -0
- package/dist/ai/summarizer.d.ts.map +1 -0
- package/dist/ai/summarizer.js +56 -0
- package/dist/ai/summarizer.js.map +1 -0
- package/dist/ai/types.d.ts +83 -0
- package/dist/ai/types.d.ts.map +1 -0
- package/dist/ai/types.js +2 -0
- package/dist/ai/types.js.map +1 -0
- package/dist/core/compaction/circuit-breaker.d.ts +32 -0
- package/dist/core/compaction/circuit-breaker.d.ts.map +1 -0
- package/dist/core/compaction/circuit-breaker.js +42 -0
- package/dist/core/compaction/circuit-breaker.js.map +1 -0
- package/dist/core/compaction/compactor.d.ts +75 -0
- package/dist/core/compaction/compactor.d.ts.map +1 -0
- package/dist/core/compaction/compactor.js +261 -0
- package/dist/core/compaction/compactor.js.map +1 -0
- package/dist/core/compaction/estimate.d.ts +39 -0
- package/dist/core/compaction/estimate.d.ts.map +1 -0
- package/dist/core/compaction/estimate.js +74 -0
- package/dist/core/compaction/estimate.js.map +1 -0
- package/dist/core/compaction/index.d.ts +5 -0
- package/dist/core/compaction/index.d.ts.map +1 -0
- package/dist/core/compaction/index.js +5 -0
- package/dist/core/compaction/index.js.map +1 -0
- package/dist/core/compaction/prune.d.ts +43 -0
- package/dist/core/compaction/prune.d.ts.map +1 -0
- package/dist/core/compaction/prune.js +51 -0
- package/dist/core/compaction/prune.js.map +1 -0
- package/dist/core/engine.d.ts +268 -0
- package/dist/core/engine.d.ts.map +1 -0
- package/dist/core/engine.js +767 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/task-tool.d.ts +175 -0
- package/dist/core/task-tool.d.ts.map +1 -0
- package/dist/core/task-tool.js +901 -0
- package/dist/core/task-tool.js.map +1 -0
- package/dist/core/workspace-query.d.ts +83 -0
- package/dist/core/workspace-query.d.ts.map +1 -0
- package/dist/core/workspace-query.js +217 -0
- package/dist/core/workspace-query.js.map +1 -0
- package/dist/core/worktree/active-marker.d.ts +31 -0
- package/dist/core/worktree/active-marker.d.ts.map +1 -0
- package/dist/core/worktree/active-marker.js +109 -0
- package/dist/core/worktree/active-marker.js.map +1 -0
- package/dist/core/worktree/create.d.ts +40 -0
- package/dist/core/worktree/create.d.ts.map +1 -0
- package/dist/core/worktree/create.js +121 -0
- package/dist/core/worktree/create.js.map +1 -0
- package/dist/core/worktree/errors.d.ts +7 -0
- package/dist/core/worktree/errors.d.ts.map +1 -0
- package/dist/core/worktree/errors.js +11 -0
- package/dist/core/worktree/errors.js.map +1 -0
- package/dist/core/worktree/gc.d.ts +39 -0
- package/dist/core/worktree/gc.d.ts.map +1 -0
- package/dist/core/worktree/gc.js +146 -0
- package/dist/core/worktree/gc.js.map +1 -0
- package/dist/core/worktree/git.d.ts +53 -0
- package/dist/core/worktree/git.d.ts.map +1 -0
- package/dist/core/worktree/git.js +166 -0
- package/dist/core/worktree/git.js.map +1 -0
- package/dist/core/worktree/index.d.ts +8 -0
- package/dist/core/worktree/index.d.ts.map +1 -0
- package/dist/core/worktree/index.js +8 -0
- package/dist/core/worktree/index.js.map +1 -0
- package/dist/core/worktree/paths.d.ts +26 -0
- package/dist/core/worktree/paths.d.ts.map +1 -0
- package/dist/core/worktree/paths.js +57 -0
- package/dist/core/worktree/paths.js.map +1 -0
- package/dist/core/worktree/slug.d.ts +6 -0
- package/dist/core/worktree/slug.d.ts.map +1 -0
- package/dist/core/worktree/slug.js +21 -0
- package/dist/core/worktree/slug.js.map +1 -0
- package/dist/local-vault/file-store.d.ts +64 -0
- package/dist/local-vault/file-store.d.ts.map +1 -0
- package/dist/local-vault/file-store.js +225 -0
- package/dist/local-vault/file-store.js.map +1 -0
- package/dist/local-vault/index.d.ts +57 -0
- package/dist/local-vault/index.d.ts.map +1 -0
- package/dist/local-vault/index.js +68 -0
- package/dist/local-vault/index.js.map +1 -0
- package/dist/local-vault/keychain.d.ts +58 -0
- package/dist/local-vault/keychain.d.ts.map +1 -0
- package/dist/local-vault/keychain.js +200 -0
- package/dist/local-vault/keychain.js.map +1 -0
- package/dist/local-vault/mutex.d.ts +20 -0
- package/dist/local-vault/mutex.d.ts.map +1 -0
- package/dist/local-vault/mutex.js +44 -0
- package/dist/local-vault/mutex.js.map +1 -0
- package/dist/local-vault/passphrase.d.ts +30 -0
- package/dist/local-vault/passphrase.d.ts.map +1 -0
- package/dist/local-vault/passphrase.js +72 -0
- package/dist/local-vault/passphrase.js.map +1 -0
- package/dist/lsp/config.d.ts +34 -0
- package/dist/lsp/config.d.ts.map +1 -0
- package/dist/lsp/config.js +68 -0
- package/dist/lsp/config.js.map +1 -0
- package/dist/lsp/detect.d.ts +7 -0
- package/dist/lsp/detect.d.ts.map +1 -0
- package/dist/lsp/detect.js +78 -0
- package/dist/lsp/detect.js.map +1 -0
- package/dist/lsp/errors.d.ts +11 -0
- package/dist/lsp/errors.d.ts.map +1 -0
- package/dist/lsp/errors.js +11 -0
- package/dist/lsp/errors.js.map +1 -0
- package/dist/lsp/formatters.d.ts +147 -0
- package/dist/lsp/formatters.d.ts.map +1 -0
- package/dist/lsp/formatters.js +259 -0
- package/dist/lsp/formatters.js.map +1 -0
- package/dist/lsp/index.d.ts +31 -0
- package/dist/lsp/index.d.ts.map +1 -0
- package/dist/lsp/index.js +31 -0
- package/dist/lsp/index.js.map +1 -0
- package/dist/lsp/instance.d.ts +72 -0
- package/dist/lsp/instance.d.ts.map +1 -0
- package/dist/lsp/instance.js +489 -0
- package/dist/lsp/instance.js.map +1 -0
- package/dist/lsp/lazy-load.d.ts +27 -0
- package/dist/lsp/lazy-load.d.ts.map +1 -0
- package/dist/lsp/lazy-load.js +57 -0
- package/dist/lsp/lazy-load.js.map +1 -0
- package/dist/lsp/manager.d.ts +59 -0
- package/dist/lsp/manager.d.ts.map +1 -0
- package/dist/lsp/manager.js +242 -0
- package/dist/lsp/manager.js.map +1 -0
- package/dist/lsp/ops.d.ts +13 -0
- package/dist/lsp/ops.d.ts.map +1 -0
- package/dist/lsp/ops.js +225 -0
- package/dist/lsp/ops.js.map +1 -0
- package/dist/lsp/rpc.d.ts +47 -0
- package/dist/lsp/rpc.d.ts.map +1 -0
- package/dist/lsp/rpc.js +134 -0
- package/dist/lsp/rpc.js.map +1 -0
- package/dist/lsp/safe-uri.d.ts +18 -0
- package/dist/lsp/safe-uri.d.ts.map +1 -0
- package/dist/lsp/safe-uri.js +96 -0
- package/dist/lsp/safe-uri.js.map +1 -0
- package/dist/lsp/types.d.ts +70 -0
- package/dist/lsp/types.d.ts.map +1 -0
- package/dist/lsp/types.js +16 -0
- package/dist/lsp/types.js.map +1 -0
- package/dist/mcp/bridge.d.ts +57 -0
- package/dist/mcp/bridge.d.ts.map +1 -0
- package/dist/mcp/bridge.js +98 -0
- package/dist/mcp/bridge.js.map +1 -0
- package/dist/mcp/category.d.ts +22 -0
- package/dist/mcp/category.d.ts.map +1 -0
- package/dist/mcp/category.js +11 -0
- package/dist/mcp/category.js.map +1 -0
- package/dist/mcp/client.d.ts +228 -0
- package/dist/mcp/client.d.ts.map +1 -0
- package/dist/mcp/client.js +352 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/mcp/content.d.ts +86 -0
- package/dist/mcp/content.d.ts.map +1 -0
- package/dist/mcp/content.js +147 -0
- package/dist/mcp/content.js.map +1 -0
- package/dist/mcp/env.d.ts +19 -0
- package/dist/mcp/env.d.ts.map +1 -0
- package/dist/mcp/env.js +50 -0
- package/dist/mcp/env.js.map +1 -0
- package/dist/mcp/host.d.ts +199 -0
- package/dist/mcp/host.d.ts.map +1 -0
- package/dist/mcp/host.js +530 -0
- package/dist/mcp/host.js.map +1 -0
- package/dist/mcp/index.d.ts +18 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +17 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/load-mcp-host.d.ts +32 -0
- package/dist/mcp/load-mcp-host.d.ts.map +1 -0
- package/dist/mcp/load-mcp-host.js +49 -0
- package/dist/mcp/load-mcp-host.js.map +1 -0
- package/dist/mcp/oauth/callback-server.d.ts +73 -0
- package/dist/mcp/oauth/callback-server.d.ts.map +1 -0
- package/dist/mcp/oauth/callback-server.js +280 -0
- package/dist/mcp/oauth/callback-server.js.map +1 -0
- package/dist/mcp/oauth/config-hash.d.ts +24 -0
- package/dist/mcp/oauth/config-hash.d.ts.map +1 -0
- package/dist/mcp/oauth/config-hash.js +55 -0
- package/dist/mcp/oauth/config-hash.js.map +1 -0
- package/dist/mcp/oauth/error-normalize.d.ts +39 -0
- package/dist/mcp/oauth/error-normalize.d.ts.map +1 -0
- package/dist/mcp/oauth/error-normalize.js +91 -0
- package/dist/mcp/oauth/error-normalize.js.map +1 -0
- package/dist/mcp/oauth/provider.d.ts +190 -0
- package/dist/mcp/oauth/provider.d.ts.map +1 -0
- package/dist/mcp/oauth/provider.js +305 -0
- package/dist/mcp/oauth/provider.js.map +1 -0
- package/dist/mcp/oauth/refresh-coalescer.d.ts +46 -0
- package/dist/mcp/oauth/refresh-coalescer.d.ts.map +1 -0
- package/dist/mcp/oauth/refresh-coalescer.js +77 -0
- package/dist/mcp/oauth/refresh-coalescer.js.map +1 -0
- package/dist/mcp/oauth/sdk-shapes.d.ts +77 -0
- package/dist/mcp/oauth/sdk-shapes.d.ts.map +1 -0
- package/dist/mcp/oauth/sdk-shapes.js +21 -0
- package/dist/mcp/oauth/sdk-shapes.js.map +1 -0
- package/dist/mcp/parse.d.ts +28 -0
- package/dist/mcp/parse.d.ts.map +1 -0
- package/dist/mcp/parse.js +209 -0
- package/dist/mcp/parse.js.map +1 -0
- package/dist/mcp/prompts.d.ts +31 -0
- package/dist/mcp/prompts.d.ts.map +1 -0
- package/dist/mcp/prompts.js +71 -0
- package/dist/mcp/prompts.js.map +1 -0
- package/dist/mcp/redact.d.ts +62 -0
- package/dist/mcp/redact.d.ts.map +1 -0
- package/dist/mcp/redact.js +87 -0
- package/dist/mcp/redact.js.map +1 -0
- package/dist/mcp/resources.d.ts +70 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +170 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/types.d.ts +123 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +2 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/memory/frontmatter.d.ts +18 -0
- package/dist/memory/frontmatter.d.ts.map +1 -0
- package/dist/memory/frontmatter.js +81 -0
- package/dist/memory/frontmatter.js.map +1 -0
- package/dist/memory/index.d.ts +5 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +5 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/store.d.ts +44 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +237 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/memory/tools.d.ts +11 -0
- package/dist/memory/tools.d.ts.map +1 -0
- package/dist/memory/tools.js +159 -0
- package/dist/memory/tools.js.map +1 -0
- package/dist/memory/types.d.ts +32 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +20 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/plugin-api/index.d.ts +167 -0
- package/dist/plugin-api/index.d.ts.map +1 -0
- package/dist/plugin-api/index.js +151 -0
- package/dist/plugin-api/index.js.map +1 -0
- package/dist/plugin-logger/index.d.ts +21 -0
- package/dist/plugin-logger/index.d.ts.map +1 -0
- package/dist/plugin-logger/index.js +59 -0
- package/dist/plugin-logger/index.js.map +1 -0
- package/dist/session/index.d.ts +125 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +202 -0
- package/dist/session/index.js.map +1 -0
- package/dist/tools/approval.d.ts +33 -0
- package/dist/tools/approval.d.ts.map +1 -0
- package/dist/tools/approval.js +53 -0
- package/dist/tools/approval.js.map +1 -0
- package/dist/tools/builtins/_shared.d.ts +94 -0
- package/dist/tools/builtins/_shared.d.ts.map +1 -0
- package/dist/tools/builtins/_shared.js +246 -0
- package/dist/tools/builtins/_shared.js.map +1 -0
- package/dist/tools/builtins/ask-user-question.d.ts +27 -0
- package/dist/tools/builtins/ask-user-question.d.ts.map +1 -0
- package/dist/tools/builtins/ask-user-question.js +191 -0
- package/dist/tools/builtins/ask-user-question.js.map +1 -0
- package/dist/tools/builtins/bash.d.ts +3 -0
- package/dist/tools/builtins/bash.d.ts.map +1 -0
- package/dist/tools/builtins/bash.js +158 -0
- package/dist/tools/builtins/bash.js.map +1 -0
- package/dist/tools/builtins/diff.d.ts +3 -0
- package/dist/tools/builtins/diff.d.ts.map +1 -0
- package/dist/tools/builtins/diff.js +83 -0
- package/dist/tools/builtins/diff.js.map +1 -0
- package/dist/tools/builtins/edit.d.ts +3 -0
- package/dist/tools/builtins/edit.d.ts.map +1 -0
- package/dist/tools/builtins/edit.js +40 -0
- package/dist/tools/builtins/edit.js.map +1 -0
- package/dist/tools/builtins/glob.d.ts +3 -0
- package/dist/tools/builtins/glob.d.ts.map +1 -0
- package/dist/tools/builtins/glob.js +37 -0
- package/dist/tools/builtins/glob.js.map +1 -0
- package/dist/tools/builtins/grep.d.ts +3 -0
- package/dist/tools/builtins/grep.d.ts.map +1 -0
- package/dist/tools/builtins/grep.js +81 -0
- package/dist/tools/builtins/grep.js.map +1 -0
- package/dist/tools/builtins/lsp.d.ts +3 -0
- package/dist/tools/builtins/lsp.d.ts.map +1 -0
- package/dist/tools/builtins/lsp.js +102 -0
- package/dist/tools/builtins/lsp.js.map +1 -0
- package/dist/tools/builtins/pty.d.ts +64 -0
- package/dist/tools/builtins/pty.d.ts.map +1 -0
- package/dist/tools/builtins/pty.js +536 -0
- package/dist/tools/builtins/pty.js.map +1 -0
- package/dist/tools/builtins/read.d.ts +3 -0
- package/dist/tools/builtins/read.d.ts.map +1 -0
- package/dist/tools/builtins/read.js +18 -0
- package/dist/tools/builtins/read.js.map +1 -0
- package/dist/tools/builtins/web-fetch.d.ts +4 -0
- package/dist/tools/builtins/web-fetch.d.ts.map +1 -0
- package/dist/tools/builtins/web-fetch.js +353 -0
- package/dist/tools/builtins/web-fetch.js.map +1 -0
- package/dist/tools/builtins/write.d.ts +3 -0
- package/dist/tools/builtins/write.d.ts.map +1 -0
- package/dist/tools/builtins/write.js +48 -0
- package/dist/tools/builtins/write.js.map +1 -0
- package/dist/tools/builtins.d.ts +9 -0
- package/dist/tools/builtins.d.ts.map +1 -0
- package/dist/tools/builtins.js +29 -0
- package/dist/tools/builtins.js.map +1 -0
- package/dist/tools/diff.d.ts +18 -0
- package/dist/tools/diff.d.ts.map +1 -0
- package/dist/tools/diff.js +57 -0
- package/dist/tools/diff.js.map +1 -0
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +9 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/permission.d.ts +120 -0
- package/dist/tools/permission.d.ts.map +1 -0
- package/dist/tools/permission.js +208 -0
- package/dist/tools/permission.js.map +1 -0
- package/dist/tools/registry.d.ts +12 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +19 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/types.d.ts +244 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +15 -0
- package/dist/tools/types.js.map +1 -0
- package/package.json +109 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (3.7) `loadMcpHost({withVault})` — boot-time factory for `McpHost` that
|
|
3
|
+
* lazily resolves `@chances-ai/local-vault` ONLY when the caller asks for
|
|
4
|
+
* a vault. Keeps the cold-start budget intact when no OAuth-configured
|
|
5
|
+
* MCP server is present (`apps/cli/src/boot/load-runtime.ts` passes
|
|
6
|
+
* `withVault: needsOAuth`).
|
|
7
|
+
*
|
|
8
|
+
* **Boundary decision (codex Round-1 NICE).** `apps/cli` MUST NOT import
|
|
9
|
+
* `@chances-ai/local-vault` directly — the only `app → local-vault` edge
|
|
10
|
+
* would force dependency-cruiser to permit a new boundary rule. Routing
|
|
11
|
+
* vault creation through this factory keeps the graph clean: only
|
|
12
|
+
* `@chances-ai/mcp` reaches into the vault package.
|
|
13
|
+
*
|
|
14
|
+
* **`bun build --compile` story.** The vault specifier is COMPUTED
|
|
15
|
+
* (`["@chances-ai", "local-vault"].join("/")`) so Bun's bundler doesn't
|
|
16
|
+
* statically resolve it. A binary built without `@chances-ai/local-vault`
|
|
17
|
+
* in node_modules still compiles cleanly — at runtime, the dynamic
|
|
18
|
+
* import fails, we log a clear diagnostic, and OAuth-configured MCP
|
|
19
|
+
* servers individually fail with the same diagnostic (the non-OAuth
|
|
20
|
+
* servers keep working). § 13.4 / § 16 of `docs/3.7-design.md` cover
|
|
21
|
+
* the five-permutation smoke gate that pins this property.
|
|
22
|
+
*/
|
|
23
|
+
import { McpHost } from "./host.js";
|
|
24
|
+
export async function loadMcpHost(opts) {
|
|
25
|
+
let vault;
|
|
26
|
+
if (opts.withVault) {
|
|
27
|
+
const loader = opts.vaultLoader ?? defaultVaultLoader;
|
|
28
|
+
try {
|
|
29
|
+
vault = await loader();
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
// Vault load failure isn't fatal — non-OAuth servers keep working,
|
|
33
|
+
// and OAuth servers will fail individually with a clear message
|
|
34
|
+
// inside `startServer`. We surface the load error here so admins
|
|
35
|
+
// see why the vault is missing.
|
|
36
|
+
opts.logger.warn(`mcp: local-vault init failed (${e.message}); OAuth MCP servers will be unavailable until resolved`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return McpHost.start({ ...opts, vault });
|
|
40
|
+
}
|
|
41
|
+
async function defaultVaultLoader() {
|
|
42
|
+
// (7.1 Step 5) local-vault folded into @chances-ai/engine — always present, so
|
|
43
|
+
// a plain relative dynamic import. Still lazy: it defers loading the optional
|
|
44
|
+
// @napi-rs/keyring peer (which local-vault itself resolves via a computed
|
|
45
|
+
// specifier in keychain.ts), so `bun build --compile` stays clean without keyring.
|
|
46
|
+
const mod = (await import("../local-vault/index.js"));
|
|
47
|
+
return mod.createLocalVault();
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=load-mcp-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-mcp-host.js","sourceRoot":"","sources":["../../src/mcp/load-mcp-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,OAAO,EAA4C,MAAM,WAAW,CAAC;AAU9E,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAwB;IACxD,IAAI,KAAiC,CAAC;IACtC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,IAAI,kBAAkB,CAAC;QACtD,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,MAAM,EAAE,CAAC;QACzB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,mEAAmE;YACnE,gEAAgE;YAChE,iEAAiE;YACjE,gCAAgC;YAChC,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,iCAAkC,CAAW,CAAC,OAAO,yDAAyD,CAC/G,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED,KAAK,UAAU,kBAAkB;IAC/B,+EAA+E;IAC/E,8EAA8E;IAC9E,0EAA0E;IAC1E,mFAAmF;IACnF,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,yBAAyB,CAAC,CAEnD,CAAC;IACF,OAAO,GAAG,CAAC,gBAAgB,EAAE,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (3.7) Loopback HTTP listener for the OAuth authorization-code callback.
|
|
3
|
+
*
|
|
4
|
+
* Lifecycle:
|
|
5
|
+
* 1. `bind({preferredPort, allowFallback})` → reserves a port,
|
|
6
|
+
* returns the loopback URL the caller registers as the redirect_uri.
|
|
7
|
+
* 2. `waitForCode({expectedState, timeoutMs, signal})` → resolves with
|
|
8
|
+
* the auth code when a callback arrives whose `state` matches.
|
|
9
|
+
* 3. `close()` → shuts down the listener. Idempotent.
|
|
10
|
+
*
|
|
11
|
+
* **(codex Round-1 MUST-FIX #2)** OAuth `state` and PKCE `code_verifier`
|
|
12
|
+
* are independent: `state` travels in the browser redirect and is the
|
|
13
|
+
* CSRF binding; `code_verifier` stays in the chances process and goes
|
|
14
|
+
* to the token endpoint. We accept `expectedState` per-flow; the SDK
|
|
15
|
+
* generates and stores the verifier separately.
|
|
16
|
+
*
|
|
17
|
+
* **(codex Round-1 MUST-FIX #3)** Manual mode reuses the same loopback
|
|
18
|
+
* URL as a registered redirect_uri but never binds — see `register-only`
|
|
19
|
+
* mode in `bind()`.
|
|
20
|
+
*
|
|
21
|
+
* **(codex Round-1 SHOULD-FIX Q8)** Response carries strict CSP +
|
|
22
|
+
* `X-Content-Type-Options: nosniff` so a future contributor who adds
|
|
23
|
+
* dynamic content can't introduce an XSS sink.
|
|
24
|
+
*
|
|
25
|
+
* **(codex Round-1 SHOULD-FIX)** Default port is OS-assigned via
|
|
26
|
+
* `bind(0)` — accepts any unprivileged port. Fixed 3118 fallback ONLY
|
|
27
|
+
* if `bind(0)` itself fails (rare). RFC 8252 §7.3.
|
|
28
|
+
*/
|
|
29
|
+
export interface BindOptions {
|
|
30
|
+
/** Explicit port from config (`auth.callbackPort` or env override).
|
|
31
|
+
* When set, port-fallback is DISABLED (exact-URI-match per oh-my-pi
|
|
32
|
+
* commit c246d88db). */
|
|
33
|
+
preferredPort?: number;
|
|
34
|
+
/** When false, this is "register-only" mode — manual flow. We compute
|
|
35
|
+
* the URL but do NOT bind a listener. */
|
|
36
|
+
bindListener: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface WaitForCodeOptions {
|
|
39
|
+
/** The state value we expect to see echoed back. */
|
|
40
|
+
expectedState: string;
|
|
41
|
+
/** Timeout in ms. Default 300_000 (5 min). */
|
|
42
|
+
timeoutMs?: number;
|
|
43
|
+
/** Optional caller-provided abort signal (e.g. user Ctrl-C). */
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
}
|
|
46
|
+
export interface CallbackResult {
|
|
47
|
+
/** The OAuth authorization code from the callback URL. */
|
|
48
|
+
code: string;
|
|
49
|
+
}
|
|
50
|
+
export declare class LoopbackCallbackServer {
|
|
51
|
+
private server;
|
|
52
|
+
private resolvedPort;
|
|
53
|
+
private pending;
|
|
54
|
+
private closed;
|
|
55
|
+
/** Resolved redirect URL. Throws when `bind()` hasn't run. */
|
|
56
|
+
get redirectUrl(): URL;
|
|
57
|
+
bind(opts: BindOptions): Promise<URL>;
|
|
58
|
+
/** Bind a listener on the given port. Resolves with the actual port
|
|
59
|
+
* (may differ from the request when port=0). */
|
|
60
|
+
private bindServerOn;
|
|
61
|
+
waitForCode(opts: WaitForCodeOptions): Promise<CallbackResult>;
|
|
62
|
+
/** Parses a manually-pasted callback URL (manual mode, § 6.5).
|
|
63
|
+
* Validates state internally. */
|
|
64
|
+
parseManualCallback(pastedUrl: string, expectedState: string): CallbackResult;
|
|
65
|
+
close(): Promise<void>;
|
|
66
|
+
/** Test seam — the resolved port after bind(). Undefined before bind. */
|
|
67
|
+
get port(): number | undefined;
|
|
68
|
+
private handleRequest;
|
|
69
|
+
private sendSuccess;
|
|
70
|
+
private sendError;
|
|
71
|
+
private writeWithCsp;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=callback-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback-server.d.ts","sourceRoot":"","sources":["../../../src/mcp/oauth/callback-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAOH,MAAM,WAAW,WAAW;IAC1B;;6BAEyB;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;8CAC0C;IAC1C,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,oDAAoD;IACpD,aAAa,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,sBAAsB;IACjC,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,OAAO,CAED;IACd,OAAO,CAAC,MAAM,CAAS;IAEvB,8DAA8D;IAC9D,IAAI,WAAW,IAAI,GAAG,CAKrB;IAEK,IAAI,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;IAyC3C;qDACiD;IACjD,OAAO,CAAC,YAAY;IAiBd,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC;IAsDpE;sCACkC;IAClC,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,cAAc;IAyBvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB5B,yEAAyE;IACzE,IAAI,IAAI,IAAI,MAAM,GAAG,SAAS,CAE7B;IAED,OAAO,CAAC,aAAa;IAyCrB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,YAAY;CASrB"}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (3.7) Loopback HTTP listener for the OAuth authorization-code callback.
|
|
3
|
+
*
|
|
4
|
+
* Lifecycle:
|
|
5
|
+
* 1. `bind({preferredPort, allowFallback})` → reserves a port,
|
|
6
|
+
* returns the loopback URL the caller registers as the redirect_uri.
|
|
7
|
+
* 2. `waitForCode({expectedState, timeoutMs, signal})` → resolves with
|
|
8
|
+
* the auth code when a callback arrives whose `state` matches.
|
|
9
|
+
* 3. `close()` → shuts down the listener. Idempotent.
|
|
10
|
+
*
|
|
11
|
+
* **(codex Round-1 MUST-FIX #2)** OAuth `state` and PKCE `code_verifier`
|
|
12
|
+
* are independent: `state` travels in the browser redirect and is the
|
|
13
|
+
* CSRF binding; `code_verifier` stays in the chances process and goes
|
|
14
|
+
* to the token endpoint. We accept `expectedState` per-flow; the SDK
|
|
15
|
+
* generates and stores the verifier separately.
|
|
16
|
+
*
|
|
17
|
+
* **(codex Round-1 MUST-FIX #3)** Manual mode reuses the same loopback
|
|
18
|
+
* URL as a registered redirect_uri but never binds — see `register-only`
|
|
19
|
+
* mode in `bind()`.
|
|
20
|
+
*
|
|
21
|
+
* **(codex Round-1 SHOULD-FIX Q8)** Response carries strict CSP +
|
|
22
|
+
* `X-Content-Type-Options: nosniff` so a future contributor who adds
|
|
23
|
+
* dynamic content can't introduce an XSS sink.
|
|
24
|
+
*
|
|
25
|
+
* **(codex Round-1 SHOULD-FIX)** Default port is OS-assigned via
|
|
26
|
+
* `bind(0)` — accepts any unprivileged port. Fixed 3118 fallback ONLY
|
|
27
|
+
* if `bind(0)` itself fails (rare). RFC 8252 §7.3.
|
|
28
|
+
*/
|
|
29
|
+
import { createServer } from "node:http";
|
|
30
|
+
const FALLBACK_PORT = 3118;
|
|
31
|
+
const LOOPBACK_HOST = "127.0.0.1";
|
|
32
|
+
export class LoopbackCallbackServer {
|
|
33
|
+
server;
|
|
34
|
+
resolvedPort;
|
|
35
|
+
pending;
|
|
36
|
+
closed = false;
|
|
37
|
+
/** Resolved redirect URL. Throws when `bind()` hasn't run. */
|
|
38
|
+
get redirectUrl() {
|
|
39
|
+
if (this.resolvedPort === undefined) {
|
|
40
|
+
throw new Error("LoopbackCallbackServer: bind() not called");
|
|
41
|
+
}
|
|
42
|
+
return new URL(`http://${LOOPBACK_HOST}:${this.resolvedPort}/callback`);
|
|
43
|
+
}
|
|
44
|
+
async bind(opts) {
|
|
45
|
+
if (this.resolvedPort !== undefined) {
|
|
46
|
+
throw new Error("LoopbackCallbackServer: already bound");
|
|
47
|
+
}
|
|
48
|
+
if (opts.preferredPort !== undefined) {
|
|
49
|
+
// Explicit port — bind exactly or fail. No fallback.
|
|
50
|
+
this.resolvedPort = opts.preferredPort;
|
|
51
|
+
if (opts.bindListener) {
|
|
52
|
+
await this.bindServerOn(opts.preferredPort, /*fallbackOnFail=*/ false);
|
|
53
|
+
}
|
|
54
|
+
return this.redirectUrl;
|
|
55
|
+
}
|
|
56
|
+
// Default: bind(0) — let the OS assign an unprivileged port.
|
|
57
|
+
if (opts.bindListener) {
|
|
58
|
+
try {
|
|
59
|
+
const port = await this.bindServerOn(0, /*fallbackOnFail=*/ false);
|
|
60
|
+
this.resolvedPort = port;
|
|
61
|
+
}
|
|
62
|
+
catch (e0) {
|
|
63
|
+
// Rare: OS refused to assign. Try fixed 3118.
|
|
64
|
+
try {
|
|
65
|
+
await this.bindServerOn(FALLBACK_PORT, /*fallbackOnFail=*/ false);
|
|
66
|
+
this.resolvedPort = FALLBACK_PORT;
|
|
67
|
+
}
|
|
68
|
+
catch (e1) {
|
|
69
|
+
throw new Error(`LoopbackCallbackServer: could not bind any loopback port. ` +
|
|
70
|
+
`bind(0) failed: ${e0.message}. ` +
|
|
71
|
+
`Fallback ${FALLBACK_PORT} failed: ${e1.message}.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Register-only (manual mode): just pick a port without binding.
|
|
77
|
+
// Use fallback so the user has a stable URL to register with the
|
|
78
|
+
// IdP; not binding means another flow can reuse it.
|
|
79
|
+
this.resolvedPort = FALLBACK_PORT;
|
|
80
|
+
}
|
|
81
|
+
return this.redirectUrl;
|
|
82
|
+
}
|
|
83
|
+
/** Bind a listener on the given port. Resolves with the actual port
|
|
84
|
+
* (may differ from the request when port=0). */
|
|
85
|
+
bindServerOn(port, _fallbackOnFail) {
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
const server = createServer((req, res) => this.handleRequest(req, res));
|
|
88
|
+
server.on("error", (err) => reject(err));
|
|
89
|
+
server.listen(port, LOOPBACK_HOST, () => {
|
|
90
|
+
const addr = server.address();
|
|
91
|
+
if (addr === null || typeof addr === "string") {
|
|
92
|
+
server.close();
|
|
93
|
+
reject(new Error(`unexpected listen address shape: ${addr}`));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.server = server;
|
|
97
|
+
resolve(addr.port);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async waitForCode(opts) {
|
|
102
|
+
if (this.server === undefined) {
|
|
103
|
+
throw new Error("LoopbackCallbackServer: waitForCode requires a bound listener (manual mode uses parseManualCallback)");
|
|
104
|
+
}
|
|
105
|
+
if (this.pending) {
|
|
106
|
+
throw new Error("LoopbackCallbackServer: a previous waitForCode is still in flight");
|
|
107
|
+
}
|
|
108
|
+
const timeoutMs = opts.timeoutMs ?? 300_000;
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
this.pending = { resolve, reject, expectedState: opts.expectedState };
|
|
111
|
+
const timer = setTimeout(() => {
|
|
112
|
+
if (this.pending) {
|
|
113
|
+
const p = this.pending;
|
|
114
|
+
this.pending = undefined;
|
|
115
|
+
// (codex Round-2 SHOULD-FIX #4) Free the port on timeout — the
|
|
116
|
+
// design (§ 6.4) requires close-on-timeout, and a held-open
|
|
117
|
+
// listener after a 5-minute timeout would block the next
|
|
118
|
+
// /mcp login from binding the same port.
|
|
119
|
+
void this.close().catch(() => undefined);
|
|
120
|
+
p.reject(new Error("OAuth callback timed out — close the browser window and retry."));
|
|
121
|
+
}
|
|
122
|
+
}, timeoutMs);
|
|
123
|
+
// Honor abort signal — caller's Ctrl-C / `/mcp logout` etc.
|
|
124
|
+
const onAbort = () => {
|
|
125
|
+
if (this.pending) {
|
|
126
|
+
const p = this.pending;
|
|
127
|
+
this.pending = undefined;
|
|
128
|
+
clearTimeout(timer);
|
|
129
|
+
void this.close().catch(() => undefined);
|
|
130
|
+
p.reject(new Error("OAuth callback aborted"));
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
if (opts.signal) {
|
|
134
|
+
if (opts.signal.aborted) {
|
|
135
|
+
onAbort();
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Wrap resolve/reject so they also clear the timer.
|
|
142
|
+
const origResolve = resolve;
|
|
143
|
+
const origReject = reject;
|
|
144
|
+
this.pending.resolve = (r) => {
|
|
145
|
+
clearTimeout(timer);
|
|
146
|
+
origResolve(r);
|
|
147
|
+
};
|
|
148
|
+
this.pending.reject = (e) => {
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
origReject(e);
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/** Parses a manually-pasted callback URL (manual mode, § 6.5).
|
|
155
|
+
* Validates state internally. */
|
|
156
|
+
parseManualCallback(pastedUrl, expectedState) {
|
|
157
|
+
let url;
|
|
158
|
+
try {
|
|
159
|
+
url = new URL(pastedUrl);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
throw new Error("OAuth manual mode: pasted value is not a valid URL");
|
|
163
|
+
}
|
|
164
|
+
if (url.protocol !== "http:" || (url.hostname !== LOOPBACK_HOST && url.hostname.toLowerCase() !== "localhost" && url.hostname !== "[::1]")) {
|
|
165
|
+
throw new Error("OAuth manual mode: pasted URL must be the loopback redirect (http://127.0.0.1:...)");
|
|
166
|
+
}
|
|
167
|
+
const error = url.searchParams.get("error");
|
|
168
|
+
if (error !== null) {
|
|
169
|
+
const desc = url.searchParams.get("error_description") ?? "";
|
|
170
|
+
throw new Error(`OAuth authorization denied: ${error}${desc ? ` — ${desc}` : ""}`);
|
|
171
|
+
}
|
|
172
|
+
const code = url.searchParams.get("code");
|
|
173
|
+
const state = url.searchParams.get("state");
|
|
174
|
+
if (code === null)
|
|
175
|
+
throw new Error("OAuth manual mode: pasted URL is missing the 'code' query parameter");
|
|
176
|
+
if (state === null)
|
|
177
|
+
throw new Error("OAuth manual mode: pasted URL is missing the 'state' query parameter");
|
|
178
|
+
if (!constantTimeEqual(state, expectedState)) {
|
|
179
|
+
throw new Error("OAuth manual mode: state mismatch (CSRF guard) — abandoned flow or copy/paste error");
|
|
180
|
+
}
|
|
181
|
+
return { code };
|
|
182
|
+
}
|
|
183
|
+
async close() {
|
|
184
|
+
if (this.closed)
|
|
185
|
+
return;
|
|
186
|
+
this.closed = true;
|
|
187
|
+
if (this.pending) {
|
|
188
|
+
const p = this.pending;
|
|
189
|
+
this.pending = undefined;
|
|
190
|
+
p.reject(new Error("OAuth callback server closed"));
|
|
191
|
+
}
|
|
192
|
+
if (this.server) {
|
|
193
|
+
await new Promise((resolve) => {
|
|
194
|
+
this.server.close(() => resolve());
|
|
195
|
+
});
|
|
196
|
+
this.server = undefined;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** Test seam — the resolved port after bind(). Undefined before bind. */
|
|
200
|
+
get port() {
|
|
201
|
+
return this.resolvedPort;
|
|
202
|
+
}
|
|
203
|
+
handleRequest(req, res) {
|
|
204
|
+
if (!req.url) {
|
|
205
|
+
this.sendError(res, 400, "Bad Request");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const url = new URL(req.url, `http://${LOOPBACK_HOST}:${this.resolvedPort}`);
|
|
209
|
+
if (url.pathname !== "/callback") {
|
|
210
|
+
this.sendError(res, 404, "Not Found");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const error = url.searchParams.get("error");
|
|
214
|
+
if (error !== null) {
|
|
215
|
+
const desc = url.searchParams.get("error_description") ?? "";
|
|
216
|
+
this.sendError(res, 200, "Authorization denied", `Authorization denied: ${escapeHtml(error)}${desc ? ` — ${escapeHtml(desc)}` : ""}`);
|
|
217
|
+
if (this.pending) {
|
|
218
|
+
const p = this.pending;
|
|
219
|
+
this.pending = undefined;
|
|
220
|
+
p.reject(new Error(`OAuth authorization denied: ${error}${desc ? ` — ${desc}` : ""}`));
|
|
221
|
+
}
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const code = url.searchParams.get("code");
|
|
225
|
+
const state = url.searchParams.get("state");
|
|
226
|
+
if (code === null || state === null) {
|
|
227
|
+
this.sendError(res, 400, "Bad Request", "Callback is missing required parameters.");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
// codex Round-1 MUST-FIX #2 — state vs verifier. Constant-time
|
|
231
|
+
// compare to defend against timing oracles.
|
|
232
|
+
if (!this.pending || !constantTimeEqual(state, this.pending.expectedState)) {
|
|
233
|
+
this.sendError(res, 400, "Bad Request", "Callback state mismatch.");
|
|
234
|
+
// Don't disclose what state was expected.
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// Success path.
|
|
238
|
+
this.sendSuccess(res);
|
|
239
|
+
const p = this.pending;
|
|
240
|
+
this.pending = undefined;
|
|
241
|
+
p.resolve({ code });
|
|
242
|
+
}
|
|
243
|
+
sendSuccess(res) {
|
|
244
|
+
const body = `<!doctype html><html><head><meta charset="utf-8"><title>chances · OAuth</title></head><body style="font-family:system-ui,sans-serif;padding:2rem"><h1>Authorization complete</h1><p>You can close this tab.</p></body></html>`;
|
|
245
|
+
this.writeWithCsp(res, 200, "text/html; charset=utf-8", body);
|
|
246
|
+
}
|
|
247
|
+
sendError(res, status, _label, body) {
|
|
248
|
+
const safe = body ?? "OAuth callback error.";
|
|
249
|
+
const html = `<!doctype html><html><head><meta charset="utf-8"><title>chances · OAuth</title></head><body style="font-family:system-ui,sans-serif;padding:2rem"><h1>OAuth callback error</h1><p>${escapeHtml(safe)}</p></body></html>`;
|
|
250
|
+
this.writeWithCsp(res, status, "text/html; charset=utf-8", html);
|
|
251
|
+
}
|
|
252
|
+
writeWithCsp(res, status, contentType, body) {
|
|
253
|
+
res.statusCode = status;
|
|
254
|
+
res.setHeader("Content-Type", contentType);
|
|
255
|
+
res.setHeader("Content-Security-Policy", "default-src 'none'; base-uri 'none'; frame-ancestors 'none'");
|
|
256
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
257
|
+
res.setHeader("Cache-Control", "no-store");
|
|
258
|
+
res.setHeader("Referrer-Policy", "no-referrer");
|
|
259
|
+
res.end(body);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function escapeHtml(s) {
|
|
263
|
+
return s
|
|
264
|
+
.replace(/&/g, "&")
|
|
265
|
+
.replace(/</g, "<")
|
|
266
|
+
.replace(/>/g, ">")
|
|
267
|
+
.replace(/"/g, """)
|
|
268
|
+
.replace(/'/g, "'");
|
|
269
|
+
}
|
|
270
|
+
/** Length-then-content constant-time compare; safe against tiny strings. */
|
|
271
|
+
function constantTimeEqual(a, b) {
|
|
272
|
+
if (a.length !== b.length)
|
|
273
|
+
return false;
|
|
274
|
+
let diff = 0;
|
|
275
|
+
for (let i = 0; i < a.length; i++) {
|
|
276
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
277
|
+
}
|
|
278
|
+
return diff === 0;
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=callback-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback-server.js","sourceRoot":"","sources":["../../../src/mcp/oauth/callback-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (3.7) Deterministic config hash for vault key derivation.
|
|
3
|
+
*
|
|
4
|
+
* Vault keys are `mcp-oauth|<serverName>|<configHash16>` so that:
|
|
5
|
+
* - Changing the server URL invalidates the stored tokens (you'd
|
|
6
|
+
* otherwise authenticate against the wrong server).
|
|
7
|
+
* - Changing `auth.scopes` invalidates them (need a new consent
|
|
8
|
+
* screen).
|
|
9
|
+
* - Cosmetic changes (header order, URL trailing slashes, scope
|
|
10
|
+
* ordering) do NOT invalidate — same logical config = same hash.
|
|
11
|
+
*
|
|
12
|
+
* Per claude-code's `auth.ts:325-341` pattern. Hash is sha256, kept
|
|
13
|
+
* to 16 hex chars (8 bytes) — enough entropy to distinguish unrelated
|
|
14
|
+
* configs but short enough to remain readable in diagnostics.
|
|
15
|
+
*/
|
|
16
|
+
import type { HttpMcpConfig } from "../types.js";
|
|
17
|
+
/**
|
|
18
|
+
* Computes the stable hash for an OAuth-configured HTTP server entry.
|
|
19
|
+
* Throws if the server isn't OAuth-configured (caller's bug).
|
|
20
|
+
*/
|
|
21
|
+
export declare function makeConfigHash(config: HttpMcpConfig): string;
|
|
22
|
+
/** Convenience: full vault key for an OAuth-configured server. */
|
|
23
|
+
export declare function makeVaultKey(serverName: string, config: HttpMcpConfig): string;
|
|
24
|
+
//# sourceMappingURL=config-hash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-hash.d.ts","sourceRoot":"","sources":["../../../src/mcp/oauth/config-hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CA6B5D;AAED,kEAAkE;AAClE,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,MAAM,CAE9E"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (3.7) Deterministic config hash for vault key derivation.
|
|
3
|
+
*
|
|
4
|
+
* Vault keys are `mcp-oauth|<serverName>|<configHash16>` so that:
|
|
5
|
+
* - Changing the server URL invalidates the stored tokens (you'd
|
|
6
|
+
* otherwise authenticate against the wrong server).
|
|
7
|
+
* - Changing `auth.scopes` invalidates them (need a new consent
|
|
8
|
+
* screen).
|
|
9
|
+
* - Cosmetic changes (header order, URL trailing slashes, scope
|
|
10
|
+
* ordering) do NOT invalidate — same logical config = same hash.
|
|
11
|
+
*
|
|
12
|
+
* Per claude-code's `auth.ts:325-341` pattern. Hash is sha256, kept
|
|
13
|
+
* to 16 hex chars (8 bytes) — enough entropy to distinguish unrelated
|
|
14
|
+
* configs but short enough to remain readable in diagnostics.
|
|
15
|
+
*/
|
|
16
|
+
import { createHash } from "node:crypto";
|
|
17
|
+
const HASH_LEN_HEX = 16;
|
|
18
|
+
/**
|
|
19
|
+
* Computes the stable hash for an OAuth-configured HTTP server entry.
|
|
20
|
+
* Throws if the server isn't OAuth-configured (caller's bug).
|
|
21
|
+
*/
|
|
22
|
+
export function makeConfigHash(config) {
|
|
23
|
+
if (config.type !== "http" || !config.auth || config.auth.kind !== "oauth") {
|
|
24
|
+
throw new Error("makeConfigHash: server is not OAuth-configured");
|
|
25
|
+
}
|
|
26
|
+
// Normalise URL: trim trailing slashes, lowercase host. Path-case
|
|
27
|
+
// and query stay as-is (some IdPs are path-sensitive).
|
|
28
|
+
let url;
|
|
29
|
+
try {
|
|
30
|
+
url = new URL(config.url);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// If the URL isn't parseable, hash the raw string — caller's
|
|
34
|
+
// parse.ts would have rejected before reaching us, but be defensive.
|
|
35
|
+
return createHash("sha256").update(config.url).digest("hex").slice(0, HASH_LEN_HEX);
|
|
36
|
+
}
|
|
37
|
+
const normalisedUrl = `${url.protocol}//${url.host.toLowerCase()}${url.pathname.replace(/\/+$/, "")}${url.search}`;
|
|
38
|
+
// Canonical scope list: dedup + sort.
|
|
39
|
+
const scopes = config.auth.scopes ? [...new Set(config.auth.scopes)].sort() : [];
|
|
40
|
+
// Stable JSON: only fields that affect IdP-side identity.
|
|
41
|
+
const stable = {
|
|
42
|
+
url: normalisedUrl,
|
|
43
|
+
authServerMetadataUrl: config.auth.authServerMetadataUrl ?? null,
|
|
44
|
+
clientId: config.auth.clientId ?? null,
|
|
45
|
+
clientSecret: config.auth.clientSecret ?? null,
|
|
46
|
+
scopes,
|
|
47
|
+
callbackPort: config.auth.callbackPort ?? null,
|
|
48
|
+
};
|
|
49
|
+
return createHash("sha256").update(JSON.stringify(stable)).digest("hex").slice(0, HASH_LEN_HEX);
|
|
50
|
+
}
|
|
51
|
+
/** Convenience: full vault key for an OAuth-configured server. */
|
|
52
|
+
export function makeVaultKey(serverName, config) {
|
|
53
|
+
return `mcp-oauth|${serverName}|${makeConfigHash(config)}`;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=config-hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-hash.js","sourceRoot":"","sources":["../../../src/mcp/oauth/config-hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,MAAM,YAAY,GAAG,EAAE,CAAC;AAExB;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,MAAqB;IAClD,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC3E,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IACD,kEAAkE;IAClE,uDAAuD;IACvD,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,6DAA6D;QAC7D,qEAAqE;QACrE,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;IACtF,CAAC;IACD,MAAM,aAAa,GAAG,GAAG,GAAG,CAAC,QAAQ,KAAK,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;IAEnH,sCAAsC;IACtC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAEjF,0DAA0D;IAC1D,MAAM,MAAM,GAAG;QACb,GAAG,EAAE,aAAa;QAClB,qBAAqB,EAAE,MAAM,CAAC,IAAI,CAAC,qBAAqB,IAAI,IAAI;QAChE,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI;QACtC,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI;QAC9C,MAAM;QACN,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI;KAC/C,CAAC;IACF,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;AAClG,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,YAAY,CAAC,UAAkB,EAAE,MAAqB;IACpE,OAAO,aAAa,UAAU,IAAI,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (3.7) Normalises non-RFC-6749 OAuth error responses.
|
|
3
|
+
*
|
|
4
|
+
* Some IdPs return HTTP 200 + JSON body with `{"ok": false, "error": ...}`
|
|
5
|
+
* instead of the RFC 6749 standard (HTTP 4xx + JSON body with
|
|
6
|
+
* `{"error": "...", "error_description": "..."}`). Slack is the
|
|
7
|
+
* canonical offender; claude-code's
|
|
8
|
+
* `services/mcp/auth.ts:157-190` has a `normalizeOAuthErrorBody()`
|
|
9
|
+
* helper for exactly this case.
|
|
10
|
+
*
|
|
11
|
+
* Without normalisation, the SDK's strict 4xx-based detection silently
|
|
12
|
+
* accepts the 200 response as success and downstream code is confused
|
|
13
|
+
* when the access_token field is missing.
|
|
14
|
+
*/
|
|
15
|
+
export interface NormalisedOAuthError {
|
|
16
|
+
/** RFC 6749 error code; defaults to `invalid_request` when unspecified. */
|
|
17
|
+
error: string;
|
|
18
|
+
/** Optional human-readable detail. Already control-stripped. */
|
|
19
|
+
errorDescription?: string;
|
|
20
|
+
/** Optional URI to a page with more info. */
|
|
21
|
+
errorUri?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Inspects a `Response` (already-parsed JSON body) plus HTTP status
|
|
25
|
+
* code and returns:
|
|
26
|
+
* - `{ ok: true }` when the response represents success;
|
|
27
|
+
* - `{ ok: false, error }` when it represents an OAuth failure
|
|
28
|
+
* (regardless of whether the HTTP status is 200 or 4xx).
|
|
29
|
+
*/
|
|
30
|
+
export declare function classifyOAuthResponse(status: number, body: unknown): {
|
|
31
|
+
ok: true;
|
|
32
|
+
} | {
|
|
33
|
+
ok: false;
|
|
34
|
+
error: NormalisedOAuthError;
|
|
35
|
+
};
|
|
36
|
+
/** Returns true when an OAuth error is "definitive" — refresh will
|
|
37
|
+
* never succeed without user intervention. */
|
|
38
|
+
export declare function isDefinitiveOAuthError(err: NormalisedOAuthError): boolean;
|
|
39
|
+
//# sourceMappingURL=error-normalize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-normalize.d.ts","sourceRoot":"","sources":["../../../src/mcp/oauth/error-normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AASD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,GACZ;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,oBAAoB,CAAA;CAAE,CAmC3D;AA0BD;+CAC+C;AAC/C,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,oBAAoB,GAAG,OAAO,CAEzE"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (3.7) Normalises non-RFC-6749 OAuth error responses.
|
|
3
|
+
*
|
|
4
|
+
* Some IdPs return HTTP 200 + JSON body with `{"ok": false, "error": ...}`
|
|
5
|
+
* instead of the RFC 6749 standard (HTTP 4xx + JSON body with
|
|
6
|
+
* `{"error": "...", "error_description": "..."}`). Slack is the
|
|
7
|
+
* canonical offender; claude-code's
|
|
8
|
+
* `services/mcp/auth.ts:157-190` has a `normalizeOAuthErrorBody()`
|
|
9
|
+
* helper for exactly this case.
|
|
10
|
+
*
|
|
11
|
+
* Without normalisation, the SDK's strict 4xx-based detection silently
|
|
12
|
+
* accepts the 200 response as success and downstream code is confused
|
|
13
|
+
* when the access_token field is missing.
|
|
14
|
+
*/
|
|
15
|
+
/** Strip control chars from a string — defensive on attacker-controlled
|
|
16
|
+
* error_description that might carry newlines/escapes. */
|
|
17
|
+
function clean(s) {
|
|
18
|
+
if (s === undefined || s === null)
|
|
19
|
+
return undefined;
|
|
20
|
+
return s.replace(/[\x00-\x1F\x7F]+/g, " ").trim() || undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Inspects a `Response` (already-parsed JSON body) plus HTTP status
|
|
24
|
+
* code and returns:
|
|
25
|
+
* - `{ ok: true }` when the response represents success;
|
|
26
|
+
* - `{ ok: false, error }` when it represents an OAuth failure
|
|
27
|
+
* (regardless of whether the HTTP status is 200 or 4xx).
|
|
28
|
+
*/
|
|
29
|
+
export function classifyOAuthResponse(status, body) {
|
|
30
|
+
// RFC 6749: 4xx with JSON body containing `error`.
|
|
31
|
+
if (status >= 400 && status < 600) {
|
|
32
|
+
const err = extractRfc6749(body);
|
|
33
|
+
return { ok: false, error: err ?? { error: "server_error", errorDescription: `HTTP ${status}` } };
|
|
34
|
+
}
|
|
35
|
+
// Slack-style: 200 with `{ok: false, error: ...}`.
|
|
36
|
+
if (status >= 200 && status < 300) {
|
|
37
|
+
if (isObject(body) && body.ok === false && typeof body.error === "string") {
|
|
38
|
+
return {
|
|
39
|
+
ok: false,
|
|
40
|
+
error: {
|
|
41
|
+
error: body.error,
|
|
42
|
+
errorDescription: clean(typeof body.error_description === "string" ? body.error_description : undefined),
|
|
43
|
+
errorUri: clean(typeof body.error_uri === "string" ? body.error_uri : undefined),
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// Some servers return 200 with a top-level `error` field but no `ok`.
|
|
48
|
+
if (isObject(body) && typeof body.error === "string" && !("access_token" in body)) {
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
error: {
|
|
52
|
+
error: body.error,
|
|
53
|
+
errorDescription: clean(typeof body.error_description === "string" ? body.error_description : undefined),
|
|
54
|
+
errorUri: clean(typeof body.error_uri === "string" ? body.error_uri : undefined),
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return { ok: true };
|
|
59
|
+
}
|
|
60
|
+
// 1xx / 3xx — surface as unknown.
|
|
61
|
+
return { ok: false, error: { error: "server_error", errorDescription: `HTTP ${status}` } };
|
|
62
|
+
}
|
|
63
|
+
function extractRfc6749(body) {
|
|
64
|
+
if (!isObject(body))
|
|
65
|
+
return undefined;
|
|
66
|
+
if (typeof body.error !== "string")
|
|
67
|
+
return undefined;
|
|
68
|
+
return {
|
|
69
|
+
error: body.error,
|
|
70
|
+
errorDescription: clean(typeof body.error_description === "string" ? body.error_description : undefined),
|
|
71
|
+
errorUri: clean(typeof body.error_uri === "string" ? body.error_uri : undefined),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function isObject(v) {
|
|
75
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
76
|
+
}
|
|
77
|
+
/** RFC 6749 "definitive failure" codes — refresh CANNOT succeed by
|
|
78
|
+
* retrying, must force full re-auth. (oh-my-pi commit c936620c6.) */
|
|
79
|
+
const DEFINITIVE_ERRORS = new Set([
|
|
80
|
+
"invalid_grant",
|
|
81
|
+
"invalid_client",
|
|
82
|
+
"unauthorized_client",
|
|
83
|
+
"unsupported_grant_type",
|
|
84
|
+
"invalid_scope",
|
|
85
|
+
]);
|
|
86
|
+
/** Returns true when an OAuth error is "definitive" — refresh will
|
|
87
|
+
* never succeed without user intervention. */
|
|
88
|
+
export function isDefinitiveOAuthError(err) {
|
|
89
|
+
return DEFINITIVE_ERRORS.has(err.error);
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=error-normalize.js.map
|