Claude Code MCP サーバー構築ガイド — ゼロから作る実用MCPの全コード
Claude Code を使い込むほど、「ローカルの DB を直接見せたい」「社内 API を叩かせたい」「Notion や Linear と繋げたい」というニーズが出てきます。これを実現するのが MCP(Model Context Protocol)サーバー です。
公式の MCP サーバーをインストールするだけなら難しくないのですが、自分の業務に最適化した MCP を自作するとなると、急に情報が少なくなります。
この記事では、Claude Code から呼び出せる実用 MCP サーバーをゼロから作る手順を、コピペで動くコード付きで解説します。前回の Skills が「手順の自動化」だったのに対し、MCP は 「外部システムとの接続」 を担います。両方を組み合わせれば、Claude Code は本当の意味でのエージェントになります。
筆者は Claude Crew で月500万トークンを運用する中で、自作 MCP を3本動かしています。この記事ではその知見をもとに、最初の1本を15分で作る流れを共有します。
結論:MCP は「Claude が呼べる関数群」をプロセスとして起動する仕組み
結論から書きます。MCP サーバーとは 「stdin/stdout で JSON-RPC を喋るプロセス」 です。Claude Code が起動時にこのプロセスを立ち上げ、必要なときに関数を呼び出します。
最小構成は次の3要素だけです。
- TypeScript or Python のスクリプト(JSON-RPC のサーバー)
@modelcontextprotocol/sdkの利用(プロトコルの煩雑な部分を吸収).mcp.jsonでの Claude Code への登録
公式 SDK を使えば、関数定義は通常の TypeScript の関数とほぼ同じ感覚で書けます。実装の煩雑さは SDK が吸収してくれるので、本質的な「何ができる関数か」に集中できます。
MCP サーバーの構造
MCP サーバーが提供するものは大きく3種類あります。
| 種類 | 用途 | 例 |
|---|---|---|
| Tools | Claude が呼び出す関数 | get_db_schema, run_query, send_slack |
| Resources | Claude が参照するデータ | ファイル、API レスポンス、DB 行 |
| Prompts | 再利用可能なプロンプト | 「コードレビュー用」テンプレート |
最も使われるのは Tools です。この記事でも Tools の作り方に絞ります。
実装:プロジェクトのタスク管理 MCP を作る
題材として 「ローカルの tasks.json を読み書きするタスク管理 MCP」 を作ります。実用度が高く、実装も短いため最初の1本に最適です。
ステップ1:プロジェクト初期化(2分)
mkdir -p ~/mcp-servers/task-manager
cd ~/mcp-servers/task-manager
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D @types/node typescript tsx
tsconfig.json を作ります。
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true,
"outDir": "dist"
},
"include": ["src/**/*"]
}
package.json に "type": "module" を追加します。
ステップ2:サーバー本体を書く(8分)
src/index.ts を作成します。
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import fs from 'fs/promises';
import path from 'path';
const TASKS_FILE = path.join(process.cwd(), 'tasks.json');
// タスク型定義
const TaskSchema = z.object({
id: z.string(),
title: z.string(),
done: z.boolean(),
createdAt: z.string(),
});
type Task = z.infer<typeof TaskSchema>;
async function loadTasks(): Promise<Task[]> {
try {
const raw = await fs.readFile(TASKS_FILE, 'utf-8');
return z.array(TaskSchema).parse(JSON.parse(raw));
} catch {
return [];
}
}
async function saveTasks(tasks: Task[]): Promise<void> {
await fs.writeFile(TASKS_FILE, JSON.stringify(tasks, null, 2));
}
const server = new Server(
{ name: 'task-manager', version: '1.0.0' },
{ capabilities: { tools: {} } }
);
// ツール一覧を返す
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'list_tasks',
description: 'List all tasks in the current project',
inputSchema: { type: 'object', properties: {} },
},
{
name: 'add_task',
description: 'Add a new task to the current project',
inputSchema: {
type: 'object',
properties: { title: { type: 'string' } },
required: ['title'],
},
},
{
name: 'complete_task',
description: 'Mark a task as completed by ID',
inputSchema: {
type: 'object',
properties: { id: { type: 'string' } },
required: ['id'],
},
},
],
}));
// ツール呼び出しを処理
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const tasks = await loadTasks();
if (name === 'list_tasks') {
return { content: [{ type: 'text', text: JSON.stringify(tasks, null, 2) }] };
}
if (name === 'add_task') {
const newTask: Task = {
id: `t_${Date.now()}`,
title: String(args?.title ?? ''),
done: false,
createdAt: new Date().toISOString(),
};
await saveTasks([...tasks, newTask]);
return { content: [{ type: 'text', text: `Added: ${newTask.id}` }] };
}
if (name === 'complete_task') {
const updated = tasks.map((t) =>
t.id === args?.id ? { ...t, done: true } : t
);
await saveTasks(updated);
return { content: [{ type: 'text', text: `Completed: ${args?.id}` }] };
}
throw new Error(`Unknown tool: ${name}`);
});
const transport = new StdioServerTransport();
await server.connect(transport);
これで動くサーバーが完成です。100行未満で、Claude から呼べる3つの関数が実装できました。
ステップ3:Claude Code に登録する(3分)
プロジェクトルートに .mcp.json を作成します。
{
"mcpServers": {
"task-manager": {
"command": "tsx",
"args": ["/Users/yourname/mcp-servers/task-manager/src/index.ts"]
}
}
}
Claude Code を再起動するか、/mcp コマンドで MCP サーバーの状態を確認します。task-manager が connected になっていれば成功です。
ステップ4:使ってみる(2分)
Claude Code に次のように頼んでみます。
このプロジェクトのタスクを一覧表示して
Claude が自動的に list_tasks を呼び出します。タスクを追加したいときは「Ep03 を執筆 というタスクを追加して」と言うだけで、add_task が呼ばれます。
事例: Anthropic 公式(Code execution with MCP)の体験談
Anthropic は2025年に「Code execution with MCP: building more efficient AI agents」というエンジニアリング記事を公開し、MCP を「コード実行モデル」で使うことでエージェントの効率が大幅に向上することを示しました。 ポイントは、必要な情報をすべてプロンプトに貼り付ける従来方式ではなく、MCP サーバー側のツールとして関数化し、Claude が必要な時だけ呼び出す設計です。これによりコンテキスト消費が最適化されます。 公式 SDK は Python と TypeScript で月間 9,700万回以上ダウンロードされ、コミュニティ駆動の MCP レジストリも整備されています。
出典: Code execution with MCP / Connect Claude Code to tools via MCP / MCP Specification
落とし穴:最初にハマった3つのポイント
筆者が MCP 開発で実際にハマったポイントを共有します。
1. パスの相対指定が効かない
.mcp.json の args で相対パス(./src/index.ts)を書くと、Claude Code の起動ディレクトリ次第で動いたり動かなかったりします。必ず絶対パスで書くのが正解です。
2. console.log がプロトコルを壊す
MCP は stdin/stdout で JSON-RPC を喋るので、デバッグ用に console.log を入れるとプロトコルが壊れます。ログは必ず console.error(stderr)に出してください。
3. ツール名がスネークケースでないとトリガーしにくい
addTask のようなキャメルケースより、add_task のスネークケースのほうが Claude が呼びやすい傾向があります。OpenAI の Function Calling 時代からの慣習が引き継がれているようです。
で、どう稼ぐ? — MCP が個人開発の収益化を加速させる理由
MCP は単なる「便利機能」ではなく、個人開発のビジネスモデル自体を変える可能性 を持っています。
1. 自分専用の業務システムが Claude から使えるようになる
例えば、自作 SaaS の管理画面を MCP 化すれば、「先月の売上ランキングを教えて」「停滞しているユーザーに自動でフォローメールを送って」のような操作が、Claude Code 経由で実現できます。管理画面を作る時間が大幅に減り、開発時間を「売れる機能」に集中できます。
2. 受託開発でクライアント環境に組み込める
「貴社の社内ツールを Claude Code から操作可能にする MCP サーバーの開発」は、これからの受託開発の有望分野です。実装が比較的シンプルで、効果がわかりやすいので、提案が通りやすい領域です。
3. MCP 自体を SaaS 化する
公開された MCP サーバーを SaaS として提供する道もあります。例えば「Stripe の売上データをワンクリックで Claude に渡せる MCP」のような特化型サービスは、開発コストの割に価値が高く、月額課金モデルにしやすい題材です。
まとめ
MCP サーバー自作の重要ポイントは次の3点です。
- 構造はシンプル:stdin/stdout で JSON-RPC を喋るプロセスを書くだけ。SDK が煩雑な部分を吸収してくれる
- 実装は100行未満:最初の1本は15〜20分で作れる。作ってから「足りないツール」を都度追加していくのが定石
.mcp.jsonで絶対パス指定、console.log厳禁、ツール名はスネークケースの3点に注意
次のエピソードでは、Skills と MCP を組み合わせた サブエージェント設計パターン を解説します。複数の Claude プロセスを並列で走らせ、本業をしながら個人開発を進める「並列化戦略」の具体的な実装です。
次に読む
→ Claude Code で作る「売れる SaaS」開発フロー — Skill × MCP の統合設計 (¥800)
MCP サーバーを構築できるようになったら、次は「それで何を売るか」です。MCP と Skills を統合して 7 日で課金開始まで到達する具体的な開発フローを、有料記事で全公開しています。
Claude Code サブエージェント設計パターン — 並列タスクで時給を上げる実践ガイド
▸▾ // see all 12 episodes
- №01Claude Code Skills 入門 — 自作スキルで開発効率を2倍にする実装ガイド
- №02Claude Code MCP サーバー構築ガイド — ゼロから作る実用MCPの全コード
- №03Claude Code サブエージェント設計パターン — 並列タスクで時給を上げる実践ガイド
- №04Claude Code Hooks 完全ガイド — 保存時自動整形・コミット前チェックの実装
- №05Claude Code リモートサーバー運用術 — VPS常駐型 Claude Crew の構築¥500
- №06Claude Code で作る「売れるSaaS」開発フロー — Skill × MCP の統合設計¥800
- №07Claude Code トークン節約運用 — 月500万トークンを¥3万に抑える実測ログ
- №08Claude Code で GitHub Issue から PR まで自動化する実装ガイド
- №09Claude Code サブエージェント並列実行 — 本番で使った5パターン¥500
- №10Claude Code × Supabase で管理画面を30分で生成する Skill 実装ガイド
- №11Claude Code Hooks で事故を防ぐ — git secrets / 本番DB保護の実装¥500
- №12Claude Code 運用まとめ — 個人開発者のための「自走するClaude」完成形¥800