言語を切り替える
テーマを切り替える

MCP Server 開発入門:ゼロから始める初めての MCP サービス構築

导语: Cursor でコードを書いているとき、AI にプロジェクトの依存関係の最新バージョンを直接確認させたいと思ったことはありませんか?または Claude でデータを分析しているとき、データベース内の情報を読み取らせたいと思ったことはありませんか?それぞれ個別に実装しようとすると、各 AI ツールごとに适配コードを書く必要があります。MCP Server を使えば、一度書くだけで MCP をサポートするすべてのクライアントが利用できるようになります。この記事では、TypeScript を使ってゼロから完全な MCP Server を構築する方法を解説します。

30 分
習得時間
ゼロから実行まで
3 つ
中核機能
Tools/Resources/Prompts
1000+
MCP サーバー
GitHub オープンソースコミュニティ
数据来源: MCP 公式データ (2025 年)

MCP とは何か?3 分で理解する核心概念

USB インターフェースの物語

数年前にデジタル製品を使ったことのある人なら、あの尴尬な時期を覚えているでしょう:マウスは丸型、キーボードは角型、プリンターは並列ポート、それぞれのデバイスに対応する挿し口を探す必要がありました。その後 USB が登場し、一つのインターフェースで全てが解決するようになりました。

MCP(Model Context Protocol)は、AI ツール界の「USB 規格」になりつつあります。

MCP がない時代、AI に特定のデータソースへアクセスさせようとすると、各 AI ツールごとに适配層を個別に書く必要がありました。Claude にはプラグインを、Cursor には拡張を、Windsurf にはまた別のものを…复杂度は N x M(N 個のデータソース x M 個の AI ツール)になります。

MCP があると、MCP Server を一つ書くだけで、MCP をサポートするすべてのクライアントが直接呼び出せるようになります。复杂度は N+M に下がります。

3 層アーキテクチャは非常にシンプルです:

+-------------+     +-------------+     +-------------+
|    Host     | ->  |   Client    | ->  |   Server    |
|  (Claude)   |     | (MCP クライアント)|     |(あなたのサービス)|
+-------------+     +-------------+     +-------------+
  • Host:AI アプリケーションそのもの(例:Claude Desktop、Cursor)
  • Client:MCP クライアント、Host との通信を担当
  • Server:あなたが書くサービス、具体的な機能を提供

MCP Server が提供する 3 つの機能

MCP Server は、3 つの異なるタイプの機能を提供できます:

機能用途
Tools(ツール)操作の実行天気照会、メッセージ送信、データベース読み取り
Resources(リソース)データの提供ファイル内容、API 応答、設定情報
Prompts(プロンプト)事前定義テンプレートコードレビューテンプレート、日報生成テンプレート

Tools は「関数」として理解できます。AI がそれを呼び出して特定のアクションを実行します。Resources は「データソース」で、AI がその内容を読み取れます。Prompts は「テンプレート」で、AI がタスクをより早く理解するのに役立ちます。

既存の記事との違い:他の MCP チュートリアルでは Python と FastMCP を使ったバージョンを見かけるかもしれません。この記事では TypeScript 原生 SDK を使用しており、フロントエンドおよびフルスタック開発者に向いています。両者の機能は同等なので、慣れた言語を選べばよいでしょう。

""


開発環境の準備

事前要件

この記事では、以下を前提としています:

  • Node.js 18+ または Bun 1.0+ がインストールされている
  • TypeScript を書いたことがあり、interfaceasync/await が何かを知っている
  • Claude Desktop または MCP をサポートするクライアント(Cursor、Windsurf など)を持っている

Bun を使ったことがない場合は、試してみることをおすすめします。npm よりもはるかに高速で、TypeScript サポートが内置されているため、ts-node を追加で設定する必要がありません。

プロジェクトの初期化

# プロジェクトディレクトリを作成
mkdir mcp-weather-server && cd mcp-weather-server

# 初期化(Bun または npm を使用)
bun init -y
# または npm init -y

# MCP TypeScript SDK をインストール
bun add @modelcontextprotocol/sdk zod
# または npm install @modelcontextprotocol/sdk zod

ここで 2 つの依存関係を使用しています:

  • @modelcontextprotocol/sdk:MCP 公式 TypeScript SDK
  • zod:TypeScript ランタイム型検証用。ツールのパラメータ schema 定義に使用

TypeScript 設定の要点

bun init を使用すれば、tsconfig.json はすでに設定済みです。手動で設定する場合は、以下のオプションに注意してください:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "strict": true
  }
}

moduleResolution: "bundler" は ESM モジュールにとって重要です。これを設定しないと、「xxx is not defined」というエラーに遭遇する可能性があります。


実践:手書き天気照会 MCP Server

このチュートリアルでは、完全な MCP Server を完成させます。以下の機能を持ちます:

  1. AI の呼び出しリクエストを受信
  2. OpenWeatherMap API に照会してリアルタイム天気を取得
  3. フォーマットされた結果を返す

プロジェクト構造設計

mcp-weather-server/
+-- src/
|   +-- index.ts      # エントリーファイル
|   +-- weather.ts    # 天気ツール実装
|   +-- resources.ts  # リソース定義
+-- package.json
+-- tsconfig.json

実際のコードはすべて index.ts に配置することもできます(この記事ではそのようにしますが)、モジュールに分割した方がメンテナンス性は高まります。

ステップ 1:MCP Server の骨組みを作成

最もシンプルな部分から始めます。起動可能な MCP Server を作成します:

// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// サーバーインスタンスを作成
const server = new McpServer({
  name: "weather-service",
  version: "1.0.0",
});

// ツール(Tools)を登録
server.tool(
  "get_weather",
  "指定した都市の現在の天気情報を取得",
  {
    city: z.string().describe("都市名、例:北京、上海"),
  },
  async ({ city }) => {
    // ツール実装は次節で展開
    return { content: [{ type: "text", text: `${city} の天気を照会中...` }] };
  }
);

// サーバーを起動
const transport = new StdioServerTransport();
await server.connect(transport);

McpServer は SDK が提供する核心クラスで、nameversion を渡す必要があります。tool() メソッドはツールの登録に使用し、第 1 引数がツール名、第 2 引数が説明、第 3 引数がパラメータ schema、最後が実行関数です。

ステップ 2:天気照会ツールを実装(核心コード)

ツールを実際に機能するようにします。OpenWeatherMap の無料 API を使用します:

// src/weather.ts
import { z } from "zod";

// OpenWeatherMap API レスポンス型を定義
interface WeatherResponse {
  name: string;
  main: { temp: number; feels_like: number; humidity: number };
  weather: [{ description: string }];
  wind: { speed: number };
}

// 天気照会ツール実装
server.tool(
  "get_weather",
  "指定した都市の現在の天気情報を取得",
  {
    city: z.string().describe("都市名、例:北京、上海"),
  },
  async ({ city }) => {
    const API_KEY = process.env.OPENWEATHER_API_KEY;
    const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric&lang=zh_cn`;

    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`API リクエスト失敗:${response.status}`);
      }

      const data: WeatherResponse = await response.json();

      // フォーマットされた結果を返す
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify({
              city: data.name,
              temperature: `${data.main.temp}°C`,
              feels_like: `${data.main.feels_like}°C`,
              description: data.weather[0].description,
              humidity: `${data.main.humidity}%`,
              wind_speed: `${data.wind.speed} m/s`,
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `照会失敗:${error instanceof Error ? error.message : '不明なエラー'}`,
          },
        ],
        isError: true,
      };
    }
  }
);

注意点:

  1. API Key は環境変数から読み込む:コードにハードコードしないでください
  2. エラーハンドリングisError: true を返して、クライアントに呼び出し失敗を知らせます
  3. 型定義WeatherResponse インターフェースで TypeScript にデータ構造を検証させます

OpenWeatherMap で無料アカウントを登録し、API Key を取得したら環境変数を設定します:

export OPENWEATHER_API_KEY=your_api_key_here

ステップ 3:Resources を追加(オプションだが推奨)

Resources を使うと、Server が「読み取り専用」データを提供できるようになります。たとえば、AI がサーバーステータスを確認できるようにするリソースを提供できます:

// src/resources.ts

// サーバー状態情報を提供
server.resource(
  "server-status",
  "status://server",
  async (uri) => ({
    contents: [
      {
        uri: uri.href,
        text: JSON.stringify({
          name: "Weather Service",
          version: "1.0.0",
          status: "running",
          timestamp: new Date().toISOString(),
        }, null, 2),
      },
    ],
  })
);

// API ドキュメントを提供
server.resource(
  "api-docs",
  "docs://api",
  async (uri) => ({
    contents: [
      {
        uri: uri.href,
        text: `
# Weather MCP Server API

## Tools
- get_weather(city: string): 指定した都市の天気を取得

## Resources
- status://server - サーバー状態
- docs://api - API ドキュメント
        `.trim(),
      },
    ],
  })
);

resource() の最初の 2 つのパラメータはリソース名と URI で、3 番目は読み取り関数です。URI は任意の scheme でかまいません(status://docs:// など)。区別できれば問題ありません。

ステップ 4:Prompts を追加(高度な機能)

Prompts は事前定義された対話テンプレートです。たとえば、「天気レポート」テンプレートを定義しておくと、AI が使用する際に都市名を自動的に埋め込みます:

// 事前定義された天気レポートテンプレート
server.prompt(
  "weather_report",
  "フォーマットされた天気レポートを生成",
  {
    city: z.string().describe("都市名"),
    include_tips: z.boolean().optional().describe("服装アドバイスを含むかどうか"),
  },
  ({ city, include_tips }) => ({
    messages: [
      {
        role: "user",
        content: {
          type: "text",
          text: `${city} の天気レポートを生成してください。${include_tips ? "服装アドバイスも含めてください。" : ""}`,
        },
      },
    ],
  })
);

prompt() の戻り値はメッセージの配列で、各メッセージには rolecontent があります。これにより、AI 使用時に事前設定されたコンテキストが直接得られます。

ステップ 5:エントリーファイルを完成

上記のすべてのコードを src/index.ts に統合し、エラーハンドリングを追加します:

// src/index.ts (完全版)
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "weather-service",
  version: "1.0.0",
});

// すべてのツール、リソース、プロンプトを登録
// ...(上記のコード)

// エラーハンドリング
process.stdin.on("error", (err) => {
  console.error("標準入力エラー:", err);
  process.exit(1);
});

process.stdout.on("error", (err) => {
  console.error("標準出力エラー:", err);
  process.exit(1);
});

// 優雅な終了
process.on("SIGINT", async () => {
  await server.close();
  process.exit(0);
});

// サーバーを起動
const transport = new StdioServerTransport();
await server.connect(transport);

console.error("MCP Weather Server が起動しました。接続を待機中...");

StdioServerTransport は標準入出力を使用して通信するため、stdin/stdout のエラーハンドリングは重要です。SIGINT 処理により、Ctrl+C でサービスを優雅に停止できます。

bun run src/index.ts で起動し、「起動しました」のメッセージが表示されれば正常です。


クライアント設定:Claude で Server を活用

Server が完成したので、次は Claude Desktop や Cursor から呼び出せるようにします。

Claude Desktop 設定

設定ファイルを見つけます:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Server 設定を追加します:

{
  "mcpServers": {
    "weather": {
      "command": "bun",
      "args": ["run", "/absolute/path/to/mcp-weather-server/src/index.ts"],
      "env": {
        "OPENWEATHER_API_KEY": "あなたの API Key"
      }
    }
  }
}

注意args 内のパスは絶対パスである必要があります。相対パスだと Server の起動に失敗します。

Cursor / Windsurf 設定

Cursor と Windsurf の設定方法も同様で、IDE 設定で MCP 設定を見つけ、サーバー条目を追加します(形式は上記と同じ)。

Cursor の設定ファイルは通常以下にあります:

  • macOS: ~/Library/Application Support/Cursor/User/globalStorage/state.vscdb
  • または IDE 内:設定 -> AI -> MCP -> サーバーを追加

Server のテスト

  1. Claude Desktop / Cursor を再起動
  2. 入力ボックスに「北京の天気を調べて」と入力
  3. Claude が自動的に MCP Server を呼び出すはずです

以下のような出力が表示されれば成功です:

{
  "city": "北京",
  "temperature": "18°C",
  "feels_like": "16°C",
  "description": "曇り",
  "humidity": "65%",
  "wind_speed": "3.2 m/s"
}

よくある問題のトラブルシューティング

問題考えられる原因解決策
Server が未接続パスエラーargs 内の絶対パスを確認
API Key が無効環境変数が未渡りenv 設定が正しいか確認
応答なしTypeScript コンパイルエラーbun build または tsc でコンパイル
権限エラー設定ファイルの権限設定ファイルが読み取り可能か確認

""


拡張とデプロイの提案

さらに多くのツールを追加

天気照会は始まりにすぎません。以下を追加できます:

  • 過去の天気照会:過去データ API を呼び出して、過去の特定の日付の天気を返す
  • 複数都市比較:一度に複数都市を照会し、比較テーブルを返す
  • 天気警報購読:悪天候の警報があるか確認

これらのツールの登録方法は get_weather と全く同じで、実装ロジックのみが異なります。

デプロイオプションの比較

Server をチームで共有したい場合、ローカルの stdio 伝送では不十分です。以下にいくつかのデプロイ方法を挙げます:

デプロイ方法適用シーンメリットデメリット
ローカル stdio個人使用、開発テストシンプル、安全共有可能ではない
HTTP/SSEチーム共有、複数ユーザーリモートアクセス可能認証処理が必要
Serverless本番環境自動スケーリングコールドスタート遅延

本番環境の注意点

認証:HTTP デプロイでは認証の実装が必須です。MCP は OAuth 2.1 をサポートしていますが、シンプルな API Key を使用することもできます:

// リクエストヘッダー内の API Key を確認
const apiKey = request.headers.get("Authorization");
if (apiKey !== `Bearer ${process.env.API_KEY}`) {
  return new Response("Unauthorized", { status: 401 });
}

レート制限:悪意のある呼び出しで API クォータを使い果たさないようにします。express-rate-limit や Cloudflare Workers の内置レート制限を使用できます。

ログpino または winston を使用してツール呼び出しを記録し、問題のトラブルシューティングを容易にします:

import pino from "pino";
const logger = pino();

server.tool("get_weather", /* ... */, async ({ city }) => {
  logger.info({ city }, "天気を照会");
  // ...
});

監視:ツールの呼び出し成功率や応答時間を追跡します。Prometheus + Grafana が一般的な組み合わせです。


まとめ

この記事では、TypeScript を使用してゼロから MCP Server を構築する方法を紹介しました。内容は以下の通りです:

  • MCP の核心概念と 3 層アーキテクチャの理解
  • MCP TypeScript SDK を使用したサーバー作成
  • 天気照会ツール(Tools)の実装
  • サーバー状態リソース(Resources)の追加
  • 天気レポートテンプレート(Prompts)の定義
  • Claude Desktop / Cursor が Server を呼び出す設定

これで以下のことが可能になります:

  1. よく使用する API の MCP ラッパーを構築(GitHub、Slack、Notion など)
  2. 内部業務システムの MCP インターフェースを作成(CRM、データベース)
  3. MCP コミュニティの既存の成果を探索

学習リソース

MCP プロトコルの原理を深く理解したい場合は、MCP プロトコル原理の深い理解 という記事もご覧ください。

FAQ

MCP Server 開発にはどのような基礎が必要ですか?
JavaScript/TypeScript の基礎が必要です。この記事では MCP TypeScript SDK を使用しており、async/await と型定義に慣れていれば習得可能です。
MCP Server と FastMCP の違いは何ですか?
FastMCP は Python フレームワークで、Python 開発者に向いています。この記事では TypeScript 原生 SDK を使用しており、フロントエンド/フルスタック開発者に向いています。両者の機能は同等で、技術スタックの好みに応じて選択します。
MCP Server が正常に動作しているかをテストするにはどうすればよいですか?
Claude Desktop を設定した後、入力ボックスに自然言語のリクエスト(例:「北京の天気を調べて」)を入力します。Claude が自動的にツールを呼び出して結果を返せば、Server は正常に動作しています。
MCP Server はリモートサーバーにデプロイできますか?
可能です。この記事では stdio 伝送を使用しており、ローカル開発に適しています。本番環境では HTTP/SSE 伝送を使用でき、OAuth 認証とレート制限保護を実装する必要があります。

6 min read · 公開日: 2026年3月19日 · 更新日: 2026年3月19日

コメント

GitHubアカウントでログインしてコメントできます

関連記事