MCP Server実装パターン:Claude Codeで作る5分間の実践ガイド¶
この記事の対象者
- MCP Serverの実装方法を具体的に知りたい中級者
この記事のポイント¶
- 最小構成のMCP Serverを動作させる
- ツール定義とレスポンス処理を実装
- Claude Codeから実際に呼び出して動作確認
なぜこの問題が今重要か¶
MCP(Model Context Protocol)は、AIエージェントと外部システムを接続する標準プロトコルとして急速に普及中。しかし実装例が少なく、多くの開発者が最初の一歩で躓いている。本記事では動作する最小コードで即座に体験可能。
解決ステップ概要¶
| ステップ | 内容 | 到達指標 |
|---|---|---|
| 0 | Claude CodeでのMCP接続・管理 | 設定方法とスコープの理解 |
| 1 | 最小MCP Server作成 | server.pyが起動 |
| 2 | ツール定義追加 | list_toolsレスポンス |
| 3 | Claude 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.json は Claude 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はツール以外にも Resources と Prompts という機能を提供する。
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())