ブログ記事

MCP(Model Context Protocol)開発ガイド2025 - Claude用カスタムツールの作り方

AnthropicのModel Context Protocol(MCP)を使ってClaude用のカスタムツールを開発する方法を徹底解説。ローカルサーバーの構築から実装、デバッグまで実践的なガイドです。

ツール
MCP Claude AI Anthropic ツール開発
MCP(Model Context Protocol)開発ガイド2025 - Claude用カスタムツールの作り方のヒーロー画像

2024 年 11 月に Anthropic が発表した Model Context Protocol(MCP)は、ai アシスタントとデータソースを接続するためのオープンスタンダードです。MCP を使うことで、Claude を Google Drive、Slack、GitHub、データベースなど様々なシステムと統合できます。

本記事では、MCP 開発の基本から実践的なサーバー実装まで、エンジニア向けに包括的に解説します。

この記事で学べること

  • MCP の基本概念とアーキテクチャ
  • 開発環境のセットアップ方法
  • MCP サーバーの実装手順
  • デバッグとトラブルシューティング
  • 実践的な活用例とベストプラクティス

MCPとは何か?

Model Context Protocol(MCP)は、ai システムとデータソースを接続するための統一されたプロトコルです。従来、各データソースへの接続には個別の実装が必要でしたが、MCP によって標準化されたインターフェースでの接続が可能になりました。

MCPアーキテクチャ概要

チャートを読み込み中...

MCPの主要な特徴

MCPの主要な特徴と利点
特徴 説明 メリット
統一プロトコル 標準化されたインターフェース 開発の簡素化
双方向通信 サーバーとクライアント間の対話的なやり取り リアルタイムな情報取得
セキュア 認証・認可の仕組みを内包 安全なデータアクセス
拡張可能 カスタムツールの実装が容易 柔軟な機能拡張
オープンソース コミュニティ主導の開発 透明性と信頼性

開発環境のセットアップ

Node.js環境の準備

Node.js 18以上をインストール

Claude Desktopのインストール

最新版をダウンロード

MCP SDK導入

TypeScript/JavaScriptで開発開始

サーバー実装

カスタムMCPサーバーを作成

必要な環境

前提条件

  • Node.js 18 以上
  • Claude Desktop(ローカル MCP サーバー対応版)
  • TypeScript(推奨)または javascript
  • お好みのエディタ(Visual Studio Code 推奨)

プロジェクトの初期化

# プロジェクトディレクトリの作成
mkdir my-mcp-server
cd my-mcp-server

# package.jsonの初期化
npm init -y

# 必要なパッケージのインストール
npm install @modelcontextprotocol/sdk
npm install -D typescript @types/node tsx

MCPサーバーの実装

基本的なサーバー構造

// server.ts(基本実装)
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const server = new Server({
  name: 'my-simple-server',
  version: '1.0.0',
});

// ツールの登録
server.setRequestHandler('tools/list', async () => ({
  tools: [{
    name: 'hello',
    description: 'Say hello',
    inputSchema: {
      type: 'object',
      properties: {
        name: { type: 'string' }
      }
    }
  }]
}));

// サーバー起動
const transport = new StdioServerTransport();
await server.connect(transport);
// server.ts(プロダクション実装)
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';

class MyMCPServer {
  private server: Server;
  
  constructor() {
    this.server = new Server({
      name: 'my-production-server',
      version: '1.0.0',
    }, {
      capabilities: {
        tools: {},
        resources: {}
      }
    });
    
    this.setupHandlers();
  }
  
  private setupHandlers() {
    // スキーマ定義
    const HelloSchema = z.object({
      name: z.string().min(1),
      language: z.enum(['en', 'ja']).default('ja')
    });
    
    // ツールハンドラー
    this.server.setRequestHandler('tools/list', async () => ({
      tools: [{
        name: 'hello',
        description: '多言語対応の挨拶ツール',
        inputSchema: HelloSchema
      }]
    }));
    
    this.server.setRequestHandler('tools/call', async (request) => {
      const { name, arguments: args } = request.params;
      
      if (name === 'hello') {
        const validated = HelloSchema.parse(args);
        const greeting = validated.language === 'ja' 
          ? `こんにちは、${validated.name}さん!`
          : `Hello, ${validated.name}!`;
          
        return {
          content: [{
            type: 'text',
            text: greeting
          }]
        };
      }
      
      throw new Error(`Unknown tool: ${name}`);
    });
  }
  
  async start() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('MCP Server started');
  }
}

// サーバー起動
const server = new MyMCPServer();
await server.start();
シンプルな実装
// server.ts(基本実装)
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const server = new Server({
  name: 'my-simple-server',
  version: '1.0.0',
});

// ツールの登録
server.setRequestHandler('tools/list', async () => ({
  tools: [{
    name: 'hello',
    description: 'Say hello',
    inputSchema: {
      type: 'object',
      properties: {
        name: { type: 'string' }
      }
    }
  }]
}));

// サーバー起動
const transport = new StdioServerTransport();
await server.connect(transport);
プロダクション向け実装
// server.ts(プロダクション実装)
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';

class MyMCPServer {
  private server: Server;
  
  constructor() {
    this.server = new Server({
      name: 'my-production-server',
      version: '1.0.0',
    }, {
      capabilities: {
        tools: {},
        resources: {}
      }
    });
    
    this.setupHandlers();
  }
  
  private setupHandlers() {
    // スキーマ定義
    const HelloSchema = z.object({
      name: z.string().min(1),
      language: z.enum(['en', 'ja']).default('ja')
    });
    
    // ツールハンドラー
    this.server.setRequestHandler('tools/list', async () => ({
      tools: [{
        name: 'hello',
        description: '多言語対応の挨拶ツール',
        inputSchema: HelloSchema
      }]
    }));
    
    this.server.setRequestHandler('tools/call', async (request) => {
      const { name, arguments: args } = request.params;
      
      if (name === 'hello') {
        const validated = HelloSchema.parse(args);
        const greeting = validated.language === 'ja' 
          ? `こんにちは、${validated.name}さん!`
          : `Hello, ${validated.name}!`;
          
        return {
          content: [{
            type: 'text',
            text: greeting
          }]
        };
      }
      
      throw new Error(`Unknown tool: ${name}`);
    });
  }
  
  async start() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('MCP Server started');
  }
}

// サーバー起動
const server = new MyMCPServer();
await server.start();

リソースの提供

MCP サーバーは、ツールだけでなくリソース(ファイル、データ)も提供できます。

// リソースの定義
server.setRequestHandler('resources/list', async () => ({
  resources: [
    {
      uri: 'file://config.json',
      name: '設定ファイル',
      description: 'アプリケーションの設定',
      mimeType: 'application/json'
    },
    {
      uri: 'db://users',
      name: 'ユーザーデータ',
      description: 'データベースのユーザー情報'
    }
  ]
}));
// リソースの実装
server.setRequestHandler('resources/read', async (request) => {
  const { uri } = request.params;
  
  if (uri === 'file://config.json') {
    const config = await fs.readFile('./config.json', 'utf-8');
    return {
      contents: [{
        uri,
        mimeType: 'application/json',
        text: config
      }]
    };
  }
  
  if (uri.startsWith('db://')) {
    const table = uri.replace('db://', '');
    const data = await database.query(`SELECT * FROM ${table}`);
    return {
      contents: [{
        uri,
        mimeType: 'application/json',
        text: JSON.stringify(data, null, 2)
      }]
    };
  }
  
  throw new Error(`Resource not found: ${uri}`);
});
// claude_desktop_config.json
{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["./dist/server.js"],
      "env": {
        "DATABASE_URL": "postgresql://localhost/mydb",
        "API_KEY": "your-api-key"
      }
    }
  }
}

実践的な例:ファイルシステムMCPサーバー

実際に使えるファイルシステム操作用の MCP サーバーを実装してみましょう。

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import fs from 'fs/promises';
import path from 'path';

class FileSystemMCPServer {
  private server: Server;
  private baseDir: string;
  
  constructor(baseDir: string = process.cwd()) {
    this.baseDir = baseDir;
    this.server = new Server({
      name: 'filesystem-server',
      version: '1.0.0',
    });
    
    this.setupHandlers();
  }
  
  private setupHandlers() {
    // ツール一覧
    this.server.setRequestHandler('tools/list', async () => ({
      tools: [
        {
          name: 'list_files',
          description: 'ディレクトリ内のファイル一覧を取得',
          inputSchema: {
            type: 'object',
            properties: {
              path: { 
                type: 'string',
                description: '対象ディレクトリのパス(相対パス)'
              }
            },
            required: ['path']
          }
        },
        {
          name: 'read_file',
          description: 'ファイルの内容を読み取る',
          inputSchema: {
            type: 'object',
            properties: {
              path: { 
                type: 'string',
                description: '読み取るファイルのパス'
              }
            },
            required: ['path']
          }
        },
        {
          name: 'write_file',
          description: 'ファイルに内容を書き込む',
          inputSchema: {
            type: 'object',
            properties: {
              path: { 
                type: 'string',
                description: '書き込むファイルのパス'
              },
              content: {
                type: 'string',
                description: '書き込む内容'
              }
            },
            required: ['path', 'content']
          }
        }
      ]
    }));
    
    // ツール実行
    this.server.setRequestHandler('tools/call', async (request) => {
      const { name, arguments: args } = request.params;
      
      try {
        switch (name) {
          case 'list_files':
            return await this.listFiles(args.path);
            
          case 'read_file':
            return await this.readFile(args.path);
            
          case 'write_file':
            return await this.writeFile(args.path, args.content);
            
          default:
            throw new Error(`Unknown tool: ${name}`);
        }
      } catch (error) {
        return {
          content: [{
            type: 'text',
            text: `エラー: ${error.message}`
          }],
          isError: true
        };
      }
    });
  }
  
  private async listFiles(relativePath: string) {
    const fullPath = path.join(this.baseDir, relativePath);
    const files = await fs.readdir(fullPath, { withFileTypes: true });
    
    const fileList = files.map(file => ({
      name: file.name,
      type: file.isDirectory() ? 'directory' : 'file',
      path: path.join(relativePath, file.name)
    }));
    
    return {
      content: [{
        type: 'text',
        text: JSON.stringify(fileList, null, 2)
      }]
    };
  }
  
  private async readFile(relativePath: string) {
    const fullPath = path.join(this.baseDir, relativePath);
    const content = await fs.readFile(fullPath, 'utf-8');
    
    return {
      content: [{
        type: 'text',
        text: content
      }]
    };
  }
  
  private async writeFile(relativePath: string, content: string) {
    const fullPath = path.join(this.baseDir, relativePath);
    await fs.writeFile(fullPath, content, 'utf-8');
    
    return {
      content: [{
        type: 'text',
        text: `ファイル ${relativePath} に書き込みました。`
      }]
    };
  }
  
  async start() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
  }
}

// 起動
const server = new FileSystemMCPServer();
await server.start();

Claude Desktopへの統合

設定ファイルの場所

  • macos: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json

設定ファイルの記述

{
  "mcpServers": {
    "filesystem": {
      "command": "node",
      "args": ["./path/to/filesystem-server.js"],
      "env": {
        "BASE_DIR": "/Users/username/Documents"
      }
    },
    "database": {
      "command": "python",
      "args": ["./path/to/db-server.py"],
      "env": {
        "DATABASE_URL": "postgresql://localhost/mydb"
      }
    }
  }
}

デバッグとトラブルシューティング

一般的な問題と解決方法

MCPサーバーのトラブルシューティング
問題 原因 解決方法
サーバーが起動しない パスの問題 絶対パスを使用する
ツールが表示されない ハンドラーの登録ミス tools/listの実装を確認
エラーが返される 例外処理の不足 try-catchで適切にハンドリング
接続が切れる タイムアウト 定期的なpingを実装
権限エラー ファイルアクセス権 適切な権限を設定

デバッグのベストプラクティス

開発効率 80 %
  1. ログ出力を活用

    console.error(`[MCP] ${new Date().toISOString()} - ${message}`);
  2. エラーハンドリングの徹底

    try {
      // 処理
    } catch (error) {
      console.error('[MCP Error]', error);
      return { content: [{ type: 'text', text: `エラー: ${error.message}` }] };
    }
  3. 開発用ツールの作成

    // デバッグ用のechoツール
    {
      name: 'debug_echo',
      description: 'デバッグ用:入力をそのまま返す',
      inputSchema: { type: 'object' }
    }

今後の展望

Claude Code がリモート MCP サーバーをサポートしました。これにより、ローカルだけでなくクラウド上の MCP サーバーとも連携可能になり、チーム開発での活用が大幅に向上しています。

2025年6月のアップデート 最新情報

ロードマップ

MCP発表

オープンソースとして公開

SDK改善

Python、Go対応

リモートサーバー対応

Claude Codeで実装

エンタープライズ機能

組織向け機能強化(予定)

まとめ

Model Context Protocol(MCP)は、ai システムとデータソースを接続する新しい標準として、開発者に大きな可能性を提供します。

MCPのメリット

  • 統一されたインターフェース: 様々なデータソースへの接続を標準化
  • 柔軟な拡張性: カスタムツールの実装が容易
  • セキュアな通信: 認証・認可の仕組みを内包
  • 活発なコミュニティ: オープンソースでの開発

MCP を活用することで、Claude をより強力な ai アシスタントとして活用できます。ぜひ自分のユースケースに合わせた MCP サーバーを開発してみてください。

この記事は役に立ちましたか?

Daily Hackでは、開発者の皆様に役立つ情報を毎日発信しています。