Skip to content
Published on

MCP(Model Context Protocol)完全ガイド:AIと外部世界を繋ぐ新しい標準

Authors

AIビルダーなら誰もが直面する瞬間があります:「このエージェントがデータベースにアクセスできたらいいのに。」そこでLangChainツールを書き、OpenAI function calling向けにラップし、Claudeにはまた別の方法で実装する... モデルごとに統合コードを書き直す悪循環に陥ります。

MCP(Model Context Protocol)は、2024年末にAnthropicが発表したこの問題への解答です。

MCPとは何か?

MCPはAIモデルと外部ツール・データソースを接続するための標準化されたプロトコルです。USB-Cのようなもの:どのデバイスを繋いでも、どのポートでも、ただ動く。MCPはAIに対して同じことをします。

主な特性:

  • オープン標準:Anthropicが作成したが特定の企業に縛られない
  • 双方向:クライアント(AI)もサーバー(ツール)もメッセージを送れる
  • JSON-RPCベース:シンプルで実証済みのプロトコル

MCP以前の世界(問題点)

M×N統合問題は現実のものでした:

モデル数 × ツール数 = 統合コード数

Claude + (Slack + GitHub + DB + Notion) = 4つのClaude統合
GPT-4 + (Slack + GitHub + DB + Notion) = 4つのGPT-4統合
Gemini + (Slack + GitHub + DB + Notion) = 4つのGemini統合

合計:12個の別々の統合コードベース

MCPの後:

ツール数 = MCPサーバー数(モデル横断で再利用可能)

1つのSlack MCPサーバー → Claude、GPT-4、Gemini、将来のモデル全てで使用可能
1つのGitHub MCPサーバー → 同様

一度書いたサーバーは、MCPに対応するどのAIクライアントでも自動的に動作します。

MCPアーキテクチャ

┌─────────────────────┐         ┌──────────────────────┐
MCP Client      │◄───────►│     MCP Server│  (Claude Desktop、JSON   │  (あなたのツール、  │
Cursor、自作アプリ)│  RPCDBAPI、ファイル)│
└─────────────────────┘         └──────────────────────┘

MCP Serverが公開する3つのプリミティブ:

├── Resources(読み取り専用データ)
│   └── ファイル、DBクエリ結果、APIレスポンス
├── Tools(実行可能なアクション)
│   └── ファイル書き込み、API呼び出し、DB変更
└── Prompts(再利用可能なプロンプトテンプレート)
    └── 「このコードをレビューして」などの構造化テンプレート

コンポーネント:

  • MCPクライアント:Claude Desktop、Cursor、自作のAIアプリ
  • MCPサーバー:ツールサーバー(データベース、API、ファイルシステムなど)
  • トランスポート:ローカルはstdio、リモートはHTTP/SSE

PythonでMCPサーバーを作る

実際に動作するサーバーを構築しましょう。顧客データベースを公開するサーバーです。

from mcp.server import Server
from mcp.server.models import InitializationOptions
import mcp.types as types
import json
import asyncio

app = Server("my-database-server")

# Resourcesの登録:読み取り専用データを公開
@app.list_resources()
async def list_resources() -> list[types.Resource]:
    return [
        types.Resource(
            uri="db://customers",
            name="Customer Database",
            description="顧客レコードにアクセス。最大100件を返します。",
            mimeType="application/json"
        ),
        types.Resource(
            uri="db://orders",
            name="Order History",
            description="全顧客の注文履歴にアクセス",
            mimeType="application/json"
        )
    ]

@app.read_resource()
async def read_resource(uri: str) -> str:
    if uri == "db://customers":
        customers = await db.fetch_all(
            "SELECT id, name, email, created_at FROM customers LIMIT 100"
        )
        return json.dumps([dict(c) for c in customers])

    elif uri == "db://orders":
        orders = await db.fetch_all(
            "SELECT id, customer_id, total, status, created_at FROM orders LIMIT 100"
        )
        return json.dumps([dict(o) for o in orders])

    raise ValueError(f"Unknown resource: {uri}")

# Toolsの登録:実行可能なアクションを公開
@app.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="create_customer",
            description=(
                "データベースに新しい顧客レコードを作成します。"
                "ユーザーが新しい顧客を追加したい場合に使用。"
                "成功時に新しい顧客のIDを返します。"
            ),
            inputSchema={
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "顧客のフルネーム"
                    },
                    "email": {
                        "type": "string",
                        "description": "顧客のメールアドレス"
                    },
                    "phone": {
                        "type": "string",
                        "description": "顧客の電話番号(オプション)"
                    }
                },
                "required": ["name", "email"]
            }
        ),
        types.Tool(
            name="search_customers",
            description=(
                "名前またはメールで顧客を検索します。"
                "新規作成前に既存顧客を確認するために使用してください。"
            ),
            inputSchema={
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "検索クエリ — 名前とメールフィールドに対してマッチング"
                    }
                },
                "required": ["query"]
            }
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "create_customer":
        try:
            result = await db.execute(
                "INSERT INTO customers (name, email, phone) VALUES (:name, :email, :phone)",
                {
                    "name": arguments["name"],
                    "email": arguments["email"],
                    "phone": arguments.get("phone", None)
                }
            )
            return [types.TextContent(
                type="text",
                text=f"顧客を正常に作成しました。ID: {result.lastrowid}"
            )]
        except Exception as e:
            return [types.TextContent(
                type="text",
                text=f"顧客作成エラー: {str(e)}"
            )]

    elif name == "search_customers":
        query = f"%{arguments['query']}%"
        customers = await db.fetch_all(
            "SELECT id, name, email FROM customers WHERE name LIKE :q OR email LIKE :q",
            {"q": query}
        )
        if not customers:
            return [types.TextContent(type="text", text="該当する顧客が見つかりませんでした")]

        result = "\n".join([
            f"ID: {c.id}, Name: {c.name}, Email: {c.email}"
            for c in customers
        ])
        return [types.TextContent(type="text", text=result)]

    raise ValueError(f"Unknown tool: {name}")

# サーバーの実行
async def main():
    from mcp.server.stdio import stdio_server
    async with stdio_server() as (read_stream, write_stream):
        await app.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="my-database-server",
                server_version="0.1.0"
            )
        )

if __name__ == "__main__":
    asyncio.run(main())

Claude DesktopでMCPサーバーを使う

サーバーを作ったら、Claude Desktopの設定ファイルに登録します:

// macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
// Windows: %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "my-database": {
      "command": "python",
      "args": ["/path/to/your/mcp_server.py"],
      "env": {
        "DATABASE_URL": "postgresql://user:pass@localhost/mydb"
      }
    },
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/username/projects"]
    }
  }
}

Claude Desktopを再起動すると、Claudeがデータベースに直接アクセスできるようになります。「顧客一覧を見せて」「山田太郎という顧客を追加して」といった自然言語コマンドが機能します。

既存のアプローチとの比較

OpenAI Function Calling

# OpenAI APIのフォーマットに縛られる
tools = [{"type": "function", "function": {"name": "search", ...}}]
response = openai.chat.completions.create(tools=tools, ...)

OpenAIエコシステムでは完璧に動作します。Claudeを使いたい?最初から再実装が必要です。

LangChain Tools

# LangChainフレームワークに縛られる
from langchain.tools import tool

@tool
def search(query: str) -> str:
    """Search the web"""
    return web_search(query)

LangChainをフル活用しているなら良いでしょう。LangChainを使わない環境には移植できません。

MCP

# MCPに対応するどのクライアントでも動作
@app.list_tools()
async def list_tools():
    return [types.Tool(name="search", ...)]

一度ビルドすれば、Claude Desktop、Cursor、自作アプリ — MCPを話すクライアントはどこでも動作します。

特性OpenAI Function CallingLangChain ToolsMCP
モデル独立性なし(OpenAI専用)部分的あり
オープン標準なしなしあり
再利用性
セットアップ複雑度
エコシステムOpenAIエコシステムLangChainエコシステム急速成長中

2025〜2026年のMCPエコシステム

Anthropicが提供する公式サーバー:

  • @modelcontextprotocol/server-filesystem — ローカルファイルアクセス
  • @modelcontextprotocol/server-github — GitHubリポジトリ
  • @modelcontextprotocol/server-postgres — PostgreSQLデータベース
  • @modelcontextprotocol/server-brave-search — Brave Search API
  • @modelcontextprotocol/server-slack — Slackメッセージング

コミュニティサーバーはNotion、Jira、Linear、Docker、Kubernetes、AWS、GCPなどをカバー。

インストール:

# Node.jsサーバー
npx -y @modelcontextprotocol/server-github

# Pythonサーバー
pip install mcp

良いMCPサーバーを作るための実践的なヒント

1. ツールの説明を具体的に書く

AIはdescriptionを使っていつどうやってツールを呼ぶかを決めます。曖昧な説明は誤った呼び出しにつながります。

# 悪い例
types.Tool(name="query_db", description="データベースをクエリ")

# 良い例
types.Tool(
    name="search_customers",
    description=(
        "名前またはメールで顧客レコードを検索します。"
        "既存の顧客を見つける必要があるときに使用してください。"
        "顧客ID、名前、メール、アカウント作成日を返します。"
        "注文関連のクエリにはget_ordersを使用してください。"
    )
)

2. エラーをツール結果として返す(例外ではなく)

@app.call_tool()
async def call_tool(name: str, arguments: dict):
    try:
        result = await execute_operation(arguments)
        return [types.TextContent(type="text", text=json.dumps(result))]
    except Exception as e:
        # 通常のレスポンスとしてエラーを返す — AIが適応できる
        return [types.TextContent(
            type="text",
            text=f"エラー: {str(e)}。入力を確認して再試行してください。"
        )]

3. 読み取りと書き込みをアーキテクチャレベルで分離する

Resources = 読み取り専用(SELECT、ファイル読み取り、GETリクエスト) Tools = 書き込み操作(INSERT/UPDATE/DELETE、ファイル書き込み、POSTリクエスト)

この境界があることで、AIが安全性を推論しやすくなります。「この操作はデータを変更するか?」という判断がアーキテクチャから明確になります。

まとめ

MCPはまだ成熟途上の標準です。しかし方向は明確です。Cursor、Zed、VS CodeがMCPサポートを追加しています。サードパーティサーバーのエコシステムが急速に成長しています。

今日AIアプリを構築しているなら、ツール統合をMCPフォーマットで実装しておくことをお勧めします。後で別のモデルをサポートしたいときに感謝することになるでしょう。

次の記事ではマルチエージェントシステム — AutoGen、CrewAI、LangGraphを比較して、ユースケースに合ったフレームワークを選べるようにします。