コンテンツにスキップ

MCP Server実装パターン:Claude Codeで作る5分間の実践ガイド

この記事の対象者

  • MCP Serverの実装方法を具体的に知りたい中級者

この記事のポイント

  1. 最小構成のMCP Serverを動作させる
  2. ツール定義とレスポンス処理を実装
  3. Claude Codeから実際に呼び出して動作確認

なぜこの問題が今重要か

MCP(Model Context Protocol)は、AIエージェントと外部システムを接続する標準プロトコルとして急速に普及中。しかし実装例が少なく、多くの開発者が最初の一歩で躓いている。本記事では動作する最小コードで即座に体験可能。

解決ステップ概要

ステップ内容到達指標
0Claude CodeでのMCP接続・管理設定方法とスコープの理解
1最小MCP Server作成server.pyが起動
2ツール定義追加list_toolsレスポンス
3Claude Code接続実際の呼び出し成功
4出力制限・安全対策大規模出力の制御
5ツール設計ベストプラクティス本番品質のツール実装

ステップ0: Claude CodeでのMCP接続・管理・制限

実装に入る前に、Claude CodeにおけるMCPの接続方法・スコープ・管理コマンドを理解する。

トランスポートタイプ

MCPは3つのトランスポートタイプをサポートする:

タイプ用途備考
HTTP (Streamable HTTP)リモートサーバー(推奨)リモート接続の推奨方式
SSE (Server-Sent Events)リモートサーバー(レガシー)非推奨--transport sseで明示指定が必要
stdioローカルプロセスローカル開発・テストに最適

MCPスコープ

MCPサーバーの設定は3つのスコープで管理される:

スコープ設定場所用途
local(デフォルト).claude/ 配下(プロジェクトディレクトリ内)個人用・gitignore推奨
project.mcp.json(プロジェクトルート)チーム共有・gitコミット対象
user~/.claude.json全プロジェクト共通のグローバル設定

Claude DesktopとClaude Codeの設定パスの違い

Claude Desktopは ~/.config/claude/claude_desktop_config.json を使用するが、Claude Codeでは使用しない。Claude Codeでは claude mcp add コマンドまたは .mcp.json / ~/.claude.json を編集する。

MCP管理コマンド

# サーバーの追加(stdioトランスポート)
claude mcp add my-server -s local -- node /path/to/server.js

# サーバーの追加(HTTPトランスポート - リモート)
claude mcp add my-remote-server https://api.example.com/mcp

# JSON形式で追加(複雑な設定向け)
claude mcp add-json my-server '{"command":"node","args":["/path/to/server.js"]}'

# サーバー一覧
claude mcp list

# サーバー詳細確認
claude mcp get my-server

# サーバー削除
claude mcp remove my-server

# Claude Desktopから設定をインポート
claude mcp add-from-claude-desktop

# Claude Code自体をMCPサーバーとして公開
claude mcp serve

セッション内でのMCP管理

Claude Codeのインタラクティブセッション内で /mcp コマンドを使用すると、接続中のMCPサーバーのステータス確認やOAuth認証フローの実行が可能。

.mcp.json での環境変数展開

プロジェクトスコープの .mcp.json では環境変数の展開をサポートする:

{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["/path/to/server.js"],
      "env": {
        "API_KEY": "${API_KEY}",
        "DB_HOST": "${DB_HOST:-localhost}"
      }
    }
  }
}
  • ${VAR} — 環境変数 VAR の値を展開
  • ${VAR:-default}VAR が未設定の場合に default を使用

ステップ1: 最小MCP Server作成

TypeScript版の基本実装。Node.js環境で即座に動作:

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const server = new Server({
  name: 'minimal-mcp',
  version: '1.0.0',
}, {
  capabilities: { tools: {} }
});

const transport = new StdioServerTransport();
await server.connect(transport);

ステップ2: ツール定義とハンドラ実装

実用的なツールを1つ追加。ファイルシステム操作の例:

server.setRequestHandler('tools/list', async () => ({
  tools: [{
    name: 'read_file',
    description: 'Read file contents',
    inputSchema: {
      type: 'object',
      properties: {
        path: { type: 'string' }
      },
      required: ['path']
    }
  }]
}));

server.setRequestHandler('tools/call', async (req) => {
  const { path } = req.params.arguments;
  const content = await fs.readFile(path, 'utf-8');
  return { content: [{ type: 'text', text: content }] };
});

ステップ3: Claude Code設定と接続

方法A: CLIコマンドで追加(推奨)

# ローカルスコープ(デフォルト)に追加
claude mcp add minimal-mcp -- node /path/to/server.js

# プロジェクトスコープに追加(チーム共有向け)
claude mcp add minimal-mcp -s project -- node /path/to/server.js

方法B: .mcp.json を直接編集(プロジェクトスコープ)

プロジェクトルートに .mcp.json を作成:

{
  "mcpServers": {
    "minimal-mcp": {
      "command": "node",
      "args": ["/path/to/server.js"]
    }
  }
}

方法C: ~/.claude.json を編集(ユーザースコープ)

~/.claude.json に追加すると全プロジェクトで利用可能:

{
  "mcpServers": {
    "minimal-mcp": {
      "command": "node",
      "args": ["/path/to/server.js"]
    }
  }
}

よくある間違い

~/.config/claude/claude_desktop_config.jsonClaude Desktop 用の設定ファイルであり、Claude Codeでは参照されない。Claude Codeを使用する場合は上記の方法A〜Cを使用すること。

設定後、Claude Codeを再起動するとツールが利用可能になる。

出力制限とセーフティ

MCPツールの出力が大きすぎるとコンテキストウィンドウを圧迫する。Claude Codeは以下の安全機構を備える:

閾値動作
10,000トークン警告メッセージが表示される
25,000トークン(デフォルト上限)出力が切り詰められる
  • 上限値は環境変数 MAX_MCP_OUTPUT_TOKENS で変更可能
  • MCPツール定義がコンテキストの 10%以上 を占める場合、ツール検索機能(ENABLE_TOOL_SEARCH)が自動的に有効化される

大規模出力への対処

大量のデータを返すツールは、ページネーションやフィルタリングを実装して出力量を制御することを推奨。詳細は次のセクション「ツールの粒度設計」を参照。

ツールの粒度設計(ベストプラクティス)

MCP Serverで本番品質のツールを設計するための指針:

ページネーション

大量の結果をページ単位で分割して返す:

server.setRequestHandler('tools/call', async (req) => {
  const { query, page = 1, pageSize = 20 } = req.params.arguments;
  const allResults = await searchDatabase(query);
  const start = (page - 1) * pageSize;
  const paged = allResults.slice(start, start + pageSize);
  return {
    content: [{
      type: 'text',
      text: JSON.stringify({
        results: paged,
        totalCount: allResults.length,
        page,
        totalPages: Math.ceil(allResults.length / pageSize)
      })
    }]
  };
});

フィルタリング

フィルタパラメータを受け付けて出力量を削減:

// ツール定義でフィルタパラメータを宣言
{
  name: 'list_issues',
  description: 'List issues with optional filters',
  inputSchema: {
    type: 'object',
    properties: {
      status: { type: 'string', enum: ['open', 'closed', 'all'] },
      assignee: { type: 'string' },
      limit: { type: 'number', default: 10 }
    }
  }
}

出力の要約

大規模データセットの場合は生データではなくサマリーを返す:

// 全件の生データを返す代わりにサマリーを返す
return {
  content: [{
    type: 'text',
    text: JSON.stringify({
      summary: `Found ${results.length} items`,
      topItems: results.slice(0, 5),
      stats: { total: results.length, avgSize: avg }
    })
  }]
};

エラーハンドリング

構造化されたエラーメッセージを返す:

server.setRequestHandler('tools/call', async (req) => {
  try {
    const result = await performAction(req.params.arguments);
    return { content: [{ type: 'text', text: JSON.stringify(result) }] };
  } catch (error) {
    return {
      content: [{
        type: 'text',
        text: JSON.stringify({
          error: true,
          code: error.code || 'UNKNOWN_ERROR',
          message: error.message,
          suggestion: 'Check the input parameters and retry'
        })
      }],
      isError: true
    };
  }
});

MCP Resources と Prompts

MCPはツール以外にも ResourcesPrompts という機能を提供する。

Resources(リソース)

MCPサーバーが公開するリソース(ファイル、データなど)を、プロンプト内から直接参照できる:

@server-name:protocol://resource/path

例えば、docs-server が公開する API ドキュメントを参照する場合:

@docs-server:file:///api/reference を確認して、認証エンドポイントの仕様を教えてください

Prompts(プロンプトテンプレート)

MCPサーバーが定義したプロンプトテンプレートは、Claude Codeのスラッシュコマンドとして利用可能:

/mcp__servername__promptname

例えば、code-review サーバーが review プロンプトを公開している場合:

/mcp__code-review__review

OAuth認証(リモートMCPサーバー)

リモートMCPサーバーは OAuth 2.0 認証をサポートする。

  • セッション内で /mcp コマンドを使用してOAuth認証フローを開始可能
  • 事前にOAuthクレデンシャルを設定する場合は --client-id--client-secret フラグを使用:
claude mcp add my-oauth-server \
  --transport http \
  --client-id "your-client-id" \
  --client-secret "your-client-secret" \
  https://api.example.com/mcp

エンタープライズ向け Managed MCP

組織で統一的にMCPサーバーを管理するための仕組み。

managed-mcp.json のデプロイ

システムディレクトリに managed-mcp.json を配置して、全ユーザーに適用されるMCPサーバー設定を一元管理できる。

ポリシー制御

ポリシー説明
allowedMcpServers使用を許可するMCPサーバーのリスト
deniedMcpServers使用を禁止するMCPサーバーのリスト

Denyリストの優先度

deniedMcpServers(拒否リスト)は 絶対的に優先 される。allowedリストとdeniedリストの両方に含まれるサーバーは拒否される。

よくある落とし穴と対処

症状原因即時対処
Server not foundパス設定ミス絶対パスを使用
Permission denied実行権限なしchmod +x server.js
Connection timeoutポート競合stdioトランスポート使用
設定が反映されない設定ファイルの場所が間違いClaude Codeは .mcp.json または ~/.claude.json を使用(claude_desktop_config.json ではない)
出力が切り詰められるMCP出力がトークン上限超過MAX_MCP_OUTPUT_TOKENS を調整、またはページネーション実装
ツールが多すぎて遅いツール定義がコンテキストの10%以上ENABLE_TOOL_SEARCH が自動有効化される
詳細設定(Python版実装) Python版MCPサーバーの実装例:
from mcp.server import Server, StdioServerTransport
import asyncio

server = Server("minimal-mcp")

@server.list_tools()
async def list_tools():
    return [{"name": "read_file", "description": "Read file"}]

@server.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "read_file":
        with open(arguments["path"]) as f:
            return {"content": f.read()}

async def main():
    transport = StdioServerTransport()
    await server.connect(transport)

asyncio.run(main())

次に読む(関連記事)