定期実行のジョブからリモートの MCP サーバーを使いたいだけなのに、その前段でローカルに MCP クライアントを常駐させ、トランスポートを張り、ライフサイクルを管理する——この「ツールを1つ呼ぶための足場」が、ヘッドレス運用では地味に重くのしかかります。私は個人開発で運営している複数のサイト(Dolice Labs)を毎日決まった時刻に更新していますが、そのランナーの中に MCP クライアントを抱えるたびに、接続断やプロセスの後始末でログが汚れていくのが悩みでした。
Messages API の MCP コネクタ は、この足場ごと省きます。リクエストの mcp_servers にサーバーの URL を書くと、Anthropic 側がそのリモート MCP サーバーへ接続してツールを実行し、結果を同じレスポンスに混ぜて返してくれます。クライアント実装が不要になる、というのが要点です。
どういうときに効くのか、効かないのか
最初に線引きをしておきます。MCP コネクタは万能ではなく、向き不向きがはっきりしています。
公式ドキュメントが明記しているとおり、サポートされているのは MCP 仕様のうち ツール呼び出しだけ です。プロンプトやリソースは対象外で、それらを使いたいなら従来どおり自分で MCP クライアントを持つ必要があります。また接続できるのは HTTPS で公開されたリモートサーバー (Streamable HTTP / SSE)に限られ、ローカルの stdio サーバーは直結できません。
私の判断軸はこうです。「URL でアクセスできるリモートサーバーのツールを呼びたいだけ」ならコネクタが最短で、「ローカルサーバー・プロンプト・リソースが要る、あるいは接続を細かく制御したい」なら自分でクライアントを持つ。この使い分けは、社内向けの非公開サーバーを安全に繋ぐManaged Agents の MCP トンネル設計 とも地続きで、「実行環境のどこに境界を引くか」という同じ問いの別の答えになっています。
やりたいこと 向いている手段
URL 公開のリモート MCP のツールを呼ぶだけ MCP コネクタ(mcp_servers)
ローカル stdio サーバーを使う 自前の MCP クライアント+SDK ヘルパー
MCP のプロンプト/リソースを使う 自前の MCP クライアント
非公開の社内サービスに繋ぐ MCP トンネル(Managed Agents)
最小の呼び出し
まずは1サーバーの全ツールを有効にする最小形です。ポイントは2つで、接続情報を mcp_servers に、どのツールを使うかを tools の mcp_toolset に分けて書きます。そしてベータヘッダーが必要です。
import anthropic
client = anthropic.Anthropic() # ANTHROPIC_API_KEY を環境変数から読む
response = client.beta.messages.create(
model = "claude-opus-4-8" ,
max_tokens = 1024 ,
messages = [
{ "role" : "user" , "content" : "利用できるツールを一覧して、その中で今日の予定を取得して" }
],
# ① 接続先の定義(URL とトークン)
mcp_servers = [
{
"type" : "url" ,
"url" : "https://example-server.modelcontextprotocol.io/sse" ,
"name" : "calendar" ,
"authorization_token" : "YOUR_ACCESS_TOKEN" , # 不要なサーバーなら省略可
}
],
# ② どのツールを露出するか(既定は全ツール有効)
tools = [{ "type" : "mcp_toolset" , "mcp_server_name" : "calendar" }],
# ③ ベータヘッダー(旧 mcp-client-2025-04-04 は非推奨)
betas = [ "mcp-client-2025-11-20" ],
)
for block in response.content:
print (block.type)
期待する出力(抜粋): text → mcp_tool_use → mcp_tool_result → text の順にブロックが並びます。Claude が「予定を取りたい」と判断するとツールが1回呼ばれ、その結果を踏まえた最終テキストが続きます。
ここで踏みやすい最初の落とし穴があります。betas=["mcp-client-2025-11-20"] を忘れると、mcp_servers ごと無視されたかのように普通の応答が返ります 。私は最初これに気づかず「ツールが呼ばれない」と数十分悩みました。バージョンが上がってヘッダー名も mcp-client-2025-04-04 から変わっているので、古い記事のコードをそのまま貼ると静かに失敗します。
レスポンスをどう読むか
MCP コネクタを使うと、見慣れない2種類のコンテンツブロックが返ります。ツール実行は Anthropic 側で完結しているので、こちらが結果を送り返す必要はありません。読み取って使うだけです。
def walk (response):
for block in response.content:
if block.type == "text" :
print ( "TEXT:" , block.text)
elif block.type == "mcp_tool_use" :
# Claude が呼んだツール。server_name でどのサーバーか分かる
print ( f "CALL: { block.server_name } . { block.name } ( { block.input } )" )
elif block.type == "mcp_tool_result" :
# 実行結果。is_error で失敗を判定する
status = "ERROR" if block.is_error else "OK"
texts = [c.text for c in block.content if c.type == "text" ]
print ( f "RESULT[ { status } ] { block.tool_use_id } : { ' ' .join(texts) } " )
walk(response)
mcp_tool_result の is_error を必ず見るようにしてください。リモート側のツールが落ちても、API リクエスト自体は 200 で返ってきます。無人運用では「リクエストは成功したのにツールは失敗していた」という状態が一番こわい ので、is_error を拾ってログとアラートに回す一本道を最初に通しておくと、本番運用で原因を追って解決するのが楽になります。
無人運用のための allowlist / denylist 設計
ここがこの記事でいちばん伝えたいところです。コネクタで全ツールを開けっ放しにすると、Claude は文脈次第で書き込み系や削除系のツールも呼べてしまいます。対面のチャットなら確認を挟めますが、定期実行のジョブにはその余地がありません。
新しい toolset では、ツールの露出を default_config と configs の組み合わせで細かく決められます。私が無人ジョブで使うのは、「既定で全部閉じてから、読み取り専用のものだけ開ける」allowlist です。
read_only_toolset = {
"type" : "mcp_toolset" ,
"mcp_server_name" : "calendar" ,
"default_config" : { "enabled" : False }, # まず全ツールを無効化
"configs" : {
"search_events" : { "enabled" : True }, # 必要な読み取りだけ明示的に開ける
"list_events" : { "enabled" : True },
},
}
逆に「ほぼ全部使うが、危ないものだけ閉じたい」場合は denylist にします。読み取り中心のアシスタントや、状態変更の前に人の確認を挟みたいときに向いています。
guarded_toolset = {
"type" : "mcp_toolset" ,
"mcp_server_name" : "calendar" ,
# default_config は省略(=既定で全ツール有効)
"configs" : {
"delete_all_events" : { "enabled" : False }, # 破壊的な操作を閉じる
"share_calendar_publicly" : { "enabled" : False },
},
}
私の運用方針は単純で、人が見ていないジョブは原則 allowlist、対話的なツールだけ denylist で十分 、というものです。「閉じ忘れ」が起きたときの被害が、allowlist では「ツールが足りない」(無害)で済むのに対し、denylist では「危険なツールが開いていた」になる——この非対称性が判断の根拠です。無人実行でツールの許可をどう詰めるかという観点は、無人エージェントの MCP ポリシー強制 で扱った「一箇所で認可を握る」考え方とセットで効きます。
設定のマージ順序も覚えておくと迷いません。優先度は高い順に「configs のツール個別設定 → default_config → システム既定」です。default_config で defer_loading: true を敷きつつ、特定ツールだけ configs で defer_loading: false に上書きする、といった重ね方ができます。
複数サーバーと、ツールが増えたときの defer_loading
1リクエストで複数の MCP サーバーに繋ぐこともできます。各サーバーを mcp_servers に並べ、それぞれに対応する mcp_toolset を tools に置きます。検証ルールとして、定義したサーバーはちょうど1つの toolset から参照されていないとエラー になります(使わないサーバーを書きっぱなしにできない、という安全側の仕様です)。
response = client.beta.messages.create(
model = "claude-opus-4-8" ,
max_tokens = 1024 ,
messages = [{ "role" : "user" , "content" : "予定とタスクを突き合わせて、今日詰みそうな時間帯を教えて" }],
mcp_servers = [
{ "type" : "url" , "url" : "https://mcp.example1.com/sse" , "name" : "calendar" ,
"authorization_token" : "TOKEN1" },
{ "type" : "url" , "url" : "https://mcp.example2.com/sse" , "name" : "tasks" ,
"authorization_token" : "TOKEN2" },
],
tools = [
{ "type" : "mcp_toolset" , "mcp_server_name" : "calendar" },
{
"type" : "mcp_toolset" ,
"mcp_server_name" : "tasks" ,
"default_config" : { "defer_loading" : True }, # 説明を初期送信しない
},
],
betas = [ "mcp-client-2025-11-20" ],
)
サーバーをまたいでツールが数十個になると、説明文だけでコンテキストを食い、選択精度も落ちます。そこで defer_loading: true を敷くと、ツールの説明をモデルへ初回から送らず、ツール検索(Tool search)と組み合わせてクエリごとに関連ツールだけを浮かせられます。私の感覚では、ツールが2桁に乗り始めたあたりが defer_loading を入れる目安です。
コストとキャッシュ
MCP コネクタ経由のツール呼び出しは、通常の Messages API のツール利用と同じ料金体系です。Batches API にも mcp_servers を載せられて、バッチ経由の MCP ツール呼び出しも同じ価格で扱われます。私はニュース収集のような「急がないが量がある」処理をバッチに寄せていますが、そこにリモート MCP を混ぜても課金の考え方が変わらないのは運用上ありがたい点でした。
toolset には cache_control を付けてプロンプトキャッシュのブレークポイントを置けます。ツール定義は安定して変わりにくい部分なので、TTL を分けてキャッシュする設計の「変わらない側」に置くと相性が良いです。この考え方はプロンプトキャッシュを 5m と 1h で二段に分ける設計 でまとめたとおりで、MCP のツール定義はまさに 1h 側に寄せたいブロックになります。
本番運用の前に確認したい点
実際に組み込む前に、私が毎回チェックしている項目を残しておきます。
ひとつは ZDR(Zero Data Retention)非対応 であることです。MCP コネクタはゼロデータ保持の対象外で、ツール定義や実行結果は標準の保持ポリシーに従って残ります。ZDR を前提にした要件があるなら、この機能は使えません。
もうひとつは 稼働プラットフォームの差 です。MCP コネクタは Claude API・AWS の Claude Platform・Microsoft Foundry で使えますが、Amazon Bedrock と Vertex AI では現状利用できません。デプロイ先を選ぶ段階で効いてくるので、早めに確認しておくと手戻りが減ります。
OAuth が必要なサーバーでは、authorization_token に渡すアクセストークンは呼び出し側で取得・更新する 前提です。トークンの寿命管理はこちらの責務なので、定期ジョブなら期限切れを見越したリフレッシュをジョブの前段に組み込み、トークン切れによる失敗を未然に回避します。テスト用のトークンが要るだけなら、npx @modelcontextprotocol/inspector の OAuth フローで手早く発行できます。
次の一歩
手元に URL でアクセスできるリモート MCP サーバーがあるなら、まずは全ツール有効の最小形で1リクエストだけ投げて、mcp_tool_use と mcp_tool_result が実際にどう並ぶかを目で確かめてみてください。レスポンスの形が腹落ちすれば、そこから allowlist で締めていく作業は驚くほど素直に進みます。私自身、この「足場を省ける」感覚を一度味わってから、ヘッドレスのジョブから余計な常駐プロセスを1つずつ外していけました。