【第2回】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 は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完全ガイド2026
料金・使い方・他ツールとの使い分けを実運用者が解説
Claude Code Skills 入門
自作スキルで開発効率を2倍にする実装ガイド
Claude Code トークン節約術トップ10
月額コストを半分以下にした実践テクニック集
Claude Code で副業を始める 6ステップ — 無料
環境構築から最初の売上まで、週1配信で学べる無料メール講座。登録で「収益化チェックリスト 15 項目」もプレゼント。
副業6ステップ講座(無料)
この記事が役に立ったらシェア