Skip to content

fix(mcp): open the login and consent pages in the browser on Windows - #85

Merged
travist merged 1 commit into
mainfrom
fix/windows-browser-launch
Oct 2, 2026
Merged

travist merged 1 commit into
mainfrom
fix/windows-browser-launch

Conversation

@travist

@travist travist commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Problem

Reported against @formio/mcp 0.13.0 on Windows 11 / Node 22 / Claude Code: when a tool call needs portal login, an empty console window opens with the login URL as its title, no browser opens, and the call waits out the login timeout with no page in front of the user.

Both local pages — the portal login (auth.ts) and the revisions consent page (revisions/browser-prompts.ts) — were opened with:

exec(`${openCmd} "${url}"`); // openCmd = 'start' on win32

exec runs that through cmd.exe /c, and start reads its first quoted argument as the window title, so start "http://127.0.0.1:53555/" opens a titled console and launches nothing.

Fix

A new browser-launch.ts that both pages use:

  • browserLaunchCommand(url, platform) — pure; returns the program and its argument list: open <url> on macOS, xdg-open <url> on Linux, rundll32.exe url.dll,FileProtocolHandler <url> on Windows (the same approach as GitHub's cli/browser).
  • openInBrowser(url, onError) — runs it with execFile (no shell, windowsHide: true), so the URL is one argument on every platform and nothing in it is parsed by cmd or sh.

Windows avoids start entirely: it is a cmd built-in, so it needs a shell, and its title slot is the bug.

The consent page previously ignored a failed launch. It now writes the failure and its URL to stderr, as the login page already did. README's "Headless environments" line, which named start, is updated.

Tests

  • browser-launch.test.ts — command per platform, Windows never uses start, execFile called with the URL as its own argument, failure reported / success not reported.
  • browser-prompts-launch.test.ts — consent page launches through the shell-free launcher and names its URL on stderr when the launch fails.
  • Existing auth tests now mock execFile instead of exec.

pnpm test, pnpm lint, pnpm format and pnpm check:releases pass. Not yet exercised on a real Windows machine.

Known limits / not in this PR

  • rundll32 exits 0 even when no handler is registered, so on Windows a failed launch usually produces no "Could not open a browser" line. The login URL is still always on stderr and in the timeout error.
  • Cutting the wait short on launch failure is not done: the 2-minute "still running" notice is the client's, our login timeout is 15 minutes, and the spec keeps the login server open after a failed launch so the URL can be opened by hand.

🤖 Generated with Claude Code

Both local pages were launched with exec(`start "<url>"`). On Windows,
cmd's start reads its first quoted argument as a window title, so it
opened an empty console titled with the URL and launched no browser;
the tool call then waited out the login timeout with nothing in front
of the user.

Both pages now go through one launcher, browser-launch.ts, that runs no
shell and hands the URL to the opener as its own argument: open on
macOS, xdg-open on Linux, and rundll32 url.dll,FileProtocolHandler on
Windows. The consent page also now reports a failed launch on stderr
with its URL, as the login page already did, instead of ignoring it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@travist
travist merged commit 757ee8a into main Oct 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant