mcpField
M
mcp

mcp

github.com/iuliandita開発943 回インストール今日更新
リポジトリ

mcp について

MCP サーバーの構築、レビュー、デバッグに使う開発スキルです。実装手順に加え、ツール、トランスポート、認証、インジェクションのリスクを確認します。

主な機能

  1. 01サーバーの構築・レビュー手順
  2. 02ツール、リソース、プロンプトの設計
  3. 03トランスポートと OAuth 2.1 のガイド
  4. 04インジェクションとツールポイズニングの確認
  5. 05Inspector を使ったテストの案内

活用シーン

  1. 01ツールやリソースを備えた MCP サーバーの作成
  2. 02Streamable HTTP トランスポートの強化
  3. 03ハンドラーのインジェクションリスク確認
  4. 04クライアントとサーバー間の MCP 接続のデバッグ

MCP 対応

AI コーディングエージェントにツール、リソース、プロンプトを公開する MCP サーバーの開発を案内し、トランスポート設定と接続のデバッグも扱います。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/iuliandita/skills --skill mcp

インストール後、Claude Code で/mcpを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
agent-hygiene.md
output-contract.md
security.md

詳細

MCP:Model Context Protocol サーバー開発

AI コーディングアシスタントにツール、リソース、プロンプトを公開する MCP サーバーを構築、レビュー、デバッグします。目標は、プロトコル仕様に準拠し、防止可能なインジェクションの脆弱性を抱えるサーバーをまた一つ増やすことのない、安全で適切に構成されたサーバーです。

対象バージョン(2026 年 10 月):

  • MCP 仕様:2026-07-28(現行の安定版。ステートレスなコア、拡張フレームワークを備え、initialize/session ハンドシェイクはありません)
  • TypeScript SDK:@modelcontextprotocol/server、@modelcontextprotocol/client、@modelcontextprotocol/core 2.3.0(モノリシックな @modelcontextprotocol/sdk 1.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) を直接呼び出すパスを解決し、許可リストに照らしてプレフィックスを検証する
URLfetch(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 を指定可能)
  • boolean
  • enum(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 サーバーのコードを生成する際、次のような間違いを繰り返し犯します。

  1. 文字列補間によるシェルコマンド - 最もよくある脆弱性です。システムコマンドには必ず引数配列を使用してください。
  2. サーバー側の検証漏れ - inputSchema を生成しても、ハンドラーでそれに対する検証を行わないことです。クライアントが検証を省略する場合があります。
  3. スキーマでの "type": "string" のみの指定 - maxLength、pattern、その他の制約がありません。長さを問わず、あらゆる文字列を受け入れてしまいます。
  4. HTTP の 0.0.0.0 へのバインド - ローカルサーバーがネットワークに公開されます。127.0.0.1 を使用してください。
  5. Origin ヘッダーの検証漏れ - ローカルサーバーに対する DNS リバインディングを可能にします。
  6. エラーの詳細情報の漏えい - ツールの応答にスタックトレース、ファイルパス、データベースエラーが含まれます。
  7. トークンのパススルー - オーディエンス検証を行わずに、他のサービス向けの OAuth トークンを受け入れることです。
  8. SDK メソッドの幻覚 - 存在しない API 呼び出しをでっち上げることです。検証チェックリストの項目については、AI Self-Check を参照してください。
  9. エリシテーションのアクションの無視 - accept は処理する一方、decline/cancel でクラッシュすることです。
  10. グレースフルシャットダウンの欠如 - 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 サーバーの構築ではなくプロンプトエンジニアリングに関する場合は、こちらに振り分けてください。

ルール

  1. すべてのツール入力をサーバー側で検証してください。 クライアントやモデルを決して信用しないでください。明示的な型、範囲、制約を指定したスキーマ検証(Zod、Pydantic)を使用してください。
  2. 文字列補間によるシェル実行は禁止です。 システムコマンドには引数配列を使用してください。
  3. 説明は簡潔にしてください。 説明が長いと、一部のクライアントでは途中で切り詰められます。ツールの機能とパラメーターを説明する数文にとどめ、実装の詳細は含めないでください。
  4. ユーザーデータを扱う際は認証してください。 ユーザーデータにアクセスするリモートサーバーでは、PKCE を使用した OAuth 2.1 を使用してください。仕様上、認証は任意ですが、強く推奨されています。

コメント