- AI ⚙️
MCPとは?サーバーとクライアントの仕組みと設定例を図解
MCP(Model Context Protocol)とは、AIアプリと外部のツールやデータをつなぐための共通の接続規格です。 ホスト・クライアント・サーバーの3役が、JSON-RPC という形式のメッセージをやり取りします。
この記事では、公式仕様の最新版 2026-07-28 を今回開いて確認し、公式の Python SDK で小さなサーバーを動かして、実際に流れた JSON メッセージをそのまま載せます。最新版では、以前の版にあった初期化の手順(initialize)が無くなりました。その違いも、同じサーバーに両方の形式で話しかけた結果で示します。
確認環境:Ubuntu 24.04、Python 3.14.7、公式 Python SDK(PyPI の mcp 2.3.0)、curl 8.5.0(確認日 2026-10-06)。図は Mermaid(@mermaid-js/mermaid-cli 12.0.0)で描画できることを確かめています。仕様は modelcontextprotocol.io の 2026-07-28 版、設定ファイルの書式は Claude Code の公式ドキュメントを今回開いて確認した内容です。
この記事の結論
- MCP は接続の規格です。サーバーを1つ作れば、MCP に対応した複数の AI アプリから同じ方法で使えます。
- 登場するのは3役。ホスト(AIアプリ)、クライアント(ホストの中の接続係。サーバー1つにつき1つ)、サーバー(ツールやデータを提供する側)。
- メッセージは JSON-RPC。基本は
tools/listでツールの一覧を取り、tools/callで呼び出す、の2つです。 - 最新仕様 2026-07-28 では初期化の手順が無くなり、毎回のリクエストに版と機能の情報(
_meta)を付ける形になりました。2025-11-25 以前はinitializeから始めます。 - 通信方式は2つ。手元で動かすなら stdio、離れたサーバーや複数人で使うなら Streamable HTTP を選びます。
MCPの早見表
| 項目 | 内容 |
|---|---|
| 正式名 | Model Context Protocol |
| 何の規格か | AIアプリ(ホスト)と、外部のツール・データ(サーバー)の間のやり取り |
| メッセージ形式 | JSON-RPC(UTF-8) |
| 3役 | ホスト/クライアント/サーバー |
| サーバーが提供する3機能 | ツール(tools)、リソース(resources)、プロンプト(prompts) |
| 標準の通信方式 | stdio、Streamable HTTP |
| 版の表し方 | YYYY-MM-DD 形式の日付(後方互換性の無い変更を最後に入れた日) |
| 現在の版(Current) | 2026-07-28(公式の Versioning ページで確認) |
| 1つ前の版 | 2025-11-25(initialize の手順がある最後の版) |
| 主なメソッド | server/discover、tools/list、tools/call、resources/list、resources/read、prompts/list、prompts/get |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「MCPとは?サーバーとクライアントの仕組みと設定例を図解」
ホスト・クライアント・サーバーの3役
flowchart LR
U[利用者] --> H
subgraph APP[ホスト(AIアプリ)]
H[ホスト本体<br>LLMとの会話を管理]
C1[クライアント1]
C2[クライアント2]
H --> C1
H --> C2
end
C1 -- stdio --> S1[サーバーA<br>備品予約]
C2 -- Streamable HTTP --> S2[サーバーB<br>社内文書検索]
S1 --> R1[(予約データ)]
S2 --> R2[(文書)]
| 役 | 何をするか(公式仕様の Architecture より) | 身近な例 |
|---|---|---|
| ホスト | クライアントを作って管理する。利用者の同意や権限の判断、LLM とのやり取り、複数サーバーの情報の取りまとめを受け持つ | チャット型の AI アプリ、AI コーディングツール |
| クライアント | ホストの中で作られ、サーバー1つとだけ通信する。毎回のリクエストに版と機能の情報を付ける | ホストに組み込まれた接続係 |
| サーバー | ツール・リソース・プロンプトを提供する。手元のプロセスでも、離れた場所のサービスでもよい | 備品予約、文書検索、データベース照会など |
仕様の設計方針には、サーバーは会話の全体を読めず、ほかのサーバーの中も見えないと書かれています。会話の履歴はホストが持ち、サーバーには必要な情報だけが渡ります。「クライアントとサーバーが1対1」という形は、この境界を保つためのものです。
通信の流れ:一覧を取って、呼び出す
最新版(2026-07-28)での、ツール呼び出しまでの流れです。
sequenceDiagram
participant U as 利用者
participant H as ホスト(LLM)
participant C as クライアント
participant S as サーバー
Note over C,S: 1. 発見(任意)
C->>S: server/discover
S-->>C: 対応する版・機能・サーバー名
Note over C,S: 2. ツールの一覧
C->>S: tools/list
S-->>C: ツール名・説明・inputSchema
C->>H: ツール定義を渡す
U->>H: プロジェクターは空いている?
Note over H: LLMが check_stock を選ぶ
H->>C: check_stock(item=projector)
Note over C,S: 3. ツールの呼び出し
C->>S: tools/call
S-->>C: content(結果のテキスト)
C->>H: 結果を渡す
H->>U: 空きは2台です
ポイントは、どのツールを使うかを決めるのは LLM、実際にサーバーへ送るのはクライアントという分担です。LLM がサーバーと直接話すわけではありません。
実際のJSONメッセージ(2026-07-28)
次の最小サーバー(後述の server.py)に対して、JSON-RPC の行を自分で書いて送り、返ってきたものを載せます。題材は架空の備品予約です。
1. server/discover(任意) ― サーバーが対応する版と機能を尋ねます。
{
"jsonrpc": "2.0",
"id": 1,
"method": "server/discover",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {"name": "raw-client", "version": "0.1.0"},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}
返ってきた結果:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"cacheScope": "private",
"capabilities": {
"prompts": {"listChanged": true},
"resources": {"listChanged": true, "subscribe": true},
"tools": {"listChanged": true}
},
"resultType": "complete",
"supportedVersions": ["2026-07-28"],
"ttlMs": 0,
"_meta": {
"io.modelcontextprotocol/serverInfo": {"name": "equipment-demo", "version": "1.0.0"}
}
}
}
2. tools/list ― ツールの一覧です。リクエストは "method": "tools/list" に変わるだけで、_meta は同じものを付けます。
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"cacheScope": "private",
"resultType": "complete",
"tools": [
{
"description": "備品の空き数を返す。item は備品の英名(projector など)。",
"inputSchema": {
"type": "object",
"properties": {"item": {"title": "Item", "type": "string"}},
"required": ["item"],
"title": "check_stockArguments"
},
"name": "check_stock",
"outputSchema": {
"properties": {"result": {"title": "Result", "type": "string"}},
"required": ["result"],
"title": "check_stockOutput",
"type": "object"
}
}
],
"ttlMs": 0,
"_meta": {
"io.modelcontextprotocol/serverInfo": {"name": "equipment-demo", "version": "1.0.0"}
}
}
}
LLM が読むのは name・description・inputSchema です。説明文と引数の定義が、LLM にとっての取扱説明書になります。
3. tools/call ― ツールを呼び出します。
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "check_stock",
"arguments": {"item": "projector"},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {"name": "raw-client", "version": "0.1.0"},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}
返ってきた結果:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [{"text": "projector の空きは 2 台です", "type": "text"}],
"isError": false,
"resultType": "complete",
"structuredContent": {"result": "projector の空きは 2 台です"},
"_meta": {
"io.modelcontextprotocol/serverInfo": {"name": "equipment-demo", "version": "1.0.0"}
}
}
}
| 項目 | 意味 |
|---|---|
jsonrpc / id |
JSON-RPC の決まり。応答はリクエストと同じ id で返る |
method |
呼び出す操作の名前 |
params._meta |
版・クライアント名・クライアントの機能。2026-07-28 では毎回のリクエストに必須 |
result.resultType |
complete(通常の結果)か input_required(追加の入力が必要)。2026-07-28 で全結果に必須になった |
result.content |
LLM に渡す結果(テキスト・画像など) |
result.isError |
ツールの実行でエラーになったら true |
ttlMs / cacheScope |
一覧などをクライアントがキャッシュしてよい時間と範囲の目安 |
2025-11-25 以前の流れ(initialize から始まる)
1つ前の版までは、最初に版と機能を取り決める初期化の手順がありました。
sequenceDiagram
participant C as クライアント
participant S as サーバー
Note over C,S: 初期化(2025-11-25 以前)
C->>S: initialize(版・機能・クライアント名)
S-->>C: 版・機能・サーバー名
C-)S: notifications/initialized
Note over C,S: 以降のやり取り(_meta なし)
C->>S: tools/list
S-->>C: ツールの一覧
C->>S: tools/call
S-->>C: 結果
同じサーバーに、以前の形式で話しかけた結果です(SDK 2.3.0 のサーバーは両方の形式に応答しました)。
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"legacy-client","version":"0.1.0"}}}
{"jsonrpc": "2.0", "id": 1, "result": {"capabilities": {"prompts": {"listChanged": false}, "resources": {"listChanged": false, "subscribe": false}, "tools": {"listChanged": false}}, "protocolVersion": "2025-11-25", "serverInfo": {"name": "equipment-demo", "version": "1.0.0"}}}
このあとクライアントが {"jsonrpc":"2.0","method":"notifications/initialized"} を送り、_meta を付けずに tools/call を送ると、次の結果が返りました。
{"jsonrpc": "2.0", "id": 2, "result": {"content": [{"text": "whiteboard の空きは 0 台です", "type": "text"}], "isError": false, "structuredContent": {"result": "whiteboard の空きは 0 台です"}}}
2つの版の違い
| 観点 | 2025-11-25 以前 | 2026-07-28(現在の版) |
|---|---|---|
| 最初の手順 | initialize → notifications/initialized が必須 |
手順なし。どのリクエストからでも始められる |
| 版と機能の伝え方 | 初期化のときに1回 | 毎回のリクエストの _meta |
| サーバーの情報を知る方法 | initialize の応答 |
server/discover(サーバーは実装必須、呼ぶかどうかは任意) |
| 版が合わないとき | 初期化の中で取り決める | -32022(UnsupportedProtocolVersionError)が返り、対応する版の一覧が付く |
| HTTP のセッション | Mcp-Session-Id ヘッダーあり |
無し(状態を持たない) |
| サーバーからの依頼(利用者への確認など) | サーバーがリクエストを送る | 結果の中に input_required として入れて返し、クライアントが送り直す |
結果の resultType |
無し | 必須 |
公式の互換性の表では、以前の形式だけを話すクライアントと、最新の形式だけを話すサーバーの組み合わせはつながらないとされています。両方に対応した実装(仕様では dual-era と呼びます)であれば、相手に合わせて切り替えられます。使っているホストと SDK がどの版に対応しているかは、それぞれの公式ドキュメントで確かめてください。
ツール・リソース・プロンプトの違い
サーバーが提供できるものは3種類あり、「誰が使うと決めるか」が違います(公式仕様の Server Features の表より)。
| 機能 | 誰が決めるか | 何か | 例(仕様に載っているもの) | 主なメソッド |
|---|---|---|---|---|
| ツール(tools) | LLM(モデル) | LLM が呼び出して、操作や情報の取得を行う関数 | API への POST、ファイルの書き込み | tools/list、tools/call |
| リソース(resources) | アプリ(ホスト) | 文脈として添える、読み取り用のデータ。URI で指す | ファイルの内容、git の履歴 | resources/list、resources/read |
| プロンプト(prompts) | 利用者 | 利用者が選んで使う、ひな形の指示文 | スラッシュコマンド、メニュー | prompts/list、prompts/get |
条件から選ぶ判断表
| 作りたいもの | 選ぶ機能 |
|---|---|
| 会話の流れに応じて LLM に自動で使わせたい(検索、登録、計算) | ツール |
| データを書き換える・外部に送る操作 | ツール(ホスト側で利用者の確認を挟む) |
| 決まった文書や設定を、文脈として読ませたい | リソース |
| 利用者がメニューから選ぶ定型の依頼文 | プロンプト |
| どれか迷う | まずツール(LLM が会話に応じて使える) |
今回のサーバーで、リソースとプロンプトを取得した結果です。
{"jsonrpc": "2.0", "id": 6, "result": {"cacheScope": "private", "contents": [{"mimeType": "text/plain", "text": "予約は利用日の前日17時まで。1人2台まで。", "uri": "equipment://rules"}], "resultType": "complete", "ttlMs": 0}}
{"jsonrpc": "2.0", "id": 7, "result": {"description": "備品予約の依頼文を作る", "messages": [{"content": {"text": "projector を予約する依頼文を、です・ます調で3行で書いてください。", "type": "text"}, "role": "user"}], "resultType": "complete"}}
(どちらも _meta のサーバー情報は省いて載せています。)リソースは URI を指定して中身を読む、プロンプトは名前と引数を渡すと、LLM に渡すメッセージが返る、という違いが形に表れています。
stdioとStreamable HTTPの選び方
| 観点 | stdio | Streamable HTTP |
|---|---|---|
| サーバーの動き方 | クライアントが子プロセスとして起動する | 独立したプロセスとして動き、複数のクライアントを受け付ける |
| メッセージの運び方 | 標準入力・標準出力に、1行1メッセージ(改行区切り) | 1メッセージごとに、1つの MCP エンドポイントへ HTTP POST |
| 応答 | 標準出力に1行 | JSON 1つ、または SSE のストリーム(text/event-stream) |
| 版などの情報 | 本文の _meta だけ |
本文の _meta に加えて、同じ値を HTTP ヘッダーにも入れる |
| 認証 | 起動した本人の権限で動く | 仕様は適切な認証の実装を求めている |
| ログの出し先 | 標準エラー出力(標準出力には MCP のメッセージ以外を書かない) | 通常のサーバーログ |
条件から選ぶ判断表
| 条件 | 選ぶ方式 |
|---|---|
| 自分のPCのファイルやコマンドを扱う | stdio |
| まず1人で試す | stdio |
| チームで1つのサーバーを共有する | Streamable HTTP |
| クラウド上のサービスにつなぐ | Streamable HTTP |
| ロードバランサーや中継サーバーを通す | Streamable HTTP(ヘッダーで振り分けられる) |
以前の版(2024-11-05)にあった HTTP+SSE 方式は、2025-03-26 で Streamable HTTP に置き換えられ、現在は非推奨(Deprecated)です。仕様は、新しく作る実装では採用しないよう求めています。
Streamable HTTPのリクエスト例(curlで確認)
2026-07-28 では、MCP-Protocol-Version・Mcp-Method・Mcp-Name の各ヘッダーが必要で、Accept には application/json と text/event-stream の両方を書きます。
curl -i -X POST http://127.0.0.1:8000/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2026-07-28' \
-H 'Mcp-Method: tools/call' \
-H 'Mcp-Name: check_stock' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"check_stock","arguments":{"item":"projector"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"curl","version":"0.1.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}'
実行結果(日付や長さのヘッダーは省略。ポート番号は記事用に 8000 へ置き換え、本文の日本語は読みやすいように文字へ戻して載せています):
HTTP/1.1 200 OK
content-type: application/json
{"jsonrpc":"2.0","id":1,"result":{"content":[{"text":"projector の空きは 2 台です","type":"text"}],"isError":false,"resultType":"complete","structuredContent":{"result":"projector の空きは 2 台です"},"_meta":{"io.modelcontextprotocol/serverInfo":{"name":"equipment-demo","version":"1.0.0"}}}}
実際の応答では、日本語の部分は JSON のエスケープ形式(バックスラッシュと u に続く4桁の16進数)で返りました。JSON として読み込めば、上のとおりの文字に戻ります。
設定ファイルの記入例
ホストに「どのサーバーへ、どうつなぐか」を教えるのが設定ファイルです。ここでは Claude Code の公式ドキュメントで確認した書式を示します。ファイル名と置き場所はホストごとに違うので、ほかのホストではそのホストの公式ドキュメントを確かめてください。
stdio のサーバー(プロジェクト直下の .mcp.json)
{
"mcpServers": {
"equipment": {
"command": "python",
"args": ["/home/taro/mcp-demo/server.py"],
"env": {
"EQUIPMENT_DB": "/home/taro/mcp-demo/stock.db"
}
}
}
}
HTTP のサーバー
{
"mcpServers": {
"docs-search": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"headers": {
"Authorization": "Bearer ${DOCS_API_KEY}"
}
}
}
}
| 項目 | 書くこと |
|---|---|
mcpServers の直下のキー |
サーバーに付ける名前(自分で決める) |
command / args |
stdio サーバーの起動コマンドと引数。パスは絶対パスにすると、起動する場所に左右されない |
env |
サーバーに渡す環境変数 |
type / url |
HTTP サーバーの場合は "type": "http" と MCP エンドポイントの URL |
headers |
認証などの HTTP ヘッダー |
${VAR} / ${VAR:-既定値} |
環境変数の展開。鍵を設定ファイルに直接書かずに済む |
コマンドで登録する場合
# stdio(-- の後ろがサーバーの起動コマンド)
claude mcp add --transport stdio equipment -- python /home/taro/mcp-demo/server.py
# HTTP
claude mcp add --transport http docs-search https://mcp.example.com/mcp
# 確認と削除
claude mcp list
claude mcp get equipment
claude mcp remove equipment
スコープ(--scope) |
使える範囲 | 保存先 |
|---|---|---|
| local(既定) | 今のプロジェクトの自分だけ | ~/.claude.json |
| project | 今のプロジェクト。バージョン管理でチームと共有できる | プロジェクト直下の .mcp.json |
| user | 自分の全プロジェクト | ~/.claude.json |
Claude Code の中では /mcp で接続の状態を確認できます。サーバーの起動待ち時間は、環境変数 MCP_TIMEOUT(ミリ秒)で変えられます。上のコマンドと書式は公式ドキュメントの記載で、サンプルのパスやサーバー名は記事用に置き換えています。
メッセージを自分の目で見るための最小コード
仕組みを理解する近道は、SDK のクライアントを使わずに JSON の行を自分で送ってみることです。前の章のメッセージは、次の2ファイルで取得しました。
pip install "mcp==2.3.0"
python raw_client.py
# server.py 備品予約を題材にした最小のMCPサーバー(ツール・リソース・プロンプトを1つずつ)
import sys
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("equipment-demo", version="1.0.0")
STOCK = {"projector": 2, "whiteboard": 0}
@mcp.tool()
def check_stock(item: str) -> str:
"""備品の空き数を返す。item は備品の英名(projector など)。"""
if item not in STOCK:
raise ValueError(f"未登録の備品です: {item}(登録済み: {', '.join(STOCK)})")
return f"{item} の空きは {STOCK[item]} 台です"
@mcp.resource("equipment://rules")
def rules() -> str:
"""備品予約のルール"""
return "予約は利用日の前日17時まで。1人2台まで。"
@mcp.prompt()
def reserve_mail(item: str) -> str:
"""備品予約の依頼文を作る"""
return f"{item} を予約する依頼文を、です・ます調で3行で書いてください。"
if __name__ == "__main__":
transport = sys.argv[1] if len(sys.argv) > 1 else "stdio"
if transport == "http":
mcp.run("streamable-http", host="127.0.0.1", port=8000)
else:
mcp.run("stdio")
# raw_client.py SDKのクライアントを使わず、JSON-RPCの行を自分で書いてサーバーに送る
import json
import subprocess
import sys
META = {
"io.modelcontextprotocol/protocolVersion": sys.argv[1] if len(sys.argv) > 1 else "2026-07-28",
"io.modelcontextprotocol/clientInfo": {"name": "raw-client", "version": "0.1.0"},
"io.modelcontextprotocol/clientCapabilities": {},
}
proc = subprocess.Popen(
[sys.executable, "server.py"],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, encoding="utf-8",
)
def call(msg_id, method, params=None):
params = dict(params or {})
params["_meta"] = META
req = {"jsonrpc": "2.0", "id": msg_id, "method": method, "params": params}
proc.stdin.write(json.dumps(req, ensure_ascii=False) + "\n") # 1メッセージ=1行
proc.stdin.flush()
res = json.loads(proc.stdout.readline())
print(f"--> {method}")
print(json.dumps(res, ensure_ascii=False, indent=2))
return res
call(1, "server/discover")
call(2, "tools/list")
call(3, "tools/call", {"name": "check_stock", "arguments": {"item": "projector"}})
call(4, "tools/call", {"name": "check_stock", "arguments": {"item": "chair"}})
call(5, "tools/call", {"name": "no_such_tool", "arguments": {}})
call(6, "resources/read", {"uri": "equipment://rules"})
call(7, "prompts/get", {"name": "reserve_mail", "arguments": {"item": "projector"}})
call(8, "no/such_method")
proc.stdin.close() # 標準入力を閉じるとサーバーが終了する
print("exit code:", proc.wait(timeout=10))
call(4, ...) 以降は、わざとエラーになる呼び出しです。結果は次の章の表にまとめます。サーバーの作り込み(説明文の書き方、検査ツールでの確認、ホストへの登録の詳細)は、この記事では扱いません。
つながらない時のエラー別確認表
今回、実際に起こして確かめた症状と、その原因です。
stdio の場合
| 症状・返ってきたもの | 原因 | 確認すること |
|---|---|---|
FileNotFoundError: [Errno 2] No such file or directory: 'no-such-command' |
command に書いたコマンドが見つからない |
コマンド名のつづり、PATH。絶対パスで書く |
| 応答が1行も返らず、サーバーがすぐ終了する | args のファイルが無い、またはサーバーが起動時にエラーで止まっている |
設定と同じコマンドをターミナルで直接実行し、標準エラー出力を見る |
クライアント側で JSONDecodeError: Expecting value: line 1 column 1 (char 0) |
サーバーが標準出力に MCP 以外の文字を書いた(今回は print("server starting...") を1行足して再現) |
ログは標準エラー出力へ出す(Python なら print(..., file=sys.stderr)) |
{"code": -32602, "message": "Invalid request parameters"} |
2026-07-28 の形式で _meta を付けていない。または初期化せずに以前の形式で送った |
クライアントとサーバーの対応する版 |
{"code": -32022, "message": "Unsupported protocol version", "data": {"supported": ["2026-07-28"], "requested": "1900-01-01"}} |
サーバーが対応していない版を指定した | data.supported にある版で送り直す |
{"code": -32601, "message": "Method not found"} |
メソッド名が違う、またはサーバーがその機能を持っていない | server/discover の capabilities |
結果は返るが "isError": true |
ツールの中でエラーになった(引数の値が合わないなど) | content の文面、サーバー側のログ |
Streamable HTTP の場合
| HTTP の応答 | 本文 | 原因 | 確認すること |
|---|---|---|---|
接続できない(curl では Failed to connect ... Couldn't connect to server) |
― | サーバーが動いていない、ポートが違う | サーバーの起動、ホスト名とポート |
| 404 Not Found | Not Found(JSON ではない) |
URL のパスが MCP エンドポイントではない(/ に送った) |
URL の末尾(今回のサーバーは /mcp) |
| 404 Not Found | -32601 Method not found の JSON |
エンドポイントは合っているが、メソッド名が違う | メソッド名 |
405 Method Not Allowed(allow: POST) |
空 | GET で送った。2026-07-28 では GET のストリームが無くなった | クライアントの対応する版 |
| 406 Not Acceptable | 空 | Accept に application/json と text/event-stream が無い |
Accept ヘッダー |
| 400 Bad Request | -32020 mcp-name header does not match the request body's 'name' parameter |
ヘッダーと本文の値が食い違っている | Mcp-Name と params.name |
| 400 Bad Request | -32600 Bad Request: Missing session ID |
MCP-Protocol-Version などのヘッダーを付けなかったため、以前の版のリクエストとして扱われた |
必須ヘッダー3つ |
| 403 Forbidden | Invalid Origin header |
Origin ヘッダーが許可されていない(外部サイトからの呼び出しを防ぐ検査) |
サーバー側の許可する Origin の設定 |
本文が JSON-RPC のエラーなら「MCP のサーバーには届いている」、そうでなければ「届く前の段階」と切り分けられます。
Function Calling・RAGとの違い
| 観点 | MCP | Function Calling(ツール呼び出し) | RAG |
|---|---|---|---|
| 何か | AIアプリと外部ツールの接続の規格 | LLM が「この関数をこの引数で呼びたい」と返す、モデルの機能 | 検索した文書を回答の材料として渡す、作り方の型 |
| 決めているもの | ツールの見つけ方・呼び出し方・運び方(JSON-RPC、stdio、HTTP) | LLM への関数定義の渡し方と、返ってくる呼び出しの形 | 検索して、結果をプロンプトに入れる手順 |
| ツールの置き場所 | アプリの外(サーバー)。別のアプリからも使える | アプリの中に定義を書く | ― |
| 関係 | ホストは、MCP で集めたツール定義を Function Calling で LLM に渡す | MCP の tools/call を送るきっかけになる |
検索の部分を MCP のツールやリソースとして提供できる |
条件から選ぶ判断表
| 条件 | 選ぶもの |
|---|---|
| 1つのアプリの中だけで使う関数が数個ある | Function Calling を直接使う |
| 同じツールを複数の AI アプリから使いたい | MCP サーバーにする |
| すでにあるツールを、既製の AI アプリに追加したい | MCP サーバーにする |
| 社内文書をもとに回答させたい | RAG(検索の入口を MCP のツールにしてもよい) |
3つは置き換えの関係ではなく、層が違います。 MCP はツールを「どこに置き、どうつなぐか」、Function Calling は「LLM がどう呼び出しを表すか」、RAG は「何を材料に答えるか」の話です。
確かめて分かったこと
- 現在の版は 2026-07-28 で、初期化の手順が無くなっていました。
initializeから始まる説明は 2025-11-25 以前の版のものです。今回のサーバーには、_meta付きのtools/listをいきなり送っても応答が返りました。 _metaを付け忘れると-32602 Invalid request parametersになります。 メッセージからは原因が読み取りにくいので、表に入れました。- 公式 Python SDK 2.3.0 のサーバーは、2つの形式の両方に応答しました。
initializeを送れば 2025-11-25 として、_meta付きで送れば 2026-07-28 として動きます。ただしserver/discoverのsupportedVersionsに載るのは 2026-07-28 だけでした。 - 存在しないツール名を呼ぶと、今回の SDK は
"isError": trueの結果(本文Unknown tool: no_such_tool)を返しました。 仕様の例では JSON-RPC のエラー(-32602)になっています。クライアントを書くときは、両方の形を受け取れるようにしておくと確実です。 - ツールの中で例外を出すと、返る文面は
Error executing tool check_stockだけでした。 例外に書いたメッセージ(登録済みの備品名)は LLM に届きません。LLM に直し方を伝えたいときは、例外ではなく結果の文面として返す作りにします。 - SDK 2.x では、サーバー用のクラスが
mcp.server.mcpserver.MCPServerになっていました。 1.x のmcp.server.fastmcpを import すると、移行ガイドへの案内つきのエラーになります。 - HTTP で必須ヘッダーを付けないと、
Missing session IDという応答になりました。 ヘッダーが無いリクエストは以前の版として扱われるためで、文面と原因が離れています。
よくある質問
Q. MCPとは何ですか? A. AIアプリと外部のツールやデータをつなぐための共通の接続規格です。サーバー側を MCP に合わせて作っておけば、対応するどのAIアプリからも同じ方法で使えます。
Q. MCPサーバーとMCPクライアントの違いは? A. サーバーはツールやデータを提供する側、クライアントはAIアプリ(ホスト)の中でサーバーと通信する側です。クライアントはサーバー1つにつき1つ作られ、利用者が直接さわるのはホストです。
Q. MCPとAPIの違いは? A. API は個々のサービスが決めた呼び出し方、MCP は「AIアプリからツールを見つけて呼ぶ方法」をそろえた規格です。MCP サーバーの中で既存の API を呼ぶ、という重ね方が一般的です。
Q. MCPサーバーにつながらないときは?
A. まず設定に書いたコマンドをターミナルで直接実行して、サーバーが起動するかを確かめます。stdio ではコマンドのパスと標準出力への余計な出力、HTTP では URL のパス・Accept・必須ヘッダーが主な確認点です。上の「エラー別確認表」を順に当てはめてください。
Q. SSE と Streamable HTTP の違いは? A. HTTP+SSE は 2024-11-05 版の通信方式で、現在は非推奨です。Streamable HTTP は 2025-03-26 版で入った後継の方式で、1つのエンドポイントへの POST でやり取りします。新しく作るなら Streamable HTTP を選びます。
まとめ
- MCP は AIアプリと外部ツールをつなぐ共通の接続規格です。
- ホスト・クライアント・サーバーの3役が JSON-RPC でやり取りし、クライアントとサーバーは1対1です。
- 基本の流れは
tools/list→tools/call。どのツールを使うかは LLM が決め、送るのはクライアントです。 - 現在の版は 2026-07-28。初期化の手順が無くなり、毎回
_metaで版と機能を伝えます。2025-11-25 以前はinitializeから始めます。 - ツールは LLM、リソースはアプリ、プロンプトは利用者が使うと決めます。
- 手元なら stdio、共有や遠隔なら Streamable HTTP。stdio では標準出力に MCP 以外を書かないことが要点です。
- つながらないときは、本文が JSON-RPC のエラーかどうかで、届いているかを切り分けます。
参考・出典
すべて 2026-10-06 に閲覧しました。
- Model Context Protocol「Versioning」 https://modelcontextprotocol.io/specification/versioning (現在の版、版の表し方)
- 同「Versioning and Compatibility」 https://modelcontextprotocol.io/specification/2026-07-28/basic/versioning (版の取り決め、互換性の表)
- 同「Key Changes」 https://modelcontextprotocol.io/specification/2026-07-28/changelog (2025-11-25 からの変更点)
- 同「Architecture」 https://modelcontextprotocol.io/specification/2026-07-28/architecture/index (3役の説明、設計方針)
- 同「Server Features」「Tools」「Resources」「Prompts」「Discovery」 https://modelcontextprotocol.io/specification/2026-07-28/server/index
- 同「Transports」「stdio」「Streamable HTTP」 https://modelcontextprotocol.io/specification/2026-07-28/basic/transports
- 同「Lifecycle(2025-11-25)」 https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle (初期化の手順)
- Claude Code ドキュメント(MCP のページ) https://code.claude.com/docs/en/mcp (設定ファイルの書式、コマンド、スコープ)
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「MCPとは?サーバーとクライアントの仕組みと設定例を図解」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。