- AI ⚙️
- サーバー
- 技術 💻
MCPサーバーの作り方|Pythonの完成コードとClaude Code登録
Python で MCP サーバーを作るには、公式 SDK(pip install "mcp[cli]")の MCPServer を作り、関数に @mcp.tool()(ツール)・@mcp.resource()(リソース)・@mcp.prompt()(プロンプト)を付けて、mcp.run() で起動します。Claude Code へは claude mcp add 名前 -- python /絶対パス/server.py の1行で登録できます。
MCP(Model Context Protocol)は、AI のアプリに外部のツールやデータをつなぐための共通の約束事(プロトコル)です。最新の SDK 2.x では、以前の FastMCP が MCPServer に名前が変わり、from mcp.server.fastmcp import FastMCP はエラーになります。この記事のコードは 2.3.0 で実際に動かしたものです。
| 手順 | やること | コマンド・書き方 |
|---|---|---|
| 1 | SDK を入れる | pip install "mcp[cli]"(Python 3.10 以上) |
| 2 | サーバーを書く | MCPServer("名前") + @mcp.tool() などのデコレーター |
| 3 | 動作を確かめる | mcp dev server.py(MCP Inspector)か Python の Client |
| 4 | Claude Code に登録 | claude mcp add rjc-tools -- python /絶対パス/server.py |
| 5 | 登録を確かめる | claude mcp list(√ Connected と出れば接続済み) |
| 6 | HTTP で公開する | mcp.run(transport="streamable-http", port=...) |
確認環境:Python 3.11.15・MCP Python SDK(PyPI の mcp)2.3.0・比較用に 1.30.0・Claude Code 2.1.301(npm の最新版)・MCP Inspector 2.11.0(npm)・Node.js 22.22.2・pytest 9.1.1・uv 0.8.17(Linux)。この記事の server.py を stdio と Streamable HTTP の両方で起動し、JSON-RPC を直接送るスクリプト、SDK の Client、curl、MCP Inspector の --cli、Claude Code の claude mcp add・list・get・remove で呼び出して、出力とエラー文を確かめました。出力中のパスは /home/you/... に置き換えています。Claude Desktop の画面操作は MCP 公式ドキュメント、設定ファイルは SDK の mcp install が書き出した内容で確かめました(確認日 2026年10月16日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「MCPサーバーの作り方|Pythonの完成コードとClaude Code登録」
この記事と関連記事の分担
この記事は「Python で作って、Claude Code・Claude Desktop につなぐ」までを1本で扱います。次の内容は関連記事に分けています。
- MCP そのものの仕組み(ホスト・クライアント・サーバーの関係)は、関連記事「MCPとは?サーバーとクライアントの仕組みと設定例を図解」で扱っています。
- TypeScript の公式 SDK で同じものを作る手順は、関連記事「MCPサーバーの作り方(TypeScript)|公式SDKでツール・リソースを作る」で扱っています。
- 既製の MCP サーバーを Claude Code に追加する設定(
.mcp.jsonの雛形や設定項目の一覧)は、関連記事「Claude Code MCP設定の追加方法|.mcp.json雛形と一覧」で扱っています。
MCPサーバーが提供する3つのもの
MCP サーバーが AI に出せるものは、ツール・リソース・プロンプトの3種類です。SDK のドキュメントは、違いを「誰が使うと決めるか」で説明しています。
| 種類 | 使うと決めるのは | 中身 | 例 |
|---|---|---|---|
| ツール(Tools) | モデル(AI) | AI が呼び出す関数。処理や書き込みもできる | 税込計算、チケット検索、API 呼び出し |
| リソース(Resources) | アプリ(ホスト) | 読み取り用のデータ。Web の GET に近い | コーディング規約、設定、チケット詳細 |
| プロンプト(Prompts) | 利用者 | 名前で呼ぶ依頼文のひな形 | 「このコードをレビューして」 |
MCP サーバーを使う側のアプリ(Claude Code・Claude Desktop など)をホスト、ホストの中でサーバーと通信する部分をクライアントと呼びます。サーバーはモデルと直接は話さず、クライアントからの要求に答えるだけです。
準備:Python SDK(mcp)を入れる
python -m venv .venv
source .venv/bin/activate # Windows は .venv\Scripts\activate
pip install "mcp[cli]" # uv なら uv add "mcp[cli]"
mcp version
MCP version 2.3.0
[cli] を付けると、mcp dev(Inspector で開く)・mcp run(ファイルを起動)・mcp install(Claude Desktop に登録)・mcp version のコマンドが入ります。PyPI の情報では mcp 2.3.0 は 2026年10月2日の公開で、Python 3.10 以上が必要です。
SDK 2.x と 1.x の違い(FastMCP → MCPServer)
PyPI には 2.x と並んで 1.x 系(1.30.0)も更新されています。pip install mcp で入るのは 2.x です。ネットで見かける FastMCP のコードは 1.x の書き方です。
| 項目 | 1.x(1.30.0) | 2.x(2.3.0) |
|---|---|---|
| import | from mcp.server.fastmcp import FastMCP |
from mcp.server import MCPServer |
| サーバーを作る | FastMCP("名前") |
MCPServer("名前") |
| デコレーター | @mcp.tool()・@mcp.resource()・@mcp.prompt() |
同じ |
| ポートなどの指定 | コンストラクターでも指定できた | run() の引数だけ |
| クライアント | stdio_client + ClientSession + initialize() |
Client 1つ |
| 話すプロトコルの版 | 2025-11-25 まで | 2026-07-28(以前の版のクライアントにも応答) |
ツール内の print(flush=True) |
通信に混ざる | SDK が stderr に逃がす |
2.3.0 で 1.x の import を書くと、次のエラーになります。
ModuleNotFoundError: No module named 'mcp.server.fastmcp'. This is mcp 2.x, where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import MCPServer) and other APIs changed; see the migration guide at https://py.sdk.modelcontextprotocol.io/v2/migration/#fastmcp-renamed-to-mcpserver or pin 'mcp<2' to keep running v1 code.
1.x のコードをそのまま動かすなら pip install "mcp[cli]<2" で固定します。1.30.0 の FastMCP で書いた add_tax も、2.3.0 の Client から呼べました(このときの版は 2025-11-25 に自動で下がりました)。なお、PyPI の fastmcp(4.1.0、PrefectHQ のリポジトリ)は、公式 SDK の mcp とは別のパッケージです。
完成コード:ツール・リソース・プロンプトを持つ server.py
社内の問い合わせチケットを検索し、税込価格を計算できるサーバーです。コピーしてそのまま動きます。
"""rjc-tools: ツール・リソース・プロンプトを1つずつ以上持つ MCP サーバー(mcp 2.x)"""
import logging
import sys
from typing import Annotated, Literal
from pydantic import Field
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ResourceNotFoundError, ToolError
from mcp.types import ToolAnnotations
logger = logging.getLogger(__name__) # ログは標準エラー出力へ(stdout は通信に使う)
mcp = MCPServer(
"rjc-tools",
instructions="社内の問い合わせチケットの検索と、税込価格の計算ができます。",
)
# 例として使う「社内チケット」のデータ(本番では DB や社内 API を呼ぶ)
TICKETS = {
"T-1001": {"id": "T-1001", "title": "ログイン画面が表示されない", "status": "open"},
"T-1002": {"id": "T-1002", "title": "CSV 出力の文字化け", "status": "closed"},
"T-1003": {"id": "T-1003", "title": "ログイン後に 500 エラー", "status": "open"},
}
@mcp.tool(annotations=ToolAnnotations(read_only_hint=True, open_world_hint=False))
def add_tax(
price: Annotated[int, Field(ge=0, description="税抜価格(円)")],
rate_percent: Literal[8, 10] = 10,
) -> int:
"""税抜価格から税込価格を計算します。1円未満は切り捨てます。"""
return price * (100 + rate_percent) // 100
@mcp.tool(annotations=ToolAnnotations(read_only_hint=True, open_world_hint=False))
def search_tickets(
keyword: Annotated[str, Field(min_length=1, description="件名に含まれる語")],
status: Literal["open", "closed"] = "open",
limit: Annotated[int, Field(ge=1, le=20)] = 10,
) -> list[dict]:
"""社内の問い合わせチケットを件名のキーワードで検索します。
過去の対応事例を調べるときに使います。新しい順に最大 limit 件を返します。"""
logger.info("search_tickets keyword=%r status=%s", keyword, status)
hits = [t for t in TICKETS.values() if keyword in t["title"] and t["status"] == status]
if not hits:
# ToolError の文はモデルに届き、モデルが引数を直して呼び直せる
raise ToolError(f"「{keyword}」を含む {status} のチケットはありません")
return hits[:limit]
@mcp.resource("docs://coding-rules", mime_type="text/markdown")
def coding_rules() -> str:
"""チームのコーディング規約"""
return "# コーディング規約\n- 変数名は英語で、意味がわかる名前にする\n- 金額は整数(円)で計算する\n"
@mcp.resource("tickets://{ticket_id}", mime_type="application/json")
def ticket_detail(ticket_id: str) -> dict:
"""チケット1件の詳細(URI の {ticket_id} が引数になる)"""
if ticket_id not in TICKETS:
raise ResourceNotFoundError(f"チケット {ticket_id} はありません")
return TICKETS[ticket_id]
@mcp.prompt()
def review_code(code: str, language: str = "Python") -> str:
"""コードレビューを依頼するプロンプトのひな形"""
return f"次の{language}のコードをレビューし、バグ・読みにくさ・テスト不足を箇条書きで挙げる。\n\n{code}"
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "http":
mcp.run(transport="streamable-http", host="127.0.0.1", port=38716)
else:
mcp.run() # 既定は stdio
コードの読みどころ
| 書いたもの | SDK が作るもの | ポイント |
|---|---|---|
関数名 add_tax |
ツール名 | name= で変えられる |
| docstring | ツールの説明文(description) |
AI はこれを読んで、いつ呼ぶかを決める |
型ヒント price: int |
引数の JSON Schema | 型が違う値は関数を呼ぶ前に弾かれる |
Field(ge=0, description=...) |
minimum・description |
引数ごとの説明と範囲 |
Literal[8, 10] |
enum: [8, 10] |
許可する値だけを受け付ける |
既定値 = 10 |
required から外れる |
省略できる引数になる |
戻り値の型 -> int |
outputSchema と structuredContent |
アプリが読む型つきの結果 |
ToolAnnotations(read_only_hint=True) |
annotations.readOnlyHint |
ホストへのヒント。安全の保証ではない |
raise ToolError(...) |
isError: true の結果 |
文がモデルに届き、引数を直して再挑戦できる |
"tickets://{ticket_id}" |
リソーステンプレート | URI の {} が関数の引数になる |
税込計算で price * 1.1 のように小数を使うと誤差で1円ずれることがあるため、整数で計算しています。ログは logging で出します。MCPServer を作った時点で、標準エラー出力(stderr)に向けたログ設定が入ります(SDK ドキュメント)。
stdio で起動して、JSON のやりとりを確かめる
stdio(標準入出力)は、ホストがサーバーを子プロセスとして起動し、標準入力に要求を書き、標準出力から応答を読む方式です。Claude Code・Claude Desktop のローカルのサーバーはこの方式です。
python server.py
何も表示されずに止まったように見えますが、これが正常です。ホストからの最初のメッセージを標準入力で待っています(Ctrl+C で終了)。すぐ終了したり、トレースバックが出たりする場合は、その内容が接続できない原因です。
tools/list と tools/call の実物
サーバーを子プロセスで起動し、JSON-RPC(1行に1つの JSON)を直接送って、応答をそのまま表示しました。まず、ホストが最初に送る initialize です(2025-11-25 版の手順)。
>>> {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-11-25", "capabilities": {}, "clientInfo": {"name": "raw", "version": "0.1"}}}
<<< {
"jsonrpc": "2.0",
"id": 1,
"result": {
"capabilities": {
"prompts": {"listChanged": false},
"resources": {"listChanged": false, "subscribe": false},
"tools": {"listChanged": false}
},
"instructions": "社内の問い合わせチケットの検索と、税込価格の計算ができます。",
"protocolVersion": "2025-11-25",
"serverInfo": {"name": "rjc-tools", "version": ""}
}
}
>>> {"jsonrpc": "2.0", "method": "notifications/initialized"}
続いて tools/list です。型ヒント・Field・Literal・既定値が、そのまま JSON Schema になっています(search_tickets は省略)。
>>> {"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}
<<< {
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"annotations": {"openWorldHint": false, "readOnlyHint": true},
"description": "税抜価格から税込価格を計算します。1円未満は切り捨てます。",
"inputSchema": {
"properties": {
"price": {"description": "税抜価格(円)", "minimum": 0, "title": "Price", "type": "integer"},
"rate_percent": {"default": 10, "enum": [8, 10], "title": "Rate Percent", "type": "integer"}
},
"required": ["price"],
"type": "object",
"title": "add_taxArguments"
},
"name": "add_tax",
"outputSchema": {
"properties": {"result": {"title": "Result", "type": "integer"}},
"required": ["result"],
"type": "object",
"title": "add_taxOutput"
}
}
]
}
}
tools/call で add_tax を呼びます。content はモデルが読むテキスト、structuredContent はアプリが読む型つきのデータです。
>>> {"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "add_tax", "arguments": {"price": 1980}}}
<<< {
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [{"text": "2178", "type": "text"}],
"isError": false,
"structuredContent": {"result": 2178}
}
}
リストを返す search_tickets では、要素ごとに content が1つずつ作られ、structuredContent には result の配列が入りました。
>>> {"jsonrpc": "2.0", "id": 5, "method": "tools/call", "params": {"name": "search_tickets", "arguments": {"keyword": "ログイン"}}}
<<< {
"jsonrpc": "2.0",
"id": 5,
"result": {
"content": [
{"text": "{\n \"id\": \"T-1001\",\n \"title\": \"ログイン画面が表示されない\",\n \"status\": \"open\"\n}", "type": "text"},
{"text": "{\n \"id\": \"T-1003\",\n \"title\": \"ログイン後に 500 エラー\",\n \"status\": \"open\"\n}", "type": "text"}
],
"isError": false,
"structuredContent": {
"result": [
{"id": "T-1001", "title": "ログイン画面が表示されない", "status": "open"},
{"id": "T-1003", "title": "ログイン後に 500 エラー", "status": "open"}
]
}
}
}
失敗したときに返る JSON
ツールの失敗は「エラー応答」ではなく、isError: true の結果として返ります。モデルはこの文を読んで、引数を直して呼び直せます。
| 送ったもの | 返ったもの |
|---|---|
add_tax に rate_percent: 5 |
isError: true、Input should be 8 or 10 [type=literal_error, ...] |
search_tickets に keyword: "請求書"(該当なし) |
isError: true、Error executing tool search_tickets: 「請求書」を含む open のチケットはありません |
存在しないツール名 no_such_tool |
isError: true、Unknown tool: no_such_tool |
resources/read に tickets://T-9999 |
JSON-RPC のエラー code: -32602、data.uri に URI |
>>> {"jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": {"name": "add_tax", "arguments": {"price": 1980, "rate_percent": 5}}}
<<< {
"jsonrpc": "2.0",
"id": 4,
"result": {
"content": [
{"text": "Error executing tool add_tax: 1 validation error for add_taxArguments\nrate_percent\n Input should be 8 or 10 [type=literal_error, input_value=5, input_type=int]\n For further information visit https://errors.pydantic.dev/2.14/v/literal_error", "type": "text"}
],
"isError": true
}
}
>>> {"jsonrpc": "2.0", "id": 11, "method": "resources/read", "params": {"uri": "tickets://T-9999"}}
<<< {"jsonrpc": "2.0", "id": 11, "error": {"code": -32602, "message": "チケット T-9999 はありません", "data": {"uri": "tickets://T-9999"}}}
SDK ドキュメントでは、ToolError 以外の想定外の例外(KeyError など)は、モデルには Error executing tool <名前> だけが届き、詳細はサーバーのログに出ると説明されています。内部の情報をモデルに渡さないための動きです。
リソースとプロンプトのやりとり
>>> {"jsonrpc": "2.0", "id": 9, "method": "resources/templates/list", "params": {}}
<<< {"jsonrpc": "2.0", "id": 9, "result": {"resourceTemplates": [{"description": "チケット1件の詳細(URI の {ticket_id} が引数になる)", "mimeType": "application/json", "name": "ticket_detail", "uriTemplate": "tickets://{ticket_id}"}]}}
>>> {"jsonrpc": "2.0", "id": 13, "method": "prompts/get", "params": {"name": "review_code", "arguments": {"code": "print(1)"}}}
<<< {"jsonrpc": "2.0", "id": 13, "result": {"description": "コードレビューを依頼するプロンプトのひな形", "messages": [{"content": {"text": "次のPythonのコードをレビューし、バグ・読みにくさ・テスト不足を箇条書きで挙げる。\n\nprint(1)", "type": "text"}, "role": "user"}]}}
固定の URI(docs://coding-rules)は resources/list に、{} を含む URI は resources/templates/list に分かれて出ます。
2026-07-28 版のやりとり(server/discover)
MCP の仕様は 2026-07-28 版で、最初の initialize の手順(ハンドシェイク)とセッションがなくなりました。代わりに、要求ごとに _meta へ版と能力を入れて送り、最初に server/discover で相手を確かめます。SDK 2.x の Client は既定でこの手順を使います。クライアントとサーバーの間に中継を入れて記録した実物です(tools/list の中身は省略)。
>>> {"jsonrpc": "2.0", "id": 1, "method": "server/discover", "params": {"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientInfo": {"name": "mcp", "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}}, "instructions": "社内の問い合わせチケットの検索と、税込価格の計算ができます。", "resultType": "complete", "supportedVersions": ["2026-07-28"], "ttlMs": 0, "_meta": {"io.modelcontextprotocol/serverInfo": {"name": "rjc-tools", "version": ""}}}}
>>> {"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "add_tax", "arguments": {"price": 1980}, "_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientInfo": {"name": "mcp", "version": "0.1.0"}, "io.modelcontextprotocol/clientCapabilities": {}}}}
<<< {"jsonrpc": "2.0", "id": 3, "result": {"content": [{"text": "2178", "type": "text"}], "isError": false, "resultType": "complete", "structuredContent": {"result": 2178}, "_meta": {"io.modelcontextprotocol/serverInfo": {"name": "rjc-tools", "version": ""}}}}
同じ server.py が、2025-11-25 版の initialize にも、2026-07-28 版の server/discover にも答えています。サーバー側で版を切り替える設定は要りません。Claude Code のドキュメントでも、Claude Code は相手が新しい版に対応していればそれを使い、そうでなければ以前の手順でつなぐと説明されています。
Python のクライアントで呼び出す
SDK 2.x の Client は、URL を渡すと Streamable HTTP、StdioServerParameters を渡すと子プロセス(stdio)、サーバーのオブジェクトを渡すとメモリ内でつなぎます。
# client.py:mcp 2.x の Client で stdio のサーバーを起動して呼ぶ
import sys
import anyio
from mcp import Client, StdioServerParameters
async def main() -> None:
params = StdioServerParameters(command=sys.executable, args=["server.py"])
async with Client(params) as client:
print("protocol:", client.protocol_version)
tools = await client.list_tools()
print("tools:", [t.name for t in tools.tools])
result = await client.call_tool("add_tax", {"price": 1980})
print("add_tax:", result.content[0].text, result.structured_content)
res = await client.read_resource("docs://coding-rules")
print("resource:", res.contents[0].text.splitlines()[0])
prompt = await client.get_prompt("review_code", {"code": "print(1)"})
print("prompt:", prompt.messages[0].content.text.splitlines()[0])
anyio.run(main)
protocol: 2026-07-28
tools: ['add_tax', 'search_tickets']
add_tax: 2178 {'result': 2178}
resource: # コーディング規約
prompt: 次のPythonのコードをレビューし、バグ・読みにくさ・テスト不足を箇条書きで挙げる。
1.x の記事にある stdio_client + ClientSession + await session.initialize() の3段の書き方は、2.x では Client 1つにまとまりました。
pytest で自動テストにする
Client(mcp) のようにサーバーのオブジェクトを渡すと、プロセスもポートも使わずにテストできます(SDK ドキュメントの Testing の書き方)。
# test_server.py
import pytest
from mcp import Client
from server import mcp
@pytest.fixture
def anyio_backend():
return "asyncio"
@pytest.fixture
async def client():
async with Client(mcp, raise_exceptions=True) as c: # プロセスもポートも使わず、メモリ内でつなぐ
yield c
@pytest.mark.anyio
async def test_add_tax(client: Client):
result = await client.call_tool("add_tax", {"price": 1980})
assert result.structured_content == {"result": 2178}
@pytest.mark.anyio
async def test_add_tax_rejects_bad_rate(client: Client):
result = await client.call_tool("add_tax", {"price": 1980, "rate_percent": 5})
assert result.is_error
assert "Input should be 8 or 10" in result.content[0].text
@pytest.mark.anyio
async def test_search_not_found(client: Client):
result = await client.call_tool("search_tickets", {"keyword": "請求書"})
assert result.is_error
assert "請求書" in result.content[0].text
$ python -m pytest -q test_server.py
... [100%]
3 passed in 0.77s
2.x では Python の属性名が is_error・structured_content のようにスネークケースです。JSON 上は今までどおり isError・structuredContent です。
MCP Inspector で確かめる
MCP Inspector は、MCP の公式の動作確認ツールです。ブラウザの画面でツールの一覧を見たり、フォームから呼び出したりできます。
mcp dev server.py # 画面で確かめる(npx が必要)
npx @modelcontextprotocol/inspector python server.py # 同じことを npx で
画面を開かずにコマンドで結果だけ見るなら --cli を付けます。CI のスクリプトにも組み込めます。
npx @modelcontextprotocol/inspector --cli python server.py --method tools/call --tool-name add_tax --tool-arg price=1980
{
"content": [
{
"type": "text",
"text": "2178"
}
],
"structuredContent": {
"result": 2178
},
"isError": false
}
| 確かめたいこと | --cli の後ろに付けるもの |
|---|---|
| ツールの一覧 | --method tools/list |
| ツールを呼ぶ | --method tools/call --tool-name add_tax --tool-arg price=1980 |
| リソースを読む | --method resources/read --uri docs://coding-rules |
| プロンプトを取る | --method prompts/get --prompt-name review_code --prompt-args code='x=1' |
| HTTP のサーバーにつなぐ | 起動コマンドの代わりに http://127.0.0.1:38716/mcp --transport http |
Inspector の接続待ちは既定で 15 秒でした。uv run --with "mcp[cli]" で初めて起動するときのように依存の取得が入ると、Connection timed out after 15000 ms になりました。この場合は --connect-timeout で延ばします(メッセージにもそう表示されます)。
Streamable HTTP で起動する
複数の人やサーバーから使う場合は、HTTP で待ち受ける Streamable HTTP を使います。完成コードは python server.py http で切り替わるようにしてあります。
python server.py http
INFO: Uvicorn running on http://127.0.0.1:38716 (Press CTRL+C to quit)
接続先は http://127.0.0.1:38716/mcp です。run() に渡せる主な引数は次のとおりです(SDK ドキュメント)。
| 引数 | 既定値 | 意味 |
|---|---|---|
host / port |
127.0.0.1 / 8000 |
待ち受けるアドレスとポート |
streamable_http_path |
/mcp |
MCP のエンドポイントのパス |
json_response |
False |
True で SSE ではなく JSON 1つで応答 |
stateless_http |
False |
以前の版のクライアントにセッションを持たない |
max_request_body_size |
4 MiB | 超えると HTTP 413 |
transport_security |
localhost 向けの設定 | Host・Origin の検査(DNS リバインディング対策) |
2.x では、port などは MCPServer(...) ではなく run() に渡します。MCPServer("x", port=9000) と書くと TypeError: MCPServer.__init__() got an unexpected keyword argument 'port' になりました。
curl で HTTP のやりとりを見る
2025-11-25 版の手順では、initialize の応答ヘッダーで mcp-session-id が返り、以後の要求にそれを付けます。
$ curl -s -i -X POST http://127.0.0.1:38716/mcp \
-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
HTTP/1.1 200 OK
content-type: text/event-stream
mcp-session-id: 28f1f2d71bd54162a03eeae28c9d130a
x-accel-buffering: no
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"capabilities":{...},"protocolVersion":"2025-11-25","serverInfo":{"name":"rjc-tools","version":""}}}
$ curl -s -X POST http://127.0.0.1:38716/mcp \
-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-H 'Mcp-Session-Id: 28f1f2d71bd54162a03eeae28c9d130a' -H 'MCP-Protocol-Version: 2025-11-25' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"add_tax","arguments":{"price":1980,"rate_percent":8}}}'
event: message
data: {"jsonrpc":"2.0","id":2,"result":{"content":[{"text":"2138","type":"text"}],"isError":false,"structuredContent":{"result":2138}}}
2026-07-28 版では、セッションなしで1回の POST で呼べます。仕様では、MCP-Protocol-Version・Mcp-Method・Mcp-Name のヘッダーを付け、本文の _meta に版を入れます。
$ curl -s -X POST http://127.0.0.1:38716/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: add_tax' \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"add_tax","arguments":{"price":1980},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'
{"jsonrpc":"2.0","id":3,"result":{"content":[{"text":"2178","type":"text"}],"isError":false,"resultType":"complete","structuredContent":{"result":2178},"_meta":{"io.modelcontextprotocol/serverInfo":{"name":"rjc-tools","version":""}}}}
| 送り方 | 返ったもの |
|---|---|
2025-11-25 版で Mcp-Session-Id なしに tools/list |
HTTP 400、Bad Request: Missing session ID(-32600) |
Host: evil.example.com を付けて送る |
HTTP 421、Invalid Host header |
2026-07-28 版でヘッダーと _meta を付ける |
HTTP 200、結果の JSON |
Python の Client で URL を渡すと 2026-07-28 版でつながり、mode="legacy" を付けると 2025-11-25 版でつながりました。
async with Client("http://127.0.0.1:38716/mcp") as c: # protocol: 2026-07-28
r = await c.call_tool("search_tickets", {"keyword": "CSV", "status": "closed"})
print(r.structured_content) # {'result': [{'id': 'T-1002', 'title': 'CSV 出力の文字化け', 'status': 'closed'}]}
async with Client("http://127.0.0.1:38716/mcp", mode="legacy") as c: # protocol: 2025-11-25
...
stdio と Streamable HTTP の使い分け
| 項目 | stdio | Streamable HTTP |
|---|---|---|
| 起動するのは | ホスト(子プロセスとして) | 自分(サーバーとして常駐) |
| 向いている用途 | 自分の PC のファイルやコマンドを扱う | チームで共有、社内システムにつなぐ |
| Claude Code への登録 | claude mcp add 名前 -- 起動コマンド |
claude mcp add --transport http 名前 URL |
| 認証 | 不要(自分の権限で動く) | 必要(--header や OAuth) |
| 標準出力 | 通信に使うので書かない | 自由に使える |
SSE(transport="sse")は以前の HTTP の方式で、SDK のドキュメントは新しく作るものには使わないよう案内しています。Claude Code のドキュメントでも SSE は非推奨(deprecated)で、HTTP が推奨です。
Claude Code に登録する(claude mcp add)
# stdio(-- の後ろがサーバーの起動コマンド。パスは絶対パス)
claude mcp add rjc-tools -- /home/you/rjc-tools/.venv/bin/python /home/you/rjc-tools/server.py
Added stdio MCP server rjc-tools with command: /home/you/rjc-tools/.venv/bin/python /home/you/rjc-tools/server.py to local config
File modified: /home/you/.claude.json [project: /home/you/proj]
python ではなく venv の中の Python を絶対パスで書くと、mcp を入れた環境で確実に起動します。uv を使うなら、SDK ドキュメントの例どおり claude mcp add rjc-tools -- uv run --with "mcp[cli]" mcp run /絶対パス/server.py でも起動できました。
claude mcp add の全オプション
Claude Code 2.1.301 の claude mcp add --help に表示されたオプションです。
| オプション | 意味 | 例 |
|---|---|---|
-s, --scope <scope> |
保存先のスコープ(local・user・project)。既定は local |
--scope project |
-t, --transport <transport> |
stdio・sse・http。省略時は stdio |
--transport http |
-e, --env <env...> |
サーバーに渡す環境変数(複数可) | -e API_KEY=xxx |
-H, --header <header...> |
HTTP・SSE のサーバーに送るヘッダー | -H "Authorization: Bearer ..." |
--client-id <clientId> |
OAuth のクライアント ID | --client-id my-app |
--client-secret |
OAuth のクライアントシークレットを入力(MCP_CLIENT_SECRET でも可) |
--client-secret |
--callback-port <port> |
OAuth のコールバックのポートを固定 | --callback-port 8080 |
-h, --help |
ヘルプ | ― |
# HTTP のサーバーを、自分のすべてのプロジェクトで使う(user スコープ)
claude mcp add --scope user --transport http rjc-tools-http http://127.0.0.1:38716/mcp \
--header "Authorization: Bearer \${MCP_TOKEN}"
Added HTTP MCP server rjc-tools-http with URL: http://127.0.0.1:38716/mcp to user config
Headers: {
"Authorization": "[REDACTED]"
}
File modified: /home/you/.claude.json
-e の直後にサーバー名を書くと失敗します。 -e は複数の値を受け取るため、名前まで環境変数として読まれます。
$ claude mcp add --scope project -e TICKET_API_KEY='${TICKET_API_KEY}' rjc-tools-team -- python server.py
Invalid environment variable format: rjc-tools-team, environment variables should be added as: -e KEY1=value1 -e KEY2=value2
ドキュメントにもこの注意があり、-e と名前の間に別のオプション(--transport stdio など)を挟むか、名前を先に書きます。次の順で通りました。
claude mcp add --scope project rjc-tools-team -e TICKET_API_KEY='${TICKET_API_KEY}' -- /home/you/rjc-tools/.venv/bin/python /home/you/rjc-tools/server.py
スコープ(local・project・user)と保存先
| スコープ | 使える範囲 | 共有 | 保存先 |
|---|---|---|---|
local(既定) |
今のプロジェクトだけ | しない | ~/.claude.json(プロジェクトごとの欄) |
project |
今のプロジェクト | する(リポジトリにコミット) | プロジェクト直下の .mcp.json |
user |
自分のすべてのプロジェクト | しない | ~/.claude.json(全体の欄) |
project スコープで登録すると、.mcp.json が次のように作られました。
{
"mcpServers": {
"rjc-tools-team": {
"type": "stdio",
"command": "/home/you/rjc-tools/.venv/bin/python",
"args": [
"/home/you/rjc-tools/server.py"
],
"env": {
"TICKET_API_KEY": "${TICKET_API_KEY}"
}
}
}
}
.mcp.json では ${VAR} と ${VAR:-既定値} で環境変数を展開でき、展開できる場所は command・args・env・url・headers です(ドキュメント)。API キーは .mcp.json に直接書かず、${...} で環境変数から渡します。 チームで共有する場合は、パスも各自で違うため、${HOME} などの変数を使うか、uv run などパスに依存しない起動コマンドにします。
同じ名前のサーバーが複数のスコープにあると、ドキュメントでは local → project → user の順で優先され、設定は項目ごとに混ぜずに1か所のものが丸ごと使われます。user と local に同じ名前 dup を登録すると、local のものが使われ、claude mcp list に [Conflicting scopes] の警告が出ました。
claude mcp list・get・remove で確かめる
$ claude mcp list
Checking MCP server health…
rjc-tools-http: http://127.0.0.1:38716/mcp (HTTP) - √ Connected
rjc-tools-team: /home/you/rjc-tools/.venv/bin/python /home/you/rjc-tools/server.py - ⏸ Pending approval (run `claude` to approve)
rjc-tools: /home/you/rjc-tools/.venv/bin/python /home/you/rjc-tools/server.py - √ Connected
MCP config diagnostics ‼
[Contains warnings] User config (available in all your projects)
Location: /home/you/.claude.json
└ [Warning] [rjc-tools-http] mcpServers.rjc-tools-http: Missing environment variables: MCP_TOKEN
[Contains warnings] Project config (shared via .mcp.json)
Location: /home/you/proj/.mcp.json
└ [Warning] [rjc-tools-team] mcpServers.rjc-tools-team: Missing environment variables: TICKET_API_KEY
| 表示 | 意味 | 次にすること |
|---|---|---|
√ Connected |
起動して接続できた | Claude Code で /mcp を開き、ツールを確かめる |
⏸ Pending approval |
.mcp.json のサーバーが承認待ち |
claude を起動して承認する |
× Failed to connect |
起動や接続に失敗 | 後ろのメッセージで原因を見る(次の章) |
Missing environment variables |
${...} の変数が未設定 |
変数を設定してから claude を起動する |
ドキュメントでは、project スコープのサーバーは安全のため、対話モードで使う前に承認を求めます。承認の選択をやり直すには claude mcp reset-project-choices を実行します。
$ claude mcp get rjc-tools
rjc-tools:
Scope: Local config (private to you in this project)
Status: √ Connected
Type: stdio
Command: /home/you/rjc-tools/.venv/bin/python
Args: /home/you/rjc-tools/server.py
Environment:
To remove this server, run: claude mcp remove rjc-tools -s local
$ claude mcp remove rjc-tools-team
Removed MCP server "rjc-tools-team" from project config
File modified: /home/you/proj/.mcp.json
| コマンド | 用途 |
|---|---|
claude mcp list |
登録済みのサーバーと接続状態の一覧 |
claude mcp get <名前> |
1件の詳細(スコープ・コマンド・環境変数) |
claude mcp remove <名前> [-s スコープ] |
削除。-s を省くと、あるスコープから消す |
claude mcp add-json <名前> '<JSON>' |
JSON 1つで登録(-s も使える) |
claude mcp add-from-claude-desktop |
Claude Desktop の設定を取り込む(Mac と WSL のみ) |
claude mcp login <名前> / logout |
HTTP のサーバーの OAuth 認証・解除 |
claude mcp reset-project-choices |
.mcp.json のサーバーの承認・拒否をリセット |
claude mcp serve |
Claude Code 自身を MCP サーバーとして起動 |
add-json は、ほかのツール向けに書かれた設定をそのまま使いたいときに便利です。
claude mcp add-json rjc-json '{"type":"stdio","command":"/home/you/rjc-tools/.venv/bin/python","args":["/home/you/rjc-tools/server.py"]}'
Claude Code の中での使い方
| やりたいこと | 操作(ドキュメント) |
|---|---|
| 接続状態・ツールを見る、オン・オフ、再接続 | セッション中に /mcp |
| リソースを会話に入れる | @ を入力して候補から選ぶ。形式は @サーバー名:プロトコル://パス |
| ツールの出力の上限を変える | MAX_MCP_OUTPUT_TOKENS(既定 25,000 トークン。10,000 で警告) |
| サーバーの起動待ちを延ばす | MCP_TIMEOUT=60000 claude(ミリ秒) |
| ツールの実行時間の上限 | MCP_TOOL_TIMEOUT、またはサーバーごとの "timeout" |
Claude Code は stdio のサーバーを起動するとき、環境変数 CLAUDE_PROJECT_DIR にプロジェクトのルートを入れます(ドキュメント)。サーバーの中で os.environ["CLAUDE_PROJECT_DIR"] を読むと、作業ディレクトリに頼らずにプロジェクトのファイルを探せます。
ドキュメントによると、ツールの説明文とサーバーの instructions は、最初から読み込まれるものでは 4,096 文字、ツール検索で読み込まれるものでは 16,384 文字を超えると切られます。大事なことは説明文の前のほうに書きます。
Claude Desktop に登録する
Claude Desktop は、設定ファイル claude_desktop_config.json にサーバーの起動コマンドを書いて登録します。MCP 公式ドキュメントでは、メニューの「Settings...」→「Developer」→「Edit Config」でファイルが開きます。
| OS | 設定ファイル | ログ |
|---|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
~/Library/Logs/Claude |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
%APPDATA%\Claude\logs |
{
"mcpServers": {
"rjc-tools": {
"command": "/Users/you/rjc-tools/.venv/bin/python",
"args": ["/Users/you/rjc-tools/server.py"],
"env": { "TICKET_API_KEY": "..." }
}
}
}
保存したら、Claude Desktop を完全に終了してから起動し直します(ウィンドウを閉じるだけでは読み直されません)。ログの mcp.log に接続の記録、mcp-server-サーバー名.log にサーバーの stderr が出ます。
SDK の mcp install を使うと、この設定を自動で書き込めます。実行すると、次の内容が書き込まれました。
mcp install server.py -v TICKET_API_KEY=dummy
{
"mcpServers": {
"rjc-tools": {
"command": "/home/you/.local/bin/uv",
"args": [
"run",
"--frozen",
"--with",
"mcp[cli]==2.3.0",
"mcp",
"run",
"/home/you/rjc-tools/server.py"
],
"env": {
"TICKET_API_KEY": "dummy"
}
}
}
}
uv の絶対パス、入っている SDK の版への固定(mcp[cli]==2.3.0)、サーバーの絶対パスが入ります。Claude Desktop の設定フォルダーがまだ無いと Claude app not found になりました。Claude Desktop を一度起動すると作られます(SDK ドキュメント)。
よくあるエラーと直し方(早見表)
| 症状・エラー文 | 原因 | 直し方 |
|---|---|---|
× Failed to connect — CONNECTION_CLOSED: Connection closed |
起動直後に終了(相対パス、import エラーなど) | 起動コマンドを手で実行し、stderr を見る |
can't open file '.../server.py': [Errno 2] No such file or directory |
相対パスで登録した | 絶対パスにする |
EACCES: permission denied, posix_spawn '.../server.py' |
スクリプトに実行権限がない | chmod +x か、python を先頭に付ける |
FileNotFoundError: [Errno 2] No such file or directory: 'pythn' |
コマンド名の誤り・PATH にない | which python の絶対パスを書く |
connection timed out after 30000ms |
起動に 30 秒以上かかった | MCP_TIMEOUT を延ばす、依存を先に入れておく |
Failed to parse JSONRPC message from server |
stdout に JSON 以外を書いた | print をやめて logging にする |
No module named 'mcp.server.fastmcp' |
1.x のコードを 2.x で動かした | MCPServer に書き換えるか mcp<2 に固定 |
unexpected keyword argument 'port' |
ポートをコンストラクターに渡した | mcp.run(transport=..., port=...) に移す |
Did you forget to call it? Use @tool() instead of @tool |
@mcp.tool の () 忘れ |
@mcp.tool() にする |
Invalid environment variable format: <名前> |
-e の直後にサーバー名 |
名前を先に書くか、間に別のオプションを挟む |
⏸ Pending approval |
.mcp.json のサーバーが未承認 |
claude を起動して承認する |
Missing session ID(HTTP 400) |
セッションなしで 2025-11-25 版の要求 | 先に initialize を送り、ID を付ける |
Invalid Host header(HTTP 421) |
許可していないホスト名で接続 | transport_security で許可するホストを設定 |
stdout に print すると壊れる
stdio では標準出力(stdout)がそのまま通信路です。MCP の仕様は、サーバーが stdout に MCP のメッセージ以外を書くことを禁止(MUST NOT)しています。実際に3通りで試しました。
| 書き方 | SDK 2.3.0 | SDK 1.30.0 |
|---|---|---|
ツールの中で print("...")(バッファーされる) |
呼び出しは成功。終了時に stdout に出て、クライアントが解析エラー | ― |
ツールの中で print("...", flush=True) |
SDK が stderr に逃がし、エラーなし | 通信に混ざり、クライアントが解析エラー |
import 時の print(python -u で即出力) |
最初の応答の前に混ざり、クライアントが解析エラー | ― |
SDK 2.3.0 のクライアントが出したエラーは次のとおりです。
Failed to parse JSONRPC message from server
Traceback (most recent call last):
...
pydantic_core._pydantic_core.ValidationError: 1 validation error for union[JSONRPCRequest,JSONRPCNotification,JSONRPCResponse,JSONRPCError]
Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='hello が呼ばれました', input_type=str]
SDK 2.x は、stdout に出された内容のうち、フラッシュされたものを stderr に移します。ただし、起動前の出力や、終了時にまとめて出る出力は防げません。今回の試験では、Python の Client は解析エラーを出したうえで続行し、Claude Code 2.1.301 の claude mcp list は √ Connected でした。ホストによっては接続を切るため、ログは logging(stderr)に出し、print は使わないのが確実です。どうしても print を使うなら print(..., file=sys.stderr) にします。
パス:ホストは別の作業ディレクトリから起動する
claude mcp add bad-relpath -- python server.py のように相対パスで登録すると、Claude Code はプロジェクトのフォルダーから起動するため、ファイルが見つかりません。
bad-relpath: /home/you/rjc-tools/.venv/bin/python server.py - × Failed to connect — CONNECTION_CLOSED: Connection closed
同じ状態を Python の Client で起動すると、stderr に can't open file '/home/you/server.py': [Errno 2] No such file or directory が出て、MCPError: Connection closed で終わりました。起動コマンドもスクリプトも絶対パスで書くのが基本です。SDK ドキュメントは、ホストが最小限の PATH でサーバーを起動するため uv が見つからないことがあり、その場合は which uv の絶対パスを書くよう案内しています。
権限:実行権限のないスクリプト
#! 行のあるスクリプトを直接登録し、実行権限を付けていなかった場合です。
no-exec: /home/you/rjc-tools/noexec.py - × Failed to connect — EACCES: EACCES: permission denied, posix_spawn '/home/you/rjc-tools/noexec.py'
chmod +x noexec.py で実行権限を付けるか、-- /絶対パス/python /絶対パス/noexec.py のように Python から起動します。
タイムアウト:起動待ちとツールの実行時間
起動に 40 秒かかるサーバーは、claude mcp list で次のように失敗しました。
slow-start: bash -c sleep 40; exec /home/you/rjc-tools/.venv/bin/python /home/you/rjc-tools/slow.py - × Failed to connect — MCP server "slow-start" connection timed out after 30000ms
MCP_TIMEOUT=60000 claude mcp list で起動待ちを 60 秒にすると √ Connected になりました。Claude Code のタイムアウトの設定は次のとおりです(ドキュメント)。
| 設定 | 対象 | 既定値・説明 |
|---|---|---|
MCP_TIMEOUT |
サーバーの起動待ち | 実行結果では 30000ms で打ち切り |
MCP_TOOL_TIMEOUT |
ツール1回の実行時間 | 未設定のときは約28時間 |
.mcp.json の "timeout" |
そのサーバーのツール実行時間 | MCP_TOOL_TIMEOUT より優先 |
CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT |
応答が途絶えたときの待ち時間 | stdio は 30 分、HTTP などは 5 分 |
CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS |
長いツール呼び出しをバックグラウンドへ | 既定は 2 分。0 で無効 |
Python の Client では、call_tool(..., read_timeout_seconds=2) で 5 秒かかるツールを呼ぶと、MCPError: Request 'tools/call' timed out になりました。時間のかかる処理は async def で書き、件数や期間を引数で絞れるようにしておくと、タイムアウトを避けやすくなります。
安全に使うための注意点
MCP サーバーは AI に新しい権限を渡すものです。stdio のサーバーは、あなたのユーザー権限で動きます。
作るとき
- ツールの権限を最小にする:汎用の「SQL を実行する」「シェルを実行する」ではなく、目的を絞ったツールにする
- 取り消せない操作は人の確認を挟む:削除・送信・支払いはツールにしないか、確認の手順を入れる。Claude Code のドキュメントでは、
tools/listの_metaに"anthropic/requiresUserInteraction": trueを付けたツールは、毎回許可を求めるようになると説明されています - 秘密情報をコードや
.mcp.jsonに書かない:環境変数から読み、.mcp.jsonには${API_KEY}の形で書く - 入力を信用しない:AI が渡す引数も、利用者の入力と同じく検証する。型ヒントと
Fieldの制約は、関数を呼ぶ前の最初の検証になる - 返すデータを絞る:個人情報や、AI に渡す必要のない項目を返さない
- 注釈は保証ではない:SDK ドキュメントは
read_only_hintなどを「ヒントであってセキュリティではない」と説明している
HTTP で公開するとき
MCP の仕様(Streamable HTTP)は、次の3点を求めています。
| 仕様の要求 | 内容 | SDK 2.3.0 での確認 |
|---|---|---|
| Origin ヘッダーの検査(MUST) | DNS リバインディング攻撃を防ぐ。不正なら 403 | 不正な Host で 421 Invalid Host header |
| localhost だけで待ち受け(SHOULD) | ローカルでは 0.0.0.0 ではなく 127.0.0.1 |
host の既定は 127.0.0.1 |
| 認証(SHOULD) | すべての接続に適切な認証 | SDK に OAuth の仕組みあり(run/authorization) |
社内のデータにアクセスできるツールを、認証なしで公開しないようにします。
他の人が作ったサーバーを使うとき
- 提供元が信頼できるか確かめてから入れる。Claude Code のドキュメントも「接続する前に各サーバーを信頼できるか確かめる」「外部の内容を取得するサーバーはプロンプトインジェクションの危険がある」と注意しています
- ツールの説明文も AI が読むため、説明文に指示が仕込まれることがある
- Web やメールなど外部の内容を読むツールと、外部に送信できるツールを同時に使うと、外部の内容に仕込まれた指示で情報が送られる危険がある
- 使わないサーバーは
/mcpで無効にするかclaude mcp removeで外す
どんなMCPサーバーを作ると便利か
| 用途 | ツール・リソースの例 |
|---|---|
| 社内の情報の検索 | 社内 Wiki・過去の問い合わせ・設計書の検索 |
| 開発の定型作業 | チケットの作成、テストデータの生成、規約に沿ったチェック |
| 社内システムとの連携 | 在庫や注文の状況の参照(読み取り専用から始める) |
| チームの知識の共有 | コーディング規約・用語集をリソースとして提供 |
まずは読み取り専用のツールから始めると、安全に効果を確かめられます。この記事の add_tax・search_tickets に read_only_hint=True を付けているのもそのためです。
よくある質問
Q. MCPサーバーの作り方を一言でいうと?
A. Python なら pip install "mcp[cli]" のあと、MCPServer("名前") を作り、関数に @mcp.tool() を付けて mcp.run() を呼ぶだけです。型ヒントが引数の定義に、docstring が説明文になります。完成コードは「完成コード」の章にあります。
Q. Python で MCP サーバーを自作するとき、FastMCP と MCPServer のどちらを使いますか?
A. 公式 SDK の最新版(2.x)では MCPServer です。FastMCP は 1.x での名前で、2.3.0 では import できません。1.x のコードを動かし続けるなら mcp<2 に固定します。PyPI の fastmcp は公式 SDK とは別のパッケージです。
Q. claude mcp add の基本の書き方は?
A. stdio なら claude mcp add [オプション] 名前 -- 起動コマンド 引数、HTTP なら claude mcp add --transport http 名前 URL です。オプション(--scope・-e・--header など)は名前より前に書き、-- の後ろはサーバーの起動コマンドとしてそのまま渡されます。
Q. チームで同じ MCP サーバーを使うには?
A. --scope project で登録すると、プロジェクト直下の .mcp.json に保存され、リポジトリにコミットして共有できます。各メンバーは最初に claude を起動したときに承認します。秘密情報は ${API_KEY} の形で環境変数から渡します。
Q. MCP と API は何が違いますか? A. API はサービスごとに呼び出し方が違います。MCP は AI のアプリとツールをつなぐための共通の約束事で、MCP サーバーとして作れば、MCP に対応した複数のアプリ(Claude Code・Claude Desktop・Cursor・VS Code など)から同じように使えます。MCP サーバーの中で既存の API を呼ぶ作りがよく使われます。
Q. Python 以外の言語でも作れますか? A. 作れます。MCP の公式 SDK は、Python のほか TypeScript・C#・Go・Rust・Ruby・Java・Swift・PHP・Kotlin があります(公式サイトの SDK 一覧)。TypeScript の手順は、関連記事「MCPサーバーの作り方(TypeScript)|公式SDKでツール・リソースを作る」で扱っています。
Q. 登録したのにツールが呼ばれません。
A. まず claude mcp list で √ Connected か、セッション中の /mcp でツールが一覧に出るかを確かめます。出ていない場合は「よくあるエラー」の表で原因を探します。出ているのに使われない場合は、docstring に「何をするか・いつ使うか・引数と戻り値」が書かれているかを見直します。
Q. print 文を入れたら、動かなくなりました。
A. stdio では stdout が通信路のため、print の出力が JSON-RPC のメッセージに混ざります。ログは logging か print(..., file=sys.stderr) で標準エラー出力に出します。
まとめ
- Python の MCP サーバーは、公式 SDK 2.x の
MCPServerに@mcp.tool()・@mcp.resource()・@mcp.prompt()を付けて作る。FastMCPは 1.x の名前 - 型ヒントと
Fieldが JSON Schema に、docstring が説明文になる。範囲外の値は関数を呼ぶ前に弾かれる - 失敗は
raise ToolError(...)でisError: trueとして返し、モデルに直させる - 同じコードが stdio と Streamable HTTP の両方で動き、2025-11-25 版の
initializeにも 2026-07-28 版のserver/discoverにも答える - 動作確認は
Client・MCP Inspector の--cli・pytest(Client(mcp)) でできる - Claude Code へは
claude mcp add 名前 -- 絶対パスの起動コマンド。共有は--scope project、-eの直後に名前を書かない - stdout に
printしない、パスは絶対パス、起動が遅いならMCP_TIMEOUT - 権限は最小に、読み取り専用から。HTTP で公開するなら Host・Origin の検査と認証を入れる
参考・出典
確認日はいずれも 2026年10月16日です。
- MCP Python SDK(GitHub、README と docs):https://github.com/modelcontextprotocol/python-sdk
- MCP Python SDK ドキュメント:https://py.sdk.modelcontextprotocol.io/
- MCP Python SDK「What's new in v2」:https://py.sdk.modelcontextprotocol.io/whats-new/
- mcp(PyPI):https://pypi.org/project/mcp/
- MCP 仕様 2026-07-28「Transports」:https://modelcontextprotocol.io/specification/2026-07-28/basic/transports
- MCP 仕様 2026-07-28「stdio」:https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio
- MCP 仕様 2026-07-28「Streamable HTTP」:https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http
- Model Context Protocol「Connect to local MCP servers」:https://modelcontextprotocol.io/docs/develop/connect-local-servers
- MCP Inspector(GitHub):https://github.com/modelcontextprotocol/inspector
- Model Context Protocol「SDKs」:https://modelcontextprotocol.io/docs/sdk
- Claude Code ドキュメント「Connect Claude Code to tools via MCP」:https://code.claude.com/docs/en/mcp
- Claude Code ドキュメントの一覧:https://code.claude.com/docs/en/claude_code_docs_map.md
- @anthropic-ai/claude-code(npm):https://www.npmjs.com/package/@anthropic-ai/claude-code
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「MCPサーバーの作り方|Pythonの完成コードとClaude Code登録」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。