fix(mcp): surface a short launch URL and log Windows opener failures for OAuth

Two independent defects broke /mcp reauth against S256-only providers on
Windows boxes whose PATH no longer references System32:

1. openPath spawned bare rundll32 and swallowed the
   `Executable not found in $PATH` throw with a bare `catch {}`, so the MCP
   controller's outer try/catch was dead and the transcript unconditionally
   claimed "Opening browser automatically...".
2. TUI#prepareLine silently truncates any composed row wider than the
   viewport. MCPAuthorizationLinkPrompt rendered `Copy URL: <full URL>` as a
   single ~271-column line whose trailing parameter is
   code_challenge_method=S256. On the reporter's 270-col terminal the cut
   landed inside that parameter, dropping the method while keeping
   code_challenge — which RFC 7636 §4.3 treats as plain PKCE, which Linear
   correctly rejects with "The plain PKCE method is not allowed. Use S256
   instead."

OAuthCallbackFlow now hosts a `GET /launch` route on the same loopback
callback server it already runs; the route 302-redirects to the pending
authorization URL and is advertised as `OAuthAuthInfo.launchUrl` — a
~30-char copy target no viewport can meaningfully truncate. The MCP OAuth
fallback, /login, setup wizard, auth-broker CLI, and login-dialog all
prefer the launch URL for the visible copy target, keep the full URL in
the OSC 8 hyperlink for click-through, and the MCP flow additionally
stages the copy target on the clipboard via OSC 52 (same pattern the
setup wizard uses).

openPath now resolves rundll32.exe through %SystemRoot%\System32 (with a
C:\Windows fallback when SystemRoot is unset) and logs both synchronous
spawn throws and non-zero exits via the shared logger, so silent
misconfigurations show up in ~/.omp/logs/omp.*.log. The dead try/catch
around openPath in the MCP controller is removed.

Fixes #4418
This commit is contained in:
roboomp
2026-07-03 08:19:14 +00:00
parent d0c1890a6c
commit 97c1d08cce
16 changed files with 418 additions and 51 deletions
@@ -1213,6 +1213,7 @@ export async function runRpcMode(
id: Snowflake.next() as string,
method: "open_url",
url: info.url,
launchUrl: info.launchUrl,
instructions: info.instructions,
} as RpcExtensionUIRequest);
},
@@ -370,7 +370,19 @@ export type RpcExtensionUIRequest =
}
| { type: "extension_ui_request"; id: string; method: "setTitle"; title: string }
| { type: "extension_ui_request"; id: string; method: "set_editor_text"; text: string }
| { type: "extension_ui_request"; id: string; method: "open_url"; url: string; instructions?: string };
| {
type: "extension_ui_request";
id: string;
method: "open_url";
url: string;
/**
* Short loopback URL that 302-redirects to {@link url}. When present,
* hosts SHOULD surface it as the copy target so terminal viewport
* truncation cannot corrupt OAuth query parameters on the full URL.
*/
launchUrl?: string;
instructions?: string;
};
// ============================================================================
// Host Tool Frames (bidirectional)