アーキテクチャ概要
- インプロセス:ツールは単なる Python の async 関数で、自分のプロセス内で実行されます。
create_sdk_mcp_serverで生成されたものは SDK の control channel を通じて CLI と通信し、別途サブプロセスを起動することはありません。 - 外部サーバー:設定でサブプロセスまたはリモート URL を宣言し、CLI が接続、発見、呼び出しを担当します。
3 つの接続方式
| 方式 | 設定項目 type | プロセス境界 | 適用シナリオ |
|---|---|---|---|
| インプロセス | 'sdk'(create_sdk_mcp_server で作成) | 同一プロセス | カスタム業務ツール、ホスト状態への直接アクセスが必要 |
| Stdio | 'stdio'(省略可能) | サブプロセス | 既存の MCP ツールパッケージ(@modelcontextprotocol/server-*) |
| SSE / HTTP | 'sse' / 'http' | リモート | リモートサービス、SaaS ツール、OAuth が必要なサービス |
query() / QoderSDKClient 内で異なる種類のサーバーを複数同時に登録できます。
💡mcp_serversにはstr/pathlib.Pathを渡すこともできます。これは JSON 設定ファイルのパスを指し、SDK は--mcp-config <path>として CLI に透過的に渡します。
インプロセスサーバー(推奨)
インプロセスツールは最もシンプルな拡張方法です。通常のasync 関数を定義し、デコレーターでスキーマを宣言するだけで、エージェントから呼び出し可能になります。@tool() / schema / handler の詳細な動作については tools.md を参照してください。本セクションでは MCP サーバーの組み立てに関する部分のみを扱います。
30 秒で開始
@tool() / create_sdk_mcp_server() 完全シグネチャ
| パラメータ | 説明 |
|---|---|
name(tool) | ツール名。完全修飾名は mcp__<server>__<name> になります |
description | モデル向けの説明。AI がいつ呼び出すかを決定します——What/When を明確に記述 |
input_schema | シンプルな dict / TypedDict / 完全な JSON Schema dict の 3 種類の書き方が可能。詳細は Tools Reference - input_schema を参照 |
annotations | MCP ツールアノテーション。詳細は下表参照 |
name(server) | サーバー名(ツールプレフィックス mcp__<name>__ を決定) |
version | デフォルト '1.0.0' |
tools | SdkMcpTool のリスト |
McpSdkServerConfig は {"type": "sdk", "name": ..., "instance": ...} の形式で、そのまま options.mcp_servers に渡せます。
annotations の実際のサポート状況
以下の 3 つのフィールドは SDK が実際に消費し、get_mcp_status().mcpServers[i].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 を検証不可能な参考情報として扱い(サーバーは過少・過大に申告できるため)、パーミッションパイプラインには敢えて取り込みません——取り込むとサーバーの自己評価に権威を付与してしまうためです。特定ツールを確実にブロックしたい場合はallowed_toolsホワイトリストまたは hooks を使用してください——annotation はホスト側での識別(get_mcp_status)と TUI 表示のためだけのものです。
idempotentHint と title は現在 SDK では消費されません——渡してもエラーにはなりませんが、CLI の動作にも影響せず、get_mcp_status() の戻り値にも現れません。アプリケーションでこれらの情報が必要な場合は、ホスト側で独自にマッピングを管理してください。
💡maxResultSizeCharsについて:Python SDK はToolAnnotations(maxResultSizeChars=...)を介してanthropic/maxResultSizeCharsをツールの_metaに書き込み、CLI はこれに基づいてデフォルトの 50K の戻り値長さ制限を緩和します。このフィールドは Python 側の追加機能です(TS は同名の annotation で公開されており、wire は同一です)。
handler の戻り値
is_error: True を使用してください。完全な content タイプの説明、Python 側と TS 側の動作の違い(resource_link がテキストにフォールバック、トップレベル _meta は透過しない、binary embedded resource はスキップ)については Tools Reference - CallToolResult を参照してください。
Handler のキャンセルシグナル
handler は第 2 引数としてToolInvocationContext を受け取ることができ、CLI が現在の呼び出しをキャンセルした際に extra.signal を介して協調的に終了できます。
⚠️ 同じサーバー設定を複数の query() で再利用しないでください:各 query は独立した transport をバインドします。再利用しても副作用はありませんが、「query をまたいで状態を共有する」機能は得られません——共有状態は handler クロージャの外のモジュールスコープに配置してください。
Stdio サーバー
サブプロセスの stdin/stdout を通じて MCP サーバーと通信します。NPM の@modelcontextprotocol/server-* シリーズはすべて stdio 実装です。
command が到達不能であったり起動に失敗したりしても、query 全体が落ちることはありません——該当サーバーの status は 'connected' 以外のままになり、他のサーバーには影響しません。
SSE / HTTP サーバー
'connected' 以外となり、他のサーバーには影響しません。OAuth が必要なリモートサービスについては OAuth 認証 を参照してください。
ツール命名とホワイトリスト
CLI がモデルに MCP ツールを公開する際、統一的にプレフィックスを付加します。my_tools、ツール名 greet の場合、モデルが認識するツール名は mcp__my_tools__greet です。サーバー名にはハイフンなどの特殊文字を含めることが許されています(my-tools → mcp__my-tools__<tool>)。
tools:モデルに見えるツール集合を制限
モデルに一部のツールだけを見せたい場合は tools を使います。CLI はリストにない組み込みツールをすべて disallow リストに追加するため、実質的な「ホワイトリスト」セマンティクスとなります。
⚠️ tools を渡さない=すべて開放:すべての組み込みツール + 接続済み MCP サーバーのツールがモデルに公開されます。本番環境では明示的に列挙して範囲を絞ることを推奨します。
allowed_tools:事前承認(可視性ホワイトリストではない)
allowed_tools はリストされたツールを「自動許可」ルールに追加します——呼び出し時に承認プロンプトをスキップしますが、リストにないツールは非表示にはなりません。低リスクの MCP ツールを承認なしで使えるようにする際によく使われます。
allowed_tools を渡さない場合は事前承認ルールが無いだけで、モデルは依然としてすべてのツールを見て呼び出せます。書き込み操作は通常の permission_mode 承認フローに従います。完全なセマンティクスは Permissions ドキュメントを参照してください。
allowed_mcp_server_names:プロセス型サーバーのホワイトリスト
プロセス型(stdio/sse/http)サーバーのみをフィルタリングし、インプロセスサーバーには影響しません。strict_mcp_config=True と組み合わせることで、CLI がローカルの追加設定を読み込むことを拒否できます。
⚠️ allowed_mcp_server_names を渡さない=すべて開放:宣言されたすべてのプロセス型サーバーが接続されます。範囲を絞りたい場合は明示的に列挙してください。インプロセスサーバーは常にこのフィールドの影響を受けません。
ランタイム管理(QoderSDKClient)
query() は使い捨てのイテレータで、途中でサーバーや認証を変更することはできません。MCP のランタイム管理を行うには QoderSDKClient を使用する必要があります。状態照会、OAuth、サーバーの追加・削除、再接続 / トグルなどがすべて公開メソッドとして提供されています。
⚠️ キャッシュの原則:MCP サーバー設定 / 認証状態の変更は tools リストを再構築するため、セッション途中の変更はプロンプトプレフィックスキャッシュを破壊します。SDK は「状態の照会 + 最初のメッセージ前に認証を完了する」ためのメソッドを提供します。サーバーセット自体はoptions.mcp_serversで起動時に一度設定し、必要に応じて新しいQoderSDKClientを作成してください。
状態の照会
💡 MCP ハンドシェイクは CLI がinitializeを完了した後、最初のユーザーメッセージの前に発生します。QoderSDKClient.connect()は initialize の戻りまで待機します。ハンドシェイク IO に数百ミリ秒かかる場合があるため、必要に応じてget_mcp_status()をconnectedになるまで自前でポーリングしてください。
状態変化の購読
次の 2 つの方式のいずれかを選択できます。 方式 1(推奨):options にon_mcp_status_change コールバックを設定すると、状態が変化するたびに 1 回呼び出されます。
system/mcp_status_change をフィルタリングします。コールバックとメッセージストリームは同じ payload です。コールバックはフィルタリング処理を省略できる便利機能です。
ランタイムでのサーバー追加・削除 / 再接続 / 起動・停止
| メソッド | 用途 |
|---|---|
client.set_mcp_servers(servers) | 全量の desired マッピングで現在の MCP 設定を置換。{added, removed, errors} を返す |
client.reconnect_mcp_server(name) | 指定サーバーを再接続。通常は 'failed' 状態からの復帰に使用 |
client.toggle_mcp_server(name, enabled) | 特定サーバーを有効化 / 無効化。無効化すると接続が切断され、そのツールも削除される |
⚠️ これら 3 つのメソッドはすべて tools リストの再構築を引き起こすため、プロンプトプレフィックスキャッシュを破壊します。本番環境では起動時に mcp_servers を完全に設定することを優先し、これらの API はデバッグやローカル開発用途に留めることを推奨します。
コントロールリクエストのタイムアウト
control_cancel_request を書き込み、現在の Future を reject します。
OAuth 認証
リモート MCP サーバー(HTTP/SSE)は OAuth を必要とすることがよくあります。CLI には完全な OAuth 2.0 + PKCE + Dynamic Client Registration(RFC 7591)実装が組み込まれています。Python SDK は inbound(CLI が能動的にホストに OAuth 完了を要求) と outbound(ホストが能動的に OAuth をトリガー) の 2 つのパスを公開しており、ホストの形態に応じて選択できます。⚠️ キャッシュの原則:OAuth 完了後、CLI はサーバーに再接続して tools を再発見するため、セッション途中での認証完了は必然的にプロンプトプレフィックスキャッシュを破壊します。最初のユーザーメッセージを送信する前に認証を完了し、tools リストが安定してから対話を開始することを推奨します。
💡 このセクションは CLI 主導の OAuth のみをカバーします:CLI 自身が metadata discovery、PKCE、token 交換、token 永続化を行います。別のサーバー主導の認証パスもあります——サーバーが MCP elicitation/create を使用してクライアントを特定の URL にリダイレクトさせて認証を完了させます。2 つのパスは独立しており、同時にトリガーされることはありません。詳細は Elicitation:サーバーによるユーザー入力要求 を参照してください。
Inbound:on_mcp_oauth_required コールバック
CLI がハンドシェイク中にサーバーで OAuth が必要であることを検出すると、control_request を介して McpOAuthRequest を SDK に送信し、SDK がホストの on_mcp_oauth_required コールバックを呼び出します。ホストは以下のいずれかの resolution を返します。
| 戻り値の型 | 意味 |
|---|---|
OAuthToken または {"token": OAuthToken} | ホストが OAuth フロー全体を完了し、token を直接 CLI に注入 |
{"callbackUrl": "..."} | ホストは完全なコールバック URL(code / state を含む)のみを取得し、CLI が解析して token に交換 |
{"code": "...", "state": "..."} | ホスト自身が code を解析し、CLI に直接返す |
None | 拒否。CLI は該当サーバーを failed としてマーク |
Outbound:ホストが能動的に認証を駆動
ホスト自身の UI に「Sign in」入口がある場合、能動的に呼び出すことができます。| メソッド | 用途 | 呼び出しタイミング |
|---|---|---|
client.mcp_authenticate(name, redirect_uri=None) | OAuth を起動。{authUrl?, requiresUserAction} を返す。サイレント更新時は requiresUserAction=False で UI 不要 | 最初の user message 前 |
client.mcp_submit_oauth_callback_url(name, callback_url) | 完全なコールバック URL(code/state を含む)を送信 | 最初の user message 前 |
client.inject_mcp_token(name, token) | ホスト自身が OAuth フロー全体を完了し、OAuthToken を直接 CLI に注入 | 最初の user message 前 |
client.mcp_clear_auth(name) | CLI に保存された OAuth 認証情報を削除。「sign out」相当 | 任意のタイミング。次回のツール呼び出しで再認証がトリガーされる |
redirect_uri はオプションで、デフォルトの OAuth コールバック先を上書きします(Electron カスタムプロトコル、社内ネットワークコールバックアドレスなど)。
CLI はデフォルトで token をシステム Keychain(macOS / Linux Secret Service)に保存し、フォールバックとして ~/.qoder/mcp-oauth-tokens.json(パーミッション 0o600 + クロスプロセスロック)を使用します。
Elicitation:サーバーによるユーザー入力要求
MCPelicitation/create は server → client 方向のリクエストで、クライアントにユーザーの前でインタラクションを表示させるためのものです(form モードは構造化入力を収集、url モードはユーザーを特定の URL に誘導して操作を完了させる)。
✅ Python SDK は TS SDK に対応済み:QoderAgentOptions.on_elicitationはElicitationResultを返す async コールバックを受け取り、シグネチャは TS 版と一致しています。コールバック未設定時は、SDK はデフォルト契約に従って自動的に{"action": "cancel"}を応答します。Elicitation/ElicitationResultの 2 種類の hook イベントは引き続き並行してトリガーされ、読み取り専用の観察チャネルとして機能します。
⚠️ 現時点で CLI はelicitation.urlcapability を advertise していません。サーバー側のelicit({mode: 'url'})は CLI から直接拒否されます(MCP error -32602: Client does not support URL-mode elicitation requests)。そのため URL モードの elicit は SDK に到達せず、system/elicitation_complete通知も現状の CLI ではトリガーされません。CLI が URL capability を有効化すれば、このパスは自動的に接続されます。
on_elicitation で elicit に応答
- フィールド名は TS SDK の camelCase に従います(
serverName / elicitationId / requestedSchema / displayName)。CLI の snake_case payload は SDK が自動的に変換します。 Noneを返すことは{"action": "cancel"}と等価で、ホストはフォールバック経路で簡単にあきらめることができます。mcp.types.ElicitResultPydantic モデルを返すこともできます(SDK がmodel_dumpを呼び出します)。
Elicitation の観察(hook チャネル)
on_elicitation を実装した後でも、Elicitation / ElicitationResult の hook は引き続き並行してトリガーされます——これらは読み取り専用の観察チャネルであり、意思決定には関与しません。
| Hook イベント | タイミング | payload TypedDict |
|---|---|---|
Elicitation | サーバーリクエスト到達時 | ElicitationHookInput — mcp_server_name / message / mode / elicitation_id? / requested_schema? / url? / title? |
ElicitationResult | SDK / host の応答完了後 | ElicitationResultHookInput — mcp_server_name / action / mode / elicitation_id? / content? |
OAuth パスとの境界
- CLI 主導の OAuth(
mcp_authenticate/inject_mcp_token/on_mcp_oauth_required):token は qodercli Keychain に保存。get_mcp_status()がneeds-authの時に駆動。Elicitation hook はトリガーされません。 - サーバー主導の elicit:token はサーバー内部に保存。
get_mcp_status()はneeds-authを示さない。on_elicitationコールバックで意思決定(未登録時は SDK が自動で cancel)。
Options クイックリファレンス
| フィールド | 型 | デフォルト | 説明 |
|---|---|---|---|
mcp_servers | dict[str, McpServerConfig] | str | Path | {} | サーバー名 → 設定。または JSON 設定ファイルのパス |
allowed_mcp_server_names | list[str] | [] | プロセス型サーバーのホワイトリスト(インプロセスには影響なし)。空リスト=すべて開放 |
strict_mcp_config | bool | False | CLI がユーザー設定ファイルから追加 MCP を読み込むことを禁止 |
tools | list[str] | ToolsPreset | None | None | モデルに見えるツールのホワイトリスト。渡さない=すべての組み込み + MCP ツールが見える |
allowed_tools | list[str] | [] | 事前承認リスト(承認プロンプトをスキップ。可視性は制御しない)。空リスト=事前承認ルール無し |
disallowed_tools | list[str] | [] | 明示的に拒否するツール。allow よりも優先 |
control_request_timeout_ms | int | 60_000 | control リクエストのタイムアウト(mcp 系列を含む)。0 で無効化 |
on_mcp_oauth_required | OnMcpOAuthRequired | None | None | CLI がサーバーの OAuth 必要を検出した際にトリガー |
on_mcp_status_change | OnMcpStatusChange | None | None | サーバーステータスが変化するたびにトリガー。system/mcp_status_change ストリームをフィルタリングするのと等価 |
on_elicitation | OnElicitation | None | None | サーバーが MCP elicitation/create でユーザー入力を要求した際にホストが意思決定。未設定時は SDK が自動的に cancel |
hooks['Elicitation'] | list[HookMatcher] | – | サーバーがユーザー入力を要求した際の読み取り専用観察 hook(意思決定は on_elicitation 経由) |
QoderSDKClient のメソッド
| メソッド | 説明 | 呼び出しタイミング |
|---|---|---|
get_mcp_status() | 現在のすべての MCP サーバー状態を取得 | 任意のタイミング |
set_mcp_servers(servers) | MCP サーバー設定を全量置換。{added, removed, errors} を返す | 任意のタイミング(プレフィックスキャッシュを破壊する) |
reconnect_mcp_server(name) | 指定サーバーを再接続 | 任意のタイミング |
toggle_mcp_server(name, enabled) | サーバーを有効化 / 無効化 | 任意のタイミング |
mcp_authenticate(name, redirect_uri=None) | OAuth を能動的に開始 | 最初の user message 前 |
mcp_submit_oauth_callback_url(name, callback_url) | OAuth コールバック URL を送信 | 最初の user message 前 |
inject_mcp_token(name, token) | ホスト自身が OAuth フローを完了した後、token を注入 | 最初の user message 前 |
mcp_clear_auth(name) | 保存済みの OAuth 認証情報を削除 | 任意のタイミング |
型リファレンス
McpServerStatus.status の列挙値(McpServerConnectionStatus):
| 値 | 意味 |
|---|---|
'pending' | 登録済み、接続未開始 |
'connecting' | ハンドシェイク中 |
'connected' | 接続済み、ツール呼び出し可能 |
'failed' | 接続失敗(error フィールドを参照) |
'needs-auth' | OAuth が必要。認証フローを実行してください |
'disabled' | 無効化(CLI 内部設定または toggle_mcp_server による) |
ベストプラクティス
- 説明は AI 向けに記述:
@toolのdescriptionは AI がいつそのツールを選択するかを決定します。「何をするか、いつ使うか、何に使うべきでないか」を明確に記述してください。 - パラメータに
Annotatedを付ける:シンプルな dict / TypedDict のフィールドにはAnnotated[type, "..."]を記述してください。AI はこの情報を使って呼び出しパラメータを構築します。 - 失敗には
is_error: Trueを使い、例外をスローしない:AI に結果を見せてください。完全な比較は ツール使用ガイド - SDK が tool 戻り値のエラーを処理する方法を参照してください。 - 読み取り専用 +
readOnlyHintを優先:書き込み操作は慎重に行い、can_use_toolまたは hooks で二次確認を行ってください。 - サーバー名は短く:ツールプレフィックスに含まれるため、長すぎる名前はトークンを浪費します。
- インプロセスの共有状態はモジュールスコープに配置:handler はクロージャですが、各 query は同じ server インスタンスを再利用します。
- OAuth は最初の user message 前に完了:
mcp_authenticate+mcp_submit_oauth_callback_url、またはon_mcp_oauth_requiredinbound コールバック、またはinject_mcp_tokenを使用してください。セッション途中で認証を完了すると必然的にプロンプトプレフィックスキャッシュを破壊します。 - MCP 状態は
get_mcp_status()またはon_mcp_status_changeで取得:push チャネル(status change message)は引き続き利用可能です。必要に応じてどちらかを選んでください。 - 適切な
control_request_timeout_msを設定:リモートサーバーのハンドシェイクは秒単位になる可能性があります。デフォルトの 60 秒で通常は十分ですが、OAuth でユーザー操作を待つ場合は大きめに、CI 環境では明示的に指定してください。 - 分離には
strict_mcp_configを使用:ユーザーローカルの~/.qoder/settings.json/.mcp.jsonで宣言された MCP サーバーがアプリケーションに干渉することを防ぎます。