MCPサーバーを自作する — Model Context Protocolの最小実装
MCP(Model Context Protocol)は、LLMが外部ツールやリソースにアクセスするためのオープンプロトコルです。Anthropicが提唱し、OpenAIやGoogleも対応を進めており、AIエージェントの機能拡張における事実上の標準になりつつあります。
本記事では、MCPサーバーをゼロから実装する最小手順を解説します。Python SDKを使って「計算機ツールを提供するサーバー」を作り、デバッグツールのMCP Inspectorで動作確認までを行います。
MCPサーバーの最小構成要素
MCPサーバーを動かすために必要な要素は4つだけです。
- サーバーインスタンス — プロトコルメッセージを処理する本体
- トランスポート — クライアントとの通信手段(デフォルトはstdio)
- ツール定義 — サーバーが提供する機能
- エントリポイント — サーバーを起動する関数
プロトコルの複雑さ(JSON-RPCメッセージの解析、ハンドシェイク、ライフサイクル管理)はSDKが内部で処理するため、開発者は「どのようなツールを提供するか」だけに集中できます。
開発環境の準備
MCPの公式Python SDKはmcpパッケージとしてPyPIで配布されています。Python 3.10以上が必要です。
mkdir mcp-server-project && cd mcp-server-project
python3 -m venv .venv
source .venv/bin/activate
pip install "mcp[cli]"
[cli]エクストラを指定すると、デバッグツールのMCP Inspectorも一緒にインストールされます。インストール後、python3 -c "import mcp; print(mcp.__version__)"で確認できます。
最小限のMCPサーバーを実装する
足し算・引き算・掛け算・割り算ができる計算機ツールを提供するサーバーを作ります。src/server.pyを作成し、以下のコードを記述します。
#!/usr/bin/env python3
""" 最小限のMCPサーバー — 計算機ツールを提供する """
from mcp.server import Server
from mcp.server.stdio import stdio_server
import mcp.types as types
import asyncio
import logging
import sys
# ログはstderrに出力(stdoutはMCP通信に使うため絶対禁止)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
stream=sys.stderr,
)
server = Server("calculator-server")
@server.list_tools()
async def list_tools() -> list[types.Tool]:
""" サーバーが提供するツールの一覧を返す """
return [
types.Tool(
name="add",
description="2つの数値を足し算する",
inputSchema={
"type": "object",
"properties": {
"a": {"type": "number", "description": "足される数"},
"b": {"type": "number", "description": "足す数"},
},
"required": ["a", "b"],
},
),
types.Tool(
name="divide",
description="2つの数値の割り算を行う(a / b)",
inputSchema={
"type": "object",
"properties": {
"a": {"type": "number", "description": "割られる数"},
"b": {"type": "number", "description": "割る数(0以外)"},
},
"required": ["a", "b"],
},
),
]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
""" ツール呼び出しを処理して結果を返す """
a = arguments.get("a")
b = arguments.get("b")
if a is None or b is None:
raise ValueError("引数 a と b は必須です")
if name == "add":
result = a + b
elif name == "divide":
if b == 0:
raise ValueError("ゼロで割ることはできません")
result = a / b
else:
raise ValueError(f"不明なツール: {name}")
return [
types.TextContent(
type="text",
text=f"計算結果: {a} {name} {b} = {result}",
)
]
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
server.create_initialization_options(),
)
if __name__ == "__main__":
asyncio.run(main())
コードの解説
ツール一覧の定義
@server.list_tools()デコレータを付けた関数は、クライアントがtools/listリクエストを送ったときに呼び出されます。各ツールにはname、description、inputSchemaを定義します。
inputSchemaはJSON Schema形式で記述します。LLMはこのスキーマを読んで「どのような引数を渡せばよいか」を判断するため、各パラメータのdescriptionを明確に記述することが重要です。曗昧な説明だとLLMが間違った引数を渡す原因になります。
ツール呼び出しの処理
@server.call_tool()デコレータを付けた関数は、クライアント(LLM)がtools/callリクエストを送ったときに呼び出されます。nameにはツール名、argumentsにはLLMが構築した引数オブジェクトが渡されます。
戻り値はコンテンツのリストで、テキスト結果を返す場合はtypes.TextContentを使います。画像を返す場合はtypes.ImageContent、リソースを埋め込む場合はtypes.EmbeddedResourceを使用します。
トランスポートの設定
stdio_server()はstdioトランスポートを提供するコンテキストマネージャです。サーバーは標準入力からMCPメッセージを読み取り、標準出力にレスポンスを書き込みます。これがMCPのデフォルト通信方式です。
stdoutへのログ出力は絶対禁止
MCPサーバーでは絶対に標準出力(stdout)にログを出力してはいけません。stdioトランスポートはstdoutをプロトコルメッセージの通信路として使うため、ログ文字列がstdoutに混入するとJSON-RPCメッセージの解析エラーを引き起こし、サーバーがクラッシュします。ログは必ずsys.stderrに出力してください。
# 絶対にやってはいけない
print("サーバー起動しました")
# loggingモジュールでstream=sys.stderrを指定
logging.basicConfig(stream=sys.stderr, level=logging.INFO)
MCP Inspectorで動作確認
サーバーをClaude Desktopに接続する前に、MCP Inspectorで動作確認を行います。
mcp dev src/server.py
実行するとブラウザ上でWeb UIが起動します。以下の手順で確認します。
-
接続状態の確認 — 画面左上に
calculator-serverと表示され、Connectedになっていれば成功 - ツール一覧の確認 — Toolsタブで定義したツールが表示されるか確認
-
ツールの実行テスト —
addツールを選び、aに3、bに5を入力してRun Tool。計算結果が返れば正常 -
エラーケースの確認 —
divideツールでbに0を入力して実行。エラーメッセージが返されることを確認
Inspectorでテストが成功すれば、サーバーの実装は完了です。あとはClaude Desktopの設定ファイルにサーバーを登録すれば、LLMからツールを呼び出せます。
よくある実装ミス
初めてMCPサーバーを作る際によく遭遇する問題を3つ紹介します。
非同期関数の同期呼び出し: MCP SDKのハンドラはすべてasync defで定義する必要があります。内部で非同期処理を行う場合はawaitを使いましょう。
inputSchemaの形式誤り: トップレベルの"type": "object"を省略すると、LLMが正しく引数を構築できない場合があります。requiredフィールドで必須パラメータを明示することも重要です。
パッケージバージョンの古さ: pip install --upgrade "mcp[cli]"で最新版に保ちましょう。SDKはまだ活発に開発されており、APIの変更が入ることがあります。
まとめ
MCPサーバーの最小構成要素は「サーバーインスタンス・トランスポート・ツール定義・エントリポイント」の4つです。Python SDKのデコレータベースAPIを使えば、数十行で動作するサーバーを作れます。
ツールのinputSchemaに明確なdescriptionを書くこと、ログをstderrに出力すること、MCP Inspectorで事前にテストすること — この3点を守れば、つまづかずに実装できます。
MCPはAIエージェントの機能拡張における標準プロトコルになりつつあります。自作サーバーで独自ツールを提供できるようになれば、AIアプリケーションの可能性が大きく広がります。
本記事は著者 葉山悠希 の書籍『MCP実践入門』の内容をベースに、Qiita単体で完結するよう再構成した技術記事です。