Other languages MCP Analytics installation
Contents
MCP Analytics has SDKs for TypeScript, Python, Go, Ruby, and Rust. If your MCP server uses another language, such as Java, Kotlin, C#, PHP, Elixir, or Swift, you can still use MCP Analytics. The SDKs send ordinary PostHog events. Send events with the same shape and PostHog shows your server in MCP Analytics.
This page describes the event your server sends for each tool call.
You can follow this page to instrument an MCP server. First check the server's language. For TypeScript, Python, Go, Ruby, or Rust, use that language's installation page instead, because the SDK handles redaction and sessions for you. For any other language, complete every step marked required and run the check in the last step. This page also has a Markdown version at /docs/mcp-analytics/installation/other-languages.md.
How it works
The MCP Analytics dashboards read $mcp_tool_call events where $mcp_source is "posthog_mcp_analytics". They don't check which library sent the event. To instrument a tool call in your server:
- Add a
contextargument to each tool. Agents use it to explain why they called the tool. - Remove
contextfrom the arguments before the tool handler runs. - After the handler returns or throws, capture one
$mcp_tool_callevent.
- 1
Install a PostHog SDK
RequiredInstall and configure the PostHog SDK for your language with your project token. If your language has no SDK, send events with the capture API.
Create one client when the server starts and reuse it.
- 2
Add the context argument to your tools
RecommendedIn your
tools/listresponse, add acontextstring to each tool's input schema. The agent fills it with the reason for the call. PostHog groups these reasons into the intent clusters on the dashboard.JSONRemove
contextfrom the arguments before your MCP framework validates them, if you can. Then you can markcontextas required, and agents fill required arguments more reliably than optional ones. If your framework validates first, makecontextoptional, and add it to the schema that the framework validates against. A schema withadditionalProperties: falserejects every argument it doesn't list, so calls that includecontextfail.Skip any tool that already has an argument named
context, because that argument belongs to the tool. Also skip any tool whose root schema uses$ref,oneOf,anyOf, orallOf.When a call arrives, remove
contextfrom the arguments before the handler runs. If its value is a string that isn't blank or"{}", use it as$mcp_intent. See Capturing agent intent for how PostHog uses it. - 3
Resolve a session ID
RequiredEvery event needs a non-empty
$session_id. It groups the calls of one agent conversation, so it must never group calls from different clients. Pick the first case that fits your server:Your server $session_idHTTP with a session ID that stays the same across a client's requests, such as the Mcp-Session-Idheader or an SSEsessionIdses_followed by a hash of that ID, such as the first 32 hex characters of its SHA-256. Use the same hash on every replica.stdio, WebSocket, or another connection that serves only one client A ses_<UUIDv7>generated when the connection opens. Generate a new one when a tool call arrives 30 minutes or more after the previous call ended, because one stdio process can serve many separate chats.HTTP with no session ID, which includes every request on the 2026-07-28revisionA fresh ses_<UUIDv7>for each request. Each call is then its own session. The SDKs use conversation IDs to group these calls.Don't use credentials, cookies, or client IP addresses as the session. Different clients can share them.
The SDKs derive session IDs with a different hash, so a session served partly by an SDK and partly by your own code splits in two.
- 4
Capture a $mcp_tool_call event
RequiredMeasure each call with a monotonic clock. Capture one event after the handler returns or throws, and don't block the response while it sends. Most PostHog SDKs queue the event and send it in the background. Some send during the capture call. For example, the PHP SDK sends its queue when the queue reaches its batch size. With such an SDK, capture after the response is sent, such as after
fastcgi_finish_request()in PHP.Capture only the final outcome of a call to a tool your server has. Don't capture a
$mcp_tool_callfor:- A call that names a tool your server doesn't have. Its name is whatever the agent sent, so it would add fake tools to your tool list.
- A result with
resultType: "input_required". Capture the retry that completes the call instead. - A task handle (
resultType: "task"). It isn't a completed call.
This is the event, as PostHog stores it. If you use the capture API, wrap it in the request body that page describes, with your project token:
JSONEvery
$mcp_tool_callneeds these properties:Property Value $mcp_sourceAlways the string "posthog_mcp_analytics". MCP Analytics ignores events without it.$mcp_tool_nameThe tool's name, at most 256 characters. $mcp_is_errorA JSON boolean, never the string "false".$mcp_duration_msA JSON number of milliseconds from the start of the request to the handler's result. $session_idFrom the previous step. $mcp_server_nameYour server's name, at most 256 characters, the same on every event. PostHog uses it to tell your servers apart. timestampWhen the request started, not when the event was sent. Send these properties whenever you have the value:
Property Value $mcp_intent,$mcp_intent_sourceThe contextvalue, redacted and cut to 2,048 characters, with source"context_parameter". Leave both out when the agent sent no context.$mcp_client_name,$mcp_client_versionThe client's clientInfofrom the request's_meta(io.modelcontextprotocol/clientInfo), or else from theinitializehandshake of the same session.$mcp_protocol_versionThe MCP revision of the request, such as "2025-11-25", from_meta(io.modelcontextprotocol/protocolVersion), theMCP-Protocol-Versionheader, or theinitializehandshake.$mcp_client_user_agent,$mcp_vendor_clientThe User-AgentandX-Anthropic-Clientheaders on HTTP. PostHog uses them to tell clients apart, such as Claude Code from Claude.ai.$mcp_server_versionYour server's version. $mcp_tool_descriptionThe tool's description, at most 2,048 characters. $mcp_resource_nameThe tool's name, same as $mcp_tool_name.Cut names, versions, and header values to 256 characters. The event reference lists every property.
The only nested JSON values are
$mcp_parametersand$mcp_response(see the payload step). Keep every other property a string, number, or boolean. - 5
Identify the user
OptionalIf your server knows who is calling, such as from an OAuth token, set
distinct_idto that user's ID. Send person properties as$setand groups as$groups, as described in Identifying users.Otherwise, set
distinct_idto the$session_idand set$process_person_profiletofalse. That way, anonymous calls don't create person profiles. - 6
Record failures
RequiredA call fails when the handler throws, or when it returns a result with
isError: true. For a failed call, set:Property Value $mcp_is_errortrue$mcp_error_typeA low-cardinality label, never the message. Use one of validation,permission,missing_context,timeout,rate_limited,api_4xx,api_5xx,cancelled,input_required, orinternalwhen one fits. Otherwise use the thrown error's type name, or"Error"for anisErrorresult.$mcp_error_messageThe error message, or the text of the isErrorresult, redacted and cut to 2,048 characters.$mcp_error_statusOptional. The upstream HTTP status code, as a number, when the failure came from an HTTP call. Analytics capture must never change what the client receives. Catch every error from your instrumentation code, and return or throw the tool's own failure unchanged.
To see failures in Error Tracking too, capture the exception with your SDK's exception capture. Use the same
distinct_idand$process_person_profileas the call event, redact the message, and add the call's$session_id,$mcp_tool_name, and$exception_source: "mcp.tool_call". - 7
Redact free text
RequiredAgents and errors put credentials and personal data into text. Before you send
$mcp_intent,$mcp_error_message, or an exception message, replace each of these with[redacted]:- The username and password in a URL (
https://user:password@host). - URL query and fragment values whose name is a credential key (below) or a signature, such as
sig,signature, orX-Amz-Signature. - The value after a credential key in
key=value,key: value, or a JSON member such as"password": "hunter2". - PostHog tokens:
ph, a lowercase letter,_, then 20 or more ofA-Z,a-z,0-9,_, or-. - Email addresses, phone numbers, IP addresses, card numbers, and US social security numbers.
A credential key is one of these names, compared as a whole and ignoring case:
authorization,cookie,set-cookie,x-api-key,token,password,secret, orapi key,api token,access token,refresh token,client secret, orprivate keywritten with_,-, or no separator.Redact first, then cut the text to its limit.
- The username and password in a URL (
- 8
Capture arguments and results
Optional$mcp_parametersand$mcp_responselet you see what agents sent and what your tool returned. For example, you can find calls that succeed but return an empty or wrong result. They can also contain credentials and personal data, so the SDKs redact them before sending. If you capture them, you need to redact them yourself, before you capture:- Send
$mcp_parametersas{"request": {"method": "tools/call", "params": {"name": "<tool>", "arguments": <arguments without context, or {}>}}}, the same shape the SDKs send. - Send
$mcp_responseas the tool's result object, with itscontentblocks and anystructuredContent. - Replace with
"[redacted]"the value of every object key that is a credential key. - Apply the free-text redaction to every string value. A text block often holds a JSON copy of
structuredContent, so it can contain the same credentials. - Replace
imageandaudiocontent blocks,resourceblocks with ablob, and base64 strings of 10 KB or more with a short text placeholder. - Cut strings to 32,768 characters, arrays and objects to 100 entries, and nesting to 10 levels.
Keep the whole event under 100 KB. If it's still too large, cut the strings in
$mcp_responseand then$mcp_parametersshorter, and remove the property only when that isn't enough. Never remove a required property or$mcp_error_message.Without a redaction pass, leave both properties out. All other features work without them.
- Send
- 9
Capture the handshake
RecommendedOn revisions before
2026-07-28, a client starts each session with aninitializerequest. Capture a$mcp_initializeevent for it, with the samedistinct_id,$session_id, and$process_person_profileas the session's tool calls, plus$mcp_source,$mcp_client_name,$mcp_client_version,$mcp_protocol_version,$mcp_server_name, and$mcp_server_version. The "MCP clients" and "MCP users and sessions" insights in the MCP Analytics dashboard template count these events. - 10
Flush on shutdown
RequiredPostHog SDKs batch events. Flush the client when the server shuts down. On serverless and edge platforms, flush at the end of each request without delaying the response, for example with the platform's
waitUntil. - 11
Check your events
RequiredCall a few tools through an MCP client, flush, then run this query in SQL insights:
callsshould match the number of calls you made, and everymissing_andbad_column should be0.with_intentcounts the calls whose agent sentcontext. If it's0, check thatcontextappears in yourtools/listresponse.Then open the MCP Analytics dashboard. Your tools should appear there.
What the SDKs do that this page doesn't
The SDKs include more features than this page covers. If you need one of these, it's worth checking whether your language has an SDK yet:
- Conversation IDs, which group the calls of a stateless HTTP client into one session.
- The missing capabilities virtual tool,
get_more_tools. - Model capture through the
llm_modelargument. $mcp_tools_list,$mcp_unknown_tool, and$mcp_input_requiredevents.- Wider redaction, such as credential-shaped strings that no key or pattern above identifies.
You can add any of these yourself. The event reference describes their events and properties.





