@@ -295,3 +295,60 @@ def boom() -> str:
295295 )
296296 # and nothing claims a conversation the agent never received
297297 assert all ("$mcp_conversation_id" not in e ["properties" ] for e in client .events )
298+
299+
300+ @pytest .mark .skipif (MCP_MAJOR != 1 , reason = "v1 FastMCP server" )
301+ async def test_in_tool_events_are_not_attributed_to_the_previous_caller ():
302+ """A custom event captured *inside* a tool body reads the shared
303+ ``data.session_id``. The conversation anchor can only be resolved after the
304+ call, so unless that field is settled first, caller B's in-tool event is
305+ attributed to caller A's session — and, through the identity cache, to
306+ caller A's person."""
307+ from types import SimpleNamespace
308+
309+ from mcp .server .fastmcp import FastMCP
310+
311+ from posthog .mcp import instrument
312+ from posthog .mcp ._internal import get_server_tracking_data
313+ from posthog .mcp .session import derive_session_id_from_mcp_session
314+
315+ def caller (session_header ):
316+ return SimpleNamespace (
317+ request_context = SimpleNamespace (
318+ request = SimpleNamespace (headers = {"mcp-session-id" : session_header }),
319+ session = SimpleNamespace (client_params = None ),
320+ )
321+ )
322+
323+ server = FastMCP ("in-tool" )
324+ seen = {}
325+
326+ @server .tool ()
327+ def echo (msg : str ) -> str :
328+ return msg
329+
330+ client = FakeClient ()
331+ handle = instrument (server , client , MCPAnalyticsOptions ())
332+
333+ @server .tool ()
334+ def emits (msg : str ) -> str :
335+ data = get_server_tracking_data (handle ._key )
336+ seen ["session_during_body" ] = data .session_id if data else None
337+ return msg
338+
339+ # Caller A runs first and leaves its session behind on the shared state.
340+ await server ._tool_manager .call_tool (
341+ "echo" , {"msg" : "a" , "context" : "caller A" }, context = caller ("session-A" )
342+ )
343+ # Caller B's tool body must see *its own* session, not A's.
344+ await server ._tool_manager .call_tool (
345+ "emits" , {"msg" : "b" , "context" : "caller B" }, context = caller ("session-B" )
346+ )
347+ await _flush ()
348+
349+ assert seen ["session_during_body" ] == derive_session_id_from_mcp_session (
350+ "session-B"
351+ )
352+ assert seen ["session_during_body" ] != derive_session_id_from_mcp_session (
353+ "session-A"
354+ )
0 commit comments