Go MCP Analytics installation
Contents
The Go SDK is in beta. It has the same defaults as the other SDKs, but captures fewer events and has fewer optional features. See Limits.
posthogmcpsdk instruments servers built on the official Go MCP SDK (github.com/modelcontextprotocol/go-sdk) v1.6.1 or later. Use go-sdk v1.8.0 or later in your own go.mod to serve protocol 2026-07-28 and input_required rounds. It ships in the posthog-go repository as its own module, so your app only depends on the MCP SDK if it uses this package.
You need Go 1.25+, posthogmcpsdk v1.33.0 or later, and a PostHog project token. Using a server that isn't built on go-sdk? See Custom dispatchers.
- 1
Install the package
RequiredTerminalEarlier releases lack some options and events on this page.
- 2
Wrap your server
RequiredCall
posthogmcpsdk.Instrumentonce, before the server accepts requests:GoWithServerInfoadds your server's name and version to each event. - 3
Close the client on shutdown
Requiredposthog-gosends events in batches.client.Close()sends queued events, so make sure it runs before the process exits. Thedeferabove does this whenmainreturns. If you stop the process from a signal handler, callclient.Close()there. - 4
Check your first events
RequiredConnect an agent to your server and call a tool. Then open the activity feed and filter for
event = $mcp_tool_call. See the event reference for every property.
Identify the caller
Use WithIdentity to attach a person and groups to each call. go-sdk gives you the HTTP headers and any verified OAuth token in req.Extra:
An error from the resolver doesn't change the MCP response. The SDK sends the event without identity and reports the error to your WithErrorHandler. The Go SDK doesn't send a separate $identify event.
Sessions
Every event has a $session_id:
- Streamable HTTP with sessions: the SDK hashes the transport's session ID into
ses_plus 32 hex characters. Every PostHog MCP SDK computes the same value, so a session served from several languages groups together. - stdio and in-memory transports: the SDK generates a
ses_plus UUIDv7 ID per go-sdk session and starts a new one after 30 minutes without a tool call. - Stateless HTTP: there's no transport session, so the SDK uses conversation IDs. It returns a handle in the first result, and calls that echo it share a
$session_id. A client that ignores the handle gets a new session per request. UseWithIdentityto group those calls by user.
The SDK mints a handle only when the call has no transport session, the tool's schema can take the argument, the handler returned no Go error, and the result isn't an input_required round. stdio and stateful HTTP servers still advertise conversation_id but never mint one.
A valid conversation handle also takes priority over the transport session, so a conversation that reconnects stays in one session.
Configuration
| Option | Default | What it does |
|---|---|---|
WithServerInfo(name, version) | – | Adds the server name and version to each event. |
WithContextParameter(bool) | true | Adds a required context argument to capture agent intent. The SDK removes it before your handler and go-sdk's input validation run. |
WithCaptureModel(bool) | true | Captures the calling model from client metadata, or from a required llm_model argument the SDK adds. |
WithConversationID(bool) | true | Adds an optional conversation_id argument to group calls across requests. When it mints a handle, it appends a last {"conversation_id": ...} text block to the result. For plain object output schemas, it also mirrors the handle into _mcp_instructions in structuredContent. |
WithIdentity(resolver) | – | Maps a call to a person and groups. See Identify the caller. |
WithProperties(resolver) | – | Adds properties to each $mcp_tool_call and its $exception. The resolver also receives the result and the handler's error. |
WithMissingCapabilityTool(name) | off | Adds the get_more_tools virtual tool. Agents call it to report a capability your server lacks. The call sends $mcp_missing_capability, not $mcp_tool_call. An empty name uses get_more_tools. |
WithCaptureParameters(bool) | true | Captures the tool arguments as $mcp_parameters. |
WithCaptureResponses(bool) | true | Captures the tool result as $mcp_response. A failed call's error text is captured either way. |
WithErrorHandler(handler) | no-op | Receives instrumentation errors. Its errors and panics never change the MCP response. |
To stop the $exception event for failed calls, pass posthogmcp.WithExceptionAutocapture(false) to posthogmcp.New. To change or drop events before they're sent, use BeforeSend in your posthog.Config. See Privacy.
The adapter's WithErrorHandler default is a no-op, but the posthog-go client logs to stderr by default. Set Config.Logger to change that.
The SDK learns each tool's schema from tools/list and refreshes it when the server sends notifications/tools/list_changed. A server with no connected session sends no notification, so the SDK trusts what it learned for 10 seconds and then lists the tools again on the next call. A tool that declares its own context, llm_model, or conversation_id argument keeps it. Schemas built from $ref, allOf, anyOf, or oneOf get no added arguments. Schemas with a non-object type or non-object properties are skipped too. Output schemas with maxProperties or propertyNames get no _mcp_instructions.
Report missing capabilities
Add WithMissingCapabilityTool to give agents a get_more_tools tool. An agent calls it when no tool fits its request. The context argument is the agent's report. The option is off by default.
- The SDK adds the tool to the last page of
tools/list. If your server already registers a tool with that name, the SDK keeps yours. - A call to the tool sends
$mcp_missing_capabilitywith the agent's request as$mcp_intent. It does not send$mcp_tool_call. The SDK answers the call, so your handlers never see it. - Pass a different name to rename the tool. An empty name uses
get_more_tools. - A server that registers no tools must still declare the tools capability. Without it, clients never list tools and never see
get_more_tools:
To see what agents ask for, query the reports:
See Tracking missing capabilities for how to read the reports.
Middleware order
Instrument adds receiving and sending middleware. Receiving middleware you add before Instrument runs inside the measured duration. Middleware you add after it runs outside. To place the middleware yourself, install both halves of NewMiddleware:
Custom dispatchers
For a server that isn't built on go-sdk, use the posthogmcp package directly. CaptureToolCall builds the same $mcp_tool_call event, with the same redaction and size limits:
A call with neither SessionID nor ConversationID gets a new ses_ plus UUIDv7 session, and distinct_id falls back to it. Pass a stable SessionID or ConversationID to group calls. ToolCall also takes ConversationID, LLMModel with LLMModelSource, ClientUserAgent, and VendorClient.
This path has no prepare steps. You strip any injected arguments and manage sessions and conversation handles yourself. If Intent comes from an argument, remove it from Parameters. An Intent of {} is omitted. SetProperties becomes $set only with an explicit DistinctID.
Two more methods capture calls that are not tool calls. See Calls that are not tool calls:
Events
The Go SDK sends $mcp_tool_call, $exception for failed calls, $mcp_unknown_tool, $mcp_input_required, and, when you turn it on, $mcp_missing_capability. WithIdentity applies to all of them. WithProperties applies to $mcp_tool_call and its $exception.
Calls that are not tool calls
These tools/call requests don't fire $mcp_tool_call:
- Unknown tool. A call that names a tool the server hasn't registered sends
$mcp_unknown_tool. It carries the requested name, redacted, and a conversation ID only if the agent sent a valid handle. The SDK never mints a handle for it. A blank name sends nothing. input_requiredround. On2026-07-28, a tool can end a round by asking the client for input. The SDK sends$mcp_input_requiredwith the round's duration and$mcp_input_request_methods, the method of each request, at most 100. It never captures the requests or the answers. The retry that completes the call sends the$mcp_tool_call.- Missing-capability report. When the agent calls the
get_more_toolsvirtual tool, the SDK answers the call itself and sends$mcp_missing_capability. The agent's report is$mcp_intent. The tool name is$mcp_resource_name. The event never carries$mcp_parameters. Your handlers never see the call.
A client on an older revision never sees a round. go-sdk answers the requests itself and runs the handler once more. If the handler asks again, the call is a failed $mcp_tool_call with $mcp_error_type set to input_required.
Properties
$lib: MCP events reportposthog-go-mcp.$lib_versionstays theposthog-goversion. A client built withCaptureMode: posthog.CaptureModeAnalyticsV1still reportsposthog-gofor MCP events.- Errors: handler errors from
mcp.AddToolkeep their Go type in$mcp_error_typeand$exception, such asfs.PathError. Plain errors giveError. The adapter has no override. On the manual API, setToolCall.ErrorType. - Client headers:
$mcp_client_user_agentand$mcp_vendor_clientcome from theUser-AgentandX-Anthropic-Clientheaders, on HTTP only. The SDK redacts them and caps each at 256 bytes.
Limits
The Go SDK doesn't yet have:
- An intent fallback callback. With
WithContextParameter(false), the SDK captures no intent $mcp_tools_list,$mcp_initialize, resource, and prompt events- Task handling
- Client name and version on legacy
2025-11-25stateless servers. The protocol version is still present $identifyand the merge of anonymous events into a user- Wizard support
Some behavior to know about:
- A before-send hook that drops a tool call leaves its
$exception. - On a stateless server with no session, an
input_requiredround and its retry land in different sessions. - The
$exceptionhas no stack trace, and no user agent, vendor, or model properties.