-
Notifications
You must be signed in to change notification settings - Fork 1.2k
fix: settle cancelled MCP OAuth callbacks #2899
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@moonshot-ai/kimi-code": patch | ||
| --- | ||
|
|
||
| Fix MCP OAuth cancellation leaving an in-flight authorization waiting for its callback timeout. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
packages/agent-core/test/mcp/oauth-callback-server.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| /** | ||
| * Scenario: lifecycle completion for the localhost MCP OAuth callback listener. | ||
| * Responsibilities: closing rejects pending waits, successful callbacks survive cleanup, and | ||
| * service cancellation settles in-flight completion. The listener and service are real; only the | ||
| * external MCP SDK authorization boundary is mocked. | ||
| * Run: pnpm --filter @moonshot-ai/agent-core exec vitest run test/mcp/oauth-callback-server.test.ts | ||
| */ | ||
|
|
||
| import { mkdtemp, rm } from 'node:fs/promises'; | ||
| import { tmpdir } from 'node:os'; | ||
| import { join } from 'pathe'; | ||
|
|
||
| import { auth } from '@modelcontextprotocol/sdk/client/auth.js'; | ||
| import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; | ||
|
|
||
| import { | ||
| type BeginAuthorizationResult, | ||
| type CallbackServer, | ||
| JsonFileStore, | ||
| McpOAuthService, | ||
| OAuthCallbackClosedError, | ||
| startCallbackServer, | ||
| } from '../../src/mcp/oauth'; | ||
|
|
||
| vi.mock('@modelcontextprotocol/sdk/client/auth.js', async (importOriginal) => ({ | ||
| ...(await importOriginal<typeof import('@modelcontextprotocol/sdk/client/auth.js')>()), | ||
| auth: vi.fn(), | ||
| })); | ||
|
|
||
| describe('OAuth callback server', () => { | ||
| let server: CallbackServer | undefined; | ||
|
|
||
| afterEach(async () => { | ||
| await server?.close(); | ||
| server = undefined; | ||
| }); | ||
|
|
||
| it('rejects a pending callback wait with a closed error when explicitly closed', async () => { | ||
| server = await startCallbackServer(); | ||
| const pending = server.waitForCode({ timeoutMs: 60_000 }); | ||
| const rejection = expect(pending).rejects.toBeInstanceOf(OAuthCallbackClosedError); | ||
|
|
||
| await server.close(); | ||
|
|
||
| await rejection; | ||
| }); | ||
|
|
||
| it('delivers the callback payload when success closes the listener', async () => { | ||
| server = await startCallbackServer(); | ||
| const pending = server.waitForCode({ timeoutMs: 60_000 }); | ||
|
|
||
| await fetch(`${server.redirectUri}?code=code-1&state=state-1`); | ||
|
|
||
| await expect(pending).resolves.toEqual({ code: 'code-1', state: 'state-1' }); | ||
| }); | ||
| }); | ||
|
|
||
| describe('McpOAuthService cancellation', () => { | ||
| let dir: string; | ||
| let flow: BeginAuthorizationResult | undefined; | ||
|
|
||
| beforeEach(async () => { | ||
| dir = await mkdtemp(join(tmpdir(), 'kimi-mcp-oauth-cancel-')); | ||
| vi.mocked(auth).mockImplementation(async (provider) => { | ||
| await provider.redirectToAuthorization(new URL('https://auth.example.test/authorize')); | ||
| return 'REDIRECT'; | ||
| }); | ||
| }); | ||
|
|
||
| afterEach(async () => { | ||
| await flow?.cancel(); | ||
| flow = undefined; | ||
| await rm(dir, { recursive: true, force: true }); | ||
| vi.clearAllMocks(); | ||
| }); | ||
|
|
||
| it('rejects an in-flight completion when the authorization flow is cancelled', async () => { | ||
| const service = new McpOAuthService({ store: new JsonFileStore(dir) }); | ||
| flow = await service.beginAuthorization('example', 'https://mcp.example.test/rpc'); | ||
| const completion = flow.complete({ timeoutMs: 60_000 }); | ||
| const rejection = expect(completion).rejects.toThrow('OAuth callback listener closed'); | ||
|
|
||
| await flow.cancel(); | ||
|
|
||
| await rejection; | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This release note claims the Kimi Code MCP OAuth cancellation hang is fixed, but the patch only changes the legacy
packages/agent-corecallback server; the default Kimi Code/kap-server path usespackages/agent-core-v2/src/mcpCore/oauth/service.tsand itsmcpCore/oauth/callback-server.tsstill hasclose()only closing the listener without rejecting a pendingwaitForCode(). Users on the v2 path can still cancel an auth flow and wait until the callback timeout, so please port the same outcome/closed-error handling to agent-core-v2 or narrow this changeset to the legacy path.AGENTS.md reference: AGENTS.md:L21-L22
Useful? React with 👍 / 👎.