はじめに / 対象と前提
リモートの MCP サーバー(Model Context Protocol サーバー)を、Claude Code などのクライアントを経由せず Messages API から直接 呼びたい場面がある。バックエンドのワーカーから「社内の MCP サーバーのツールを Claude に使わせたい」といったケースだ。
Claude API にはこれ専用の MCP コネクタ がある。mcp_servers にサーバーの URL を書くと、Anthropic 側がそのサーバーへ接続してツールを呼んでくれる。自分でツールループを書かなくていい。
- 想定読者:Claude API でツール実行を組んだことがあり、MCP を自前のバックエンドから使いたい人
-
前提環境:Python 3.10+ /
anthropicSDK 1.x / モデルはclaude-opus-5 - 前提:呼び出し先は URL でアクセスできるリモート MCP サーバー。ローカルの stdio サーバーはこの方式では呼べない(後述)
自分は記憶していた古い書き方でそのまま投げて 400 を食らった。以下はその実録。
TL;DR
-
mcp_serversとtoolsのmcp_toolsetは 2 つで 1 セット。片方だけだと validation error で落ちる - beta フラグは
mcp-client-2025-11-20。記憶にあるmcp-client-2025-04-04やtool_configurationは旧仕様 - 何もしないと サーバーの全ツール定義が毎リクエストの入力トークンに乗る。
default_config+configsで allowlist 化して絞る
手順 / 動かし方
1. 最小構成
MCP コネクタは beta なので、client.messages ではなく client.beta.messages を使う。ここを間違えると mcp_servers が未知パラメータ扱いになる。
import anthropic
client = anthropic.Anthropic() # ANTHROPIC_API_KEY を環境変数から読む
response = client.beta.messages.create(
model="claude-opus-5",
max_tokens=16000,
betas=["mcp-client-2025-11-20"],
mcp_servers=[
{
"type": "url",
"url": "https://mcp.example.internal/sse",
"name": "example-mcp",
}
],
tools=[
{"type": "mcp_toolset", "mcp_server_name": "example-mcp"},
],
messages=[
{"role": "user", "content": "在庫が 10 個を切っている商品を一覧にして"}
],
)
for block in response.content:
print(block.type)
必須は 3 点。betas にフラグを付ける / mcp_servers に接続先を定義する(name は自分で決める識別子)/ tools に mcp_toolset を入れて 同じ name を mcp_server_name で指す。
実行するとブロックがこう並ぶ。
thinking
mcp_tool_use
mcp_tool_result
text
mcp_tool_use / mcp_tool_result は サーバー側で完結して返ってくる。通常の tool_use と違い、自分でループを回して結果を返す必要がない。stop_reason が tool_use にならず、1 リクエストで答えまで返る。
2. 認証付きサーバーに繋ぐ
トークンが要るサーバーは authorization_token を足す。当然だがベタ書きはしない。
import os
mcp_servers = [
{
"type": "url",
"url": "https://mcp.example.internal/sse",
"name": "example-mcp",
"authorization_token": os.environ["EXAMPLE_MCP_TOKEN"],
}
]
このトークンは Anthropic のサーバーから MCP サーバーへ送られる。裏を返すと、社外に出したくないエンドポイントには使えない。
3. 結果ブロックを取り出す
型で分岐して拾う。
for block in response.content:
if block.type == "mcp_tool_use":
print("call:", block.name, block.input)
elif block.type == "mcp_tool_result":
# is_error が立つことがあるので必ず見る
print("result:", block.content, "error:", block.is_error)
elif block.type == "text":
print(block.text)
MCP サーバー側でツールが失敗しても 例外は飛ばない。HTTP 200 で返ってきて is_error が立つだけなので、try/except だけ書いて満足していると失敗を取りこぼす。Web 検索などのサーバーサイドツールと同じ挙動だ。
ハマりどころ
1. mcp_servers だけ書くと 400 で落ちる
一番最初に踏んだのがこれ。tools を省いて投げると、
anthropic.BadRequestError: Error code: 400 - invalid_request_error
「サーバーを登録したんだから使ってくれるだろう」と思うが、そうはならない。tools 側に mcp_toolset を置いて、そのサーバーを明示的に参照する必要がある。
ルールは 2 つ。
-
mcp_server_nameはmcp_serversのnameと 完全一致 させる -
mcp_serversに登録したサーバーは、すべて ちょうど 1 つの toolset から参照されていること
サーバーを 2 つ繋ぐなら toolset も 2 つ書く。片方を忘れると同じエラーになる。
mcp_servers=[
{"type": "url", "url": "https://a.example/sse", "name": "srv-a"},
{"type": "url", "url": "https://b.example/sse", "name": "srv-b"},
],
tools=[
{"type": "mcp_toolset", "mcp_server_name": "srv-a"},
{"type": "mcp_toolset", "mcp_server_name": "srv-b"}, # これを忘れると 400
],
エラー文からは「toolset が足りない」とまで読み取れないので、400 が出たらまず 両者の数と名前が一致しているか を数えるのが早い。
2. beta フラグと tool_configuration が古い
記憶や古い記事に残っている書き方が、そのままだと通らない。
| 古い書き方 | 現在 |
|---|---|
mcp-client-2025-04-04 |
mcp-client-2025-11-20 |
tool_configuration をサーバー定義の中に置く |
tools の mcp_toolset に default_config / configs |
「サーバー定義の内側でツールを絞る」構造から「toolset 側で絞る」構造に変わっている。ここを混ぜると、フラグは通ったのに絞り込みだけ無視される、という分かりにくい状態になる。Go SDK なら定数も anthropic.AnthropicBetaMCPClient2025_11_20 が現行で、...2025_04_04 は非推奨。
確認方法:response.usage.input_tokens を見て、絞ったつもりなのに数字が減っていなければ旧仕様が無視されている。
3. 何もしないと全ツールが入力トークンに乗る
mcp_toolset を素で書くと、そのサーバーが公開している 全ツールの定義(名前 + 説明 + JSON Schema)がリクエストに載る。5 個なら気にならないが、40〜50 個あるサーバーだと入力トークンが一気に膨らむ。毎リクエスト課金されるので地味に効く。
対策は allowlist モード。default_config で全部オフにして、使うものだけ configs で開ける。
tools=[
{
"type": "mcp_toolset",
"mcp_server_name": "example-mcp",
"default_config": {"enabled": False}, # まず全部オフ
"configs": { # 使うものだけオン
"search_inventory": {"enabled": True},
"get_product": {"enabled": True},
},
}
],
効果は response.usage.input_tokens を絞る前後で比べれば分かる。副次効果として ツールの選択精度も上がる。似た名前のツールが 40 個並ぶより 2 個のほうが迷わない。「動くけど時々違うツールを呼ぶ」症状が出ていたら、まずここを疑うといい。
背景・補足
この方式を選ばないほうがいいケース
MCP コネクタは「Anthropic のサーバーが MCP サーバーへ接続しにいく」構造なので、向き不向きがある。
| やりたいこと | MCP コネクタ | 自前 MCP クライアント |
|---|---|---|
| リモート(URL)サーバーを手軽に繋ぐ | ◎ | △ |
| ローカルの stdio サーバーを繋ぐ | ✕ | ◎ |
| MCP の Prompts / Resources も使う | ✕ | ◎ |
| 外部に出せないエンドポイント | ✕ | ◎ |
後者をやりたいなら、Python SDK に MCP をツール定義へ変換するヘルパー(anthropic.lib.tools.mcp)があり、pip install "anthropic[mcp]" で入る。接続を自分のプロセスが持つので、ローカルサーバーも社内限定エンドポイントも扱える。
プラットフォーム可用性
MCP コネクタは Claude API では beta として使える が、クラウドプロバイダ経由だと状況が違う。Amazon Bedrock と Google Vertex AI では現時点で利用できない。「ローカルでは動いたのに Bedrock 経由の本番で落ちる」事故になりやすいので、デプロイ先を決める前に対応表を確認しておきたい。
まとめ
- MCP コネクタは
mcp_servers+toolsのmcp_toolsetを セットで 書く。片方だけは 400 - beta フラグは
mcp-client-2025-11-20。絞り込みはtool_configurationから toolset 側のdefault_config/configsへ移った - デフォルトでは全ツールが入力トークンに乗る。全部オフにしてから使うものだけ開けると、コストもツール選択精度も改善する
- ツールのエラーは例外ではなく
is_errorで返る。必ずブロックを見る - ローカル stdio サーバーや外に出せないエンドポイントは、この方式ではなく自前の MCP クライアントで