「MCPを使えばAPI連携やRAGは不要になるのか」「まず何を試せばよいのか」が分かりにくい、と感じていないでしょうか。
MCP、API、RAGは同じものを別の名前で呼んでいるわけではありません。比較する軸が違います。本記事では、記事専用の架空在庫データを読む小さなtoolを題材に、三者の役割とMCPの接続経路を整理します。さらに、正常応答だけでなく、権限外、不存在、接続失敗、データ非変更まで単一の隔離環境で確認します。
MCP・API・RAGは比較軸が違う
同じ「在庫を調べて回答する」処理でも、三者が担当する範囲は次のように異なります。
| 用語 | 主な役割 | 架空在庫検索で見ると |
|---|---|---|
| API | アプリケーション同士が機能やデータを呼び出す契約 | SKUと倉庫を渡し、在庫を返すサービスの呼び出し口 |
| RAG | 検索した外部情報を生成処理のコンテキストへ加える構成 | 在庫資料を検索し、その結果を回答生成へ渡す流れ |
| MCP | AIアプリと外部のデータ・機能をつなぐ接続規約 | AIアプリが在庫検索toolを発見し、決められた形式で呼ぶための共通契約 |
MCP serverの内部から既存APIを呼ぶことも、検索基盤を使うこともできます。つまり、三者は代替関係ではなく併用できます。
一つの自社アプリから決まったAPIを一つ呼ぶだけなら、通常のAPI連携で十分な場合があります。MCPを使わないAPI呼び出しの設計例は、Linux/Pythonから画像対応APIを呼ぶ前の確認項目でも確認できます。
host・client・serverの関係
MCPはclient-server構成です。hostはAIアプリ全体を管理し、接続先のserverごとにclientを持ちます。今回の実測では、モデルは呼ばず、検証スクリプトをhost側のアプリに相当するものとして使いました。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
[host相当の検証スクリプト] | v [MCP client] | | stdio / MCP 2026-07-28 v [MCP server] | | 読み取りだけ v [架空のinventory.json] |
公式アーキテクチャでは、JSON-RPCベースのメッセージやtoolsなどを扱う「データ層」と、stdioやStreamable HTTPなどの接続方法を扱う「トランスポート層」を分けています。ローカルプロセス間のstdioと、リモート接続向けのStreamable HTTPは同じものではありません。
今回固定した環境は、MCP Python SDK 2.0.0、Python 3.11.13、Linux 4.18.0、stdioです。clientが実行時に採用したprotocol versionは2026-07-28でした。別トランスポートや外部hostで同じ結果になることまでは確認していません。

tools・resources・promptsを分けて考える
MCP serverが公開する三つの中核要素には、次の役割があります。
- tools:呼び出して処理を実行できる関数
- resources:コンテキストとして読み取るデータ
- prompts:再利用できる対話テンプレート
clientはtools/listなどで利用可能な要素を発見し、toolならtools/callで呼び出します。今回必要なのは「SKUと倉庫を指定して在庫を調べる」という処理なので、get_inventoryというtoolを一つだけ実装しました。resourcesやpromptsは追加していません。
架空在庫だけを読む最小tool
検証用データには架空のSKU、倉庫、数量、呼出主体、許可範囲だけを保存しました。実在する在庫、顧客、社内API、業務DB、認証情報には接続していません。
toolの中心部分は次のとおりです。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
@server.tool( name="get_inventory", description="許可された呼出主体が、許可範囲内の架空在庫をSKUと倉庫で検索します。", annotations=ToolAnnotations( readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=False, ), structured_output=True, ) def get_inventory( caller_id: Literal["warehouse-east-reader", "warehouse-west-reader"], sku: str, warehouse: Literal["WH-EAST", "WH-WEST"], ) -> InventoryResult: """架空fixtureだけを読み、在庫を変更しない。""" data = json.loads(FIXTURE_PATH.read_text(encoding="utf-8")) allowed_warehouses = data["permissions"].get(caller_id, []) if warehouse not in allowed_warehouses: raise ToolError("AUTHORIZATION_DENIED: requested scope is not allowed") for item in data["items"]: if item["sku"] == sku and item["warehouse"] == warehouse: return InventoryResult(**item) raise ToolError("INVENTORY_NOT_FOUND: no matching item in the allowed scope") |
掲載コードは記事専用fixtureからtool定義を抜き出したものです。Literal、InventoryResult、FIXTURE_PATHなどの定義は省略しています。実際の入力・出力schemaはSDKが生成したtools/list応答で確認しました。
| 項目 | 実測したschema |
|---|---|
| tool名 | get_inventory |
| 必須入力 | caller_id、sku、warehouse |
| 呼出主体 | warehouse-east-readerまたはwarehouse-west-reader |
| 倉庫 | WH-EASTまたはWH-WEST |
| 出力 | sku、warehouse、0以上の整数quantity |
| annotation | readOnlyHint: true、destructiveHint: false、idempotentHint: true、openWorldHint: false |
annotationはtoolの性質を伝えるヒントです。公式資料も、信頼できないserverのannotationだけでtool利用を判断しないよう注意しています。読み取り専用にする実際の境界は、書き込み機能を実装しないこと、server側で認可すること、実行前後のデータを比較することで確かめます。
正常応答だけでなく三つの失敗を確認する
検証前に入力と期待結果を固定し、同じserverへ順番に問い合わせました。
1. 許可範囲内の既知SKU
入力は次のとおりです。
|
1 2 3 4 5 |
{ "caller_id": "warehouse-east-reader", "sku": "SKU-DEMO-001", "warehouse": "WH-EAST" } |
応答はisError: falseで、構造化された結果は次の値でした。
|
1 2 3 4 5 |
{ "sku": "SKU-DEMO-001", "warehouse": "WH-EAST", "quantity": 12 } |
2. 権限外の倉庫
同じeast readerがWH-WESTを指定すると、server側の認可分岐がAUTHORIZATION_DENIEDを返し、isError: trueになりました。応答には数量も、そのSKUが存在するかどうかも含めていません。
3. 許可範囲内に存在しないSKU
east readerがWH-EASTでSKU-DEMO-999を検索すると、INVENTORY_NOT_FOUNDとisError: trueを返しました。権限エラーとは別の応答なので、利用者は「権限がない」のか「許可範囲内に該当データがない」のかを区別できます。
4. serverへ接続できない
存在しないserverファイルをstdio接続先に指定したところ、clientは接続を確立できず、検証側でCONNECTION_FAILEDに分類しました。これを在庫なしや空配列へ置き換えてはいません。
tool発見と以上4ケースの前後で、fixtureのファイル一覧とinventory.jsonのSHA-256を比較しました。全記録でハッシュはc5c7cba8be360dc22ffd935428aff737b90099ad63b4635dd755e6347a25d8acのままで、ファイル一覧にも変化はありませんでした。ただし、この結果が示すのは今回の一つのfixtureと実行経路が無変更だったことだけです。MCP対応やreadOnlyHintだけで、任意のserverが安全だと保証されるわけではありません。

実測経路を応答まで含めると、次の順序です。
|
1 2 3 4 5 6 7 8 9 |
host相当 MCP client MCP server 架空fixture | | | | | tools/list |--------------->| | | |<--tool schema--| | | tools/call |--------------->| | | | |--read only----->| | | |<--matching row--| | |<--result/error-| | |<--判定・記録--| | | |
RAG検索、外部API、モデル呼び出し、リモート認証はこの実測経路に含まれていません。
認証・認可・切断はMCPという名前だけで解決しない
今回のローカルfixtureでは、caller_idに応じて読める倉庫をserver側で限定しました。これは説明用の小さな認可モデルであり、本番の本人確認や資格情報管理を再現したものではありません。
リモート接続を検討するときは、少なくとも次を別途確認します。
- 利用clientが採用するprotocol versionとtransportへ対応しているか
- 認証で「誰か」を確認し、認可で「どのtoolとデータを使えるか」を絞っているか
- tokenをそのまま別サービスへ流用していないか
- 必要以上に広いscopeを与えていないか
- 接続解除後にtokenやserverプロセスをどう無効化するか
- 外部データを信頼済みとせず、機密情報の除外と原文照合を行うか
外部入力をAIへ渡す前の扱いは、生成AIへ送るサーバーログのマスキングと原因切り分けでも整理しています。
MCPのSecurity Best Practicesは、2026年10月20日時点でdraftとして公開されています。安定仕様の確定要件と同じ扱いにはせず、採用時は安定版Authorization仕様と利用clientの実装を確認してください。今回、リモートOAuthや接続解除後のtoken無効化は実測していません。
MCPを採用するか判断するチェックリスト
次のような場合は、MCPを検討する理由があります。
- 複数のAI hostから同じ外部機能を発見・利用させたい
- tools、resources、promptsの共通契約が必要
- hostごとの個別連携を減らし、server側に機能境界を置きたい
一方、固定された一つのアプリが既存APIを呼ぶだけなら、通常のAPI連携のほうが小さく保てる場合があります。課題がRAGの検索品質だけなら、MCP導入より先に検索対象、検索結果、回答との対応を見直すべきです。
採用前は、toolを一つに絞り、入力schema、出力schema、認可、正常応答、権限外、不存在、接続失敗、データ非変更を確認します。別transportや外部hostへ広げるのは、この最小経路が通ってからで十分です。
まとめ
APIはサービスを呼び出す契約、RAGは検索結果を生成へ渡す構成、MCPはAIアプリが外部データや機能を発見・利用する接続規約です。MCP serverの内側でAPIや検索基盤を使えるため、三者は併用できます。
MCPを試すときは、成功応答だけで判断せず、読み取り専用の小さなtoolで権限外、不存在、接続失敗、データ非変更まで確認してください。MCP対応、schema、annotation、モデルの判断だけでは、認可や安全性、回答精度は保証されません。
資料・検証確認日:2026年10月20日。参照:MCP公式「What is the Model Context Protocol?」、MCP公式「Architecture overview」、MCP Python SDK v2.0.0リリース、MCP公式「Tool Annotations as Risk Vocabulary」、MCP公式「Security Best Practices」(draft)、OpenAPI Specification、RAG原典論文。公開直前に安定仕様、SDK版、利用clientの対応を再確認してください。
著者・監修者:タケナカ

