MCP(Model Context Protocol)は AI Agent が外部ツールを呼び出すためのオープンプロトコルです。SDK では MCP Server を定義し、Agent にツールを設定できます。接続管理、ツール発見、OAuth、状態同期などのランタイム処理は基盤の CLI が担います。
3つの方式は併用できます——同一セッションに異なるタイプのサーバーを複数登録できます。
In-process ツールは最も直接的な拡張方法です。普通の async 関数に schema 宣言を付けるだけで Agent から呼び出せます。ツール作成 / schema / ハンドラーの詳細は Tools を参照してください。このセクションは MCP server の組み立てに関する部分のみを扱います。
戻り値は
以下の3つのフィールドは SDK が実際に消費し、MCP 状態クエリ(TypeScript の
ビジネス上の失敗にはエラーフラグ(
Python のハンドラーは第2引数
サブプロセスの stdin/stdout を通じて MCP サーバーと通信します。NPM の
リモート URL が到達不能でも同様に query がハングすることはなく、サーバーステータスは
CLI がモデルに MCP ツールを公開する際、統一的にプレフィックスを付加します:
たとえば server 名
モデルに一部のツールだけを見せたい場合は
事前承認リストを渡さないのは、事前承認ルールがないという意味だけです——モデルは引き続きすべてのツールを見て呼び出せます。書き込み操作が権限モードに従って承認フローを通るだけです。完全なセマンティクスは Permissions ドキュメント を参照してください。
TypeScript のランタイム管理は
prompt prefix キャッシュを安定させるため、server セットの変更は起動時に一括で済ませることを推奨します:
タイムアウト後、SDK は自動的に
リモート MCP サーバー(HTTP/SSE)は OAuth を必要とすることがよくあります。CLI には完全な OAuth 2.0 + PKCE + Dynamic Client Registration(RFC 7591)実装が内蔵されています。
ホスト自身が OAuth のタイミングを制御し、最初のユーザーメッセージを送信する前に完了します:
Python SDK は inbound 経路もサポートします:CLI がハンドシェイク中に server の OAuth 必要性を検出すると、control_request 経由で
MCP
URL モードは非同期で完了します:server が自身のコールバックでユーザーの認可を受け取ると
Python の注意事項:
ホストは hooks でも elicitation を観察 / インターセプトできます:
アーキテクチャ概要
- In-Process:ツールはあなた自身のプロセスで動く普通の async 関数(JS / Python)です。server インスタンスは SDK の control channel 経由で CLI と通信し、追加のプロセスは起動しません。
- External:設定でサブプロセスまたはリモート URL を宣言し、CLI が接続、発見、呼び出しを担当します。
3つの接続方式
| 方式 | 設定項目 type | プロセス境界 | 適用シナリオ |
|---|---|---|---|
| In-Process | 'sdk'(createSdkMcpServer / create_sdk_mcp_server で作成) | 同一プロセス | ホストの状態への直接アクセスが必要なカスタム業務ツール |
| Stdio | 'stdio'(省略可能) | サブプロセス | 既存の MCP ツールパッケージ(@modelcontextprotocol/server-*) |
| SSE / HTTP | 'sse' / 'http' | リモート | リモートサービス、SaaS ツール、OAuth が必要なサービス |
💡 Python ではmcp_serversにstr/pathlib.Pathも渡せます。JSON 設定ファイルのパスを指すと、SDK が--mcp-config <path>として CLI に透過します。
In-Process Server(推奨)
In-process ツールは最も直接的な拡張方法です。普通の async 関数に schema 宣言を付けるだけで Agent から呼び出せます。ツール作成 / schema / ハンドラーの詳細は Tools を参照してください。このセクションは MCP server の組み立てに関する部分のみを扱います。
30秒で開始
完全なシグネチャ
| パラメータ | 説明 |
|---|---|
name(tool) | ツール名。完全修飾名は mcp__<server>__<name> になります |
description | モデル向けの説明。AI がいつ呼び出すかを決定します——What/When を明確に記述 |
inputSchema / input_schema | TypeScript は Zod raw shape(z.object(...) ではない)。Python はシンプル dict / TypedDict / 完全な JSON Schema dict をサポート |
handler | 実際のロジック。CallToolResult を返します |
annotations | MCP ツールアノテーション。詳細は下表参照 |
name(server) | サーバー名(ツールプレフィックス mcp__<name>__ を決定) |
version | デフォルト '1.0.0' |
tools | ツールのリスト |
{ type: 'sdk', name, instance } の形で、そのまま MCP servers 設定に入れられます。
⚠️ 同じ server 設定を複数の query() で再利用しないでください:各 query は独立した transport をバインドします。再利用に副作用はありませんが、「query 間で状態を共有する」機能は得られません——共有状態は handler クロージャの外のモジュールスコープに配置してください。
annotations の実際のサポート状況
以下の3つのフィールドは SDK が実際に消費し、MCP 状態クエリ(TypeScript の mcpServerStatus().tools[i].annotations、Python の 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オブジェクトには明示的に設定されたフィールドのみ含まれます。 ⚠️ これら3つのフィールドは auto モードの権限判断に影響しません。CLI は server が自己宣言した annotation を検証不能なヒントとみなし(server は自由に過少/過大宣言できるため)、権限パイプラインに入れません。特定のツールを確実に拒否したい場合は、ツール許可リストまたは hooks で遮断してください——annotation はホスト側の識別と TUI 表示のためだけのものです。
idempotentHint と title は現在 SDK が消費しません——渡してもエラーにはなりませんが、SDK は消費せず、ホストにもエコーしません。アプリでこれらの情報が必要なら、ホスト側で独自にマッピングを保持してください。
💡maxResultSizeCharsについて:Python SDK はToolAnnotations(maxResultSizeChars=...)でツールの_metaにanthropic/maxResultSizeCharsを書き込み、CLI がデフォルト 50K の戻り値長制限を緩和します(TS も同名 annotation で公開し、ワイヤ形式は同一)。
CallToolResult 構造
isError: true / is_error: True)を使い、例外を投げないでください——例外は tool call 全体を終了させ AI に情報が届きません。エラーフラグなら AI は「この呼び出しは失敗した、別の方法を試そう」と判断できます。Python 側と TS 側の動作差異(resource_link のテキスト降格、トップレベル _meta の非透過など)は Tools を参照してください。
ハンドラーのキャンセルシグナル(Python)
Python のハンドラーは第2引数 ToolInvocationContext を受け取れます。CLI が実行中の呼び出しをキャンセルした際、extra.signal で協調的に終了できます:
Stdio Server
サブプロセスの stdin/stdout を通じて MCP サーバーと通信します。NPM の @modelcontextprotocol/server-* シリーズはすべて stdio 実装です。
command が到達不能または起動失敗でも query 全体は巻き込まれません——該当 server の status が非 'connected' のままになるだけで、他の server は影響を受けません。
SSE / HTTP Server
'connected' 以外となり、他のサーバーには影響しません。OAuth が必要なリモートサービスについては OAuth 認証 を参照してください。
ツール命名とホワイトリスト
CLI がモデルに MCP ツールを公開する際、統一的にプレフィックスを付加します:
my_tools、tool 名 greet なら、モデルに見えるツール名は mcp__my_tools__greet です。server 名はハイフンなどの特殊文字を含められます(my-tools → mcp__my-tools__<tool>)。
tools:モデルに見えるツールを絞る
モデルに一部のツールだけを見せたい場合は tools を使います。CLI はリストにない組み込みツールをすべて disallow リストに追加します — 実質的な可視性ホワイトリストです:
⚠️ tools を渡さない=すべて開放:すべての組み込みツールと接続済み MCP サーバーのツールがモデルに公開されます。本番環境では明示的に列挙して範囲を絞ることを推奨します。
事前承認リスト(可視性のホワイトリストではない)
allowedTools / allowed_tools は列挙したツールを「自動許可」ルールに加えます——呼び出し時に権限プロンプトをスキップしますが、列挙されていないツールが隠されるわけではありません。低リスクの MCP ツールを承認不要にする用途が一般的です:
プロセス型 server のホワイトリスト
allowedMcpServerNames / allowed_mcp_server_names はプロセス型(stdio/sse/http)サーバーのみをフィルタし、in-process サーバーには影響しません。strictMcpConfig: true / strict_mcp_config=True と組み合わせると、CLI がローカルの追加設定を読み込むのを拒否できます:
⚠️ このホワイトリストを渡さないと全開放になります:宣言されたすべてのプロセス型 server が接続されます。絞りたい場合は明示的に列挙してください。in-process server はこのフィールドの影響を受けません。
ランタイム管理
TypeScript のランタイム管理は query() が返す Query オブジェクトを通じて行います。Python では単発の query() イテレーターは途中で server や認証を変更できないため、QoderSDKClient を使う必要があります。すべてのメソッドは control channel 経由で CLI と通信し、非同期かつ冪等です。
⚠️ キャッシュ原則:MCP server 設定 / 認証状態の変更は tools リストを再構築し、セッション途中の変更は prompt prefix キャッシュを壊します。SDK は「状態のクエリ + 最初のメッセージ前の認証完了」のためのメソッドを提供します。server セット自体は起動時に options で一括設定し、変更が必要ならセッションを再起動してください。
状態の照会
💡 MCP ハンドシェイクは CLI のinitialize完了後、最初のユーザーメッセージ前に行われます。初期化結果が返った後に status を照会しないと実際の結果は得られません——ハンドシェイク IO は数百ミリ秒かかることがあるため、connectedになるまでポーリングすることを推奨します。
状態変化のサブスクリプション
- TypeScript:MCP 状態はプッシュではなくプルです——
await q.mcpServerStatus()を呼び、必要なら自分のコードでポーリングします。 - Python:プルに加えて、options に
on_mcp_status_changeコールバックを設定でき、状態変化のたびに1回呼ばれます。あるいはメッセージストリームでsystem/mcp_status_changeをフィルタしてもかまいません。コールバックとストリームは同じ payload です。
server セットの変更
prompt prefix キャッシュを安定させるため、server セットの変更は起動時に一括で済ませることを推奨します:
| やりたいこと | TypeScript | Python |
|---|---|---|
| server の追加 / 削除 / 置換 | options.mcpServers で設定。セット変更時は query() を再起動 | 起動時に mcp_servers を設定。ランタイムでは client.set_mcp_servers(servers) で全量置換({added, removed, errors} を返す) |
| 一部のプロセス型 server のみ有効化 | allowedMcpServerNames ホワイトリスト | allowed_mcp_server_names ホワイトリスト |
| 特定 server の再接続 | query() を再起動 | client.reconnect_mcp_server(name)。主に 'failed' 状態からの復旧に使用 |
| server の有効化 / 無効化 | query() を再起動 | client.toggle_mcp_server(name, enabled)。無効化すると切断され、そのツールが下ろされる |
| 特定 server からのサインアウト | token なしで query() を再起動 | client.mcp_clear_auth(name) |
⚠️ Python のこれらのランタイムメソッドはいずれも tools リストを再構築するため、prompt prefix キャッシュを壊します。本番環境では起動時にすべて設定し、これらの API はデバッグとローカル開発用に留めてください。
コントロールリクエストのタイムアウト
control_cancel_request を書き込み、保留中のリクエストを reject します。
OAuth 認証
リモート MCP サーバー(HTTP/SSE)は OAuth を必要とすることがよくあります。CLI には完全な OAuth 2.0 + PKCE + Dynamic Client Registration(RFC 7591)実装が内蔵されています。
⚠️ キャッシュ原則:OAuth 完了後、CLI は server に再接続し tools を再発見します——セッション途中の認証完了は必ず prompt prefix キャッシュを壊します。最初のユーザーメッセージ送信前に認証を完了し、tools リストが安定してから会話を始めることを推奨します。
💡 このセクションは CLI 主導の OAuth のみを扱います:CLI 自身が metadata discovery、PKCE、token 交換、token 永続化を行います。もう1つサーバー主導の認証経路があります——server が MCP elicitation/create で client をある URL に誘導して認可を完了させる方式です(典型例:GitHub MCP)。2つの経路は独立しており、同時にトリガーされることはありません。詳細は Elicitation:サーバーによるユーザー入力要求 を参照してください。
ホスト主導の認証(outbound)
ホスト自身が OAuth のタイミングを制御し、最初のユーザーメッセージを送信する前に完了します:
| メソッド(TypeScript / Python) | 用途 | 呼び出しタイミング |
|---|---|---|
mcpAuthenticate(name, redirectUri?) / mcp_authenticate(name, redirect_uri=None) | OAuth を開始。{ authUrl?, requiresUserAction } を返す。サイレント更新成功時は requiresUserAction: false で UI 不要 | 最初のユーザーメッセージ前 |
mcpSubmitOAuthCallbackUrl(name, url) / mcp_submit_oauth_callback_url(name, callback_url) | 完全なコールバック URL(code/state 含む)を提出 | 最初のユーザーメッセージ前 |
inject_mcp_token(name, token)(Python のみ) | ホストが OAuth 全体を自前で実行し、OAuthToken を CLI に直接注入 | 最初のユーザーメッセージ前 |
mcp_clear_auth(name)(Python のみ) | CLI 保存の OAuth 資格情報を削除。「サインアウト」に相当 | いつでも。次のツール呼び出しで再認証がトリガーされる |
redirectUri / redirect_uri は省略可能で、デフォルトの OAuth コールバック先(Electron カスタムプロトコル、社内ネットワークのコールバックアドレスなど)を上書きします。
CLI はデフォルトでトークンをシステム Keychain(macOS / Linux Secret Service)に保存し、フォールバックとして ~/.qoder/mcp-oauth-tokens.json(0o600 パーミッション + クロスプロセスロック)を使用します。
Inbound:on_mcp_oauth_required コールバック(Python)
Python SDK は inbound 経路もサポートします:CLI がハンドシェイク中に server の 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 としてマーク |
Elicitation:サーバーによるユーザー入力要求
MCP elicitation/create は server → client 方向のリクエストで、client にユーザーへのインタラクション表示を求めます。SDK はこれを onElicitation(TypeScript)/ on_elicitation(Python)でホストに公開します。
2つのモード
| モード | トリガーシナリオ | 典型的な用途 |
|---|---|---|
'form' | サーバーが構造化入力を求める。リクエストに requestedSchema(MCP 制限サブセットの JSON Schema)が付く | API key 入力、設定項目の記入、二次確認 |
'url' | サーバーがユーザーをある URL に誘導する。リクエストに url + elicitationId が付く | サーバー自前の OAuth、デバイスコード認証、アカウント連携 |
notifications/elicitation/complete を送信し、SDK はそれを elicitation complete メッセージとしてメッセージストリームに投影します。
⚠️ qodercli は現在 MCP capability 宣言でelicitation: {}のみを送信します({ form: {} }と等価)。そのため現時点でリモート server から client に到達するのは form モードのみです。URL モードはプロトコル層では完全ですが、CLI がelicitation.urlcapability を明示的に宣言する必要があります——今後の CLI バージョンで対応され次第、この経路は自動的に開通します。
コールバックシグネチャ
- フィールド名は TS SDK の camelCase に従います(
serverName / elicitationId / requestedSchema / displayName)。CLI の snake_case payload は SDK が自動的に変換します。 Noneを返すと{"action": "cancel"}と等価です。コールバック未設定時、SDK はデフォルト契約に従い自動的に cancel を返します。mcp.types.ElicitResultPydantic モデルを返すこともできます(SDK がmodel_dumpを呼び出します)。
signal が q.close() / 中断時に abort されるため、長いフローではチェックしてください。
form モードの例
URL モードの例(elicitation_complete との連携)
💡 elicitation コールバック内でブラウザーの戻りを await しないでください。URL モードの設計では、コールバックは即座にaccept(=ユーザーがフローを開始した)を返し、CLI は control チャンネルをブロックしません。本当の「完了」シグナルは後続のelicitation_completeメッセージです。OAuth リダイレクト全体を await すると、control リクエストのタイムアウトが発生します。
OAuth パスとの境界
- CLI 主導 OAuth(
mcpAuthenticate/mcp_authenticateなど):token は qodercli の Keychain に保存。MCP 状態がneeds-authのときに駆動。elicitation コールバックはトリガーされない。 - サーバー主導 elicit URL:token は server 内部に保存。MCP 状態が
needs-authになることはない。elicitation コールバックで受け止め、elicitation_completeメッセージで完了。
elicitation/create を送るかどうかを見てください——送るならサーバー主導です。
Hook チャネル
ホストは hooks でも elicitation を観察 / インターセプトできます:
| Hook イベント | タイミング | 説明 |
|---|---|---|
Elicitation | server リクエスト到達時 | TypeScript では onElicitation より優先され、自動 accept / decline / cancel(UI の短絡)または素通しが可能。Python では読み取り専用の観察チャンネルで、判断は on_elicitation が担う |
ElicitationResult | ユーザー応答後 | TypeScript は action / content の書き換えや block が可能。Python は読み取り専用 |
Notification(type=elicitation_complete) | URL モード完了通知の到達時 | IDE / システム通知のトリガー |
Options クイックリファレンス
| フィールド(TypeScript / Python) | デフォルト | 説明 |
|---|---|---|
mcpServers / mcp_servers | – | サーバー名 → 設定。Python は JSON 設定ファイルパスも受け付ける |
allowedMcpServerNames / allowed_mcp_server_names | – | プロセス型サーバーのホワイトリスト(in-process には影響しない)。省略すると全開放 |
strictMcpConfig / strict_mcp_config | false | CLI がユーザー設定ファイルから追加の MCP を読み込むのを禁止 |
tools / tools | – | モデル可視ツールのホワイトリスト。省略すると組み込み + MCP ツールすべてが可視 |
allowedTools / allowed_tools | – | 事前承認リスト(権限プロンプトをスキップ、可視性は制御しない)。省略すると事前承認ルールなし |
disallowedTools / disallowed_tools | – | 明示的に拒否するツール。allow より優先 |
controlRequestTimeoutMs / control_request_timeout_ms | 60_000 | control リクエストのタイムアウト(mcp 系を含む)。0 で無効化 |
onElicitation / on_elicitation | – | MCP server がユーザー入力を要求したときに発火(form / url の2モード) |
on_mcp_oauth_required(Python のみ) | – | CLI が server の OAuth 必要性を検出したときに発火 |
on_mcp_status_change(Python のみ) | – | server の状態変化ごとに発火。system/mcp_status_change ストリームのフィルタと等価 |
ランタイムメソッド早見表
| メソッド(TypeScript / Python) | 説明 | 呼び出しタイミング |
|---|---|---|
mcpServerStatus() / get_mcp_status() | 現在のすべての MCP サーバー状態を取得 | いつでも |
mcpAuthenticate(...) / mcp_authenticate(...) | OAuth を能動的に開始。{ authUrl?, requiresUserAction } を返す | 最初のユーザーメッセージ前 |
mcpSubmitOAuthCallbackUrl(...) / mcp_submit_oauth_callback_url(...) | OAuth コールバックを提出 | 最初のユーザーメッセージ前 |
set_mcp_servers(servers)(Python のみ) | MCP server 設定を全量置換。{added, removed, errors} を返す | いつでも(prefix キャッシュを壊す) |
reconnect_mcp_server(name)(Python のみ) | 指定 server を再接続 | いつでも |
toggle_mcp_server(name, enabled)(Python のみ) | server の有効化 / 無効化 | いつでも |
inject_mcp_token(name, token)(Python のみ) | ホストが OAuth 完走後に token を注入 | 最初のユーザーメッセージ前 |
mcp_clear_auth(name)(Python のみ) | 保存済み OAuth 資格情報を削除 | いつでも |
TypeScript では server セットの増減・変更はoptions.mcpServers(起動時設定)+query()の再起動で行ってください。
型リファレンス
McpServerStatus.status の列挙値:
| 値 | 意味 |
|---|---|
'pending' | 登録済み、接続未開始 |
'connecting' | ハンドシェイク中 |
'connected' | 接続済み、ツール呼び出し可能 |
'failed' | 接続失敗(error フィールドを参照) |
'needs-auth' | OAuth が必要、認証フローを実行してください |
'disabled' | 無効化(CLI 内部設定または外部状態による) |
ベストプラクティス
- 説明は AI に向けて書く:ツールの
descriptionが AI の選択タイミングを決めます。「何をするか、いつ使うか、何に使うべきでないか」を明確に。 - フィールドに説明を付ける:TypeScript の Zod フィールドには必ず
.describe(...)、Python はAnnotated[type, "..."]を。AI はこれらの情報で呼び出し引数を構築します。 - 失敗はエラーフラグで、例外を投げない:AI に結果を見せましょう。例外はモデルを混乱させ、リトライを誘発することがあります。
- 読み取り専用 +
readOnlyHintを優先:書き込み操作は慎重に。権限コールバックまたは hooks による二次確認を併用してください。 - サーバー名は短く:ツールプレフィックスに含まれるため、長い名前はトークンを浪費します。
- In-process の共有状態はモジュールスコープに配置:handler はクロージャですが、各 query は同じ server インスタンスを再利用します。
- OAuth は最初のユーザーメッセージ前に完了:セッション途中の認証完了は必ず prompt prefix キャッシュを壊します。
- MCP 状態は必要に応じてプル:TypeScript は
mcpServerStatus()でポーリング、Python はget_mcp_status()またはon_mcp_status_changeコールバックを選択。 - control リクエストタイムアウトを適切に設定:リモートサーバーのハンドシェイクは秒単位になることがあります。デフォルト 60 秒で通常は十分ですが、OAuth のユーザー操作待ちでは大きめに、CI 環境では明示的に設定を。
- strict MCP config で隔離:ユーザーローカルの
settings.json/.mcp.jsonで宣言された MCP サーバーがアプリに干渉しないようにします。