レッスン 1 / 7
MCPとは?AIと道具をつなぐ共通の規格
学ぶコト
- MCP が解決する「AIアプリ × 道具」の組み合わせの問題
- ホスト・クライアント・サーバーと、ツール・リソース・プロンプトの役割
- stdio と Streamable HTTP の違い
- MCP と Function Calling の関係
このコースでは、AIに社内のデータや道具を使わせるための共通の規格「MCP(Model Context Protocol)」を学びます。できあがった MCP サーバーをつないで使うところから始めて、Python で自分のサーバーを作り、最後は売上データを集計・分析するサーバーを作って、Claude などのAIアプリから使えるようにします。
最初のレッスンでは、手を動かす前に「MCP とは何で、何がうれしいのか」をおさえます。後半では、MCP のやりとりを Python でまねして、中で何が起きているのかを見てみます。
このページの Python のコードは「▶ 実行」を押すと、ブラウザの中で動かせます。上から順に実行してください。
MCPとは
MCP は、AIアプリ(Claude・ChatGPT・Cursor など)と、外の道具やデータ(ファイル・データベース・社内のシステム・Webのサービスなど)をつなぐための、オープンな共通の規格です。2024年11月に Anthropic が公開し、2025年12月からは Linux Foundation の中の Agentic AI Foundation という中立の団体で育てられています。
公式のサイトでは、MCP を「AIアプリにとっての USB-C のようなもの」とたとえています。USB-C の口があれば、どのメーカーのパソコンにも、どのメーカーの周辺機器をつなげます。同じように、MCP に合わせて作った道具(MCP サーバー)は、MCP に対応したどのAIアプリからも使えます。
2026年10月時点で、Claude(Claude Desktop・Claude Code)・ChatGPT・Cursor・VS Code(GitHub Copilot)・Gemini CLI など、主なAIアプリの多くが MCP に対応しています。
どんな困りごとを解決するのか
「PythonでAIチャットボットを作る」のコースの「関数を呼ばせる(Function Calling)」では、注文を調べる関数をチャットボットに持たせました。関数の説明(ツールの定義)を書き、モデルが頼んできたら関数を動かして結果を返す、という仕組みでした。
この仕組みは、そのチャットボットの中だけで完結しています。同じ「注文を調べる」機能を、社員がふだん使っている Claude や Cursor からも使いたくなったら、どうなるでしょうか。アプリごとに、ツールの定義の書き方も、結果の返し方も違います。アプリの数だけ作り直しになります。
社内で使いたいAIアプリが4つ、つなぎたい道具が5つあるとしましょう。
| つなぐ作業の数 | |
|---|---|
| アプリごとに道具をつなぐ | 4 × 5 = 20 |
| MCP でつなぐ | 4 + 5 = 9(アプリは MCP に対応するだけ、道具は MCP サーバーを1つ作るだけ) |
アプリが増えても道具が増えても、足し算で済むようになります。これが MCP のいちばんの価値です。
3つの登場人物:ホスト・クライアント・サーバー
MCP には、3つの役割が出てきます。
| 役割 | 何をするか | 例 |
|---|---|---|
| ホスト | 人が使うAIアプリ。モデルと会話し、どの MCP サーバーにつなぐかを管理する | Claude Desktop、Claude Code、ChatGPT、Cursor、自分で作ったアプリ |
| クライアント | ホストの中の部品。MCP サーバー1つにつき1つ作られ、そのサーバーとやりとりする | (ホストの中に入っていて、ふだんは見えない) |
| サーバー | 道具やデータを MCP の形で差し出すプログラム | ファイルを読むサーバー、GitHub のサーバー、このコースで作る売上分析のサーバー |
サーバーには、自分のパソコンの中で動くローカルのサーバーと、インターネットの向こうで動くリモートのサーバーがあります。ローカルのサーバーは、ホストが自分でプログラムを起動して、その1つのホストだけが使います。リモートのサーバーは、サービスの会社などが動かしていて、たくさんの人のホストからつながります。
社員が Claude Desktop で「先月の売上を地域ごとにまとめて」と頼んだとき、中では次のことが起きています。
- Claude Desktop(ホスト)は、起動したときに、設定された MCP サーバーから「使えるツールの一覧」を受け取っておく
- ホストは、社員の質問と一緒に、ツールの一覧をモデルに渡す
- モデルが「売上分析サーバーの集計のツールを、この引数で呼びたい」と答える
- ホストは社員に確かめたうえで、そのサーバーのクライアントを通して、ツールを呼ぶ
- サーバーが集計し、結果を返す。ホストは結果をモデルに渡し、モデルが答えの文章を書く
サーバーが出せる3つのもの
MCP サーバーは、次の3種類のものを差し出せます。だれが「使う」と決めるかが違います。
| 種類 | 中身 | 使うと決めるのは | 例 |
|---|---|---|---|
| ツール(tools) | AIに呼ばせる関数 | モデル | 売上を集計する、ファイルを読む、チケットを作る |
| リソース(resources) | AIに読ませるデータ | アプリ・人 | 会社の休日の一覧、データベースの表の形、設定ファイル |
| プロンプト(prompts) | 決まった頼み方のひな形 | 人 | 「週報の下書き」「納期の返信メール」のひな形 |
いちばんよく使うのはツールです。このコースでも、ほとんどの時間をツールに使います。
逆に、サーバーからホストに頼めることもあります。代表的なのが elicitation で、サーバーが「この操作の前に、利用者にこれを聞いてほしい」と頼み、ホストが利用者に入力してもらう仕組みです。
やりとりを Python でまねしてみる
ホストとサーバーのやりとりは、JSON-RPC 2.0 という決まった形の JSON で行います。「この method を、この params で実行して」というリクエストを送ると、同じ id を付けたレスポンスが返ってきます。
本物のサーバーは次のレッスンから使います。ここでは、営業日を数えるツールを1つだけ持つサーバーを、Python の関数でまねしてみます。handle がサーバーで、リクエストの辞書を受け取り、レスポンスの辞書を返します。
import json
from datetime import date, timedelta
# サーバーが持っているツールの説明(名前・何をするか・引数の形)
TOOLS = [
{
"name": "count_business_days",
"description": "start から end までの営業日(土日を除いた日)の数を返す",
"inputSchema": {
"type": "object",
"properties": {
"start": {"type": "string", "description": "数え始める日(YYYY-MM-DD)"},
"end": {"type": "string", "description": "数え終わる日(YYYY-MM-DD)"},
},
"required": ["start", "end"],
},
}
]
def count_business_days(start, end):
s, e = date.fromisoformat(start), date.fromisoformat(end)
return sum((s + timedelta(days=i)).weekday() < 5 for i in range((e - s).days + 1))
def handle(request):
"""MCP サーバーのまね。JSON-RPC のリクエスト(辞書)を受け取り、レスポンスを返す"""
method, params = request["method"], request.get("params", {})
if method == "tools/list":
result = {"tools": TOOLS}
elif method == "tools/call" and params.get("name") == "count_business_days":
try:
days = count_business_days(**params["arguments"])
result = {"content": [{"type": "text", "text": str(days)}], "isError": False}
except (TypeError, ValueError) as e:
result = {"content": [{"type": "text", "text": f"引数がおかしいです:{e}"}], "isError": True}
else:
return {"jsonrpc": "2.0", "id": request["id"], "error": {"code": -32601, "message": f"{method} は使えません"}}
return {"jsonrpc": "2.0", "id": request["id"], "result": result}
request = {"jsonrpc": "2.0", "id": 1, "method": "tools/list"}
print(json.dumps(handle(request), ensure_ascii=False, indent=2))
tools/list は「使えるツールの一覧をください」というリクエストです。返ってきた inputSchema は、引数の形を表す JSON Schema で、Function Calling のツールの定義で書いた parameters と同じ書き方です。
次は、ツールを呼ぶ tools/call です。
request = {
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {"name": "count_business_days", "arguments": {"start": "2026-11-02", "end": "2026-11-13"}},
}
print(json.dumps(handle(request), ensure_ascii=False, indent=2))
結果は content の中に、文字("type": "text")として入っています。画像などを返すこともできます。日付を "11月2日" のように渡すと、isError が true の結果が返ります。この「うまくいかなかったことを、エラーで止めずに結果として返す」形は、モデルが引数を直してやり直せるようにするためのもので、レッスン4以降でくわしく扱います。
Function Calling との関係
「MCP があれば Function Calling はいらないの?」と思うかもしれません。実はその逆で、MCP は Function Calling の上に乗っています。モデルが知っているのは、これまでどおりの「関数を呼ぶ」仕組みだけです。MCP のツールを関数の形に直してモデルに渡し、モデルの頼みを tools/call に直してサーバーに届けるのは、ホストの仕事です。
ホストがしていることを、Python でまねしてみましょう。まず、tools/list の結果を、OpenAI の Responses API の関数の定義に直します。
def to_function_tools(tools):
"""tools/list の結果を、Function Calling のツールの定義(Responses API の形)に直す"""
return [
{"type": "function", "name": t["name"], "description": t["description"], "parameters": t["inputSchema"]}
for t in tools
]
listed = handle({"jsonrpc": "2.0", "id": 3, "method": "tools/list"})["result"]["tools"]
function_tools = to_function_tools(listed)
print(json.dumps(function_tools, ensure_ascii=False, indent=2))
名前・説明・引数の形をそのまま移しただけです。この定義を tools に入れてモデルを呼ぶと、モデルは必要に応じて「この関数を呼んでほしい」と答えます。その答えを受け取ったあとの流れが、次のコードです。
# モデルから「この関数を呼んでほしい」という頼みが返ってきたとする(本当は API の答えに入っている)
call = {
"type": "function_call",
"call_id": "call_abc",
"name": "count_business_days",
"arguments": '{"start": "2026-11-02", "end": "2026-11-13"}',
}
# ホストは、頼みを tools/call のリクエストに直してサーバーに送る
response = handle({
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {"name": call["name"], "arguments": json.loads(call["arguments"])},
})
# サーバーの結果を、モデルに返す形(function_call_output)にする
text = "".join(c["text"] for c in response["result"]["content"] if c["type"] == "text")
{"type": "function_call_output", "call_id": call["call_id"], "output": text}
Function Calling のレッスンでは、ツールの定義も、関数を動かすコードも、チャットボットの中に書きました。MCP では、その2つがサーバーに移り、ホストは「一覧をもらって渡す」「頼みを届けて結果を戻す」だけになります。だから、同じサーバーを、どのホストからでも使えるのです。
このやりとりのループ(モデルが頼む → 動かす → 結果を返す → また考える)は、「AIエージェント入門」の「エージェントのループを自分で書く」で書いたものと同じです。MCP は、エージェントが使う道具を、ほかのアプリと共有できる形にしたもの、と考えると分かりやすいでしょう。
通り道:stdio と Streamable HTTP
ホストとサーバーのあいだで JSON を運ぶ方法(トランスポート)は、2種類あります。
| stdio | Streamable HTTP | |
|---|---|---|
| どこで動くか | 自分のパソコン(ローカル) | サーバーのコンピューター(リモート) |
| つなぎ方 | ホストがサーバーのプログラムを起動し、標準入力・標準出力で JSON をやりとりする | ホストが URL(例:https://example.com/mcp)に HTTP で送る |
| 使う人 | 起動したホスト1つだけ | 多くの人のホスト |
| ログインなど | 要らないことが多い。APIキーなどは環境変数で渡す | OAuth などでログインすることが多い |
| 向いている場面 | 手元のファイルやツールを使う、自作のサーバーを試す | 会社やサービスが提供するサーバー、チームで共有するサーバー |
このコースで作るサーバーは、まず stdio で動かします。設定が簡単で、外から勝手につながれる心配も少ないからです。チームで共有する方法と、そのときの注意は最後のレッスンで扱います。
ウマたんMCP の仕組みは「ホストがツールの一覧をもらって、モデルの頼みを届ける」だけです。この絵が頭に入っていれば、設定でつまずいたときも、どこで止まっているのかを考えやすくなりますよ。
演習:2つのサーバーのツールをまとめる
ホストは、たくさんのサーバーに同時につなぐことがあります。すると、別々のサーバーに同じ名前のツールがあって、ぶつかることがあります。そこでホストは、ツールの名前の前にサーバーの名前を付けて区別します(例えば Claude Code は、MCP のツールを mcp__サーバー名__ツール名 という名前でモデルに見せています)。
予定を管理する calendar のサーバーと、注文を管理する sales のサーバーに、どちらも search というツールがあるとします。次のコードを実行してから、演習に進んでください。
servers = {
"calendar": [{"name": "search", "description": "予定を探す",
"inputSchema": {"type": "object", "properties": {"q": {"type": "string"}}}}],
"sales": [{"name": "search", "description": "注文を探す",
"inputSchema": {"type": "object", "properties": {"q": {"type": "string"}}}}],
}
上の servers は、サーバーの名前ごとに tools/list の結果を入れた辞書です。次の2つの関数を書いてください。
merge_tools(servers):すべてのサーバーのツールを、Function Calling の定義(type・name・description・parameters)のリストにする。nameは「サーバー名__ツール名」にするroute(function_name):「サーバー名__ツール名」を(サーバー名, ツール名)に分ける。ツール名に__が入っていてもよいように、最初の__で分ける
def merge_tools(servers):
# ここに書く
return []
def route(function_name):
# ここに書く("calendar__search" -> ("calendar", "search"))
return None, None
print([t["name"] for t in merge_tools(servers)])
print(route("sales__search"))解答を見る
def merge_tools(servers):
result = []
for server, tools in servers.items():
for t in tools:
result.append({
"type": "function",
"name": f"{server}__{t['name']}",
"description": t["description"],
"parameters": t["inputSchema"],
})
return result
def route(function_name):
server, tool = function_name.split("__", 1)
return server, tool
print([t["name"] for t in merge_tools(servers)])
print(route("sales__search"))split("__", 1) の 1 は「最初の1か所だけで分ける」という意味です。モデルが sales__search を呼びたいと言ってきたら、ホストは route で sales のサーバーを選び、search という名前で tools/call を送ります。サーバーの名前に __ を使わないように決めておけば、この方法で迷わずに届け先を決められます。
このコースの進め方
| レッスン | 内容 |
|---|---|
| 1. MCPとは? | 解決する困りごと、登場人物、Function Calling との関係(このレッスン) |
| 2. できあがったMCPサーバーを使う | Claude Desktop・Claude Code・ChatGPT・Cursor などへのつなぎ方、context7、信頼できるサーバーの選び方 |
| 3. PythonでMCPサーバーを作る | uv と公式の Python SDK で小さなサーバーを作り、確かめて、つなぐ |
| 4. データ分析のツールを作る① | 読み込み・統計量・グループ集計・期間ごとの集計・上位N件(ブラウザで動かせます) |
| 5. データ分析のツールを作る② | 相関・外れ値・結合・クロス集計と、AIが読みやすい結果の返し方(ブラウザで動かせます) |
| 6. 分析サーバーを仕上げて使う | 関数をまとめてサーバーにし、Claude や自分のプログラムから分析を頼む |
| 7. MCPを安全に使う | 権限、秘密の情報、プロンプトインジェクション、ツールの承認、リモートのサーバーとログイン |
レッスン3とレッスン6は、自分のパソコンに Python の環境を作って進めます。準備のしかたは、レッスン3で説明します。
まとめ
- MCP は、AIアプリと外の道具・データをつなぐための共通の規格。道具を MCP サーバーとして1つ作れば、MCP に対応したどのAIアプリからも使える
- 登場人物は、ホスト(AIアプリ)・クライアント(ホストの中でサーバーとやりとりする部品)・サーバー(道具を差し出すプログラム)
- サーバーは、ツール(モデルが呼ぶ)・リソース(アプリや人が読ませる)・プロンプト(人が選ぶひな形)を差し出せる
- やりとりは JSON-RPC。
tools/listで一覧をもらい、tools/callで呼ぶ。通り道は stdio(ローカル)と Streamable HTTP(リモート) - MCP は Function Calling の代わりではなく、その上に乗る仕組み。ツールの一覧を関数の定義に直し、頼みをサーバーに届けるのはホストの仕事
次のレッスンでは、できあがった MCP サーバーを Claude Desktop や Claude Code につないで、実際に使ってみます。
分からないところがあればこのレッスンについて質問するこのコースの Q&A を見る
