Skip to content

feat(oauth): device flow UX — auto-browser + auth-required tool response (mcp-server, local-mcp)#208

Merged
liplus-lin-lay merged 1 commit into
mainfrom
207-device-flow-ux
Apr 20, 2026
Merged

feat(oauth): device flow UX — auto-browser + auth-required tool response (mcp-server, local-mcp)#208
liplus-lin-lay merged 1 commit into
mainfrom
207-device-flow-ux

Conversation

@liplus-lin-lay
Copy link
Copy Markdown
Member

Closes #207

要約

v0.11.0 で導入した device authorization grant の初回認証 UX を修復する。

背景

v0.11.0 では user_codeverification_uri を stderr にのみ書き出し、その後 600 秒の polling を同期でブロックしていた。Claude Code / Claude Desktop は MCP サーバの stderr をチャット UI に露出しないため、ユーザからは「ツールが何も返さずに 10 分後にタイムアウトするだけ」の状態に見えていた。

変更内容

mcp-server/server/index.js と local-mcp/src/index.ts(同じ設計で揃えた)

  • performOAuthFlow() を 2 段階に分割:
    • phase 1/oauth/device_authorization で device code を取得し、_pendingDeviceAuth に格納。verification_uri_complete 優先でブラウザ自動オープン、stderr ログ出力。
    • phase 2/oauth/token の polling をバックグラウンドで継続(_deviceFlowLock で多重実行を直列化)。
  • tool handler 専用の getAccessTokenForToolCall() を追加:
    • トークン有効 → 通常処理
    • リフレッシュ可能 → 同期リフレッシュ(1 round-trip)
    • どちらも不可 → phase 1 完了後に AuthRequiredError を throw、ハンドラが isError: true の構造化応答を返す。2 回目以降の tool call も承認完了前なら同じメッセージ(ポーリングは 1 本を共有)。
  • ブラウザ自動オープンは child_process.spawn で platform 別に cmd /c start / open / xdg-open を呼ぶ。失敗は stderr 警告のみで fatal にしない。
  • WebSocket 側の connectWebSocket() は従来通り blocking getAccessToken() を使う(UI を止めないバックグラウンド接続のため)。

docs

  • docs/0-requirements.md / docs/0-requirements.ja.md:
    • F7.9 — ブラウザ自動オープンの仕様
    • F7.10 — 未承認時の isError: true 応答仕様と polling serialize
  • docs/installation.md / docs/installation.ja.md:
    • 初回認証節を v0.11.1 の体験(ブラウザ自動オープン + 即時ツール応答)に合わせて書き換え
    • ブラウザ自動オープン失敗時の手動フォールバック手順を追記

version bump

  • mcp-server/manifest.json: 1.0.00.11.1
  • mcp-server/package.json: 0.8.20.11.1
  • mcp-server/server.json: 0.8.20.11.1(2 箇所)

tests

  • mcp-server/test/auth-required.test.mjs を新規追加(5 件、black-box 契約テスト):
    • verification_uri_complete 優先で表示される
    • 不在時は verification_uriuser_code のみ
    • expires_at 不在時は残り時間ヒントを省略
    • retry 指示文を含む
    • isError: true が立つ
  • 既存 migration.test.mjs は 5 件 pass のまま無回帰。

影響範囲

  • ローカルブリッジ(mcp-server / local-mcp)のみ。Worker 側コードは無変更。
  • 既存トークン保持ユーザは影響なし(通常処理パスは従来通り)。
  • 初回認証または device code 期限切れユーザのみ、新 UX に乗る。

検証

  • node --check server/index.js: OK
  • node --test mcp-server/test/*.test.mjs: 10/10 pass
  • npx tsc --noEmit(local-mcp): 型エラーなし

実機検証は merge 後の CI/CD 通過と Claude Code / Desktop での初回接続挙動で確認する。

…ce flow

v0.11.0 で導入した device authorization grant の初回認証 UX を修復する。
stderr へのログ出力のみでは Claude Code / Desktop のチャット UI にユーザが
気付けず、ツール呼び出しが 600 秒沈黙したまま expired_token で終わる状態に
なっていた。

主な変更:
- performOAuthFlow() を phase 1(device code 取得)と phase 2(polling)に
  分割し、polling をバックグラウンドに逃がした。
- tool handler は新設した getAccessTokenForToolCall() を経由し、device flow
  未完了時は AuthRequiredError 経由で isError=true + user_code / verification
  URL を含む構造化応答を即座に返す。2 回目以降の同一ツール呼び出しも同じ
  メッセージを返し(polling は 1 本を共有)、完了すれば次の呼び出しで通常
  処理に戻る。
- device authorization 応答受信直後に child_process.spawn で platform 既定の
  ブラウザを起動する(Windows: cmd /c start、macOS: open、Linux: xdg-open)。
  失敗は non-fatal。
- mcp-server/server/index.js と local-mcp/src/index.ts を同じ設計で揃えた。
- manifest.json / package.json / server.json を 0.11.1 に version bump。
- docs/0-requirements.{md,ja.md} に F7.9(自動オープン)/ F7.10(auth-required
  応答)を追加。docs/installation.{md,ja.md} の初回認証節を書き換えて
  v0.11.1 の体験を説明。
- mcp-server/test/auth-required.test.mjs で応答フォーマットの契約テストを
  追加(verification_uri_complete 優先 / user_code 必須 / isError=true /
  retry 指示 / expires_at の有無)。

Refs #207
@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
github-webhook-mcp 4cf120f Apr 20 2026, 01:47 PM

@liplus-lin-lay liplus-lin-lay self-assigned this Apr 20, 2026
Copy link
Copy Markdown
Member Author

@liplus-lin-lay liplus-lin-lay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-review: PASS

Acceptance check (issue #207)

  • A-1 browser auto-openopenBrowser() spawns platform browser (cmd /c start / open / xdg-open); failure is non-fatal with stderr warning. Present in both mcp-server/server/index.js and local-mcp/src/index.ts.
  • A-2 auth-required tool responseAuthRequiredError + formatAuthRequiredResponse() returns isError: true with user_code, verification_uri_complete, expiry hint, retry instruction. Polling serialized by _deviceFlowLock so repeat calls share one background flow.
  • docs — F7.9 + F7.10 added to 0-requirements.{md,ja.md}; installation docs rewritten for v0.11.1 first-auth experience.
  • version bump — 0.11.1 across manifest.json / package.json / server.json.
  • testsauth-required.test.mjs (5) + existing migration.test.mjs (5) = 10/10 pass. CI all green.

Scope deviations

None.

Later / non-blocking

Browser-open failure path still produces the "browser should have opened" phrasing in the tool response. A future pass could thread a success flag into formatAuthRequiredResponse() to adjust wording conditionally. Not a merge blocker.

Next

Mode = auto: no external human review required. Proceeding to merge.

@liplus-lin-lay liplus-lin-lay merged commit 74b928c into main Apr 20, 2026
3 checks passed
@liplus-lin-lay liplus-lin-lay deleted the 207-device-flow-ux branch April 20, 2026 13:49
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.

feat(oauth): surface device flow URL via auto-browser + auth-required tool response

1 participant