メインコンテンツへスキップ
Tech Playground
AI開発

Claude Code MCP Server カスタムツール統合完全チュートリアル|プロトコル設計から本番デプロイまでの段階的実装【2026年7月最新】

Claude Code の MCP (Model Context Protocol) サーバーでカスタムツールを統合する完全ガイド。プロトコル設計・TypeScript実装・本番デプロイまで段階的に解説。2026年7月最新版。

約11分で読めます

Claude Code の MCP (Model Context Protocol) サーバー機能は、2026年5月のバージョン1.4.0で大幅に拡張され、カスタムツールの統合が大幅に簡略化されました。本記事では、MCP サーバーの実装から本番デプロイまでの完全な手順を、実際に動作するコード例とともに解説します。

MCP は Claude Code がサードパーティツール・API・データベースと通信するための標準プロトコルです。2026年7月現在、Gmail・POST.・Slack・Linear など主要サービスの公式 MCP サーバーが提供されていますが、独自のビジネスロジック・社内ツール・プロプライエタリAPIを統合するには、カスタム MCP サーバーの実装が必要です。

MCP プロトコルアーキテクチャの理解

MCP は JSON-RPC 2.0 をベースとした双方向通信プロトコルです。Claude Code (クライアント) と MCP サーバー間で、ツール定義・実行リクエスト・結果返却を行います。

以下のシーケンス図は、Claude Code が MCP サーバーとやり取りする際の典型的な通信フローを示しています。

sequenceDiagram
    participant User as ユーザー
    participant CC as Claude Code
    participant MCP as MCPサーバー
    participant API as 外部API/DB
    
    User->>CC: タスク実行依頼
    CC->>MCP: initialize (プロトコルバージョン交換)
    MCP-->>CC: initialized (サーバー情報返却)
    CC->>MCP: tools/list (利用可能ツール一覧要求)
    MCP-->>CC: ツール定義リスト (JSON Schema含む)
    CC->>CC: ツール選択・パラメータ生成
    CC->>MCP: tools/call (ツール実行リクエスト)
    MCP->>API: 外部API呼び出し
    API-->>MCP: レスポンス
    MCP->>MCP: データ整形
    MCP-->>CC: 実行結果 (構造化データ)
    CC->>User: タスク完了報告

MCP サーバーは以下の責務を持ちます。

  • ツール定義の提供: 各ツールの名前・説明・パラメータスキーマを JSON Schema 形式で公開
  • リクエスト検証: Claude Code からのパラメータを検証し、不正な入力を拒否
  • 外部システム連携: API 呼び出し・データベースクエリ・ファイル操作などを実行
  • エラーハンドリング: 外部システムの障害を適切にハンドリングし、エラー情報を返却

2026年5月のアップデートで、MCP SDK (@anthropics/mcp-sdk) がバージョン2.0にアップデートされ、以下の機能が追加されました。

  • Streaming Responses: 大量データを段階的に返却する機能
  • Resource Metadata: ツールが操作するリソース(ファイル・DB レコード等)のメタデータ提供
  • Session Management: 複数ターンに渡るコンテキスト保持機能

TypeScript による MCP サーバーの基本実装

最もシンプルな MCP サーバーを実装してみましょう。ここでは、現在時刻を返す get_current_time ツールを例にします。

まず、プロジェクトをセットアップします。

mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @anthropics/mcp-sdk zod
npm install -D typescript @types/node tsx

tsconfig.json を作成します。

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"]
}

src/index.ts に MCP サーバーの実装を記述します。

import { Server } from '@anthropics/mcp-sdk/server/index.js';
import { StdioServerTransport } from '@anthropics/mcp-sdk/server/stdio.js';
import { z } from 'zod';

// ツールパラメータのスキーマ定義
const GetCurrentTimeSchema = z.object({
  timezone: z.string().optional().describe('タイムゾーン (例: Asia/Tokyo)'),
  format: z.enum(['iso', 'unix', 'readable']).optional().describe('出力形式')
});

// MCP サーバーインスタンス作成
const server = new Server(
  {
    name: 'my-time-server',
    version: '1.0.0'
  },
  {
    capabilities: {
      tools: {}
    }
  }
);

// ツール定義の登録
server.setRequestHandler('tools/list', async () => ({
  tools: [
    {
      name: 'get_current_time',
      description: '現在時刻を取得します。タイムゾーンと出力形式を指定できます。',
      inputSchema: {
        type: 'object',
        properties: {
          timezone: {
            type: 'string',
            description: 'タイムゾーン (例: Asia/Tokyo)',
            default: 'UTC'
          },
          format: {
            type: 'string',
            enum: ['iso', 'unix', 'readable'],
            description: '出力形式',
            default: 'iso'
          }
        }
      }
    }
  ]
}));

// ツール実行ハンドラの登録
server.setRequestHandler('tools/call', async (request) => {
  const { name, arguments: args } = request.params;

  if (name === 'get_current_time') {
    // パラメータ検証
    const validated = GetCurrentTimeSchema.parse(args);
    const timezone = validated.timezone || 'UTC';
    const format = validated.format || 'iso';

    // 現在時刻取得
    const now = new Date();
    const formatter = new Intl.DateTimeFormat('ja-JP', {
      timeZone: timezone,
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
      timeZoneName: 'short'
    });

    let result: string;
    if (format === 'iso') {
      result = now.toISOString();
    } else if (format === 'unix') {
      result = Math.floor(now.getTime() / 1000).toString();
    } else {
      result = formatter.format(now);
    }

    return {
      content: [
        {
          type: 'text',
          text: `現在時刻 (${timezone}): ${result}`
        }
      ]
    };
  }

  throw new Error(`Unknown tool: ${name}`);
});

// サーバー起動
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('MCP Server started');
}

main().catch(console.error);

このコードでは、Zod を使ってパラメータ検証を行い、タイムゾーンと出力形式を柔軟に指定できるようにしています。

package.json に実行スクリプトを追加します。

{
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsx src/index.ts"
  }
}

ビルドして実行します。

npm run build
npm start

このサーバーは標準入出力で MCP プロトコルを処理します。Claude Code から直接呼び出すには、設定ファイルに登録する必要があります。

Claude Code への MCP サーバー登録と動作確認

実装した MCP サーバーを Claude Code に登録します。~/.claude/settings.json を編集します。

{
  "mcpServers": {
    "my-time-server": {
      "command": "node",
      "args": ["/path/to/my-mcp-server/dist/index.js"],
      "env": {}
    }
  }
}

Claude Code を再起動すると、MCP サーバーが自動的にロードされます。ツールが正しく認識されているか確認するには、以下のコマンドを実行します。

claude /tasks

出力に my-time-server が表示されていれば成功です。

Claude Code のチャットで以下のように入力して動作確認します。

現在の東京の時刻を教えてください

Claude Code は自動的に get_current_time ツールを呼び出し、結果を返却します。

現在時刻 (Asia/Tokyo): 2026-07-15 14:32:45 JST

以下のフローチャートは、Claude Code がユーザー入力から適切なツールを選択・実行するまでの意思決定プロセスを示しています。

flowchart TD
    A[ユーザー入力] --> B{ツール呼び出しが必要?}
    B -->|No| C[直接回答生成]
    B -->|Yes| D[利用可能ツール一覧取得]
    D --> E{該当ツールが存在?}
    E -->|No| F[ツール無しで回答試行]
    E -->|Yes| G[ツール選択]
    G --> H[パラメータ生成]
    H --> I{パラメータ検証}
    I -->|Invalid| J[エラー処理]
    I -->|Valid| K[ツール実行リクエスト]
    K --> L[MCPサーバー処理]
    L --> M{実行成功?}
    M -->|Error| N[エラーメッセージ返却]
    M -->|Success| O[結果をコンテキストに追加]
    O --> P[回答生成]
    C --> Q[ユーザーに返却]
    F --> Q
    J --> Q
    N --> Q
    P --> Q

このフローチャートから分かるように、Claude Code は複数のツールが登録されている場合でも、コンテキストから最も適切なツールを自動選択します。

複数ツールの統合と高度な機能実装

実際のユースケースでは、複数のツールを連携させる必要があります。以下は、GitHub API を統合した MCP サーバーの実装例です。

import { Server } from '@anthropics/mcp-sdk/server/index.js';
import { StdioServerTransport } from '@anthropics/mcp-sdk/server/stdio.js';
import { Octokit } from '@octokit/rest';
import { z } from 'zod';

// GitHub API クライアント
const octokit = new Octokit({
  auth: process.env.GITHUB_TOKEN
});

// スキーマ定義
const ListIssuesSchema = z.object({
  owner: z.string().describe('リポジトリオーナー'),
  repo: z.string().describe('リポジトリ名'),
  state: z.enum(['open', 'closed', 'all']).optional().describe('Issue状態')
});

const CreateIssueSchema = z.object({
  owner: z.string(),
  repo: z.string(),
  title: z.string().describe('Issueタイトル'),
  body: z.string().optional().describe('Issue本文'),
  labels: z.array(z.string()).optional().describe('ラベル一覧')
});

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

// ツール一覧
server.setRequestHandler('tools/list', async () => ({
  tools: [
    {
      name: 'github_list_issues',
      description: 'GitHub リポジトリの Issue 一覧を取得します',
      inputSchema: {
        type: 'object',
        properties: {
          owner: { type: 'string', description: 'リポジトリオーナー' },
          repo: { type: 'string', description: 'リポジトリ名' },
          state: {
            type: 'string',
            enum: ['open', 'closed', 'all'],
            description: 'Issue状態',
            default: 'open'
          }
        },
        required: ['owner', 'repo']
      }
    },
    {
      name: 'github_create_issue',
      description: 'GitHub リポジトリに新しい Issue を作成します',
      inputSchema: {
        type: 'object',
        properties: {
          owner: { type: 'string' },
          repo: { type: 'string' },
          title: { type: 'string', description: 'Issueタイトル' },
          body: { type: 'string', description: 'Issue本文' },
          labels: {
            type: 'array',
            items: { type: 'string' },
            description: 'ラベル一覧'
          }
        },
        required: ['owner', 'repo', 'title']
      }
    }
  ]
}));

// ツール実行ハンドラ
server.setRequestHandler('tools/call', async (request) => {
  const { name, arguments: args } = request.params;

  try {
    if (name === 'github_list_issues') {
      const params = ListIssuesSchema.parse(args);
      const { data } = await octokit.issues.listForRepo({
        owner: params.owner,
        repo: params.repo,
        state: params.state || 'open'
      });

      const issueList = data.map(issue => ({
        number: issue.number,
        title: issue.title,
        state: issue.state,
        url: issue.html_url,
        created_at: issue.created_at
      }));

      return {
        content: [
          {
            type: 'text',
            text: `取得した Issue 数: ${issueList.length}\n\n` +
                  JSON.stringify(issueList, null, 2)
          }
        ]
      };
    }

    if (name === 'github_create_issue') {
      const params = CreateIssueSchema.parse(args);
      const { data } = await octokit.issues.create({
        owner: params.owner,
        repo: params.repo,
        title: params.title,
        body: params.body,
        labels: params.labels
      });

      return {
        content: [
          {
            type: 'text',
            text: `Issue を作成しました\nURL: ${data.html_url}\nNumber: #${data.number}`
          }
        ]
      };
    }

    throw new Error(`Unknown tool: ${name}`);
  } catch (error) {
    // エラーハンドリング
    if (error instanceof z.ZodError) {
      return {
        content: [
          {
            type: 'text',
            text: `パラメータ検証エラー: ${error.errors.map(e => e.message).join(', ')}`
          }
        ],
        isError: true
      };
    }

    return {
      content: [
        {
          type: 'text',
          text: `エラー: ${error.message}`
        }
      ],
      isError: true
    };
  }
});

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('GitHub MCP Server started');
}

main().catch(console.error);

この実装では、GitHub API への認証・複数ツールの定義・エラーハンドリングが含まれています。環境変数 GITHUB_TOKEN を設定して実行します。

export GITHUB_TOKEN=ghp_xxxxxxxxxxxx
npm run dev

Claude Code から以下のように使用できます。

anthropics/claude-code リポジトリの open な Issue を一覧表示してください
anthropics/claude-code リポジトリに「MCP サーバーのドキュメント改善」というタイトルで Issue を作成してください

以下のダイアグラムは、複数ツールを持つ MCP サーバーの構成を示しています。

graph TD
    A[Claude Code] --> B[MCP Server]
    B --> C[Tool Registry]
    C --> D[github_list_issues]
    C --> E[github_create_issue]
    C --> F[github_add_comment]
    D --> G[GitHub REST API]
    E --> G
    F --> G
    B --> H[Error Handler]
    H --> I[Zod Validation Error]
    H --> J[API Error]
    H --> K[Network Error]
    B --> L[Response Formatter]
    L --> M[Structured Data]
    L --> N[Error Message]

このように、ツールレジストリで複数のツールを管理し、共通のエラーハンドリング・レスポンスフォーマット機能を提供します。

本番環境への MCP サーバーデプロイ

本番環境では、以下の要件を満たす必要があります。

  • セキュリティ: API キーの安全な管理・アクセス制御
  • スケーラビリティ: 複数ユーザーからの同時リクエスト処理
  • 監視: ログ記録・エラートラッキング
  • 可用性: 障害時の自動復旧

2026年7月現在、MCP サーバーのデプロイ方法として以下のオプションがあります。

オプション1: ローカル実行(開発・個人利用向け)

最もシンプルな方法は、各ユーザーのマシンで MCP サーバーをローカル実行することです。

{
  "mcpServers": {
    "github-server": {
      "command": "node",
      "args": ["/path/to/github-mcp-server/dist/index.js"],
      "env": {
        "GITHUB_TOKEN": "ghp_xxxxxxxxxxxx"
      }
    }
  }
}

メリット:

  • セットアップが簡単
  • レイテンシが低い
  • API キーが外部に漏れない

デメリット:

  • 各ユーザーが個別にセットアップする必要がある
  • 集中管理ができない

オプション2: HTTP/WebSocket サーバーとして公開(チーム利用向け)

MCP SDK 2.0 は HTTP/WebSocket トランスポートをサポートしています。サーバーを常時起動し、複数のクライアントから接続できるようにします。

import { Server } from '@anthropics/mcp-sdk/server/index.js';
import { WebSocketServerTransport } from '@anthropics/mcp-sdk/server/websocket.js';
import WebSocket from 'ws';

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

// ツール定義・ハンドラの登録(前述のコードと同じ)

// WebSocket サーバー起動
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', async (ws) => {
  console.error('Client connected');
  const transport = new WebSocketServerTransport(ws);
  await server.connect(transport);
});

console.error('MCP Server listening on ws://localhost:8080');

Claude Code の設定ファイルで WebSocket 接続を指定します。

{
  "mcpServers": {
    "github-server": {
      "transport": "websocket",
      "url": "ws://mcp-server.example.com:8080",
      "auth": {
        "type": "bearer",
        "token": "your-auth-token"
      }
    }
  }
}

メリット:

  • 複数ユーザーで共有可能
  • サーバー側でログ・監視を一元管理できる
  • スケールアウト可能

デメリット:

  • サーバーインフラが必要
  • 認証・セキュリティの実装が必要
  • ネットワーク遅延が発生する

オプション3: Docker コンテナ化(エンタープライズ向け)

本番運用では、Docker コンテナ化が推奨されます。

Dockerfile:

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY dist ./dist

ENV NODE_ENV=production

CMD ["node", "dist/index.js"]

docker-compose.yml:

version: '3.8'

services:
  mcp-server:
    build: .
    ports:
      - "8080:8080"
    environment:
      - GITHUB_TOKEN=${GITHUB_TOKEN}
      - LOG_LEVEL=info
    restart: unless-stopped
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

デプロイ:

docker-compose up -d

Kubernetes での運用も可能です。以下は簡易的な Deployment 例です。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mcp-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mcp-server
  template:
    metadata:
      labels:
        app: mcp-server
    spec:
      containers:
      - name: mcp-server
        image: my-registry/mcp-server:latest
        ports:
        - containerPort: 8080
        env:
        - name: GITHUB_TOKEN
          valueFrom:
            secretKeyRef:
              name: github-secrets
              key: token
---
apiVersion: v1
kind: Service
metadata:
  name: mcp-server
spec:
  selector:
    app: mcp-server
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: LoadBalancer

以下のダイアグラムは、本番環境での MCP サーバーの構成例を示しています。

graph TD
    A[Claude Code Client 1] --> B[Load Balancer]
    C[Claude Code Client 2] --> B
    D[Claude Code Client 3] --> B
    B --> E[MCP Server Pod 1]
    B --> F[MCP Server Pod 2]
    B --> G[MCP Server Pod 3]
    E --> H[Secrets Manager]
    F --> H
    G --> H
    H --> I[GitHub API Token]
    H --> J[Database Credentials]
    E --> K[External APIs]
    F --> K
    G --> K
    E --> L[Monitoring/Logs]
    F --> L
    G --> L

ロードバランサー配下で複数の MCP サーバーインスタンスを起動し、シークレット管理・監視ログを一元化します。

セキュリティとパフォーマンスの最適化

本番運用では、セキュリティとパフォーマンスの両立が重要です。

認証・認可の実装

MCP サーバーに認証機能を追加します。

import { Server } from '@anthropics/mcp-sdk/server/index.js';
import jwt from 'jsonwebtoken';

const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';

// リクエスト前の認証チェック
server.setRequestHandler('tools/call', async (request, extra) => {
  // カスタムヘッダーから JWT トークンを取得
  const token = extra?.headers?.['authorization']?.replace('Bearer ', '');
  
  if (!token) {
    throw new Error('Unauthorized: Missing token');
  }

  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    console.error(`Authenticated user: ${decoded.sub}`);
  } catch (error) {
    throw new Error('Unauthorized: Invalid token');
  }

  // 実際のツール実行処理
  // ...
});

レート制限の実装

DoS 攻撃や過度な API 呼び出しを防ぐため、レート制限を実装します。

import rateLimit from 'express-rate-limit';
import express from 'express';

const app = express();

const limiter = rateLimit({
  windowMs: 60 * 1000, // 1分
  max: 100, // 最大100リクエスト
  message: 'Too many requests, please try again later.'
});

app.use('/mcp', limiter);

キャッシング戦略

頻繁にアクセスされるデータはキャッシュします。

import NodeCache from 'node-cache';

const cache = new NodeCache({ stdTTL: 600 }); // 10分間キャッシュ

server.setRequestHandler('tools/call', async (request) => {
  const { name, arguments: args } = request.params;

  if (name === 'github_list_issues') {
    const cacheKey = `issues:${args.owner}:${args.repo}:${args.state}`;
    const cached = cache.get(cacheKey);
    
    if (cached) {
      console.error('Cache hit');
      return { content: [{ type: 'text', text: cached }] };
    }

    // API 呼び出し
    const { data } = await octokit.issues.listForRepo(args);
    const result = JSON.stringify(data, null, 2);
    
    cache.set(cacheKey, result);
    return { content: [{ type: 'text', text: result }] };
  }
});

ロギング・監視

構造化ログを記録し、エラートラッキングを実装します。

import winston from 'winston';

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

server.setRequestHandler('tools/call', async (request) => {
  const startTime = Date.now();
  
  try {
    logger.info('Tool call started', {
      tool: request.params.name,
      args: request.params.arguments
    });

    const result = await executeToolLogic(request);
    
    logger.info('Tool call completed', {
      tool: request.params.name,
      duration: Date.now() - startTime
    });

    return result;
  } catch (error) {
    logger.error('Tool call failed', {
      tool: request.params.name,
      error: error.message,
      stack: error.stack
    });
    throw error;
  }
});

まとめ

本記事では、Claude Code の MCP サーバーを実装し、本番環境にデプロイするまでの完全な手順を解説しました。

重要なポイント:

  • MCP は JSON-RPC 2.0 ベースの双方向プロトコルで、ツール定義・実行・結果返却を標準化
  • TypeScript + MCP SDK で数十行のコードから実装可能
  • Zod による型安全なパラメータ検証が推奨される
  • 本番環境では WebSocket または HTTP トランスポートを使用し、認証・レート制限・キャッシュを実装
  • Docker/Kubernetes によるコンテナ化で、スケーラビリティと可用性を確保
  • 2026年5月リリースの MCP SDK 2.0 で Streaming Responses・Session Management が追加され、より高度な統合が可能に

Claude Code の MCP エコシステムは急速に成長しており、2026年7月時点で公式サーバーは20種類以上、コミュニティサーバーは100種類以上が公開されています。独自のビジネスロジックを統合することで、AI 駆動開発のワークフローを完全に自動化できます。

参考リンク

#Claude Code #MCP #カスタムツール #開発効率化 #プロトコル実装
シェア: