MCP Server 開発入門:ゼロから作る初めての MCP サービス
はじめに: Cursor でコードを書いているとき、「AI にプロジェクト依存の最新バージョンをそのまま調べてほしい」と思ったことはありませんか?Claude でデータを分析するとき、データベースの中身まで読み取ってほしい、という場面もあるでしょう。それぞれ個別に実装するなら、AI ツールごとに専用のアダプターコードが必要になります。MCP Server を用意すれば、一度書くだけで MCP 対応クライアントすべてから使えます。本記事では、TypeScript でゼロから完全な MCP Server を手書きしていきます。
MCP とは?3 分で理解するコアコンセプト
USB 端子の物語
数年前のデジタル機器を使っていた人なら、あのややこしい時期を覚えているでしょう。マウスは丸い端子、キーボードは四角い端子、プリンターはパラレルポート。機器ごとに対応する差し込み口を探す必要がありました。その後 USB が登場し、1 つの端子ですべて解決しました。
MCP(Model Context Protocol)は、AI ツールの世界における「USB 標準」になりつつあります。
MCP がない時代は、AI にデータソースへアクセスさせるたびに、ツールごとのアダプター層を別々に書く必要がありました。Claude 用のプラグイン、Cursor 用の拡張、Windsurf 用の連携……。複雑さは N × M(データソース N 個 × AI ツール M 個)です。
MCP があれば、MCP Server を 1 つ書くだけで、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 を使い、フロントエンドやフルスタックの開発者に向いています。どちらの実装も機能は同等なので、慣れている言語を選べば問題ありません。
"https://modelcontextprotocol.io"
開発環境の準備
前提条件
この記事では、次のことを前提とします。
- Node.js 18+ または Bun 1.0+ がインストール済み
- TypeScript を書いたことがあり、
interfaceやasync/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 SDKzod:TypeScript のランタイム型検証。ツール引数のスキーマ定義に使う
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 を作ります。
- AI からの呼び出しリクエストを受け取る
- OpenWeatherMap API を照会してリアルタイムの天気を取得する
- 整形した結果を返す
プロジェクト構成の設計
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 が提供するコアクラスで、name と version を渡す必要があります。tool() メソッドはツールを 1 つ登録するためのもので、第 1 引数がツール名、第 2 引数が説明、第 3 引数が引数スキーマ、最後が実行関数です。
ステップ 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=ja`;
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,
};
}
}
);
注意点です。
- API Key は環境変数から読み取る:鍵をコードに直接書き込んではいけません
- エラー処理:
isError: trueを返して、呼び出しが失敗したことをクライアントに伝える - 型定義:
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 には任意のスキームが使えます。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() の戻り値はメッセージの配列で、各メッセージには role と content があります。これにより、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 をテストする
- Claude Desktop / Cursor を再起動する
- 対話欄に「北京の天気を調べて」と入力する
- 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 でコンパイルする |
| 権限エラー | 設定ファイルの権限 | 設定ファイルが読み取り可能か確認する |
"https://github.com/modelcontextprotocol/typescript-sdk"
拡張とデプロイのアドバイス
ツールをさらに追加する
天気照会はほんの始まりにすぎません。次のようなこともできます。
- 過去の天気照会:履歴データ 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 を呼び出すよう設定する
これであなたは、次のことができます。
- よく使う API の MCP ラッパーを構築する(GitHub、Slack、Notion など)
- 社内業務システムの MCP インターフェースを作る(CRM、データベース)
- MCP コミュニティの既存の成果を探索する
さらに学ぶためのリソース:
MCP プロトコルの仕組みをさらに深く知りたい場合は、MCP プロトコルの仕組みを深掘りするの記事もあわせてご覧ください。
FAQ
MCP Server 開発にはどんな前提知識が必要ですか?
MCP Server と FastMCP の違いは何ですか?
MCP Server が正しく動作しているかをどうテストしますか?
MCP Server はリモートサーバーにデプロイできますか?
5分で読めます · 公開日: 2026年3月19日 · 更新日: 2026年6月8日
関連記事
Workers AI 完全ガイド:毎日 1 万回相当の無料 LLM 呼び出し、OpenAI より最大 90% 節約
Workers AI 完全ガイド:毎日 1 万回相当の無料 LLM 呼び出し、OpenAI より最大 90% 節約
AI で 1 万行のレガシーコードをリファクタリング:1 ヶ月分の仕事を 2 週間で終えた実録
AI で 1 万行のレガシーコードをリファクタリング:1 ヶ月分の仕事を 2 週間で終えた実録
OpenAI API がタイムアウトする?Workers で専用チャネルを構築、コストゼロで安定化
コメント
GitHubアカウントでログインしてコメントできます