atama plus techblog
🔖

作って理解するModel Context Protocol (MCP) Server

2025/03/23に公開
1

はじめに

Model Context Protocol (MCP) は、AIモデルとユーザーのデータソースを安全に接続するための規格です。
この記事では、MCP Serverの実装を通じて学んだ、MCPの技術概要や通信の仕様について解釈してみます。

MCPの技術的概要と通信インターフェース

MCPとは何か

Model Context Protocol (MCP) は、AIアシスタントと外部データソース・ツールを接続するための標準プロトコルです[1]
ClaudeやCursorなどのAIアシスタントが、ユーザーの許可したデータにアクセスできるようにする橋渡し役を担います。

Core Architecture

MCPはClient-Serverモデルのアーキテクチャに準拠しています[2]

  • ホスト(Hosts): Claude DesktopやCursor IDEなどのLLMアプリケーションで、接続を開始する役割
  • クライアント(Clients): ホストアプリケーション内でサーバーと1対1の接続を維持する役割
  • サーバー(Servers): クライアントに対して、コンテキスト、ツール、プロンプトを提供する役割

さらに、Core Componentとして以下が定義されています。

  • プロトコル層(Protocol layer): メッセージのフレーミング、リクエスト/レスポンスの関連付け、高レベルな通信パターンを扱う
  • トランスポート層(Transport layer): クライアントとサーバー間の実際の通信を扱う
  • メッセージタイプ: リクエスト(Request): 応答を期待するメッセージ。method とオプションの params を含む

エラーコードなどは、後述するJSON-RPCの標準エラーコードが使用されます。

通信の仕組み

MCPは基本的にJSON-RPC[3]を基盤としています。クライアント(AIアシスタント)とサーバー(データソース)間で、以下のような通信が行われます:

  • リソース探索: データソースが持つリソース(テーブル、ビュー等)の一覧取得
  • リソース読み取り: 特定のリソースの内容取得
  • ツール呼び出し: 特定の機能(クエリ実行等)の呼び出し

通信は標準入出力(stdin/stdout)を通じて行われ、JSON形式のメッセージをやり取りします。例えば、リソース一覧を取得するリクエストは以下のようになります:

{
  "jsonrpc": "2.0",      // JSON-RPCプロトコルのバージョン(固定)
  "id": "request-123",   // リクエストを識別するID(レスポンスで同じIDが返される)
  "method": "resources/list", // 呼び出すメソッド名
  "params": {}           // メソッドのパラメータ(メソッドによって異なる)
}

例えば、クライアントが resources/list を呼び出してアクセス可能なリソース一覧を取得しようとした際、以下のようなschemaのレスポンスが返ってきます:

{
  "jsonrpc": "2.0",
  "id": "request-123",
  "result": {
    "resources": [
      {
        "uri": "bigquery://project-id/dataset/table/schema",
        "mimeType": "application/json",
        "name": "dataset.table schema"
      }
    ]
  }
}

MCPで利用される主要なメソッドには以下のようなものがあります。

MCPでは、以下の主要なメソッドが定義されています:

サーバー情報と初期化関連

  • serverInfo: サーバーの情報を取得
  • initialize: クライアントとサーバー間の初期化処理を実行

リソース関連

ツール関連

MCP Serverの実装

実装の基本

MCPサーバーの実装には、公式のSDKが用意されているのでそれを利用できます。
TypeScript[4]やPython[5]など、多くの言語のものが公式に用意されています。

ここではTypeScript版を利用してMCP Serverを実装していく例を書きます。

npm install @modelcontextprotocol/sdk

SDKを使用したサーバーの基本構造は以下の通りです:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListResourcesRequestSchema,
  ListToolsRequestSchema,
  ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

// サーバーの初期化
const server = new Server(
  { name: "mcp-server/bigquery", version: "0.1.0" },
  { capabilities: { resources: {}, tools: {} } }
);

// リクエストハンドラーの登録
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  // リソース一覧取得の処理
});

// サーバー起動
async function runServer() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
}

runServer().catch(console.error);

BigQueryを題材にしたMCP Server実装例

では実際にBigQueryにアクセスするMCP Serverを作ってみましょう。

プロジェクト構成

mcp-bigquery/
├── src/
│   └── index.ts  # メインコード
├── test/
│   └── connectivity-test.js  # 疎通テスト
├── package.json
└── tsconfig.json

主要コンポーネントの実装

  1. サーバー設定と初期化:
// コマンドライン引数からの設定取得
function parseArgs(): ServerConfig {
  // プロジェクトID、ロケーション等のパース
}

// BigQueryクライアントの初期化
const config = parseArgs();
const bigquery = new BigQuery({
  projectId: config.projectId
});

// ベースURLの設定
const resourceBaseUrl = new URL(`bigquery://${config.projectId}`);
  1. リソース一覧取得ハンドラー:
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  try {
    const [datasets] = await bigquery.getDatasets();
    const resources = [];

    for (const dataset of datasets) {
      const [tables] = await dataset.getTables();
      
      for (const table of tables) {
        const [metadata] = await table.getMetadata();
        const resourceType = metadata.type === 'VIEW' ? 'view' : 'table';
        
        resources.push({
          uri: new URL(`${dataset.id}/${table.id}/schema`, resourceBaseUrl).href,
          mimeType: "application/json",
          name: `"${dataset.id}.${table.id}" ${resourceType} schema`,
        });
      }
    }

    return { resources };
  } catch (error) {
    throw error;
  }
});
  1. ツール一覧取得ハンドラー:
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "query",
        description: "読み取り専用のBigQuery SQLクエリを実行する",
        inputSchema: {
          type: "object",
          properties: {
            sql: { type: "string" },
            maximumBytesBilled: { 
              type: "string",
              description: "課金される最大バイト数(デフォルト: 1GB)",
              optional: true
            }
          },
        },
      },
    ],
  };
});
  1. SQLクエリ実行ハンドラー:
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "query") {
    let sql = request.params.arguments?.sql as string;
    let maximumBytesBilled = request.params.arguments?.maximumBytesBilled || "1000000000";
    
    // 読み取り専用クエリであることを検証
    const forbiddenPattern = /\b(INSERT|UPDATE|DELETE|CREATE|DROP|ALTER)\b/i;
    if (forbiddenPattern.test(sql)) {
      throw new Error('読み取り操作のみが許可されています');
    }    

    try {
      const [rows] = await bigquery.query({
        query: sql,
        location: config.location,
        maximumBytesBilled: maximumBytesBilled.toString(),
      });

      return {
        content: [{ type: "text", text: JSON.stringify(rows, null, 2) }],
        isError: false,
      };
    } catch (error) {
      throw error;
    }
  }
  throw new Error(`不明なツール: ${request.params.name}`);
});

認証情報の渡し方と注意点

MCP Serverでは、特にクラウドサービスへの接続において認証情報の管理が重要です。

ローカル開発での認証
開発環境では、Google Cloud CLIを使った認証が簡単です:

gcloud auth application-default login

これにより、 ~/.config/gcloud/application_default_credentials.json に認証情報が保存されます。

Claude Desktopでの設定と注意点
Claude Desktopで使用する場合、設定ファイルに絶対パスを指定する必要があります:

{
  "mcpServers": {
    "bigquery": {
      "command": "node",
      "args": [
        "/absolute/path/to/dist/index.js",
        "--project-id",
        "YOUR_PROJECT_ID",
        "--location",
        "us"
      ],
      "env": {
        "GOOGLE_APPLICATION_CREDENTIALS": "/absolute/path/to/your/credentials.json"
      }
    }
  }
}

デバッグと疎通テスト

MCP Serverの動作確認には、標準入出力を介してJSON-RPCメッセージを送信するテストスクリプトが有効です:

// 簡易的な疎通テストツール
const server = spawn('node', [
  'dist/index.js',
  '--project-id', 'YOUR_PROJECT_ID',
  '--location', 'us'
], { stdio: ['pipe', 'pipe', 'pipe'] });

// テストリクエスト(リソース一覧取得)
const request = {
  jsonrpc: '2.0',
  id: '1',
  method: 'resources/list',
  params: {}
};

// リクエスト送信
server.stdin.write(JSON.stringify(request) + '\n');

このようなテストスクリプトを用意しておくことで、Claude Desktop環境に依存せずにMCP Serverの動作確認ができます。

今回作成したMCP Serverの全体はこちらです:
https://github.com/atamaplus-public/mcp-bigquery

まとめ

MCPを使うことで、AIアシスタントと各種データを直接接続できるようになり、自然言語でデータ分析や探索が可能になります。
今回勉強のために(Claudeに手伝ってもらいながら)MCP Serverを実装してみましたが、SDKを用いて比較的少ないコード量で実現でき、他のツールにもいろいろ応用が効きそうでした!

MCPも仕組みを理解すれば怖くなく利用できます!いいMCPライフを送りましょう!

参考

脚注
  1. Model Context Protocol (MCP) Introduction ↩︎

  2. MCP Core Architecture ↩︎

  3. JSON-RPC 2.0 ↩︎

  4. MCP TypeScript SDK ↩︎

  5. MCP Python SDK ↩︎

1
atama plus techblog
atama plus techblog

Discussion