Custom events and metadata

Contents

Use an event properties callback (eventProperties in TypeScript, event_properties in Python and Ruby, WithProperties in Go) to add metadata to captured events. Use analytics.capture() for events that are not MCP requests.

Add metadata to every event

Pass a callback to attach extra properties to automatically captured MCP events. The callback receives request context, such as headers, transport, and the request ID.

import { instrument, getRequestHeaders } from "@posthog/mcp"
const analytics = instrument(server, posthog, {
eventProperties: async (request, extra) => ({
$app_version: process.env.GIT_SHA ?? "unknown",
request_id: getRequestHeaders(extra)?.["x-request-id"],
}),
})

In TypeScript, getRequestHeaders reads headers on both MCP SDK majors – see MCP SDK v2.

The returned object is spread flat onto the event's properties alongside the built-in $mcp_* keys:

JSON
{
"event": "$mcp_tool_call",
"properties": {
"$mcp_tool_name": "search_events",
"$app_version": "a1b2c3d",
"region": "iad",
"request_id": "req_…"
}
}

Return constants from the callback to add the same values to captured MCP events. This is similar to posthog.register(...) in other SDKs. Return values from the request context when metadata must vary between calls.

For group analytics, return groups from identify. The SDK adds $groups to events for the session.

The Go SDK drops returned keys that start with $mcp_ or equal $groups, $set, $process_person_profile, $session_id, or $exception_level.

Returned values must be JSON-serializable. The SDK catches callback errors and sends them to your logger. These errors do not interrupt tool execution.

Send a custom event

Use analytics.capture() for events that aren't MCP requests, such as UI feedback or workflow milestones. It uses the SDK's sanitization, current server session and identity, and beforeSend hook. In TypeScript it returns a promise, and in Python it returns a coroutine, so await it. In Ruby it runs synchronously and returns nil.

Custom capture has no request context and doesn't run the event properties callback. Pass custom metadata in properties.

You name the event. It's sent verbatim – it's your event, so it is not $-prefixed.

const analytics = instrument(server, posthog)
await analytics.capture({
event: "feedback_submitted",
properties: { rating: 5 },
})

The Go SDK has no analytics handle. Call client.Enqueue(posthog.Capture{...}) on your posthog-go client instead. That path has no SDK redaction.

PostHog receives:

  • One event under the verbatim event name you passed, with your properties merged in.
  • The current server session and cached identity apply. These may differ from the session of a concurrent tool request.

capture() is a method on the handle that instrument() returns, so you call it on the instrumented server's analytics handle directly.

Which one to use

You want to...Use
Attach the same properties to every auto-captured eventThe event properties callback
Emit a one-off event that isn't an MCP requestanalytics.capture()
Attach data only to matching requestsCheck the request in the callback and return properties only for matches.

Still have questions?

Was this page useful?