> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP 統合

MCP（Model Context Protocol）は AI エージェントが外部ツールを呼び出すためのオープンプロトコルです。SDK を使って MCP Server を定義し、Agent にツールを装備できます。接続管理やツール発見などのランタイム処理は底層の CLI が担います。

<div id="アーキテクチャ概要" />

## アーキテクチャ概要

```
┌────────────────────────────────────────────────────────────┐
│  Your Node.js application (SDK Host)                       │
│                                                            │
│   ┌──────────────────────────┐                             │
│   │ createSdkMcpServer(...)  │  ← In-Process tools         │
│   │  + tool(...)             │     defined inline, no proc │
│   └──────────────────────────┘                             │
│                  │                                         │
│                  ▼                                         │
│   ┌──────────────────────────┐                             │
│   │  query({ mcpServers })   │── stdio ─▶ qodercli child  │
│   └──────────────────────────┘                             │
│                                          │                 │
│                                          ├── stdio ──▶ MCP server (process)
│                                          ├── sse   ──▶ MCP server (HTTP/SSE)
│                                          └── http  ──▶ MCP server (Streamable HTTP)
└────────────────────────────────────────────────────────────┘
```

* **In-Process**：ツールは単なる JS 関数で、自分のプロセス内で実行されます。McpServer インスタンスは SDK の control channel を通じて CLI と通信し、別のサブプロセスは起動しません。
* **External**：設定でサブプロセスまたはリモート URL を宣言し、CLI が接続、発見、呼び出しを担当します。

***

<div id="3つの接続方式" />

## 3つの接続方式

| 方式             | 設定項目 type                         | プロセス境界 | 適用シナリオ                                             |
| -------------- | --------------------------------- | ------ | -------------------------------------------------- |
| **In-Process** | `'sdk'`（`createSdkMcpServer` で作成） | 同一プロセス | カスタムビジネスツール、ホスト状態への直接アクセスが必要                       |
| **Stdio**      | `'stdio'`（省略可能）                   | サブプロセス | 既存の MCP ツールパッケージ（`@modelcontextprotocol/server-*`） |
| **SSE / HTTP** | `'sse'` / `'http'`                | リモート   | リモートサービス、SaaS ツール、OAuth が必要なサービス                   |

3つの方式は**混在可能**です——同じ `query()` 内で複数の異なるタイプのサーバーを同時に登録できます。

***

<div id="in-process-server推奨" />

## In-Process Server（推奨）

In-process ツールは最も直接的な拡張方法です：通常の async 関数を定義し、Zod スキーマを追加するだけで、エージェントから呼び出し可能になります。

<div id="30秒で開始" />

### 30秒で開始

```typescript theme={null}
import { query, createSdkMcpServer, tool } from '@qoder-ai/qoder-agent-sdk';
import { z } from 'zod';

const greet = tool(
  'greet',
  'Greet someone.',
  { name: z.string().describe('Recipient name') },
  async ({ name }) => ({
    content: [{ type: 'text', text: `Hello, ${name}!` }],
  }),
);

const server = createSdkMcpServer({
  name: 'my_tools',
  tools: [greet],
});

const q = query({
  prompt: 'Use the greet tool to greet Alice',
  options: {
    mcpServers: { my_tools: server },
    allowedTools: ['mcp__my_tools__greet'],
  },
});

for await (const msg of q) {
  if (msg.type === 'result') console.log(msg.result);
}
```

<div id="tool-完全なシグネチャ" />

### `tool()` 完全なシグネチャ

```typescript theme={null}
function tool<Schema extends ZodRawShape>(
  name: string,
  description: string,
  inputSchema: Schema,
  handler: (args: z.infer<ZodObject<Schema>>, extra: unknown) => Promise<CallToolResult>,
  extras?: ToolExtras,
): SdkMcpToolDefinition<Schema>;

type ToolExtras = {
  annotations?: ToolAnnotations;  // see "What annotations are actually consumed" below
};
```

| パラメータ                | 説明                                                   |
| -------------------- | ---------------------------------------------------- |
| `name`               | ツール名。完全修飾名は `mcp__<server>__<name>` になります            |
| `description`        | モデル向けの説明。AI がいつ呼び出すかを決定します——**What/When を明確に記述**     |
| `inputSchema`        | Zod raw shape（`z.object(...)` ではなく、フィールドオブジェクトを渡します） |
| `handler`            | 実際のロジック。`CallToolResult` を返します                       |
| `extras.annotations` | MCP ツールアノテーション。詳細は下表参照                               |

<div id="annotations-の実際のサポート状況" />

#### annotations の実際のサポート状況

以下の 3 つのフィールドは SDK が実際に消費し、`mcpServerStatus().tools[i].annotations` を通じてホスト側に返されます：

| フィールド             | 動作                                                                                              | ホスト側の読み方                  |
| ----------------- | ----------------------------------------------------------------------------------------------- | ------------------------- |
| `readOnlyHint`    | ツールが**読み取り専用**であることを宣言。読み取り専用ツールは並行実行可能（同一バッチ内で互いをブロックしない）；TUI のツール詳細に `[read-only]` バッジが表示されます | `annotations.readOnly`    |
| `destructiveHint` | ツールが**破壊的操作**を実行することを宣言。TUI のツール詳細に `[destructive]` バッジが表示されます                                  | `annotations.destructive` |
| `openWorldHint`   | ツールが**外部世界**（Web 検索、サードパーティ API 呼び出しなど）に触れることを宣言。TUI のツール詳細に `[open-world]` バッジが表示されます          | `annotations.openWorld`   |

> ホスト側のフィールド名は **`Hint` サフィックスを除いた**形式です：`readOnlyHint` → `annotations.readOnly` のように。`annotations` オブジェクトには明示的に設定されたフィールドのみが含まれます。
>
> ⚠️ **これらのフィールドは auto モードのパーミッション判定には影響しません**。CLI はサーバーが自己宣言した annotation を検証不可能な参考情報として扱い（サーバーは過少・過大に申告できるため）、パーミッションパイプラインには敢えて取り込みません——取り込むとサーバーの自己評価に権威を付与してしまうためです。**特定ツールを確実にブロックしたい場合は `allowedTools` ホワイトリストまたは hooks を使用してください**——annotation はホスト側での識別（`mcpServerStatus`）と TUI 表示のためだけのものです。

`idempotentHint` と `title` は現在サポートされていません——渡してもエラーにはなりませんが、SDK は消費せず、ホストにも返しません。アプリケーションでこれらの情報が必要な場合は、ホスト側で独自にマッピングを管理してください。

<div id="calltoolresult-構造" />

#### `CallToolResult` 構造

```typescript theme={null}
type CallToolResult = {
  content: Array<
    | { type: 'text'; text: string }
    | { type: 'image'; data: string; mimeType: string }     // base64
    | { type: 'audio'; data: string; mimeType: string }
    | { type: 'resource'; resource: { uri: string; text?: string; blob?: string; mimeType?: string } }
    | { type: 'resource_link'; uri: string; title?: string; name?: string }
  >;
  isError?: boolean;  // when true, the AI sees this as a failed result
};
```

**ビジネスエラーには例外ではなく `isError: true` を使用してください**——例外はツール呼び出し全体を終了させ、AI は情報を得られません。`isError` は AI に「この呼び出しは失敗した。別の方法を試してください」と伝えます。

```typescript theme={null}
const queryDb = tool(
  'query_db',
  'Read-only SQL query.',
  { sql: z.string() },
  async ({ sql }) => {
    if (!/^\s*SELECT/i.test(sql)) {
      return {
        isError: true,
        content: [{ type: 'text', text: 'Only SELECT statements are allowed' }],
      };
    }
    const rows = await db.query(sql);
    return { content: [{ type: 'text', text: JSON.stringify(rows) }] };
  },
  { annotations: { readOnlyHint: true } },
);
```

<div id="createsdkmcpserver-完全なシグネチャ" />

### `createSdkMcpServer()` 完全なシグネチャ

```typescript theme={null}
function createSdkMcpServer(options: {
  name: string;       // server name (determines tool prefix mcp__<name>__)
  version?: string;   // defaults to '1.0.0'
  tools?: Array<SdkMcpToolDefinition<any>>;
}): McpSdkServerConfigWithInstance;
```

戻り値は `{ type: 'sdk', name, instance }` の形式で、そのまま `options.mcpServers` に渡せます。

> ⚠️ **同じ server 設定を複数の `query()` で再利用しないでください**：各 query は独立した transport をバインドします。再利用に副作用はありませんが、「query 間で状態を共有する」機能は得られません——共有状態は handler クロージャの外のモジュールスコープに配置してください。

<div id="複数ツールの例" />

### 複数ツールの例

```typescript theme={null}
const searchDocs = tool(
  'search_docs',
  'Search internal docs and return relevant snippets.',
  {
    query: z.string().describe('Search keywords'),
    maxResults: z.number().int().min(1).max(20).optional()
      .describe('Maximum number of results, defaults to 5'),
  },
  async ({ query, maxResults = 5 }) => {
    const hits = await docs.search(query, maxResults);
    return { content: [{ type: 'text', text: JSON.stringify(hits) }] };
  },
  { annotations: { readOnlyHint: true } },
);

const server = createSdkMcpServer({
  name: 'kb',
  tools: [searchDocs, queryDb /* ... */],
});
```

***

<div id="stdio-server" />

## Stdio Server

サブプロセスの stdin/stdout を通じて MCP サーバーと通信します。NPM の `@modelcontextprotocol/server-*` シリーズはすべて stdio 実装です。

```typescript theme={null}
type McpStdioServerConfig = {
  type?: 'stdio';                       // optional; stdio is the default
  command: string;                      // executable command
  args?: string[];                      // command arguments
  env?: Record<string, string>;         // environment variables
  isProxy?: boolean;                    // proxy flag (aggregates multiple backends)
};
```

```typescript theme={null}
const q = query({
  prompt: 'Read the title from the project README',
  options: {
    mcpServers: {
      fs: {
        command: 'npx',
        args: ['-y', '@modelcontextprotocol/server-filesystem', '/path/to/project'],
      },
      gh: {
        command: 'npx',
        args: ['-y', '@modelcontextprotocol/server-github'],
        env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN! },
      },
    },
  },
});
```

***

<div id="sse-http-server" />

## SSE / HTTP Server

```typescript theme={null}
type McpSSEServerConfig = {
  type: 'sse';
  url: string;
  headers?: Record<string, string>;
  isProxy?: boolean;
};

type McpHttpServerConfig = {
  type: 'http';                         // Streamable HTTP
  url: string;
  headers?: Record<string, string>;
  isProxy?: boolean;
};
```

```typescript theme={null}
const q = query({
  prompt: 'Query this month\'s sales data',
  options: {
    mcpServers: {
      analytics: {
        type: 'http',
        url: 'https://analytics.example.com/mcp',
        headers: { Authorization: `Bearer ${process.env.ANALYTICS_TOKEN}` },
      },
    },
  },
});
```

OAuth が必要なリモートサービスについては [OAuth 認証](#oauth-認証) を参照してください。

***

<div id="ツール命名とホワイトリスト" />

## ツール命名とホワイトリスト

CLI がモデルに MCP ツールを公開する際、統一的にプレフィックスを付加します：

```
mcp__<server_name>__<tool_name>
```

例えばサーバー名 `my_tools`、ツール名 `greet` の場合、モデルが認識するツール名は `mcp__my_tools__greet` です。

<div id="toolsモデルに見えるツールを絞る" />

### `tools`：モデルに見えるツールを絞る

モデルに**一部のツールだけを見せたい**場合は `tools` を使います。CLI はリストにない組み込みツールをすべて disallow リストに追加します — 実質的な可視性ホワイトリストです：

```typescript theme={null}
options: {
  mcpServers: { my_tools: server },
  tools: [
    'Read', 'Grep',                  // built-in tools you still want
    'mcp__my_tools__greet',
    'mcp__my_tools__search_docs',
  ],
}
```

> ⚠️ **`tools` を渡さない＝すべて開放**：すべての組み込みツールと接続済み MCP サーバーのツールがモデルに公開されます。本番環境では明示的に列挙して範囲を絞ることを推奨します。

<div id="allowedtools事前承認可視性のホワイトリストではない" />

### `allowedTools`：事前承認（**可視性のホワイトリストではない**）

`allowedTools` はリストされたツールを「常に許可」ルールに追加します — 呼び出し時に**承認プロンプトをスキップ**しますが、リストにないツールは**非表示にはなりません**。低リスクの MCP ツールを無人承認したい場合に使います：

```typescript theme={null}
options: {
  mcpServers: { my_tools: server },
  allowedTools: [
    'mcp__my_tools__greet',          // pre-approved, no prompt
    'mcp__my_tools__search_docs',
  ],
}
```

`allowedTools` を渡さない場合は事前承認ルールが無いだけで、モデルは依然としてすべてのツールを見て呼び出せます。書き込み系操作は通常の `permissionMode` 承認フローに従います。完全なセマンティクスは [Permissions ドキュメント](/ja/cli/sdk/permissions#ツール範囲の制御toolsallowedtoolsdisallowedtools)を参照してください。

<div id="allowedmcpservernamesプロセス型サーバーのホワイトリスト" />

### `allowedMcpServerNames`：プロセス型サーバーのホワイトリスト

**プロセス型**（stdio/sse/http）サーバーのみをフィルタリングし、**in-process サーバーには影響しません**。`strictMcpConfig: true` と組み合わせることで、CLI がローカルの追加設定を読み込むことを拒否できます：

```typescript theme={null}
options: {
  mcpServers: {
    keep: makeStdioConfig('...'),
    drop: makeStdioConfig('...'),
  },
  allowedMcpServerNames: ['keep'],   // 'drop' still appears in status but does not connect
  strictMcpConfig: true,             // skip loading MCP servers from settings.json / .mcp.json
}
```

> ⚠️ **`allowedMcpServerNames` を渡さない＝すべて接続**：宣言されたすべてのプロセス型サーバーが接続されます。範囲を絞りたい場合は明示的に列挙してください。in-process サーバーは常にこのフィールドの影響を受けません。

***

<div id="ランタイム管理query-api" />

## ランタイム管理（Query API）

`query()` が返す `Query` オブジェクトには MCP 関連のメソッドがいくつかあります。すべてのメソッドは control channel を通じて CLI と通信し、動作は非同期かつ冪等です。

> ⚠️ **キャッシュの原則**：MCP server の設定/認証状態の変更は tools リストを再構築し、**セッション途中の変更はプロンプトプレフィックスキャッシュを破壊します**。SDK は「状態を照会 + 最初のメッセージ前に認証を完了する」メソッドを提供します。server セット自体は `options.mcpServers` で起動時に一度設定し、必要に応じて `query()` を再起動してください。

<div id="状態の照会" />

### 状態の照会

```typescript theme={null}
const status = await q.mcpServerStatus();
// Returns McpServerStatus[], each item includes:
//   { name, status: 'pending' | 'connecting' | 'connected' | 'failed' | 'needs-auth' | 'disabled', tools?, ... }

for (const s of status) {
  console.log(`${s.name}: ${s.status}`);
  if (s.status === 'connected') {
    console.log('  tools:', s.tools?.map((t) => t.name));
  }
}
```

> 💡 MCP ハンドシェイクは CLI が `initialize` を完了した後、最初のユーザーメッセージの前に発生します。`initializationResult()` が返された後に status を照会して初めて実際の結果を取得できます——ハンドシェイク IO には数百ミリ秒かかる場合があるため、`pollUntil` で `connected` を待ってから使用することを推奨します。

<div id="状態変化のサブスクリプション" />

### 状態変化のサブスクリプション

MCP 状態はプッシュではなく**プル**で取得します：`await q.mcpServerStatus()` を呼び出すだけです。ポーリングが必要な場合は自分のコードで実装してください。

<div id="server-セットの変更" />

### server セットの変更

プロンプトプレフィックスキャッシュの安定性を確保するため、server セットの変更は起動時に一度で完了します：

| やりたいこと                | 方法                                                    |
| --------------------- | ----------------------------------------------------- |
| サーバーの追加/削除/置換         | `options.mcpServers` で設定；セットの変更が必要な場合は `query()` を再起動 |
| プロセス型 server の一部のみ有効化 | `options.allowedMcpServerNames` ホワイトリスト               |
| 特定 server への再接続       | `query()` を再起動（再接続はツールを再発見し、キャッシュに影響）                 |
| 特定 server からのログアウト    | `query()` 再起動時にそのトークンを含めない；または外部の認証情報ストアをクリアしてから再起動   |

<div id="コントロールリクエストのタイムアウト" />

### コントロールリクエストのタイムアウト

```typescript theme={null}
options: {
  controlRequestTimeoutMs: 20_000,  // default 60_000; pass 0 to disable
}
```

タイムアウト後、SDK は自動的に `control_cancel_request` を書き込み、現在の Promise を reject します。

***

<div id="oauth-認証" />

## OAuth 認証

リモート MCP サーバー（HTTP/SSE）は OAuth を必要とすることがよくあります。CLI には完全な OAuth 2.0 + PKCE + Dynamic Client Registration（RFC 7591）実装が内蔵されています。

> ⚠️ **キャッシュの原則**：OAuth 完了後、CLI は server に再接続してツールを再発見し、**セッション途中での認証完了は必然的にプロンプトプレフィックスキャッシュを破壊します**。そのため「能動的駆動」モード認証のみをサポートしています——**最初の `streamInput` の前に**すべての認証を完了し、tools リストが安定してから最初のユーザーメッセージを送信してください。

> 💡 **このセクションは CLI 主導の OAuth のみをカバーします**：CLI 自身が metadata discovery、PKCE、token 交換、token 永続化を行います。別の**サーバー主導**の認証パスもあります——server が MCP `elicitation/create` を使用してクライアントを特定の URL にリダイレクトさせて認証を完了させます（典型例：GitHub MCP）。2つのパスは独立しており、同時にトリガーされることはありません：サーバー主導の場合 `mcpServerStatus()` は `needs-auth` を示さず、`mcpAuthenticate` を呼ぶべきではなく、ホストは `onElicitation` でリクエストを受け取ります。詳細は [Elicitation：サーバーによるユーザー入力要求](#elicitationサーバーによるユーザー入力要求) を参照してください。

ホスト自身が OAuth のタイミングを制御し、最初のユーザーメッセージを送信する**前に**完了します：

```typescript theme={null}
const q = query({
  prompt: userMessages(),  // AsyncIterable — no message is sent yet
  options: {
    mcpServers: {
      // Assume this remote server uses the CLI-driven standard OAuth (metadata discovery + PKCE).
      // If you connect to a server like GitHub MCP that implements OAuth on its own side, use onElicitation instead.
      analytics: { type: 'http', url: 'https://analytics.example.com/mcp' },
    },
  },
});

// Wait for handshake to complete
await q.initializationResult();

// Find servers that need authentication
const status = await q.mcpServerStatus();
for (const s of status.filter((x) => x.status === 'needs-auth')) {
  const result = await q.mcpAuthenticate(s.name);
  if (result.requiresUserAction) {
    await openInBrowser(result.authUrl!);
    const callbackUrl = await waitForUserPasteCallback();
    await q.mcpSubmitOAuthCallbackUrl(s.name, callbackUrl);
  }
  // Silent path (cached client + valid refresh token): result.requiresUserAction === false
  // No UI prompt needed; just proceed to the next step.
}

// At this point the tools list is stable; sending the first user message
// will let the prompt prefix cache be established cleanly.
for await (const msg of q) { /* ... */ }
```

| Pull メソッド                              | 用途                                                                                               | 呼び出しタイミング               |
| -------------------------------------- | ------------------------------------------------------------------------------------------------ | ----------------------- |
| `mcpAuthenticate(name, redirectUri?)`  | OAuth を開始；`{ authUrl?, requiresUserAction }` を返す。サイレント更新成功時は `requiresUserAction: false` で UI 不要 | **最初の `streamInput` 前** |
| `mcpSubmitOAuthCallbackUrl(name, url)` | 完全なコールバック URL（code/state を含む）を送信                                                                 | **最初の `streamInput` 前** |

`redirectUri` はオプションで、デフォルトの OAuth コールバック先を上書きします（Electron カスタムプロトコル、社内ネットワークコールバックアドレスなど）。

CLI はデフォルトでトークンをシステム Keychain（macOS / Linux Secret Service）に保存し、フォールバックとして `~/.qoder/mcp-oauth-tokens.json`（0o600 パーミッション + クロスプロセスロック）を使用します。

***

<div id="elicitationサーバーによるユーザー入力要求" />

## Elicitation：サーバーによるユーザー入力要求

MCP `elicitation/create` は **server → client** 方向のリクエストで、クライアントにユーザーの前でインタラクションを表示させるためのものです。SDK はこのリクエストを `Options.onElicitation` を通じてホストに公開します。

<div id="2つのモード" />

### 2つのモード

| モード      | トリガーシナリオ                                                             | 典型的な用途                                 |
| -------- | -------------------------------------------------------------------- | -------------------------------------- |
| `'form'` | サーバーが構造化入力を要求し、リクエストに `requestedSchema`（MCP 制限サブセットの JSON Schema）を含む | API キー入力、設定項目の入力、二次確認                  |
| `'url'`  | サーバーがユーザーを特定の URL に誘導して操作を完了させ、リクエストに `url` + `elicitationId` を含む    | サーバー独自の OAuth、デバイスコードアクティベーション、アカウント連携 |

URL モードは非同期で完了します：server は自身のコールバックでユーザー認可を受信した後、`notifications/elicitation/complete` を送信します——SDK はこれを `SDKElicitationCompleteMessage` として `Query` のメッセージストリームにプッシュします。

<div id="コールバックシグネチャ" />

### コールバックシグネチャ

```typescript theme={null}
import type { OnElicitation, ElicitationRequest, ElicitationResult } from '@qoder-ai/qoder-agent-sdk';

type OnElicitation = (
  request: ElicitationRequest,
  options: { signal: AbortSignal },
) => Promise<ElicitationResult>;

type ElicitationRequest = {
  serverName: string;          // name of the MCP server that issued the request
  message: string;             // explanation shown to the user
  mode?: 'form' | 'url';       // defaults to form
  url?: string;                // required when mode='url'
  elicitationId?: string;      // required when mode='url'; used to correlate later completion notifications
  requestedSchema?: Record<string, unknown>;  // field schema carried when mode='form'
  title?: string;
  displayName?: string;
  description?: string;
};

type ElicitationResult = {
  action: 'accept' | 'decline' | 'cancel';
  content?: Record<string, string | number | boolean | string[]>;  // populated when accept + form
};
```

`signal` は `q.close()` / 中断時に abort されるため、長時間プロセスではチェックが必要です。

<div id="form-モードの例" />

### form モードの例

```typescript theme={null}
const q = query({
  prompt: userMessages(),
  options: {
    mcpServers: { my_server: { type: 'http', url: '...' } },
    onElicitation: async (request) => {
      if (request.mode !== 'url' && request.requestedSchema) {
        // Show a form in the UI and collect the user's input
        const filled = await showForm(request.message, request.requestedSchema);
        if (!filled) return { action: 'cancel' };
        return { action: 'accept', content: filled };
      }
      return { action: 'decline' };
    },
  },
});
```

<div id="url-モードの例elicitation-complete-との連携" />

### url モードの例（elicitation\_complete との連携）

```typescript theme={null}
const q = query({
  prompt: userMessages(),
  options: {
    mcpServers: { gh: { type: 'http', url: 'https://mcp.github.com/mcp' } },
    onElicitation: async (request, { signal }) => {
      if (request.mode !== 'url' || !request.url) {
        return { action: 'cancel' };
      }
      // Open the browser so the user can authorize; we only acknowledge "I have started the flow"
      // Real completion is signaled by notifications/elicitation/complete from the server side
      await openInBrowser(request.url);
      return { action: 'accept' };
    },
  },
});

// Listen for system/elicitation_complete to learn when server-side authorization is done
for await (const msg of q) {
  if (msg.type === 'system' && msg.subtype === 'elicitation_complete') {
    console.log(`server '${msg.mcp_server_name}' finished elicitation ${msg.elicitation_id}`);
    // The server now has its token; subsequent tool calls can succeed directly.
  }
}
```

> 💡 **`onElicitation` 内でブラウザのリダイレクトを await しないでください**。URL モードの設計は：コールバックは即座に `accept` を返し（＝ユーザーがフローを開始した）、CLI は control チャネルをブロックしません。真の「完了」シグナルは後続の `elicitation_complete` メッセージから届きます。OAuth リダイレクト全体を await すると、control リクエストタイムアウト（`controlRequestTimeoutMs`）がトリガーされます。

<div id="oauth-パスとの境界" />

### OAuth パスとの境界

* **CLI 主導の OAuth**（`mcpAuthenticate` / `mcpSubmitOAuthCallbackUrl`）：トークンは qodercli Keychain に保存；`mcpServerStatus()` が `needs-auth` の時に駆動；**`onElicitation` はトリガーされません**。
* **サーバー主導の elicit URL**：トークンは server 内部；`mcpServerStatus()` は `needs-auth` を示しません；**`onElicitation`** で受け取り、`system/elicitation_complete` で完了。

2つのパスは相互排他ではありませんが重複もしません：同じ server は通常どちらか一方のみを使用します。特定の server がどちらを使用するか不明な場合：ハンドシェイク時にクライアントに `elicitation/create` を送信するかどうかを確認してください——送信した場合はサーバー主導です。

<div id="hook-チャネル" />

### Hook チャネル

ホストは `settings.json` で hooks を設定して elicitation をインターセプトすることもでき、その動作は `onElicitation` よりも優先されます：

| Hook イベント                                    | タイミング                              | できること                                                 |
| -------------------------------------------- | ---------------------------------- | ----------------------------------------------------- |
| `Elicitation`                                | server リクエスト到着時、`onElicitation` の前 | 自動 `accept` / `decline` / `cancel`（UI をショートカット）、または通過 |
| `ElicitationResult`                          | ユーザー応答後                            | `action` / `content` の書き換え、またはブロック（強制 decline）        |
| `Notification` (type=`elicitation_complete`) | URL モード完了通知到着時                     | IDE / システム通知のトリガー                                     |

> ⚠️ qodercli 0.2.x は MCP capability 宣言で `elicitation: {}`（空オブジェクト、Spring AI Java MCP SDK 互換）のみを送信します。MCP SDK サーバー側はこれを `{ form: {} }` と同等に解釈するため、**現在は form モードのみがリモート server からクライアントに到達可能です**。URL モードはプロトコル層では完全ですが、CLI が明示的に `elicitation.url` を宣言して初めて server 側の `elicitInput({ mode: 'url' })` がバリデーションを通過します——CLI バージョンの進化に伴い対応予定です。

***

<div id="options-クイックリファレンス" />

## Options クイックリファレンス

| フィールド                     | 型                                 | デフォルト    | 説明                                                                                                    |
| ------------------------- | --------------------------------- | -------- | ----------------------------------------------------------------------------------------------------- |
| `mcpServers`              | `Record<string, McpServerConfig>` | –        | サーバー名 → 設定                                                                                            |
| `allowedMcpServerNames`   | `string[]`                        | –        | プロセス型サーバーのホワイトリスト（in-process には影響なし）；渡さない＝すべて開放                                                       |
| `strictMcpConfig`         | `boolean`                         | `false`  | CLI がユーザー設定ファイルから追加 MCP を読み込むことを禁止                                                                    |
| `tools`                   | `string[]`                        | –        | **モデルに見えるツールのホワイトリスト**；渡さない＝すべての組み込み + MCP ツールが見える                                                    |
| `allowedTools`            | `string[]`                        | –        | **事前承認**リスト（承認プロンプトをスキップ；可視性は制御**しない**）；渡さない＝事前承認ルール無し                                                |
| `disallowedTools`         | `string[]`                        | –        | 明示的な拒否リスト；allow よりも優先                                                                                 |
| `controlRequestTimeoutMs` | `number`                          | `60_000` | control リクエストタイムアウト（mcp 系列を含む）、0 で無効化                                                                 |
| `onElicitation`           | `OnElicitation`                   | –        | MCP server がユーザー入力を能動的に要求した際にトリガー（form / url の2モード）。詳細は [Elicitation](#elicitationサーバーによるユーザー入力要求) 参照 |

<div id="query-上のメソッド" />

### Query 上のメソッド

| メソッド                                   | 説明                                                    | 呼び出しタイミング               |
| -------------------------------------- | ----------------------------------------------------- | ----------------------- |
| `mcpServerStatus()`                    | 現在のすべての MCP サーバー状態を取得                                 | 任意のタイミング                |
| `mcpAuthenticate(name, redirectUri?)`  | 能動的に OAuth を開始；`{ authUrl?, requiresUserAction }` を返す | **最初の `streamInput` 前** |
| `mcpSubmitOAuthCallbackUrl(name, url)` | OAuth コールバックを送信                                       | **最初の `streamInput` 前** |

> server セットの追加・削除・変更は `options.mcpServers`（起動時設定）+ `query()` の再起動で完了してください。詳細は [server セットの変更](#server-セットの変更) を参照。

***

<div id="型リファレンス" />

## 型リファレンス

```typescript theme={null}
import type {
  // Factory function return value
  McpSdkServerConfigWithInstance,
  // Union type — pass into options.mcpServers
  McpServerConfig,
  // Individual transport types
  McpStdioServerConfig,
  McpSSEServerConfig,
  McpHttpServerConfig,
  McpSdkServerConfig,
  // Status
  McpServerStatus,
  McpServerStatusConfig,
  // OAuth
  // (OAuthToken / McpOAuthRequest / McpOAuthResolution are exposed via coreTypes)
  // Elicitation
  OnElicitation,
  ElicitationRequest,
  ElicitationResult,
  SDKElicitationCompleteMessage,
} from '@qoder-ai/qoder-agent-sdk';

import { tool, createSdkMcpServer } from '@qoder-ai/qoder-agent-sdk';
import type {
  AnyZodRawShape,
  InferShape,
  SdkMcpToolDefinition,
} from '@qoder-ai/qoder-agent-sdk';
```

McpServerStatus の `status` 列挙値：

| 値              | 意味                       |
| -------------- | ------------------------ |
| `'pending'`    | 登録済み、接続未開始               |
| `'connecting'` | ハンドシェイク中                 |
| `'connected'`  | 接続済み、ツール呼び出し可能           |
| `'failed'`     | 接続失敗（`error` フィールドを参照）   |
| `'needs-auth'` | OAuth が必要、認証フローを実行してください |
| `'disabled'`   | 無効化（CLI 内部設定または外部状態による）  |

***

<div id="ベストプラクティス" />

## ベストプラクティス

1. **説明は AI 向けに記述**：`tool()` の `description` は AI がいつそのツールを選択するかを決定します。「何をするか、いつ使うか、何に使うべきでないか」を明確に記述してください。
2. **フィールドに `.describe()` を付ける**：Zod フィールドには必ず `.describe(...)` を付けてください。AI はこの情報を使って呼び出しパラメータを構築します。
3. **失敗には `isError` を使用し、例外をスローしない**：AI に結果を見せてください。例外はモデルを困惑させ、リトライをトリガーする可能性があります。
4. **読み取り専用 + `readOnlyHint` を優先**：書き込み操作は慎重に行い、`canUseTool` または hooks で二次確認を行ってください。
5. **サーバー名は短く**：ツールプレフィックスに含まれるため、長い名前はトークンを浪費します。
6. **In-process の共有状態はモジュールスコープに配置**：handler はクロージャですが、各 query は同じ server インスタンスを再利用します。
7. **OAuth は最初の `streamInput` 前に完了**：`mcpAuthenticate` + `mcpSubmitOAuthCallbackUrl` を使用してください。セッション途中での認証完了は必然的にプロンプトプレフィックスキャッシュを破壊します。
8. **MCP 状態は `mcpServerStatus()` でプル**：push チャネルは廃止されています。必要に応じてポーリングしてください。
9. **適切な `controlRequestTimeoutMs` を設定**：リモートサーバーのハンドシェイクは秒単位になる可能性があり、デフォルトの 60 秒で通常は十分ですが、CI 環境では明示的に指定してください。
10. **分離には `strictMcpConfig` を使用**：ユーザーローカルの `settings.json` / `.mcp.json` に宣言された MCP サーバーがアプリケーションに干渉することを防ぎます。

***

<div id="完全な例" />

## 完全な例

```typescript theme={null}
import { query, createSdkMcpServer, tool } from '@qoder-ai/qoder-agent-sdk';
import { z } from 'zod';

// 1. Define business tools
const getUserOrders = tool(
  'get_user_orders',
  'Query a user\'s orders, optionally filtered by status.',
  {
    userId: z.string().describe('User UUID'),
    status: z.enum(['pending', 'paid', 'shipped', 'cancelled']).optional()
      .describe('Filter by order status'),
  },
  async ({ userId, status }) => {
    try {
      const orders = await db.getOrders(userId, status);
      return { content: [{ type: 'text', text: JSON.stringify(orders) }] };
    } catch (err) {
      return {
        isError: true,
        content: [{ type: 'text', text: `Query failed: ${(err as Error).message}` }],
      };
    }
  },
  { annotations: { readOnlyHint: true } },
);

// 2. Assemble the server
const myServer = createSdkMcpServer({
  name: 'crm',
  tools: [getUserOrders /* , ... */],
});

// 3. Start query (use AsyncIterable so no message is sent yet)
async function* userMessages() {
  yield {
    type: 'user' as const,
    message: { role: 'user' as const, content: 'List the recently paid orders for user-123' },
    parent_tool_use_id: null,
  };
}

const q = query({
  prompt: userMessages(),
  options: {
    mcpServers: {
      crm: myServer,
      // Assume a remote server that uses CLI-driven OAuth (GitHub MCP uses elicit-URL, not this path)
      analytics: { type: 'http', url: 'https://analytics.example.com/mcp' },
    },
    allowedTools: ['mcp__crm__get_user_orders'],
    controlRequestTimeoutMs: 30_000,
  },
});

// 4. Wait for handshake; actively drive auth before the first user message
await q.initializationResult();
const status = await q.mcpServerStatus();
for (const s of status.filter((x) => x.status === 'needs-auth')) {
  const result = await q.mcpAuthenticate(s.name);
  if (result.requiresUserAction) {
    const callbackUrl = await openInBrowserAndWaitForCallback(result.authUrl!);
    await q.mcpSubmitOAuthCallbackUrl(s.name, callbackUrl);
  }
  // Silent refresh success: requiresUserAction === false; no UI required
}

// 5. Consume messages (tools list is now stable; prompt prefix cache will be established correctly)
for await (const msg of q) {
  if (msg.type === 'result') {
    console.log(msg.subtype === 'success' ? msg.result : msg);
    break;
  }
}

await q.close?.();
```
