アーキテクチャ概要
- In-Process:ツールは単なる JS 関数で、自分のプロセス内で実行されます。McpServer インスタンスは SDK の control channel を通じて CLI と通信し、別のサブプロセスは起動しません。
- External:設定でサブプロセスまたはリモート URL を宣言し、CLI が接続、発見、呼び出しを担当します。
3つの接続方式
| 方式 | 設定項目 type | プロセス境界 | 適用シナリオ |
|---|---|---|---|
| In-Process | 'sdk'(createSdkMcpServer で作成) | 同一プロセス | カスタムビジネスツール、ホスト状態への直接アクセスが必要 |
| Stdio | 'stdio'(省略可能) | サブプロセス | 既存の MCP ツールパッケージ(@modelcontextprotocol/server-*) |
| SSE / HTTP | 'sse' / 'http' | リモート | リモートサービス、SaaS ツール、OAuth が必要なサービス |
query() 内で複数の異なるタイプのサーバーを同時に登録できます。
In-Process Server(推奨)
In-process ツールは最も直接的な拡張方法です:通常の async 関数を定義し、Zod スキーマを追加するだけで、エージェントから呼び出し可能になります。30秒で開始
tool() 完全なシグネチャ
| パラメータ | 説明 |
|---|---|
name | ツール名。完全修飾名は mcp__<server>__<name> になります |
description | モデル向けの説明。AI がいつ呼び出すかを決定します——What/When を明確に記述 |
inputSchema | Zod raw shape(z.object(...) ではなく、フィールドオブジェクトを渡します) |
handler | 実際のロジック。CallToolResult を返します |
extras.annotations | MCP ツールアノテーション。詳細は下表参照 |
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 は消費せず、ホストにも返しません。アプリケーションでこれらの情報が必要な場合は、ホスト側で独自にマッピングを管理してください。
CallToolResult 構造
isError: true を使用してください——例外はツール呼び出し全体を終了させ、AI は情報を得られません。isError は AI に「この呼び出しは失敗した。別の方法を試してください」と伝えます。
createSdkMcpServer() 完全なシグネチャ
{ type: 'sdk', name, instance } の形式で、そのまま options.mcpServers に渡せます。
⚠️ 同じ server 設定を複数の query() で再利用しないでください:各 query は独立した transport をバインドします。再利用に副作用はありませんが、「query 間で状態を共有する」機能は得られません——共有状態は handler クロージャの外のモジュールスコープに配置してください。
複数ツールの例
Stdio Server
サブプロセスの stdin/stdout を通じて MCP サーバーと通信します。NPM の@modelcontextprotocol/server-* シリーズはすべて stdio 実装です。
SSE / HTTP Server
ツール命名とホワイトリスト
CLI がモデルに MCP ツールを公開する際、統一的にプレフィックスを付加します:my_tools、ツール名 greet の場合、モデルが認識するツール名は mcp__my_tools__greet です。
tools:モデルに見えるツールを絞る
モデルに一部のツールだけを見せたい場合は tools を使います。CLI はリストにない組み込みツールをすべて disallow リストに追加します — 実質的な可視性ホワイトリストです:
⚠️ tools を渡さない=すべて開放:すべての組み込みツールと接続済み MCP サーバーのツールがモデルに公開されます。本番環境では明示的に列挙して範囲を絞ることを推奨します。
allowedTools:事前承認(可視性のホワイトリストではない)
allowedTools はリストされたツールを「常に許可」ルールに追加します — 呼び出し時に承認プロンプトをスキップしますが、リストにないツールは非表示にはなりません。低リスクの MCP ツールを無人承認したい場合に使います:
allowedTools を渡さない場合は事前承認ルールが無いだけで、モデルは依然としてすべてのツールを見て呼び出せます。書き込み系操作は通常の permissionMode 承認フローに従います。完全なセマンティクスは Permissions ドキュメントを参照してください。
allowedMcpServerNames:プロセス型サーバーのホワイトリスト
プロセス型(stdio/sse/http)サーバーのみをフィルタリングし、in-process サーバーには影響しません。strictMcpConfig: true と組み合わせることで、CLI がローカルの追加設定を読み込むことを拒否できます:
⚠️ allowedMcpServerNames を渡さない=すべて接続:宣言されたすべてのプロセス型サーバーが接続されます。範囲を絞りたい場合は明示的に列挙してください。in-process サーバーは常にこのフィールドの影響を受けません。
ランタイム管理(Query API)
query() が返す Query オブジェクトには MCP 関連のメソッドがいくつかあります。すべてのメソッドは control channel を通じて CLI と通信し、動作は非同期かつ冪等です。
⚠️ キャッシュの原則:MCP server の設定/認証状態の変更は tools リストを再構築し、セッション途中の変更はプロンプトプレフィックスキャッシュを破壊します。SDK は「状態を照会 + 最初のメッセージ前に認証を完了する」メソッドを提供します。server セット自体はoptions.mcpServersで起動時に一度設定し、必要に応じてquery()を再起動してください。
状態の照会
💡 MCP ハンドシェイクは CLI がinitializeを完了した後、最初のユーザーメッセージの前に発生します。initializationResult()が返された後に status を照会して初めて実際の結果を取得できます——ハンドシェイク IO には数百ミリ秒かかる場合があるため、pollUntilでconnectedを待ってから使用することを推奨します。
状態変化のサブスクリプション
MCP 状態はプッシュではなくプルで取得します:await q.mcpServerStatus() を呼び出すだけです。ポーリングが必要な場合は自分のコードで実装してください。
server セットの変更
プロンプトプレフィックスキャッシュの安定性を確保するため、server セットの変更は起動時に一度で完了します:| やりたいこと | 方法 |
|---|---|
| サーバーの追加/削除/置換 | options.mcpServers で設定;セットの変更が必要な場合は query() を再起動 |
| プロセス型 server の一部のみ有効化 | options.allowedMcpServerNames ホワイトリスト |
| 特定 server への再接続 | query() を再起動(再接続はツールを再発見し、キャッシュに影響) |
| 特定 server からのログアウト | query() 再起動時にそのトークンを含めない;または外部の認証情報ストアをクリアしてから再起動 |
コントロールリクエストのタイムアウト
control_cancel_request を書き込み、現在の Promise を reject します。
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ホスト自身が OAuth のタイミングを制御し、最初のユーザーメッセージを送信する前に完了します:elicitation/createを使用してクライアントを特定の URL にリダイレクトさせて認証を完了させます(典型例:GitHub MCP)。2つのパスは独立しており、同時にトリガーされることはありません:サーバー主導の場合mcpServerStatus()はneeds-authを示さず、mcpAuthenticateを呼ぶべきではなく、ホストはonElicitationでリクエストを受け取ります。詳細は Elicitation:サーバーによるユーザー入力要求 を参照してください。
| 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 パーミッション + クロスプロセスロック)を使用します。
Elicitation:サーバーによるユーザー入力要求
MCPelicitation/create は server → client 方向のリクエストで、クライアントにユーザーの前でインタラクションを表示させるためのものです。SDK はこのリクエストを Options.onElicitation を通じてホストに公開します。
2つのモード
| モード | トリガーシナリオ | 典型的な用途 |
|---|---|---|
'form' | サーバーが構造化入力を要求し、リクエストに requestedSchema(MCP 制限サブセットの JSON Schema)を含む | API キー入力、設定項目の入力、二次確認 |
'url' | サーバーがユーザーを特定の URL に誘導して操作を完了させ、リクエストに url + elicitationId を含む | サーバー独自の OAuth、デバイスコードアクティベーション、アカウント連携 |
notifications/elicitation/complete を送信します——SDK はこれを SDKElicitationCompleteMessage として Query のメッセージストリームにプッシュします。
コールバックシグネチャ
signal は q.close() / 中断時に abort されるため、長時間プロセスではチェックが必要です。
form モードの例
url モードの例(elicitation_complete との連携)
💡onElicitation内でブラウザのリダイレクトを await しないでください。URL モードの設計は:コールバックは即座にacceptを返し(=ユーザーがフローを開始した)、CLI は control チャネルをブロックしません。真の「完了」シグナルは後続のelicitation_completeメッセージから届きます。OAuth リダイレクト全体を await すると、control リクエストタイムアウト(controlRequestTimeoutMs)がトリガーされます。
OAuth パスとの境界
- CLI 主導の OAuth(
mcpAuthenticate/mcpSubmitOAuthCallbackUrl):トークンは qodercli Keychain に保存;mcpServerStatus()がneeds-authの時に駆動;onElicitationはトリガーされません。 - サーバー主導の elicit URL:トークンは server 内部;
mcpServerStatus()はneeds-authを示しません;onElicitationで受け取り、system/elicitation_completeで完了。
elicitation/create を送信するかどうかを確認してください——送信した場合はサーバー主導です。
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 バージョンの進化に伴い対応予定です。
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 参照 |
Query 上のメソッド
| メソッド | 説明 | 呼び出しタイミング |
|---|---|---|
mcpServerStatus() | 現在のすべての MCP サーバー状態を取得 | 任意のタイミング |
mcpAuthenticate(name, redirectUri?) | 能動的に OAuth を開始;{ authUrl?, requiresUserAction } を返す | 最初の streamInput 前 |
mcpSubmitOAuthCallbackUrl(name, url) | OAuth コールバックを送信 | 最初の streamInput 前 |
server セットの追加・削除・変更はoptions.mcpServers(起動時設定)+query()の再起動で完了してください。詳細は server セットの変更 を参照。
型リファレンス
status 列挙値:
| 値 | 意味 |
|---|---|
'pending' | 登録済み、接続未開始 |
'connecting' | ハンドシェイク中 |
'connected' | 接続済み、ツール呼び出し可能 |
'failed' | 接続失敗(error フィールドを参照) |
'needs-auth' | OAuth が必要、認証フローを実行してください |
'disabled' | 無効化(CLI 内部設定または外部状態による) |
ベストプラクティス
- 説明は AI 向けに記述:
tool()のdescriptionは AI がいつそのツールを選択するかを決定します。「何をするか、いつ使うか、何に使うべきでないか」を明確に記述してください。 - フィールドに
.describe()を付ける:Zod フィールドには必ず.describe(...)を付けてください。AI はこの情報を使って呼び出しパラメータを構築します。 - 失敗には
isErrorを使用し、例外をスローしない:AI に結果を見せてください。例外はモデルを困惑させ、リトライをトリガーする可能性があります。 - 読み取り専用 +
readOnlyHintを優先:書き込み操作は慎重に行い、canUseToolまたは hooks で二次確認を行ってください。 - サーバー名は短く:ツールプレフィックスに含まれるため、長い名前はトークンを浪費します。
- In-process の共有状態はモジュールスコープに配置:handler はクロージャですが、各 query は同じ server インスタンスを再利用します。
- OAuth は最初の
streamInput前に完了:mcpAuthenticate+mcpSubmitOAuthCallbackUrlを使用してください。セッション途中での認証完了は必然的にプロンプトプレフィックスキャッシュを破壊します。 - MCP 状態は
mcpServerStatus()でプル:push チャネルは廃止されています。必要に応じてポーリングしてください。 - 適切な
controlRequestTimeoutMsを設定:リモートサーバーのハンドシェイクは秒単位になる可能性があり、デフォルトの 60 秒で通常は十分ですが、CI 環境では明示的に指定してください。 - 分離には
strictMcpConfigを使用:ユーザーローカルのsettings.json/.mcp.jsonに宣言された MCP サーバーがアプリケーションに干渉することを防ぎます。