1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MCPサーバーを自作してClaude CodeとCursorから使う実践ガイド

1
Posted at

既存の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_infocreate_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.jsonenv フィールドで渡し、コード側では os.environ から読む。これにより設定ファイルをgitignoreしておけばリポジトリに漏れない。

一度作れば複数ツールで使い回せる

MCPの強みは、一度作ったサーバーがClaude Code・Cursor・VS Code(Copilot Chat)・Clineなど複数のAI開発ツールで使い回せる点だ。クライアント側を変えてもサーバーの実装は変えなくてよい。

社内APIの薄いラッパーから始めて、徐々に実務に必要なツールを追加していくのが長続きするコツだ。「どのツールがあれば開発体験が上がるか」を先に考え、実装の複雑さより価値を優先する。毎日使うコンテキストスイッチを1つ削れば、それだけで元が取れる。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?