Source profileQuality 90/100Review permissions

JSONbored/metagraphed/.claude/skills/mcp-analytics/SKILL.md

mcp-analytics

Add PostHog MCP analytics to a TypeScript, JavaScript, or Python MCP server. Instruments the server so every tool call, agent intent, and failure is captured as a $mcp_* event — the @posthog/mcp Node SDK for TS/JS, or posthog.mcp (shipped inside the posthog package) for Python. Detects the server style (official MCP SDK Server/McpServer/FastMCP, mcp-handler, @rekog/mcp-nest, jlowin fastmcp, or a custom HTTP/edge dispatcher) and wires in the matching instrumentation, credentials, and graceful shu

Source repository stars
12
Declared platforms
0
Static risk flags
2
Last source update
2026-07-28
Source checked
2026-07-28

Decision brief

What it does—and where it fits

Use this skill to instrument a user's own MCP server with PostHog MCP analytics. Once instrumented, every tool call, agent intent, and failure the server handles is captured as a $mcp event in PostHog — so the user can see which tools get used, what agents are trying to do, erro…

Best for

    Not for

    • Tasks that require unconfirmed production actions or broad system permissions.
    • Environments where the pinned source and install steps cannot be inspected.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/JSONbored/metagraphed --skill ".claude/skills/mcp-analytics"
    Safe inspection promptEditorial

    Inspect the Agent Skill "mcp-analytics" from https://github.com/JSONbored/metagraphed/blob/bd49f179f9b4597bd8467ab32783f27108e0bb28/.claude/skills/mcp-analytics/SKILL.md at commit bd49f179f9b4597bd8467ab32783f27108e0bb28. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      Instructions

      Follow these steps IN ORDER. Each step has a TypeScript / JavaScript part and a Python part — use the one for the language you detect in STEP 1.

      TypeScript / JavaScript — there's a package.json. Look for MCP signals in dependencies and source:@modelcontextprotocol/sdk — the official SDK (most common).mcp-handler — the Next.js / Vercel adapter.
    2. 02

      STEP 1: Identify the language and the MCP server entry point

      Determine the language first, then route to the matching instructions throughout:

      TypeScript / JavaScript — there's a package.json. Look for MCP signals in dependencies and source:@modelcontextprotocol/sdk — the official SDK (most common).mcp-handler — the Next.js / Vercel adapter.
    3. 03

      STEP 2: Choose the instrumentation path

      Pick exactly one based on what STEP 1 found. When in doubt, read the bundled reference docs — installation.md covers the wrapping paths; custom-servers.md covers the custom-dispatcher paths.

      Path A — official SDK server object (new Server(...) or new McpServer(...) from @modelcontextprotocol/sdk): wrap it with instrument(server, posthog). One line.Path B — mcp-handler (createMcpHandler((server) = { ... })): same instrument(server, posthog) call, inside the setup callback. Because Vercel's transport is stateless, also wire identify (STEP 4) and flush per invocatio…Path C — custom dispatcher (Hono / Express / Cloudflare Worker / edge function with no SDK server object to wrap): use the PostHogMCP client and call captureToolCall / captureInitialize yourself at the dispatch points.
    4. 04

      STEP 3: Install the SDK

      TypeScript / JavaScript: install @posthog/mcp and posthog-node with the project's package manager, pinning @posthog/mcp to its current published version (it's pre-1.0) — e.g. pnpm add @posthog/mcp@ posthog-node. Read th…

      TypeScript / JavaScript: install @posthog/mcp and posthog-node with the project's package manager, pinning @posthog/mcp to its current published version (it's pre-1.0) — e.g. pnpm add @posthog/mcp@ posthog-node. Read th…Python: the SDK ships inside posthog, so install (or require) posthog=7.21 with the project's installer — e.g. pip install "posthog=7.21", uv add posthog, poetry add posthog. The MCP SDK itself (mcp / fastmcp) is a peer…- TypeScript / JavaScript: install @posthog/mcp and posthog-node with the project's package manager, pinning @posthog/mcp to its current published version (it's pre-1.0) — e.g. pnpm add @posthog/mcp@ posthog-node. Read…
    5. 05

      STEP 4: Instrument the server

      Create the PostHog client once at module scope (never per request), reading credentials from env (set up in STEP 5).

      Create the PostHog client once at module scope (never per request), reading credentials from env (set up in STEP 5).Path A — official SDK server:instrument() is idempotent per server and returns an analytics handle (used later for custom events). It works on both the low-level Server and the high-level McpServer.

    Permission review

    Static risk signals and limitations

    Runs scripts

    medium · line 15

    The documentation asks the agent to run terminal commands or scripts.

    **This must be an MCP server.** Search thoroughly before concluding there isn't one: check dependency manifests _and_ source for the STEP 1 signals across the whole project, including monorepo workspace packages and subdirectories — a serve

    Network access

    medium · line 87

    The documentation includes network, browsing, or remote request actions.

    host: process.env.POSTHOG_HOST, // https://us.i.posthog.com or https://eu.i.posthog.com

    Network access

    medium · line 177

    The documentation includes network, browsing, or remote request actions.

    host=os.environ["POSTHOG_HOST"], # https://us.i.posthog.com or https://eu.i.posthog.com

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score90/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars12SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    JSONbored/metagraphed
    Skill path
    .claude/skills/mcp-analytics/SKILL.md
    Commit
    bd49f179f9b4597bd8467ab32783f27108e0bb28
    License
    AGPL-3.0
    Collected
    2026-07-28
    Default branch
    main
    View the original SKILL.md

    Add PostHog MCP analytics

    Use this skill to instrument a user's own MCP server with PostHog MCP analytics. Once instrumented, every tool call, agent intent, and failure the server handles is captured as a $mcp_* event in PostHog — so the user can see which tools get used, what agents are trying to do, error rates, and latency.

    There are two SDKs and this skill handles both:

    • TypeScript / JavaScript — the @posthog/mcp Node package.
    • Pythonposthog.mcp, which ships inside the posthog package (like posthog.ai).

    This is not about adding the PostHog MCP server to a coding agent (that's wizard mcp add). This skill instruments the user's own MCP server code so it reports analytics about itself.

    Scope and guardrails

    • TypeScript / JavaScript and Python are supported. Detect the language in STEP 1 and follow the matching part of every step. If the MCP server is written in anything else (Go, Rust, …), stop: emit [ABORT] unsupported language for mcp analytics on its own line and do nothing else.
    • This must be an MCP server. Search thoroughly before concluding there isn't one: check dependency manifests and source for the STEP 1 signals across the whole project, including monorepo workspace packages and subdirectories — a server is often under packages/*, apps/*, server/, or src/, not the repo root. Only after an exhaustive search finds nothing, stop: emit [ABORT] no mcp server found on its own line, and in the same message tell the user where you looked and to re-run the command from inside the package or directory that actually defines their MCP server. Do nothing else.
    • Beta SDK. Both SDKs are pre-1.0 and may ship breaking changes in minor releases. Pin a version (see STEP 3).
    • Minimal, additive changes only. Add instrumentation alongside the existing server; do not restructure tool handlers or change their behavior. The wrapper is designed to be one line.

    Abort cases

    If anything blocks instrumentation, always emit exactly one [ABORT] <reason> line and stop — never halt, finish, or error out silently. The wizard catches [ABORT] and terminates the run for you; don't try to exit yourself. A silent stop is recorded as a failed run with no reason, which can't be acted on, so every dead end must carry a reason. Use one of:

    • [ABORT] no mcp server found — an exhaustive search (see the guardrail above) found no MCP server in the project.
    • [ABORT] unsupported language for mcp analytics — the server is neither TypeScript/JavaScript nor Python.
    • [ABORT] could not locate the server entry point — MCP signals are present, but the place the server is constructed or where requests are dispatched couldn't be found to instrument.
    • [ABORT] <short specific reason> — anything else that blocks the run (e.g. no readable project, or no PostHog credentials and no MCP server connected to fetch them). Keep it short and specific so it's useful when aggregated across runs.

    Instructions

    Follow these steps IN ORDER. Each step has a TypeScript / JavaScript part and a Python part — use the one for the language you detect in STEP 1.

    STEP 1: Identify the language and the MCP server entry point

    Determine the language first, then route to the matching instructions throughout:

    • TypeScript / JavaScript — there's a package.json. Look for MCP signals in dependencies and source:

      • @modelcontextprotocol/sdk — the official SDK (most common).
      • mcp-handler — the Next.js / Vercel adapter.
      • @rekog/mcp-nest — the NestJS adapter (tools defined with @Tool() decorators; the server is built inside McpModule.forRoot(...), so there's no new McpServer in user code).
      • fastmcp, xmcp, or a similar TS MCP framework.
      • A custom HTTP/edge handler speaking the MCP protocol directly (JSON-RPC methods like tools/call, initialize, an Mcp-Session-Id header) with none of the above.

      Determine the package manager from the lockfile (pnpm-lock.yaml, package-lock.json, yarn.lock, bun.lockb).

    • Python — there's a pyproject.toml, requirements.txt, or setup.py, or .py sources. Look for MCP signals:

      • the official mcp package — from mcp.server.fastmcp import FastMCP or from mcp.server.lowlevel import Server.
      • jlowin's standalone fastmcp 2.0 — from fastmcp import FastMCP.
      • a custom HTTP/edge dispatcher (FastAPI / Starlette / Flask / edge) speaking the MCP protocol directly with no server object to wrap.

      Determine the installer (pip / uv / poetry) from the lockfile / pyproject.toml.

    • If it's neither TS/JS nor Python, apply the guardrail above and stop.

    Then identify the file and the exact place where the server is constructed or where MCP requests are dispatched, and read it before editing. If PostHog MCP analytics is already wired in (an instrument( call, or a PostHogMCP client — in either language), don't duplicate it: verify it's correct and skip to STEP 7.

    STEP 2: Choose the instrumentation path

    Pick exactly one based on what STEP 1 found. When in doubt, read the bundled reference docs — installation.md covers the wrapping paths; custom-servers.md covers the custom-dispatcher paths.

    TypeScript / JavaScript:

    • Path A — official SDK server object (new Server(...) or new McpServer(...) from @modelcontextprotocol/sdk): wrap it with instrument(server, posthog). One line.
    • Path B — mcp-handler (createMcpHandler((server) => { ... })): same instrument(server, posthog) call, inside the setup callback. Because Vercel's transport is stateless, also wire identify (STEP 4) and flush per invocation (STEP 6).
    • Path C — custom dispatcher (Hono / Express / Cloudflare Worker / edge function with no SDK server object to wrap): use the PostHogMCP client and call captureToolCall / captureInitialize yourself at the dispatch points.
    • Path D — @rekog/mcp-nest (NestJS): the framework builds the server, so there's no new McpServer for you to wrap. Instrument it through the module's serverMutator hook in McpModule.forRoot(...). See STEP 4.

    Python:

    • Path P1 — a FastMCP or low-level Server (the official mcp package's FastMCP/Server, or jlowin's fastmcp 2.0): wrap it with instrument(server, posthog). One line — the SDK detects which framework it is.
    • Path P2 — custom dispatcher (FastAPI / Starlette / Flask / edge with no server object to wrap): use the PostHogMCP client and call capture_tool_call / capture_initialize yourself at the dispatch points.

    STEP 3: Install the SDK

    • TypeScript / JavaScript: install @posthog/mcp and posthog-node with the project's package manager, pinning @posthog/mcp to its current published version (it's pre-1.0) — e.g. pnpm add @posthog/mcp@<latest> posthog-node. Read the installed version back from package.json / the lockfile rather than guessing.
    • Python: the SDK ships inside posthog, so install (or require) posthog>=7.21 with the project's installer — e.g. pip install "posthog>=7.21", uv add posthog, poetry add posthog. The MCP SDK itself (mcp / fastmcp) is a peer dependency you already have — you built the server with it — so don't add it. A custom-dispatcher (path P2) project needs nothing beyond posthog.

    STEP 4: Instrument the server

    Create the PostHog client once at module scope (never per request), reading credentials from env (set up in STEP 5).

    TypeScript / JavaScript

    import { PostHog } from "posthog-node";
    
    const posthog = new PostHog(process.env.POSTHOG_PROJECT_TOKEN, {
      host: process.env.POSTHOG_HOST, // https://us.i.posthog.com or https://eu.i.posthog.com
    });
    

    Path A — official SDK server:

    import { instrument } from "@posthog/mcp";
    
    const server = new McpServer({ name: "my-mcp-server", version: "1.0.0" });
    const analytics = instrument(server, posthog); // wrap immediately after constructing the server
    // register tools as usual — tools added after instrument() are still captured
    

    instrument() is idempotent per server and returns an analytics handle (used later for custom events). It works on both the low-level Server and the high-level McpServer.

    Path B — mcp-handler: call instrument(server, posthog) as the first line of the setup callback, with the posthog client created at module scope (not per request). Because the transport is stateless, group calls by user with identify:

    const handler = createMcpHandler((server) => {
      instrument(server, posthog, {
        identify: (request, extra) => ({ distinctId: getUserId(extra) }),
      });
      server.registerTool("...", {/* ... */}, async () => {
        /* ... */
      });
    });
    

    Path C — custom dispatcher: swap the existing PostHog client for PostHogMCP (a drop-in posthog-node subclass) and call the capture helpers at the dispatch points. Read custom-servers.md for the full field reference before editing.

    import { PostHogMCP } from "@posthog/mcp";
    
    const posthog = new PostHogMCP(process.env.POSTHOG_PROJECT_TOKEN, {
      host: process.env.POSTHOG_HOST,
    });
    
    // on the initialize handshake:
    posthog.captureInitialize({ clientName, clientVersion, distinctId });
    
    // after each tools/call resolves (wrap the existing handler, time it):
    const start = Date.now();
    // ...run the tool...
    posthog.captureToolCall({
      toolName: request.params.name,
      parameters: request.params.arguments,
      response: result,
      durationMs: Date.now() - start,
      isError: false,
      distinctId, // who the request is from, if known
      sessionId, // your transport/session id, if you have one
    });
    

    Resolve distinctId / sessionId from whatever auth/session the dispatcher already has; omit them rather than inventing values. These calls are fire-and-forget and never throw, so they can't take down a tool.

    Path D — @rekog/mcp-nest (NestJS): the framework builds the server, so pass a serverMutator to McpModule.forRoot(...). Prefer the instrumentMutator helper — it instruments the server and returns it, so it drops straight into the hook:

    import { Module } from "@nestjs/common";
    import { McpModule } from "@rekog/mcp-nest";
    import { PostHog, instrumentMutator } from "@posthog/mcp";
    
    const posthog = new PostHog(process.env.POSTHOG_PROJECT_TOKEN, {
      host: process.env.POSTHOG_HOST,
    });
    
    @Module({
      imports: [
        McpModule.forRoot({
          name: "my-mcp-server",
          version: "1.0.0",
          serverMutator: instrumentMutator(posthog),
        }),
      ],
    })
    class AppModule {}
    

    instrumentMutator returns the server (not instrument()'s handle), so it slots straight into the hook. Compose with an existing serverMutator if there is one, and handlers nest registers after the mutator runs are still captured. For custom events, call instrument() directly inside your own mutator and keep its handle, returning the server yourself.

    Python

    import os
    from posthog import Posthog
    
    posthog = Posthog(
        os.environ["POSTHOG_PROJECT_TOKEN"],
        host=os.environ["POSTHOG_HOST"],  # https://us.i.posthog.com or https://eu.i.posthog.com
    )
    

    Path P1 — FastMCP / low-level Server:

    from posthog.mcp import instrument
    
    server = FastMCP("my-mcp-server")
    analytics = instrument(server, posthog)  # wrap right after constructing the server
    # register tools as usual — tools added after instrument() are still captured
    

    instrument() is idempotent per server and returns an analytics handle (used later for custom events). The same call works on the official FastMCP, the low-level Server, and jlowin's fastmcp 2.0.

    Path P2 — custom dispatcher: swap the existing client for PostHogMCP (a drop-in posthog client subclass) and call the capture helpers at the dispatch points. Read custom-servers.md for the full field reference before editing.

    import time
    from posthog.mcp import PostHogMCP
    
    posthog = PostHogMCP(os.environ["POSTHOG_PROJECT_TOKEN"], host=os.environ["POSTHOG_HOST"])
    
    # on the initialize handshake:
    posthog.capture_initialize(client_name=client_name, client_version=client_version, distinct_id=distinct_id)
    
    # after each tools/call resolves (time it):
    start = time.monotonic()
    # ...run the tool...
    posthog.capture_tool_call(
        request.params.name,
        parameters=arguments,
        response=result,
        duration_ms=(time.monotonic() - start) * 1000,
        is_error=False,
        distinct_id=distinct_id,  # who the request is from, if known
        session_id=session_id,    # your transport/session id, if you have one
    )
    

    Resolve distinct_id / session_id from whatever auth/session the dispatcher already has; omit them rather than inventing values. These calls are fire-and-forget and never throw, so they can't take down a tool.

    STEP 5: Wire up credentials

    • Check existing env files (.env, .env.local, etc.) for a PostHog project token. If a valid phc_… token and host are already set, reference those and skip the rest of this step.
    • If the token is missing, use the PostHog MCP server's projects-get tool to fetch the project's api_token. If multiple projects come back, ask the user which to use. If the MCP server isn't connected, ask the user for their project token directly.
    • Host: https://us.i.posthog.com for US Cloud, https://eu.i.posthog.com for EU Cloud.
    • Write POSTHOG_PROJECT_TOKEN and POSTHOG_HOST to the appropriate env file and reference them in code (process.env.* in JS, os.environ[...] in Python) — never hardcode the token.

    STEP 6: Ensure events get flushed

    The PostHog client batches events; the user owns the client's lifecycle.

    TypeScript / JavaScript:

    • Long-running server (STDIO or a persistent HTTP server): drain on shutdown.

      process.on("SIGTERM", async () => {
        await posthog.shutdown();
        process.exit(0);
      });
      
    • Serverless / edge (mcp-handler on Vercel, Workers, Lambda): SIGTERM is unreliable — flush at the end of each invocation with await posthog.flush(), or ctx.waitUntil(posthog.flush()) where supported.

    • STDIO transports specifically: the server's stdout is the protocol channel. Do not add console.log for debugging — it corrupts the MCP stream. If you need SDK-internal warnings, pass a logger option to instrument() that writes to stderr or a file.

    Python:

    • Long-running server (STDIO or persistent HTTP): drain on exit. On the instrument() path, await analytics.flush() waits for in-flight auto-capture events, then posthog.shutdown() flushes and stops the client — call both from your shutdown path. For PostHogMCP, posthog.shutdown() (or posthog.flush()) drains the MCP captures first.
    • STDIO transports specifically: stdout is the protocol channel — never print() to it. For SDK-internal warnings pass logger=lambda m: print(m, file=sys.stderr) (or a file writer) via MCPAnalyticsOptions(...).

    STEP 7: Verify

    • TypeScript / JavaScript: run the project's type-check and/or build script (e.g. tsc --noEmit, pnpm build) and fix any errors your changes introduced. Run any linter/formatter the project uses on the files you touched.
    • Python: run the project's type-check / tests if present (mypy, pytest) and fix any errors your changes introduced. Run any formatter the project uses (ruff, black) on the files you touched.
    • Summarize for the user: which path you used, the files you changed, the env vars to set, and that they'll see $mcp_* events in PostHog once the server handles its next request. Link them to https://posthog.com/docs/mcp-analytics for the dashboard and event reference.

    Reference files

    • references/installation.md - Installing the mcp analytics SDK - docs
    • references/custom-servers.md - Instrumenting a custom server - docs
    • references/intent.md - Capturing agent intent - docs
    • references/identifying-users.md - Identifying users - docs
    • references/conversation-id.md - Conversation ids - docs
    • references/events.md - Event and property reference - docs
    • references/custom-events.md - Custom events and metadata - docs
    • references/start-here.md - Getting started with mcp analytics - docs
    • references/COMMANDMENTS.md - Framework-specific rules the integration must follow

    installation.md is the source of truth for the wrapping paths (A/B and Python P1) and the full instrument() options table (identify, context/intent, enableConversationId/enable_conversation_id, reportMissing/report_missing, beforeSend/before_send, eventProperties/event_properties). custom-servers.md is the source of truth for the custom-dispatcher paths (C and P2) — PostHogMCP, captureToolCall/capture_tool_call, captureInitialize/capture_initialize, and the per-call field mapping. intent.md, identifying-users.md, and conversation-id.md cover optional enrichment; events.md and custom-events.md describe what gets captured. The event/property vocabulary is identical across both SDKs.

    Key principles

    • One server, one wrapper. instrument() is idempotent; don't call it twice on the same server.
    • Module-scope client. Construct the PostHog / Posthog / PostHogMCP client once, not per request.
    • Env, never hardcode. The project token and host come from environment variables.
    • Additive only. Don't change tool behavior or restructure the server — just wrap/capture.
    • Don't break STDIO. No console.* (JS) or print() (Python) on STDIO transports; use a logger instead.
    • Pin the beta SDK and tell the user it's pre-1.0. (Python: posthog.mcp ships inside posthog; pin posthog>=7.21.)

    Alternatives

    Compare before choosing

    Computed 9831,966

    K-Dense-AI/scientific-agent-skills

    dask

    Distributed computing for larger-than-RAM pandas/NumPy workflows. Use when you need to scale existing pandas/NumPy code beyond memory or across clusters. Best for parallel file processing, distributed ML, integration with existing pandas code. For out-of-core analytics on single machine use vaex; for in-memory speed use polars.

    Computed 9737,126

    github/awesome-copilot

    geofeed-tuner

    Use this skill whenever the user mentions IP geolocation feeds, RFC 8805, geofeeds, or wants help creating, tuning, validating, or publishing a self-published IP geolocation feed in CSV format. Intended user audience is a network operator, ISP, mobile carrier, cloud provider, hosting company, IXP, or satellite provider asking about IP geolocation accuracy, or geofeed authoring best practices. Helps create, refine, and improve CSV-format IP geolocation feeds with opinionated recommendations beyon

    Computed 9331,966

    K-Dense-AI/scientific-agent-skills

    polars-bio

    High-performance genomic interval operations and bioinformatics file I/O on Polars DataFrames. Overlap, nearest, merge, coverage, complement, subtract for BED/VCF/BAM/GFF intervals. Streaming, cloud-native, faster bioframe alternative.

    Computed 9331,966

    K-Dense-AI/scientific-agent-skills

    polars

    High-performance DataFrame library for Python ETL, analytics, and pandas migration. Use for expression-based data manipulation with lazy query optimization, parallel execution, streaming out-of-core processing, Arrow interoperability, and optional GPU execution.