> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt # Other languages MCP Analytics installation Canonical URL: https://posthog.com/docs/mcp-analytics/installation/other-languages > **Cloud region:** This page uses US Cloud hosts. For an EU Cloud project, use `https://eu.posthog.com` instead of `https://app.posthog.com` (app host for links). ## On this page - [How it works](#how-it-works) - [Install a PostHog SDK](#install-a-posthog-sdk) - [Add the context argument to your tools](#add-the-context-argument-to-your-tools) - [Resolve a session ID](#resolve-a-session-id) - [Capture a $mcp_tool_call event](#capture-a-mcp_tool_call-event) - [Identify the user](#identify-the-user) - [Record failures](#record-failures) - [Redact free text](#redact-free-text) - [Capture arguments and results](#capture-arguments-and-results) - [Capture the handshake](#capture-the-handshake) - [Flush on shutdown](#flush-on-shutdown) - [Check your events](#check-your-events) - [What the SDKs do that this page doesn't](#what-the-sdks-do-that-this-page-doesnt) MCP Analytics has SDKs for [TypeScript](https://posthog.com/docs/mcp-analytics/installation/typescript.md), [Python](https://posthog.com/docs/mcp-analytics/installation/python.md), [Go](https://posthog.com/docs/mcp-analytics/installation/go.md), [Ruby](https://posthog.com/docs/mcp-analytics/installation/ruby.md), and [Rust](https://posthog.com/docs/mcp-analytics/installation/rust.md). 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. **For coding agents** 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`](https://posthog.com/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: 1. Add a `context` argument to each tool. Agents use it to explain why they called the tool. 2. Remove `context` from the arguments before the tool handler runs. 3. After the handler returns or throws, capture one `$mcp_tool_call` event. 1. 1 ## Install a PostHog SDK Required Install and configure the [PostHog SDK for your language](https://posthog.com/docs/libraries.md) with your [project token](https://posthog.com/docs/feature-flags/installation.md#where-to-find-your-project-api-key). If your language has no SDK, send events with the [capture API](https://posthog.com/docs/api/capture.md). Create one client when the server starts and reuse it. 2. 2 ## Add the context argument to your tools Recommended In your `tools/list` response, add a `context` string 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. ```json { "name": "search_docs", "inputSchema": { "type": "object", "properties": { "query": { "type": "string" }, "context": { "type": "string", "description": "Why are you calling this tool? Describe the goal in general terms, and refer to people by role (\"a customer\") rather than by name. Leave out personal and identifying information." } }, "required": ["query", "context"] } } ``` Remove `context` from the arguments before your MCP framework validates them, if you can. Then you can mark `context` as required, and agents fill required arguments more reliably than optional ones. If your framework validates first, make `context` optional, and add it to the schema that the framework validates against. A schema with `additionalProperties: false` rejects every argument it doesn't list, so calls that include `context` fail. 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`, or `allOf`. When a call arrives, remove `context` from 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](https://posthog.com/docs/mcp-analytics/intent.md) for how PostHog uses it. 3. 3 ## Resolve a session ID Required Every 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_id` | | --- | --- | | HTTP with a session ID that stays the same across a client's requests, such as the `Mcp-Session-Id` header or an SSE `sessionId` | `ses_` 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_` 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-28` revision | A fresh `ses_` for each request. Each call is then its own session. The SDKs use [conversation IDs](https://posthog.com/docs/mcp-analytics/conversation-id.md) 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. 4 ## Capture a $mcp\_tool\_call event Required Measure 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_call` for: - 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](https://posthog.com/docs/api/capture.md), wrap it in the request body that page describes, with your project token: ```json { "event": "$mcp_tool_call", "distinct_id": "ses_9f2c4e0b7d1a4c3e8b6f5a2d1c0e9b8a", "timestamp": "2026-10-07T14:03:11.204Z", "properties": { "$mcp_source": "posthog_mcp_analytics", "$session_id": "ses_9f2c4e0b7d1a4c3e8b6f5a2d1c0e9b8a", "$mcp_server_name": "acme-docs", "$mcp_server_version": "1.4.0", "$mcp_tool_name": "search_docs", "$mcp_resource_name": "search_docs", "$mcp_tool_description": "Search the Acme documentation", "$mcp_is_error": false, "$mcp_duration_ms": 182, "$mcp_intent": "Find how to rotate an API key for a customer", "$mcp_intent_source": "context_parameter", "$mcp_client_name": "claude-code", "$mcp_client_version": "2.1.0", "$mcp_protocol_version": "2025-11-25", "$process_person_profile": false } } ``` Every `$mcp_tool_call` needs these properties: | Property | Value | | --- | --- | | `$mcp_source` | Always the string `"posthog_mcp_analytics"`. MCP Analytics ignores events without it. | | `$mcp_tool_name` | The tool's name, at most 256 characters. | | `$mcp_is_error` | A JSON boolean, never the string `"false"`. | | `$mcp_duration_ms` | A JSON number of milliseconds from the start of the request to the handler's result. | | `$session_id` | From the previous step. | | `$mcp_server_name` | Your server's name, at most 256 characters, the same on every event. PostHog uses it to tell your servers apart. | | `timestamp` | When the request started, not when the event was sent. | Send these properties whenever you have the value: | Property | Value | | --- | --- | | `$mcp_intent`, `$mcp_intent_source` | The `context` value, [redacted](#redact-free-text) and cut to 2,048 characters, with source `"context_parameter"`. Leave both out when the agent sent no context. | | `$mcp_client_name`, `$mcp_client_version` | The client's `clientInfo` from the request's `_meta` (`io.modelcontextprotocol/clientInfo`), or else from the `initialize` handshake of the same session. | | `$mcp_protocol_version` | The MCP revision of the request, such as `"2025-11-25"`, from `_meta` (`io.modelcontextprotocol/protocolVersion`), the `MCP-Protocol-Version` header, or the `initialize` handshake. | | `$mcp_client_user_agent`, `$mcp_vendor_client` | The `User-Agent` and `X-Anthropic-Client` headers on HTTP. PostHog uses them to tell clients apart, such as Claude Code from Claude.ai. | | `$mcp_server_version` | Your server's version. | | `$mcp_tool_description` | The tool's description, at most 2,048 characters. | | `$mcp_resource_name` | The tool's name, same as `$mcp_tool_name`. | Cut names, versions, and header values to 256 characters. The [event reference](https://posthog.com/docs/mcp-analytics/events.md) lists every property. The only nested JSON values are `$mcp_parameters` and `$mcp_response` (see [the payload step](#capture-arguments-and-results)). Keep every other property a string, number, or boolean. 5. 5 ## Identify the user Optional If your server knows who is calling, such as from an OAuth token, set `distinct_id` to that user's ID. Send person properties as `$set` and groups as `$groups`, as described in [Identifying users](https://posthog.com/docs/mcp-analytics/identifying-users.md). Otherwise, set `distinct_id` to the `$session_id` and set `$process_person_profile` to `false`. That way, anonymous calls don't create person profiles. 6. 6 ## Record failures Required A call fails when the handler throws, or when it returns a result with `isError: true`. For a failed call, set: | Property | Value | | --- | --- | | `$mcp_is_error` | `true` | | `$mcp_error_type` | A low-cardinality label, never the message. Use one of `validation`, `permission`, `missing_context`, `timeout`, `rate_limited`, `api_4xx`, `api_5xx`, `cancelled`, `input_required`, or `internal` when one fits. Otherwise use the thrown error's type name, or `"Error"` for an `isError` result. | | `$mcp_error_message` | The error message, or the text of the `isError` result, [redacted](#redact-free-text) and cut to 2,048 characters. | | `$mcp_error_status` | Optional. 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](https://posthog.com/docs/error-tracking.md) too, capture the exception with your SDK's exception capture. Use the same `distinct_id` and `$process_person_profile` as the call event, redact the message, and add the call's `$session_id`, `$mcp_tool_name`, and `$exception_source: "mcp.tool_call"`. 7. 7 ## Redact free text Required Agents 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`, or `X-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 of `A-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`, or `api key`, `api token`, `access token`, `refresh token`, `client secret`, or `private key` written with `_`, `-`, or no separator. Redact first, then cut the text to its limit. 8. 8 ## Capture arguments and results Optional `$mcp_parameters` and `$mcp_response` let 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](https://posthog.com/docs/mcp-analytics/privacy.md) before sending. If you capture them, you need to redact them yourself, before you capture: - Send `$mcp_parameters` as `{"request": {"method": "tools/call", "params": {"name": "", "arguments": }}}`, the same shape the SDKs send. - Send `$mcp_response` as the tool's result object, with its `content` blocks and any `structuredContent`. - Replace with `"[redacted]"` the value of every object key that is a [credential key](#redact-free-text). - Apply the [free-text redaction](#redact-free-text) to every string value. A text block often holds a JSON copy of `structuredContent`, so it can contain the same credentials. - Replace `image` and `audio` content blocks, `resource` blocks with a `blob`, 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_response` and then `$mcp_parameters` shorter, 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. 9. 9 ## Capture the handshake Recommended On revisions before `2026-07-28`, a client starts each session with an `initialize` request. Capture a `$mcp_initialize` event for it, with the same `distinct_id`, `$session_id`, and `$process_person_profile` as 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. 10 ## Flush on shutdown Required PostHog 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. 11 ## Check your events Required Call a few tools through an MCP client, flush, then run this query in [SQL insights](https://app.posthog.com/sql): ```sql SELECT count() AS calls, countIf(properties.$mcp_tool_name IS NULL OR properties.$mcp_tool_name = '') AS missing_tool_name, countIf(properties.$mcp_server_name IS NULL OR properties.$mcp_server_name = '') AS missing_server_name, countIf(properties.$session_id IS NULL OR properties.$session_id = '') AS missing_session_id, countIf(JSONType(properties, '$mcp_is_error') != 'Bool') AS bad_is_error, countIf(JSONType(properties, '$mcp_duration_ms') NOT IN ('Int64', 'UInt64', 'Double')) AS bad_duration, countIf(properties.$mcp_intent IS NOT NULL) AS with_intent FROM events WHERE event = '$mcp_tool_call' AND properties.$mcp_source = 'posthog_mcp_analytics' AND timestamp > now() - INTERVAL 1 HOUR ``` `calls` should match the number of calls you made, and every `missing_` and `bad_` column should be `0`. `with_intent` counts the calls whose agent sent `context`. If it's `0`, check that `context` appears in your `tools/list` response. Then open the [MCP Analytics dashboard](https://app.posthog.com/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](https://posthog.com/docs/mcp-analytics/conversation-id.md), which group the calls of a stateless HTTP client into one session. - The [missing capabilities](https://posthog.com/docs/mcp-analytics/missing-capability.md) virtual tool, `get_more_tools`. - Model capture through the `llm_model` argument. - `$mcp_tools_list`, `$mcp_unknown_tool`, and `$mcp_input_required` events. - Wider redaction, such as credential-shaped strings that no key or pattern above identifies. You can add any of these yourself. The [event reference](https://posthog.com/docs/mcp-analytics/events.md) describes their events and properties.