Guide for exposing PostHog product endpoints as MCP tools. Use when creating new or updating API endpoints, adding MCP tool definitions, scaffolding YAML configs, or writing serializers with good descriptions. Covers the full pipeline from Django serializer to generated TypeScript tool handler.
Read the full guide at docs/published/handbook/engineering/ai/implementing-mcp-tools.md.
# 1. Scaffold a starter YAML with all operations disabled.
# --product discovers endpoints via x-explicit-tags (priority 1) then
# URL substring match (fallback). ViewSets in products/<name>/backend/
# are auto-tagged. ViewSets elsewhere need @extend_schema(tags=["<product>"]).
pnpm --filter=@posthog/mcp run scaffold-yaml -- --product your_product \
--output ../../products/your_product/mcp/tools.yaml
# 2. Configure the YAML — enable tools, add scopes, annotations, descriptions
# Place in products/<product>/mcp/*.yaml (preferred) or services/mcp/definitions/*.yaml
# 3. Add a HogQL system table in posthog/hogql/database/schema/system.py
# and a model reference in products/posthog_ai/skills/query-examples/references/
# 4. Generate handlers and schemas
hogli build:openapi
The codegen pipeline can only generate correct tools if the Django backend exposes correct types. Read the type system guide for the full picture.
Before scaffolding YAML, verify:
help_text —
these flow all the way to Zod .describe() in the generated tool.
Missing descriptions = agents guessing at parameters.
Use ListField(child=serializers.CharField()) instead of bare ListField(),
and @extend_schema_field(PydanticModel) on JSONField subclasses to get typed Zod output
(see posthog/api/alert.py for the pattern).ViewSet methods have @extend_schema(request=...) —
without it, drf-spectacular can't discover the request body
and the generated tool gets z.object({}) (zero parameters).
ModelViewSet with a serializer_class is fine; plain ViewSet with manual validation is not.@validated_request or @extend_schema with a query serializer —
otherwise boolean and array query params may produce type mismatches in the generated code.If a generated tool has an empty or wrong schema, the fix is almost always on the Django side,
not in the YAML config.
For a full audit checklist and before/after examples, use the improving-drf-endpoints skill.
When a product exposes API endpoints that agents should be able to call. MCP tools are atomic capabilities (list, get, create, update, delete) — not workflows.
If you're adding a new endpoint, check whether it should be agent-accessible. If yes, add a YAML definition and generate the tool.
Tools should be basic capabilities — atomic CRUD operations and simple actions. Agents compose these primitives into higher-level workflows.
Good: "List feature flags", "Get experiment by ID", "Create a survey". Bad: "Search for session recordings of an experiment" — bundles multiple concerns.
Tool names and feature identifiers are validated at build time and in CI. Violations fail the build.
[a-z0-9-], no leading/trailing hyphensdomain-action, e.g. cohorts-create, dashboard-get, feature-flags-list[a-z0-9*], must start with a lettererror_tracking, feature_flagsMCP clients enforce different limits on tool names. The 52-char limit is the safe zone that works across all known clients:
| Client | Limit | Notes |
|---|---|---|
| MCP spec (draft) | 1–128 chars, [A-Za-z0-9_\-.] | Official recommendation, not enforced |
| Claude Code | 64 chars | Hard limit; prefixes tool names with mcp____ |
| Cursor | 60 chars combined | server_name + tool_name; tools over this are silently filtered |
| OpenAI API | ^[a-zA-Z0-9_-]+$, 64 chars | No dots allowed |
With the server name "posthog" (7 chars) plus a separator, tool names must stay at or below 52 characters to fit within Cursor's 60-char combined limit.
pnpm --filter=@posthog/mcp lint-tool-names — validates length and pattern for YAML and JSON definitionsTOOL_MAP and GENERATED_TOOL_MAP entriesYAML files configure which operations are exposed as MCP tools. See existing definitions for patterns:
products/<product>/mcp/*.yaml — preferred, keeps config close to the codeservices/mcp/definitions/*.yaml — fallback for functionality without a product folderThe build pipeline discovers YAML files from both paths.