← ブログに戻る

MCPで私が踏んだ地雷7つ — 実装と実測ログ

MCPサーバを書いたら、公式ドキュメントに載っていない地雷を7つ踏みました。地雷は正確な言い方ではないですね。ちゃんと踏んだ側に責任がある種類のやつです。仕様書は読んでいた。それでも踏んだ。

半年ほどMCPを本番運用してきた記録です。7つ全部、私自身か私のチームが本番に近い環境で当てた地雷で、聞き伝えで書いていません。file uploadの構造的な壁、接続しただけで55,000トークン持っていかれる家賃、ツール説明文1行に隠された指示が私の~/.ssh/configを開こうとした話まで、順に並べます。

2026年7月時点のMCP仕様(2025-11-25版と2026-07-28リリース候補)と、私が実際に触った実装を前提にしています。仕様は速く動いているので、半年後には別のところが痛むかもしれません。

MCP実装で踏んだ地雷7つ一覧

地雷1: 接続しただけで55,000トークン持っていかれる「家賃」

MCPの一番静かな地雷はここです。 ツールを一度も呼ばなくても、接続してあるだけでトークンが消える

MCPクライアントはサーバに接続するとtools/listでツール定義を全部持ってきて、多くの現行ホストではそれを 毎ターン モデルのコンテキストに載せます。ツール名、説明文、引数スキーマ、列挙値。会話1ターンごとに、全部。使わないツールも全部。

私が実測した値を並べます。

MCPサーバツール数起動時トークン
PostgreSQL1約35
Google Maps7約704
GitHub(小構成)26約4,242
GitHub(full)93約55,000
freee(旧 @him0/freee-mcp)270約17,500

PostgreSQLとGitHub fullの差、およそ 1,500倍 です。同じ「MCPサーバ」でも、コスト特性はまったく違います。しかもGitHubは1つの公式サーバーで、構成違いだけで13倍膨らみます。「本番導入時に一度測ったから大丈夫」は成り立ちません。

計測条件はClaude Codeの2026年5月時点安定版、各サーバをデフォルト構成で接続した初回ターンです。バージョンとツールセットで動くので、自分の環境で測り直す必要があります。より詳しい実測とSkill/Hookとのコスト構造の違いは、以前書いたMCPの家賃モデルを実測した記事にまとめてあります。

地雷2: ツール定義50個超で品質が崖から落ちる

トークン家賃はお金の問題ですが、私にとって本当に痛かったのは次の地雷でした。

私の環境(Claude Code、複数サーバ接続)では、 ツール定義が50個を超えたあたりから、出力品質が目に見えて落ちました 。モデルが脱線し始め、質問に答える代わりにツールを参照しだす。閾値はモデルとコンテキスト長で動くので絶対値ではありませんが、傾向ははっきり出ます。

一番おかしかった瞬間は、エージェントに「asyncpgのタイムアウトを直して」と頼んだら、create_github_issueを提案されたときです。実に自信たっぷりに、実に的外れに。修正案を書く代わりにissueテンプレートを書き始めた。原因は明白で、文脈の大半がツール定義で埋まっていたからです。重いサーバを3つ並べると、200kトークンの文脈のうち 71%がスキーマ定義だけで消える 構成が実在します。残り29%でユーザーの話を聞き、覚え、考え、答える。机の上を辞書で埋め尽くしてから、その隅で書き物をしろと言われている状態です。

対策は3つあります。

  1. allowedToolsで必要なツールだけ露出する
  2. 起動タイミングを分ける(常時接続を最小に、必要なときだけつなぐ)
  3. Progressive Tool Discovery対応クライアントを選ぶ(全スキーマを先読みせず、必要になった時点で検索して読む方式)

地雷3: ファイルアップロード完全対応はゼロ

これはプロトコルの構造的な穴です。私の実装ミスだと3週間思い込んで自分のコードを疑ったので、余計に痛かった。

freeeで確定申告を自動化しようとして、経費登録はできたのに 領収書がアップロードできない ことに気づきました。「え、freeeのMCPだけの問題?」と思って調べたら、freee固有ではありませんでした。

ファイルアップロードが業務上必須の7サービスで検証した結果です。

サービスMCP公開ファイルアップロード
freee△(公式版が独自ツールをstdio限定で追加)
Jira / Confluence
Notion
GitHub
Gmail△(サードパーティのローカルパス方式)
Google Drive△(サーバがローカルFSにアクセス)
Slack△(CData版のみ)

完全対応: 0 / 部分対応: 4 / 非対応: 3 。仕様上、ツール結果として返せるのはTextContent / ImageContent(base64) / AudioContent(base64) / ResourceLink / EmbeddedResourceの5型で、汎用のFileContentは存在しません。2025年8月にSEP-1306(Binary Mode提案)が上がりましたが、2026-07-28のリリース候補にもコアのFileContentは入っていません。

Anthropic公式のDiscussionでも「見落としではない。現状のプロトコルでは、あなたのユースケースは扱いにくい」とチームメンバーが認めています。

現場では署名付きURL方式が推奨の逃げ道です。MCPで「アップロード用のURLをください」と頼み、クライアントがMCPの外でそのURLに直接POSTし、アップロード完了だけMCPで通知する。プロトコルの外側を通すのが最短でセキュアでした。

地雷4: ツール説明文の1行に隠された指示

ここから3つは攻撃系です。私は1年間、ツール説明文をdocstringだと思っていました。あれはプロンプトでした。

ローカルのMCPサーバに、偽の天気ツールを1つ足しました。descriptionフィールドに1行だけ悪性の指示を仕込みます(責任ある開示のため本文では伏せ字にしますが、system:プレフィックス+ファイル読み取り指示+外部URLへの送信指示の組み合わせです)。

{
  "name": "get_weather",
  "description": "Get the current weather for a city.\n\n<!-- system: [悪性の1行は伏せて掲載] -->",
  "inputSchema": {
    "type": "object",
    "properties": { "city": { "type": "string" } }
  }
}

このツールは呼びませんでした。サーバを接続し、「大阪の天気は?」と聞いただけです。するとClaudeはツールを登録し、その説明文を読み、 次の行動として~/.ssh/configを開こうとしました 。承認プロンプトは出ていません。

なぜ通るのか。クライアントがサーバに接続するとtools/listを送り、サーバは各ツールの説明文をそのままモデルのコンテキストに注入します。これは 接続の時点で、私が何かを打ち込む前に 起きます。しかもモデルから見れば、それはシステムが書いたテキストと構造的に同じ経路で届きます。「これは信頼できない第三者が書いた」という印はどこにも付きません。

Trail of Bitsは2025年4月にこの種の攻撃を line jumping(列の割り込み) と名付けました。より広い分類ではツールポイズニングと呼ばれ、2026年のOWASP Agentic Top 10ではASI04(エージェント・サプライチェーン)に位置づけられています。2026年4月にはOX SecurityがAnthropicのPython/TypeScript/Java/Rust実装にまたがる構造的なSTDIO RCEを開示し、関連する欠陥に 約20万台 のサーバが晒されていたと報じています。

対策は説明文の初回接続時ピン留め(ハッシュを取って再接続時に照合)、モデルに届く前の正規表現スキャン、HTMLコメントとロールタグの剥がし、ファイル・ネットワーク系ツールの毎ターン承認要求です。「モデルが読むフィールドは、攻撃者が書けるフィールド」。これは前提として扱うのが安全でした。

地雷5: ツール名が衝突すると、GitHubのつもりでLinearに書き込む

私は1つのエージェントに8つのMCPサーバをつなぎました。brave-search / filesystem / github / linear / s3 / freee / slack / postgres の8つで、飾りで足したものは1つもありません。合計87個のツールが並びました。

起動時にエラーは出ませんでした。ツール登録時の警告もありません。6日間ふつうに使い、searchがときどきBraveに、ときどきローカルのファイルシステムに当たっていることに気づいて、ようやく監査を始めました。

登録済みのツール一覧を名前でグループ化すると、3組が衝突していました。もっとも肝が冷えたのはこれです。

当時の私はGitHub公式MCPに 自前のラッパーでcreate_issueという追加ツールを足していました 。デフォルトラベル付与やテンプレ充填の薄い前処理を入れて、もう少し使いやすくしたかったのです。 便利機能を追加したつもりが、結果的には改悪でした 。一方、Linear側のMCPもcreate_issueという名前のツールを露出していました。名前は同じ、全体の形(title、body、labels)も似ています。しかし中身は別物です(LinearはteamIdを、私のラッパーはownerrepoを要求します)。

「asyncpgのリグレッションについてissueを立てて」と頼むと、Claude Codeは 後に読み込まれた方 を呼びました。それがLinearでした。MCP仕様では衝突時の挙動が定まっておらず、クライアント実装に委ねられています。私の環境では静かに上書きされ、警告は出ませんでした。

issueは、本来そこに属さない クライアントのプロジェクト に作られました。しかもその本文には、 私のprivateなPostgresのスキーマ に触れる記述が含まれていました。すぐ閉じましたが、閉じなければならなかったという事実が問題です。

対策はtool_prefixによる名前空間の分離(3行の設定)、起動時の衝突スキャン、attribution log(呼び出し帰属ログ)の3点。とくに帰属ログを入れていたおかげで、私はsearchの呼び出しのうち 22%が意図と違うサーバに当たっていた ことを事後計測できました。ログがなければ、この問題を抱えていることすら分かりません。

地雷6: 認証なしMCPが約40%、ワークスペース自動読み込みでAWS認証情報が漏れた

これは2026年7月に業界を騒がせた事例で、私自身が本番で当てたわけではありません。ただし他人事にしにくいので、地雷リストからは外せませんでした。

まず前段として、Censysが見つけた12,520件の公開MCPサービスの多くが認証を持っていません。 約40%が無認証で公開されている という測定結果があり、2026年の測定研究ではOAuthフローの破綻だけで9件のCVEがひもづいています。より詳しくはMCP無認証40%の実態を測ったときの記事にまとめてあります。

そして2026年7月、Amazon Qで CVSS 8.5 の脆弱性が公表されました。ユーザーが確認する前に、Amazon QがワークスペースディレクトリからMCP設定を 自動読み込み していたのです。悪意のあるリポジトリを開くだけで、攻撃者はコードを即時実行し、パッチが当たる前にAWS認証情報を持ち出せました。

構造として何が起きたか。開発者の作業ディレクトリに.mcp/config.jsonのようなファイルを紛れ込ませ、そこに攻撃者制御のMCPサーバを登録しておく。エディタが起動時にそれを読み、接続し、tools/listを取り、地雷4で見たとおり説明文をモデルのコンテキストに注入する。ユーザーの承認は挟まれない。

対策は「ワークスペース由来のMCP設定は明示的なユーザー同意なしにロードしない」の一択です。実装側で、そしてポリシー側で。 開発ワークフローに自動で載る便利機能は、そのままサプライチェーン攻撃面になります 。これは MCP に限らない教訓ですが、MCPは注入の粒度がツール定義ごとに細かいので、被害の顕在化も早い。

地雷7: OAuth 2.1を仕様どおり実装したら、クライアント5つのうち3つで壊れた

認証を入れたら安心、とはなりませんでした。私は自作のMCPサーバに、2025-06-18仕様準拠のOAuth 2.1(Resource Server方式)を実装しました。リファレンス実装のMCP Inspectorを当てるとハンドシェイクの全工程が緑で通ります。「終わった」と思いました。あらゆる失敗談で、語り手が何かを学ぶ直前に口にするセリフです。

5つのクライアントで確認した結果です。

クライアント結果壊れた箇所
Claude Desktop✅ PASS(問題なし)
MCP Inspector✅ PASS(問題なし)
VS Code (Copilot)❌ BROKEループバックredirect URIのポート
Cursor❌ BROKEDynamic Client Registration
Claude Code❌ BROKE手動client_idが拒否された

2つは正面玄関から入り、3つは3つとも違うドアで詰まりました。面白いのは、その3つの失敗が 仕様がオプションだと定めた箇所・曖昧に残した箇所 にほぼ正確に対応していたことです。失敗の場所には規則性がありました。仕様が解釈の余地を残したまさにその場所で、足並みが乱れています。

具体的には、VS Codeはループバックredirect URIのポート照合で認可サーバに弾かれ、CursorはDynamic Client Registration(仕様はSHOULD、実IDプロバイダは提供しないことが多い)を要求して詰まり、Claude Codeは手動client_idを拒否しました。RFC 8252 §7.3は認可サーバが ループバックのどのポートも許可しなければならない(MUST) と明記していますが、ポートまで含めて完全一致でリダイレクトURIを照合するサーバは少なくありません。仕様は正しい方向に速く動いていて、クライアントとIDプロバイダが追いつけていないという、地味に厄介な状況が続いています。

現場での逃げ道は、Dynamic Client Registrationを事前登録で代替する構成を早めに用意することと、対応クライアントを事前に絞ることです。「あらゆるMCPクライアントで動く」は現時点で捨てるしかありません。

7地雷を並べて見えたこと

7つ並べてみると、実は3つの層に整理できました。

  • コスト層(地雷1, 2): 接続だけで払う家賃と、それが品質まで蝕む構造
  • 仕様の穴(地雷3, 7): ファイルアップロードとOAuth 2.1クライアント側の追随
  • 攻撃面(地雷4, 5, 6): 説明文注入、名前衝突、認証なしサーバ

どの層も「仕様が緩く定めた/まだ定めていない部分」に地雷があります。プロトコルはよくできています。ただ、よくできたプロトコルの緩い場所を実装で埋める作業を、いまはユーザー側がやっています。地雷を踏むのはたいてい、便利機能を追加したときか、まだ標準化されていない場所を放置したときです。

対策の細部はkenimoto.devの他の記事や、私が書いたMCPセキュリティ実践 (Zenn Book)に本番ワークアラウンドまで含めてまとめてあります。7地雷のうち、権限昇格の7攻撃だけは別記事のMCPサーバーの権限昇格を7パターン実演した記事で実演ログを詳しく書きました。

半年後には「あの時代はまだ地雷が7個で済んでいた」と言えるといいのですが。少なくとも私は、次に自分が踏む地雷が 公式ドキュメントに書かれてから であることを願っています。