MCP サーバーをゼロから構築する方法 (2026年版)
Model Context Protocol サーバーをステップバイステップで作成する
Hyperealで構築を始めよう
Kling、Flux、Sora、Veoなどに単一のAPIでアクセス。無料クレジットで開始、数百万規模まで拡張可能。
クレジットカード不要 • 10万人以上の開発者 • エンタープライズ対応
MCP サーバーを一から構築する方法 (2026年版)
Model Context Protocol (MCP) は、AI アシスタントが統合されたインターフェースを介して外部ツールやデータソースに接続できるようにする、Anthropic が作成したオープン標準です。個々の AI クライアントごとにカスタム統合を構築する代わりに、1 つの MCP サーバーを構築すれば、Claude Desktop、Cursor、VS Code、およびその他の MCP 対応クライアントで動作します。
このガイドでは、TypeScript を使用して MCP サーバーを一から構築する手順を説明します。最後まで進めると、カスタムツール、リソース、プロンプトを任意の MCP クライアントに提供する、動作可能なサーバーが完成します。
構築するもの
次のような機能を持つ、仮想のプロジェクト管理システム用 MCP サーバーを構築します。
- ツールの公開: AI が呼び出し可能な機能(タスク作成、タスク一覧表示、ステータス更新)
- リソースの公開: コンテキストを提供するデータ(プロジェクトデータ、チーム情報)
- プロンプトの公開: 再利用可能なインタラクションパターンの定義
前提条件
| 必要条件 | 詳細 |
|---|---|
| Node.js | v18 以上 |
| TypeScript | v5 以上 |
| npm または yarn | パッケージ管理用 |
| TypeScript の基礎知識 | 関数、型、async/await |
| MCP クライアント | Claude Desktop、Cursor、または VS Code |
ステップ 1: プロジェクトの初期化
新しいプロジェクトを作成し、MCP SDK をインストールします。
mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
TypeScript の設定を行います。
npx tsc --init
tsconfig.json を更新します。
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"declaration": true
},
"include": ["src/**/*"]
}
ビルドスクリプトを追加し、タイプを設定するために package.json を更新します。
{
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx src/index.ts"
},
"bin": {
"my-mcp-server": "./dist/index.js"
}
}
ステップ 2: 基本的なサーバーの作成
MCP サーバーの雛形となる src/index.ts を作成します。
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// インメモリのタスクストレージ (本番環境ではデータベースに置き換えてください)
interface Task {
id: string;
title: string;
description: string;
status: "todo" | "in_progress" | "done";
assignee: string;
createdAt: string;
}
const tasks: Map<string, Task> = new Map();
let nextId = 1;
// MCP サーバーの作成
const server = new McpServer({
name: "project-manager",
version: "1.0.0",
});
console.error("MCP server initialized"); // ログは stderr へ (stdout は MCP プロトコル用)
理解すべき重要な点:MCP サーバーはデフォルトで stdio を介して通信します。すべてのプロトコルメッセージは stdout を通るため、すべてのロギングは stderr に出力する必要があります。
ステップ 3: ツールの追加
ツールは、AI アシスタントが呼び出すことができる関数です。これらは、ほとんどの MCP サーバーの核となります。src/index.ts に以下を追加します。
// ツール: 新しいタスクを作成する
server.tool(
"create_task",
"Create a new task in the project",
{
title: z.string().describe("The task title"),
description: z.string().describe("Detailed description of the task"),
assignee: z.string().describe("Name of the person assigned to the task"),
},
async ({ title, description, assignee }) => {
const id = `TASK-${nextId++}`;
const task: Task = {
id,
title,
description,
status: "todo",
assignee,
createdAt: new Date().toISOString(),
};
tasks.set(id, task);
return {
content: [
{
type: "text",
text: `Created task ${id}: "${title}" assigned to ${assignee}`,
},
],
};
}
);
// ツール: タスクを一覧表示する
server.tool(
"list_tasks",
"List all tasks, optionally filtered by status or assignee",
{
status: z
.enum(["todo", "in_progress", "done"])
.optional()
.describe("Filter by status"),
assignee: z.string().optional().describe("Filter by assignee name"),
},
async ({ status, assignee }) => {
let filtered = Array.from(tasks.values());
if (status) {
filtered = filtered.filter((t) => t.status === status);
}
if (assignee) {
filtered = filtered.filter((t) =>
t.assignee.toLowerCase().includes(assignee.toLowerCase())
);
}
if (filtered.length === 0) {
return {
content: [{ type: "text", text: "No tasks found matching the criteria." }],
};
}
const taskList = filtered
.map(
(t) =>
`- [${t.id}] ${t.title} (${t.status}) - Assigned to: ${t.assignee}`
)
.join("\n");
return {
content: [{ type: "text", text: taskList }],
};
}
);
// ツール: タスクのステータスを更新する
server.tool(
"update_task_status",
"Update the status of an existing task",
{
taskId: z.string().describe("The task ID (e.g., TASK-1)"),
status: z
.enum(["todo", "in_progress", "done"])
.describe("The new status"),
},
async ({ taskId, status }) => {
const task = tasks.get(taskId);
if (!task) {
return {
content: [{ type: "text", text: `Task ${taskId} not found.` }],
isError: true,
};
}
const oldStatus = task.status;
task.status = status;
return {
content: [
{
type: "text",
text: `Updated ${taskId}: ${oldStatus} → ${status}`,
},
],
};
}
);
ツールの構造
各ツールの登録には 4 つの要素があります:
- 名前 (Name) -- AI がツールを呼び出すために使用する一意の識別子
- 説明 (Description) -- AI にこのツールをいつ、なぜ使用するかを伝える
- スキーマ (Schema) -- パラメータを定義する Zod スキーマ(自動的に検証されます)
- ハンドラー (Handler) -- ツールを実行し、結果を返す非同期関数
ステップ 4: リソースの追加
リソースは、AI に読み取り専用のコンテキストを提供します。ツールとは異なり、アクションは実行せず、データのみを提供します。
// リソース: プロジェクトの概要
server.resource(
"project-summary",
"project://summary",
async (uri) => {
const totalTasks = tasks.size;
const byStatus = {
todo: Array.from(tasks.values()).filter((t) => t.status === "todo").length,
in_progress: Array.from(tasks.values()).filter(
(t) => t.status === "in_progress"
).length,
done: Array.from(tasks.values()).filter((t) => t.status === "done").length,
};
const summary = `# Project Summary
Total tasks: ${totalTasks}
- To Do: ${byStatus.todo}
- In Progress: ${byStatus.in_progress}
- Done: ${byStatus.done}
Completion rate: ${totalTasks > 0 ? Math.round((byStatus.done / totalTasks) * 100) : 0}%`;
return {
contents: [
{
uri: uri.href,
mimeType: "text/markdown",
text: summary,
},
],
};
}
);
ステップ 5: プロンプトの追加
プロンプトは、AI クライアントがユーザーに提供できる再利用可能なテンプレートです。構造化されたインタラクションパターンを定義します。
// プロンプト: スプリント計画
server.prompt(
"sprint-planning",
"Generate a sprint planning summary based on current tasks",
{
sprintName: z.string().describe("Name of the sprint (e.g., Sprint 23)"),
},
async ({ sprintName }) => {
const todoTasks = Array.from(tasks.values()).filter(
(t) => t.status === "todo"
);
const taskListText =
todoTasks.length > 0
? todoTasks.map((t) => `- ${t.id}: ${t.title} (${t.assignee})`).join("\n")
: "No pending tasks.";
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Create a sprint planning document for "${sprintName}". Here are the current unassigned/todo tasks:\n\n${taskListText}\n\nPlease organize them by priority, estimate story points, and suggest a sprint goal.`,
},
},
],
};
}
);
ステップ 6: サーバーの起動
src/index.ts の最後にサーバー起動コードを追加します。
// サーバーの起動
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Project Manager MCP server running on stdio");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});
ビルドとテストを実行します。
# ビルド
npm run build
# 直接実行してテスト (サーバーが stdin で MCP メッセージを待機しているのがわかります)
echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}' | node dist/index.js
ステップ 7: Claude Desktop への接続
Claude Desktop の設定ファイルにサーバーを追加します。
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"project-manager": {
"command": "node",
"args": ["/absolute/path/to/my-mcp-server/dist/index.js"]
}
}
}
Claude Desktop を再起動します。3 つの利用可能なツールを持つ project-manager サーバーが表示されるはずです。
ステップ 8: Cursor への接続
Cursor の場合は、ワークスペースのルートにある .cursor/mcp.json に MCP サーバーを追加します。
{
"mcpServers": {
"project-manager": {
"command": "node",
"args": ["/absolute/path/to/my-mcp-server/dist/index.js"]
}
}
}
Cursor を再起動すると、AI チャット内でツールが利用可能になります。
サーバーのテスト
MCP SDK には、デバッグ用のインスペクターツールが含まれています。
npx @modelcontextprotocol/inspector node dist/index.js
これにより、以下のことができる Web UI が開きます:
- 登録されたすべてのツール、リソース、プロンプトの確認
- テスト入力を用いたツールの呼び出し
- 生の MCP プロトコルメッセージの表示
- エラーのリアルタイムデバッグ
プロジェクト構造
最終的なプロジェクトは以下のようになります:
my-mcp-server/
src/
index.ts # メインサーバーファイル
dist/ # コンパイル出力
package.json
tsconfig.json
大規模なサーバーの場合は、ファイルを分割します:
my-mcp-server/
src/
index.ts # サーバーの設定と起動
tools/
tasks.ts # タスク関連ツール
reports.ts # レポート作成ツール
resources/
project.ts # プロジェクトリソース
prompts/
planning.ts # 計画プロンプトテンプレート
types.ts # 共通型定義
dist/
package.json
tsconfig.json
一般的なパターン
データベースへの接続
インメモリの Map をデータベースクライアントに置き換えます。
import Database from "better-sqlite3";
const db = new Database("./tasks.db");
db.exec(`
CREATE TABLE IF NOT EXISTS tasks (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
status TEXT DEFAULT 'todo',
assignee TEXT,
created_at TEXT DEFAULT (datetime('now'))
)
`);
認証の追加
MCP サーバーが外部 API にアクセスする場合、環境変数を介して資格情報を渡します。
{
"mcpServers": {
"project-manager": {
"command": "node",
"args": ["/path/to/dist/index.js"],
"env": {
"API_KEY": "your-secret-key",
"DATABASE_URL": "postgresql://localhost/mydb"
}
}
}
}
サーバー内でこれらにアクセスします。
const apiKey = process.env.API_KEY;
if (!apiKey) {
console.error("API_KEY environment variable is required");
process.exit(1);
}
まとめ
ツール(アクション)、リソース(データ)、プロンプト(テンプレート)という 3 つのプリミティブを理解すれば、MCP サーバーの構築は非常にシンプルです。MCP SDK がプロトコルの詳細をすべて処理してくれるため、アプリケーションにとって重要なロジックに集中できます。
MCP は、AI アシスタントが外部世界と対話するための標準になりつつあります。独自のサーバーを構築することで、AI がアクセスできるものや実行できることを完全に制御できるようになります。
もしあなたの AI プロジェクトに画像、動画、話すアバターなどのメディア生成が含まれる場合は、主要な AI メディアモデルをすべて網羅する統合 API である Hypereal AI をチェックしてみてください。
Hypereal AI を無料で試す -- 35 クレジット、クレジットカード不要。
