implementing-mcp-tools — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited implementing-mcp-tools (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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 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. 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/querying-posthog-data/references/
# 4. Generate handlers and schemas
hogli build:openapiThe 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:
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).
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.
@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-listerror_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.
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
scopes:
- your_product:read
annotations:
readOnly: true
destructive: false
idempotent: true
# Optional:
mcp_version: 1 # 2 for create/update/delete ops, 1 for read/list if available via HogQL
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
feature_flag: my-flag-key # gate this tool behind a PostHog feature flag
feature_flag_behavior: enable # 'enable' (default) or 'disable'Unknown keys are rejected at build time (Zod .strict()).
Add feature_flag to any tool (standard or query wrapper) to gate its exposure on a PostHog feature flag evaluated at MCP init time for the current user.
feature_flag_behavior: enable (default) — tool is shown only when the flag is on. Use for rolling out new tools.feature_flag_behavior: disable — tool is hidden when the flag is on. Use for sunsetting old tools.Reusing the same flag key with both behaviors performs an atomic swap: flag on → new tool visible, old tool hidden; flag off → old tool visible, new tool hidden. Useful for A/B testing tool variations.
Flags are evaluated in parallel at init via evaluateFeatureFlags. If a flag can't be evaluated (service error, missing flag), enable-gated tools are excluded and disable-gated tools are included — fail-closed for new tools, fail-open for existing ones.
pnpm --filter=@posthog/mcp run scaffold-yaml -- --sync-allIdempotent and non-destructive — adds new operations as enabled: false, removes stale ones.
Descriptions flow through the entire pipeline:
Django serializer field → OpenAPI spec → Zod schema → MCP tool descriptionThese descriptions are what agents read to understand tool parameters.
help_text on serializer fields — it becomes the OpenAPI description.param_overrides in YAML to override generated descriptions with imperative instructions.Every list/get endpoint should have a corresponding HogQL system table in posthog/hogql/database/schema/system.py. This lets agents query data via SQL in v2 of the MCP.
Each system table must include a `team_id` column for data isolation.
Use mcp_version: 1 on read/list YAML tools when a system table covers the same data — v2 agents use SQL instead.
When adding a system table, also add a model reference file (models-<domain>.md) in products/posthog_ai/skills/querying-posthog-data/references/ and register it in products/posthog_ai/skills/querying-posthog-data/SKILL.md under Data Schema.
Control per-tool availability with mcp_version: 1/2 in the YAML definition.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.