# 機能設計書 35-MCPサーバー管理

## 概要

本ドキュメントは、OpenCodeにおけるModel Context Protocol (MCP) サーバーの管理機能について記載する。この機能により、ローカルおよびリモートのMCPサーバーへの接続・管理が可能となり、AIアシスタントの機能を外部ツールやサービスで拡張できる。

### 本機能の処理概要

**業務上の目的・背景**：AIアシスタントの機能を拡張するために、外部サービスやツールとの連携が必要となる場面がある。MCPサーバー管理機能により、Anthropic標準のModel Context Protocolに準拠したサーバーへの接続・管理が可能となり、データベースアクセス、API連携、カスタムツールなど多様な機能をAIアシスタントに追加できる。

**機能の利用シーン**：
- ローカルのMCPサーバー（stdio接続）を起動して独自ツールを利用可能にする
- リモートのMCPサーバー（HTTP/SSE接続）に接続してクラウドサービスの機能を利用する
- 複数のMCPサーバーを同時に管理し、状況に応じて有効/無効を切り替える
- MCPサーバーからのツール一覧変更通知を受信して動的にツールを更新する

**主要な処理内容**：
1. 設定ファイルからのMCPサーバー設定読み込み
2. ローカルMCPサーバーの起動（StdioClientTransport）
3. リモートMCPサーバーへの接続（StreamableHTTP/SSEClientTransport）
4. MCPクライアントの初期化とツール一覧取得
5. 接続状態の管理（connected/disabled/failed/needs_auth等）
6. ToolListChangedNotificationの受信とイベント発行
7. MCPサーバーのライフサイクル管理（接続・切断・再接続）

**関連システム・外部連携**：
- Model Context Protocol SDK（@modelcontextprotocol/sdk）
- 各種MCPサーバー実装（ローカル/リモート）
- OAuth認証サーバー（リモートMCP用）
- 設定ファイル（opencode.json）

**権限による制御**：MCPサーバーは設定ファイルで`enabled: false`により無効化可能。リモートサーバーはOAuth認証が必要な場合がある。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 4 | レイアウト | API連携 | MCPサーバー状態の監視と通知表示 |
| 25 | ホーム | 主機能 | MCPサーバー接続状態の表示、接続数カウント |

## 機能種別

外部連携 / プロセス管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| mcp (設定) | object | No | MCPサーバー設定 | typeが必須（local/remote） |
| mcp.{name}.type | "local" \| "remote" | Yes | サーバー種別 | - |
| mcp.{name}.command | string[] | Yes (local) | 起動コマンド | - |
| mcp.{name}.url | string | Yes (remote) | サーバーURL | 有効なURL |
| mcp.{name}.timeout | number | No | 接続タイムアウト（ms） | デフォルト30000 |
| mcp.{name}.enabled | boolean | No | 有効/無効 | デフォルトtrue |
| mcp.{name}.oauth | boolean \| object | No (remote) | OAuth設定 | - |

### 入力データソース

- `opencode.json` / `opencode.jsonc`: MCP設定の読み込み元
- 環境変数: ローカルMCPの環境設定

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| Status | MCPStatus | 各MCPサーバーのステータス |
| Status.status | string | "connected" / "disabled" / "failed" / "needs_auth" / "needs_client_registration" |
| Status.error | string | エラーメッセージ（failedの場合） |

### 出力先

- インメモリ状態管理: MCPクライアントインスタンスの保持
- バスイベント: `mcp.tools.changed`イベントの発行

## 処理フロー

### 処理シーケンス

```
1. MCP状態初期化
   └─ 設定ファイルからMCP設定を読み込み
   └─ 各サーバーの設定検証

2. サーバー接続（並列処理）
   └─ enabled=falseならdisabledステータス
   └─ localタイプ: StdioClientTransportで起動
   └─ remoteタイプ: StreamableHTTP/SSE試行

3. クライアント初期化
   └─ MCP SDK Clientの作成
   └─ transport.connect()の実行
   └─ ToolListChangedNotification登録
   └─ listTools()でツール一覧取得

4. 状態管理
   └─ 成功: connected
   └─ OAuth必要: needs_auth
   └─ 失敗: failed (エラーメッセージ付き)

5. ライフサイクル管理
   └─ connect: 手動接続
   └─ disconnect: 切断
   └─ add: 動的追加
```

### フローチャート

```mermaid
flowchart TD
    A[MCP初期化] --> B[設定読み込み]
    B --> C{enabled?}
    C -->|No| D[disabled設定]
    C -->|Yes| E{type?}
    E -->|local| F[StdioTransport作成]
    E -->|remote| G[HTTP/SSE Transport作成]
    F --> H[spawn実行]
    G --> I{OAuth必要?}
    I -->|Yes| J[OAuthProvider設定]
    I -->|No| K[直接接続]
    J --> K
    H --> L[client.connect]
    K --> L
    L --> M{接続成功?}
    M -->|No| N{OAuth失敗?}
    N -->|Yes| O[needs_auth設定]
    N -->|No| P[failed設定]
    M -->|Yes| Q[listTools取得]
    Q --> R{取得成功?}
    R -->|No| P
    R -->|Yes| S[connected設定]
    S --> T[通知ハンドラ登録]
    D --> U[終了]
    O --> U
    P --> U
    T --> U
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-35-01 | デフォルトタイムアウト | 接続タイムアウトのデフォルト値は30,000ms | timeout未指定時 |
| BR-35-02 | Transport優先順位 | リモートMCPはStreamableHTTP優先、失敗時SSEにフォールバック | remote type |
| BR-35-03 | OAuth自動有効化 | リモートMCPはデフォルトでOAuth有効（oauth=falseで無効化） | remote type |
| BR-35-04 | ツール名サニタイズ | クライアント名・ツール名の特殊文字を_に置換 | ツール名生成時 |
| BR-35-05 | 既存クライアント置換 | 同名のMCPサーバーを追加する場合、既存クライアントを閉じてから置換 | add/connect時 |

### 計算ロジック

**ツール名生成**:
```
sanitizedClientName = clientName.replace(/[^a-zA-Z0-9_-]/g, "_")
sanitizedToolName = mcpTool.name.replace(/[^a-zA-Z0-9_-]/g, "_")
toolKey = sanitizedClientName + "_" + sanitizedToolName
```

## データベース操作仕様

### 操作別データベース影響一覧

本機能はデータベースを使用しない。認証情報はファイルベースで管理。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| MCPFailed | 接続エラー | MCPサーバーへの接続失敗 | failedステータス設定、エラーメッセージ記録 |
| UnauthorizedError | 認証エラー | リモートMCPでOAuth認証が必要 | needs_authステータス設定、トースト表示 |
| TimeoutError | タイムアウト | 接続/ツール取得がタイムアウト | failedステータス設定 |
| NeedsClientRegistration | 登録エラー | 動的クライアント登録不可 | needs_client_registrationステータス設定 |

### リトライ仕様

- 接続失敗時の自動リトライは行わない
- 手動でconnect()を呼び出すことで再接続可能

## トランザクション仕様

該当なし

## パフォーマンス要件

- 初期化時は全MCPサーバーへの接続を並列実行
- withTimeoutによるタイムアウト制御（デフォルト30秒）

## セキュリティ考慮事項

- ローカルMCPサーバーは設定されたコマンドを実行するため、設定ファイルの信頼性に依存
- リモートMCPサーバーへの接続にはOAuth認証をサポート
- 認証情報は`mcp-auth.json`に保存（パーミッション0o600）

## 備考

MCPステータス一覧:
- `connected`: 正常接続
- `disabled`: 設定で無効化
- `failed`: 接続失敗（エラーメッセージあり）
- `needs_auth`: OAuth認証が必要
- `needs_client_registration`: OAuth動的クライアント登録が必要

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

MCP関連の型定義を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | index.ts | `packages/opencode/src/mcp/index.ts` | Resource型定義（31-40行目） |
| 1-2 | index.ts | `packages/opencode/src/mcp/index.ts` | Status discriminated union定義（66-109行目） |
| 1-3 | index.ts | `packages/opencode/src/mcp/index.ts` | ToolsChangedイベント定義（42-47行目） |

**読解のコツ**: StatusはZodのdiscriminatedUnionで定義され、connected/disabled/failed/needs_auth/needs_client_registrationの5種類がある。

#### Step 2: エントリーポイントを理解する

MCP状態初期化の流れ。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/opencode/src/mcp/index.ts` | state関数（163-210行目） |

**主要処理フロー**:
1. **165-166行目**: 設定ファイルからMCP設定取得
2. **170-192行目**: 各MCPサーバーへの並列接続
3. **177-179行目**: enabled=falseの場合はdisabledステータス
4. **183行目**: create関数による接続処理

#### Step 3: サーバー接続処理

ローカル/リモートMCPサーバーへの接続。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.ts | `packages/opencode/src/mcp/index.ts` | create関数（291-494行目） |

**主要処理フロー**:
- **304-406行目**: リモートMCP接続（StreamableHTTP→SSEフォールバック）
- **328-343行目**: Transport配列の定義
- **347-405行目**: Transport順次試行とエラーハンドリング
- **408-450行目**: ローカルMCP接続（StdioClientTransport）
- **466-487行目**: listTools取得とクライアント登録

#### Step 4: 通知ハンドラ

ToolListChanged通知の処理。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | index.ts | `packages/opencode/src/mcp/index.ts` | registerNotificationHandlers関数（112-117行目） |

**主要処理フロー**:
- **113-116行目**: ToolListChangedNotificationSchemaの受信とイベント発行

#### Step 5: ライフサイクル管理

接続・切断・追加の操作。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | index.ts | `packages/opencode/src/mcp/index.ts` | connect関数（515-552行目） |
| 5-2 | index.ts | `packages/opencode/src/mcp/index.ts` | disconnect関数（554-564行目） |
| 5-3 | index.ts | `packages/opencode/src/mcp/index.ts` | add関数（257-289行目） |

**主要処理フロー**:
- **515-552行目**: 設定を取得してcreate実行、状態更新
- **554-564行目**: クライアントclose、状態をdisabledに
- **277-283行目**: 既存クライアントのclose、新規クライアント登録

### プログラム呼び出し階層図

```
MCP.state() [初期化]
    │
    ├─ Config.get() - 設定読み込み
    │
    └─ Promise.all([...Object.entries(config)])
           │
           └─ create(key, mcp)
                  │
                  ├─ [remote] StreamableHTTPClientTransport / SSEClientTransport
                  │      │
                  │      ├─ McpOAuthProvider (OAuth有効時)
                  │      │
                  │      ├─ new Client({name: "opencode"})
                  │      │
                  │      └─ withTimeout(client.connect(transport))
                  │
                  ├─ [local] StdioClientTransport
                  │      │
                  │      ├─ spawn(command, args, {cwd, env})
                  │      │
                  │      └─ withTimeout(client.connect(transport))
                  │
                  ├─ registerNotificationHandlers(client, key)
                  │      └─ client.setNotificationHandler(ToolListChangedNotificationSchema)
                  │
                  └─ withTimeout(client.listTools())

MCP.tools()
    │
    ├─ state().clients - 全クライアント取得
    │
    └─ Promise.all([...clients.map(...)])
           │
           └─ client.listTools()
                  │
                  └─ convertMcpTool(mcpTool, client, timeout)
```

### データフロー図

```
[入力]                    [処理]                         [出力]

設定ファイル ────────▶ MCP設定解析 ────────────▶ サーバー種別判定
                            │
                            ├─ [local] ──────────▶ Stdio起動
                            │                          │
                            └─ [remote] ─────────▶ HTTP/SSE接続
                                                       │
                                                       ▼
                                               クライアント初期化
                                                       │
                                                       ▼
                                               ツール一覧取得
                                                       │
                                                       ▼
状態マップ ◀───────────────────────────────── ステータス更新
                                                       │
                                                       ▼
                                            mcp.tools.changedイベント
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/opencode/src/mcp/index.ts` | ソース | MCPメインモジュール、状態管理、接続処理 |
| oauth-provider.ts | `packages/opencode/src/mcp/oauth-provider.ts` | ソース | OAuth認証プロバイダー実装 |
| oauth-callback.ts | `packages/opencode/src/mcp/oauth-callback.ts` | ソース | OAuthコールバックサーバー |
| auth.ts | `packages/opencode/src/mcp/auth.ts` | ソース | 認証情報の永続化管理 |
| config.ts | `packages/opencode/src/config/config.ts` | ソース | MCP設定スキーマ定義 |
