目的
v0.11.1 の Windows 版 openBrowser() が、URL に含まれる & 文字を cmd.exe が command separator として解釈するために、authorize URL のクエリパラメータ(state など)を途中で切り落とした状態でブラウザに渡している。結果、ユーザが browser 自動オープンから到達する /oauth/authorize リクエストは state を失い、Worker は invalid_request: state parameter is required (min 8 chars) で弾く。手動で URL 全文をコピペすると通るが、v0.11.1 が謳う「ブラウザ自動オープン」が Windows 上で機能していない状態。
実機検証 2026-04-20(Master)で再現確認済み。
前提(検証済)
再現手順
- Windows(Claude Desktop / Claude Code CLI)で v0.11.1 mcpb を使用
- 未認証状態で webhook tool を呼び出す
performOAuthFlow() が authorize URL を生成し openBrowser() を呼ぶ
- ブラウザのアドレスバーに
?client_id=... までが届き、&state=... 以降が落ちる
- Worker
/oauth/authorize handler が state 欠落で 400 JSON エラー
根本原因(コード)
mcp-server/server/index.js の現行実装(local-mcp/src/index.ts も同構造):
if (plat === "win32") {
command = "cmd.exe";
args = ["/c", "start", "", url]; // ← url に & が入ると cmd が分断
}
cmd.exe /c start "" <URL> の形で渡しているが、cmd.exe は argv を再組み立てして GetCommandLine ベースで解釈するため & が command separator として働く
start "" https://.../authorize?client_id=X までがブラウザ起動に使われ、&state=Y 以降は別コマンドとして評価(通常は失敗)
類似症状の past history
- v0.11.0 実機検証時に Master が観察した「
?user_code=... が ?skip_account_picker=true に変わる」現象も、このバグ由来の可能性が高い(GitHub 側リダイレクトと重なって隠れていた)
- v0.10.x 系の
openBrowser() も同じ構造だったため、web OAuth flow + authorization_code で動いていた時代から潜在バグが存在していた。ただし authorization_code flow では redirect_uri に localhost が使われるため影響が違う形で出ていた可能性がある
制約
- v0.11.2 は patch リリース(v0.11.1 の hotfix)
- 挙動変更なし(クエリ付き URL を「正しく」Windows ブラウザに渡すだけ)
- macOS (
open) と Linux (xdg-open) は & を普通の引数として受け取るので現行実装で正しく動く(修正対象は Windows のみ)
- ネイティブ依存を増やさない(Node.js 標準
node:child_process のみで解決)
- mcp-server と local-mcp の両方を揃える
実装方針
推奨アプローチ: URL 全体をダブルクォートで括る
Windows では cmd.exe は "..." で括った文字列内の & をリテラル扱いする(command separator として解釈しない)。Node.js spawn は Windows で cmd.exe にコマンドラインを渡す際、各 arg を必要に応じて quote するが、URL はデフォルトで quote されない(& は quote 必要文字として Node 側に認識されていないため)。
明示的に URL を " で囲って渡す:
if (plat === "win32") {
command = "cmd.exe";
args = ["/c", "start", "", `"${url}"`];
}
ただし Node.js の Windows spawn は args 内の既存 " を自動エスケープするため、この書き方が素直に働くか要検証。動かない場合は shell: true を使って start "" "URL" を文字列としてコマンド渡し するのが確実:
if (plat === "win32") {
command = `start "" "${url}"`;
options.shell = true;
}
代替アプローチ: PowerShell 経由で Start-Process
if (plat === "win32") {
command = "powershell.exe";
args = ["-NoProfile", "-Command", "Start-Process", url];
}
PowerShell は argv を直接扱うので & エスケープ問題を回避できる。ただし PowerShell 起動オーバヘッドが cmd より大きい。
推奨順序
- まず shell: true + quoted URL を試す(最小の差分)
- 動かない場合は PowerShell フォールバック
既存コメント("start is a cmd.exe builtin, not a standalone executable. The empty "" argument is the window title placeholder expected by start when the URL is quoted.")に URL 自身の quote も明示する形でコメント更新。
テスト追加
mcp-server/test/ に unit-level テストを追加:
- URL に
& を含むケースで openBrowser が想定の引数列を組むこと(spawn をモック)
- platform 別(win32 / darwin / linux)で分岐が正しいこと
実機テストは Master の Windows 環境で Master が行う。
target files
mcp-server/server/index.js — openBrowser() Windows 分岐
local-mcp/src/index.ts — 同上(TypeScript)
mcp-server/test/open-browser.test.mjs — 新規テスト
docs/installation.ja.md / docs/installation.md — v0.11.2 の変更点として短い note を追加
version bump:
mcp-server/manifest.json 0.11.1 → 0.11.2
mcp-server/package.json 0.11.1 → 0.11.2
mcp-server/server.json 0.11.1 → 0.11.2(2 箇所)
関連
目的
v0.11.1 の Windows 版
openBrowser()が、URL に含まれる&文字を cmd.exe が command separator として解釈するために、authorize URL のクエリパラメータ(state など)を途中で切り落とした状態でブラウザに渡している。結果、ユーザが browser 自動オープンから到達する/oauth/authorizeリクエストは state を失い、Worker はinvalid_request: state parameter is required (min 8 chars)で弾く。手動で URL 全文をコピペすると通るが、v0.11.1 が謳う「ブラウザ自動オープン」が Windows 上で機能していない状態。実機検証 2026-04-20(Master)で再現確認済み。
前提(検証済)
再現手順
performOAuthFlow()が authorize URL を生成しopenBrowser()を呼ぶ?client_id=...までが届き、&state=...以降が落ちる/oauth/authorizehandler が state 欠落で 400 JSON エラー根本原因(コード)
mcp-server/server/index.jsの現行実装(local-mcp/src/index.tsも同構造):cmd.exe /c start "" <URL>の形で渡しているが、cmd.exe は argv を再組み立てしてGetCommandLineベースで解釈するため&が command separator として働くstart "" https://.../authorize?client_id=Xまでがブラウザ起動に使われ、&state=Y以降は別コマンドとして評価(通常は失敗)類似症状の past history
?user_code=...が?skip_account_picker=trueに変わる」現象も、このバグ由来の可能性が高い(GitHub 側リダイレクトと重なって隠れていた)openBrowser()も同じ構造だったため、web OAuth flow + authorization_code で動いていた時代から潜在バグが存在していた。ただし authorization_code flow では redirect_uri に localhost が使われるため影響が違う形で出ていた可能性がある制約
open) と Linux (xdg-open) は&を普通の引数として受け取るので現行実装で正しく動く(修正対象は Windows のみ)node:child_processのみで解決)実装方針
推奨アプローチ: URL 全体をダブルクォートで括る
Windows では cmd.exe は
"..."で括った文字列内の&をリテラル扱いする(command separator として解釈しない)。Node.jsspawnは Windows でcmd.exeにコマンドラインを渡す際、各 arg を必要に応じて quote するが、URL はデフォルトで quote されない(&は quote 必要文字として Node 側に認識されていないため)。明示的に URL を
"で囲って渡す:ただし Node.js の Windows spawn は args 内の既存
"を自動エスケープするため、この書き方が素直に働くか要検証。動かない場合はshell: trueを使ってstart "" "URL"を文字列としてコマンド渡し するのが確実:代替アプローチ: PowerShell 経由で Start-Process
PowerShell は argv を直接扱うので
&エスケープ問題を回避できる。ただし PowerShell 起動オーバヘッドが cmd より大きい。推奨順序
既存コメント("
startis a cmd.exe builtin, not a standalone executable. The empty "" argument is the window title placeholder expected bystartwhen the URL is quoted.")に URL 自身の quote も明示する形でコメント更新。テスト追加
mcp-server/test/に unit-level テストを追加:&を含むケースでopenBrowserが想定の引数列を組むこと(spawn をモック)実機テストは Master の Windows 環境で Master が行う。
target files
mcp-server/server/index.js—openBrowser()Windows 分岐local-mcp/src/index.ts— 同上(TypeScript)mcp-server/test/open-browser.test.mjs— 新規テストdocs/installation.ja.md/docs/installation.md— v0.11.2 の変更点として短い note を追加version bump:
mcp-server/manifest.json0.11.1 → 0.11.2mcp-server/package.json0.11.1 → 0.11.2mcp-server/server.json0.11.1 → 0.11.2(2 箇所)関連