Skip to content

feat(ai-engine,frontend): ADR-0011 PR-E3b — OAuth Route Handler + AuthRequestCard 改修#32

Merged
shomatan merged 1 commit into
mainfrom
feat/oauth-route-and-ui-pr-e3b
May 2, 2026
Merged

feat(ai-engine,frontend): ADR-0011 PR-E3b — OAuth Route Handler + AuthRequestCard 改修#32
shomatan merged 1 commit into
mainfrom
feat/oauth-route-and-ui-pr-e3b

Conversation

@shomatan
Copy link
Copy Markdown
Contributor

@shomatan shomatan commented May 2, 2026

Summary

ADR-0011 PR-E3b: 外部 MCP の OAuth 2.1 フローを Tally の Route Handler 経由で開始/監視/中止できるようにし、AuthRequestCard の paste UX を廃止して 1 ステップ化する。PR-E3a で merge 済みの OAuthFlowOrchestrator を Route Handler でラップし、UI 側は新エンドポイントを polling する。

  • 新規 Route Handler /api/projects/[id]/mcp/[mcpServerId]/oauth (POST=start / GET=status / DELETE=clear)
  • AuthRequestCard を完全リライト: paste UI 廃止 → 「認証ボタン → POST → window.open → polling → 完了」の 1 ステップ
  • codex Major 対応: orchestrator の flow key を composite (projectId:mcpServerId) 化 + AuthRequestCard マウント時の rehydrate effect 追加

ADR-0011 ロードマップ進捗

主な変更点

Route Handler (新規)

Method path 役割
POST /api/projects/[id]/mcp/[mcpServerId]/oauth orchestrator を start し authorizationUrl を返す
GET 同上 現在の OAuthFlowStatus (未開始は 404)
DELETE 同上 進行中フローを clear (UI の「やり直す」)

エラー分類:

  • project / mcpServer 不在 → 404
  • mcpServer.oauth 未設定 → 400
  • 二重 start → 409 (固定文言、内部 err.message は server log に分離)
  • その他 → 500

AuthRequestCard 改修

  • paste UI 削除 (callback URL を Tally の loopback が直接受けるため)
  • 内部 cardState (idle | starting | pending | completed | failed) で UI 駆動
  • block.status / block.authUrl は無視 (PR-E4 で block 自体が消える過渡期 prop)
  • polling は 2s setTimeout self-rescheduling
  • 失敗時は「やり直す」→ DELETE → idle

codex セカンドオピニオン Major 対応

  1. flow key composite 化: orchestrator の flows Map のキーが mcpServerId のみだと、複数 project が同名 'atlassian' を持つときにクロス汚染する。projectId:mcpServerId の composite key に変更。getOAuthFlowStatus / awaitOAuthFlowSettled / clearOAuthFlow の signature も (projectId, mcpServerId) に変更。
  2. rehydrate effect: AuthRequestCard マウント時に GET で orchestrator 状態を取得し cardState に反映。チャット再表示で pending/completed の状態が消えて 409 で詰まるバグを防ぐ。

Test plan

  • ai-engine 247 tests pass (新規: 別 project の同名 mcpServerId は flow が独立)
  • frontend 281 tests pass (新規 Route Handler 9 ケース + AuthRequestCard 7 ケース)
  • typecheck (ai-engine + frontend) clean
  • biome lint clean
  • 実環境で Atlassian OAuth フローを通せること (PR-E4 マージ後の手動 QA)

既知の限界 (PR-E4 で解消)

  • chat-runner は依然として mcp__<id>__authenticate AI tool を発行する。AuthRequestCard は新 Route Handler を使うので、SDK 側の loopback は orphan になる (実害は無いが冗長)。
  • WS event mcp_oauth_completed への移行は未実施。本 PR では HTTP polling で完了を検知。

Summary by CodeRabbit

リリースノート

  • 新機能

    • OAuth認証フローが改善されました。認証ボタンをクリックするだけで新しいウィンドウで認可画面が開きます。
    • 認証に失敗した場合、リトライボタンで再度試行できるようになりました。
    • 複数プロジェクト間での認証状態の分離が強化されました。
  • テスト

    • OAuth関連のエンドツーエンドテストを追加しました。

…hRequestCard 改修

ADR-0011 PR-E3b: 外部 MCP の OAuth 2.1 フローを Tally Route Handler 経由で
開始/監視/中止できるようにし、AuthRequestCard の paste UX を廃止して 1 ステップ化。

新規 Route Handler:
- POST   /api/projects/[id]/mcp/[mcpServerId]/oauth → orchestrator を start し authorizationUrl を返す
- GET    /api/projects/[id]/mcp/[mcpServerId]/oauth → 現在の OAuthFlowStatus (未開始は 404)
- DELETE /api/projects/[id]/mcp/[mcpServerId]/oauth → 進行中フローを clear (UI の「やり直す」)
- error 分類: project/mcpServer 不在 → 404、oauth 未設定 → 400、二重 start → 409 (固定文言)、その他 → 500

AuthRequestCard 全面リライト:
- 旧 paste UI 削除。callback URL を Tally の loopback が直接受けるため。
- ボタン → POST → window.open → 内部 cardState で polling (2s setTimeout self-rescheduling)。
- block.status / block.authUrl は無視 (PR-E4 で block 自体が消える過渡期 prop)。
- 失敗時は「やり直す」→ DELETE → idle。

codex セカンドオピニオン Major 対応:
- orchestrator の flow key を composite (projectId:mcpServerId) に変更。複数 project が
  同名 mcpServerId='atlassian' を持っても flow がクロス汚染しない。getOAuthFlowStatus /
  awaitOAuthFlowSettled / clearOAuthFlow の signature を (projectId, mcpServerId) に変更。
- AuthRequestCard マウント時に GET で orchestrator 状態を rehydrate する effect を追加。
  リマウント時に pending/completed が UI から消えて 409 で詰まるバグを防ぐ。

ai-engine: oauth API を package root から re-export (frontend Route Handler が import するため)。

テスト追加:
- ai-engine: 別 project の同名 mcpServerId は flow が独立 (cross-project isolation)
- frontend: Route Handler の POST/GET/DELETE 全 9 ケース
- frontend: AuthRequestCard の hydrate / completed 表示 / 認証 / 失敗 / やり直す / projectId 未設定 disable
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

📝 Walkthrough

Walkthrough

OAuthフロー管理を複合キー(projectId + mcpServerId)で実装し、プロジェクト間の状態衝突を防止。ai-engineパッケージがOAuth関連ユーティリティを再エクスポートし、フロントエンドに新規ルートハンドラーとサーバー駆動型UIフローを追加。

Changes

OAuth フロー オーケストレーション統合

Layer / File(s) Summary
データ形状
packages/ai-engine/src/oauth/oauth-flow-orchestrator.ts
StartOAuthFlowInputprojectId: stringフィールドを追加。複合キーmakeFlowKey(projectId, mcpServerId)を導入。
コア実装
packages/ai-engine/src/oauth/oauth-flow-orchestrator.ts
startOAuthFlowが複合キーで保留スロットを予約・検証。getOAuthFlowStatusawaitOAuthFlowSettledclearOAuthFlow(projectId, mcpServerId)パラメータを受け取り、複合キーで状態を検索。
パッケージ エクスポート
packages/ai-engine/src/index.ts
./oauthからOAuthフロー関数(startOAuthFlowclearOAuthFlowawaitOAuthFlowSettledgetOAuthFlowStatus)と型(OAuthFlowStatusStartOAuthFlowInputStartOAuthFlowResult)を再エクスポート。
ルート ハンドラー
packages/frontend/src/app/api/projects/[id]/mcp/[mcpServerId]/oauth/route.ts
Next.jsエンドポイントPOST/GET/DELETE /api/projects/[id]/mcp/[mcpServerId]/oauthを実装。resolveTargetでプロジェクトメタとOAuth設定を検証。POSTがフロー開始、GETが状態取得、DELETEがフロー取消。
フロントエンド UI
packages/frontend/src/components/chat/auth-request-card.tsx
ユーザーペースト式コールバックフローから、サーバー駆動型OAuth フローに再実装。onAuthClickPOSTを実行して認可URLを取得し、新タブで開く。マウント時にGETで状態をリハイドレート。onRetryClickDELETEを実行してリセット。
テスト更新
packages/ai-engine/src/oauth/oauth-flow-orchestrator.test.ts, packages/frontend/src/app/api/projects/[id]/mcp/[mcpServerId]/oauth/route.test.ts, packages/frontend/src/components/chat/auth-request-card.test.tsx
TEST_PID定数を導入。すべてのフロー操作呼び出しで複合キーを使用。ルートテストではファイルシステムバックアップのプロジェクト作成・MCP設定を検証。UIテストはfetchモック化とAPI駆動フローアサーション。

Sequence Diagram

sequenceDiagram
    participant User as User<br>(Frontend)
    participant Card as AuthRequestCard<br>Component
    participant Route as OAuth Route<br>Handler
    participant Orchestrator as OAuthFlow<br>Orchestrator
    participant Storage as Flow State<br>Storage

    rect rgb(200, 150, 255, 0.5)
    Note over User,Storage: OAuth Flow Initiation (Composite Key)
    User->>Card: Click Auth Button
    Card->>Route: POST /api/projects/{id}/mcp/{mcpServerId}/oauth
    Route->>Orchestrator: startOAuthFlow({projectId, mcpServerId, ...})
    Orchestrator->>Storage: Reserve pending entry<br>via flowKey=(projectId, mcpServerId)
    Orchestrator-->>Route: {authorizationUrl}
    Route-->>Card: {authorizationUrl}
    Card->>User: window.open(authorizationUrl)<br>Transition to "pending" UI
    end

    rect rgb(150, 200, 255, 0.5)
    Note over User,Storage: Polling & Rehydration
    Card->>Route: GET /api/projects/{id}/mcp/{mcpServerId}/oauth
    Route->>Orchestrator: getOAuthFlowStatus(projectId, mcpServerId)
    Orchestrator->>Storage: Lookup flowKey state
    Storage-->>Orchestrator: {status, ...}
    Orchestrator-->>Route: {status: "pending"|"completed"}
    Route-->>Card: API Response
    Card->>Card: Re-render based on status
    end

    rect rgb(255, 200, 150, 0.5)
    Note over User,Storage: Retry / Cleanup
    User->>Card: Click Retry Button
    Card->>Route: DELETE /api/projects/{id}/mcp/{mcpServerId}/oauth
    Route->>Orchestrator: clearOAuthFlow(projectId, mcpServerId)
    Orchestrator->>Storage: Delete flowKey entry<br>Close callback server
    Storage-->>Orchestrator: Cleared
    Orchestrator-->>Route: {ok: true}
    Route-->>Card: Success
    Card->>Card: Reset to "idle" state
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: introducing an OAuth Route Handler and refactoring AuthRequestCard per ADR-0011 and PR-E3b, with specific affected packages (ai-engine, frontend).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/oauth-route-and-ui-pr-e3b

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
packages/ai-engine/src/oauth/oauth-flow-orchestrator.ts (1)

156-158: 💤 Low value

コメント内の変数名が古い参照のまま

Line 157 のコメントで flows.get(mcpServerId) と書かれていますが、実際には flowKey を使用しています。コメントの整合性のため更新をお勧めします。

📝 コメント修正案
   // bg promise を IIFE で生成。IIFE 内部から自分自身の promise 変数を直接参照すると
-  // tsc の definite-assignment 解析でエラーになるため、`flows.get(mcpServerId)?.promise`
+  // tsc の definite-assignment 解析でエラーになるため、`flows.get(flowKey)?.promise`
   // 経由で取り出して再 set する。これは外側で flows.set した後の値を読むので安全。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ai-engine/src/oauth/oauth-flow-orchestrator.ts` around lines 156 -
158, Update the outdated variable name in the explanatory comment inside
oauth-flow-orchestrator.ts: replace the reference to flows.get(mcpServerId) with
flows.get(flowKey)?.promise (or otherwise reference flowKey consistently) in the
comment that explains the bg promise IIFE and definite-assignment behavior so
the comment matches the actual code using flowKey and flows.set(...).
packages/frontend/src/components/chat/auth-request-card.test.tsx (1)

44-71: ⚖️ Poor tradeoff

polling 中の状態遷移テストの追加を検討

現在のテストは POST 成功後の即座の状態遷移をカバーしていますが、pending 状態での polling による completed/failed への遷移はカバーされていません。polling ロジックの回帰を防ぐため、以下のようなテストを追加することを検討してください:

  • pending 状態で GET が completed を返したら "認証済" に遷移
  • pending 状態で GET が failed を返したら "失敗" に遷移 + failureMessage 表示
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/src/components/chat/auth-request-card.test.tsx` around
lines 44 - 71, The test suite for AuthRequestCard lacks coverage for the polling
transitions from pending to completed/failed; add tests that render
<AuthRequestCard block={pendingBlock} />, stub global fetch (the existing
fetchMock/EXPECTED_BASE_URL logic) to first respond to the POST with the
authorizationUrl and then on subsequent GET polls return JSON with status:
"completed" (and separately a case with status: "failed" plus failureMessage),
advance timers or wait for the polling interval so the component executes its
polling logic, and assert that the UI transitions to show "認証済" for completed
and "失敗" plus the failureMessage for failed; keep using vi.stubGlobal('fetch',
...) and waitFor/assertions similar to the POST test to verify state changes and
that the callback textbox is removed.
packages/ai-engine/src/oauth/oauth-flow-orchestrator.test.ts (1)

259-261: 💤 Low value

テストの堅牢性向上の提案

現在のテストは単一の project + mcpServerId の組み合わせのみを確認していますが、異なる projectId でも null を返すことを追加で検証すると、composite key の正しさがより明確になります。

💡 追加アサーション案
   it('未開始の mcpServerId は getOAuthFlowStatus が null を返す', () => {
     expect(getOAuthFlowStatus(TEST_PID, 'never-started')).toBeNull();
+    // 異なる projectId でも同様に null
+    expect(getOAuthFlowStatus('other-project', 'atlassian')).toBeNull();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ai-engine/src/oauth/oauth-flow-orchestrator.test.ts` around lines
259 - 261, The test case only asserts getOAuthFlowStatus(TEST_PID,
'never-started') is null, which misses verifying the composite key logic; update
the same it block to also call getOAuthFlowStatus with a second distinct project
id (e.g., a new constant like ANOTHER_TEST_PID or a different literal) and
assert that getOAuthFlowStatus(ANOTHER_TEST_PID, 'never-started') is also null
so both projectId+mcpServerId combinations return null; reference
getOAuthFlowStatus, TEST_PID and the 'never-started' mcpServerId when adding the
additional assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/ai-engine/src/oauth/oauth-flow-orchestrator.test.ts`:
- Around line 259-261: The test case only asserts getOAuthFlowStatus(TEST_PID,
'never-started') is null, which misses verifying the composite key logic; update
the same it block to also call getOAuthFlowStatus with a second distinct project
id (e.g., a new constant like ANOTHER_TEST_PID or a different literal) and
assert that getOAuthFlowStatus(ANOTHER_TEST_PID, 'never-started') is also null
so both projectId+mcpServerId combinations return null; reference
getOAuthFlowStatus, TEST_PID and the 'never-started' mcpServerId when adding the
additional assertion.

In `@packages/ai-engine/src/oauth/oauth-flow-orchestrator.ts`:
- Around line 156-158: Update the outdated variable name in the explanatory
comment inside oauth-flow-orchestrator.ts: replace the reference to
flows.get(mcpServerId) with flows.get(flowKey)?.promise (or otherwise reference
flowKey consistently) in the comment that explains the bg promise IIFE and
definite-assignment behavior so the comment matches the actual code using
flowKey and flows.set(...).

In `@packages/frontend/src/components/chat/auth-request-card.test.tsx`:
- Around line 44-71: The test suite for AuthRequestCard lacks coverage for the
polling transitions from pending to completed/failed; add tests that render
<AuthRequestCard block={pendingBlock} />, stub global fetch (the existing
fetchMock/EXPECTED_BASE_URL logic) to first respond to the POST with the
authorizationUrl and then on subsequent GET polls return JSON with status:
"completed" (and separately a case with status: "failed" plus failureMessage),
advance timers or wait for the polling interval so the component executes its
polling logic, and assert that the UI transitions to show "認証済" for completed
and "失敗" plus the failureMessage for failed; keep using vi.stubGlobal('fetch',
...) and waitFor/assertions similar to the POST test to verify state changes and
that the callback textbox is removed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 27ecc71b-a294-445f-837d-d14f5fdeae2d

📥 Commits

Reviewing files that changed from the base of the PR and between 3a70d24 and 37a1902.

📒 Files selected for processing (7)
  • packages/ai-engine/src/index.ts
  • packages/ai-engine/src/oauth/oauth-flow-orchestrator.test.ts
  • packages/ai-engine/src/oauth/oauth-flow-orchestrator.ts
  • packages/frontend/src/app/api/projects/[id]/mcp/[mcpServerId]/oauth/route.test.ts
  • packages/frontend/src/app/api/projects/[id]/mcp/[mcpServerId]/oauth/route.ts
  • packages/frontend/src/components/chat/auth-request-card.test.tsx
  • packages/frontend/src/components/chat/auth-request-card.tsx

@shomatan shomatan merged commit 3f8c60b into main May 2, 2026
2 checks passed
@shomatan shomatan deleted the feat/oauth-route-and-ui-pr-e3b branch May 2, 2026 11:39
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