fix(mcp): cancel manual OAuth waits on timeout
This commit is contained in:
@@ -133,6 +133,10 @@
|
||||
- Fixed Windows stdio MCP servers launched through PATH shims such as `codegraph.cmd` so bare commands like `codegraph` resolve via `PATHEXT` before spawn ([#2174](https://github.com/can1357/oh-my-pi/issues/2174)).
|
||||
- Fixed compiled-binary extensions failing to load `@oh-my-pi/pi-*` packages when `bun --compile` quietly dropped one of the extra entrypoints (observed on macOS arm64 release builds): the legacy-pi compat shim's package-root override branch returned the bunfs path without checking the target was present, so the rewrite emitted a `file://` URL to a missing module and the #1216 fallback (scoped to the throwing `getResolvedSpecifier` path) never ran. Override targets are now validated against the on-disk filesystem at module init, missing entries are dropped, and resolution falls through to canonical lookup so Bun resolves the import from the extension's own `node_modules` ([#2168](https://github.com/can1357/oh-my-pi/issues/2168)).
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed MCP OAuth flows accepting pasted redirect URLs or authorization codes through `/login` in headless environments ([#2122](https://github.com/can1357/oh-my-pi/issues/2122)).
|
||||
|
||||
## [15.10.8] - 2026-06-09
|
||||
|
||||
### Added
|
||||
|
||||
@@ -46,9 +46,14 @@ import { theme } from "../theme/theme";
|
||||
import type { InteractiveModeContext } from "../types";
|
||||
import { groupBySource, parseRemoveArgs, readScopeFlag, showCommandMessage } from "./command-controller-shared";
|
||||
|
||||
function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
|
||||
const MCP_MANUAL_INPUT_PROVIDER_ID = "mcp";
|
||||
const MCP_MANUAL_LOGIN_TIP = "Headless? Paste the redirect URL or code with /login <value>.";
|
||||
function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string, onTimeout?: () => void): Promise<T> {
|
||||
const { promise: timeoutPromise, reject } = Promise.withResolvers<T>();
|
||||
const timer = setTimeout(() => reject(new Error(message)), timeoutMs);
|
||||
const timer = setTimeout(() => {
|
||||
onTimeout?.();
|
||||
reject(new Error(message));
|
||||
}, timeoutMs);
|
||||
return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timer));
|
||||
}
|
||||
|
||||
@@ -592,6 +597,7 @@ export class MCPCommandController {
|
||||
const resolvedClientSecret = clientSecret.trim() || undefined;
|
||||
|
||||
const manualInput = this.ctx.oauthManualInput;
|
||||
const oauthTimeout = new AbortController();
|
||||
try {
|
||||
// Create OAuth flow
|
||||
const flow = new MCPOAuthFlow(
|
||||
@@ -621,9 +627,7 @@ export class MCPCommandController {
|
||||
0,
|
||||
),
|
||||
);
|
||||
block.addChild(
|
||||
new Text(theme.fg("muted", "Headless? Paste the redirect URL or code with /login <value>."), 1, 0),
|
||||
);
|
||||
block.addChild(new Text(theme.fg("muted", MCP_MANUAL_LOGIN_TIP), 1, 0));
|
||||
block.addChild(new Spacer(1));
|
||||
block.addChild(new Text(theme.fg("accent", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"), 1, 0));
|
||||
// Try to open browser automatically
|
||||
@@ -648,12 +652,18 @@ export class MCPCommandController {
|
||||
onProgress: (message: string) => {
|
||||
this.ctx.present([new Spacer(1), new Text(theme.fg("muted", message), 1, 0)]);
|
||||
},
|
||||
onManualCodeInput: () => manualInput.waitForInput("mcp"),
|
||||
onManualCodeInput: () => manualInput.waitForInput(MCP_MANUAL_INPUT_PROVIDER_ID),
|
||||
signal: oauthTimeout.signal,
|
||||
},
|
||||
);
|
||||
|
||||
// Execute OAuth flow with 5 minute timeout
|
||||
const credentials = await withTimeout(flow.login(), 5 * 60 * 1000, "OAuth flow timed out after 5 minutes");
|
||||
const credentials = await withTimeout(
|
||||
flow.login(),
|
||||
5 * 60 * 1000,
|
||||
"OAuth flow timed out after 5 minutes",
|
||||
() => oauthTimeout.abort("MCP OAuth flow timed out"),
|
||||
);
|
||||
|
||||
this.ctx.present([
|
||||
new Spacer(1),
|
||||
|
||||
Reference in New Issue
Block a user