diff --git a/.sampo/changesets/mcp-protocol-version.md b/.sampo/changesets/mcp-protocol-version.md new file mode 100644 index 00000000..b03fb636 --- /dev/null +++ b/.sampo/changesets/mcp-protocol-version.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: minor +--- + +feat(mcp): emit `$mcp_protocol_version` on MCP analytics events — the MCP spec version, recovered from the session token across stateless pods (parity with the TypeScript SDK). `PostHogMCP` capture methods gain a `protocol_version` argument. diff --git a/posthog/mcp/_capture.py b/posthog/mcp/_capture.py index fd3255bd..26cf67fd 100644 --- a/posthog/mcp/_capture.py +++ b/posthog/mcp/_capture.py @@ -49,6 +49,7 @@ def capture_event( "server_version": data.server_version, "client_name": event_input.get("client_name"), "client_version": event_input.get("client_version"), + "protocol_version": event_input.get("protocol_version"), "identify_actor_given_id": actor.distinct_id if actor else None, "identify_actor_data": (actor.properties or {}) if actor else {}, "groups": actor.groups if actor else None, diff --git a/posthog/mcp/_instrument_fastmcp.py b/posthog/mcp/_instrument_fastmcp.py index 6d728fd5..6fd871f2 100644 --- a/posthog/mcp/_instrument_fastmcp.py +++ b/posthog/mcp/_instrument_fastmcp.py @@ -91,9 +91,12 @@ async def wrapped( convert_result: bool = False, ) -> Any: client_name, client_version = _client_info(context) + protocol_version = _protocol_version(context) mcp_session_id = _mcp_session_id(context) - token, client_name, client_version = resolve_session_and_client( - mcp_session_id, client_name, client_version + token, client_name, client_version, protocol_version = ( + resolve_session_and_client( + mcp_session_id, client_name, client_version, protocol_version + ) ) request = build_tool_call_request(name, arguments) extra: Dict[str, Any] = {"session_id": mcp_session_id} @@ -103,6 +106,7 @@ async def wrapped( mcp_session_id=mcp_session_id, client_name=client_name, client_version=client_version, + protocol_version=protocol_version, request=request, extra=extra, token=token, @@ -118,6 +122,7 @@ async def wrapped( arguments=arguments, client_name=client_name, client_version=client_version, + protocol_version=protocol_version, extra=extra, ) return [ @@ -162,6 +167,7 @@ async def wrapped( duration_ms=(time.monotonic() - start) * 1000, client_name=client_name, client_version=client_version, + protocol_version=protocol_version, conversation_id=None if minted else conversation_id, extra=extra, ) @@ -187,6 +193,7 @@ async def wrapped( duration_ms=(time.monotonic() - start) * 1000, client_name=client_name, client_version=client_version, + protocol_version=protocol_version, conversation_id=delivered_conversation_id, extra=extra, ) @@ -215,9 +222,12 @@ async def list_handler(req: Any) -> Any: return await original(req) client_name, client_version = _low_level_client_info(server) + protocol_version = _low_level_protocol_version(server) mcp_session_id = _low_level_session_id(server) - token, client_name, client_version = resolve_session_and_client( - mcp_session_id, client_name, client_version + token, client_name, client_version, protocol_version = ( + resolve_session_and_client( + mcp_session_id, client_name, client_version, protocol_version + ) ) request = request_to_dict(req) extra: Dict[str, Any] = {"session_id": mcp_session_id} @@ -228,6 +238,7 @@ async def list_handler(req: Any) -> Any: mcp_session_id=mcp_session_id, client_name=client_name, client_version=client_version, + protocol_version=protocol_version, request=request, extra=extra, token=token, @@ -247,6 +258,7 @@ async def list_handler(req: Any) -> Any: error=error, client_name=client_name, client_version=client_version, + protocol_version=protocol_version, extra=extra, ) raise @@ -299,6 +311,7 @@ async def list_handler(req: Any) -> Any: error="tools/list returned no tools" if empty else None, client_name=client_name, client_version=client_version, + protocol_version=protocol_version, extra=extra, ) @@ -389,6 +402,17 @@ def _low_level_session_id(server: Any) -> Optional[str]: return None +def _low_level_protocol_version(server: Any) -> Optional[str]: + ctx = _low_level_request_context(server) + try: + client_params = ctx.session.client_params + if client_params: + return client_params.protocolVersion + except Exception: # noqa: BLE001 + pass + return None + + def _client_info(context: Any) -> Tuple[Optional[str], Optional[str]]: try: client_params = context.request_context.session.client_params @@ -399,6 +423,16 @@ def _client_info(context: Any) -> Tuple[Optional[str], Optional[str]]: return None, None +def _protocol_version(context: Any) -> Optional[str]: + try: + client_params = context.request_context.session.client_params + if client_params: + return client_params.protocolVersion + except Exception: # noqa: BLE001 + pass + return None + + def _mcp_session_id(context: Any) -> Optional[str]: """Best-effort transport session id (e.g. the ``Mcp-Session-Id`` header on the streamable-HTTP transport). Returns ``None`` for stdio, where the SDK-generated diff --git a/posthog/mcp/_instrument_lowlevel.py b/posthog/mcp/_instrument_lowlevel.py index 03372744..cbe6c500 100644 --- a/posthog/mcp/_instrument_lowlevel.py +++ b/posthog/mcp/_instrument_lowlevel.py @@ -94,9 +94,12 @@ async def handler(req: Any) -> Any: name = req.params.name arguments = dict(req.params.arguments or {}) client_name, client_version = _client_info(server) + protocol_version = _protocol_version(server) mcp_session_id = _mcp_session_id(server) - token, client_name, client_version = resolve_session_and_client( - mcp_session_id, client_name, client_version + token, client_name, client_version, protocol_version = ( + resolve_session_and_client( + mcp_session_id, client_name, client_version, protocol_version + ) ) request = build_tool_call_request(name, arguments) extra = {"session_id": mcp_session_id} @@ -106,6 +109,7 @@ async def handler(req: Any) -> Any: mcp_session_id=mcp_session_id, client_name=client_name, client_version=client_version, + protocol_version=protocol_version, request=request, extra=extra, token=token, @@ -121,6 +125,7 @@ async def handler(req: Any) -> Any: arguments=arguments, client_name=client_name, client_version=client_version, + protocol_version=protocol_version, extra=extra, ) return mcp_types.ServerResult( @@ -169,6 +174,7 @@ async def handler(req: Any) -> Any: duration_ms=(time.monotonic() - start) * 1000, client_name=client_name, client_version=client_version, + protocol_version=protocol_version, conversation_id=None if minted else conversation_id, extra=extra, ) @@ -203,6 +209,7 @@ async def handler(req: Any) -> Any: duration_ms=duration_ms, client_name=client_name, client_version=client_version, + protocol_version=protocol_version, conversation_id=delivered_conversation_id, extra=extra, ) @@ -227,9 +234,12 @@ async def handler(req: Any) -> Any: return await original(req) client_name, client_version = _client_info(server) + protocol_version = _protocol_version(server) mcp_session_id = _mcp_session_id(server) - token, client_name, client_version = resolve_session_and_client( - mcp_session_id, client_name, client_version + token, client_name, client_version, protocol_version = ( + resolve_session_and_client( + mcp_session_id, client_name, client_version, protocol_version + ) ) request = request_to_dict(req) extra = {"session_id": mcp_session_id} @@ -240,6 +250,7 @@ async def handler(req: Any) -> Any: mcp_session_id=mcp_session_id, client_name=client_name, client_version=client_version, + protocol_version=protocol_version, request=request, extra=extra, token=token, @@ -259,6 +270,7 @@ async def handler(req: Any) -> Any: error=error, client_name=client_name, client_version=client_version, + protocol_version=protocol_version, extra=extra, ) raise @@ -317,6 +329,7 @@ async def handler(req: Any) -> Any: error="tools/list returned no tools" if empty else None, client_name=client_name, client_version=client_version, + protocol_version=protocol_version, extra=extra, ) @@ -368,6 +381,17 @@ def _client_info(server: Any) -> Tuple[Optional[str], Optional[str]]: return None, None +def _protocol_version(server: Any) -> Optional[str]: + ctx = _request_context(server) + try: + client_params = ctx.session.client_params + if client_params: + return client_params.protocolVersion + except Exception: # noqa: BLE001 + pass + return None + + def _mcp_session_id(server: Any) -> Optional[str]: ctx = _request_context(server) try: diff --git a/posthog/mcp/_instrumentation.py b/posthog/mcp/_instrumentation.py index f3408f3c..865e5473 100644 --- a/posthog/mcp/_instrumentation.py +++ b/posthog/mcp/_instrumentation.py @@ -151,6 +151,7 @@ async def _maybe_emit_initialize( client_name: Optional[str], client_version: Optional[str], extra: Optional[Dict[str, Any]], + protocol_version: Optional[str] = None, ) -> None: """Lazily emit ``$mcp_initialize`` once per session. The Python MCP SDK handles ``InitializeRequest`` inside the session layer (not ``request_handlers``), so we @@ -163,6 +164,7 @@ async def _maybe_emit_initialize( "session_id": session_id, "client_name": client_name, "client_version": client_version, + "protocol_version": protocol_version, "timestamp": datetime.now(timezone.utc), } await _apply_event_properties( @@ -188,18 +190,21 @@ def resolve_session_and_client( raw_session_id: Optional[str], client_name: Optional[str], client_version: Optional[str], -) -> tuple[Optional[SessionTokenPayload], Optional[str], Optional[str]]: + protocol_version: Optional[str] = None, +) -> tuple[Optional[SessionTokenPayload], Optional[str], Optional[str], Optional[str]]: """Decode a replayed ``Mcp-Session-Id`` value as a self-encoded session token, - and backfill the client name/version from it when the live transport supplied - none (the stateless-pod case, where ``initialize`` was never seen here). + and backfill the client name/version/protocol version from it when the live + transport supplied none (the stateless-pod case, where ``initialize`` was never + seen here). - Returns ``(token, client_name, client_version)``; ``token`` is ``None`` when the - header isn't one of our tokens (a plain transport UUID, JWT, or nothing).""" + Returns ``(token, client_name, client_version, protocol_version)``; ``token`` is + ``None`` when the header isn't one of our tokens (a plain UUID, JWT, or nothing).""" token = decode_session_id(raw_session_id) if token is not None: client_name = client_name or token.client_name client_version = client_version or token.client_version - return token, client_name, client_version + protocol_version = protocol_version or token.protocol_version + return token, client_name, client_version, protocol_version async def prepare_request( @@ -211,6 +216,7 @@ async def prepare_request( request: Dict[str, Any], extra: Optional[Dict[str, Any]], token: Optional[SessionTokenPayload] = None, + protocol_version: Optional[str] = None, ) -> str: """Resolve the session id, run identify, then lazily emit initialize. Returns the session id to stamp on the event for this request. @@ -228,7 +234,9 @@ async def prepare_request( identify_event = await handle_identify(data, session_id, request, extra) if identify_event: fire_and_forget(capture_event(data, identify_event)) - await _maybe_emit_initialize(data, session_id, client_name, client_version, extra) + await _maybe_emit_initialize( + data, session_id, client_name, client_version, extra, protocol_version + ) return session_id @@ -243,6 +251,7 @@ async def record_tool_call( duration_ms: Optional[float] = None, client_name: Optional[str] = None, client_version: Optional[str] = None, + protocol_version: Optional[str] = None, conversation_id: Optional[str] = None, extra: Optional[Dict[str, Any]] = None, ) -> None: @@ -260,6 +269,7 @@ async def record_tool_call( "duration": duration_ms, "client_name": client_name, "client_version": client_version, + "protocol_version": protocol_version, "conversation_id": conversation_id, "is_error": False, } @@ -341,6 +351,7 @@ async def record_missing_capability( arguments: Optional[Dict[str, Any]], client_name: Optional[str] = None, client_version: Optional[str] = None, + protocol_version: Optional[str] = None, extra: Optional[Dict[str, Any]] = None, ) -> None: """Record a ``get_more_tools`` call as ``$mcp_missing_capability``, with the @@ -354,6 +365,7 @@ async def record_missing_capability( "parameters": build_captured_mcp_parameters(request), "client_name": client_name, "client_version": client_version, + "protocol_version": protocol_version, } if isinstance(context, str) and context.strip(): event["user_intent"] = context.strip() @@ -376,6 +388,7 @@ async def record_tools_list( error: Any = None, client_name: Optional[str] = None, client_version: Optional[str] = None, + protocol_version: Optional[str] = None, extra: Optional[Dict[str, Any]] = None, ) -> None: try: @@ -388,6 +401,7 @@ async def record_tools_list( "duration": duration_ms, "client_name": client_name, "client_version": client_version, + "protocol_version": protocol_version, "is_error": is_error, "timestamp": datetime.now(timezone.utc), } diff --git a/posthog/mcp/_posthog_events.py b/posthog/mcp/_posthog_events.py index 2e351132..fd6f7363 100644 --- a/posthog/mcp/_posthog_events.py +++ b/posthog/mcp/_posthog_events.py @@ -131,6 +131,8 @@ def _add_common_properties(event: Event, properties: Dict[str, Any]) -> None: properties[_P.CLIENT_NAME] = event["client_name"] if event.get("client_version"): properties[_P.CLIENT_VERSION] = event["client_version"] + if event.get("protocol_version"): + properties[_P.PROTOCOL_VERSION] = event["protocol_version"] if event.get("user_intent"): properties[_P.INTENT] = event["user_intent"] if event.get("user_intent_source"): @@ -183,6 +185,8 @@ def _build_exception_event(event: Event) -> PostHogCaptureEvent: properties[_P.CLIENT_NAME] = event["client_name"] if event.get("client_version"): properties[_P.CLIENT_VERSION] = event["client_version"] + if event.get("protocol_version"): + properties[_P.PROTOCOL_VERSION] = event["protocol_version"] _add_custom_properties(event, properties) diff --git a/posthog/mcp/constants.py b/posthog/mcp/constants.py index c2177213..eb2de16e 100644 --- a/posthog/mcp/constants.py +++ b/posthog/mcp/constants.py @@ -57,6 +57,7 @@ class PostHogMCPAnalyticsProperty: CLIENT_NAME = "$mcp_client_name" CLIENT_VERSION = "$mcp_client_version" + PROTOCOL_VERSION = "$mcp_protocol_version" CONVERSATION_ID = "$mcp_conversation_id" DURATION_MS = "$mcp_duration_ms" IS_ERROR = "$mcp_is_error" diff --git a/posthog/mcp/posthog_mcp.py b/posthog/mcp/posthog_mcp.py index 8a93e754..67dcb210 100644 --- a/posthog/mcp/posthog_mcp.py +++ b/posthog/mcp/posthog_mcp.py @@ -89,6 +89,7 @@ def capture_tool_call( error: Any = None, category: Optional[str] = None, tool_description: Optional[str] = None, + protocol_version: Optional[str] = None, distinct_id: Optional[str] = None, session_id: Optional[str] = None, set_properties: Optional[JsonRecord] = None, @@ -109,6 +110,7 @@ def capture_tool_call( event["resource_name"] = tool_name event["tool_description"] = tool_description event["tool_category"] = category + event["protocol_version"] = protocol_version event["parameters"] = parameters event["response"] = response event["duration"] = duration_ms @@ -125,6 +127,7 @@ def capture_initialize( *, client_name: Optional[str] = None, client_version: Optional[str] = None, + protocol_version: Optional[str] = None, parameters: Any = None, response: Any = None, duration_ms: Optional[float] = None, @@ -147,6 +150,7 @@ def capture_initialize( ) event["client_name"] = client_name event["client_version"] = client_version + event["protocol_version"] = protocol_version event["parameters"] = parameters event["response"] = response event["duration"] = duration_ms @@ -161,6 +165,7 @@ def capture_tools_list( duration_ms: Optional[float] = None, is_error: bool = False, error: Any = None, + protocol_version: Optional[str] = None, distinct_id: Optional[str] = None, session_id: Optional[str] = None, set_properties: Optional[JsonRecord] = None, @@ -180,6 +185,7 @@ def capture_tools_list( timestamp, ) event["listed_tool_names"] = tool_names + event["protocol_version"] = protocol_version event["parameters"] = parameters event["response"] = response event["duration"] = duration_ms @@ -195,6 +201,7 @@ def capture_missing_capability( *, context: Optional[str] = None, parameters: Any = None, + protocol_version: Optional[str] = None, distinct_id: Optional[str] = None, session_id: Optional[str] = None, set_properties: Optional[JsonRecord] = None, @@ -214,6 +221,7 @@ def capture_missing_capability( timestamp, ) event["resource_name"] = self._missing_capability_tool_name + event["protocol_version"] = protocol_version event["parameters"] = parameters _apply_intent(event, context, "context_parameter") self._emit(event) diff --git a/posthog/test/mcp/test_pipeline.py b/posthog/test/mcp/test_pipeline.py index 3e8d05e6..f57c1572 100644 --- a/posthog/test/mcp/test_pipeline.py +++ b/posthog/test/mcp/test_pipeline.py @@ -164,6 +164,7 @@ def test_build_tool_call_event_properties(): "tool_description": "Search events", "tool_category": "Logs", "duration": 12.5, + "protocol_version": "2025-06-18", "user_intent": "find churn cohort", "user_intent_source": "context_parameter", "is_error": False, @@ -176,6 +177,7 @@ def test_build_tool_call_event_properties(): assert props[PostHogMCPAnalyticsProperty.SOURCE] == POSTHOG_MCP_ANALYTICS_SOURCE assert props[PostHogMCPAnalyticsProperty.TOOL_NAME] == "search_events" assert props[PostHogMCPAnalyticsProperty.TOOL_CATEGORY] == "Logs" + assert props[PostHogMCPAnalyticsProperty.PROTOCOL_VERSION] == "2025-06-18" assert props[PostHogMCPAnalyticsProperty.INTENT] == "find churn cohort" assert props[PostHogMCPAnalyticsProperty.INTENT_SOURCE] == "context_parameter" assert props[PostHogMCPAnalyticsProperty.SESSION_ID] == "ses_abc" @@ -183,6 +185,26 @@ def test_build_tool_call_event_properties(): assert props["$process_person_profile"] is False +def test_protocol_version_on_primary_and_exception_events(): + event = { + "event_type": MCPAnalyticsEventType.MCP_TOOLS_CALL, + "session_id": "ses_abc", + "resource_name": "search_events", + "protocol_version": "2025-06-18", + "is_error": True, + "error": {"$exception_list": [{"type": "ValueError", "value": "boom"}]}, + "timestamp": datetime.now(timezone.utc), + } + captures = build_posthog_capture_events(event) + # Both the primary $mcp_tool_call and the $exception sibling carry it. + assert len(captures) == 2 + for capture in captures: + assert ( + capture["properties"][PostHogMCPAnalyticsProperty.PROTOCOL_VERSION] + == "2025-06-18" + ) + + def test_identity_enables_person_processing_and_set(): event = { "event_type": MCPAnalyticsEventType.MCP_TOOLS_CALL, diff --git a/posthog/test/mcp/test_session_token.py b/posthog/test/mcp/test_session_token.py index c971ad21..cf536e80 100644 --- a/posthog/test/mcp/test_session_token.py +++ b/posthog/test/mcp/test_session_token.py @@ -191,17 +191,24 @@ async def test_multi_pod_two_instances_resolve_same_session_and_harness(): from posthog.mcp._instrumentation import resolve_session_and_client token_str = encode_session_id( - SessionTokenPayload(session_id="ses_shared", client_name="Claude Code") + SessionTokenPayload( + session_id="ses_shared", + client_name="Claude Code", + protocol_version="2025-06-18", + ) ) results = [] for _ in range(2): # two "pods" data = _data() token = decode_session_id(token_str) sid = await resolve_session_id(data, token_str, token=token) - # Harness comes back per request in the adapters (not from shared state). - _tok, name, _ver = resolve_session_and_client(token_str, None, None) - results.append((sid, data.session_source, name)) - assert results[0] == results[1] == ("ses_shared", "token", "Claude Code") + # Harness + protocol version come back per request in the adapters + # (not from shared state). + _tok, name, _ver, protocol = resolve_session_and_client(token_str, None, None) + results.append((sid, data.session_source, name, protocol)) + assert ( + results[0] == results[1] == ("ses_shared", "token", "Claude Code", "2025-06-18") + ) # --- ASGI minting middleware ------------------------------------------------- diff --git a/references/public_api_snapshot.txt b/references/public_api_snapshot.txt index b3276684..3a1e65db 100644 --- a/references/public_api_snapshot.txt +++ b/references/public_api_snapshot.txt @@ -708,6 +708,7 @@ attribute posthog.mcp.constants.PostHogMCPAnalyticsProperty.INTENT_SOURCE = '$mc attribute posthog.mcp.constants.PostHogMCPAnalyticsProperty.IS_ERROR = '$mcp_is_error' attribute posthog.mcp.constants.PostHogMCPAnalyticsProperty.LISTED_TOOL_NAMES = '$mcp_listed_tool_names' attribute posthog.mcp.constants.PostHogMCPAnalyticsProperty.PARAMETERS = '$mcp_parameters' +attribute posthog.mcp.constants.PostHogMCPAnalyticsProperty.PROTOCOL_VERSION = '$mcp_protocol_version' attribute posthog.mcp.constants.PostHogMCPAnalyticsProperty.RESOURCE_NAME = '$mcp_resource_name' attribute posthog.mcp.constants.PostHogMCPAnalyticsProperty.RESPONSE = '$mcp_response' attribute posthog.mcp.constants.PostHogMCPAnalyticsProperty.SERVER_NAME = '$mcp_server_name' @@ -1309,10 +1310,10 @@ method posthog.integrations.django.PosthogContextMiddleware.extract_tags(request method posthog.integrations.django.PosthogContextMiddleware.process_exception(request, exception) method posthog.mcp.McpAnalytics.capture(event: str, properties: Optional[dict] = None) -> None method posthog.mcp.McpAnalytics.flush() -> None -method posthog.mcp.posthog_mcp.PostHogMCP.capture_initialize(*, client_name: Optional[str] = None, client_version: Optional[str] = None, parameters: Any = None, response: Any = None, duration_ms: Optional[float] = None, distinct_id: Optional[str] = None, session_id: Optional[str] = None, set_properties: Optional[JsonRecord] = None, groups: Optional[Dict[str, str]] = None, properties: Optional[JsonRecord] = None, timestamp: Optional[datetime] = None) -> None -method posthog.mcp.posthog_mcp.PostHogMCP.capture_missing_capability(*, context: Optional[str] = None, parameters: Any = None, distinct_id: Optional[str] = None, session_id: Optional[str] = None, set_properties: Optional[JsonRecord] = None, groups: Optional[Dict[str, str]] = None, properties: Optional[JsonRecord] = None, timestamp: Optional[datetime] = None) -> None -method posthog.mcp.posthog_mcp.PostHogMCP.capture_tool_call(tool_name: str, *, intent: Optional[str] = None, intent_source: Optional[str] = None, parameters: Any = None, response: Any = None, duration_ms: Optional[float] = None, is_error: bool = False, error: Any = None, category: Optional[str] = None, tool_description: Optional[str] = None, distinct_id: Optional[str] = None, session_id: Optional[str] = None, set_properties: Optional[JsonRecord] = None, groups: Optional[Dict[str, str]] = None, properties: Optional[JsonRecord] = None, timestamp: Optional[datetime] = None) -> None -method posthog.mcp.posthog_mcp.PostHogMCP.capture_tools_list(*, tool_names: Optional[List[str]] = None, parameters: Any = None, response: Any = None, duration_ms: Optional[float] = None, is_error: bool = False, error: Any = None, distinct_id: Optional[str] = None, session_id: Optional[str] = None, set_properties: Optional[JsonRecord] = None, groups: Optional[Dict[str, str]] = None, properties: Optional[JsonRecord] = None, timestamp: Optional[datetime] = None) -> None +method posthog.mcp.posthog_mcp.PostHogMCP.capture_initialize(*, client_name: Optional[str] = None, client_version: Optional[str] = None, protocol_version: Optional[str] = None, parameters: Any = None, response: Any = None, duration_ms: Optional[float] = None, distinct_id: Optional[str] = None, session_id: Optional[str] = None, set_properties: Optional[JsonRecord] = None, groups: Optional[Dict[str, str]] = None, properties: Optional[JsonRecord] = None, timestamp: Optional[datetime] = None) -> None +method posthog.mcp.posthog_mcp.PostHogMCP.capture_missing_capability(*, context: Optional[str] = None, parameters: Any = None, protocol_version: Optional[str] = None, distinct_id: Optional[str] = None, session_id: Optional[str] = None, set_properties: Optional[JsonRecord] = None, groups: Optional[Dict[str, str]] = None, properties: Optional[JsonRecord] = None, timestamp: Optional[datetime] = None) -> None +method posthog.mcp.posthog_mcp.PostHogMCP.capture_tool_call(tool_name: str, *, intent: Optional[str] = None, intent_source: Optional[str] = None, parameters: Any = None, response: Any = None, duration_ms: Optional[float] = None, is_error: bool = False, error: Any = None, category: Optional[str] = None, tool_description: Optional[str] = None, protocol_version: Optional[str] = None, distinct_id: Optional[str] = None, session_id: Optional[str] = None, set_properties: Optional[JsonRecord] = None, groups: Optional[Dict[str, str]] = None, properties: Optional[JsonRecord] = None, timestamp: Optional[datetime] = None) -> None +method posthog.mcp.posthog_mcp.PostHogMCP.capture_tools_list(*, tool_names: Optional[List[str]] = None, parameters: Any = None, response: Any = None, duration_ms: Optional[float] = None, is_error: bool = False, error: Any = None, protocol_version: Optional[str] = None, distinct_id: Optional[str] = None, session_id: Optional[str] = None, set_properties: Optional[JsonRecord] = None, groups: Optional[Dict[str, str]] = None, properties: Optional[JsonRecord] = None, timestamp: Optional[datetime] = None) -> None method posthog.mcp.posthog_mcp.PostHogMCP.flush(timeout_seconds: Optional[float] = 10) -> None method posthog.mcp.posthog_mcp.PostHogMCP.prepare_tool_call(name: str, args: Optional[JsonRecord] = None) -> PreparedToolCall method posthog.mcp.posthog_mcp.PostHogMCP.prepare_tool_list(tools: List[Any], context: Union[bool, MCPAnalyticsContextOptions] = True, report_missing: bool = False) -> List[Any]