メインコンテンツへスキップ
← 記事一覧に戻る

Claude Code MCP サーバー構築ガイド — ゼロから作る実用MCPの全コード

12 min readClaude Code
#Claude Code#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要素だけです。

  1. TypeScript or Python のスクリプト(JSON-RPC のサーバー)
  2. @modelcontextprotocol/sdk の利用(プロトコルの煩雑な部分を吸収)
  3. .mcp.json での Claude Code への登録

公式 SDK を使えば、関数定義は通常の TypeScript の関数とほぼ同じ感覚で書けます。実装の煩雑さは SDK が吸収してくれるので、本質的な「何ができる関数か」に集中できます。

MCP サーバーの構造

MCP サーバーが提供するものは大きく3種類あります。

種類用途
ToolsClaude が呼び出す関数get_db_schema, run_query, send_slack
ResourcesClaude が参照するデータファイル、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-managerconnected になっていれば成功です。

ステップ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.jsonargs で相対パス(./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 日で課金開始まで到達する具体的な開発フローを、有料記事で全公開しています。

← 記事一覧に戻る