1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【完全ガイド】TypeScriptでMCPサーバー構築入門:Claude AIとの連携とカスタムツール実装方法

Posted at

はじめに

MCPとはModel Context Protocolの略であり、LLM(大規模言語モデル)にコンテキストを提供する方法を標準化するプロトコルです。MCPを活用することで、LLMは単なる質問応答だけでなく、外部ツールからの情報取得、コード実行、データ保存など、より実用的なタスクを実行できるようになります。

本記事では、TypeScriptを使ってMCPサーバーを実装し、Claude Desktopなどのホストアプリケーションから利用する方法を解説します。さらに、実用的なサーバーの構築方法から実際の運用までをカバーします。

MCP(Model Context Protocol)の基本概念

MCPとは何か

MCPは、アプリケーションがLLMにコンテキスト情報を提供するための標準プロトコルです。例えば、「今日の天気は?」という質問に対して、LLMは過去のデータだけでは回答できませんが、MCPを通じて天気APIを呼び出し、最新の情報を取得することができます。

Function Callingとの違いは何でしょうか?Function Callingも外部APIを呼び出す手段ですが、実装がLLMごとに異なります。一方、MCPは標準化されたインターフェースを提供し、複数のツールを組み合わせた複雑なワークフローの構築が容易になります。

MCPのアーキテクチャ

MCPは以下の3つの主要コンポーネントで構成されています:

  1. ホスト:ユーザーが利用するLLMアプリケーション(Claude DesktopやClineなど)
  2. MCPクライアント:ホストアプリケーション内でサーバーとの接続を管理するコンポーネント
  3. MCPサーバー:クライアントにコンテキスト、ツール、プロンプトを提供するサービス

Claude Desktopで既存のMCPサーバーを利用する

Claude Desktopのインストール

まずはClaude Desktopを公式サイトからダウンロードしてインストールします。既にインストール済みの場合は、最新バージョンにアップデートしておきましょう。

MCPサーバーの追加方法

Claude Desktopで既存のMCPサーバーを利用するには、設定ファイルを編集する必要があります:

  1. Claude Desktopを起動し、メニューバーから「Claude」→「Settings...」を選択
  2. 左側のメニューから「Developer」を選択
  3. 「Edit Config」ボタンをクリック
  4. claude_desktop_config.jsonファイルをテキストエディタで開く

例として、GitHubのMCPサーバーを追加してみましょう:

{
  "mcpServers": {
    "github": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "GITHUB_PERSONAL_ACCESS_TOKEN",
        "mcp/github"
      ],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "YOUR_TOKEN_HERE"
      }
    }
  }
}

設定を保存してClaude Desktopを再起動すると、設定画面のDeveloperメニューに「github」が表示されるようになります。

TypeScriptで独自のMCPサーバーを実装する

それでは、TypeScriptで独自のMCPサーバーを実装してみましょう。今回は「数値計算ツール」を作成します。これは数学的な計算を実行するシンプルなMCPサーバーです。

プロジェクトのセットアップ

まず新しいプロジェクトを作成し、必要なパッケージをインストールします:

mkdir mcp-math-tools
cd mcp-math-tools
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D @types/node typescript vitest

package.jsonを次のように編集します:

{
  "name": "mcp-math-tools",
  "version": "1.0.0",
  "main": "src/server.ts",
  "type": "module",
  "bin": {
    "mathTools": "./build/index.js"
  },
  "scripts": {
    "build": "tsc && chmod 755 build/index.js",
    "test": "vitest"
  },
  "files": [
    "build"
  ]
}

tsconfig.jsonを作成します:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

数値計算ツールの実装

src/index.tsファイルを作成し、MCPサーバーを初期化します:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

// サーバーインスタンスの作成
export const server = new McpServer({
  name: "MathTools",
  version: "0.1.0",
});

// 因数分解ツールの実装
server.tool(
  "factorize",
  "Factorize a number into its prime factors",
  { number: z.number().int().positive().describe("An integer to factorize") },
  async ({ number }) => {
    const factors = findPrimeFactors(number);
    
    return {
      content: [
        {
          type: "text",
          text: `Prime factors of ${number}: ${factors.join(' × ')}`,
        },
      ],
    };
  }
);

// 最大公約数を計算するツール
server.tool(
  "gcd",
  "Calculate the greatest common divisor of two numbers",
  {
    a: z.number().int().describe("First integer"),
    b: z.number().int().describe("Second integer")
  },
  async ({ a, b }) => {
    const result = calculateGCD(a, b);
    
    return {
      content: [
        {
          type: "text",
          text: `The greatest common divisor of ${a} and ${b} is ${result}`,
        },
      ],
    };
  }
);

// 素数判定ツール
server.tool(
  "isPrime",
  "Check if a number is prime",
  { number: z.number().int().positive().describe("Number to check") },
  async ({ number }) => {
    const prime = isPrime(number);
    
    return {
      content: [
        {
          type: "text",
          text: prime ? 
            `${number} is a prime number.` : 
            `${number} is not a prime number.`,
        },
      ],
    };
  }
);

// 素数判定関数
function isPrime(num: number): boolean {
  if (num <= 1) return false;
  if (num <= 3) return true;
  
  if (num % 2 === 0 || num % 3 === 0) return false;
  
  for (let i = 5; i * i <= num; i += 6) {
    if (num % i === 0 || num % (i + 2) === 0) return false;
  }
  
  return true;
}

// 因数分解関数
function findPrimeFactors(n: number): number[] {
  const factors: number[] = [];
  let divisor = 2;
  
  while (n > 1) {
    while (n % divisor === 0) {
      factors.push(divisor);
      n /= divisor;
    }
    divisor++;
    
    if (divisor * divisor > n && n > 1) {
      factors.push(n);
      break;
    }
  }
  
  return factors;
}

// 最大公約数計算関数
function calculateGCD(a: number, b: number): number {
  while (b !== 0) {
    const temp = b;
    b = a % b;
    a = temp;
  }
  return Math.abs(a);
}

// サーバー起動
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP Math Tools Server running on stdio");
}

main().catch((error) => {
  console.error("Fatal error in main():", error);
  process.exit(1);
});

ツールのテスト実装

ツールをテストするためのファイルsrc/index.test.tsを作成します:

import { describe, it, expect } from "vitest";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { server } from "./index.js";

describe("MathTools Tests", () => {
  it("factorize returns correct prime factors", async () => {
    const client = new Client({
      name: "test client",
      version: "0.1.0",
    });

    const [clientTransport, serverTransport] = 
      InMemoryTransport.createLinkedPair();
    
    await Promise.all([
      client.connect(clientTransport),
      server.connect(serverTransport),
    ]);

    const result = await client.callTool({
      name: "factorize",
      arguments: {
        number: 60,
      },
    });

    expect(result).toEqual({
      content: [
        {
          type: "text",
          text: "Prime factors of 60: 2 × 2 × 3 × 5",
        },
      ],
    });
  });

  it("gcd calculates correct greatest common divisor", async () => {
    const client = new Client({
      name: "test client",
      version: "0.1.0",
    });

    const [clientTransport, serverTransport] = 
      InMemoryTransport.createLinkedPair();
    
    await Promise.all([
      client.connect(clientTransport),
      server.connect(serverTransport),
    ]);

    const result = await client.callTool({
      name: "gcd",
      arguments: {
        a: 48,
        b: 18,
      },
    });

    expect(result).toEqual({
      content: [
        {
          type: "text",
          text: "The greatest common divisor of 48 and 18 is 6",
        },
      ],
    });
  });

  it("isPrime correctly identifies prime numbers", async () => {
    const client = new Client({
      name: "test client",
      version: "0.1.0",
    });

    const [clientTransport, serverTransport] = 
      InMemoryTransport.createLinkedPair();
    
    await Promise.all([
      client.connect(clientTransport),
      server.connect(serverTransport),
    ]);

    const result = await client.callTool({
      name: "isPrime",
      arguments: {
        number: 17,
      },
    });

    expect(result).toEqual({
      content: [
        {
          type: "text",
          text: "17 is a prime number.",
        },
      ],
    });
  });
});

テストを実行するには以下のコマンドを使用します:

npm run test

サーバーのビルドと起動

サーバーをビルドして実行可能ファイルを生成します:

npm run build

生成されたファイルを実行して、サーバーが起動することを確認します:

node ./build/index.js

「MCP Math Tools Server running on stdio」というメッセージが表示されれば成功です。

Claude DesktopでMCPサーバーを利用する

自作したMath Toolsサーバーをクライアントから呼び出してみましょう。Claude Desktopの設定ファイルclaude_desktop_config.jsonに以下のJSONを追加します:

{
  "mcpServers": {
    "mathTools": {
      "command": "node",
      "args": [
        "/absolute/path/to/your/mcp-math-tools/build/index.js"
      ]
    }
  }
}

Claude Desktopを再起動すると、ツールアイコンをクリックして「factorize」「gcd」「isPrime」のツールが追加されていることが確認できます。

例えば、「120という数字の素因数分解をしてください」と尋ねてみましょう。すると「factorize」ツールの実行が求められ、ツールの使用を許可すると「Prime factors of 120: 2 × 2 × 2 × 3 × 5」という結果が返ってきます。

実践的なMCPサーバーの構築ポイント

エラーハンドリング

実際のMCPサーバーでは適切なエラーハンドリングが重要です。例えば、不正な入力に対してわかりやすいエラーメッセージを返すようにしましょう:

server.tool(
  "fibonacci",
  "Generate Fibonacci sequence up to n terms",
  { terms: z.number().int().positive().max(100).describe("Number of terms to generate") },
  async ({ terms }) => {
    try {
      if (terms > 100) {
        return {
          content: [
            {
              type: "text",
              text: "Error: Can only generate up to 100 terms for performance reasons.",
            },
          ],
        };
      }
      
      const sequence = generateFibonacci(terms);
      
      return {
        content: [
          {
            type: "text",
            text: `Fibonacci sequence (${terms} terms): ${sequence.join(', ')}`,
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error calculating Fibonacci sequence: ${error.message}`,
          },
        ],
      };
    }
  }
);

function generateFibonacci(n: number): number[] {
  const sequence: number[] = [0, 1];
  for (let i = 2; i < n; i++) {
    sequence.push(sequence[i-1] + sequence[i-2]);
  }
  return sequence.slice(0, n);
}

リッチコンテンツの返却

MCPはテキストだけでなく、リッチなコンテンツを返すこともできます:

server.tool(
  "calculateStats",
  "Calculate basic statistics for a set of numbers",
  { 
    numbers: z.array(z.number()).min(1).describe("Array of numbers to analyze") 
  },
  async ({ numbers }) => {
    const sum = numbers.reduce((a, b) => a + b, 0);
    const average = sum / numbers.length;
    const sortedNumbers = [...numbers].sort((a, b) => a - b);
    const median = getMedian(sortedNumbers);
    const min = sortedNumbers[0];
    const max = sortedNumbers[sortedNumbers.length - 1];
    
    return {
      content: [
        {
          type: "text",
          text: "Statistical analysis results:",
        },
        {
          type: "json",
          json: {
            count: numbers.length,
            sum,
            average,
            median,
            min,
            max,
            sortedData: sortedNumbers
          },
        }
      ],
    };
  }
);

function getMedian(sortedArr: number[]): number {
  const mid = Math.floor(sortedArr.length / 2);
  return sortedArr.length % 2 === 0
    ? (sortedArr[mid - 1] + sortedArr[mid]) / 2
    : sortedArr[mid];
}

まとめ

本記事では、TypeScriptを使ってMCP(Model Context Protocol)サーバーを実装する方法を解説しました。MCPを活用することで、LLMはより豊かな機能を持つアプリケーションとなります。

MCPの主なポイントは以下の通りです:

  1. MCPはアプリケーションがLLMにコンテキストを提供する方法を標準化するプロトコル
  2. MCPを使うことで、LLMは外部ツールやサービスからコンテキストを取得し、様々なアクションを実行できる
  3. MCPはホスト、クライアント、サーバーの3つのコンポーネントで構成される
  4. MCPサーバーはリソース、ツール、プロンプトを提供し、クライアントはこれらを利用してタスクを実行する

今回実装した数値計算ツールは基本的な機能しか持っていませんが、この基盤を拡張することで、より複雑で実用的なMCPサーバーを構築できるでしょう。MCPの標準化されたインターフェースを活用することで、LLMアプリケーションの可能性は無限に広がります。

TypeScriptによるMCPサーバー実装は、型安全性と開発効率の高さから、特に複雑なツールを開発する際に強みを発揮します。今後のAI開発において、MCPは重要な役割を担うことでしょう。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?