Agent SDK MCP接続ガイド — stdio/HTTPとSDKサーバーの使い分け
Agent SDKから外部MCPサーバーへ接続する手順。stdio・HTTP/SSEの選び方、allowedToolsでの権限付与、接続タイミングとエラーハンドリングまで扱います。
Agent SDKから外部MCPサーバーに接続するとは
Agent SDKからMCPサーバーに接続するとは、Model Context Protocolで公開された外部ツール群を、自分でコードを書かずに mcpServers オプションへ設定として渡すだけで使えるようにする仕組みです。GitHubのissue操作やSlack通知、社内データベースへのクエリなど、すでにMCPサーバーとして公開されている機能をそのまま取り込めます。
自分で処理を書きたい場合との対比が分かりやすい入口です。関数を自作したいならAgent SDKでカスタムツールを作る手順でインプロセスサーバーを組みます。一方、既存のサーバーに繋ぐだけでよいなら、本記事が扱う3種類のトランスポート(stdio・HTTP/SSE・インプロセス)から選ぶだけで完結します。
Agent SDKそのものの基本的な使い方はAgent SDKクイックスタートで扱っています。まずクエリの投げ方や query() の基本形から確認したい場合はそちらが入り口になります。
手順1 — トランスポートを選ぶ
MCPサーバーのドキュメントを見れば、どのトランスポートを使うべきかはすぐ分かります。
| ドキュメントに書かれているもの | 使うトランスポート |
|---|---|
実行コマンド(npx @modelcontextprotocol/server-filesystem など) | 使うトランスポートstdio |
| URL | 使うトランスポートHTTPまたはSSE |
| 自分でコードを書いてツールを定義する | 使うトランスポートインプロセスのSDKサーバー(カスタムツールの作り方を参照) |
stdioはローカルプロセスとして起動し標準入出力でやり取りするため、同じマシン上で動くサーバー向きです。HTTP/SSEはクラウドでホストされたリモートサーバーや外部APIとの接続に向いています。ストリーマブルHTTPを使う場合は "type": "http" を指定し、.mcp.json などのJSON設定では "streamable-http" もエイリアスとして通りますが、コード内の mcpServers オプションでは "http" のみ有効です。
手順2 — stdioサーバーに接続する
ローカルのファイルシステムサーバーに接続する例です。コード内で直接指定する方法と、プロジェクト直下の .mcp.json に書く方法のどちらでも動きます。
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "List files in my project",
options: {
mcpServers: {
filesystem: {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
}
},
allowedTools: ["mcp__filesystem__*"]
}
})) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}.mcp.json に書く場合は、query() のデフォルト設定で読み込まれる project 設定ソースが有効になっている前提です。settingSources を明示的に指定しているときは、配列に "project" を含めないとこのファイルは読み込まれません。
手順3 — HTTP/SSEサーバーに接続し、認証情報を渡す
リモートサーバーへは認証ヘッダーを添えて接続します。次の例はGitHubのMCPサーバーに接続し、直近のissueを取得します。
export GITHUB_TOKEN=YOUR_GITHUB_PATfor await (const message of query({
prompt: "List the 3 most recent issues in anthropics/claude-code",
options: {
mcpServers: {
github: {
type: "http",
url: "https://api.githubcopilot.com/mcp/",
headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` }
}
},
allowedTools: ["mcp__github__list_issues"]
}
})) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}環境変数はサーバー設定の env フィールドで渡すこともできます。.mcp.json の中では ${API_KEY} の形で実行時に展開されます。
MCPの仕様自体はOAuth 2.1による認可をサポートしていますが、Agent SDKはブラウザを開いてOAuthフローを実行しません。設定したサーバーが認可チャレンジを返し、保存済みトークンもない場合、そのサーバーのツールを使わないままエージェントの実行は続行され、サーバーのステータスは needs-auth になります。認証情報を渡すには、自分のアプリ側でOAuthフローを完了させ、得られたアクセストークンを headers に渡します。リモートMCPのOAuth認可サーバー発見からトークン取得までの流れはリモートMCPのOAuth認証で詳しく扱っています。
例 — データベースにクエリを投げる
もうひとつの典型例が、自然言語のリクエストをSQLに変換してデータベースへ渡すパターンです。次の例はDBHub経由でPostgresに接続し、Claudeが直接SQLを組み立てて集計します。
[[sources]]
id = "production"
dsn = "${DATABASE_URL}"
[[tools]]
name = "execute_sql"
source = "production"
readonly = trueexport DATABASE_URL=postgresql://user:password@localhost:5432/mydbfor await (const message of query({
prompt: "How many users signed up last week? Break it down by day.",
options: {
mcpServers: {
postgres: { command: "npx", args: ["-y", "@bytebase/dbhub", "--config", "dbhub.toml"] }
},
allowedTools: ["mcp__postgres__execute_sql"]
}
})) {
if (message.type === "result" && message.subtype === "success") console.log(message.result);
}設定ファイル側で readonly = true にしておくと、DBHubは INSERT / UPDATE / DELETE やDDL文を拒否します。Claudeが誤って書き込み系のSQLを組み立てても、実行段階でサーバー側が弾く二重の安全策になります。接続文字列自体はコードに書かず、${DATABASE_URL} の形でプロセス環境から解決させているので、設定ファイルをリポジトリにコミットしても秘密情報は漏れません。
接続済みのツール一覧を確認する
サーバーがどんなツールを公開しているかは、ドキュメントを読む以外に system メッセージの init サブタイプから実行時に確認できます。tools 配列のうち mcp__ で始まるものがMCP由来のツールです。
for await (const message of query({ prompt: "...", options })) {
if (message.type === "system" && message.subtype === "init") {
const mcpTools = message.tools.filter((name) => name.startsWith("mcp__"));
console.log("Available MCP tools:", mcpTools);
}
}この一覧には、初回接続の待ち時間内に接続できたサーバーと、キャッシュ済みツール一覧を持つサーバーのツールが並びます。まだ接続していない他のサーバーのツールはここには出てこないため、期待したツールが見当たらないときは、まず該当サーバーの status を確認するのが調査の近道です。
allowedToolsで権限を許可する
MCPツールの名前は mcp__<サーバー名>__<ツール名> という形式になります。permissionMode: "acceptEdits" はファイル編集とファイルシステム系のBashコマンドしか自動承認しないため、MCPツールへのアクセスには効きません。permissionMode: "bypassPermissions" はMCPツールも自動承認しますが、他の安全確認まで広く無効化するため必要以上に緩くなります。狙ったサーバーだけを許可したいなら allowedTools にワイルドカードで書くのが最も絞り込みが効きます。
ワイルドカードにはひとつ落とし穴があります。allowedTools の許可ルールは、リテラルな mcp__<サーバー名>__ プレフィックスの後ろだけをグロブにできます。サーバー名部分自体をグロブにした mcp__* や単独の * は、起動時に警告が出るだけで何も自動承認しません。mcp__github__* のようにサーバー名まで書き切って初めて機能します。
Claudeがツールの存在は認識しているのに一向に呼び出さない場合、原因のほとんどは権限不足です。allowedTools にサーバー名を含むワイルドカードを渡し、承認プロンプトなしで実行できる状態になっているかをまず確認します。ツールが視界にすら入っていない場合は、権限ではなく tools オプションでその機能自体を除外していないか、サーバーの接続ステータスが failed になっていないかを疑う番です。
接続タイミングを把握する
options.mcpServers で渡したサーバーは起動時に登録されますが、いつ接続が完了して最初のターンに反映されるかはサーバーの種類で変わります。
| サーバーの種類 | 最初のターンを待たせるか | タイムアウト |
|---|---|---|
| stdioサーバー、またはキャッシュされたツール一覧を持たないHTTP/SSEサーバー | 最初のターンを待たせるか待たせる | タイムアウトMCP_TIMEOUT(既定30秒)。超えると接続失敗扱い |
| 前回接続時のツール一覧がキャッシュされているリモートサーバー | 最初のターンを待たせるか待たせない | タイムアウトなし(初回のツール呼び出し時に接続) |
| インプロセスのSDKサーバー | 最初のターンを待たせるか待たせない | タイムアウトなし |
system メッセージの init サブタイプで各サーバーの状態(pending / connected / failed / needs-auth / disabled)が分かります。pending は失敗ではありません。まだ接続中か、キャッシュされたツール一覧で動いているか、締め切りが過ぎて failed に転じる直前かのいずれかです。もっとも遭遇しやすいのは、options.mcpServers ではなく .mcp.json などの設定ファイルからサーバーを読み込んだケースです。この経路では最初のターンの前に2秒しか待たれないため、接続がまだ済んでいなくても init の時点では pending と表示されるのが通常の挙動です。接続が切れた場合はバックグラウンドで自動的に再接続を試み、5回失敗すると failed(または再認可が必要なら needs-auth)に切り替わります。
起動そのものを待たせるかどうかは、環境変数とサーバー設定でも調整できます。MCP_CONNECTION_NONBLOCKING=0 を設定すると、この節の表にある「待たせる」対象に限らず接続バッチ全体がブロッキングになります。既定5秒のキャップは MCP_CONNECT_TIMEOUT_MS で伸縮でき、サーバー設定に alwaysLoad: true を指定したサーバーは、キャッシュの有無にかかわらず毎回このブロッキング対象に含まれます。
いったん connected になったあとでも、セッションの途中で接続が落ちれば再び pending に戻ります。この状態は mcpServerStatus()(TypeScript)や ClaudeSDKClient.get_mcp_status()(Python)を呼べばセッションの後半でも確認できます。自動再接続の5回上限に達して failed になったサーバーは、自動では復帰しません。設定を直したうえで reconnectMcpServer()(TypeScript)や ClaudeSDKClient.reconnect_mcp_server()(Python)を呼び、手動で再接続を試みる必要があります。
大量のMCPツールを扱うとき
MCPサーバーを何個も繋ぐと、ツール定義だけでコンテキストウィンドウの大きな割合を占めることがあります。Tool Searchは既定でオンになっており、ツール定義をコンテキストから外し、必要になったときだけ読み込みます。しきい値の調整方法や、カスタムツールと組み合わせたときの挙動はTool Searchの使い方にまとめています。
よくあるつまずき
- 接続に時間がかかるサーバーでタイムアウトする: 起動が遅いサーバーは既定30秒の
MCP_TIMEOUTに間に合わないことがあります。軽量な代替サーバーを検討するか、MCP_TIMEOUTを伸ばして起動時間を確保します。接続待ちのMCP_TIMEOUTと、接続後のツール実行そのものが長引く場合のタイムアウトは別物です。両者の切り分けはMCP_TIMEOUTとMCP_TOOL_TIMEOUTの違いにまとめています。 - ツール出力が大きすぎて警告が出る: 25,000トークンを超えるツール結果は、全文がファイルに保存され、そのファイルパスを示すエラーメッセージに置き換わります。上限は
MAX_MCP_OUTPUT_TOKENSで調整できます。設定できる値の範囲や挙動の詳細はMAX_MCP_OUTPUT_TOKENSの設定を参照してください。 .mcp.jsonの環境変数が展開されない:${API_KEY}の記法はJSON設定ファイル内でのみ展開されます。コード内で直接指定する場合はprocess.env.API_KEYのように自分で参照する必要があります。acceptEditsにすればMCPも通ると思い込む: 前述の通りこのモードはファイル編集系にしか効きません。MCPツールはallowedToolsで個別に許可します。
よくある質問
stdioとHTTPはどちらを優先しますか
サーバーのドキュメントが提示する形式に従うのが基本です。同じサーバーが両方をサポートしていることは稀で、ローカル実行前提ならstdio、クラウドでホストされていればHTTPかSSEになります。
.mcp.json とコード内の mcpServers はどちらを使うべきですか
プロジェクト全体で共有したい固定サーバー(社内標準のfilesystemサーバーなど)は .mcp.json に、環境ごとに認証情報が変わるサーバーやテスト用の一時的な接続はコード内で指定するのが素直な切り分けです。
接続が pending のまま変わらないのは異常ですか
必ずしも異常ではありません。キャッシュされたツール一覧で動いているサーバーは、実際の接続を初回のツール呼び出しまで遅らせるため pending のまま最初のターンを迎えることがあります。.mcp.json などの設定ファイルから読み込んだサーバーも、最初のターンの前に2秒しか待たれないため pending のままになるのが最頻のパターンです。failed や needs-auth に変わっていないかを確認してください。
リモートサーバーでOAuthログイン画面を出せますか
SDK自体はブラウザを開きません。自分のアプリケーション側でOAuthフローを実装し、取得したアクセストークンを headers に渡す形になります。認可サーバー発見からトークン取得までの詳しい流れは、前述のリモートMCPのOAuth認証の解説にまとめています。
まとめ
Agent SDKからMCPサーバーに接続する作業は、サーバーのドキュメントに合わせてstdioかHTTP/SSEを選び、mcpServers に設定を書き、allowedTools でサーバー名まで含めたワイルドカードを許可するところまでが基本の流れです。認証が絡む場合はOAuthをSDKが肩代わりしない点を踏まえ、接続タイミングやタイムアウトの挙動を init メッセージで確認しながら運用してください。サーバーが増えてきたら、allowedTools の棚卸しと接続ステータスの定期確認を運用の型にしておくと、障害の切り分けが速くなります。