Skip to content

PostHog — WebMCP Analytics

Track every before → after/error/denied with webmcp.configure. One snippet covers all tools. See Analytics Overview and Step-by-Step.

For hook lifecycle, see Guide — Hooks. For types, see Reference — Hooks.

When to use

PostHog for product analytics — capture webmcp.invoked / succeeded / failed / denied + duration + tenantId for all tools.

External: PostHog JS docs · posthog-js on npm

Step-by-step

Step 1 — Install & init PostHog

bash
npm i posthog-js

In your app entry (e.g. main.tsx or app/layout.tsx):

ts
import posthog from 'posthog-js';
posthog.init('phc_...', { api_host: 'https://us.i.posthog.com', capture_pageview: true });

See PostHog — Install JS.

Step 2 — Configure hooks once (global)

ts
import { webmcp } from 'simple-webmcp';
import posthog from 'posthog-js';

webmcp.configure({
  hooks: {
    before: [({tool, input, invocationId, metadata})=>{
      metadata.start = Date.now();
      posthog.capture('webmcp.invoked', { tool: tool.tool.name, invocationId, input });
    }],
    after: [({tool, invocationId, metadata})=>{
      posthog.capture('webmcp.succeeded', {
        tool: tool.tool.name,
        invocationId,
        duration_ms: Date.now()-(metadata.start as number)
      });
    }],
    error: [({tool, error, invocationId})=>{
      posthog.capture('webmcp.failed', { tool: tool.tool.name, invocationId, error: String(error) });
    }],
    denied: [({tool, reason, code, invocationId})=>{
      posthog.capture('webmcp.denied', { tool: tool.tool.name, invocationId, reason, code });
    }],
  }
});

Hooks wrap only the agent path — tool({input}) stays pure. invocationId is crypto.randomUUID() per invocation; metadata is the shared bag per call.

Step 3 — Verify in the demo

  1. npm run docs:dev → open /demo.
  2. Inspect → Invoke add_to_cart with {"productId":"keyboard","quantity":1} → check Hooks & HITL card and console [webmcp:hook].
  3. Toggle Require approval for checkoutInvoke checkoutDeny → verify webmcp.denied with code:USER_DENIED in PostHog Activity.
  4. Toggle approval off → Invoke checkout → verify webmcp.succeeded with duration_ms.

Step 4 — Production notes

  • PII: Redact input before capture — e.g. strip email/token in before or after. See Hooks — After.

  • Sampling: if (Math.random() > 0.1) return inside hooks for cost control.

  • Tenant: Use scoped provider to add tenantId:

    tsx
    import { WebMCPProvider } from 'simple-webmcp/react';
    <WebMCPProvider hooks={{ before:[({input})=>({input:{...(input as any), tenantId}})] }}>
      <Scope tools={[tool]}>{children}</Scope>
    </WebMCPProvider>

    See React — WebMCPProvider and Analytics Overview.

Troubleshooting

SymptomFix
No events in PostHogEnsure webmcp.configure runs before tool.register() / useWebMCP mount; check posthog.has_opted_in_capturing()
duration_ms missingbefore must set metadata.start; after reads same metadata object per invocation
input too largeRedact PII and truncate arrays before capture

See also

Released under the MIT License.