Skip to content
Published on

MCPサーバーを自作する:プロトコル原理から構築まで — Grafana、Mermaid、PPT、DB連携MCPエコシステム完全解剖

Authors

1. MCPプロトコル内部構造の解剖

MCP(Model Context Protocol)はAIモデルと外部システムを接続する標準プロトコルです。この記事では、前回のMCP概要ガイドでは扱えなかったプロトコル内部の動作原理を深く掘り下げ、MCPサーバーを自分で構築する方法を解説します。

1-1. JSON-RPC 2.0ベースのメッセージ構造

MCPの全通信はJSON-RPC 2.0仕様に従います。メッセージは大きく3種類に分かれます。

Request(リクエスト): クライアントまたはサーバーが相手に作業を要求する際に送信します。

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {
      "city": "Tokyo"
    }
  }
}

Response(レスポンス): リクエストに対する結果を返します。

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Tokyo: 18 degrees, sunny"
      }
    ]
  }
}

Notification(通知): レスポンスを期待しない一方向メッセージです。idフィールドがありません。

{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}

Error Response(エラーレスポンス): リクエスト処理に失敗した場合に返します。

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "Invalid params: city is required"
  }
}

主なエラーコード:

コード意味説明
-32700Parse ErrorJSONパース失敗
-32600Invalid Request不正なリクエスト形式
-32601Method Not Found存在しないメソッド
-32602Invalid Params不正なパラメータ
-32603Internal Errorサーバー内部エラー

1-2. 接続ライフサイクル

MCP接続は明確な段階を経ます:

Phase 1 - Initialize(初期化)

クライアントがサーバーにinitializeリクエストを送信し、双方のプロトコルバージョンと機能(Capabilities)を交換します。

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "roots": {
        "listChanged": true
      },
      "sampling": {}
    },
    "clientInfo": {
      "name": "Claude Desktop",
      "version": "1.5.0"
    }
  }
}

サーバーが応答します:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "tools": {
        "listChanged": true
      },
      "resources": {
        "subscribe": true,
        "listChanged": true
      },
      "prompts": {
        "listChanged": true
      }
    },
    "serverInfo": {
      "name": "Weather MCP Server",
      "version": "1.0.0"
    }
  }
}

Phase 2 - Initialized(初期化完了)

クライアントがnotifications/initialized通知を送信し、初期化完了を宣言します。

{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}

Phase 3 - Normal Operation(通常運用)

クライアントとサーバーが自由にメッセージをやり取りします:

  • クライアントがtools/listで利用可能なツール一覧を要求
  • クライアントがtools/callで特定のツールを呼び出し
  • クライアントがresources/readでリソースを読み取り
  • サーバーがnotifications/tools/list_changedでツール一覧変更を通知

Phase 4 - Shutdown(シャットダウン)

クライアントが接続を終了すると、Transport層がクリーンアップされます。

1-3. Transport方式

MCPは2つのTransport方式をサポートしています。

stdio(Standard Input/Output)

最も一般的な方式で、ローカルでMCPサーバープロセスを直接実行します。

  • クライアントがサーバープロセスをspawn
  • stdinでリクエストを送信し、stdoutでレスポンスを受信
  • 各メッセージは改行で区切り
  • デバッグログはstderrに出力
  • Claude Desktop、Claude Codeで主に使用
Client                    Server Process
  |                           |
  |--- spawn process -------->|
  |                           |
  |--- stdin: JSON-RPC ------>|
  |<-- stdout: JSON-RPC -----|
  |                           |
  |--- stdin: JSON-RPC ------>|
  |<-- stdout: JSON-RPC -----|
  |                           |
  |--- kill process --------->|

Streamable HTTP

リモートサーバーに接続する際に使用します。2025年3月のスペック更新で従来のHTTP+SSE方式を置き換えました。

  • HTTP POSTでリクエストを送信
  • サーバーがSSE(Server-Sent Events)でストリーミングレスポンス
  • リモートデプロイ、マルチテナントシナリオに適合
  • セッション管理のためMcp-Session-Idヘッダーを使用
Client                    HTTP Server
  |                           |
  |--- POST /mcp ----------->|
  |    (initialize)           |
  |<-- SSE: result -----------|
  |<-- Mcp-Session-Id --------|
  |                           |
  |--- POST /mcp ----------->|
  |    (tools/call)           |
  |<-- SSE: result -----------|
  |                           |
  |--- DELETE /mcp ---------->|
  |    (session close)        |

どちらのTransportを選ぶべきか?

基準stdioStreamable HTTP
ユースケースローカル開発、デスクトップアプリリモートサーバー、クラウドデプロイ
セットアップ難易度とても簡単中程度
セキュリティローカルプロセス分離OAuth 2.0、TLSが必要
マルチユーザー不可可能
ネットワーク不要必要
代表例Claude Desktopチーム共有MCPサーバー

1-4. 3つのコアプリミティブ

MCPサーバーは3種類の機能をAIに提供します。

Resources(リソース) - 読み取り専用データ

URIで識別されるデータを提供します。ファイル内容、DBスキーマ、APIレスポンスなどが該当します。

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "resources/read",
  "params": {
    "uri": "file:///project/schema.sql"
  }
}

レスポンス:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "contents": [
      {
        "uri": "file:///project/schema.sql",
        "mimeType": "text/plain",
        "text": "CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(100));"
      }
    ]
  }
}

特徴:

  • アプリケーションがコンテキストとして活用(ユーザー制御)
  • URIベースの識別(file://、db://、api://など)
  • サブスクリプションで変更通知を受信可能
  • テキストまたはバイナリ(base64)コンテンツに対応

Tools(ツール) - 実行可能な関数

AIモデルが呼び出せる関数を定義します。JSON Schemaで入力パラメータを明示します。

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/list"
}

レスポンス:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "tools": [
      {
        "name": "get_weather",
        "description": "Get current weather for a city",
        "inputSchema": {
          "type": "object",
          "properties": {
            "city": {
              "type": "string",
              "description": "City name"
            }
          },
          "required": ["city"]
        }
      }
    ]
  }
}

特徴:

  • モデルが自律的に呼び出し(モデル制御)
  • JSON Schemaでパラメータ型を検証
  • 副作用(side effect)を持つ場合がある
  • ユーザー確認が必要な場合がある(セキュリティ)

Prompts(プロンプトテンプレート) - 再利用可能なプロンプト

よく使うプロンプトパターンを事前に定義します。

{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "prompts/get",
  "params": {
    "name": "code_review",
    "arguments": {
      "language": "python"
    }
  }
}

レスポンス:

{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "description": "Code review prompt for Python",
    "messages": [
      {
        "role": "user",
        "content": {
          "type": "text",
          "text": "Please review the following Python code for best practices, bugs, and performance issues."
        }
      }
    ]
  }
}

1-5. Capability Negotiationの詳細

初期化時にクライアントとサーバーはそれぞれサポートする機能を交換します。サーバーは提供するプリミティブのみ宣言すればよいです。

サーバーCapabilities例(Toolsのみ)

{
  "capabilities": {
    "tools": {
      "listChanged": true
    }
  }
}

このサーバーはToolsのみを提供し、ツール一覧が変更されると通知を送信できます。ResourcesやPromptsは提供しません。

全機能を提供するサーバー

{
  "capabilities": {
    "tools": { "listChanged": true },
    "resources": { "subscribe": true, "listChanged": true },
    "prompts": { "listChanged": true },
    "logging": {}
  }
}

クライアントCapabilities例

{
  "capabilities": {
    "roots": { "listChanged": true },
    "sampling": {}
  }
}
  • roots: クライアントが作業中のディレクトリ/プロジェクト情報を提供
  • sampling: サーバーがクライアントのLLMを逆に呼び出し可能

1-6. Sampling:サーバーからLLMへの逆呼び出し

SamplingはMCPの独特な機能で、サーバーがクライアントのAIモデルに逆にリクエストを送信できます。

{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "sampling/createMessage",
  "params": {
    "messages": [
      {
        "role": "user",
        "content": {
          "type": "text",
          "text": "Summarize this error log: Connection timeout after 30s..."
        }
      }
    ],
    "maxTokens": 500,
    "modelPreferences": {
      "hints": [{ "name": "claude-sonnet-4-20250514" }]
    }
  }
}

活用シナリオ:

  • サーバーが収集したログデータをAIに分析要求
  • 複雑なデータをAIに要約要求
  • エージェントワークフローでの中間判断要求

ただし、Samplingはセキュリティ上ユーザー確認が必須です。クライアントがHuman-in-the-loopで制御します。


2. Python FastMCPでMCPサーバーを作る(実習1)

FastMCPはPythonでMCPサーバーを最も簡単に構築できるフレームワークです。MCP Python SDKの公式高レベルAPIでもあります。

2-1. 天気MCPサーバー(最もシンプルな例)

インストール

pip install fastmcp

weather_server.py

from fastmcp import FastMCP

# MCPサーバーインスタンス作成
mcp = FastMCP("Weather Server")


@mcp.tool()
def get_weather(city: str) -> str:
    """Get current weather for a city.

    Args:
        city: Name of the city (e.g., Tokyo, Seoul, New York)
    """
    # 実際には天気APIを呼び出しますが、ここではシミュレーション
    weather_data = {
        "Tokyo": "18 degrees, sunny",
        "Seoul": "15 degrees, partly cloudy",
        "New York": "10 degrees, rainy",
        "London": "8 degrees, foggy",
    }
    result = weather_data.get(city, f"Weather data not available for {city}")
    return f"Weather in {city}: {result}"


@mcp.tool()
def get_forecast(city: str, days: int = 3) -> str:
    """Get weather forecast for a city.

    Args:
        city: Name of the city
        days: Number of days to forecast (1-7)
    """
    if days < 1 or days > 7:
        return "Days must be between 1 and 7"
    return f"{days}-day forecast for {city}: Mostly sunny with temperatures 10-20C"


@mcp.resource("weather://cities")
def list_cities() -> str:
    """List all supported cities."""
    cities = ["Tokyo", "Seoul", "New York", "London", "Paris"]
    return "\n".join(cities)


if __name__ == "__main__":
    mcp.run()

実行方法

# 直接実行(stdioモード)
python weather_server.py

# またはFastMCP CLIで実行
fastmcp run weather_server.py

# MCP Inspectorでテスト
fastmcp dev weather_server.py

Claude Desktop連携(claude_desktop_config.json):

{
  "mcpServers": {
    "weather": {
      "command": "python",
      "args": ["/path/to/weather_server.py"]
    }
  }
}

Claude Code連携(.mcp.json):

{
  "mcpServers": {
    "weather": {
      "command": "python",
      "args": ["weather_server.py"]
    }
  }
}

2-2. データベースMCPサーバー(実践)

実務で最も多く使われるDB連携MCPサーバーを構築しましょう。

import sqlite3
import json
from fastmcp import FastMCP

mcp = FastMCP("Database Server")

DB_PATH = "myapp.db"


def get_connection():
    """Get database connection."""
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    return conn


@mcp.resource("db://schema")
def get_schema() -> str:
    """Get the database schema information."""
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute(
        "SELECT sql FROM sqlite_master WHERE type='table' ORDER BY name"
    )
    tables = cursor.fetchall()
    conn.close()
    schema_lines = []
    for table in tables:
        if table[0]:
            schema_lines.append(table[0])
    return "\n\n".join(schema_lines)


@mcp.resource("db://tables")
def list_tables() -> str:
    """List all tables in the database."""
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute(
        "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
    )
    tables = [row[0] for row in cursor.fetchall()]
    conn.close()
    return json.dumps(tables, indent=2)


@mcp.tool()
def query_database(sql: str) -> str:
    """Execute a read-only SQL query and return results.

    Args:
        sql: SQL SELECT query to execute
    """
    # セキュリティ:SELECTのみ許可
    if not sql.strip().upper().startswith("SELECT"):
        return "Error: Only SELECT queries are allowed for safety"

    conn = get_connection()
    try:
        cursor = conn.cursor()
        cursor.execute(sql)
        rows = cursor.fetchall()
        if not rows:
            return "No results found"

        columns = [description[0] for description in cursor.description]
        results = []
        for row in rows:
            results.append(dict(zip(columns, row)))

        return json.dumps(results, indent=2, ensure_ascii=False)
    except Exception as e:
        return f"Query error: {str(e)}"
    finally:
        conn.close()


@mcp.tool()
def insert_record(table: str, data: str) -> str:
    """Insert a record into a table.

    Args:
        table: Table name
        data: JSON string of column-value pairs
    """
    try:
        record = json.loads(data)
    except json.JSONDecodeError:
        return "Error: Invalid JSON data"

    columns = ", ".join(record.keys())
    placeholders = ", ".join(["?" for _ in record])
    values = list(record.values())

    conn = get_connection()
    try:
        cursor = conn.cursor()
        cursor.execute(
            f"INSERT INTO {table} ({columns}) VALUES ({placeholders})",
            values,
        )
        conn.commit()
        return f"Successfully inserted record with id {cursor.lastrowid}"
    except Exception as e:
        return f"Insert error: {str(e)}"
    finally:
        conn.close()


@mcp.tool()
def update_record(table: str, record_id: int, data: str) -> str:
    """Update a record in a table.

    Args:
        table: Table name
        record_id: ID of the record to update
        data: JSON string of column-value pairs to update
    """
    try:
        updates = json.loads(data)
    except json.JSONDecodeError:
        return "Error: Invalid JSON data"

    set_clause = ", ".join([f"{k} = ?" for k in updates.keys()])
    values = list(updates.values()) + [record_id]

    conn = get_connection()
    try:
        cursor = conn.cursor()
        cursor.execute(
            f"UPDATE {table} SET {set_clause} WHERE id = ?", values
        )
        conn.commit()
        return f"Successfully updated {cursor.rowcount} record(s)"
    except Exception as e:
        return f"Update error: {str(e)}"
    finally:
        conn.close()


@mcp.tool()
def delete_record(table: str, record_id: int) -> str:
    """Delete a record from a table.

    Args:
        table: Table name
        record_id: ID of the record to delete
    """
    conn = get_connection()
    try:
        cursor = conn.cursor()
        cursor.execute(f"DELETE FROM {table} WHERE id = ?", [record_id])
        conn.commit()
        return f"Successfully deleted {cursor.rowcount} record(s)"
    except Exception as e:
        return f"Delete error: {str(e)}"
    finally:
        conn.close()


if __name__ == "__main__":
    mcp.run()

このサーバーは以下を提供します:

  • Resources: DBスキーマ情報とテーブル一覧
  • Tools: SELECTクエリ、INSERT、UPDATE、DELETE操作

Claudeに「usersテーブルから最近登録した10人を表示して」と言えば、自動的にquery_databaseツールを呼び出します。

2-3. ファイルシステムMCPサーバー

import os
from pathlib import Path
from fastmcp import FastMCP

mcp = FastMCP("Filesystem Server")

# セキュリティ:許可されたディレクトリのみアクセス可能
ALLOWED_DIRS = [
    Path("/Users/dev/projects"),
    Path("/Users/dev/documents"),
]


def is_path_allowed(path: str) -> bool:
    """Check if the path is within allowed directories."""
    resolved = Path(path).resolve()
    return any(
        resolved == allowed or allowed in resolved.parents
        for allowed in ALLOWED_DIRS
    )


@mcp.tool()
def read_file(path: str) -> str:
    """Read contents of a file.

    Args:
        path: Absolute file path
    """
    if not is_path_allowed(path):
        return "Error: Access denied. Path is outside allowed directories."

    try:
        with open(path, "r", encoding="utf-8") as f:
            return f.read()
    except FileNotFoundError:
        return f"Error: File not found: {path}"
    except UnicodeDecodeError:
        return "Error: File is not a text file"


@mcp.tool()
def write_file(path: str, content: str) -> str:
    """Write content to a file.

    Args:
        path: Absolute file path
        content: Content to write
    """
    if not is_path_allowed(path):
        return "Error: Access denied. Path is outside allowed directories."

    try:
        with open(path, "w", encoding="utf-8") as f:
            f.write(content)
        return f"Successfully wrote to {path}"
    except Exception as e:
        return f"Error writing file: {str(e)}"


@mcp.tool()
def list_directory(path: str) -> str:
    """List contents of a directory.

    Args:
        path: Absolute directory path
    """
    if not is_path_allowed(path):
        return "Error: Access denied. Path is outside allowed directories."

    try:
        entries = []
        for entry in sorted(Path(path).iterdir()):
            entry_type = "dir" if entry.is_dir() else "file"
            size = entry.stat().st_size if entry.is_file() else 0
            entries.append(f"[{entry_type}] {entry.name} ({size} bytes)")
        return "\n".join(entries)
    except FileNotFoundError:
        return f"Error: Directory not found: {path}"


@mcp.tool()
def search_files(directory: str, pattern: str) -> str:
    """Search for files matching a glob pattern.

    Args:
        directory: Directory to search in
        pattern: Glob pattern (e.g., '*.py', '**/*.md')
    """
    if not is_path_allowed(directory):
        return "Error: Access denied."

    matches = list(Path(directory).glob(pattern))
    if not matches:
        return "No files found matching the pattern"
    return "\n".join(str(m) for m in matches[:50])


@mcp.resource("fs://allowed-dirs")
def get_allowed_dirs() -> str:
    """List allowed directories."""
    return "\n".join(str(d) for d in ALLOWED_DIRS)


if __name__ == "__main__":
    mcp.run()

重要なセキュリティポイント:

  • ALLOWED_DIRSでアクセス可能なディレクトリを制限
  • Path.resolve()でシンボリックリンクのバイパスを防止
  • 親ディレクトリ探索(../)をブロック

3. TypeScript SDKでMCPサーバーを作る(実習2)

TypeScriptはMCPサーバー開発でPythonの次に多く使われています。公式の@modelcontextprotocol/sdkパッケージが提供されています。

3-1. 基本構造

インストール

npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "declaration": true
  },
  "include": ["src/**/*"]
}

src/index.ts(基本構造):

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'

// サーバーインスタンス作成
const server = new McpServer({
  name: 'My MCP Server',
  version: '1.0.0',
})

// Tool登録
server.tool(
  'hello',
  'Say hello to someone',
  {
    name: z.string().describe('Name of the person'),
  },
  async ({ name }) => {
    return {
      content: [
        {
          type: 'text',
          text: `Hello, ${name}! Welcome to MCP.`,
        },
      ],
    }
  }
)

// Resource登録
server.resource('info', 'server://info', async (uri) => {
  return {
    contents: [
      {
        uri: uri.href,
        mimeType: 'text/plain',
        text: 'This is My MCP Server v1.0.0',
      },
    ],
  }
})

// Transport接続と実行
async function main() {
  const transport = new StdioServerTransport()
  await server.connect(transport)
  console.error('MCP Server running on stdio')
}

main().catch(console.error)

実行

npx tsc
node dist/index.js

Zodを使ったパラメータ検証がTypeScript SDKの核心です。型安全性と自動JSON Schema生成の両方を実現します。

3-2. GitHub Issue MCPサーバー(実践)

実践で役立つGitHub Issue管理MCPサーバーを構築しましょう。

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'

const server = new McpServer({
  name: 'GitHub Issue Server',
  version: '1.0.0',
})

const GITHUB_TOKEN = process.env.GITHUB_TOKEN
const BASE_URL = 'https://api.github.com'

// 共通ヘッダー
function getHeaders() {
  return {
    Authorization: `Bearer ${GITHUB_TOKEN}`,
    Accept: 'application/vnd.github.v3+json',
    'Content-Type': 'application/json',
    'User-Agent': 'MCP-GitHub-Server',
  }
}

// Tool: Issue一覧取得
server.tool(
  'list_issues',
  'List issues in a GitHub repository',
  {
    owner: z.string().describe('Repository owner'),
    repo: z.string().describe('Repository name'),
    state: z.enum(['open', 'closed', 'all']).default('open').describe('Issue state filter'),
    labels: z.string().optional().describe('Comma-separated label names'),
    per_page: z.number().min(1).max(100).default(10).describe('Results per page'),
  },
  async ({ owner, repo, state, labels, per_page }) => {
    const params = new URLSearchParams({
      state,
      per_page: String(per_page),
    })
    if (labels) params.set('labels', labels)

    const response = await fetch(`${BASE_URL}/repos/${owner}/${repo}/issues?${params}`, {
      headers: getHeaders(),
    })

    if (!response.ok) {
      return {
        content: [
          {
            type: 'text' as const,
            text: `Error: ${response.status} ${response.statusText}`,
          },
        ],
      }
    }

    const issues = await response.json()
    const formatted = issues.map(
      (issue: Record<string, unknown>) =>
        `#${issue.number} [${issue.state}] ${issue.title}\n  Labels: ${
          (issue.labels as Array<Record<string, string>>).map((l) => l.name).join(', ') || 'none'
        }\n  Created: ${issue.created_at}`
    )

    return {
      content: [
        {
          type: 'text' as const,
          text: formatted.join('\n\n') || 'No issues found',
        },
      ],
    }
  }
)

// Tool: Issue作成
server.tool(
  'create_issue',
  'Create a new issue in a GitHub repository',
  {
    owner: z.string().describe('Repository owner'),
    repo: z.string().describe('Repository name'),
    title: z.string().describe('Issue title'),
    body: z.string().optional().describe('Issue body (Markdown)'),
    labels: z.array(z.string()).optional().describe('Labels to add'),
    assignees: z.array(z.string()).optional().describe('Usernames to assign'),
  },
  async ({ owner, repo, title, body, labels, assignees }) => {
    const response = await fetch(`${BASE_URL}/repos/${owner}/${repo}/issues`, {
      method: 'POST',
      headers: getHeaders(),
      body: JSON.stringify({ title, body, labels, assignees }),
    })

    if (!response.ok) {
      const error = await response.text()
      return {
        content: [
          {
            type: 'text' as const,
            text: `Error creating issue: ${response.status} - ${error}`,
          },
        ],
      }
    }

    const issue = await response.json()
    return {
      content: [
        {
          type: 'text' as const,
          text: `Created issue #${issue.number}: ${issue.title}\nURL: ${issue.html_url}`,
        },
      ],
    }
  }
)

// Tool: Issueクローズ
server.tool(
  'close_issue',
  'Close an issue in a GitHub repository',
  {
    owner: z.string().describe('Repository owner'),
    repo: z.string().describe('Repository name'),
    issue_number: z.number().describe('Issue number'),
    comment: z.string().optional().describe('Optional closing comment'),
  },
  async ({ owner, repo, issue_number, comment }) => {
    if (comment) {
      await fetch(`${BASE_URL}/repos/${owner}/${repo}/issues/${issue_number}/comments`, {
        method: 'POST',
        headers: getHeaders(),
        body: JSON.stringify({ body: comment }),
      })
    }

    const response = await fetch(`${BASE_URL}/repos/${owner}/${repo}/issues/${issue_number}`, {
      method: 'PATCH',
      headers: getHeaders(),
      body: JSON.stringify({ state: 'closed' }),
    })

    if (!response.ok) {
      return {
        content: [
          {
            type: 'text' as const,
            text: `Error closing issue: ${response.status}`,
          },
        ],
      }
    }

    return {
      content: [
        {
          type: 'text' as const,
          text: `Closed issue #${issue_number} successfully`,
        },
      ],
    }
  }
)

// Resource: リポジトリ情報
server.resource('repo-info', 'github://repo-info', async (uri) => {
  return {
    contents: [
      {
        uri: uri.href,
        mimeType: 'text/plain',
        text: 'GitHub Issue MCP Server - Manage issues via AI',
      },
    ],
  }
})

// Prompt: Issueトリアージ
server.prompt(
  'triage_issues',
  'Generate a prompt for triaging open issues',
  { owner: z.string(), repo: z.string() },
  ({ owner, repo }) => ({
    messages: [
      {
        role: 'user' as const,
        content: {
          type: 'text' as const,
          text: `Please triage the open issues in ${owner}/${repo}. For each issue:
1. Assess priority (critical/high/medium/low)
2. Suggest appropriate labels
3. Recommend if it should be assigned to someone
4. Provide a brief action plan`,
        },
      },
    ],
  })
)

async function main() {
  const transport = new StdioServerTransport()
  await server.connect(transport)
  console.error('GitHub Issue MCP Server running')
}

main().catch(console.error)

package.json

{
  "name": "github-issue-mcp",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.12.0",
    "zod": "^3.24.0"
  },
  "devDependencies": {
    "typescript": "^5.7.0",
    "@types/node": "^22.0.0"
  }
}

Claude Desktop連携

{
  "mcpServers": {
    "github-issues": {
      "command": "node",
      "args": ["/path/to/github-issue-mcp/dist/index.js"],
      "env": {
        "GITHUB_TOKEN": "ghp_your_token_here"
      }
    }
  }
}

4. MCPサーバーのテストとデバッグ

4-1. MCP Inspector

MCP InspectorはMCPサーバーをローカルでテストできる公式ツールです。

# npxで直接実行
npx @modelcontextprotocol/inspector

# 特定のサーバーを指定して実行
npx @modelcontextprotocol/inspector python weather_server.py

# FastMCPのdevモード(Inspector自動接続)
fastmcp dev weather_server.py

Inspectorが提供する機能:

  • サーバーのすべてのツール、リソース、プロンプトを確認
  • ツールを直接呼び出して結果を確認
  • JSON-RPCメッセージログをリアルタイムで確認
  • 接続状態をモニタリング

4-2. Claude Desktopでの連携

macOSでの設定ファイルの場所:

# 設定ファイルを開く
code ~/Library/Application\ Support/Claude/claude_desktop_config.json

Windows:

%APPDATA%\Claude\claude_desktop_config.json

設定例:

{
  "mcpServers": {
    "weather": {
      "command": "python",
      "args": ["/absolute/path/to/weather_server.py"]
    },
    "database": {
      "command": "python",
      "args": ["/absolute/path/to/db_server.py"],
      "env": {
        "DB_PATH": "/data/myapp.db"
      }
    },
    "github": {
      "command": "node",
      "args": ["/absolute/path/to/github-mcp/dist/index.js"],
      "env": {
        "GITHUB_TOKEN": "ghp_xxx"
      }
    }
  }
}

4-3. Claude Codeでの連携

プロジェクトルートに.mcp.jsonファイルを作成します:

{
  "mcpServers": {
    "my-server": {
      "command": "python",
      "args": ["./tools/my_mcp_server.py"]
    }
  }
}

またはグローバル設定:

claude mcp add my-server python ./tools/my_mcp_server.py

4-4. ロギング戦略

MCPサーバーでデバッグログはstderrに出力します。stdoutはJSON-RPCメッセージ専用だからです。

Python

import sys
import logging

# stderrへのログ出力設定
logging.basicConfig(
    level=logging.DEBUG,
    stream=sys.stderr,
    format="%(asctime)s [%(levelname)s] %(message)s",
)

logger = logging.getLogger("my-mcp-server")

@mcp.tool()
def my_tool(param: str) -> str:
    logger.debug(f"Tool called with param: {param}")
    try:
        result = do_something(param)
        logger.info(f"Tool succeeded: {result}")
        return result
    except Exception as e:
        logger.error(f"Tool failed: {e}")
        return f"Error: {str(e)}"

TypeScript

// stderrへのログ出力
function log(level: string, message: string) {
  console.error(`[${new Date().toISOString()}] [${level}] ${message}`)
}

server.tool('my_tool', 'Description', { param: z.string() }, async ({ param }) => {
  log('DEBUG', `Tool called with: ${param}`)
  try {
    const result = await doSomething(param)
    log('INFO', `Success: ${result}`)
    return { content: [{ type: 'text', text: result }] }
  } catch (error) {
    log('ERROR', `Failed: ${error}`)
    return { content: [{ type: 'text', text: `Error: ${error}` }] }
  }
})

4-5. エラーハンドリングパターン

from fastmcp import FastMCP
from fastmcp.exceptions import ToolError

mcp = FastMCP("Robust Server")

@mcp.tool()
def safe_operation(param: str) -> str:
    """A tool with proper error handling."""
    # 入力検証
    if not param or len(param) > 1000:
        raise ToolError("Parameter must be 1-1000 characters")

    try:
        result = external_api_call(param)
        return result
    except ConnectionError:
        raise ToolError("Failed to connect to external service. Please try again.")
    except TimeoutError:
        raise ToolError("Request timed out. The service may be temporarily unavailable.")
    except Exception as e:
        # 予想外のエラーは詳細情報を隠して一般メッセージのみ返却
        logging.error(f"Unexpected error: {e}", exc_info=True)
        raise ToolError("An unexpected error occurred. Check server logs for details.")

5. MCPサーバーのデプロイ

5-1. Cloudflare Workersへのデプロイ

Cloudflare Workersを使えば、サーバーレスでMCPサーバーをグローバルにデプロイできます。

# プロジェクト作成
npm create cloudflare@latest -- my-mcp-server
cd my-mcp-server
npm install @modelcontextprotocol/sdk

src/index.ts(Workers用):

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'

export default {
  async fetch(request: Request): Promise<Response> {
    const server = new McpServer({
      name: 'Cloudflare MCP Server',
      version: '1.0.0',
    })

    // Tools登録
    server.tool('hello', 'Say hello', { name: z.string() }, async ({ name }) => ({
      content: [{ type: 'text', text: `Hello ${name} from the edge!` }],
    }))

    // Streamable HTTP transportの処理
    // 実際にはMCP SDKのHTTP transport handlerを使用
    return new Response('MCP Server Running', { status: 200 })
  },
}
# デプロイ
npx wrangler deploy

5-2. Dockerコンテナ化

FROM node:22-slim

WORKDIR /app

COPY package*.json ./
RUN npm ci --production

COPY dist/ ./dist/

# MCPサーバーはstdioで通信
CMD ["node", "dist/index.js"]
docker build -t my-mcp-server .
docker run -i my-mcp-server

Claude DesktopからDockerサーバーに接続:

{
  "mcpServers": {
    "my-server": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "my-mcp-server"]
    }
  }
}

5-3. npm/PyPIパッケージとして配布

npmパッケージ

{
  "name": "@myorg/mcp-weather",
  "version": "1.0.0",
  "bin": {
    "mcp-weather": "./dist/index.js"
  }
}
npm publish --access public

ユーザーはすぐに使えます:

{
  "mcpServers": {
    "weather": {
      "command": "npx",
      "args": ["-y", "@myorg/mcp-weather"]
    }
  }
}

PyPIパッケージ

pip install build twine
python -m build
twine upload dist/*
{
  "mcpServers": {
    "weather": {
      "command": "uvx",
      "args": ["mcp-weather"]
    }
  }
}

5-4. Smitheryマーケットプレイス

Smithery(smithery.ai)はMCPサーバー専用のレジストリです。登録プロセス:

  1. GitHubリポジトリにMCPサーバーコードをプッシュ
  2. smithery.yaml設定ファイルを追加
  3. Smitheryウェブサイトでリポジトリを接続
  4. 自動ビルド・デプロイ
# smithery.yaml
name: my-weather-mcp
description: Weather MCP server for AI assistants
icon: cloud-sun
startCommand:
  type: stdio
  configSchema:
    type: object
    properties:
      API_KEY:
        type: string
        description: Weather API key
    required:
      - API_KEY
  commandFunction:
    - node
    - dist/index.js

5-5. OAuth 2.0認証の追加

リモートMCPサーバーには認証が必須です。2025年6月のスペックでOAuth 2.0サポートが公式化されました。

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'

const server = new McpServer({
  name: 'Authenticated Server',
  version: '1.0.0',
})

// OAuth 2.0ミドルウェア(Streamable HTTP transport)
async function validateToken(token: string): Promise<boolean> {
  const response = await fetch('https://auth.example.com/introspect', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: `token=${token}`,
  })
  const data = await response.json()
  return data.active === true
}

// HTTPハンドラーで認証確認
async function handleRequest(req: Request): Promise<Response> {
  const authHeader = req.headers.get('Authorization')
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return new Response('Unauthorized', { status: 401 })
  }

  const token = authHeader.slice(7)
  if (!(await validateToken(token))) {
    return new Response('Invalid token', { status: 403 })
  }

  // MCPリクエスト処理を続行...
  return new Response('OK')
}

6. MCPエコシステム100+サーバー総整理

2026年3月現在、MCPエコシステムは爆発的に成長しています。カテゴリ別に主要サーバーを整理します。

6-1. データと分析

サーバー機能活用
Grafana MCPダッシュボード照会、メトリクスクエリ、アラート管理AIでモニタリング自動化
PostgreSQL MCPSQLクエリ、スキーマ探索、データ分析自然言語からSQLへ変換
MongoDB MCPコレクションCRUD、集約パイプラインNoSQLデータ探索
ClickHouse MCP分析クエリ、ログ照会ログベースの問題解決
BigQuery MCP大規模データ分析データチームの生産性向上
MySQL MCPSQLクエリ、テーブル管理RDBMS直接操作
Redis MCPキャッシュ照会、キーバリュー管理キャッシュデバッグ
Elasticsearch MCP全文検索、インデックス管理ログ分析自動化
Snowflake MCPデータウェアハウスクエリビジネスインテリジェンス
DuckDB MCPローカル分析クエリ高速データ探索
Prometheus MCPメトリクスクエリ(PromQL)システムモニタリング
Datadog MCPメトリクス、ダッシュボード、アラート管理統合オブザーバビリティ

Grafana MCP活用例

ユーザー:「過去24時間でCPU使用率が80%を超えたサーバーを見つけて」
Claude:[Grafana MCPのquery_metricsツールを呼び出し]
        -> PromQL: avg by (instance)(cpu_usage_percent > 80)
        -> 結果: server-03(平均87%)、server-07(平均92%)

6-2. 開発ツール

サーバー機能活用
GitHub MCPIssue、PR、Actions、コード検索コードレビュー自動化
GitLab MCPパイプライン、マージリクエストCI/CD管理
Jira MCPIssue追跡、スプリント管理PM業務自動化
Linear MCPIssue/プロジェクト管理モダンIssueトラッカー連携
Sentry MCPエラー追跡、パフォーマンスモニタリングデバッグ自動化
CircleCI MCPCI/CDパイプライン管理ビルドモニタリング
npm MCPパッケージ検索、バージョン確認依存関係管理
Snyk MCPセキュリティ脆弱性スキャンセキュリティ監査自動化
SonarQube MCPコード品質分析コードレビュー補助
Postman MCPAPIテスト、コレクション管理API開発効率化

6-3. ドキュメントと可視化

サーバー機能活用
Mermaid MCPダイアグラム生成(flowchart、sequence、ER)ドキュメント自動化
PPT/Slides MCPプレゼンテーション作成/編集レポート自動化
Notion MCPページ、データベースCRUDナレッジ管理
Google Docs MCPドキュメント読み取り/編集協働ドキュメント自動化
Confluence MCPWikiページ管理技術文書化
Excalidraw MCPホワイトボードダイアグラムアーキテクチャダイアグラム
Google Sheets MCPスプレッドシート読み取り/編集データレポート
Obsidian MCPマークダウンノート管理個人ナレッジベース

6-4. コミュニケーション

サーバー機能活用
Slack MCPチャネルメッセージ、検索、DMチームコミュニケーション自動化
Discord MCPサーバー/チャネル管理コミュニティ管理
Email MCPメール読み取り/送信/検索メール自動化
Microsoft Teams MCPチャット、ミーティング管理エンタープライズ協業
Telegram MCPボットメッセージ、チャネル管理通知自動化
Twilio MCPSMS、音声通話顧客コミュニケーション

6-5. クラウドとインフラ

サーバー機能活用
AWS MCPS3、Lambda、EC2管理インフラ自動化
Kubernetes MCPクラスター管理、Pod照会K8s運用自動化
Docker MCPコンテナ/イメージ管理開発環境管理
Terraform MCPIaC実行、状態照会インフラコード生成
Vercel MCPデプロイ、環境変数、ログフロントエンドデプロイ
GCP MCPGoogle Cloudサービス管理GCPインフラ自動化
Azure MCPAzureサービス管理エンタープライズクラウド
Cloudflare MCPWorkers、DNS、CDNエッジコンピューティング管理
Pulumi MCPIaC(プログラミング言語)コードベースのインフラ
Ansible MCP構成管理、自動化サーバープロビジョニング

6-6. AIとML

サーバー機能活用
HuggingFace MCPモデル検索、推論MLワークフロー
Weights and Biases MCP実験追跡、メトリクスML実験管理
LangChain MCPチェーン/エージェント実行AIパイプライン
Ollama MCPローカルLLM管理プライベートAI実行
Replicate MCPクラウドモデル実行MLモデルデプロイ
Pinecone MCPベクトルDB管理RAG構築
Weaviate MCPベクトル検索セマンティック検索
ChromaDB MCPローカルベクトルDBエンベディング管理

6-7. ブラウザとスクレイピング

サーバー機能活用
Playwright MCPブラウザ自動化、スクリーンショットWebテスト
Puppeteer MCPChrome自動化クローリング
Brave Search MCPWeb検索APIAIにリアルタイム情報提供
Firecrawl MCPWebサイトクローリング、マークダウン変換RAGデータ収集
Browserbase MCPクラウドブラウザ大規模Web自動化
Exa MCPセマンティックWeb検索高品質検索結果
Tavily MCPAI最適化検索リサーチ自動化

6-8. ファイルとストレージ

サーバー機能活用
Filesystem MCPローカルファイル読み取り/書き込みファイル管理
Google Drive MCPファイル検索/ダウンロードクラウドファイル管理
S3 MCPバケット/オブジェクト管理クラウドストレージ
Dropbox MCPファイル同期、共有ファイル協業
OneDrive MCPMicrosoftファイル管理エンタープライズファイル
Box MCPエンタープライズファイル管理エンタープライズコンテンツ管理

6-9. デザインとメディア

サーバー機能活用
Figma MCPデザインファイル照会、コンポーネント抽出デザインからコードへ変換
Canva MCPデザイン作成/編集マーケティング資料
FFmpeg MCPオーディオ/ビデオ変換メディア処理
Sharp MCP画像処理画像最適化

6-10. その他の特化サーバー

サーバー機能活用
Stripe MCP決済、サブスクリプション管理決済システム
Shopify MCPストア、商品管理Eコマース
SendGrid MCPメール配信マーケティングメール
Airtable MCPスプレッドシートDBノーコードデータ管理
Supabase MCPBaaS(DB、Auth、Storage)フルスタック開発
Firebase MCPGoogle BaaSモバイル/Webバックエンド
Neon MCPサーバーレスPostgreSQLデータベース管理
PlanetScale MCPサーバーレスMySQLスケーラブルDB
Turso MCPエッジSQLite分散データベース

7. オリジナルMCPサーバーアイデア10選

MCPサーバーを自作すれば、AIの活用範囲が大きく広がります。以下は実用的なMCPサーバーアイデアです。

7-1. 個人ブログMCP

MDXファイルベースのブログを管理するMCPサーバーです。

from fastmcp import FastMCP
import glob
import yaml

mcp = FastMCP("Blog Manager")

BLOG_DIR = "/path/to/blog/data"

@mcp.tool()
def list_posts(tag: str = "") -> str:
    """List all blog posts, optionally filtered by tag."""
    posts = glob.glob(f"{BLOG_DIR}/**/*.mdx", recursive=True)
    results = []
    for post_path in sorted(posts, reverse=True):
        with open(post_path) as f:
            content = f.read()
            if content.startswith("---"):
                fm_end = content.index("---", 3)
                frontmatter = yaml.safe_load(content[3:fm_end])
                if tag and tag not in frontmatter.get("tags", []):
                    continue
                results.append(
                    f"- {frontmatter['title']} ({frontmatter['date']})"
                )
    return "\n".join(results[:20])

@mcp.tool()
def create_post(title: str, tags: str, content: str) -> str:
    """Create a new blog post."""
    # 実装...
    return "Post created successfully"

7-2. 株式/暗号通貨レートMCP

リアルタイムレートを照会し、ポートフォリオを分析します。

7-3. カレンダー/スケジュールMCP

GoogleカレンダーやAppleカレンダーと連携してスケジュールを管理します。

7-4. CI/CDパイプラインモニタリングMCP

GitHub Actions、Jenkins、CircleCIパイプラインの状態をモニタリングします。

7-5. Kubernetesログ分析MCP

kubectlをラップしてPodログを分析し、異常を検知します。

7-6. Figmaデザイン抽出MCP

Figma APIでデザインコンポーネントを抽出しコードに変換します。

7-7. 翻訳MCP

DeepLやGoogle Translate APIで多言語翻訳を提供します。

7-8. 個人メモ/WikiMCP

ObsidianやLogseqのボールトをAIが探索・編集できるようにします。

7-9. ヘルスケアデータMCP

Apple HealthやFitbitのデータを分析します。

7-10. スマートホームIoT制御MCP

Home Assistant APIで照明、温度、セキュリティシステムを制御します。

from fastmcp import FastMCP
import httpx

mcp = FastMCP("Smart Home")

HA_URL = "http://homeassistant.local:8123"
HA_TOKEN = "your_long_lived_token"

@mcp.tool()
def control_light(entity_id: str, action: str) -> str:
    """Control a smart light.

    Args:
        entity_id: Home Assistant entity ID (e.g., light.living_room)
        action: 'on' or 'off'
    """
    service = "turn_on" if action == "on" else "turn_off"
    response = httpx.post(
        f"{HA_URL}/api/services/light/{service}",
        headers={"Authorization": f"Bearer {HA_TOKEN}"},
        json={"entity_id": entity_id},
    )
    return f"Light {entity_id}: {action} (status: {response.status_code})"

@mcp.tool()
def get_temperature(entity_id: str) -> str:
    """Get temperature from a sensor."""
    response = httpx.get(
        f"{HA_URL}/api/states/{entity_id}",
        headers={"Authorization": f"Bearer {HA_TOKEN}"},
    )
    data = response.json()
    return f"Temperature: {data['state']}{data['attributes'].get('unit_of_measurement', '')}"

8. MCPセキュリティベストプラクティス

MCPサーバーはAIにシステムアクセス権限を付与するため、セキュリティが非常に重要です。

8-1. 入力検証

Python(Pydantic)

from pydantic import BaseModel, Field, validator

class QueryInput(BaseModel):
    sql: str = Field(..., max_length=5000)
    timeout: int = Field(default=30, ge=1, le=300)

    @validator("sql")
    def validate_sql(cls, v):
        forbidden = ["DROP", "DELETE", "TRUNCATE", "ALTER"]
        upper = v.upper()
        for keyword in forbidden:
            if keyword in upper:
                raise ValueError(f"Forbidden SQL keyword: {keyword}")
        return v

TypeScript(Zod)

const querySchema = z.object({
  sql: z
    .string()
    .max(5000)
    .refine(
      (sql) => {
        const forbidden = ['DROP', 'DELETE', 'TRUNCATE', 'ALTER']
        const upper = sql.toUpperCase()
        return !forbidden.some((kw) => upper.includes(kw))
      },
      { message: 'Forbidden SQL keyword detected' }
    ),
  timeout: z.number().min(1).max(300).default(30),
})

8-2. 最小権限の原則

  • DBサーバー:読み取り専用アカウントを使用(SELECTのみ許可)
  • ファイルサーバー:許可ディレクトリの制限、シンボリックリンクの解決
  • APIサーバー:必要なスコープのみのトークンを使用
  • ネットワーク:内部ネットワークアクセスをブロック

8-3. レートリミット

import time
from collections import defaultdict

class RateLimiter:
    def __init__(self, max_calls: int = 10, window: int = 60):
        self.max_calls = max_calls
        self.window = window
        self.calls = defaultdict(list)

    def check(self, key: str) -> bool:
        now = time.time()
        self.calls[key] = [
            t for t in self.calls[key] if now - t < self.window
        ]
        if len(self.calls[key]) >= self.max_calls:
            return False
        self.calls[key].append(now)
        return True

limiter = RateLimiter(max_calls=20, window=60)

@mcp.tool()
def rate_limited_tool(param: str) -> str:
    if not limiter.check("default"):
        return "Rate limit exceeded. Please wait before trying again."
    return do_work(param)

8-4. シークレット管理

import os

# 環境変数からシークレットをロード(ハードコーディング禁止)
DB_PASSWORD = os.environ.get("DB_PASSWORD")
API_KEY = os.environ.get("API_KEY")

if not DB_PASSWORD or not API_KEY:
    raise RuntimeError("Required environment variables not set")

Claude Desktop設定で環境変数を渡す:

{
  "mcpServers": {
    "my-server": {
      "command": "python",
      "args": ["server.py"],
      "env": {
        "DB_PASSWORD": "secret_from_keychain",
        "API_KEY": "sk-xxx"
      }
    }
  }
}

8-5. 監査ログ

import json
import sys
from datetime import datetime

def audit_log(tool_name: str, params: dict, result: str, success: bool):
    log_entry = {
        "timestamp": datetime.utcnow().isoformat(),
        "tool": tool_name,
        "params": params,
        "success": success,
        "result_length": len(result),
    }
    print(json.dumps(log_entry), file=sys.stderr)

8-6. サンドボックス化

DockerやVMを使ってMCPサーバーを分離します:

# docker-compose.yml
services:
  mcp-server:
    build: .
    read_only: true
    security_opt:
      - no-new-privileges:true
    tmpfs:
      - /tmp:size=100M
    mem_limit: 512m
    cpus: '0.5'
    networks:
      - mcp-net
    volumes:
      - ./allowed-data:/data:ro

networks:
  mcp-net:
    driver: bridge
    internal: true # 外部インターネットアクセスをブロック

9. MCPの未来:2026年展望

9-1. Agent-to-Agent MCP通信

現在のMCPは主に人間(ユーザー)-AI間のツール接続に焦点を当てています。2026年にはAIエージェント同士がMCPを通じて直接通信するシナリオが拡大するでしょう。

例えば:

  • コードレビューエージェントがセキュリティ分析エージェントにMCPで脆弱性スキャンを要求
  • データパイプラインエージェントがモニタリングエージェントに異常検知を確認
  • カスタマーサポートエージェントが注文管理エージェントに注文状況を照会

9-2. マルチモーダルMCP

現在のMCPはテキスト中心ですが、画像、オーディオ、ビデオをやり取りするマルチモーダル拡張が進行中です。

  • 画像リソース:スクリーンショット、チャート、デザインファイルをAIに直接配信
  • オーディオ処理:会議の録音をAIが分析
  • ビデオ分析:セキュリティカメラの映像をAIがモニタリング

9-3. MCP Registry(中央サーバーレジストリ)

npmやPyPIのように、MCPサーバーを中央で検索・インストールできる公式レジストリが構築されています。現在はSmitheryがこの役割を果たしていますが、より標準化されたレジストリが登場する予定です。

9-4. Enterprise MCP Gateway

企業環境ではMCPサーバーを中央で管理するゲートウェイが必要です:

  • アクセス制御:誰がどのMCPサーバーを使用できるか
  • 監査ログ:すべてのツール呼び出しを記録
  • コスト管理:API呼び出し回数の制限
  • セキュリティポリシー:機密データへのアクセス制御

9-5. Linux Foundation AAIFガバナンス

2025年末にMCPがLinux FoundationのAI and AI Foundation(AAIF)に寄贈されました。これはMCPのガバナンスが特定の企業(Anthropic)からオープンコミュニティに移行することを意味します。

期待される効果:

  • より透明なスペック発展プロセス
  • 多様な企業の参加促進
  • 相互運用性標準の強化
  • 認定プログラムの導入可能性

10. クイズ

これまで学んだ内容を確認しましょう。

Q1. MCPの基盤プロトコルは何で、メッセージの3種類は?

正解: MCPはJSON-RPC 2.0ベースです。メッセージの種類は以下の3つです:

  1. Request: idを持つリクエスト(レスポンスを期待)
  2. Response: リクエストに対する結果を返却
  3. Notification: idを持たない一方向の通知
Q2. MCPの2つのTransport方式とそれぞれの主な使用場面は?

正解:

  1. stdio: ローカルプロセスのstdin/stdoutで通信。Claude DesktopやClaude Codeでローカルサーバーを実行する際に使用。
  2. Streamable HTTP: HTTP POSTとSSEで通信。リモートサーバーデプロイ、マルチテナント環境で使用。
Q3. MCPの3つのコアプリミティブ(Resources、Tools、Prompts)の違いは?

正解:

  • Resources: URIベースの読み取り専用データ。アプリケーションがコンテキストとして活用。(ユーザー制御)
  • Tools: AIモデルが呼び出す実行可能な関数。JSON Schemaでパラメータ定義。副作用あり得る。(モデル制御)
  • Prompts: 事前定義された再利用可能なプロンプトテンプレート。(ユーザー制御)
Q4. FastMCPでMCPツール(Tool)を定義する最もシンプルな方法は?

正解: @mcp.tool()デコレーターを使用します。関数のdocstringがツールの説明になり、型ヒントがJSON Schemaに自動変換されます。

from fastmcp import FastMCP
mcp = FastMCP("My Server")

@mcp.tool()
def greet(name: str) -> str:
    """Greet someone by name."""
    return f"Hello, {name}!"
Q5. MCPサーバーセキュリティで最も重要な5つの原則は?

正解:

  1. 入力検証: Zod/Pydanticですべてのパラメータを検証
  2. 最小権限: 必要な権限のみ付与(読み取り専用DBアカウントなど)
  3. レートリミット: API呼び出し回数を制限
  4. シークレット管理: 環境変数を使用、ハードコーディング禁止
  5. 監査ログ: すべてのツール呼び出しを記録

加えて、サンドボックス化(Docker分離)とOAuth 2.0認証も重要です。


11. 参考資料

  1. MCP公式仕様 - Model Context Protocolの全スペック
  2. MCP公式ドキュメント - 入門ガイド、チュートリアル
  3. MCP GitHubリポジトリ - SDK、リファレンスサーバー
  4. FastMCPドキュメント - Python FastMCPフレームワーク
  5. MCP TypeScript SDK - TypeScript SDK
  6. MCP Python SDK - Python SDK
  7. Awesome MCP Servers - コミュニティMCPサーバーリスト
  8. Smithery - MCPサーバーレジストリ
  9. MCP Inspector - MCPサーバーテストツール
  10. Claude Desktop MCP設定ガイド - Claude Desktop連携
  11. Claude Code MCP設定 - Claude Code連携
  12. Grafana MCP Server - Grafana公式MCPサーバー
  13. GitHub MCP Server - GitHub公式MCPサーバー
  14. PostgreSQL MCP Server - PostgreSQL MCP
  15. Playwright MCP Server - Microsoft Playwright MCP
  16. Slack MCP Server - Slack MCP
  17. Filesystem MCP Server - ファイルシステムMCP
  18. Cloudflare MCP Workers - Cloudflareデプロイガイド
  19. MCP OAuth 2.0仕様 - 認証スペック
  20. Linux Foundation AAIF - MCPガバナンス
  21. Anthropic MCPブログ - MCP発表ブログ
  22. OpenAI MCPサポート発表 - OpenAIのMCP採用
  23. Google MCPサポート - GoogleのMCPサポート