mcpField
M
mcp-creator

mcp-creator

github.com/curiositech開発257210 回インストール2 日前に更新
リポジトリ

mcp-creator について

mcp-creatorは、本番環境向けMCPサーバーの構築を支援する開発者向けスキルです。設計、セキュリティ、エラー処理、性能、テストを扱い、テンプレートとリファレンスも提供します。

主な機能

  1. 01MCPサーバーの構成とツール設計
  2. 02入力検証とシークレット管理
  3. 03レート制限と認証境界
  4. 04構造化エラーと縮退処理
  5. 05接続プール、キャッシュ、テスト

活用シーン

  1. 01新しいMCPサーバーの作成
  2. 02APIやデータベース連携の保護
  3. 03サーバーエラーやテスト失敗の調査

MCP 対応

ツール、リソース、プロンプトの設計を含め、MCPサーバーの作成とテストを案内します。

インストール

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

$npx skills add https://github.com/curiositech/some_claude_skills --skill mcp-creator

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

同梱ファイル

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

CHANGELOG.md
.claude-plugin/
plugin.json
references/
architecture-patterns.md
security-hardening.md
testing-debugging.md
tool-design.md
templates/
authenticated-api.ts
basic-server.ts

詳細

MCP Creator

本番環境に対応した Model Context Protocol サーバーの構築に精通しています。適切なセキュリティ境界、堅牢なエラー処理、優れた開発者エクスペリエンスを備えた、安全で高性能な MCP を作成します。

このスキルを使用するタイミング

次のような場合に MCP を使用します:

  • 認証を伴う外部 API 統合
  • ステートフルな接続(データベース、WebSocket、セッション)
  • 設定を共有する複数の関連ツール
  • Claude と外部サービス間のセキュリティ境界
  • コネクションプーリングとリソース管理

次の用途には MCP を使用しないでください:

  • 純粋なドメイン知識(Skill を使用)
  • 複数ステップのオーケストレーション(Agent を使用)
  • ローカルでステートレスな処理(Script を使用)
  • 単純なファイル処理(Claude の組み込みツールを使用)

クイックスタート

# Scaffold new MCP server
npx @modelcontextprotocol/create-server my-mcp-server

# Install SDK
npm install @modelcontextprotocol/sdk

# Test with inspector
npx @modelcontextprotocol/inspector

MCP アーキテクチャの概要

┌─────────────────────────────────────────────────────────────┐
│                        Claude                               │
└─────────────────────────┬───────────────────────────────────┘
                          │ MCP Protocol (JSON-RPC)
┌─────────────────────────┴───────────────────────────────────┐
│                    MCP Server                               │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐ │
│  │   Tools     │  │  Resources  │  │     Prompts         │ │
│  │ (actions)   │  │ (read-only) │  │ (templates)         │ │
│  └─────────────┘  └─────────────┘  └─────────────────────┘ │
│                          │                                  │
│  ┌───────────────────────┴──────────────────────────────┐  │
│  │           Auth / Rate Limiting / Caching              │  │
│  └───────────────────────┬──────────────────────────────┘  │
└──────────────────────────┼──────────────────────────────────┘
                           │
┌──────────────────────────┴──────────────────────────────────┐
│                    External Services                         │
│         APIs │ Databases │ File Systems │ WebSockets         │
└─────────────────────────────────────────────────────────────┘

MCP サーバーの基本テンプレート

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  ErrorCode,
  McpError,
} from "@modelcontextprotocol/sdk/types.js";

const server = new Server(
  { name: "my-mcp-server", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

// Tool definitions
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "my_tool",
      description: "Clear description of what this tool does",
      inputSchema: {
        type: "object",
        properties: {
          input: { type: "string", description: "Input description" },
        },
        required: ["input"],
      },
    },
  ],
}));

// Tool implementation
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === "my_tool") {
    try {
      const result = await processInput(args.input);
      return { content: [{ type: "text", text: JSON.stringify(result) }] };
    } catch (error) {
      throw new McpError(ErrorCode.InternalError, error.message);
    }
  }

  throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
});

// Start server
const transport = new StdioServerTransport();
await server.connect(transport);

ツール設計の原則

1. 分かりやすい名前
// ✅ Good: Action-oriented, specific
"get_user_profile"
"create_issue"
"analyze_sentiment"

// ❌ Bad: Vague, generic
"process"
"do_thing"
"handle"
2. 正確な入力スキーマ
// ✅ Good: Typed, constrained, documented
{
  type: "object",
  properties: {
    userId: { type: "string", pattern: "^[a-f0-9]{24}$" },
    action: { type: "string", enum: ["read", "write", "delete"] },
    limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }
  },
  required: ["userId", "action"],
  additionalProperties: false
}

// ❌ Bad: Untyped, unconstrained
{ type: "object" }
3. 構造化された出力
// ✅ Good: Consistent structure
return {
  content: [{
    type: "text",
    text: JSON.stringify({
      success: true,
      data: result,
      metadata: { requestId, timestamp }
    }, null, 2)
  }]
};

// ❌ Bad: Inconsistent, unstructured
return { content: [{ type: "text", text: "done" }] };

セキュリティの強化(重大)

入力の検証
import { z } from "zod";

const UserInputSchema = z.object({
  userId: z.string().regex(/^[a-f0-9]{24}$/),
  email: z.string().email(),
  query: z.string().max(1000).refine(
    (q) => !q.includes("--") && !q.includes(";"),
    { message: "Invalid characters in query" }
  ),
});

async function handleTool(args: unknown) {
  const validated = UserInputSchema.parse(args); // Throws on invalid
  // Safe to use validated data
}
シークレットの管理
// ✅ Good: Environment variables
const API_KEY = process.env.SERVICE_API_KEY;
if (!API_KEY) throw new Error("SERVICE_API_KEY required");

// ✅ Good: Secret manager integration
const secret = await secretManager.getSecret("service-api-key");

// ❌ NEVER: Hardcoded secrets
const API_KEY = "sk-abc123..."; // SECURITY VULNERABILITY
レート制限
class RateLimiter {
  private requests: Map<string, number[]> = new Map();

  canProceed(key: string, limit: number, windowMs: number): boolean {
    const now = Date.now();
    const timestamps = this.requests.get(key) || [];
    const recent = timestamps.filter(t => now - t < windowMs);

    if (recent.length >= limit) return false;

    recent.push(now);
    this.requests.set(key, recent);
    return true;
  }
}

const limiter = new RateLimiter();

// In tool handler
if (!limiter.canProceed(userId, 100, 60000)) {
  throw new McpError(ErrorCode.InvalidRequest, "Rate limit exceeded");
}
認証境界
// Validate credentials before any operation
async function withAuth<T>(
  credentials: Credentials,
  operation: () => Promise<T>
): Promise<T> {
  if (!await validateCredentials(credentials)) {
    throw new McpError(ErrorCode.InvalidRequest, "Invalid credentials");
  }
  return operation();
}

エラー処理のパターン

構造化されたエラーレスポンス
// Define error types
enum ServiceError {
  NOT_FOUND = "NOT_FOUND",
  UNAUTHORIZED = "UNAUTHORIZED",
  RATE_LIMITED = "RATE_LIMITED",
  VALIDATION_ERROR = "VALIDATION_ERROR",
  EXTERNAL_SERVICE_ERROR = "EXTERNAL_SERVICE_ERROR",
}

// Map to MCP errors
function toMcpError(error: unknown): McpError {
  if (error instanceof z.ZodError) {
    return new McpError(
      ErrorCode.InvalidParams,
      `Validation error: ${error.errors.map(e => e.message).join(", ")}`
    );
  }

  if (error instanceof ServiceError) {
    return new McpError(ErrorCode.InternalError, error.message);
  }

  return new McpError(ErrorCode.InternalError, "Unknown error occurred");
}
グレースフルデグラデーション
async function fetchWithFallback<T>(
  primary: () => Promise<T>,
  fallback: () => Promise<T>,
  options: { retries?: number; timeout?: number } = {}
): Promise<T> {
  const { retries = 3, timeout = 5000 } = options;

  for (let i = 0; i < retries; i++) {
    try {
      return await Promise.race([
        primary(),
        new Promise<never>((_, reject) =>
          setTimeout(() => reject(new Error("Timeout")), timeout)
        ),
      ]);
    } catch (error) {
      if (i === retries - 1) {
        console.error("Primary failed, trying fallback:", error);
        return fallback();
      }
      await new Promise(r => setTimeout(r, 1000 * (i + 1))); // Backoff
    }
  }
  throw new Error("All retries exhausted");
}

パフォーマンスの最適化

コネクションプーリング
// PostgreSQL pool
import { Pool } from "pg";

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

// Reuse connections
async function query(sql: string, params: unknown[]) {
  const client = await pool.connect();
  try {
    return await client.query(sql, params);
  } finally {
    client.release();
  }
}
キャッシュレイヤー
class Cache<T> {
  private store: Map<string, { value: T; expires: number }> = new Map();

  get(key: string): T | undefined {
    const entry = this.store.get(key);
    if (!entry) return undefined;
    if (Date.now() > entry.expires) {
      this.store.delete(key);
      return undefined;
    }
    return entry.value;
  }

  set(key: string, value: T, ttlMs: number): void {
    this.store.set(key, { value, expires: Date.now() + ttlMs });
  }
}

const cache = new Cache<ApiResponse>();

async function fetchWithCache(url: string): Promise<ApiResponse> {
  const cached = cache.get(url);
  if (cached) return cached;

  const response = await fetch(url);
  const data = await response.json();
  cache.set(url, data, 300000); // 5 min TTL
  return data;
}

アンチパターン

アンチパターン:入力を検証しない

例:ユーザー入力を API やデータベースに直接渡す 問題点:SQL インジェクション、コマンドインジェクション、データ破損 代わりに:Zod で検証し、入力をサニタイズし、パラメーター化クエリを使用します

アンチパターン:コード内のシークレット

例:API キーやトークンをソースコードにハードコードする 問題点:シークレットが git、ログ、エラーメッセージを通じて漏えいします 代わりに:環境変数、シークレットマネージャー、暗号化された設定を使用します

アンチパターン:レート制限がない

例:外部サービスへの API 呼び出しが無制限 問題点:コストの急増、API の利用停止、リソースの枯渇 代わりに:トークンバケット、スライディングウィンドウ、または適応型レート制限を使用します

アンチパターン:同期的なブロッキング

例:非同期ハンドラー内での sleep() やブロッキング I/O 問題点:すべてのリクエストをブロックし、タイムアウトを引き起こします 代わりに:適切な async/await とノンブロッキングパターンを使用します

アンチパターン:失敗を黙殺する

例:空の catch ブロック、エラーの握りつぶし 問題点:デバッグが不可能になり、データ破損を検知できません 代わりに:構造化されたエラー処理、ロギング、適切なエラー伝播を行います

アンチパターン:タイムアウトがない

例:外部サービスからの応答を無期限に待機する 問題点:接続がハングし、リソースがリークします 代わりに:すべての外部呼び出しに明示的なタイムアウトとサーキットブレーカーを設定します

MCP のテスト

MCP Inspector の使用
# Start inspector
npx @modelcontextprotocol/inspector

# In another terminal, start your server
node dist/index.js

# Connect inspector to your server
# Test tool invocations manually
単体テスト
import { describe, it, expect } from "vitest";

describe("my_tool", () => {
  it("should validate input", async () => {
    await expect(
      handleTool({ userId: "invalid" })
    ).rejects.toThrow("Invalid userId format");
  });

  it("should return structured output", async () => {
    const result = await handleTool({ userId: "507f1f77bcf86cd799439011" });
    expect(result).toHaveProperty("success", true);
    expect(result).toHaveProperty("data");
  });
});

MCP に追加するタイミング:判断フロー

Does this tool need...
├── External API with auth? → Add to MCP
├── Persistent state/connection? → Add to MCP
├── Rate limiting for external service? → Add to MCP
├── Shared credentials with other tools? → Add to MCP
├── Security boundary from Claude? → Add to MCP
└── None of the above? → Consider Script instead

成功指標

指標目標
ツールのレイテンシー P95< 500ms
エラー率< 1%
入力検証のカバレッジ100%
シークレットの露出0
レート制限違反0

参考ファイル

ファイル内容
references/architecture-patterns.mdトランスポート層、サーバーのライフサイクル、リソース管理
references/tool-design.mdスキーマのパターン、命名規則、出力形式
references/security-hardening.mdOWASP に準拠したセキュリティチェックリストの全容
references/error-handling.mdエラーの種類、復旧戦略、ロギング
references/testing-debugging.mdInspector の使用方法、単体テスト/統合テスト
references/performance.mdキャッシュ、プーリング、非同期パターン
templates/本番環境に対応したサーバーテンプレート

作成するもの:安全で高性能な MCP サーバー | 堅牢なツールインターフェース | セキュリティを強化した統合

併用するツール:security-auditor(セキュリティレビュー) | site-reliability-engineer(デプロイ) | agent-creator(MCP がエージェントをサポートする場合)

コメント