mcp について
MCP サーバーの構築、レビュー、デバッグに使う開発スキルです。実装手順に加え、ツール、トランスポート、認証、インジェクションのリスクを確認します。
主な機能
- 01サーバーの構築・レビュー手順
- 02ツール、リソース、プロンプトの設計
- 03トランスポートと OAuth 2.1 のガイド
- 04インジェクションとツールポイズニングの確認
- 05Inspector を使ったテストの案内
活用シーン
- 01ツールやリソースを備えた MCP サーバーの作成
- 02Streamable HTTP トランスポートの強化
- 03ハンドラーのインジェクションリスク確認
- 04クライアントとサーバー間の MCP 接続のデバッグ
MCP 対応
AI コーディングエージェントにツール、リソース、プロンプトを公開する MCP サーバーの開発を案内し、トランスポート設定と接続のデバッグも扱います。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ iuliandita/ skills --skill mcpインストール後、Claude Code で/mcpを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/agent-hygiene.mdoutput-contract.mdsecurity.md詳細
MCP:Model Context Protocol サーバー開発
AI コーディングアシスタントにツール、リソース、プロンプトを公開する MCP サーバーを構築、レビュー、デバッグします。目標は、プロトコル仕様に準拠し、防止可能なインジェクションの脆弱性を抱えるサーバーをまた一つ増やすことのない、安全で適切に構成されたサーバーです。
対象バージョン(2026 年 10 月):
- MCP 仕様:2026-07-28(現行の安定版。ステートレスなコア、拡張フレームワークを備え、initialize/session ハンドシェイクはありません)
- TypeScript SDK:
@modelcontextprotocol/server、@modelcontextprotocol/client、@modelcontextprotocol/core2.3.0(モノリシックな@modelcontextprotocol/sdk1.32.0 はレガシー系列です。2026-10-02 の両リリースでは、クライアントのリダイレクト先を同一オリジンに制限しており、2.3.0 では 1 つのServerインスタンスにつき 1 接続を提供します。重大度の高い OAuth 認証情報リダイレクトに関する勧告(CVE-2026-104850)へのクライアント側の最低バージョンは、client 2.2.0 および sdk 1.31.0 です) - Python SDK:mcp 2.3.0(2.x 安定版。v1 から v2 への移行ガイドを確認してください)。重大度の高い OAuth 認証情報リダイレクトおよびセッション未解放に関する勧告(GHSA-qx49-fqc8-xw99、CVE-2026-59951)に対する最低バージョンは、2.2.0(または 1.x では 1.30.0)です
- プロトコルのトランスポート:stdio および Streamable HTTP。スタンドアロンの HTTP+SSE トランスポートは非推奨で、一時的なレガシーブリッジとしてのみ利用できます
使用するケース
- 新しい MCP サーバーを構築する(ツール、リソース、プロンプト)
- 既存の MCP サーバーにツールハンドラーを追加する
- MCP トランスポートを設定する(ローカルでは stdio、リモートでは Streamable HTTP)
- MCP 認証(OAuth 2.1)を実装する
- MCP エリシテーション(対話型ダイアログ)を実装する
- インジェクションやツールポイズニングの脆弱性がないか、MCP サーバーのコードをレビューする
- クライアントとサーバー間の MCP 接続の問題をデバッグする
- 独自のツール統合から MCP に移行する
使用しないケース
- MCP を使用しない一般的な REST API の開発 — backend-api を使用してください
- アプリケーションでの Claude API/Anthropic SDK の使用 — llm-app-development を使用してください
- コードベース全体にわたる既存サーバーのセキュリティ監査 — security-audit を使用してください(MCP のセクションがあります)
- MCP ブラウジングツールを使用したウェブページの閲覧やスクレイピング — ホストのブラウジングツールを使用してください
- LLM 向けプロンプトの作成(MCP のプロンプトリソースではありません)— prompt-generator を使用してください
AI による自己チェック
MCP サーバーのコードを生成またはレビューする際は、結果を提示する前に各項目を確認してください。
- すべてのツールハンドラーの入力がサーバー側で検証されている(シェルコマンド、SQL、ファイルパス、URL への生の文字列補間がない)
- すべてのツールに、
tools/listで空でなく、正確かつ簡潔な説明が設定されている(FastMCP は docstring から説明を取得するため、ドキュメント化されていない関数は空の説明で登録されます。また、一部のクライアントは長い説明を切り詰めます) - リソース URI が定義済みのスキームを使用し、使用前に検証されている
- エラーレスポンスで、生のスタックトレースではなく適切な MCP エラーコードが使用されている
- ユーザーデータを扱うリモートトランスポートに認証が実装されている(PKCE を使用する OAuth 2.1)
- ツールハンドラーやサーバー構成にシークレットがハードコードされていない
-
inputSchemaで、required、maxLength、制約を含む具体的な JSON Schema 型が使用されている - サーバーが正常なシャットダウンに対応している(SIGINT/SIGTERM でクリーンアップする)
- Streamable HTTP:ローカルの場合は
0.0.0.0ではなく127.0.0.1にバインドしている - Streamable HTTP:
Originヘッダーを検証している(DNS リバインディングを防止する) - ツール呼び出しにレート制限を設定している
- クライアント側のコードで、ツールのアノテーションを信頼できないものとして扱っている
- エリシテーションでパスワード、トークン、シークレットを要求していない
- 仕様バージョンを確認:トランスポート、認証、リソース、ツール、プロンプトが現在の MCP ドキュメントおよび SDK の動作と一致している
- ツールポイズニングを考慮:ツールの説明、動的メタデータ、サーバーの更新によって、権限がひそかに拡大されない
- SDK メソッドを検証:よくある間違い #8 を参照してください。メソッド名を作り出さず、実際の SDK ドキュメントで各 API 呼び出しを確認してください
- 複数領域にまたがるエージェントの衛生対策を適用している。「
references/agent-hygiene.md」を参照してください
パフォーマンス
- ツールスキーマを簡潔にし、レスポンスを小さくしてください。構造化されていない大きなツール出力は、モデルのコンテキストを浪費します。
- すべてのツール呼び出しで同じ大きなペイロードを返すのではなく、再利用可能なコンテキストにはリソースを使用してください。
- レイテンシーが重要な場合は読み取り専用の検索をバッチ処理し、副作用のあるツールは分離して監査可能にしてください。
ベストプラクティス
- MCP サーバーをセキュリティ境界として扱い、認証、認可を行い、副作用を明示的にログに記録してください。
- ツール名とスキーマを安定させ、破壊的変更はその場で意味を変えるのではなく、バージョン管理してください。
- 金銭を使う、インフラストラクチャを変更する、データを削除する、またはシークレットを公開するツールには、ユーザーの確認を必須にしてください。
ワークフロー
構築とレビュー: ステップ 1〜6 は、新しいサーバーを構築する場合を対象としています。既存の MCP サーバーコードをレビューする場合は、次の手順に従ってください。(1)ステップ 1 の質問で範囲を把握します。サーバーが使用するツール、トランスポート、認証を確認します。(2)ステップ 3 のインジェクション攻撃ベクトルと「AI による自己チェック」に照らして、各ツールハンドラーを監査します。(3)「よくある間違い」セクションを参照し、AI モデルが頻繁に導入するパターンを確認します。
サーバーを構築する際は、このチェックリストをコピーして進捗を記録してください。
- ステップ 1:目的、ツール、トランスポート、認証を決定する
- ステップ 2:正常なシャットダウンに対応するサーバーのひな形を作成する
- ステップ 3:すべてのツールハンドラーで、補間を使わずに入力を検証する
- ステップ 4:トランスポートを構成して強化する
- ステップ 5:エリシテーションで承認、拒否、キャンセルに対応する
- ステップ 6:Inspector と悪意ある入力のテストに合格する(失敗した場合は修正してステップ 3 に戻る)
ステップ 1:サーバーの目的を決定する
コードを書く前に、次の点を明確にしてください。
- どのようなツールを公開しますか? 各ツールは、AI が呼び出せる 1 つの操作です。
- どのようなリソースを提供しますか? リソースは、AI がアクセスできる読み取り専用データです。
- どのトランスポートを使用しますか? ローカルの CLI 統合には stdio、リモート/本番環境には streamable HTTP を使用します。
- どの認証方式を使用しますか? stdio では不要です。ユーザーデータを扱うリモートサーバーには OAuth 2.1 を推奨します。
- どの言語を使用しますか? TypeScript(最も成熟した SDK)または Python(よりシンプルで、FastMCP を使用)を選択します。
ステップ 2:サーバーのひな形を作成する
TypeScript(本番環境向けに推奨):
import { McpServer } from "@modelcontextprotocol/server";
import { serveStdio } from "@modelcontextprotocol/server/stdio";
import { readdir, readFile, stat } from "node:fs/promises";
import path from "node:path";
import * as z from "zod/v4";
const DOCS_ROOT = path.resolve(process.env.DOCS_ROOT ?? "./docs");
const MAX_DOCS = 1_000;
const MAX_DOC_BYTES = 256_000;
const config = { mode: "read-only" };
async function loadMarkdownDocs(): Promise<Array<{ title: string; body: string }>> {
const entries = await readdir(DOCS_ROOT, { withFileTypes: true });
const names = entries
.filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
.map((entry) => entry.name)
.sort()
.slice(0, MAX_DOCS);
const docs: Array<{ title: string; body: string }> = [];
for (const name of names) {
const filePath = path.join(DOCS_ROOT, name);
if ((await stat(filePath)).size > MAX_DOC_BYTES) continue;
docs.push({ title: name, body: await readFile(filePath, "utf8") });
}
return docs;
}
let docsPromise: ReturnType<typeof loadMarkdownDocs> | undefined;
function docsIndex(): ReturnType<typeof loadMarkdownDocs> {
docsPromise ??= loadMarkdownDocs();
return docsPromise;
}
function createServer(): McpServer {
const server = new McpServer({ name: "my-server", version: "1.0.0" });
server.registerTool(
"search_docs",
{
title: "Search docs",
description: "Search documentation by keyword",
inputSchema: z.object({
query: z.string().max(200).describe("Search query"),
limit: z.number().int().min(1).max(100).default(10),
}),
outputSchema: z.object({
results: z.array(z.object({ title: z.string(), snippet: z.string() })),
}),
},
async ({ query, limit }) => {
try {
const needle = query.normalize("NFKC").trim().toLocaleLowerCase();
if (!/[\p{L}\p{N}]/u.test(needle)) {
return { isError: true, content: [{ type: "text", text: "Query needs a letter or number." }] };
}
const results = (await docsIndex())
.flatMap(({ title, body }) => {
const text = `${title}\n${body}`;
const index = text.toLocaleLowerCase().indexOf(needle);
if (index < 0) return [];
const start = Math.max(0, index - 160);
const end = Math.min(text.length, index + needle.length + 320);
return [{ title, snippet: text.slice(start, end) }];
})
.slice(0, limit);
const output = { results };
return {
content: [{ type: "text", text: JSON.stringify(output) }],
structuredContent: output,
};
} catch (error: unknown) {
console.error("search_docs failed", error);
return { isError: true, content: [{ type: "text", text: "Search failed while loading documentation." }] };
}
}
);
server.registerResource(
"config",
"config://app/settings",
{ title: "Application config", mimeType: "application/json" },
async (uri) => ({
contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(config) }],
})
);
return server;
}
async function main(): Promise<void> {
const handle = await serveStdio(createServer);
let closing = false;
const shutdown = async (signal: NodeJS.Signals): Promise<void> => {
if (closing) return;
closing = true;
try {
await handle.close();
} catch (error: unknown) {
console.error(`Failed to shut down after ${signal}:`, error);
process.exitCode = 1;
}
};
process.once("SIGINT", () => void shutdown("SIGINT"));
process.once("SIGTERM", () => void shutdown("SIGTERM"));
}
void main().catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
serveStdio は stdio トランスポートを管理し、close() を呼び出すと固定されたサーバーインスタンスとトランスポートの両方を終了するハンドルを返します。stdio の診断情報はすべて stderr に出力してください。stdout はプロトコルの通信チャネルです。
Python(手早いプロトタイピングには FastMCP):
import json, re
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
docs = [{"title": "Getting started", "body": "Install the server and connect over stdio."}]
config = {"mode": "read-only"}
@mcp.tool()
def search_docs(query: str, limit: int = 10) -> dict[str, list[dict[str, str]]]:
"""Search documentation; return bounded title/snippet matches."""
if not 1 <= len(query) <= 200 or not 1 <= limit <= 20:
raise ValueError("Require a 1-200 character query and limit 1-20")
needle = re.sub(r"[^\w\s-]", "", query).strip().casefold()
if not any(char.isalnum() for char in needle):
raise ValueError("Query must contain a letter or number")
matches = [doc for doc in docs if needle in f"{doc['title']}\n{doc['body']}".casefold()]
return {"matches": [{"title": doc["title"][:120], "snippet": doc["body"][:240]}
for doc in matches[:limit]]}
@mcp.resource("config://app/settings")
def get_config() -> str:
"""Application configuration."""
return json.dumps(config)
if __name__ == "__main__":
mcp.run()
ステップ 3:ツールを安全に実装する
インジェクションは、MCP で最も多い脆弱性の種類です。すべてのツールハンドラーが攻撃対象になり得ます。
最も重要なルール:ユーザー入力をコマンド、クエリ、パスに絶対に埋め込まないでください。
MCP ツールでよくあるインジェクション経路:
| 経路 | 危険なパターン | 安全なパターン |
|---|---|---|
| シェル | コマンド文字列への埋め込み | 引数配列とパス検証を使った execFile |
| SQL | クエリ内での文字列連結 | $1 プレースホルダーを使ったパラメーター化クエリ |
| ファイルパス | readFile(userPath) を直接呼び出す | パスを解決し、許可リストに照らしてプレフィックスを検証する |
| URL | fetch(userUrl) を直接呼び出す | URL を解析し、許可リストに照らしてスキームとホストを検証する |
| テンプレート | 動的なコード評価 | コードを実行しないサンドボックス化されたテンプレートエンジン |
パストラバーサルの防止:
import path from "node:path";
import { realpath } from "node:fs/promises";
async function safeExistingPath(base: string, userInput: string): Promise<string> {
const baseReal = await realpath(base);
const targetReal = await realpath(path.resolve(baseReal, userInput));
const relative = path.relative(baseReal, targetReal);
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
throw new Error("Path traversal detected");
}
return targetReal;
}
このヘルパーを使ったツールハンドラーの変更前と変更後の例、および新しいファイルを作成するパターンについては、references/security.md(パストラバーサル)を読んでください。
SSRF の防止(ツールがユーザー入力から URL を取得する場合):
- プライベート IP 範囲をブロックする:
10.0.0.0/8、172.16.0.0/12、192.168.0.0/16 - リンクローカルをブロックする:
169.254.0.0/16(169.254.169.254のクラウドメタデータを含む) - ループバックをブロックする:
127.0.0.0/8 - 本番環境では HTTPS を必須にする
- チェックから使用までの DNS 解決を固定する(TOCTOU 対策)
ステップ 4:トランスポートを設定する
| トランスポート | 用途 | 認証の要否 | 備考 |
|---|---|---|---|
| stdio | ローカルツール、CLI 連携 | 不要 | ユーザーのプロセスとして実行されます。最も安全です。 |
| Streamable HTTP | リモート/複数クライアント対応のサーバー | 推奨 | 単一のエンドポイント。メッセージには POST を使用し、任意で SSE ストリーミングを利用できます。 |
スタンドアロンの HTTP+SSE トランスポート(spec 2024-11-05)は非推奨です。リモートサーバーには Streamable HTTP を使用してください。MCP 2026-07-28 では、コアプロトコルから initialize/session ハンドシェイクが削除されます。1 つのエンドポイントで旧式の 2025 クライアントにも対応する必要がある場合は、SDK の移行ヘルパーを使用してください。 仕様上、認証は任意ですが、ユーザーデータを扱うサーバーには強く推奨されます。認証を実装する場合は、PKCE を用いた OAuth 2.1 を使用してください。Dynamic Client Registration(DCR は必須ではなく、フォールバックです)よりも Client ID Metadata Documents を優先してください。
Streamable HTTP のセキュリティ:
- ローカルサーバーは
127.0.0.1にバインドする(0.0.0.0は絶対に使用しない) - すべてのリクエストで
Originヘッダーを検証する(DNS リバインディングの防止) - 2026-07-28 のリクエストはすべて自己完結型として扱い、
MCP-Session-Idを必須にしない - クライアントは
MCP-Protocol-Version: 2026-07-28と、_meta内のアイデンティティおよび機能を送信する - DNS リバインディング対策が組み込まれた
createMcpExpressApp()/createMcpHonoApp()の使用を検討する。これらはランタイム中立なコアパッケージではなく、別パッケージの@modelcontextprotocol/expressおよび@modelcontextprotocol/honoから提供されています
ステップ 5:エリシテーションを安全に扱う
MCP のエリシテーションを使うと、サーバーはタスクの途中でユーザーに構造化された入力を求められます。
スキーマの制限 — エリシテーションのスキーマは、プリミティブなフィールドを持つフラットなオブジェクトに限られます。
string(任意のformat:email、uri、date、date-time)number/integer(minimum、maximumを指定可能)booleanenum(enumを指定した string。ラベル付きの選択肢にはtitleを使ったanyOfを使用)- enum 文字列の
array(複数選択用)
ネストされたオブジェクトは使用できません。幅広いクライアントに対応するため、スキーマはシンプルにしてください。
セキュリティ:エリシテーションを通じて認証情報を要求してはいけません。クライアントは、どのサーバーが入力を求めているかを表示し、いつでも辞退またはキャンセルできるようにしてください。accept(データあり)、decline、cancel の 3 つの応答すべてを処理してください。
ステップ 6:サーバーをテストする
command -v npx >/dev/null || echo "install Node.js to get npx for the Inspector"
command -v uv >/dev/null || echo "install uv for the Python dev inspector"
# Test with MCP Inspector (official debugging tool)
npx @modelcontextprotocol/inspector your-server-command
# Python alternative
uv run mcp dev server.py
各ツールハンドラーを、正常な入力(成功パス)、必須フィールドの欠落、悪意のある入力(インジェクション、パストラバーサル、過大なペイロード)、並行リクエストでテストします。ケースに失敗した場合は、ハンドラーを修正し(ステップ 3)、すべてのケースに合格するまで再実行します。
具体的なインジェクションテスト用ペイロードについては、references/security.md をお読みください。
ツールポイズニングとラグプルへの防御
これらの攻撃は、ツールの実行ではなくメタデータを標的にします。ツールの説明に隠された指示が含まれていたり、承認後に定義が変更されたりします。誠実で簡潔な説明を記述し、指示を埋め込まないようにしてください。また、攻撃例とサーバー側およびクライアント側の防御策については、references/security.md(Tool Poisoning and Rug Pull Attacks)をお読みください。
よくある間違い
AI モデルは MCP サーバーのコードを生成する際、次のような間違いを繰り返し犯します。
- 文字列補間によるシェルコマンド - 最もよくある脆弱性です。システムコマンドには必ず引数配列を使用してください。
- サーバー側の検証漏れ -
inputSchemaを生成しても、ハンドラーでそれに対する検証を行わないことです。クライアントが検証を省略する場合があります。 - スキーマでの
"type": "string"のみの指定 -maxLength、pattern、その他の制約がありません。長さを問わず、あらゆる文字列を受け入れてしまいます。 - HTTP の
0.0.0.0へのバインド - ローカルサーバーがネットワークに公開されます。127.0.0.1を使用してください。 Originヘッダーの検証漏れ - ローカルサーバーに対する DNS リバインディングを可能にします。- エラーの詳細情報の漏えい - ツールの応答にスタックトレース、ファイルパス、データベースエラーが含まれます。
- トークンのパススルー - オーディエンス検証を行わずに、他のサービス向けの OAuth トークンを受け入れることです。
- SDK メソッドの幻覚 - 存在しない API 呼び出しをでっち上げることです。検証チェックリストの項目については、AI Self-Check を参照してください。
- エリシテーションのアクションの無視 -
acceptは処理する一方、decline/cancelでクラッシュすることです。 - グレースフルシャットダウンの欠如 - stdio サーバーに SIGINT/SIGTERM ハンドラーがありません。
参照ファイル
references/security.md- OAuth 2.1 の詳細、既知の CVE、インジェクションテスト用ペイロード、パストラバーサルと SSRF の防止、セッション管理、ツールポイズニングへの防御策を説明しています。認証の実装、ハンドラーの強化、サーバーのレビュー時にお読みください。
出力規約
規約の全文については、references/output-contract.md を参照してください。
- スキル名: MCP
- 成果物の分類:
audits - モード: 条件付き。既存のリポジトリ内容の分析、レビュー、監査、改善を依頼された場合は、
references/output-contract.mdの報告サイズと根拠に関する規則に従い、成果物をdocs/local/audits/mcp/<YYYY-MM-DD>-<slug>.mdに書き込みます。質問への回答、概念の説明、新しい成果物の作成、コンテンツの生成を依頼された場合は、規約に縛られずに回答します。 - 重要度の尺度:
P0 | P1 | P2 | P3 | info(共通規約を参照。監査/レビューのモードでのみ使用)。
関連スキル
- security-audit - より広範なセキュリティレビューの一環として MCP サーバーを監査する場合に使用します。security-audit スキルの ASI セクションと MCP セクションでは脆弱性のパターンを扱い、このスキルでは最初から正しくサーバーを構築する方法を扱います。
- code-review - セキュリティ以外の正確性について MCP サーバーのコードをレビューする場合に使用します。
- docker - 最小限の権限で MCP サーバーをコンテナ化する場合に使用します。
- llm-app-development - MCP ツールを呼び出すアプリケーションで Claude API/Anthropic SDK を使用する場合に使用します。Anthropic SDK の統合コードにはこのスキルではなく、llm-app-development を使用してください。
- backend-api - MCP プロトコルを使用しない一般的な REST/GraphQL API の開発に使用します。
- ウェブページの閲覧や操作には、ホストのブラウジングツールを使用してください。このスキルでは MCP サーバーを構築します。
- prompt-generator - LLM プロンプト(MCP のプロンプトリソースではありません)の作成に使用します。依頼が MCP サーバーの構築ではなくプロンプトエンジニアリングに関する場合は、こちらに振り分けてください。
ルール
- すべてのツール入力をサーバー側で検証してください。 クライアントやモデルを決して信用しないでください。明示的な型、範囲、制約を指定したスキーマ検証(Zod、Pydantic)を使用してください。
- 文字列補間によるシェル実行は禁止です。 システムコマンドには引数配列を使用してください。
- 説明は簡潔にしてください。 説明が長いと、一部のクライアントでは途中で切り詰められます。ツールの機能とパラメーターを説明する数文にとどめ、実装の詳細は含めないでください。
- ユーザーデータを扱う際は認証してください。 ユーザーデータにアクセスするリモートサーバーでは、PKCE を使用した OAuth 2.1 を使用してください。仕様上、認証は任意ですが、強く推奨されています。