MCPサーバー自作入門|Python SDK 2.0の実装と3つの移行ポイント
代表取締役 上坂大地郎

結論: MCPサーバーは、Python SDK 2.0なら50行以下のコードで自作でき、Claude CodeやCodexからすぐに呼び出せる。ただし2026年7月28日のSDK 2.0でFastMCPがMCPServerにリネームされ、既存のチュートリアルはそのままでは動かない。この記事ではSDK 2.0に対応した最小実装から、実務で使える拡張パターンまでを手順つきで解説する。
この記事の要点:
- MCP Python SDK v2.0.0(2026年7月28日リリース)でクラス名・import path・ハンドラモデルが破壊的に変更された。v1のコードは
ModuleNotFoundErrorで起動しない - 最小構成なら3ファイル・50行で動く。セットアップから
claude mcp addでの登録、動作確認まで15分で完了できる - v1→v2への移行は3つのポイント(クラス名、トランスポート設定、ハンドラの型)を押さえれば対応できる
対象読者: Claude CodeやCodexでMCPサーバーを使っているが、自分の業務に合った独自サーバーを作りたいエンジニア。MCPの仕組みは知っているが、SDKの使い方や最新の変更点を整理したい方
読了後にできること: SDK 2.0で最小のMCPサーバーを動かし、Claude Codeに登録して実際にツールとして使える。既存のv1サーバーを移行するための判断材料も揃う
MCPの仕様自体が2026年7月にステートレスへ刷新された背景は、「MCP仕様がステートレスに刷新|セッション廃止で何が変わるか」で詳しく解説している。この記事では実装に集中し、「自分のMCPサーバーをどう作り、AIエージェントからどう使うか」を手順つきで解説する。
MCPサーバーの役割 — AIエージェントと外部をつなぐ標準規格
MCP(Model Context Protocol)は、Anthropicが2024年11月にオープンソースで公開したAIモデルと外部ツール・データソースをつなぐ標準プロトコル。社内のデータベース、外部API、ファイルシステムなど、AIが直接アクセスできないリソースへの橋渡しを担う。
プロトコルの3要素
MCPサーバーは、クライアント(Claude CodeやCodexなど)に対して3種類の機能を公開できる。
要素 | 役割 | 具体例 |
|---|---|---|
Tools | AIが「操作」するための関数 | DBクエリ実行、APIコール、ファイル変換 |
Resources | AIが「読む」ためのデータ | 設定ファイル、ドキュメント、DB内のレコード |
Prompts | 定型の入力テンプレート | コードレビュー依頼、レポート生成指示 |
実務で最も使うのはTools。まずはToolsだけのサーバーを作り、必要に応じてResourcesやPromptsを追加するのが効率的な進め方になる。
接続方式
MCPサーバーとクライアントの接続には、主に2つの方式がある。
- stdio: サーバーをローカルプロセスとして起動し、標準入出力で通信する。ローカル開発で最も手軽
- Streamable HTTP: HTTPエンドポイントとしてサーバーを公開する。チーム共有やリモート環境に向く
この記事ではまずstdioで動かし、後からStreamable HTTPに切り替える手順も示す。

SDKのインストールからClaude Codeへの登録、動作確認までの流れ。最小構成なら15分で完了する
SDK 2.0で変わったこと — FastMCPからMCPServerへ
2026年7月28日にリリースされたMCP Python SDK v2.0.0は、破壊的変更を含むメジャーアップデートだった。既存のv1チュートリアルやブログ記事のコードをそのまま使うと、ModuleNotFoundErrorで起動しない。
主な変更点を整理する。
クラス名とimport pathの変更
最も影響が大きいのがクラス名の変更。FastMCPはMCPServerにリネームされ、モジュールパスも移動した。
# v1(動かない)
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
# v2(現行)
from mcp.server import MCPServer
mcp = MCPServer("my-server")関連する変更として、FastMCPErrorはMCPServerErrorに、ctx.fastmcpはctx.mcp_serverに、それぞれリネームされている。
トランスポート設定の移動
v1ではコンストラクタでポートやホストを指定していたが、v2ではrun()メソッドに移動した。
# v1
mcp = FastMCP("my-server", port=9000)
# v2
mcp = MCPServer("my-server")
mcp.run(transport="stdio") # またはstreamable_http(port=9000)ハンドラの型変更
低レベルAPIを使っている場合は、ハンドラの書き方も変わっている。v1のデコレータ方式からコンストラクタパラメータ方式に移行し、引数の型も統一された。
ただし、@mcp.tool()デコレータを使ったツール定義はv1とv2で書き方がほぼ同じ。多くのユーザーにとって移行で触る箇所はimportとrunの2箇所だけになる。

v1→v2で変わった3つのポイント。@mcp.tool()デコレータの書き方は変わらないため、ほとんどのサーバーはimportとrunの修正で動く
最小構成のMCPサーバーを作る — 3ファイル・50行
ここからはSDK 2.0でゼロからMCPサーバーを作る手順を示す。Python 3.10以上が必要になる(SDK 2.0の要件、参照日: 2026-08-25)。
Step 1: プロジェクトを作る
mkdir my-mcp-server && cd my-mcp-server
python3 -m venv .venv && source .venv/bin/activate
pip install "mcp>=2.0"mcpパッケージのバージョンは、v2.0.0以上を指定する。v1系との互換性が必要なら"mcp>=2.0,<3"と上限も付けておくと安全になる。
Step 2: ツールを定義する
server.pyを作成する。この例では、指定したディレクトリのファイル一覧を返すツールと、テキストファイルの文字数を数えるツールを定義する。
from mcp.server import MCPServer
from pathlib import Path
mcp = MCPServer("file-tools")
@mcp.tool()
async def list_files(directory: str) -> str:
"""指定ディレクトリのファイル一覧を返す"""
path = Path(directory).resolve()
if not path.is_dir():
return f"エラー: {directory} はディレクトリではありません"
files = [f.name for f in path.iterdir() if f.is_file()]
return "\n".join(files) if files else "ファイルが見つかりません"
@mcp.tool()
async def count_chars(file_path: str) -> str:
"""テキストファイルの文字数を数える"""
path = Path(file_path).resolve()
if not path.is_file():
return f"エラー: {file_path} が見つかりません"
text = path.read_text(encoding="utf-8")
return f"{len(text)}文字({len(text.split())}語)"
if __name__ == "__main__":
mcp.run(transport="stdio")ポイントは3つある。
@mcp.tool()デコレータでツールを登録する。関数のdocstringがツールの説明としてAIに渡される- 型ヒント(
directory: str)がそのまま入力スキーマになる。Zodやjsonschemaを手書きする必要はない mcp.run(transport="stdio")で起動方式を指定する
Step 3: 動作確認する
SDK 2.0にはCLIの検査コマンドが付属している。サーバーが正しくツール一覧を返すか確認できる。
python3 -m mcp dev server.pyブラウザが開き、定義したツールの一覧とテスト実行画面が表示される。list_filesに適当なディレクトリパスを渡して結果が返ればサーバーは正常に動作している。
Claude Codeに登録して使う
作ったサーバーをClaude Codeから呼び出すには、claude mcp addコマンドで登録する。
.mcp.jsonへの登録
# プロジェクトスコープで登録(.mcp.jsonに書き込まれる)
claude mcp add file-tools \
--transport stdio \
-- python3 /absolute/path/to/server.py登録後、.mcp.jsonには次のようなエントリが追加される。
{
"mcpServers": {
"file-tools": {
"command": "python3",
"args": ["/absolute/path/to/server.py"],
"transport": "stdio"
}
}
}スコープは3段階ある。
スコープ | 保存先 | 用途 |
|---|---|---|
|
| このプロジェクトだけで使う |
|
| すべてのプロジェクトで使う |
|
| gitで共有する |
個人の実験にはlocal、チーム全体で使うならprojectを選ぶ。
Claude Codeで動作確認する
Claude Codeを起動し、登録したツールが認識されているか確認する。
/mcpfile-toolsが一覧に表示され、list_filesとcount_charsがツールとして見えていれば成功。あとは会話の中で「このディレクトリのファイル一覧を見せて」と指示すれば、Claude Codeが自動的にMCPツールを呼び出す。

claude mcp addの3つのスコープと、登録後の動作確認手順。個人用途ならlocal、チーム共有ならprojectを選ぶ
実用パターン — 業務で使えるツール設計3例
最小サーバーが動いたら、実務に合わせてツールを追加していく。ここでは3つのパターンを示す。
パターン1: データベースへのクエリ
社内のSQLiteやPostgreSQLに対して、読み取り専用のクエリを実行するツール。書き込み権限を与えないことで安全に運用できる。
import sqlite3
@mcp.tool()
async def query_db(sql: str) -> str:
"""読み取り専用でSQLを実行する(SELECT文のみ)"""
if not sql.strip().upper().startswith("SELECT"):
return "エラー: SELECT文のみ実行できます"
conn = sqlite3.connect("data.db")
conn.execute("PRAGMA query_only = ON")
try:
rows = conn.execute(sql).fetchall()
return "\n".join(str(row) for row in rows)
finally:
conn.close()パターン2: 外部APIの呼び出し
REST APIを呼び出してデータを取得するツール。APIキーは環境変数で渡し、コードに埋め込まない。
import httpx
import os
@mcp.tool()
async def fetch_weather(city: str) -> str:
"""指定した都市の天気を取得する"""
api_key = os.environ.get("WEATHER_API_KEY")
if not api_key:
return "エラー: WEATHER_API_KEYが設定されていません"
async with httpx.AsyncClient() as client:
resp = await client.get(
f"https://api.example.com/weather?q={city}&key={api_key}"
)
return resp.textパターン3: ファイル変換
MarkdownをHTMLに変換する、CSVをJSON形式にする、といったファイル変換ツール。AIが直接扱いにくいフォーマットの橋渡しに使える。
import csv
import json
from io import StringIO
@mcp.tool()
async def csv_to_json(csv_text: str) -> str:
"""CSV文字列をJSON配列に変換する"""
reader = csv.DictReader(StringIO(csv_text))
return json.dumps(list(reader), ensure_ascii=False, indent=2)いずれのパターンでも、ツールが受け取る引数と返す値は文字列にしておくのが扱いやすい。AIモデルが直接解釈できる形式にすることで、呼び出し側の処理が簡潔になる。
v1から移行する — チェックリストと手順
既にv1(FastMCP)でサーバーを運用している場合の移行手順をまとめる。
移行チェックリスト
# | 確認項目 | v1 | v2 | 影響 |
|---|---|---|---|---|
1 | クラス名 |
|
| 全サーバー |
2 | importパス |
|
| 全サーバー |
3 | エラークラス |
|
| 例外を捕捉している場合 |
4 | コンテキスト参照 |
|
| Contextを使う場合 |
5 | get_context() | あり | 削除( | get_context()を呼ぶ場合 |
6 | トランスポート設定 | コンストラクタ |
| port/host指定がある場合 |
7 | 型フィールド名 | camelCase混在 | snake_case統一 | 型を直接参照する場合 |
8 | WebSocket |
| 削除 | WS接続を使う場合 |
典型的な移行手順
大半のサーバーは、次の2箇所を変更するだけで動く。
- from mcp.server.fastmcp import FastMCP
- mcp = FastMCP("my-server")
+ from mcp.server import MCPServer
+ mcp = MCPServer("my-server")@mcp.tool()デコレータを使ったツール定義は書き換え不要。importとクラス名を直せば、ほとんどのサーバーはそのまま起動する。
get_context()を呼んでいる場合は、関数の引数にctx: Contextを追加し、from mcp.server import Contextをimportする。
pip install時の注意
requirements.txtやpyproject.tomlでmcpのバージョンを上限なしで指定していると、pip installのタイミングでv2に上がり、既存コードが壊れる。移行前のサーバーは"mcp>=1.27,<2"のように上限を付けておく。
【要注意】自作MCPサーバーでつまずく4パターン
パターン1: ModuleNotFoundError — v1のimportが残っている
ModuleNotFoundError: No module named 'mcp.server.fastmcp'最も多いエラー。pip install --upgrade mcpでSDK 2.0が入った後に、v1のimportパスを使っているケースで発生する。修正はimport行の書き換えだけで済む。
パターン2: ツールの引数スキーマが壊れている
サーバーは起動し、ツール一覧も返すが、実際にツールを呼ぶと引数が正しく渡らないケース。SDK 2.0では型ヒントが*argsにまとめられると引数スキーマが空になるという報告がある(Zenn undocumented氏の検証、参照日: 2026-08-25)。
回避策は、ツール関数の引数を個別の名前付き引数で定義すること。*argsや**kwargsは使わない。
パターン3: run()のtransport指定漏れ
v2ではサーバーの起動方法が変わったため、mcp.run()を引数なしで呼ぶとエラーになる場合がある。transport="stdio"を明示する。
パターン4: 仮想環境のパス不一致
claude mcp addで指定したPythonのパスが、MCPサーバーの依存パッケージがインストールされた仮想環境と一致しないケース。.mcp.jsonのcommandに仮想環境のPythonフルパス(例: /path/to/.venv/bin/python3)を指定する。

起動しない、ツールが見えない、引数が渡らない — よくある3パターンの原因と対処法の分岐
よくある質問
Q1: TypeScriptでも作れますか?
作れる。MCP TypeScript SDK v2も同時期(2026年7月下旬)にリリースされている。@modelcontextprotocol/sdkパッケージを使い、ESMプロジェクトならcodemodで自動移行も可能。ただしCommonJS(require())プロジェクトはcodemodの対象外で、手動移行が必要になる。
Q2: v2.0とv2.1の違いは何ですか?
v2.1.0(2026年8月24日リリース)では、Clientの直接設定、Prompt messagesでのImage/Audio対応、SSEとOAuthのリクエストボディ上限拡張が追加された。v2.0.0からの破壊的変更はない。
Q3: 既存のMCPサーバーは公式からどのくらい公開されていますか?
2026年8月時点で公開MCPサーバーは17,000本を超えている。AWSが54本を一括公開した例もある。自作する前に、やりたいことに近いサーバーがないか公式レジストリを検索する価値はある。
Q4: セキュリティ上の注意点はありますか?
MCPサーバーはローカルプロセスまたはHTTPサーバーとして動くため、ツールに与える権限には注意が必要。データベース接続はSELECTのみに制限する、ファイル操作は対象ディレクトリを限定するなど、最小権限の原則を守る。APIキーや認証情報はコードに埋め込まず、環境変数で渡す。
Q5: Claude Code以外のクライアントでも使えますか?
MCPはオープンプロトコルなので、対応クライアントなら使える。2026年8月時点では、Claude Code、Codex、Cursor、Cline、Zed、Continue.devなどが対応している。.mcp.jsonの形式はクライアントによって異なる場合がある。
今日サーバーを動かし、来週チームに展開する
MCPサーバーの自作は、SDK 2.0で50行のコードと15分のセットアップで始められる。やるべきことを3つに絞ると次のようになる。
- 今日: この記事の最小構成(
server.py)をコピーし、claude mcp addで登録して動かす - 今週: 自分の業務で繰り返しているAPI呼び出しやDB検索をツール化する
- 来週:
.mcp.jsonをgitにコミットし、チームで共有する。Streamable HTTPで共用サーバーにする選択肢も検討する
v1のサーバーを持っている場合は、importとrunの2行を直すところから始める。@mcp.tool()の書き方は変わっていないため、ツール定義自体は触る必要がない。
MCPサーバーで何ができるかの具体例は、「Claude Code Hooks入門|HTTP対応の設定と自動化レシピ5選」のHooks連携パターンも参考になる。また「Agent Skillsの作り方|5分で始める7つの実践設計ルール」では、MCPとは別のアプローチであるSkillsとの使い分けを解説している。
参考・出典
- MCP Python SDK — 公式リリースノート(https://github.com/modelcontextprotocol/python-sdk/releases)(参照日: 2026-08-25)
- MCP Python SDK v2 — What's New(https://py.sdk.modelcontextprotocol.io/whats-new/)(参照日: 2026-08-25)
- MCP公式ブログ — Beta SDKs for the 2026-07-28 Spec Release Candidate(https://blog.modelcontextprotocol.io/posts/sdk-betas-2026-07-28/)(参照日: 2026-08-25)
- MCP Python SDK v2 beta: what is new and how to try it — Pydantic(https://pydantic.dev/articles/mcp-python-sdk-v2-beta)(参照日: 2026-08-25)
- MCP Python SDK 2.0 で自作サーバーが壊れた2つの原因 — Zenn undocumented氏(https://zenn.dev/undocumented/articles/mcp-python-sdk-pitfalls)(参照日: 2026-08-25)
- FastMCP 4 (MCP 2026-07-28) で何が変わった? — Zenn woodstock_tech氏(https://zenn.dev/woodstock_tech/articles/61c1a4048f6eea)(参照日: 2026-08-25)
- MCP Gets Its Biggest Rewrite — Real Python(https://realpython.com/python-news-august-2026/)(参照日: 2026-08-25)
- MCP Went Stateless: What the 2026-07-28 Spec Actually Changes — DEV Community(https://dev.to/krlz/mcp-went-stateless-what-the-2026-07-28-spec-actually-changes-273k)(参照日: 2026-08-25)
