既存のMCPだけでは足りなくなった
Claude CodeやCursorでMCPを使い始めると、最初は公式・コミュニティ提供のサーバーで十分に感じる。GitHubのPRを操作し、Filesystemでファイルを読む。それだけで十分便利だ。
しかし実務で使い込むにつれ、こんな状況が増えてくる。
- 社内APIをAIから直接叩きたい — 外部MCPサーバーには当然ない
- 独自のDBクエリをワンコマンドで実行したい — 汎用DBサーバーでは制御が難しい
- Slackの特定チャンネルを要約させたい — 社内の認証や権限管理が絡む
この壁を乗り越える手段が、MCPサーバーの自作だ。FastMCPを使えばPythonで50行から始められる。本記事では、実際に動くサーバーを作りClaude CodeとCursorに繋ぐまでを解説する。
MCPの仕組みを30秒で理解する
MCP(Model Context Protocol)は、Anthropicが2024年11月に公開したオープンプロトコルだ。現在はOpenAI、Google、Microsoft、JetBrainsなど主要なAI関連企業が採用している。
構造はシンプルだ。
AI(Claude/Cursor) ─── MCP Client ─── MCPサーバー ─── 外部リソース
(stdio/SSE)
MCPサーバーはTools(関数実行)、Resources(データ読み取り)、Prompts(テンプレート)の3種類を提供できる。実務で最もよく使うのはToolsだ。
FastMCPで最小構成を作る
環境セットアップ
# Python 3.10以上が必要
uv init my-mcp-server && cd my-mcp-server
uv add fastmcp httpx asyncpg
Hello World相当の最小実装
# server.py
from fastmcp import FastMCP
mcp = FastMCP("my-server")
@mcp.tool()
def add(a: int, b: int) -> int:
"""2つの整数を足す"""
return a + b
if __name__ == "__main__":
mcp.run(transport="stdio")
@mcp.tool() デコレータをつけた関数が、AIから呼び出せるツールになる。型ヒントとdocstringがそのままツールの仕様として使われるため、命名とdocstringが品質を決める。
実務パターン: 社内APIラッパー
社内マイクロサービスのエンドポイントをMCPツールとして公開する。
import httpx
import os
from fastmcp import FastMCP
mcp = FastMCP("internal-api")
BASE_URL = "https://api.internal.example.com"
API_KEY = os.environ["INTERNAL_API_KEY"]
@mcp.tool()
async def get_user_info(user_id: str) -> dict:
"""社内ユーザー管理APIからユーザー情報を取得する"""
async with httpx.AsyncClient() as client:
resp = await client.get(
f"{BASE_URL}/users/{user_id}",
headers={"X-API-Key": API_KEY}
)
resp.raise_for_status()
return resp.json()
@mcp.tool()
async def create_ticket(
title: str,
description: str,
assignee_id: str,
priority: str = "medium"
) -> dict:
"""社内チケット管理システムにチケットを作成する。
Args:
title: チケットのタイトル
description: 詳細説明
assignee_id: 担当者のユーザーID
priority: 優先度 (low/medium/high)
"""
async with httpx.AsyncClient() as client:
resp = await client.post(
f"{BASE_URL}/tickets",
headers={"X-API-Key": API_KEY},
json={"title": title, "description": description,
"assignee_id": assignee_id, "priority": priority}
)
resp.raise_for_status()
return resp.json()
if __name__ == "__main__":
mcp.run(transport="stdio")
これを繋いでおくと「ユーザーID u123 の情報を調べて、バグ対応チケットを高優先度で作成して」という一言で get_user_info → create_ticket の順に自動実行される。
MCP Inspectorでデバッグする
自作サーバーをClaude/Cursorに繋ぐ前に、MCP Inspectorで動作確認するのが効率的だ。
npx @modelcontextprotocol/inspector python server.py
http://localhost:5173 をブラウザで開くと、ツール一覧・引数定義・実行テストができるUIが立ち上がる。引数のバリデーションエラーや例外もここで確認できる。
Claude Code への接続
~/.claude.json にMCPサーバーの設定を追加する。
{
"mcpServers": {
"internal-api": {
"type": "stdio",
"command": "python",
"args": ["/path/to/server.py"],
"env": {
"INTERNAL_API_KEY": "your-secret-key"
}
}
}
}
設定後にClaude Codeを再起動し、/mcp コマンドで接続確認できる。
> /mcp
● MCP Server: internal-api (connected)
Tools: get_user_info, create_ticket
Cursorへの接続
Cursorでは ~/.cursor/mcp.json(グローバル)または .cursor/mcp.json(プロジェクト単位)で設定する。
{
"mcpServers": {
"internal-api": {
"command": "python",
"args": ["/path/to/server.py"],
"env": {
"INTERNAL_API_KEY": "your-secret-key"
}
}
}
}
Cursorでは Composer(Agent mode)のみMCPツールが利用可能な点に注意。Settings → MCP から接続状態を確認できる。
よくあるハマりどころ3つ
1. 型ヒントは必ず書く
FastMCPは型ヒントからJSONスキーマを自動生成する。型ヒントがないとツールの引数定義が不明確になり、AIが誤った引数を渡すことがある。
# NG: 型ヒントなし
def search(query):
...
# OK: 明示的な型ヒント
def search(query: str, limit: int = 10) -> list[dict]:
...
2. エラーは例外で返す
MCPプロトコルでは、例外を投げるとエラーレスポンスとしてAIに返される。AIは受け取ったエラーメッセージを理解して次の手を考えてくれる。
@mcp.tool()
def get_config(key: str) -> str:
allowed_keys = {"app_version", "env", "region"}
if key not in allowed_keys:
raise ValueError(f"許可されていないキー: {key}。使用可能: {allowed_keys}")
return CONFIG[key]
3. 機密情報はenvで渡す
APIキーをコードに直書きしない。~/.claude.json や ~/.cursor/mcp.json の env フィールドで渡し、コード側では os.environ から読む。これにより設定ファイルをgitignoreしておけばリポジトリに漏れない。
一度作れば複数ツールで使い回せる
MCPの強みは、一度作ったサーバーがClaude Code・Cursor・VS Code(Copilot Chat)・Clineなど複数のAI開発ツールで使い回せる点だ。クライアント側を変えてもサーバーの実装は変えなくてよい。
社内APIの薄いラッパーから始めて、徐々に実務に必要なツールを追加していくのが長続きするコツだ。「どのツールがあれば開発体験が上がるか」を先に考え、実装の複雑さより価値を優先する。毎日使うコンテキストスイッチを1つ削れば、それだけで元が取れる。