@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 @@
|
|
|
1
|
+
{"version":3,"file":"error-normalize.js","sourceRoot":"","sources":["../../../src/mcp/oauth/error-normalize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (3.7) `ChancesOAuthClientProvider` — chances' implementation of the SDK's
|
|
3
|
+
* `OAuthClientProvider` interface (`@modelcontextprotocol/sdk/client/auth.js`).
|
|
4
|
+
*
|
|
5
|
+
* One provider instance per OAuth-configured MCP server. Holds:
|
|
6
|
+
* - A **generation snapshot** captured at construction. Late-arriving
|
|
7
|
+
* `save*` calls from an in-flight refresh that started BEFORE a logout
|
|
8
|
+
* are dropped when the snapshot is older than the vault's current
|
|
9
|
+
* generation (codex Round-1 MUST-FIX #5).
|
|
10
|
+
* - A 30-second in-memory `tokens()` cache, invalidated on every `save*`
|
|
11
|
+
* / `invalidateCredentials` and on detected staleness.
|
|
12
|
+
* - A reference to the per-server `Redactor` so newly-discovered secrets
|
|
13
|
+
* (access tokens, refresh tokens, PKCE verifier) join the scrub set
|
|
14
|
+
* AT SAVE TIME — so any subsequent thrown error is already scrubbed
|
|
15
|
+
* when it surfaces (codex Round-1 SHOULD-FIX redactor extension).
|
|
16
|
+
*
|
|
17
|
+
* **Hot path (`tokens()`):** vault read → generation check → freshness
|
|
18
|
+
* check. We deliberately do NOT proactively refresh from inside
|
|
19
|
+
* `tokens()` in v1 — the SDK's `auth()` orchestrator handles 401-driven
|
|
20
|
+
* refresh and the `RefreshCoalescer` infrastructure is wired at the
|
|
21
|
+
* host-level dispose/logout-abort seam, ready for a 3.7.1 follow-up that
|
|
22
|
+
* adds proactive refresh if real-world parallel-call refresh storms
|
|
23
|
+
* become an observed problem. Keeping V1 simple matches the SDK's own
|
|
24
|
+
* stance (the SDK doesn't coalesce either) and avoids a complex
|
|
25
|
+
* refresh-inside-tokens() path that re-discovers metadata on the hot
|
|
26
|
+
* path.
|
|
27
|
+
*
|
|
28
|
+
* **Write path:** every `save*` and `invalidateCredentials` routes
|
|
29
|
+
* through `vault.mergeMutate(key, mutator)` — the per-key in-process
|
|
30
|
+
* mutex serialises read-modify-write and prevents the partial-update
|
|
31
|
+
* clobber from codex Round-1 MUST-FIX #1.
|
|
32
|
+
*
|
|
33
|
+
* Construction-time DI lets unit tests substitute a fake vault, a fake
|
|
34
|
+
* callback server, and a deterministic clock.
|
|
35
|
+
*/
|
|
36
|
+
import type { OAuthClientInformationFull, OAuthClientInformationMixed, OAuthClientMetadata, OAuthTokens } from "./sdk-shapes.js";
|
|
37
|
+
import type { LoopbackCallbackServer } from "./callback-server.js";
|
|
38
|
+
import type { HttpMcpConfig } from "../types.js";
|
|
39
|
+
/** SDK's `OAuthDiscoveryState` shape narrowed to what we accept on save.
|
|
40
|
+
* We strip the metadata bags (§ 4.2) so the keychain stays well under
|
|
41
|
+
* the 4 KiB Windows limit. */
|
|
42
|
+
export interface OAuthDiscoveryStateInput {
|
|
43
|
+
authorizationServerUrl: string;
|
|
44
|
+
authorizationServerMetadata?: unknown;
|
|
45
|
+
resourceMetadata?: unknown;
|
|
46
|
+
resourceMetadataUrl?: string;
|
|
47
|
+
}
|
|
48
|
+
/** What the SDK gets back when it asks for the cached discovery state.
|
|
49
|
+
* We only carry URLs; the SDK will re-fetch full metadata on demand. */
|
|
50
|
+
export interface OAuthDiscoveryStateOutput {
|
|
51
|
+
authorizationServerUrl: string;
|
|
52
|
+
resourceMetadataUrl?: string;
|
|
53
|
+
}
|
|
54
|
+
/** Sub-set of the host-supplied logger interface this module touches. */
|
|
55
|
+
interface Logger {
|
|
56
|
+
debug(msg: string): void;
|
|
57
|
+
info(msg: string): void;
|
|
58
|
+
warn(msg: string): void;
|
|
59
|
+
error(msg: string): void;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Vault payload shape — one record per (serverName, configHash). Versioned
|
|
63
|
+
* for forward-compat. All sub-records use the SDK's snake_case shapes so
|
|
64
|
+
* the round-trip back to `tokens()` / `clientInformation()` requires no
|
|
65
|
+
* field renaming.
|
|
66
|
+
*/
|
|
67
|
+
export interface VaultPayload {
|
|
68
|
+
v: 1;
|
|
69
|
+
/** The 16-hex `makeConfigHash(serverConfig)` slice. Cross-checked at
|
|
70
|
+
* read time — a payload whose stored hash differs from the current
|
|
71
|
+
* config's hash is from a previous config and must be ignored. */
|
|
72
|
+
configHash: string;
|
|
73
|
+
/** Monotonic counter — see codex Round-1 MUST-FIX #5. Logout and
|
|
74
|
+
* `invalidateCredentials("all")` bump it; stale providers' writes
|
|
75
|
+
* drop via the mergeMutate generation check. */
|
|
76
|
+
generation: number;
|
|
77
|
+
/** Stored client info from DCR. SDK's `OAuthClientInformationFull` shape. */
|
|
78
|
+
client?: OAuthClientInformationFull;
|
|
79
|
+
/** Stored tokens. SDK's `OAuthTokens` shape PLUS an absolute `expires_at`
|
|
80
|
+
* (epoch ms) computed at save time from the SDK-supplied `expires_in`
|
|
81
|
+
* relative seconds — relative values aren't usable for expiry comparison
|
|
82
|
+
* once they leave the IdP context. */
|
|
83
|
+
tokens?: OAuthTokens & {
|
|
84
|
+
expires_at?: number;
|
|
85
|
+
};
|
|
86
|
+
/** Cached discovery URLs. **No metadata bodies** — § 4.2. */
|
|
87
|
+
discovery?: {
|
|
88
|
+
authorizationServerUrl: string;
|
|
89
|
+
resourceMetadataUrl?: string;
|
|
90
|
+
/** Epoch ms — used for the 24h TTL on cached URLs. */
|
|
91
|
+
discoveredAt: number;
|
|
92
|
+
};
|
|
93
|
+
/** Transient PKCE verifier. Carries `createdAt` so a stale `/mcp login`
|
|
94
|
+
* that crashed mid-flow can't surface its old verifier 15+ minutes
|
|
95
|
+
* later (codex Round-1 SHOULD-FIX Q3). */
|
|
96
|
+
verifier?: {
|
|
97
|
+
value: string;
|
|
98
|
+
createdAt: number;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** The narrow vault surface this provider depends on. Lets tests substitute
|
|
102
|
+
* an in-memory map without standing up the real LocalVault. */
|
|
103
|
+
export interface ProviderVault {
|
|
104
|
+
read<T>(key: string): Promise<T | undefined>;
|
|
105
|
+
mergeMutate<T>(key: string, mutator: (current: T | undefined) => T | undefined | Promise<T | undefined>, allowDelete?: boolean): Promise<void>;
|
|
106
|
+
}
|
|
107
|
+
/** Per-server redactor — the host owns one of these and passes it in so
|
|
108
|
+
* newly-discovered secrets join the scrub set in real time. Mirrored from
|
|
109
|
+
* `../redact.ts#RuntimeRedactor` to avoid a runtime dep on the redact
|
|
110
|
+
* module from this file (which lives under `oauth/`). */
|
|
111
|
+
interface RuntimeRedactor {
|
|
112
|
+
(msg: string): string;
|
|
113
|
+
add(secrets: readonly string[]): void;
|
|
114
|
+
}
|
|
115
|
+
export interface ProviderOptions {
|
|
116
|
+
serverName: string;
|
|
117
|
+
serverConfig: HttpMcpConfig;
|
|
118
|
+
vault: ProviderVault;
|
|
119
|
+
callbackServer: LoopbackCallbackServer;
|
|
120
|
+
redactor: RuntimeRedactor;
|
|
121
|
+
logger: Logger;
|
|
122
|
+
/** Platform-agnostic browser launcher. Default no-op for tests; the
|
|
123
|
+
* slash command supplies the real `open` / `xdg-open` / `start.exe` shim. */
|
|
124
|
+
openBrowser: (url: URL) => Promise<void>;
|
|
125
|
+
/** Test seam — defaults to `Date.now`. */
|
|
126
|
+
now?: () => number;
|
|
127
|
+
}
|
|
128
|
+
export declare class ChancesOAuthClientProvider {
|
|
129
|
+
private readonly opts;
|
|
130
|
+
private readonly snapshotGen;
|
|
131
|
+
private readonly vaultKey;
|
|
132
|
+
private staleFlag;
|
|
133
|
+
private cache;
|
|
134
|
+
/**
|
|
135
|
+
* Use `ChancesOAuthClientProvider.create({...})` instead — the factory
|
|
136
|
+
* captures the vault's current generation BEFORE any caller can call
|
|
137
|
+
* `saveTokens`/`invalidateCredentials` etc. Constructing manually is a
|
|
138
|
+
* test-only path that requires passing `snapshotGen` explicitly.
|
|
139
|
+
*/
|
|
140
|
+
constructor(opts: ProviderOptions, snapshotGen: number);
|
|
141
|
+
/**
|
|
142
|
+
* Async factory — reads the vault to capture the current generation
|
|
143
|
+
* AT CONSTRUCTION TIME, satisfying the codex Round-1 MUST-FIX #5
|
|
144
|
+
* invariant. Any later `save*` whose snapshot is older than the
|
|
145
|
+
* vault's current generation gets dropped by `merge()`.
|
|
146
|
+
*/
|
|
147
|
+
static create(opts: ProviderOptions): Promise<ChancesOAuthClientProvider>;
|
|
148
|
+
/** Read-only test seam — the gen captured at construction. */
|
|
149
|
+
get snapshotGeneration(): number;
|
|
150
|
+
/** Set when a vault read reveals the provider's snapshot is older
|
|
151
|
+
* than the current vault generation. Once set, the provider refuses
|
|
152
|
+
* to write — fresh provider needed (host restart). */
|
|
153
|
+
get isStale(): boolean;
|
|
154
|
+
get redirectUrl(): URL | undefined;
|
|
155
|
+
get clientMetadata(): OAuthClientMetadata;
|
|
156
|
+
/** SDK contract: a 32-byte base64url string. Independent of the PKCE
|
|
157
|
+
* verifier (codex Round-1 MUST-FIX #2) — state lives in the browser
|
|
158
|
+
* URL, verifier never leaves the chances process. */
|
|
159
|
+
state(): string;
|
|
160
|
+
clientInformation(): Promise<OAuthClientInformationMixed | undefined>;
|
|
161
|
+
saveClientInformation(info: OAuthClientInformationMixed): Promise<void>;
|
|
162
|
+
tokens(): Promise<OAuthTokens | undefined>;
|
|
163
|
+
saveTokens(tokens: OAuthTokens): Promise<void>;
|
|
164
|
+
redirectToAuthorization(url: URL): Promise<void>;
|
|
165
|
+
saveCodeVerifier(verifier: string): Promise<void>;
|
|
166
|
+
codeVerifier(): Promise<string>;
|
|
167
|
+
saveDiscoveryState(state: OAuthDiscoveryStateInput): Promise<void>;
|
|
168
|
+
discoveryState(): Promise<OAuthDiscoveryStateOutput | undefined>;
|
|
169
|
+
invalidateCredentials(scope: "all" | "client" | "tokens" | "verifier" | "discovery"): Promise<void>;
|
|
170
|
+
/**
|
|
171
|
+
* Wraps `vault.mergeMutate` with the generation-staleness guard. If
|
|
172
|
+
* the current vault payload's generation is greater than our captured
|
|
173
|
+
* snapshot, the provider is stale (its parent host should already have
|
|
174
|
+
* been restarted) and we DROP the write rather than resurrect cleared
|
|
175
|
+
* credentials. (codex Round-1 MUST-FIX #5)
|
|
176
|
+
*/
|
|
177
|
+
private merge;
|
|
178
|
+
/** Returns the current payload or a fresh empty one with this server's
|
|
179
|
+
* configHash baked in. Centralised so every mutator starts from the
|
|
180
|
+
* same baseline. */
|
|
181
|
+
private seed;
|
|
182
|
+
/** SDK's `OAuthClientInformationMixed` is either `OAuthClientInformation`
|
|
183
|
+
* (preset/config) or `OAuthClientInformationFull` (DCR response). We
|
|
184
|
+
* store the FULL shape — preset entries lack the DCR metadata but
|
|
185
|
+
* have empty `redirect_uris` which is fine for refresh-token flows. */
|
|
186
|
+
private toFullClient;
|
|
187
|
+
private now;
|
|
188
|
+
}
|
|
189
|
+
export {};
|
|
190
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/mcp/oauth/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (3.7) `ChancesOAuthClientProvider` — chances' implementation of the SDK's
|
|
3
|
+
* `OAuthClientProvider` interface (`@modelcontextprotocol/sdk/client/auth.js`).
|
|
4
|
+
*
|
|
5
|
+
* One provider instance per OAuth-configured MCP server. Holds:
|
|
6
|
+
* - A **generation snapshot** captured at construction. Late-arriving
|
|
7
|
+
* `save*` calls from an in-flight refresh that started BEFORE a logout
|
|
8
|
+
* are dropped when the snapshot is older than the vault's current
|
|
9
|
+
* generation (codex Round-1 MUST-FIX #5).
|
|
10
|
+
* - A 30-second in-memory `tokens()` cache, invalidated on every `save*`
|
|
11
|
+
* / `invalidateCredentials` and on detected staleness.
|
|
12
|
+
* - A reference to the per-server `Redactor` so newly-discovered secrets
|
|
13
|
+
* (access tokens, refresh tokens, PKCE verifier) join the scrub set
|
|
14
|
+
* AT SAVE TIME — so any subsequent thrown error is already scrubbed
|
|
15
|
+
* when it surfaces (codex Round-1 SHOULD-FIX redactor extension).
|
|
16
|
+
*
|
|
17
|
+
* **Hot path (`tokens()`):** vault read → generation check → freshness
|
|
18
|
+
* check. We deliberately do NOT proactively refresh from inside
|
|
19
|
+
* `tokens()` in v1 — the SDK's `auth()` orchestrator handles 401-driven
|
|
20
|
+
* refresh and the `RefreshCoalescer` infrastructure is wired at the
|
|
21
|
+
* host-level dispose/logout-abort seam, ready for a 3.7.1 follow-up that
|
|
22
|
+
* adds proactive refresh if real-world parallel-call refresh storms
|
|
23
|
+
* become an observed problem. Keeping V1 simple matches the SDK's own
|
|
24
|
+
* stance (the SDK doesn't coalesce either) and avoids a complex
|
|
25
|
+
* refresh-inside-tokens() path that re-discovers metadata on the hot
|
|
26
|
+
* path.
|
|
27
|
+
*
|
|
28
|
+
* **Write path:** every `save*` and `invalidateCredentials` routes
|
|
29
|
+
* through `vault.mergeMutate(key, mutator)` — the per-key in-process
|
|
30
|
+
* mutex serialises read-modify-write and prevents the partial-update
|
|
31
|
+
* clobber from codex Round-1 MUST-FIX #1.
|
|
32
|
+
*
|
|
33
|
+
* Construction-time DI lets unit tests substitute a fake vault, a fake
|
|
34
|
+
* callback server, and a deterministic clock.
|
|
35
|
+
*/
|
|
36
|
+
import { randomBytes } from "node:crypto";
|
|
37
|
+
import { makeVaultKey } from "./config-hash.js";
|
|
38
|
+
/** RFC 7591 DCR registration request — sent on first-flow auth when the
|
|
39
|
+
* IdP supports DCR AND we don't have a pre-registered `clientId`. */
|
|
40
|
+
const CHANCES_CLIENT_METADATA = {
|
|
41
|
+
client_name: "chances-cli",
|
|
42
|
+
client_uri: "https://github.com/chances-ai/cli",
|
|
43
|
+
redirect_uris: [],
|
|
44
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
45
|
+
response_types: ["code"],
|
|
46
|
+
token_endpoint_auth_method: "none",
|
|
47
|
+
};
|
|
48
|
+
const VERIFIER_TTL_MS = 15 * 60 * 1000;
|
|
49
|
+
const DISCOVERY_TTL_MS = 24 * 60 * 60 * 1000;
|
|
50
|
+
const TOKEN_CACHE_TTL_MS = 30 * 1000;
|
|
51
|
+
const FAILED_AT_CACHE_TTL_MS = 30 * 1000;
|
|
52
|
+
export class ChancesOAuthClientProvider {
|
|
53
|
+
opts;
|
|
54
|
+
snapshotGen;
|
|
55
|
+
vaultKey;
|
|
56
|
+
staleFlag = false;
|
|
57
|
+
cache;
|
|
58
|
+
/**
|
|
59
|
+
* Use `ChancesOAuthClientProvider.create({...})` instead — the factory
|
|
60
|
+
* captures the vault's current generation BEFORE any caller can call
|
|
61
|
+
* `saveTokens`/`invalidateCredentials` etc. Constructing manually is a
|
|
62
|
+
* test-only path that requires passing `snapshotGen` explicitly.
|
|
63
|
+
*/
|
|
64
|
+
constructor(opts, snapshotGen) {
|
|
65
|
+
this.opts = opts;
|
|
66
|
+
this.snapshotGen = snapshotGen;
|
|
67
|
+
this.vaultKey = makeVaultKey(opts.serverName, opts.serverConfig);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Async factory — reads the vault to capture the current generation
|
|
71
|
+
* AT CONSTRUCTION TIME, satisfying the codex Round-1 MUST-FIX #5
|
|
72
|
+
* invariant. Any later `save*` whose snapshot is older than the
|
|
73
|
+
* vault's current generation gets dropped by `merge()`.
|
|
74
|
+
*/
|
|
75
|
+
static async create(opts) {
|
|
76
|
+
const key = makeVaultKey(opts.serverName, opts.serverConfig);
|
|
77
|
+
let snapshot = 0;
|
|
78
|
+
try {
|
|
79
|
+
const current = await opts.vault.read(key);
|
|
80
|
+
snapshot = current?.generation ?? 0;
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
opts.logger.warn(`oauth provider '${opts.serverName}': snapshot read failed: ${opts.redactor(e.message)}`);
|
|
84
|
+
}
|
|
85
|
+
return new ChancesOAuthClientProvider(opts, snapshot);
|
|
86
|
+
}
|
|
87
|
+
/** Read-only test seam — the gen captured at construction. */
|
|
88
|
+
get snapshotGeneration() {
|
|
89
|
+
return this.snapshotGen;
|
|
90
|
+
}
|
|
91
|
+
/** Set when a vault read reveals the provider's snapshot is older
|
|
92
|
+
* than the current vault generation. Once set, the provider refuses
|
|
93
|
+
* to write — fresh provider needed (host restart). */
|
|
94
|
+
get isStale() {
|
|
95
|
+
return this.staleFlag;
|
|
96
|
+
}
|
|
97
|
+
// ─── OAuthClientProvider — required methods ──────────────────────────
|
|
98
|
+
get redirectUrl() {
|
|
99
|
+
try {
|
|
100
|
+
return this.opts.callbackServer.redirectUrl;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// `bind()` hasn't run yet — the SDK calls this before our flow has
|
|
104
|
+
// bound the callback. Returning undefined is the documented contract.
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
get clientMetadata() {
|
|
109
|
+
return CHANCES_CLIENT_METADATA;
|
|
110
|
+
}
|
|
111
|
+
/** SDK contract: a 32-byte base64url string. Independent of the PKCE
|
|
112
|
+
* verifier (codex Round-1 MUST-FIX #2) — state lives in the browser
|
|
113
|
+
* URL, verifier never leaves the chances process. */
|
|
114
|
+
state() {
|
|
115
|
+
return randomBytes(32).toString("base64url");
|
|
116
|
+
}
|
|
117
|
+
async clientInformation() {
|
|
118
|
+
const payload = await this.opts.vault.read(this.vaultKey);
|
|
119
|
+
// Codex Q5 resolution order: stored DCR result first, then config-provided.
|
|
120
|
+
if (payload?.client)
|
|
121
|
+
return payload.client;
|
|
122
|
+
const cfg = this.opts.serverConfig.auth;
|
|
123
|
+
if (cfg?.kind === "oauth" && cfg.clientId) {
|
|
124
|
+
return {
|
|
125
|
+
client_id: cfg.clientId,
|
|
126
|
+
client_secret: cfg.clientSecret,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
async saveClientInformation(info) {
|
|
132
|
+
if (info.client_secret)
|
|
133
|
+
this.opts.redactor.add([info.client_secret]);
|
|
134
|
+
await this.merge((current) => ({
|
|
135
|
+
...this.seed(current),
|
|
136
|
+
client: this.toFullClient(info),
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
async tokens() {
|
|
140
|
+
const now = this.now();
|
|
141
|
+
// 1. In-memory cache.
|
|
142
|
+
if (this.cache) {
|
|
143
|
+
if (this.cache.kind === "tokens" && now - this.cache.cachedAt <= TOKEN_CACHE_TTL_MS) {
|
|
144
|
+
return this.cache.value;
|
|
145
|
+
}
|
|
146
|
+
if (this.cache.kind === "fail" && now - this.cache.cachedAt <= FAILED_AT_CACHE_TTL_MS) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
// TTL expired — drop and re-read.
|
|
150
|
+
this.cache = undefined;
|
|
151
|
+
}
|
|
152
|
+
// 2. Vault read.
|
|
153
|
+
const payload = await this.opts.vault.read(this.vaultKey);
|
|
154
|
+
if (!payload)
|
|
155
|
+
return undefined;
|
|
156
|
+
// 3. Generation guard — payload bumped under us → this provider is
|
|
157
|
+
// stale and must NOT return resurrected credentials.
|
|
158
|
+
if (payload.generation > this.snapshotGen) {
|
|
159
|
+
this.staleFlag = true;
|
|
160
|
+
this.cache = { kind: "fail", cachedAt: now };
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
if (!payload.tokens)
|
|
164
|
+
return undefined;
|
|
165
|
+
// 5. Add the token's strings to the redactor (defence-in-depth — they
|
|
166
|
+
// may have been added at save time, but a freshly-loaded provider
|
|
167
|
+
// starting from an existing vault hasn't seen the save yet).
|
|
168
|
+
this.opts.redactor.add([payload.tokens.access_token, payload.tokens.refresh_token].filter((s) => !!s));
|
|
169
|
+
// 6. Cache + return. We hand the SDK the snake_case bag as-is.
|
|
170
|
+
this.cache = { kind: "tokens", value: payload.tokens, cachedAt: now };
|
|
171
|
+
return payload.tokens;
|
|
172
|
+
}
|
|
173
|
+
async saveTokens(tokens) {
|
|
174
|
+
// Register the runtime secret BEFORE any persistence error can
|
|
175
|
+
// surface its value in a thrown message.
|
|
176
|
+
this.opts.redactor.add([tokens.access_token, tokens.refresh_token].filter((s) => !!s));
|
|
177
|
+
const expiresAt = typeof tokens.expires_in === "number" && Number.isFinite(tokens.expires_in)
|
|
178
|
+
? this.now() + tokens.expires_in * 1000
|
|
179
|
+
: undefined;
|
|
180
|
+
await this.merge((current) => {
|
|
181
|
+
const seeded = this.seed(current);
|
|
182
|
+
// Preserve existing refresh_token when the IdP didn't return a new
|
|
183
|
+
// one — the SDK's `refreshAuthorization` already preserves it but
|
|
184
|
+
// a defensive save() might not. RFC 6749 §6 says "the
|
|
185
|
+
// authorization server MAY issue a new refresh token" (i.e. MAY,
|
|
186
|
+
// not MUST), so an absent refresh_token means "reuse the old one".
|
|
187
|
+
const refresh_token = tokens.refresh_token ?? seeded.tokens?.refresh_token;
|
|
188
|
+
return {
|
|
189
|
+
...seeded,
|
|
190
|
+
tokens: { ...tokens, refresh_token, expires_at: expiresAt },
|
|
191
|
+
};
|
|
192
|
+
});
|
|
193
|
+
// Invalidate in-memory cache so the next tokens() call observes the write.
|
|
194
|
+
this.cache = undefined;
|
|
195
|
+
}
|
|
196
|
+
async redirectToAuthorization(url) {
|
|
197
|
+
await this.opts.openBrowser(url);
|
|
198
|
+
}
|
|
199
|
+
async saveCodeVerifier(verifier) {
|
|
200
|
+
this.opts.redactor.add([verifier]);
|
|
201
|
+
await this.merge((current) => ({
|
|
202
|
+
...this.seed(current),
|
|
203
|
+
verifier: { value: verifier, createdAt: this.now() },
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
async codeVerifier() {
|
|
207
|
+
const payload = await this.opts.vault.read(this.vaultKey);
|
|
208
|
+
const v = payload?.verifier;
|
|
209
|
+
if (!v)
|
|
210
|
+
throw new Error("OAuth provider: no PKCE verifier in storage (flow not started)");
|
|
211
|
+
if (this.now() - v.createdAt > VERIFIER_TTL_MS) {
|
|
212
|
+
throw new Error("OAuth provider: stored PKCE verifier expired (>15 min) — restart the flow");
|
|
213
|
+
}
|
|
214
|
+
return v.value;
|
|
215
|
+
}
|
|
216
|
+
// ─── OAuthClientProvider — optional methods ──────────────────────────
|
|
217
|
+
async saveDiscoveryState(state) {
|
|
218
|
+
// § 4.2: strip metadata bodies; keep URLs.
|
|
219
|
+
await this.merge((current) => ({
|
|
220
|
+
...this.seed(current),
|
|
221
|
+
discovery: {
|
|
222
|
+
authorizationServerUrl: state.authorizationServerUrl,
|
|
223
|
+
resourceMetadataUrl: state.resourceMetadataUrl,
|
|
224
|
+
discoveredAt: this.now(),
|
|
225
|
+
},
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
async discoveryState() {
|
|
229
|
+
const payload = await this.opts.vault.read(this.vaultKey);
|
|
230
|
+
const d = payload?.discovery;
|
|
231
|
+
if (!d)
|
|
232
|
+
return undefined;
|
|
233
|
+
if (this.now() - d.discoveredAt > DISCOVERY_TTL_MS)
|
|
234
|
+
return undefined;
|
|
235
|
+
return {
|
|
236
|
+
authorizationServerUrl: d.authorizationServerUrl,
|
|
237
|
+
resourceMetadataUrl: d.resourceMetadataUrl,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
async invalidateCredentials(scope) {
|
|
241
|
+
this.cache = undefined;
|
|
242
|
+
await this.merge((current) => {
|
|
243
|
+
const seeded = this.seed(current);
|
|
244
|
+
switch (scope) {
|
|
245
|
+
case "all":
|
|
246
|
+
return {
|
|
247
|
+
v: 1,
|
|
248
|
+
configHash: seeded.configHash,
|
|
249
|
+
generation: seeded.generation + 1,
|
|
250
|
+
};
|
|
251
|
+
case "tokens":
|
|
252
|
+
return { ...seeded, tokens: undefined };
|
|
253
|
+
case "client":
|
|
254
|
+
return { ...seeded, client: undefined };
|
|
255
|
+
case "verifier":
|
|
256
|
+
return { ...seeded, verifier: undefined };
|
|
257
|
+
case "discovery":
|
|
258
|
+
return { ...seeded, discovery: undefined };
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
// ─── Internal helpers ────────────────────────────────────────────────
|
|
263
|
+
/**
|
|
264
|
+
* Wraps `vault.mergeMutate` with the generation-staleness guard. If
|
|
265
|
+
* the current vault payload's generation is greater than our captured
|
|
266
|
+
* snapshot, the provider is stale (its parent host should already have
|
|
267
|
+
* been restarted) and we DROP the write rather than resurrect cleared
|
|
268
|
+
* credentials. (codex Round-1 MUST-FIX #5)
|
|
269
|
+
*/
|
|
270
|
+
async merge(mutator) {
|
|
271
|
+
await this.opts.vault.mergeMutate(this.vaultKey, (current) => {
|
|
272
|
+
if (current && current.generation > this.snapshotGen) {
|
|
273
|
+
this.staleFlag = true;
|
|
274
|
+
this.opts.logger.debug(`oauth provider '${this.opts.serverName}': dropping save (snapshot gen ${this.snapshotGen} < vault gen ${current.generation})`);
|
|
275
|
+
return current;
|
|
276
|
+
}
|
|
277
|
+
return mutator(current);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
/** Returns the current payload or a fresh empty one with this server's
|
|
281
|
+
* configHash baked in. Centralised so every mutator starts from the
|
|
282
|
+
* same baseline. */
|
|
283
|
+
seed(current) {
|
|
284
|
+
if (current)
|
|
285
|
+
return current;
|
|
286
|
+
return {
|
|
287
|
+
v: 1,
|
|
288
|
+
configHash: this.vaultKey.split("|")[2] ?? "",
|
|
289
|
+
generation: this.snapshotGen,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/** SDK's `OAuthClientInformationMixed` is either `OAuthClientInformation`
|
|
293
|
+
* (preset/config) or `OAuthClientInformationFull` (DCR response). We
|
|
294
|
+
* store the FULL shape — preset entries lack the DCR metadata but
|
|
295
|
+
* have empty `redirect_uris` which is fine for refresh-token flows. */
|
|
296
|
+
toFullClient(info) {
|
|
297
|
+
if ("redirect_uris" in info)
|
|
298
|
+
return info;
|
|
299
|
+
return { redirect_uris: [], ...info };
|
|
300
|
+
}
|
|
301
|
+
now() {
|
|
302
|
+
return (this.opts.now ?? Date.now)();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../../src/mcp/oauth/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAS1C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AA4GhD;sEACsE;AACtE,MAAM,uBAAuB,GAAwB;IACnD,WAAW,EAAE,aAAa;IAC1B,UAAU,EAAE,mCAAmC;IAC/C,aAAa,EAAE,EAAE;IACjB,WAAW,EAAE,CAAC,oBAAoB,EAAE,eAAe,CAAC;IACpD,cAAc,EAAE,CAAC,MAAM,CAAC;IACxB,0BAA0B,EAAE,MAAM;CACnC,CAAC;AAEF,MAAM,eAAe,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACvC,MAAM,gBAAgB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAC7C,MAAM,kBAAkB,GAAG,EAAE,GAAG,IAAI,CAAC;AACrC,MAAM,sBAAsB,GAAG,EAAE,GAAG,IAAI,CAAC;AAEzC,MAAM,OAAO,0BAA0B;IAcR;IAAwC;IAbpD,QAAQ,CAAS;IAC1B,SAAS,GAAG,KAAK,CAAC;IAClB,KAAK,CAGC;IAEd;;;;;OAKG;IACH,YAA6B,IAAqB,EAAmB,WAAmB;QAA3D,SAAI,GAAJ,IAAI,CAAiB;QAAmB,gBAAW,GAAX,WAAW,CAAQ;QACtF,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IACnE,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAqB;QACvC,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAC7D,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAe,GAAG,CAAC,CAAC;YACzD,QAAQ,GAAG,OAAO,EAAE,UAAU,IAAI,CAAC,CAAC;QACtC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,mBAAmB,IAAI,CAAC,UAAU,4BAA4B,IAAI,CAAC,QAAQ,CAAE,CAAW,CAAC,OAAO,CAAC,EAAE,CACpG,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,0BAA0B,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACxD,CAAC;IAED,8DAA8D;IAC9D,IAAI,kBAAkB;QACpB,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED;;2DAEuD;IACvD,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,wEAAwE;IAExE,IAAI,WAAW;QACb,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;YACnE,sEAAsE;YACtE,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,IAAI,cAAc;QAChB,OAAO,uBAAuB,CAAC;IACjC,CAAC;IAED;;0DAEsD;IACtD,KAAK;QACH,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,iBAAiB;QACrB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAe,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxE,4EAA4E;QAC5E,IAAI,OAAO,EAAE,MAAM;YAAE,OAAO,OAAO,CAAC,MAAM,CAAC;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC;QACxC,IAAI,GAAG,EAAE,IAAI,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;YAC1C,OAAO;gBACL,SAAS,EAAE,GAAG,CAAC,QAAQ;gBACvB,aAAa,EAAE,GAAG,CAAC,YAAY;aAChC,CAAC;QACJ,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,qBAAqB,CAAC,IAAiC;QAC3D,IAAI,IAAI,CAAC,aAAa;YAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;QACrE,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAC7B,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;YACrB,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC;SAChC,CAAC,CAAC,CAAC;IACN,CAAC;IAED,KAAK,CAAC,MAAM;QACV,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,sBAAsB;QACtB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,kBAAkB,EAAE,CAAC;gBACpF,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;YAC1B,CAAC;YACD,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,sBAAsB,EAAE,CAAC;gBACtF,OAAO,SAAS,CAAC;YACnB,CAAC;YACD,kCAAkC;YAClC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACzB,CAAC;QAED,iBAAiB;QACjB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAe,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxE,IAAI,CAAC,OAAO;YAAE,OAAO,SAAS,CAAC;QAE/B,mEAAmE;QACnE,wDAAwD;QACxD,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YAC1C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;YACtB,IAAI,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;YAC7C,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,MAAM;YAAE,OAAO,SAAS,CAAC;QAEtC,sEAAsE;QACtE,qEAAqE;QACrE,gEAAgE;QAChE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CACpB,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAC5F,CAAC;QAEF,+DAA+D;QAC/D,IAAI,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;QACtE,OAAO,OAAO,CAAC,MAAM,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAmB;QAClC,+DAA+D;QAC/D,yCAAyC;QACzC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAEpG,MAAM,SAAS,GACb,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC;YACzE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,UAAU,GAAG,IAAI;YACvC,CAAC,CAAC,SAAS,CAAC;QAEhB,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE;YAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAClC,mEAAmE;YACnE,kEAAkE;YAClE,sDAAsD;YACtD,iEAAiE;YACjE,mEAAmE;YACnE,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC;YAC3E,OAAO;gBACL,GAAG,MAAM;gBACT,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE;aAC5D,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,2EAA2E;QAC3E,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,uBAAuB,CAAC,GAAQ;QACpC,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,QAAgB;QACrC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QACnC,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAC7B,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;YACrB,QAAQ,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE;SACrD,CAAC,CAAC,CAAC;IACN,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAe,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxE,MAAM,CAAC,GAAG,OAAO,EAAE,QAAQ,CAAC;QAC5B,IAAI,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;QAC1F,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,SAAS,GAAG,eAAe,EAAE,CAAC;YAC/C,MAAM,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAC;QAC/F,CAAC;QACD,OAAO,CAAC,CAAC,KAAK,CAAC;IACjB,CAAC;IAED,wEAAwE;IAExE,KAAK,CAAC,kBAAkB,CAAC,KAA+B;QACtD,2CAA2C;QAC3C,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAC7B,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;YACrB,SAAS,EAAE;gBACT,sBAAsB,EAAE,KAAK,CAAC,sBAAsB;gBACpD,mBAAmB,EAAE,KAAK,CAAC,mBAAmB;gBAC9C,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE;aACzB;SACF,CAAC,CAAC,CAAC;IACN,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAe,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxE,MAAM,CAAC,GAAG,OAAO,EAAE,SAAS,CAAC;QAC7B,IAAI,CAAC,CAAC;YAAE,OAAO,SAAS,CAAC;QACzB,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,YAAY,GAAG,gBAAgB;YAAE,OAAO,SAAS,CAAC;QACrE,OAAO;YACL,sBAAsB,EAAE,CAAC,CAAC,sBAAsB;YAChD,mBAAmB,EAAE,CAAC,CAAC,mBAAmB;SAC3C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,qBAAqB,CACzB,KAA6D;QAE7D,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE;YAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAClC,QAAQ,KAAK,EAAE,CAAC;gBACd,KAAK,KAAK;oBACR,OAAO;wBACL,CAAC,EAAE,CAAC;wBACJ,UAAU,EAAE,MAAM,CAAC,UAAU;wBAC7B,UAAU,EAAE,MAAM,CAAC,UAAU,GAAG,CAAC;qBAClC,CAAC;gBACJ,KAAK,QAAQ;oBACX,OAAO,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;gBAC1C,KAAK,QAAQ;oBACX,OAAO,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;gBAC1C,KAAK,UAAU;oBACb,OAAO,EAAE,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;gBAC5C,KAAK,WAAW;oBACd,OAAO,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;YAC/C,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,wEAAwE;IAExE;;;;;;OAMG;IACK,KAAK,CAAC,KAAK,CACjB,OAA4D;QAE5D,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAe,IAAI,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE;YACzE,IAAI,OAAO,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;gBACrD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;gBACtB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CACpB,mBAAmB,IAAI,CAAC,IAAI,CAAC,UAAU,kCAAkC,IAAI,CAAC,WAAW,gBAAgB,OAAO,CAAC,UAAU,GAAG,CAC/H,CAAC;gBACF,OAAO,OAAO,CAAC;YACjB,CAAC;YACD,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;yBAEqB;IACb,IAAI,CAAC,OAAiC;QAC5C,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;QAC5B,OAAO;YACL,CAAC,EAAE,CAAC;YACJ,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;YAC7C,UAAU,EAAE,IAAI,CAAC,WAAW;SAC7B,CAAC;IACJ,CAAC;IAED;;;4EAGwE;IAChE,YAAY,CAAC,IAAiC;QACpD,IAAI,eAAe,IAAI,IAAI;YAAE,OAAO,IAAI,CAAC;QACzC,OAAO,EAAE,aAAa,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,CAAC;IACxC,CAAC;IAEO,GAAG;QACT,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IACvC,CAAC;CACF"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (3.7) Per-server in-flight refresh coalescer.
|
|
3
|
+
*
|
|
4
|
+
* Without this, oh-my-pi (`commit 37fe5b93b`) hit a real bug: two tool
|
|
5
|
+
* calls fire HTTP requests simultaneously, both get 401 with the same
|
|
6
|
+
* just-expired token, both trigger `refreshAuthorization()`. The first
|
|
7
|
+
* succeeds and rotates the refresh_token; the second presents the now-
|
|
8
|
+
* rotated old refresh_token and gets `invalid_grant` — credential
|
|
9
|
+
* permanently broken, user must re-auth.
|
|
10
|
+
*
|
|
11
|
+
* Fix: keep one in-flight refresh promise per server; awaiters share it.
|
|
12
|
+
*
|
|
13
|
+
* **(codex Round-1 MUST-FIX #4)** Two bugs in the original sketch:
|
|
14
|
+
* 1. Sync throw in `refreshFn` would leave a permanently rejected
|
|
15
|
+
* promise in the map (set happened AFTER IIFE construction).
|
|
16
|
+
* Fix: `Promise.resolve().then(refreshFn)` so sync throws go
|
|
17
|
+
* THROUGH the promise.
|
|
18
|
+
* 2. `inFlight.delete` without identity check could delete a sibling
|
|
19
|
+
* cycle's promise on misordered cleanup. Fix: only delete when
|
|
20
|
+
* the slot still holds OUR promise.
|
|
21
|
+
*
|
|
22
|
+
* Also adds `abort(serverName)`: fires an AbortController so an
|
|
23
|
+
* in-flight refresh can be cancelled by `/mcp logout` (codex Round-1
|
|
24
|
+
* MUST-FIX #4). The cancelled fetch unwinds before saveTokens runs;
|
|
25
|
+
* the generation check (see `provider.ts`) is the belt-and-braces
|
|
26
|
+
* defence for the race where the fetch slipped past the abort signal.
|
|
27
|
+
*/
|
|
28
|
+
export declare class RefreshCoalescer<T> {
|
|
29
|
+
private readonly inFlight;
|
|
30
|
+
/**
|
|
31
|
+
* Coalesces concurrent refresh requests for the same `serverName`.
|
|
32
|
+
* The first call invokes `refreshFn(abortSignal)`; subsequent calls
|
|
33
|
+
* during the in-flight window receive the SAME promise. The slot
|
|
34
|
+
* clears after settlement so the next call kicks off a fresh refresh.
|
|
35
|
+
*/
|
|
36
|
+
refresh(serverName: string, refreshFn: (signal: AbortSignal) => Promise<T | undefined>): Promise<T | undefined>;
|
|
37
|
+
/** Fires the AbortController for an in-flight refresh. Idempotent —
|
|
38
|
+
* no-op when nothing's in flight. Used by `/mcp logout` to stop a
|
|
39
|
+
* refresh from writing tokens AFTER the credential was cleared. */
|
|
40
|
+
abort(serverName: string): void;
|
|
41
|
+
/** Test seam / `/mcp status` lookup. */
|
|
42
|
+
isInFlight(serverName: string): boolean;
|
|
43
|
+
/** Number of distinct servers currently refreshing. */
|
|
44
|
+
size(): number;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=refresh-coalescer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh-coalescer.d.ts","sourceRoot":"","sources":["../../../src/mcp/oauth/refresh-coalescer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAOH,qBAAa,gBAAgB,CAAC,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAE3D;;;;;OAKG;IACG,OAAO,CACX,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,GACzD,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IA2BzB;;wEAEoE;IACpE,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAI/B,wCAAwC;IACxC,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAIvC,uDAAuD;IACvD,IAAI,IAAI,MAAM;CAGf"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (3.7) Per-server in-flight refresh coalescer.
|
|
3
|
+
*
|
|
4
|
+
* Without this, oh-my-pi (`commit 37fe5b93b`) hit a real bug: two tool
|
|
5
|
+
* calls fire HTTP requests simultaneously, both get 401 with the same
|
|
6
|
+
* just-expired token, both trigger `refreshAuthorization()`. The first
|
|
7
|
+
* succeeds and rotates the refresh_token; the second presents the now-
|
|
8
|
+
* rotated old refresh_token and gets `invalid_grant` — credential
|
|
9
|
+
* permanently broken, user must re-auth.
|
|
10
|
+
*
|
|
11
|
+
* Fix: keep one in-flight refresh promise per server; awaiters share it.
|
|
12
|
+
*
|
|
13
|
+
* **(codex Round-1 MUST-FIX #4)** Two bugs in the original sketch:
|
|
14
|
+
* 1. Sync throw in `refreshFn` would leave a permanently rejected
|
|
15
|
+
* promise in the map (set happened AFTER IIFE construction).
|
|
16
|
+
* Fix: `Promise.resolve().then(refreshFn)` so sync throws go
|
|
17
|
+
* THROUGH the promise.
|
|
18
|
+
* 2. `inFlight.delete` without identity check could delete a sibling
|
|
19
|
+
* cycle's promise on misordered cleanup. Fix: only delete when
|
|
20
|
+
* the slot still holds OUR promise.
|
|
21
|
+
*
|
|
22
|
+
* Also adds `abort(serverName)`: fires an AbortController so an
|
|
23
|
+
* in-flight refresh can be cancelled by `/mcp logout` (codex Round-1
|
|
24
|
+
* MUST-FIX #4). The cancelled fetch unwinds before saveTokens runs;
|
|
25
|
+
* the generation check (see `provider.ts`) is the belt-and-braces
|
|
26
|
+
* defence for the race where the fetch slipped past the abort signal.
|
|
27
|
+
*/
|
|
28
|
+
export class RefreshCoalescer {
|
|
29
|
+
inFlight = new Map();
|
|
30
|
+
/**
|
|
31
|
+
* Coalesces concurrent refresh requests for the same `serverName`.
|
|
32
|
+
* The first call invokes `refreshFn(abortSignal)`; subsequent calls
|
|
33
|
+
* during the in-flight window receive the SAME promise. The slot
|
|
34
|
+
* clears after settlement so the next call kicks off a fresh refresh.
|
|
35
|
+
*/
|
|
36
|
+
async refresh(serverName, refreshFn) {
|
|
37
|
+
const existing = this.inFlight.get(serverName);
|
|
38
|
+
if (existing)
|
|
39
|
+
return existing.promise;
|
|
40
|
+
const abort = new AbortController();
|
|
41
|
+
// Promise.resolve().then so a synchronous throw inside refreshFn
|
|
42
|
+
// surfaces through the promise rather than escaping back to the
|
|
43
|
+
// caller of `refresh()`. Without this, sync-throw before the first
|
|
44
|
+
// `await` would bypass our error handling entirely.
|
|
45
|
+
let entry;
|
|
46
|
+
const promise = Promise.resolve()
|
|
47
|
+
.then(() => refreshFn(abort.signal))
|
|
48
|
+
.finally(() => {
|
|
49
|
+
// Identity-check delete: only remove the slot if WE are still
|
|
50
|
+
// there. Defends against pathological orderings where another
|
|
51
|
+
// cycle slotted in under the same key (e.g. logout → relogin
|
|
52
|
+
// → new refresh all in one tick).
|
|
53
|
+
const current = this.inFlight.get(serverName);
|
|
54
|
+
if (current && current === entry) {
|
|
55
|
+
this.inFlight.delete(serverName);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
entry = { promise, abort };
|
|
59
|
+
this.inFlight.set(serverName, entry);
|
|
60
|
+
return promise;
|
|
61
|
+
}
|
|
62
|
+
/** Fires the AbortController for an in-flight refresh. Idempotent —
|
|
63
|
+
* no-op when nothing's in flight. Used by `/mcp logout` to stop a
|
|
64
|
+
* refresh from writing tokens AFTER the credential was cleared. */
|
|
65
|
+
abort(serverName) {
|
|
66
|
+
this.inFlight.get(serverName)?.abort.abort();
|
|
67
|
+
}
|
|
68
|
+
/** Test seam / `/mcp status` lookup. */
|
|
69
|
+
isInFlight(serverName) {
|
|
70
|
+
return this.inFlight.has(serverName);
|
|
71
|
+
}
|
|
72
|
+
/** Number of distinct servers currently refreshing. */
|
|
73
|
+
size() {
|
|
74
|
+
return this.inFlight.size;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=refresh-coalescer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh-coalescer.js","sourceRoot":"","sources":["../../../src/mcp/oauth/refresh-coalescer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAOH,MAAM,OAAO,gBAAgB;IACV,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE3D;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CACX,UAAkB,EAClB,SAA0D;QAE1D,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC/C,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC,OAAO,CAAC;QAEtC,MAAM,KAAK,GAAG,IAAI,eAAe,EAAE,CAAC;QACpC,iEAAiE;QACjE,gEAAgE;QAChE,mEAAmE;QACnE,oDAAoD;QACpD,IAAI,KAAkB,CAAC;QACvB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE;aAC9B,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;aACnC,OAAO,CAAC,GAAG,EAAE;YACZ,8DAA8D;YAC9D,8DAA8D;YAC9D,6DAA6D;YAC7D,kCAAkC;YAClC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC9C,IAAI,OAAO,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;gBACjC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACnC,CAAC;QACH,CAAC,CAAC,CAAC;QACL,KAAK,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC3B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACrC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;wEAEoE;IACpE,KAAK,CAAC,UAAkB;QACtB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IAC/C,CAAC;IAED,wCAAwC;IACxC,UAAU,CAAC,UAAkB;QAC3B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACvC,CAAC;IAED,uDAAuD;IACvD,IAAI;QACF,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;CACF"}
|