速習・MCP
先週くらいから各所のホットエントリーに立ち並ぶ"MCP"。最近のAI関連のインプットを怠ってきた(僕のような)人向けの、MCPの速習記事です。
MCPの概要を押さえて、応用や事例の記事で言っていることをイメージできるようになることがゴールです。
MCPとは
一言でいうと、MCPはLLMが外部システムと連携するための統一的なプロトコルで、お互いがこれをサポートすることで、LLMにとって外部システムがプラグアンドプレイなモジュールになることを目指すものです。
LLMと外部システムの連携の課題とMCPが提示する解決策
LLMの発展を原動力に、ChatGPTやClineなどのAIアシスタントアプリケーションが急速に浸透してきましたが、既存のシステム(SlackやGitHubなど)との接続については、アプリケーションごとに独自の方法を定めていました。そのため、この接続の実装はAIアプリケーション毎に必要となり、データソースとの統合の足かせとなっていました。
MCP(Model Context Protocol)は、AIアプリケーションと多様なデータソースを接続するためのオープンな標準で、Claudeなどを開発するAnthropic社によって2024年11月に公開されました。2025/4/11現時点では、2025-03-26が最新版となっています。
既存システムはMCPサーバーを実装・公開するだけで、MCPに準拠したAIアプリケーションであれば、どれでも組み込むことができるようになります。AIアプリケーション側から見ると、MCPに準拠したクライアント実装にしておけば、MCPサーバーが提供する広範なデータソースを自由に選択・接続できるようになります。AIアプリケーションとデータソースの統合がお互いに進むようになるという意味で、大きなインパクトがあります。(なので、今これだけMCPが騒がれてるんだと理解してます。)
現在では、多くのAIアプリケーション、多くのシステム(データソース)で、MCPをサポートし始めています。Google Drive、Slack、GitHubなどと連携できるのは、ビジネスユースケースで特に可能性を感じますね。また、OpenAI Agent SDKでもMCPがサポートされ、今後さらにOpenAI APIなどにもMCPをサポートしていく[1]とのことなので、デファクトスタンダードとしての地位を着実に築きつつあります。
- クライアントのサポート状況 https://modelcontextprotocol.io/clients
- サーバーの例 https://modelcontextprotocol.io/examples
MCPのコンポーネントと大まかな流れ
最初にコンポーネントと大まかな流れを掴んでおきましょう。Toolについては後述しますが、LLMから外部システムを呼び出すためのインタフェースと思ってください。
- アプリケーションで実行されるMCPクライアントにて、MCPサーバーからアクセス可能なTool一覧を取得
- ユーザーは、アプリケーション(例えばClaude Desktop)にクエリを送る
- アプリケーションは、クエリに加えて、アクセス可能なToolの一覧をLLM(例えばClaude)へ送る
- LLMは、Toolを使うか・どれを使うかを選択して、アプリケーションへ返す
- クライアントは、選択されたToolに対応するサーバーへパラメータを送り、結果を受け取る
- アプリケーションは、結果をLLMへ渡す
- LLMは、自然言語に変換して返す
- アプリケーションがユーザーに表示する
https://modelcontextprotocol.io/introduction#general-architecture 抜粋(2025/4/12時点)
MCPの仕様的な特徴
MCPの仕様的な特徴を見ていきます。MCPは日付でバージョニングされており[2]、投稿時点での最新は2025-03-26です。
JSON-RPC 2.0がベース
MCPはJSON-RPC 2.0[3]をベースにしたプロトコルです。
RPC(Remote Procedure Call)[4]は、他のアドレス空間で手続き(プログラム)を実行するためのルールのことです。RPCの標準としてはJSON-RPCの他に、gRPCやSOAPなどが有名です。
JSON-RPC 2.0は、シンプルなJSON形式のメッセージを取り決めています。以下に例を示します。リクエストでは、$.method
で呼び出す手続き、$.params
で手続きに渡すパラメータを指定し、レスポンスでは、$.result
や$.error
に実行やエラーの結果を詰めています。
// リクエスト
{"jsonrpc": "2.0", "id": 123, "method": "hello", "params": {"name": "ohke"}}
// レスポンス(成功時)
{"jsonrpc": "2.0", "id": 123, "result": {"greeting": "Hello, ohke!"}}
// レスポンス(失敗時)
{"jsonrpc": "2.0", "id": 123, "error": {"code": -1, "message": "Unknown param, `name`."}}
MCPではこのJSON-RPC 2.0に則り、Toolなどのメッセージを構成しています。後ほどMCPサーバーにログを見ますが、以下のようなメッセージがやり取りされます。
// Toolへのリクエスト
{"jsonrpc":"2.0","id":35,"method":"tools/call","params":{"name":"add","arguments":{"a":88,"b":102}}}
// Toolからのレスポンス
{"jsonrpc":"2.0","id":35,"result":{"content":[{"type":"text","text":"190"}],"isError":false}}
通信方法はstdioまたはSSE/Streamable HTTPから選択
JSON-RPC 2.0では、通信方法について具体的な定めがありません。MCPでは、stdioまたはSSE/Streamable HTTPで通信できます[5]。AIアプリケーションの多くはstdioはサポートしています。
- stdioは、ローカルプロセスと効率的に通信したい場合に有効
- SSE/Streamable HTTPは、HTTP互換が必要なケースに用いる
- SSEは2025-03-26では非推奨
サーバーの機能
MCPサーバーで提供すべき機能は3つあります。外部のシステムとの連携では、Toolsが主に用いられます。
-
Resources
- サーバーから公開するデータで、LLMのコンテキストとして利用する
-
Prompts
- 予めサーバーで定義したプロンプトテンプレートで、LLMのプロンプトエンジニアリングを助ける
-
Tools
- LLMが呼び出す関数
クライアントの機能
-
Sampling
- サーバーからクライアントのLLMを実行する
- クライアント側でLLMへの入力や生成物をコントロールできる
-
Roots
- クライアントからサーバーに参照してほしいリソース(ルート)を示唆するURIを与える
MCPサーバーを作ってみよう
MCPの仕様の策定に加えて、各プログラミング言語に向けたSDKの提供など、MCP利用にあたっての周辺整備も進められています。
本投稿ではPythonのSDKを使ってMCPサーバーを実装し、Claude Desktopと接続してみます。
python-sdkでMCPサーバーの実装
MCPのサーバーやクライアントの開発のために、各プログラミング言語でSDKが開発されています。
Python SDKはこちらで、pip install "mcp[cli]"
や、uvを使う場合はuv add "mcp[cli]"
でインストールします。
Quickstartから、MCPサーバーでTools(2つの値を足し算するadd)を実装する例を抜粋します。
FastMCPインスタンスを作り、定義した関数add()にデコレータで@mcp.tool()
とするだけで、add()がToolsに登録されます。
これを~/python-mcp/server.py
として作成します。
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Demo")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
MCP Inspectorで動作確認
Claude Desktopに接続する前に、まずは単体で起動してみましょう。
uvでインストールした場合はuv run mcp dev server.py
とすると、MCP Inspectorというサービスが http://127.0.0.1:6274 で立ち上がります。
ブラウザからアクセスしてConnectすると、作成したMCPサーバーを起動・接続し、作成したものが公開されているか(tools/list
)を確認したり、作成したToolsに実行したり(tools/call
)できます。
Claude Desktopでの設定
動作確認ができたら、Claude Desktopと繋いでLLMから呼び出してもらいましょう。
Claude Desktopをインストール・起動したら、メニューから 設定 > 開発者 > 構成を編集 を選んで、claude_desktop_config.jsonをテキストエディタで開きます。Macの場合は、~/Library/Application Support/Claude/claude_desktop_config.json
にあるかと思います。
先ほど作成したMCPサーバーを$.mcpServers
に、"Demo"という名前で追加します。また、MCPサーバーを実行するコマンドと引数を記述します。
{
"mcpServers": {
"Demo": {
"command": "/Users/ohke/.local/bin/uv",
"args": [
"--directory",
"/Users/ohke/python-mcp",
"run",
"mcp",
"run",
"server.py"
]
}
}
}
編集を保存したら、Claude Desktopを再起動してください。
チャットフォームのハンマーマークを押すと、追加したToolを呼び出せるか確認できます。
Claude DesktopでMCPサーバーを呼び出す
ここでチャットを新規作成して、試しに"88 + 102"でポストしてみます。追加したDemoのTool実行を許可するか聞かれますので、これを許可するとaddが実行され、さらに結果をLLMで自然言語にして返答します。LLMと僕のPythonコードがめでたく融合しました。
一方で、同じ足し算でも値を変えるとaddが実行されないことがあります。試しに先ほどよりも簡単な値にしてみると、addは実行されずに、結果が返されています。LLMがToolsを呼び出すかどうかをジャッジしていることがわかりますね。
MCPの通信ログを見てみる
ここで通信ログを見てみましょう。MacのClaude Desktopでは、~/Library/Logs/Claude/mcp.log
に出力されているかと思います。
以下の順番で実行されていることがわかります。
- Claude Desktopを立ち上げると、MCPサーバーが起動
- "method":"initialize"で、双方の情報を交換(id=0)
- "method":"tools/list"で、選択可能なToolとそれぞれのパラメータスキーマを取得(id=1)
- チャットで "88 + 102" を実行
-
{"method":"tools/call","params":{"name":"add","arguments":{"a":88,"b":102}}
でリクエスト、{"result":{"content":[{"type":"text","text":"190"}],"isError":false}
でレスポンスされており、MCPサーバーで実行されいていることを確認できます(id=35) - チャットで "190" が返ってくる
2025-04-10T09:47:02.184Z [info] [Demo] Initializing server...
2025-04-10T09:47:02.263Z [info] [Demo] Server started and connected successfully
2025-04-10T09:47:02.283Z [info] [Demo] Message from client: {"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude-ai","version":"0.1.0"}},"jsonrpc":"2.0","id":0}
2025-04-10T09:47:03.001Z [info] [Demo] Message from server: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2024-11-05","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"Demo","version":"1.6.0"}}}
2025-04-10T09:47:03.002Z [info] [Demo] Message from client: {"method":"notifications/initialized","jsonrpc":"2.0"}
2025-04-10T09:47:03.004Z [info] [Demo] Message from client: {"method":"tools/list","params":{},"jsonrpc":"2.0","id":1}
2025-04-10T09:47:03.007Z [info] [Demo] Message from server: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"add","description":"Add two numbers","inputSchema":{"properties":{"a":{"title":"A","type":"integer"},"b":{"title":"B","type":"integer"}},"required":["a","b"],"title":"addArguments","type":"object"}}]}}
2025-04-10T09:47:04.438Z [info] [Demo] Message from client: {"method":"resources/list","params":{},"jsonrpc":"2.0","id":2}
2025-04-10T09:47:04.441Z [info] [Demo] Message from client: {"method":"tools/list","params":{},"jsonrpc":"2.0","id":3}
2025-04-10T09:47:04.444Z [info] [Demo] Message from server: {"jsonrpc":"2.0","id":2,"result":{"resources":[]}}
2025-04-10T09:47:04.444Z [info] [Demo] Message from server: {"jsonrpc":"2.0","id":3,"result":{"tools":[{"name":"add","description":"Add two numbers","inputSchema":{"properties":{"a":{"title":"A","type":"integer"},"b":{"title":"B","type":"integer"}},"required":["a","b"],"title":"addArguments","type":"object"}}]}}
2025-04-10T09:47:04.577Z [info] [Demo] Message from client: {"method":"prompts/list","params":{},"jsonrpc":"2.0","id":4}
2025-04-10T09:47:04.580Z [info] [Demo] Message from server: {"jsonrpc":"2.0","id":4,"result":{"prompts":[]}}
...
2025-04-10T09:48:21.823Z [info] [Demo] Message from client: {"method":"tools/call","params":{"name":"add","arguments":{"a":88,"b":102}},"jsonrpc":"2.0","id":35}
2025-04-10T09:48:21.828Z [info] [Demo] Message from server: {"jsonrpc":"2.0","id":35,"result":{"content":[{"type":"text","text":"190"}],"isError":false}}
...
まとめ
"MCPなんもわからん"人向けに、MCPの概要についてまとめました。
参考
- https://speakerdeck.com/shuntaka/introduction-to-mcp
- https://zenn.dev/cloud_ace/articles/model-context-protocol
Discussion