※この記事には広告・アフィリエイトリンクを含む場合があります。
「MCPサーバー、結局どう作るの?」に答える回
最近あちこちで「MCP」「MCPサーバー」って言葉を見かけるけど、いざ自分で作るとなると身構えちゃうよね。今回はその一番小さいやつを実際に動かしてみた記録。「あ、MCPサーバーってこれだけで作れるんだ」を体験してもらうのがゴールだ。

なお、この記事は軽量版。Claude Desktopへの登録まで含めた完全な手順は別途 note の詳細版でやる。ここでは「作る流れ」と「全体像」を掴むのが目的だ。
そもそもMCPって何?
MCP(Model Context Protocol)は、AIアプリを外部のデータやツールにつなぐためのオープンな標準規格。公式サイトでは「AIアプリ版のUSB-Cポート」と表現されてる。USB-Cが機器同士をつなぐ共通の差込口なら、MCPはAIと外部システムをつなぐ共通の差込口ってわけ(modelcontextprotocol.io)。
登場人物は3つ。
- ホスト: Claude Desktop や Claude Code みたいなAIアプリ本体
- クライアント: ホストの中で、サーバー1つにつき1つ作られる接続係
- サーバー: 「こういう道具あるよ」とAIに機能を提供するプログラム ← 今回作るのはコレ

そしてサーバーがAIに渡せる機能は、公式に3種類ある。Tools(AIが呼び出せる関数)/Resources(読み込めるデータ)/Prompts(定型テンプレ)だ。今回はこの中で一番わかりやすい「Tools」だけを使う(公式アーキテクチャ解説)。

今回作るMCPサーバー
題材は「自分のブログ記事を検索できるMCPサーバー」。ツールは2つだけにした。
search_articles(keyword): キーワードで記事を探すget_article(title): タイトル指定で要約とURLを取る
これをAIに登録すると、AIが自分のブログを検索して答えられるようになる、という寸法。使ったのは公式のPython SDKに入ってる FastMCP。環境は mcp SDK 1.28.1 / Python 3.10 だ。
サーバー本体はこれだけ
いきなり核心。実際に動かしたコードから、大事なところだけ抜き出すとこうなる。
from mcp.server.fastmcp import FastMCP
# サーバーに名前を付けて作る。この名前がクライアント側の表示名になる。
mcp = FastMCP("blog-tools")
# ARTICLES は記事のサンプルデータ(辞書のリスト)。ここでは省略。
@mcp.tool()
def search_articles(keyword: str) -> list[dict]:
"""キーワードでブログ記事を検索する。
タイトル・タグ・要約のいずれかにキーワードを含む記事を返す。
Args:
keyword: 検索したいキーワード(例: "RAG"、"エージェント")
"""
kw = keyword.lower()
hits = []
for a in ARTICLES:
haystack = (a["title"] + a["summary"] + " ".join(a["tags"])).lower()
if kw in haystack:
hits.append({"title": a["title"], "url": a["url"]})
return hits
if __name__ == "__main__":
mcp.run()

ここがポイント。keyword: str という型ヒントが、そのまま「この道具は文字列を1個受け取る」という仕様(スキーマ)になる。docstringはそのまま「この道具が何をするか」の説明になる。公式SDKの言葉を借りると『JSONスキーマを書く必要はない。型ヒント付きの関数とdocstring、それだけ』だ(MCP Python SDK)。手書きのJSON定義や、リクエストの解析コードは一切いらない。
最後の mcp.run() は、transportを省略すると stdio(標準入出力) で待ち受ける。stdioはローカルのプロセス同士を標準入出力でつなぐ通信方式で、Claude Desktop や Claude Code はこの stdio 経由でサーバーを起動して繋ぐ(公式アーキテクチャ解説)。ローカルで動かす分にはこれで十分だ。
動かして確かめる
サーバーだけあっても喋る相手がいない。そこで Claude Desktop がやってることを手動で真似する小さなクライアントを書いて、繋いでみた。中でやってるのはこの4ステップだけ。
- サーバーを stdio で起動して接続
initialize(あいさつ・機能の交換)list_tools(どんな道具ある?)call_tool(実際に道具を呼ぶ)

実行した結果がこれ(実際のログ)。
[接続成功] サーバー名: blog-tools v1.28.1
[ツール一覧] ['search_articles', 'get_article']
--- search_articles(keyword='RAG') ---
text: {
"title": "RAGって何?",
"url": "https://example.com/rag"
}
--- get_article(title='ハルシネーションとは?') ---
text: {
"title": "ハルシネーションとは?",
"url": "https://example.com/hallucination",
"summary": "AIがもっともらしい嘘を自信満々に答えてしまう現象と、その対策。"
}
ちゃんと動いた。search_articles に “RAG” を渡したら RAG の記事が返り、get_article にタイトルを渡したら要約まで返ってきてる。

中では何が起きてるのか
流れをもう一段ほどこう。クライアントとサーバーは JSON-RPC 2.0 というルールでメッセージをやり取りしてる。最初の initialize でお互いの機能をすり合わせ、tools/list で道具の一覧を取り、tools/call で実行する(公式アーキテクチャ解説)。
面白いのはツールの戻り値だ。さっきのログで text: の中身がJSONになってたよね。MCPではツールの結果は content という配列で返り、その中の各要素に type(今回は "text")と本文が入る。だからクライアント側は result.content[0].text で中身を読んでる。AIが実際に読むのも、このテキスト部分だ(公式アーキテクチャ解説)。

ちなみに今回サンプルに入れてた「RAGって何?」は、実際にこのブログでも解説してる。気になる人はRAGって何?もどうぞ。
まとめ
- MCPサーバーは「AIに道具を渡す」ための小さなプログラム。最小なら数十行で動く
- FastMCP を使えば、普通のPython関数に
@mcp.tool()を付けるだけ。型ヒントとdocstringがそのまま仕様になる mcp.run()は stdio で待ち受け、Claude Desktop / Claude Code から呼び出せる- 実行すると
initialize → tools/list → tools/callの流れで、AIが道具を使える

完全な手順やClaude Desktopへの登録方法は、note の詳細版で解説予定。まずは「最小のMCPサーバー、自分で作れた」って感覚を持ち帰ってもらえたら十分だ。
参考文献
関連記事

プログラミングを基礎から学びたい方へ
手を動かして作る楽しさが分かってきたら、次はプログラミングの土台を固めるのが近道です。未経験からでも実践形式で学べる講座を紹介します。

コメント