mcpField
I
implementing-mcp-tools

implementing-mcp-tools

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

implementing-mcp-tools について

PostHog のエンジニアリング向け MCP ツール実装スキルです。バックエンドの型、YAML 設定、ツールのライフサイクルを含め、API エンドポイントからのツール生成を案内します。

主な機能

  1. 01バックエンドスキーマ要件
  2. 02YAML によるツール定義
  3. 03ツール生成手順
  4. 04名前と CI の検証
  5. 05フィーチャーフラグによる制御
  6. 06非推奨化のガイド

活用シーン

  1. 01製品 API エンドポイントの Agent 公開
  2. 02生成ツールのスキーマ誤りの調査
  3. 03エンドポイント公開時のツール制御

MCP 対応

PostHog MCP ツールの実装方法を示し、YAML 定義から API 操作を MCP クライアント向けツールに変換する手順を説明します。

インストール

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

$npx skills add https://github.com/posthog/posthog --skill implementing-mcp-tools

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

詳細

MCP ツールの実装

完全なガイドは docs/published/handbook/engineering/ai/implementing-mcp-tools.md で確認してください。

クイックワークフロー

# 1. Only for a product with no YAML yet: create one with no tools.
#    --product discovers endpoints via their x-product attribution.
#    ViewSets in products/<name>/backend/ are auto-attributed via module
#    path. ViewSets elsewhere need
#    @extend_schema(extensions={"x-product": "<product>"}).
pnpm --filter=@posthog/mcp run scaffold-yaml -- --product your_product \
    --output ../../products/your_product/mcp/tools.yaml

# 2. List the product's operations that have no YAML entry, then add the ones agents need.
#    --add writes an enabled entry; title and description come from the API unless the YAML sets them.
#    Add --file <path> to write to a YAML file other than the product's tools.yaml.
pnpm --filter=@posthog/mcp run scaffold-yaml -- --candidates --product your_product
pnpm --filter=@posthog/mcp run scaffold-yaml -- --add your_product_things_list --product your_product

# 3. Configure the entry — description, and annotations for PATCH/POST/PUT
#    (scopes come from the API when omitted)
#    Place in products/<product>/mcp/*.yaml (preferred) or services/mcp/definitions/*.yaml

# 4. Add a HogQL system table in posthog/hogql/database/schema/system.py
#    and a model reference in products/posthog_ai/skills/querying-posthog-data/references/

# 5. Generate handlers and schemas
hogli build:openapi

# 6. Refresh the tool input schema snapshots (CI unit tests fail on a stale snapshot)
pnpm --filter=@posthog/mcp exec vitest run tests/unit/tool-schema-snapshots.test.ts -u
# A tool behind a new `feature_flag` needs that flag in the test's `featureFlags` map, set to the value that shows the tool:
# true for a plain gate, the variant string for a variant gate, a non-true value for a `disable` gate.

# 7. Only when the YAML uses ui_apps: regenerate the UI apps (CI checks they are current)
pnpm --filter=@posthog/mcp run generate:ui-apps

スキャフォールドの前に: まずバックエンドを修正する

コード生成パイプラインが正しいツールを生成できるのは、Django バックエンドが正しい型を公開している場合だけです。 全体像については型システムガイドを参照してください。

YAML をスキャフォールドする前に、次を確認してください:

  1. シリアライザーに明示的なフィールド型と help_text があること — これらは生成されるツールの Zod の .describe() までずっと引き継がれます。 説明が欠けていると、エージェントがパラメーターを推測することになります。 素の ListField() ではなく ListField(child=serializers.CharField()) を使用し、 JSONField のサブクラスには @extend_schema_field(PydanticModel) を付けて型付きの Zod 出力を得てください (パターンについては products/alerts/backend/presentation/views/alert.py を参照してください)。
  2. 素の ViewSet メソッドに @extend_schema(request=...) があること — これがないと、drf-spectacular がリクエストボディを検出できず、 生成されるツールが z.object({}) (パラメーターゼロ) になります。 serializer_class を持つ ModelViewSet は問題ありませんが、手動バリデーションを行う素の ViewSet はそうではありません。
  3. クエリパラメーターが @validated_request またはクエリスキーマー付きの @extend_schema を使用していること — そうでない場合、ブール値や配列のクエリパラメーターが生成コードで型の不一致を引き起こす可能性があります。

生成されたツールのスキーマが空または誤っている場合、修正箇所はほとんどの場合 YAML 設定ではなく Django 側です。 完全な監査チェックリストとビフォー・アフターの例については、improving-drf-endpoints スキルを使用してください。

MCP ツールを追加すべきタイミング

プロダクトが、エージェントが呼び出せるべき API エンドポイントを公開する場合です。 MCP ツールはアトミックなケイパビリティ (list、get、create、update、delete) であり、ワークフローではありません。

新しいエンドポイントを追加する場合は、それがエージェントからアクセス可能であるべきか確認してください。 そうである場合は、YAML 定義を追加してツールを生成してください。

ツール設計

ツールは基本的なケイパビリティであるべきです — アトミックな CRUD 操作と単純なアクションです。 エージェントはこれらのプリミティブを組み合わせて、より高レベルのワークフローを構築します。

良い例: 「フィーチャーフラグを一覧表示する」、「ID で実験を取得する」、「サーベイを作成する」。 悪い例: 「実験のセッション記録を検索する」 — 複数の関心事を束ねています。

ツール命名の制約

ツール名と機能識別子はビルド時および CI で検証されます。 違反するとビルドが失敗します。

ツール名
  • 形式: 小文字のケバブケース — [a-z0-9-] のみ、先頭・末尾のハイフンは不可
  • 長さ: 52 文字以下
  • 規約: domain-action、例: cohorts-create、dashboard-get、feature-flags-list
コンパクトなツールドメインに操作動詞を含めない

単一実行プロンプトは ToolDomainExtractor を使ってコンパクトなドメインインデックスを構築します。 大規模なファミリーは中間セグメントで分割されることがあります。 操作動詞のトリミングがないと、experiment-freeze-exposure は experiment ではなく冗長なドメイン experiment-freeze を提示してしまう可能性があります。

アクションツールを追加または名前変更するときは常に、同じ変更内で TRAILING_ACTIONS セットを確認してください。レンダリングされたドメインが、まだ存在しない操作動詞で終わる可能性がある場合は、その動詞を追加してください。services/mcp/tests/unit/instructions.test.ts でカバーしてください。これは、その動詞がツール名全体の最終セグメントでない場合でも適用されます。

freeze、publish、emit などの操作動詞を追加してください。プロンプトを短くするためだけに config、logs、stats、schedule などのリソースやケイパビリティの名詞を追加しないでください。これらは依然として有用なディスカバリードメインです。

機能識別子
  • 形式: 小文字のスネークケース — [a-z0-9_] のみ、文字で始まる必要があります
  • 規約: プロダクトフォルダー名と一致させるべきです、例: error_tracking、feature_flags
なぜ 52 文字なのか?

MCP クライアントはツール名にそれぞれ異なる制限を課しています。52 文字の制限は、既知のすべてのクライアントで機能する安全な範囲です:

クライアント制限備考
MCP 仕様(ドラフト)1~128 文字、[A-Za-z0-9_\-.]公式の推奨であり、強制ではありません
Claude Code64 文字ハード制限。ツール名の先頭に mcp____ を付けます
Cursor合計 60 文字server_name + tool_name。これを超えるツールは通知なくフィルタリングされます
OpenAI API^[a-zA-Z0-9_-]+$、64 文字ドットは使用できません

サーバー名 "posthog"(7 文字)にセパレーターを加えると、Cursor の合計 60 文字の制限に収めるには、ツール名は 52 文字以下に保つ必要があります。

CI による強制
  • pnpm --filter=@posthog/mcp lint-tool-names — YAML および JSON 定義の長さとパターンを検証します
  • vitest テストが、実行時のすべての TOOL_MAP および GENERATED_TOOL_MAP エントリを検証します

YAML 定義

YAML ファイルは、どの操作を MCP ツールとして公開するかを設定します。 パターンについては既存の定義を参照してください:

  • products/<product>/mcp/*.yaml — 推奨。設定をコードの近くに保ちます
  • services/mcp/definitions/*.yaml — 製品フォルダーがない機能向けのフォールバック

ビルドパイプラインは両方のパスから YAML ファイルを検出します。

主要なフィールド
category: Human readable name
feature: snake_case_name # should match the product folder name (used for runtime filtering)
url_prefix: /path # frontend app route, used for enrich_url links
tools:
  your-tool-name: # kebab-case
    operation: operationId_from_openapi
    enabled: true
    # Optional:
    scopes: # defaults to the scopes the API requires (from the OpenAPI spec)
      - your_product:read
    annotations: # defaults for GET and DELETE; required for PATCH, POST and PUT
      readOnly: true
      destructive: false
      idempotent: true
    title: List things
    description: >
      Human-friendly description for the LLM.
    list: true
    enrich_url: '{id}'
    param_overrides:
      name:
        description: Custom description for the LLM
    response: # filter response fields (applied per-item on list endpoints)
      include: [id, key, name] # keep only these fields (dot-path wildcards supported)
      exclude: [filters.groups.*.properties] # remove these fields
      # include and exclude are mutually exclusive
      selectable: true # add optional `fields` param so the agent picks a subset of `include` per call
      # (constrained to the allowlist); omit `fields` to return the full set. Requires `include`.
      strip_nulls: true # remove keys whose value is `null`, applied after include/exclude
      # Use it on tools that echo a nested serializer schema, where the unset optional fields
      # dominate the payload. Rejected with `list: true`, where per-row null removal makes the
      # TOON table larger. Use `exclude` to drop the fields on a list tool instead.
    feature_flag: my-flag-key # gate this tool behind a PostHog feature flag
    feature_flag_behavior: enable # 'enable' (default) or 'disable'

scopes を省略した場合、ジェネレーターは API が要求するスコープを使用するため、ツールがエンドポイントから乖離することはありません。 scopes を手動で設定するのは、API がリクエストごとにスコープを計算する場合(ジェネレーターが失敗し、その旨を報告します)、またはツールをより厳密に制限する場合だけにしてください。 API が要求するスコープを欠いた scopes リストはコード生成に失敗し、CI 上で GitHub アノテーションが付きます。 API がリクエストごとにスコープを選択する場合、そのアクションをビューセットの request_dependent_scope_actions にリストします。 すると仕様がその操作を x-request-dependent-scopes でマークし、コード生成はそのチェックをスキップし、そのツールは scopes を宣言しなければなりません。 annotations はデフォルトで、GET(読み取り専用)と DELETE(破壊的)の HTTP メソッドになります。 PATCH、POST、PUT はばらつきが大きすぎる(PATCH はソフト削除や非冪等になり得ます)ため、それらには annotations を宣言してください。

ツールがリクエスト周辺でカスタムロジックを必要とする場合は、hooks: <src/tools/ 配下のパス> を設定し、そのモジュールから beforeRequest、afterResponse、または onError を持つオブジェクトをデフォルトエクスポートします。export default { onError } satisfies ToolHooks<Params> と記述することで、タイプミスが型チェックで失敗します(src/tools/tool-hooks.ts の ToolHooks を参照)。 生成されたツールを手書きのツールでシャドーイングするのではなく、書き込みの前に状態を読み取ったり、既知のエラーを結果に変換したりするために使用してください。

不明なキーはビルド時に拒否されます(Zod の .strict())。

フィーチャーフラグによるツールの制限

任意のツール(標準またはクエリラッパー)に feature_flag を追加すると、MCP の初期化時に現在のユーザーに対して評価される PostHog フィーチャーフラグに基づいて、そのツールの公開を制限できます。

  • feature_flag_behavior: enable(デフォルト) — ツールはフラグがオンのときだけ表示されます。新しいツールのロールアウトに使用します。
  • feature_flag_behavior: disable — ツールはフラグがオンのときに非表示になります。古いツールの廃止に使用します。

同じフラグキーを両方の動作で再利用すると、アトミックスワップが実行されます。フラグがオン → 新しいツールが表示され、古いツールは非表示。フラグがオフ → 古いツールが表示され、新しいツールは非表示。ツールのバリエーションの A/B テストに便利です。

フラグは初期化時に evaluateFeatureFlags を介して並行して評価されます。フラグを評価できない場合(サービスエラー、フラグの欠落)、enable で制限されたツールは除外され、disable で制限されたツールは含まれます。つまり、新しいツールについてはフェイルクローズド、既存のツールについてはフェイルオープンです。

ツールの有効化または名前変更

MCP サーバーと Django は別々にデプロイされます。 ルートが反映される前にクライアントに到達したツールは、Django のデプロイが追いつくまで、すべての呼び出しで 404 を返します。 これはエージェントフリート全体に一度に影響します。

  • 先にルートを反映させます。 エンドポイントを出荷してから、後続の変更でツールを有効化します。真新しいルートと同じコミットで enabled: true になっているツールは、MCP サーバーがデプロイされるとすぐにクライアントで有効になります。
  • またはゲートします。 feature_flag を feature_flag_behavior: enable とともに追加し、ルートがサービスを提供し始めたらフラグをオンにします。
  • 名前変更の際は古い名前を保持します。 新しい名前がすべての環境にデプロイされるまで、以前のツール名を同じ オペレーションを指したまま YAML に残します。同じフラグキーに feature_flag_behavior: disable を設定して廃止すると、2 つがアトミックに切り替わります。
ツールの非推奨化

名前変更や削除には 2 つの段階があります。最初の段階で止めないでください。

  1. 呼び出し元の移行中はエイリアス。 古い名前を、現在のハンドラーを呼び出してレスポンスに _deprecation_notice を追加する薄いラッパーとして登録します。呼び出しは引き続き成功し、エージェントは新しい名前を学習します。services/mcp/src/tools/skills/deprecatedAliases.ts を参照してください。これは services/mcp/src/tools/index.ts の TOOL_MAP に展開されます。エイリアスは、置き換え先が同じ引数を受け入れる場合にのみ使用してください。
  2. エイリアスを削除する際はリダイレクト。 同じ変更で、古い名前を services/mcp/src/tools/exec.ts の DEPRECATED_TOOL_REDIRECTS に追加します。これにより、呼び出しは汎用の Unknown tool: "..." ではなく、置き換え先を示す deprecated_tool エラーで失敗します。 引数の変更がある場合はそのテキストに記載してください — self-driving-inbox-get のエントリを参照してください。

置き換え先がドロップインでない場合は、直接段階 2 に進んでください。名前が変更されたパラメータを黙って破棄するエイリアスは、失敗する呼び出しよりも悪いです。

古い名前がトラフィックを受信しなくなるまで、リダイレクトエントリを保持してください。isRecordableToolName は、サーバーが所有する名前、つまり稼働中のツールまたは DEPRECATED_TOOL_REDIRECTS のキーに対してのみ $mcp_exec_target_tool を記録します。エントリを早すぎるタイミングで削除すると、残りの呼び出しは MCP アナリティクスで帰属不能になり、古い名前をまだ何かが使用しているかどうかを判断できなくなります。

フィーチャーフラグが削除するツールは別のケースです。これは定義を保持し、YAML で superseded_by を宣言し、flagGatedToolMessage が呼び出しに応答します。

オペレーションを意図的にオフのままにする

ツールはオプトインです。YAML エントリのないオペレーションは公開されず、新しいエンドポイントに対してエントリを書き込むものはありません。 enabled: false のエントリを書くのは、決定を記録するためだけです。たとえば、別のツールに取って代わられたツールなどです。 そのようなエントリには disabled_reason: <why> が必要です。同期は、それがない enabled: false のエントリを残骸スタブとして削除し、コード生成は有効なツールに対する disabled_reason を拒否します。

エンドポイント変更後の同期
pnpm --filter=@posthog/mcp run scaffold-yaml -- --sync-all

冪等で、エントリを追加することはありません。オペレーションが存在するすべてのエントリを保持し、番号が振り直された _N オペレーション ID を更新し、 オペレーションが存在しなくなった無効なエントリを削除し、オペレーションが存在しなくなった有効なツールでは失敗します。

シリアライザーの説明

説明はパイプライン全体を流れます。

Django serializer field → OpenAPI spec → Zod schema → MCP tool description

これらの説明は、エージェントがツールのパラメータを理解するために読むものです。

  • シリアライザーのフィールドには help_text を使用してください — これが OpenAPI の説明になります。
  • YAML の param_overrides を使用して、生成された説明を命令形の指示で上書きしてください。
  • フォーマット、制約、有効な値について具体的に記述してください。
  • LLM がコンテキストなしでは理解できない専門用語は避けてください。

HogQL システムテーブル

すべての list/get エンドポイントには、posthog/hogql/database/schema/system.py に対応する HogQL システムテーブルが必要です。 これにより、エージェントは SQL 経由でデータをクエリできます。

各システムテーブルには、データ分離のために team_id カラムを含める必要があります。

システムテーブルを追加する際は、products/posthog_ai/skills/querying-posthog-data/references/ にモデル参照ファイル (models-<domain>.md) も追加し、products/posthog_ai/skills/querying-posthog-data/SKILL.md の Data Schema に登録してください。

コメント