初めてのMCPサーバー構築:開発者向け実践ガイド
Engineering Team
Model Context Protocol(MCP)とは?
Model Context Protocol(MCP) は、Anthropicが開発したオープンスタンダードであり、AIアプリケーションが外部データソースやツールと通信する方法を定義します。MCPは AI統合のためのUSB-C と考えてください。USB-Cが充電、データ転送、ディスプレイ出力のための単一のユニバーサルコネクタを提供するように、MCPはAIモデルをデータベース、API、ファイルシステム、その他のサービスに接続するための単一のユニバーサルプロトコルを提供します。MCPにより、すべてのAIアプリケーションとすべてのツール間のカスタム統合が不要になります。
2026年現在、MCPはAIツール通信の主要なスタンダードとなっています。Gartnerは 2026年末までにエンタープライズアプリケーションの40%がAIエージェントを含む と予測しており、MCPはこれを大規模に実現可能にするプロトコルです。MCP以前は、AIモデルをN個のツールに接続するにはN個のカスタム統合が必要でした。M個のAIアプリケーションとN個のツールでは、M x N個の統合アダプタが必要でした。MCPはこれをM + Nに削減します。すべてのAIアプリが1つのMCP clientを実装し、すべてのツールが1つのMCP serverを実装します。
MCPアーキテクチャ:Host、Client、Server、Transport
コードを書く前にMCPアーキテクチャを理解することが重要です。プロトコルは4つの主要な役割を定義しています:
| コンポーネント | 役割 | 例 |
|---|---|---|
| Host | エンドユーザーが操作するAIアプリケーション | Claude Desktop、Cursor、VS Code Copilot |
| Client | Host内のMCPプロトコルハンドラー | Hostアプリケーションに組み込み |
| Server | MCPを通じてtools、resources、promptsを公開 | カスタムサーバー(これから構築するもの) |
| Transport | ClientとServer間の通信レイヤー | stdio、HTTP+SSE、Streamable HTTP |
通信フローは次のように動作します:
- Hostが起動し、設定された各サーバーに対してMCP Clientを初期化します。
- ClientがTransport(ローカルはstdio、リモートはHTTP)を介してServerに接続します。
- Clientが
initializeリクエストを送信し、プロトコルバージョンと機能をネゴシエートします。 - Serverが利用可能なtools、resources、promptsを応答します。
- AIモデルが外部データやアクションを必要とする場合、Clientが適切なサーバーメソッドを呼び出します。
- Serverが操作を実行し、JSON-RPC 2.0メッセージで結果を返します。
Tools vs Resources vs Prompts
MCP serverは3種類の機能を公開できます:
- Tools はAIモデルが呼び出せる関数です。構造化された入力を受け取り、構造化された出力を返します。例:
query_database(sql: string)やsend_email(to: string, subject: string, body: string)。 - Resources はAIモデルが読み取れるデータです。URIで識別され、コンテンツを返します。例:
file:///path/to/document.mdやpostgres://localhost/mydb/users。 - Prompts はサーバーが提供する再利用可能なプロンプトテンプレートです。AIがサーバーのドメインとやり取りする方法を標準化します。例:Jira MCP serverの
summarize_ticketプロンプトテンプレート。
ステップバイステップ:TypeScriptでMCPサーバーを構築
SQLiteデータベースにクエリを実行するツールを提供する実用的なMCPサーバーを構築しましょう。これは一般的なユースケースで、AIモデルにアプリケーションデータへの安全な読み取り専用アクセスを提供します。
ステップ1:プロジェクトの初期化
mkdir mcp-sqlite-server && cd mcp-sqlite-server
npm init -y
npm install @modelcontextprotocol/sdk better-sqlite3
npm install -D typescript @types/node @types/better-sqlite3
npx tsc --init
tsconfig.jsonを設定:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"declaration": true
},
"include": ["src/**/*"]
}
ステップ2:サーバーの実装
src/index.tsを作成:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import Database from "better-sqlite3";
import { z } from "zod";
const db = new Database(process.env.DB_PATH || "./data.db", {
readonly: true,
});
const server = new McpServer({
name: "sqlite-query",
version: "1.0.0",
});
server.tool(
"query",
"SQLiteデータベースに対して読み取り専用SQLクエリを実行",
{
sql: z.string().describe("実行するSQL SELECTクエリ"),
},
async ({ sql }) => {
const normalized = sql.trim().toUpperCase();
if (!normalized.startsWith("SELECT")) {
return {
content: [{ type: "text", text: "エラー:SELECTクエリのみ許可されています。" }],
isError: true,
};
}
try {
const rows = db.prepare(sql).all();
return {
content: [{ type: "text", text: JSON.stringify(rows, null, 2) }],
};
} catch (error) {
return {
content: [{ type: "text", text: `クエリエラー:${(error as Error).message}` }],
isError: true,
};
}
}
);
server.tool(
"list_tables",
"データベース内のすべてのテーブルとスキーマを一覧表示",
{},
async () => {
const tables = db
.prepare(
`SELECT name, sql FROM sqlite_master
WHERE type='table' AND name NOT LIKE 'sqlite_%'
ORDER BY name`
)
.all();
return {
content: [{ type: "text", text: JSON.stringify(tables, null, 2) }],
};
}
);
ステップ3:ビルドとローカルテスト
npx tsc
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | node dist/index.js
サーバーの機能を含むJSON-RPCレスポンスが表示されるはずです。
ステップ4:Pythonで同じサーバーを構築
Python開発者向けに、公式MCP Python SDKを使用した同等の実装を示します:
import sqlite3
import json
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("sqlite-query")
DB_PATH = "data.db"
@mcp.tool()
def query(sql: str) -> str:
"""SQLiteデータベースに対して読み取り専用SQLクエリを実行。"""
normalized = sql.strip().upper()
if not normalized.startswith("SELECT"):
raise ValueError("SELECTクエリのみ許可されています。")
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
try:
cursor = conn.execute(sql)
rows = [dict(row) for row in cursor.fetchall()]
return json.dumps(rows, indent=2, default=str)
finally:
conn.close()
@mcp.tool()
def list_tables() -> str:
"""データベース内のすべてのテーブルとスキーマを一覧表示。"""
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
try:
cursor = conn.execute(
"SELECT name, sql FROM sqlite_master "
"WHERE type='table' AND name NOT LIKE 'sqlite_%'"
)
tables = [dict(row) for row in cursor.fetchall()]
return json.dumps(tables, indent=2)
finally:
conn.close()
if __name__ == "__main__":
mcp.run(transport="stdio")
Claude Desktopへの接続
Claude DesktopはMCPサーバーをネイティブにサポートしています。サーバーを接続するには、Claude Desktop設定ファイルを編集します:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"sqlite-query": {
"command": "node",
"args": ["/absolute/path/to/dist/index.js"],
"env": {
"DB_PATH": "/absolute/path/to/your/data.db"
}
}
}
}
Claude Desktopを再起動します。チャットインターフェースにハンマーアイコンが表示され、利用可能なMCPツールが示されます。「データベースにはどんなテーブルがありますか?」や「登録日順にトップ10のユーザーを表示して」のようにClaudeに質問すると、MCPサーバーを使用してデータベースに直接クエリを実行します。
Cursorへの接続
CursorもMCPサーバーをサポートしています。プロジェクトルートの.cursor/mcp.jsonに設定を追加します:
{
"mcpServers": {
"sqlite-query": {
"command": "node",
"args": ["./dist/index.js"],
"env": {
"DB_PATH": "./data.db"
}
}
}
}
保存後、Cursorを再起動します。MCPツールがAIアシスタントパネルに表示され、コード生成やデバッグセッション中に呼び出すことができます。
MCPサーバーのテストとデバッグ
MCP Inspectorの使用
MCP Inspectorは公式デバッグツールです。サーバーとやり取りするためのWebインターフェースを提供します:
npx @modelcontextprotocol/inspector node dist/index.js
http://localhost:5173でブラウザインターフェースが開き、以下のことができます:
- 登録されたすべてのtools、resources、promptsを表示
- カスタム入力でツールを呼び出し、レスポンスを検査
- JSON-RPCメッセージストリームをリアルタイムで監視
- 不正なリクエストを送信してエラー処理をテスト
一般的なデバッグの問題
| 問題 | 原因 | 解決策 |
|---|---|---|
| サーバーがClaude Desktopに表示されない | 設定パスまたはJSON構文エラー | JSONを検証し、絶対パスを確認 |
| 「Tool not found」エラー | 接続前にツールが登録されていない | server.connect()を呼ぶ前にツールを登録 |
| ツール呼び出しのタイムアウト | 進捗なしの長時間実行操作 | server.sendProgress()で進捗通知を追加 |
| stderrの出力がプロトコルを破損 | console.logがstdoutに書き込み(stdioトランスポート) | stdioではロギングにconsole.error()を使用 |
| アイドル後の接続切断 | トランスポートのタイムアウト | ハートビートの実装またはHTTPトランスポートを使用 |
本番環境のベストプラクティス
-
入力バリデーション:常にツールの入力を検証しサニタイズします。TypeScriptではZodスキーマ、PythonではPydanticモデルを使用して厳密な型チェックを行います。
-
デフォルトで読み取り専用:読み取り専用アクセスから始めます。書き込み機能は本当に必要な場合にのみ追加し、破壊的な操作には必ず確認を求めます。
-
エラー処理:
isError: trueで構造化されたエラーメッセージを返します。内部スタックトレースやデータベース接続文字列を公開しないでください。 -
ロギング:すべてのツール呼び出しをタイムスタンプ、入力、実行時間とともにログに記録します。stdioトランスポート使用時はログにstderr(stdoutではなく)を使用します。
-
レート制限:制御されないAIループがバックエンドサービスを過負荷にすることを防ぐため、ツールごとのレート制限を実装します。
-
タイムアウト:すべてのツールハンドラーに実行タイムアウトを設定します。AIモデルが高コストなクエリをトリガーするツールを呼び出す可能性があります。インフラストラクチャを保護しましょう。
-
環境の分離:すべての設定に環境変数を使用します。データベースURL、APIキー、ファイルパスをハードコードしないでください。
-
バージョニング:MCPサーバーにセマンティックバージョニングを使用します。
initializeハンドシェイクにはバージョンネゴシエーションが含まれ、破壊的変更にはメジャーバージョンのバンプが必要です。
FAQ
Q:MCPとfunction callingの違いは何ですか? A:Function calling(OpenAI、Anthropicなどが使用)は、各APIリクエストにインラインでツールを定義します。MCPはツール定義をスタンドアロンサーバーに外部化し、MCP互換ホストが発見して使用できるようにします。Function callingはリクエストごと、MCPはステートフルなセッションを持つ永続的なプロトコルです。
Q:Claude以外のモデルでMCPを使用できますか? A:はい。MCPはオープンプロトコルです。OpenAI、Google DeepMind、Microsoftは2026年初頭からプラットフォームにMCPサポートを採用しています。MCP clientを実装するすべてのAIアプリケーションは、あらゆるMCP serverに接続できます。
Q:MCPはローカルツール専用ですか? A:いいえ。stdioトランスポートはローカルサーバー向けに設計されていますが、HTTP+SSEおよびStreamable HTTPトランスポートはリモートMCPサーバーをサポートします。MCPサーバーをネットワーク経由でアクセス可能なクラウドサービスとしてデプロイできます。
Q:MCPは認証をどのように処理しますか? A:リモートサーバーにはOAuth 2.0をサポートします。ローカルstdioサーバーはホストプロセスのセキュリティコンテキストを継承します。エンタープライズデプロイメントでは、MCPゲートウェイが認証と認可を一元化できます。
Q:MCPサーバーを構築するにはどの言語を使用できますか? A:公式SDKはTypeScript、Python、Java、Kotlin、C#、Swiftで利用できます。コミュニティSDKにはRust、Go、Ruby、PHPが含まれます。プロトコルは言語に依存しません。stdioまたはHTTPを介してJSON-RPCの読み書きができる言語であれば、MCPサーバーを実装できます。
Q:ホストを再起動せずにMCPサーバーを更新する方法は?
A:MCPは機能変更通知をサポートしています。サーバーのツールが変更された場合、サーバーはnotifications/tools/list_changedメッセージを送信し、clientにツールリストの再取得を促します。stdioサーバーの場合、通常ホストの再起動が必要です。HTTPサーバーはホストの再起動なしで更新できます。