@posthog/mcp 0.10.6 → 0.10.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import type { MCPServerLike, McpEvent } from '../types';
1
+ import type { MCPServerLike, McpEvent, SessionInfo } from '../types';
2
2
  import type { LoggerFn } from './logger';
3
3
  /**
4
4
  * Materializes an `McpEvent` against the server's tracking data + session info,
@@ -10,5 +10,5 @@ import type { LoggerFn } from './logger';
10
10
  * Auto-capture callers (tool calls, listings, identify) intentionally ignore the
11
11
  * return value, keeping the tool path isolated from analytics latency/errors.
12
12
  */
13
- export declare function captureEvent(server: MCPServerLike, eventInput: McpEvent, logger: LoggerFn): Promise<void> | undefined;
13
+ export declare function captureEvent(server: MCPServerLike, eventInput: McpEvent, logger: LoggerFn, requestAttribution?: SessionInfo): Promise<void> | undefined;
14
14
  //# sourceMappingURL=capture.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/extensions/capture.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAGvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAGxC;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAkErH"}
1
+ {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/extensions/capture.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAGpE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAGxC;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,aAAa,EACrB,UAAU,EAAE,QAAQ,EACpB,MAAM,EAAE,QAAQ,EAChB,kBAAkB,CAAC,EAAE,WAAW,GAC/B,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAoE3B"}
@@ -29,12 +29,12 @@ __webpack_require__.d(__webpack_exports__, {
29
29
  const external_event_types_js_namespaceObject = require("./event-types.js");
30
30
  const external_internal_js_namespaceObject = require("./internal.js");
31
31
  const external_session_js_namespaceObject = require("./session.js");
32
- function captureEvent(server, eventInput, logger) {
32
+ function captureEvent(server, eventInput, logger, requestAttribution) {
33
33
  const data = (0, external_internal_js_namespaceObject.getServerTrackingData)(server);
34
34
  if (!data) return void logger('Warning: Server tracking data not found. Event will not be published.');
35
35
  const sink = data.sink;
36
36
  if (!sink) return;
37
- const sessionInfo = (0, external_session_js_namespaceObject.getSessionInfo)(server, data, eventInput.sessionId);
37
+ const sessionInfo = requestAttribution ?? (0, external_session_js_namespaceObject.getSessionInfo)(server, data, eventInput.sessionId);
38
38
  const duration = eventInput.duration || (eventInput.timestamp ? Date.now() - eventInput.timestamp.getTime() : void 0);
39
39
  const fullEvent = {
40
40
  id: eventInput.id || '',
@@ -1,12 +1,12 @@
1
1
  import { MCPAnalyticsEventType } from "./event-types.mjs";
2
2
  import { getServerTrackingData } from "./internal.mjs";
3
3
  import { getSessionInfo } from "./session.mjs";
4
- function captureEvent(server, eventInput, logger) {
4
+ function captureEvent(server, eventInput, logger, requestAttribution) {
5
5
  const data = getServerTrackingData(server);
6
6
  if (!data) return void logger('Warning: Server tracking data not found. Event will not be published.');
7
7
  const sink = data.sink;
8
8
  if (!sink) return;
9
- const sessionInfo = getSessionInfo(server, data, eventInput.sessionId);
9
+ const sessionInfo = requestAttribution ?? getSessionInfo(server, data, eventInput.sessionId);
10
10
  const duration = eventInput.duration || (eventInput.timestamp ? Date.now() - eventInput.timestamp.getTime() : void 0);
11
11
  const fullEvent = {
12
12
  id: eventInput.id || '',
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"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"}
@@ -55,17 +55,17 @@ async function captureToolCall(params) {
55
55
  const conversation = (0, external_conversation_id_js_namespaceObject.resolveConversationId)(ownership.conversationId, request.params?.arguments, request.params?.name, resolvedEventType === external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpMissingCapability ? (0, external_tools_js_namespaceObject.resolveMissingCapabilityToolName)(data.options) : '');
56
56
  const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership);
57
57
  const startTime = new Date();
58
- const event = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType);
59
- if (event && explicitContextIntent) (0, external_intent_js_namespaceObject.setExplicitContextIntent)(event, explicitContextIntent);
58
+ const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType);
59
+ if (preparedEvent && explicitContextIntent) (0, external_intent_js_namespaceObject.setExplicitContextIntent)(preparedEvent.event, explicitContextIntent);
60
60
  let result;
61
61
  try {
62
62
  result = await execute(downstreamRequest);
63
63
  } catch (error) {
64
- publishFailedToolEvent(server, event, error, startTime, conversation, data.logger);
64
+ publishFailedToolEvent(server, preparedEvent, error, startTime, conversation, data.logger);
65
65
  throw error;
66
66
  }
67
- const finalResult = applyConversationPromptBack(event, result, conversation);
68
- publishSuccessfulToolEvent(server, event, finalResult, startTime, data.logger, takeCapturedError);
67
+ const finalResult = applyConversationPromptBack(preparedEvent?.event ?? null, result, conversation);
68
+ publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError);
69
69
  return finalResult;
70
70
  }
71
71
  function getActiveAnalyticsParameterOwnership(data, toolName, override, isMissingCapabilityTool) {
@@ -90,7 +90,9 @@ function cloneRequestWithoutOwnedAnalyticsArguments(request, ownership) {
90
90
  async function prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, eventType) {
91
91
  try {
92
92
  const sessionId = (0, external_session_js_namespaceObject.getSessionId)(server, extra);
93
- await (0, external_internal_js_namespaceObject.handleIdentify)(server, data, sessionId, request, extra);
93
+ const sessionInfo = (0, external_session_js_namespaceObject.getSessionInfo)(server, data, sessionId);
94
+ const identity = await (0, external_internal_js_namespaceObject.handleIdentify)(server, data, sessionId, request, sessionInfo, extra);
95
+ const requestAttribution = (0, external_internal_js_namespaceObject.withIdentity)(sessionInfo, identity);
94
96
  const toolName = request.params?.name;
95
97
  const event = {
96
98
  sessionId,
@@ -105,7 +107,10 @@ async function prepareToolCallEvent(server, data, request, downstreamRequest, ex
105
107
  (0, external_client_identity_js_namespaceObject.stampMetaClientInfo)(event, request);
106
108
  await (0, external_tracing_helpers_js_namespaceObject.applyResolvedMetadata)(event, data, request, extra);
107
109
  (0, external_intent_js_namespaceObject.setEventIntent)(event, await (0, external_intent_js_namespaceObject.resolveToolCallIntent)(data, request, ownership.context, extra));
108
- return event;
110
+ return {
111
+ event,
112
+ requestAttribution
113
+ };
109
114
  } catch (error) {
110
115
  data.logger(`Warning: PostHog MCP analytics instrumentation failed for tool ${request.params?.name}, the tool will still run - ${error}`);
111
116
  return null;
@@ -117,8 +122,9 @@ function applyConversationPromptBack(event, result, conversation) {
117
122
  if (event) event.conversationId = void 0;
118
123
  return result;
119
124
  }
120
- function publishSuccessfulToolEvent(server, event, result, startTime, logger, takeCapturedError) {
121
- if (!event) return;
125
+ function publishSuccessfulToolEvent(server, preparedEvent, result, startTime, logger, takeCapturedError) {
126
+ if (!preparedEvent) return;
127
+ const { event, requestAttribution } = preparedEvent;
122
128
  try {
123
129
  if ((0, external_tracing_helpers_js_namespaceObject.isToolResultError)(result)) {
124
130
  event.isError = true;
@@ -127,19 +133,20 @@ function publishSuccessfulToolEvent(server, event, result, startTime, logger, ta
127
133
  } else event.isError = false;
128
134
  event.response = result;
129
135
  event.duration = Date.now() - startTime.getTime();
130
- (0, external_capture_js_namespaceObject.captureEvent)(server, event, logger);
136
+ (0, external_capture_js_namespaceObject.captureEvent)(server, event, logger, requestAttribution);
131
137
  } catch (error) {
132
138
  logger(`Warning: PostHog MCP analytics failed to publish tool event - ${error}`);
133
139
  }
134
140
  }
135
- function publishFailedToolEvent(server, event, error, startTime, conversation, logger) {
136
- if (!event) return;
141
+ function publishFailedToolEvent(server, preparedEvent, error, startTime, conversation, logger) {
142
+ if (!preparedEvent) return;
143
+ const { event, requestAttribution } = preparedEvent;
137
144
  try {
138
145
  if (conversation.minted) event.conversationId = void 0;
139
146
  event.isError = true;
140
147
  event.error = (0, external_exceptions_js_namespaceObject.captureException)(error);
141
148
  event.duration = Date.now() - startTime.getTime();
142
- (0, external_capture_js_namespaceObject.captureEvent)(server, event, logger);
149
+ (0, external_capture_js_namespaceObject.captureEvent)(server, event, logger, requestAttribution);
143
150
  } catch (publishError) {
144
151
  logger(`Warning: PostHog MCP analytics failed to publish failed tool event - ${publishError}`);
145
152
  }
@@ -194,15 +201,17 @@ async function isToolAdvertised(server, toolName, extra, logger) {
194
201
  async function handleListToolsRequest(server, originalListToolsHandler, request, extra, logger) {
195
202
  const data = (0, external_internal_js_namespaceObject.getServerTrackingData)(server);
196
203
  const startTime = new Date();
204
+ const sessionId = (0, external_session_js_namespaceObject.getSessionId)(server, extra);
205
+ const requestAttribution = (0, external_session_js_namespaceObject.getSessionInfo)(server, data, sessionId);
197
206
  const event = {
198
- sessionId: (0, external_session_js_namespaceObject.getSessionId)(server, extra),
207
+ sessionId,
199
208
  parameters: (0, external_mcp_payloads_js_namespaceObject.buildCapturedMcpParameters)(request),
200
209
  eventType: external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpToolsList,
201
210
  timestamp: startTime
202
211
  };
203
212
  (0, external_client_identity_js_namespaceObject.stampMetaClientInfo)(event, request);
204
213
  if (data) await (0, external_tracing_helpers_js_namespaceObject.applyResolvedMetadata)(event, data, request, extra);
205
- const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger);
214
+ const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger, requestAttribution);
206
215
  if (!data) {
207
216
  logger('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
208
217
  return {
@@ -214,7 +223,7 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
214
223
  event.error = (0, external_exceptions_js_namespaceObject.captureException)('No tools were sent to MCP client.');
215
224
  event.isError = true;
216
225
  event.duration = Date.now() - startTime.getTime();
217
- (0, external_capture_js_namespaceObject.captureEvent)(server, event, data.logger);
226
+ (0, external_capture_js_namespaceObject.captureEvent)(server, event, data.logger, requestAttribution);
218
227
  return {
219
228
  tools
220
229
  };
@@ -225,7 +234,7 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
225
234
  event.listedToolNames = collectListedToolNames(tools);
226
235
  event.isError = false;
227
236
  event.duration = Date.now() - startTime.getTime();
228
- (0, external_capture_js_namespaceObject.captureEvent)(server, event, data.logger);
237
+ (0, external_capture_js_namespaceObject.captureEvent)(server, event, data.logger, requestAttribution);
229
238
  return {
230
239
  tools
231
240
  };
@@ -238,7 +247,7 @@ function collectListedToolNames(tools) {
238
247
  const names = tools.map((tool)=>tool?.name).filter((name)=>'string' == typeof name);
239
248
  return names.length > 0 ? names : void 0;
240
249
  }
241
- async function getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger) {
250
+ async function getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger, requestAttribution) {
242
251
  try {
243
252
  const data = (0, external_internal_js_namespaceObject.getServerTrackingData)(server);
244
253
  const originalResponse = await originalListToolsHandler(request, extra);
@@ -270,7 +279,7 @@ async function getTracedToolsList(server, originalListToolsHandler, request, ext
270
279
  event.error = (0, external_exceptions_js_namespaceObject.captureException)(error);
271
280
  event.isError = true;
272
281
  event.duration = event.timestamp ? Date.now() - event.timestamp.getTime() : 0;
273
- (0, external_capture_js_namespaceObject.captureEvent)(server, event, logger);
282
+ (0, external_capture_js_namespaceObject.captureEvent)(server, event, logger, requestAttribution);
274
283
  throw error;
275
284
  }
276
285
  }
@@ -319,19 +328,19 @@ function mintStatelessSessionOnInitialize(server, data, request, extra) {
319
328
  return;
320
329
  }
321
330
  }
322
- function upgradeMintedTokenToNegotiated(server, data, mintedSessionId, negotiatedProtocolVersion) {
331
+ function upgradeMintedTokenToNegotiated(server, mintedSessionId, sessionInfo, negotiatedProtocolVersion, logger) {
323
332
  try {
324
333
  const transport = server.transport;
325
334
  if (!transport) return;
326
335
  const token = (0, external_session_token_js_namespaceObject.encodeSessionId)({
327
336
  sessionId: mintedSessionId,
328
- clientName: data.sessionInfo.clientName,
329
- clientVersion: data.sessionInfo.clientVersion,
337
+ clientName: sessionInfo.clientName,
338
+ clientVersion: sessionInfo.clientVersion,
330
339
  protocolVersion: negotiatedProtocolVersion
331
340
  });
332
341
  (0, external_session_token_js_namespaceObject.writeSessionIdToTransport)(transport, token);
333
342
  } catch (error) {
334
- data.logger(`Warning: PostHog MCP analytics failed to upgrade the stateless session token - ${error}`);
343
+ logger(`Warning: PostHog MCP analytics failed to upgrade the stateless session token - ${error}`);
335
344
  }
336
345
  }
337
346
  function readInitializeClientInfo(request) {
@@ -351,7 +360,15 @@ async function handleInitializeRequest(server, originalInitializeHandler, reques
351
360
  }
352
361
  const mintedSessionId = mintStatelessSessionOnInitialize(server, data, request, extra);
353
362
  const sessionId = (0, external_session_js_namespaceObject.getSessionId)(server, extra);
354
- await (0, external_internal_js_namespaceObject.handleIdentify)(server, data, sessionId, request, extra);
363
+ const sessionInfo = (0, external_session_js_namespaceObject.getSessionInfo)(server, data, sessionId);
364
+ const initializeClientInfo = readInitializeClientInfo(request);
365
+ const requestSessionInfo = {
366
+ ...sessionInfo,
367
+ clientName: initializeClientInfo?.name ?? sessionInfo.clientName,
368
+ clientVersion: initializeClientInfo?.version ?? sessionInfo.clientVersion
369
+ };
370
+ const identity = await (0, external_internal_js_namespaceObject.handleIdentify)(server, data, sessionId, request, requestSessionInfo, extra);
371
+ const requestAttribution = (0, external_internal_js_namespaceObject.withIdentity)(requestSessionInfo, identity);
355
372
  const event = {
356
373
  sessionId,
357
374
  resourceName: request.params?.name || 'Unknown Tool Name',
@@ -365,10 +382,18 @@ async function handleInitializeRequest(server, originalInitializeHandler, reques
365
382
  event.response = result;
366
383
  const negotiatedProtocolVersion = readProtocolVersion(result, request);
367
384
  event.protocolVersion = negotiatedProtocolVersion;
368
- data.sessionInfo.protocolVersion = negotiatedProtocolVersion;
369
- (0, external_internal_js_namespaceObject.setServerTrackingData)(server, data);
370
- if (mintedSessionId) upgradeMintedTokenToNegotiated(server, data, mintedSessionId, negotiatedProtocolVersion);
371
- (0, external_capture_js_namespaceObject.captureEvent)(server, event, data.logger);
385
+ if (data.sessionId === sessionId) {
386
+ data.sessionInfo = {
387
+ ...data.sessionInfo,
388
+ protocolVersion: negotiatedProtocolVersion
389
+ };
390
+ (0, external_internal_js_namespaceObject.setServerTrackingData)(server, data);
391
+ }
392
+ if (mintedSessionId) upgradeMintedTokenToNegotiated(server, mintedSessionId, requestSessionInfo, negotiatedProtocolVersion, data.logger);
393
+ (0, external_capture_js_namespaceObject.captureEvent)(server, event, data.logger, {
394
+ ...requestAttribution,
395
+ protocolVersion: negotiatedProtocolVersion
396
+ });
372
397
  return result;
373
398
  }
374
399
  function readProtocolVersion(result, request) {
@@ -6,10 +6,10 @@ import { captureEvent } from "./capture.mjs";
6
6
  import { MCPAnalyticsEventType } from "./event-types.mjs";
7
7
  import { captureException } from "./exceptions.mjs";
8
8
  import { resolveToolCallIntent, setEventIntent, setExplicitContextIntent } from "./intent.mjs";
9
- import { getServerTrackingData, handleIdentify, setServerTrackingData } from "./internal.mjs";
9
+ import { getServerTrackingData, handleIdentify, setServerTrackingData, withIdentity } from "./internal.mjs";
10
10
  import { buildCapturedMcpParameters } from "./mcp-payloads.mjs";
11
11
  import { getLiteralValue, getObjectShape } from "./mcp-sdk-compat.mjs";
12
- import { getSessionId, newSessionId } from "./session.mjs";
12
+ import { getSessionId, getSessionInfo, newSessionId } from "./session.mjs";
13
13
  import { encodeSessionId, readMcpSessionHeader, writeSessionIdToTransport } from "./session-token.mjs";
14
14
  import { getReportMissingToolDescriptor, resolveMissingCapabilityToolName } from "./tools.mjs";
15
15
  import { applyResolvedMetadata, isToolResultError } from "./tracing-helpers.mjs";
@@ -20,17 +20,17 @@ async function captureToolCall(params) {
20
20
  const conversation = resolveConversationId(ownership.conversationId, request.params?.arguments, request.params?.name, resolvedEventType === MCPAnalyticsEventType.mcpMissingCapability ? resolveMissingCapabilityToolName(data.options) : '');
21
21
  const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership);
22
22
  const startTime = new Date();
23
- const event = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType);
24
- if (event && explicitContextIntent) setExplicitContextIntent(event, explicitContextIntent);
23
+ const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType);
24
+ if (preparedEvent && explicitContextIntent) setExplicitContextIntent(preparedEvent.event, explicitContextIntent);
25
25
  let result;
26
26
  try {
27
27
  result = await execute(downstreamRequest);
28
28
  } catch (error) {
29
- publishFailedToolEvent(server, event, error, startTime, conversation, data.logger);
29
+ publishFailedToolEvent(server, preparedEvent, error, startTime, conversation, data.logger);
30
30
  throw error;
31
31
  }
32
- const finalResult = applyConversationPromptBack(event, result, conversation);
33
- publishSuccessfulToolEvent(server, event, finalResult, startTime, data.logger, takeCapturedError);
32
+ const finalResult = applyConversationPromptBack(preparedEvent?.event ?? null, result, conversation);
33
+ publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError);
34
34
  return finalResult;
35
35
  }
36
36
  function getActiveAnalyticsParameterOwnership(data, toolName, override, isMissingCapabilityTool) {
@@ -55,7 +55,9 @@ function cloneRequestWithoutOwnedAnalyticsArguments(request, ownership) {
55
55
  async function prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, eventType) {
56
56
  try {
57
57
  const sessionId = getSessionId(server, extra);
58
- await handleIdentify(server, data, sessionId, request, extra);
58
+ const sessionInfo = getSessionInfo(server, data, sessionId);
59
+ const identity = await handleIdentify(server, data, sessionId, request, sessionInfo, extra);
60
+ const requestAttribution = withIdentity(sessionInfo, identity);
59
61
  const toolName = request.params?.name;
60
62
  const event = {
61
63
  sessionId,
@@ -70,7 +72,10 @@ async function prepareToolCallEvent(server, data, request, downstreamRequest, ex
70
72
  stampMetaClientInfo(event, request);
71
73
  await applyResolvedMetadata(event, data, request, extra);
72
74
  setEventIntent(event, await resolveToolCallIntent(data, request, ownership.context, extra));
73
- return event;
75
+ return {
76
+ event,
77
+ requestAttribution
78
+ };
74
79
  } catch (error) {
75
80
  data.logger(`Warning: PostHog MCP analytics instrumentation failed for tool ${request.params?.name}, the tool will still run - ${error}`);
76
81
  return null;
@@ -82,8 +87,9 @@ function applyConversationPromptBack(event, result, conversation) {
82
87
  if (event) event.conversationId = void 0;
83
88
  return result;
84
89
  }
85
- function publishSuccessfulToolEvent(server, event, result, startTime, logger, takeCapturedError) {
86
- if (!event) return;
90
+ function publishSuccessfulToolEvent(server, preparedEvent, result, startTime, logger, takeCapturedError) {
91
+ if (!preparedEvent) return;
92
+ const { event, requestAttribution } = preparedEvent;
87
93
  try {
88
94
  if (isToolResultError(result)) {
89
95
  event.isError = true;
@@ -92,19 +98,20 @@ function publishSuccessfulToolEvent(server, event, result, startTime, logger, ta
92
98
  } else event.isError = false;
93
99
  event.response = result;
94
100
  event.duration = Date.now() - startTime.getTime();
95
- captureEvent(server, event, logger);
101
+ captureEvent(server, event, logger, requestAttribution);
96
102
  } catch (error) {
97
103
  logger(`Warning: PostHog MCP analytics failed to publish tool event - ${error}`);
98
104
  }
99
105
  }
100
- function publishFailedToolEvent(server, event, error, startTime, conversation, logger) {
101
- if (!event) return;
106
+ function publishFailedToolEvent(server, preparedEvent, error, startTime, conversation, logger) {
107
+ if (!preparedEvent) return;
108
+ const { event, requestAttribution } = preparedEvent;
102
109
  try {
103
110
  if (conversation.minted) event.conversationId = void 0;
104
111
  event.isError = true;
105
112
  event.error = captureException(error);
106
113
  event.duration = Date.now() - startTime.getTime();
107
- captureEvent(server, event, logger);
114
+ captureEvent(server, event, logger, requestAttribution);
108
115
  } catch (publishError) {
109
116
  logger(`Warning: PostHog MCP analytics failed to publish failed tool event - ${publishError}`);
110
117
  }
@@ -159,15 +166,17 @@ async function isToolAdvertised(server, toolName, extra, logger) {
159
166
  async function handleListToolsRequest(server, originalListToolsHandler, request, extra, logger) {
160
167
  const data = getServerTrackingData(server);
161
168
  const startTime = new Date();
169
+ const sessionId = getSessionId(server, extra);
170
+ const requestAttribution = getSessionInfo(server, data, sessionId);
162
171
  const event = {
163
- sessionId: getSessionId(server, extra),
172
+ sessionId,
164
173
  parameters: buildCapturedMcpParameters(request),
165
174
  eventType: MCPAnalyticsEventType.mcpToolsList,
166
175
  timestamp: startTime
167
176
  };
168
177
  stampMetaClientInfo(event, request);
169
178
  if (data) await applyResolvedMetadata(event, data, request, extra);
170
- const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger);
179
+ const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger, requestAttribution);
171
180
  if (!data) {
172
181
  logger('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
173
182
  return {
@@ -179,7 +188,7 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
179
188
  event.error = captureException('No tools were sent to MCP client.');
180
189
  event.isError = true;
181
190
  event.duration = Date.now() - startTime.getTime();
182
- captureEvent(server, event, data.logger);
191
+ captureEvent(server, event, data.logger, requestAttribution);
183
192
  return {
184
193
  tools
185
194
  };
@@ -190,7 +199,7 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
190
199
  event.listedToolNames = collectListedToolNames(tools);
191
200
  event.isError = false;
192
201
  event.duration = Date.now() - startTime.getTime();
193
- captureEvent(server, event, data.logger);
202
+ captureEvent(server, event, data.logger, requestAttribution);
194
203
  return {
195
204
  tools
196
205
  };
@@ -203,7 +212,7 @@ function collectListedToolNames(tools) {
203
212
  const names = tools.map((tool)=>tool?.name).filter((name)=>'string' == typeof name);
204
213
  return names.length > 0 ? names : void 0;
205
214
  }
206
- async function getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger) {
215
+ async function getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger, requestAttribution) {
207
216
  try {
208
217
  const data = getServerTrackingData(server);
209
218
  const originalResponse = await originalListToolsHandler(request, extra);
@@ -235,7 +244,7 @@ async function getTracedToolsList(server, originalListToolsHandler, request, ext
235
244
  event.error = captureException(error);
236
245
  event.isError = true;
237
246
  event.duration = event.timestamp ? Date.now() - event.timestamp.getTime() : 0;
238
- captureEvent(server, event, logger);
247
+ captureEvent(server, event, logger, requestAttribution);
239
248
  throw error;
240
249
  }
241
250
  }
@@ -284,19 +293,19 @@ function mintStatelessSessionOnInitialize(server, data, request, extra) {
284
293
  return;
285
294
  }
286
295
  }
287
- function upgradeMintedTokenToNegotiated(server, data, mintedSessionId, negotiatedProtocolVersion) {
296
+ function upgradeMintedTokenToNegotiated(server, mintedSessionId, sessionInfo, negotiatedProtocolVersion, logger) {
288
297
  try {
289
298
  const transport = server.transport;
290
299
  if (!transport) return;
291
300
  const token = encodeSessionId({
292
301
  sessionId: mintedSessionId,
293
- clientName: data.sessionInfo.clientName,
294
- clientVersion: data.sessionInfo.clientVersion,
302
+ clientName: sessionInfo.clientName,
303
+ clientVersion: sessionInfo.clientVersion,
295
304
  protocolVersion: negotiatedProtocolVersion
296
305
  });
297
306
  writeSessionIdToTransport(transport, token);
298
307
  } catch (error) {
299
- data.logger(`Warning: PostHog MCP analytics failed to upgrade the stateless session token - ${error}`);
308
+ logger(`Warning: PostHog MCP analytics failed to upgrade the stateless session token - ${error}`);
300
309
  }
301
310
  }
302
311
  function readInitializeClientInfo(request) {
@@ -316,7 +325,15 @@ async function handleInitializeRequest(server, originalInitializeHandler, reques
316
325
  }
317
326
  const mintedSessionId = mintStatelessSessionOnInitialize(server, data, request, extra);
318
327
  const sessionId = getSessionId(server, extra);
319
- await handleIdentify(server, data, sessionId, request, extra);
328
+ const sessionInfo = getSessionInfo(server, data, sessionId);
329
+ const initializeClientInfo = readInitializeClientInfo(request);
330
+ const requestSessionInfo = {
331
+ ...sessionInfo,
332
+ clientName: initializeClientInfo?.name ?? sessionInfo.clientName,
333
+ clientVersion: initializeClientInfo?.version ?? sessionInfo.clientVersion
334
+ };
335
+ const identity = await handleIdentify(server, data, sessionId, request, requestSessionInfo, extra);
336
+ const requestAttribution = withIdentity(requestSessionInfo, identity);
320
337
  const event = {
321
338
  sessionId,
322
339
  resourceName: request.params?.name || 'Unknown Tool Name',
@@ -330,10 +347,18 @@ async function handleInitializeRequest(server, originalInitializeHandler, reques
330
347
  event.response = result;
331
348
  const negotiatedProtocolVersion = readProtocolVersion(result, request);
332
349
  event.protocolVersion = negotiatedProtocolVersion;
333
- data.sessionInfo.protocolVersion = negotiatedProtocolVersion;
334
- setServerTrackingData(server, data);
335
- if (mintedSessionId) upgradeMintedTokenToNegotiated(server, data, mintedSessionId, negotiatedProtocolVersion);
336
- captureEvent(server, event, data.logger);
350
+ if (data.sessionId === sessionId) {
351
+ data.sessionInfo = {
352
+ ...data.sessionInfo,
353
+ protocolVersion: negotiatedProtocolVersion
354
+ };
355
+ setServerTrackingData(server, data);
356
+ }
357
+ if (mintedSessionId) upgradeMintedTokenToNegotiated(server, mintedSessionId, requestSessionInfo, negotiatedProtocolVersion, data.logger);
358
+ captureEvent(server, event, data.logger, {
359
+ ...requestAttribution,
360
+ protocolVersion: negotiatedProtocolVersion
361
+ });
337
362
  return result;
338
363
  }
339
364
  function readProtocolVersion(result, request) {
@@ -1,4 +1,4 @@
1
- import type { CompatibleRequestHandlerExtra, MCPAnalyticsData, MCPRequestLike, MCPServerLike, UserIdentity } from '../types';
1
+ import type { CompatibleRequestHandlerExtra, MCPAnalyticsData, MCPRequestLike, MCPServerLike, SessionInfo, UserIdentity } from '../types';
2
2
  /**
3
3
  * Bounded LRU cache for session identities, capped at `maxSize` entries so a
4
4
  * long-lived server can't accumulate identities for unboundedly many sessions.
@@ -24,7 +24,8 @@ export declare function mergeIdentities(previous: UserIdentity | undefined, next
24
24
  * `$identify` event at most once per session: at `initialize`, when a
25
25
  * long-lived server first sees the identity, or when it materially changes.
26
26
  */
27
- export declare function handleIdentify(server: MCPServerLike, data: MCPAnalyticsData, sessionId: string, request: MCPRequestLike, extra?: CompatibleRequestHandlerExtra): Promise<void>;
27
+ export declare function handleIdentify(server: MCPServerLike, data: MCPAnalyticsData, sessionId: string, request: MCPRequestLike, requestAttribution: SessionInfo, extra?: CompatibleRequestHandlerExtra): Promise<UserIdentity | undefined>;
28
+ export declare function withIdentity(sessionInfo: SessionInfo, identity: UserIdentity | undefined): SessionInfo;
28
29
  /**
29
30
  * Resolves the `eventProperties` callback. Returns null when no callback is
30
31
  * configured, or when it returns nullish or throws — a throw never propagates
@@ -1 +1 @@
1
- {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../src/extensions/internal.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,cAAc,EACd,aAAa,EAEb,YAAY,EACb,MAAM,UAAU,CAAA;AAKjB;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;gBAErB,OAAO,SAAO;IAI1B,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAWhD,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI;IAapD,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAI/B,IAAI,IAAI,MAAM;CAGf;AAID,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,gBAAgB,GAAG,SAAS,CAEzF;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAEzF;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,OAAO,CA4B5E;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,SAAS,EAAE,IAAI,EAAE,YAAY,GAAG,YAAY,CAapG;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,gBAAgB,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,EACvB,KAAK,CAAC,EAAE,6BAA6B,GACpC,OAAO,CAAC,IAAI,CAAC,CAkDf;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,cAAc,EACvB,KAAK,CAAC,EAAE,6BAA6B,GACpC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAUzC"}
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../src/extensions/internal.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,cAAc,EACd,aAAa,EAEb,WAAW,EACX,YAAY,EACb,MAAM,UAAU,CAAA;AAKjB;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;gBAErB,OAAO,SAAO;IAI1B,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAWhD,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI;IAapD,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAI/B,IAAI,IAAI,MAAM;CAGf;AAID,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,gBAAgB,GAAG,SAAS,CAEzF;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAEzF;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,OAAO,CA4B5E;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,SAAS,EAAE,IAAI,EAAE,YAAY,GAAG,YAAY,CAapG;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,gBAAgB,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,EACvB,kBAAkB,EAAE,WAAW,EAC/B,KAAK,CAAC,EAAE,6BAA6B,GACpC,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAsDnC;AAED,wBAAgB,YAAY,CAAC,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,GAAG,SAAS,GAAG,WAAW,CAOtG;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,cAAc,EACvB,KAAK,CAAC,EAAE,6BAA6B,GACpC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAUzC"}
@@ -26,11 +26,12 @@ __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
27
  mergeIdentities: ()=>mergeIdentities,
28
28
  areIdentitiesEqual: ()=>areIdentitiesEqual,
29
+ getServerTrackingData: ()=>getServerTrackingData,
30
+ setServerTrackingData: ()=>setServerTrackingData,
29
31
  resolveEventProperties: ()=>resolveEventProperties,
32
+ withIdentity: ()=>withIdentity,
30
33
  handleIdentify: ()=>handleIdentify,
31
- getServerTrackingData: ()=>getServerTrackingData,
32
- IdentityCache: ()=>IdentityCache,
33
- setServerTrackingData: ()=>setServerTrackingData
34
+ IdentityCache: ()=>IdentityCache
34
35
  });
35
36
  const external_event_types_js_namespaceObject = require("./event-types.js");
36
37
  const external_capture_js_namespaceObject = require("./capture.js");
@@ -94,8 +95,10 @@ function mergeIdentities(previous, next) {
94
95
  groups: next.groups ?? previous.groups
95
96
  };
96
97
  }
97
- async function handleIdentify(server, data, sessionId, request, extra) {
98
- if (!data.options.identify) return;
98
+ async function handleIdentify(server, data, sessionId, request, requestAttribution, extra) {
99
+ const identityBeforeRequest = data.identifiedSessions.get(sessionId);
100
+ const sessionSourceBeforeIdentify = data.sessionSource;
101
+ if (!data.options.identify) return identityBeforeRequest;
99
102
  const identifyEvent = {
100
103
  sessionId,
101
104
  resourceName: getRequestResourceName(request),
@@ -114,17 +117,28 @@ async function handleIdentify(server, data, sessionId, request, extra) {
114
117
  const mergedIdentity = mergeIdentities(previousIdentity, identityResult);
115
118
  const changed = void 0 !== previousIdentity && !areIdentitiesEqual(previousIdentity, mergedIdentity);
116
119
  const firstSeen = void 0 === previousIdentity;
117
- const announcedAtInitialize = 'token' === data.sessionSource && 'initialize' !== request.method;
120
+ const announcedAtInitialize = 'token' === sessionSourceBeforeIdentify && 'initialize' !== request.method;
118
121
  const shouldPublish = changed || firstSeen && !announcedAtInitialize;
119
122
  data.identifiedSessions.set(sessionId, mergedIdentity);
120
123
  if (shouldPublish) {
121
124
  data.logger(`Identified session ${sessionId}`);
122
- (0, external_capture_js_namespaceObject.captureEvent)(server, identifyEvent, data.logger);
125
+ (0, external_capture_js_namespaceObject.captureEvent)(server, identifyEvent, data.logger, withIdentity(requestAttribution, mergedIdentity));
123
126
  }
124
- } else data.logger(`Warning: Supplied identify function returned null for session ${sessionId}`);
127
+ return mergedIdentity;
128
+ }
129
+ data.logger(`Warning: Supplied identify function returned null for session ${sessionId}`);
125
130
  } catch (error) {
126
131
  data.logger(`Error: User supplied identify function threw an error while identifying session ${sessionId} - ${error}`);
127
132
  }
133
+ return identityBeforeRequest;
134
+ }
135
+ function withIdentity(sessionInfo, identity) {
136
+ return {
137
+ ...sessionInfo,
138
+ identifyActorGivenId: identity?.distinctId,
139
+ identifyActorData: identity?.properties || {},
140
+ identifyActorGroups: identity?.groups
141
+ };
128
142
  }
129
143
  async function resolveEventProperties(data, request, extra) {
130
144
  if (!data.options.eventProperties) return null;
@@ -148,6 +162,7 @@ exports.handleIdentify = __webpack_exports__.handleIdentify;
148
162
  exports.mergeIdentities = __webpack_exports__.mergeIdentities;
149
163
  exports.resolveEventProperties = __webpack_exports__.resolveEventProperties;
150
164
  exports.setServerTrackingData = __webpack_exports__.setServerTrackingData;
165
+ exports.withIdentity = __webpack_exports__.withIdentity;
151
166
  for(var __webpack_i__ in __webpack_exports__)if (-1 === [
152
167
  "IdentityCache",
153
168
  "areIdentitiesEqual",
@@ -155,7 +170,8 @@ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
155
170
  "handleIdentify",
156
171
  "mergeIdentities",
157
172
  "resolveEventProperties",
158
- "setServerTrackingData"
173
+ "setServerTrackingData",
174
+ "withIdentity"
159
175
  ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
160
176
  Object.defineProperty(exports, '__esModule', {
161
177
  value: true
@@ -60,8 +60,10 @@ function mergeIdentities(previous, next) {
60
60
  groups: next.groups ?? previous.groups
61
61
  };
62
62
  }
63
- async function handleIdentify(server, data, sessionId, request, extra) {
64
- if (!data.options.identify) return;
63
+ async function handleIdentify(server, data, sessionId, request, requestAttribution, extra) {
64
+ const identityBeforeRequest = data.identifiedSessions.get(sessionId);
65
+ const sessionSourceBeforeIdentify = data.sessionSource;
66
+ if (!data.options.identify) return identityBeforeRequest;
65
67
  const identifyEvent = {
66
68
  sessionId,
67
69
  resourceName: getRequestResourceName(request),
@@ -80,17 +82,28 @@ async function handleIdentify(server, data, sessionId, request, extra) {
80
82
  const mergedIdentity = mergeIdentities(previousIdentity, identityResult);
81
83
  const changed = void 0 !== previousIdentity && !areIdentitiesEqual(previousIdentity, mergedIdentity);
82
84
  const firstSeen = void 0 === previousIdentity;
83
- const announcedAtInitialize = 'token' === data.sessionSource && 'initialize' !== request.method;
85
+ const announcedAtInitialize = 'token' === sessionSourceBeforeIdentify && 'initialize' !== request.method;
84
86
  const shouldPublish = changed || firstSeen && !announcedAtInitialize;
85
87
  data.identifiedSessions.set(sessionId, mergedIdentity);
86
88
  if (shouldPublish) {
87
89
  data.logger(`Identified session ${sessionId}`);
88
- captureEvent(server, identifyEvent, data.logger);
90
+ captureEvent(server, identifyEvent, data.logger, withIdentity(requestAttribution, mergedIdentity));
89
91
  }
90
- } else data.logger(`Warning: Supplied identify function returned null for session ${sessionId}`);
92
+ return mergedIdentity;
93
+ }
94
+ data.logger(`Warning: Supplied identify function returned null for session ${sessionId}`);
91
95
  } catch (error) {
92
96
  data.logger(`Error: User supplied identify function threw an error while identifying session ${sessionId} - ${error}`);
93
97
  }
98
+ return identityBeforeRequest;
99
+ }
100
+ function withIdentity(sessionInfo, identity) {
101
+ return {
102
+ ...sessionInfo,
103
+ identifyActorGivenId: identity?.distinctId,
104
+ identifyActorData: identity?.properties || {},
105
+ identifyActorGroups: identity?.groups
106
+ };
94
107
  }
95
108
  async function resolveEventProperties(data, request, extra) {
96
109
  if (!data.options.eventProperties) return null;
@@ -107,4 +120,4 @@ function getRequestResourceName(request) {
107
120
  if (!params || 'object' != typeof params || !('name' in params)) return 'Unknown';
108
121
  return 'string' == typeof params.name ? params.name : 'Unknown';
109
122
  }
110
- export { IdentityCache, areIdentitiesEqual, getServerTrackingData, handleIdentify, mergeIdentities, resolveEventProperties, setServerTrackingData };
123
+ export { IdentityCache, areIdentitiesEqual, getServerTrackingData, handleIdentify, mergeIdentities, resolveEventProperties, setServerTrackingData, withIdentity };
@@ -90,7 +90,9 @@ function getSessionInfo(server, data, sessionId) {
90
90
  if (!data) return sessionInfo;
91
91
  data.sessionInfo = sessionInfo;
92
92
  (0, external_internal_js_namespaceObject.setServerTrackingData)(server, data);
93
- return data.sessionInfo;
93
+ return {
94
+ ...sessionInfo
95
+ };
94
96
  }
95
97
  exports.deriveSessionIdFromMCPSession = __webpack_exports__.deriveSessionIdFromMCPSession;
96
98
  exports.getSessionId = __webpack_exports__.getSessionId;
@@ -59,6 +59,8 @@ function getSessionInfo(server, data, sessionId) {
59
59
  if (!data) return sessionInfo;
60
60
  data.sessionInfo = sessionInfo;
61
61
  setServerTrackingData(server, data);
62
- return data.sessionInfo;
62
+ return {
63
+ ...sessionInfo
64
+ };
63
65
  }
64
66
  export { deriveSessionIdFromMCPSession, getSessionId, getSessionInfo, newSessionId };
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const version = "0.10.6";
1
+ export declare const version = "0.10.7";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -26,7 +26,7 @@ __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
27
  version: ()=>version
28
28
  });
29
- const version = '0.10.6';
29
+ const version = '0.10.7';
30
30
  exports.version = __webpack_exports__.version;
31
31
  for(var __webpack_i__ in __webpack_exports__)if (-1 === [
32
32
  "version"
package/dist/version.mjs CHANGED
@@ -1,2 +1,2 @@
1
- const version = '0.10.6';
1
+ const version = '0.10.7';
2
2
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/mcp",
3
- "version": "0.10.6",
3
+ "version": "0.10.7",
4
4
  "bugs": {
5
5
  "url": "https://github.com/PostHog/posthog-js/issues"
6
6
  },
@@ -39,8 +39,8 @@
39
39
  "@types/node": "^20.0.0",
40
40
  "jest": "29.7.0",
41
41
  "zod": "^3.25.0",
42
- "posthog-node": "^5.47.8",
43
- "@posthog-tooling/tsconfig-base": "1.1.1"
42
+ "@posthog-tooling/tsconfig-base": "1.1.1",
43
+ "posthog-node": "^5.47.9"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@modelcontextprotocol/sdk": ">=1.26.0",
@@ -3,7 +3,7 @@
3
3
  // Copyright (c) 2025 AgentCat, Inc. (formerly MCPcat)
4
4
  // Licensed under the MIT License: https://github.com/agentcathq/agentcat-typescript-sdk/blob/main/LICENSE
5
5
 
6
- import type { MCPServerLike, McpEvent } from '../types'
6
+ import type { MCPServerLike, McpEvent, SessionInfo } from '../types'
7
7
  import { MCPAnalyticsEventType } from './event-types'
8
8
  import { getServerTrackingData } from './internal'
9
9
  import type { LoggerFn } from './logger'
@@ -19,7 +19,12 @@ import { getSessionInfo } from './session'
19
19
  * Auto-capture callers (tool calls, listings, identify) intentionally ignore the
20
20
  * return value, keeping the tool path isolated from analytics latency/errors.
21
21
  */
22
- export function captureEvent(server: MCPServerLike, eventInput: McpEvent, logger: LoggerFn): Promise<void> | undefined {
22
+ export function captureEvent(
23
+ server: MCPServerLike,
24
+ eventInput: McpEvent,
25
+ logger: LoggerFn,
26
+ requestAttribution?: SessionInfo
27
+ ): Promise<void> | undefined {
23
28
  const data = getServerTrackingData(server)
24
29
  if (!data) {
25
30
  logger('Warning: Server tracking data not found. Event will not be published.')
@@ -31,7 +36,10 @@ export function captureEvent(server: MCPServerLike, eventInput: McpEvent, logger
31
36
  return
32
37
  }
33
38
 
34
- const sessionInfo = getSessionInfo(server, data, eventInput.sessionId)
39
+ // Instrumented request paths pass an immutable snapshot resolved before any
40
+ // user callback or handler can yield to a concurrent request. Custom capture
41
+ // has no request context, so it intentionally falls back to current state.
42
+ const sessionInfo = requestAttribution ?? getSessionInfo(server, data, eventInput.sessionId)
35
43
 
36
44
  const duration =
37
45
  eventInput.duration || (eventInput.timestamp ? Date.now() - eventInput.timestamp.getTime() : undefined)
@@ -48,9 +56,8 @@ export function captureEvent(server: MCPServerLike, eventInput: McpEvent, logger
48
56
  sdkVersion: sessionInfo.sdkVersion,
49
57
  serverName: sessionInfo.serverName,
50
58
  serverVersion: sessionInfo.serverVersion,
51
- // Prefer identity the event captured for *this* request (from `_meta`, see
52
- // stampMetaClientInfo) over the server-wide sessionInfo, so a concurrent
53
- // request from another client can't misattribute this one.
59
+ // Prefer client metadata stamped onto this event from the request's `_meta`
60
+ // over sessionInfo, so concurrent stateless requests stay isolated.
54
61
  clientName: eventInput.clientName ?? sessionInfo.clientName,
55
62
  clientVersion: eventInput.clientVersion ?? sessionInfo.clientVersion,
56
63
  identifyActorGivenId: sessionInfo.identifyActorGivenId,
@@ -12,6 +12,7 @@ import type {
12
12
  MCPServerLike,
13
13
  McpEvent,
14
14
  ServerClientInfoLike,
15
+ SessionInfo,
15
16
  } from '../types'
16
17
  import { getAnalyticsParameterOwnership, stripOwnedAnalyticsArguments } from './analytics-parameters'
17
18
  import { addContextParameterToTools, getContextDescription, isContextEnabled } from './context-parameters'
@@ -27,11 +28,11 @@ import { captureEvent } from './capture'
27
28
  import { MCPAnalyticsEventType } from './event-types'
28
29
  import { captureException } from './exceptions'
29
30
  import { resolveToolCallIntent, setEventIntent, setExplicitContextIntent } from './intent'
30
- import { getServerTrackingData, handleIdentify, setServerTrackingData } from './internal'
31
+ import { getServerTrackingData, handleIdentify, setServerTrackingData, withIdentity } from './internal'
31
32
  import type { LoggerFn } from './logger'
32
33
  import { buildCapturedMcpParameters } from './mcp-payloads'
33
34
  import { getLiteralValue, getObjectShape } from './mcp-sdk-compat'
34
- import { getSessionId, newSessionId } from './session'
35
+ import { getSessionId, getSessionInfo, newSessionId } from './session'
35
36
  import { encodeSessionId, readMcpSessionHeader, writeSessionIdToTransport } from './session-token'
36
37
  import { getReportMissingToolDescriptor, resolveMissingCapabilityToolName } from './tools'
37
38
  import { applyResolvedMetadata, isToolResultError } from './tracing-helpers'
@@ -118,7 +119,7 @@ export async function captureToolCall(params: TraceToolCallParams): Promise<unkn
118
119
  // Prepare the event in isolation: if identity/metadata/intent resolution
119
120
  // throws, we drop instrumentation for this call but still run the tool.
120
121
  const startTime = new Date()
121
- const event = await prepareToolCallEvent(
122
+ const preparedEvent = await prepareToolCallEvent(
122
123
  server,
123
124
  data,
124
125
  request,
@@ -129,23 +130,28 @@ export async function captureToolCall(params: TraceToolCallParams): Promise<unkn
129
130
  ownership,
130
131
  resolvedEventType
131
132
  )
132
- if (event && explicitContextIntent) {
133
- setExplicitContextIntent(event, explicitContextIntent)
133
+ if (preparedEvent && explicitContextIntent) {
134
+ setExplicitContextIntent(preparedEvent.event, explicitContextIntent)
134
135
  }
135
136
 
136
137
  let result: unknown
137
138
  try {
138
139
  result = await execute(downstreamRequest)
139
140
  } catch (error) {
140
- publishFailedToolEvent(server, event, error, startTime, conversation, data.logger)
141
+ publishFailedToolEvent(server, preparedEvent, error, startTime, conversation, data.logger)
141
142
  throw error
142
143
  }
143
144
 
144
- const finalResult = applyConversationPromptBack(event, result, conversation)
145
- publishSuccessfulToolEvent(server, event, finalResult, startTime, data.logger, takeCapturedError)
145
+ const finalResult = applyConversationPromptBack(preparedEvent?.event ?? null, result, conversation)
146
+ publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError)
146
147
  return finalResult
147
148
  }
148
149
 
150
+ interface PreparedToolEvent {
151
+ event: McpEvent
152
+ requestAttribution: SessionInfo
153
+ }
154
+
149
155
  function getActiveAnalyticsParameterOwnership(
150
156
  data: MCPAnalyticsData,
151
157
  toolName: string | undefined,
@@ -188,10 +194,14 @@ async function prepareToolCallEvent(
188
194
  conversation: ConversationIdResolution,
189
195
  ownership: AnalyticsParameterOwnership,
190
196
  eventType: MCPAnalyticsEventType
191
- ): Promise<McpEvent | null> {
197
+ ): Promise<PreparedToolEvent | null> {
192
198
  try {
193
199
  const sessionId = getSessionId(server, extra)
194
- await handleIdentify(server, data, sessionId, request, extra)
200
+ // Snapshot token/client/protocol metadata synchronously, before identify or
201
+ // metadata callbacks can yield and let another request replace shared state.
202
+ const sessionInfo = getSessionInfo(server, data, sessionId)
203
+ const identity = await handleIdentify(server, data, sessionId, request, sessionInfo, extra)
204
+ const requestAttribution = withIdentity(sessionInfo, identity)
195
205
 
196
206
  const toolName = request.params?.name
197
207
  const event: McpEvent = {
@@ -211,7 +221,7 @@ async function prepareToolCallEvent(
211
221
 
212
222
  await applyResolvedMetadata(event, data, request, extra)
213
223
  setEventIntent(event, await resolveToolCallIntent(data, request, ownership.context, extra))
214
- return event
224
+ return { event, requestAttribution }
215
225
  } catch (error) {
216
226
  data.logger(
217
227
  `Warning: PostHog MCP analytics instrumentation failed for tool ${request.params?.name}, the tool will still run - ${error}`
@@ -244,15 +254,16 @@ function applyConversationPromptBack(
244
254
 
245
255
  function publishSuccessfulToolEvent(
246
256
  server: MCPServerLike,
247
- event: McpEvent | null,
257
+ preparedEvent: PreparedToolEvent | null,
248
258
  result: unknown,
249
259
  startTime: Date,
250
260
  logger: LoggerFn,
251
261
  takeCapturedError?: () => unknown
252
262
  ): void {
253
- if (!event) {
263
+ if (!preparedEvent) {
254
264
  return
255
265
  }
266
+ const { event, requestAttribution } = preparedEvent
256
267
  try {
257
268
  if (isToolResultError(result)) {
258
269
  event.isError = true
@@ -263,7 +274,7 @@ function publishSuccessfulToolEvent(
263
274
  }
264
275
  event.response = result
265
276
  event.duration = Date.now() - startTime.getTime()
266
- captureEvent(server, event, logger)
277
+ captureEvent(server, event, logger, requestAttribution)
267
278
  } catch (error) {
268
279
  logger(`Warning: PostHog MCP analytics failed to publish tool event - ${error}`)
269
280
  }
@@ -271,15 +282,16 @@ function publishSuccessfulToolEvent(
271
282
 
272
283
  function publishFailedToolEvent(
273
284
  server: MCPServerLike,
274
- event: McpEvent | null,
285
+ preparedEvent: PreparedToolEvent | null,
275
286
  error: unknown,
276
287
  startTime: Date,
277
288
  conversation: ConversationIdResolution,
278
289
  logger: LoggerFn
279
290
  ): void {
280
- if (!event) {
291
+ if (!preparedEvent) {
281
292
  return
282
293
  }
294
+ const { event, requestAttribution } = preparedEvent
283
295
  try {
284
296
  if (conversation.minted) {
285
297
  event.conversationId = undefined
@@ -287,7 +299,7 @@ function publishFailedToolEvent(
287
299
  event.isError = true
288
300
  event.error = captureException(error)
289
301
  event.duration = Date.now() - startTime.getTime()
290
- captureEvent(server, event, logger)
302
+ captureEvent(server, event, logger, requestAttribution)
291
303
  } catch (publishError) {
292
304
  logger(`Warning: PostHog MCP analytics failed to publish failed tool event - ${publishError}`)
293
305
  }
@@ -407,8 +419,12 @@ export async function handleListToolsRequest(
407
419
  ): Promise<{ tools: ListToolsResult['tools'] }> {
408
420
  const data = getServerTrackingData(server)
409
421
  const startTime = new Date()
422
+ const sessionId = getSessionId(server, extra)
423
+ // Snapshot before metadata resolution or the list handler can yield to a
424
+ // concurrent request using the same instrumented server.
425
+ const requestAttribution = getSessionInfo(server, data, sessionId)
410
426
  const event: McpEvent = {
411
- sessionId: getSessionId(server, extra),
427
+ sessionId,
412
428
  parameters: buildCapturedMcpParameters(request),
413
429
  eventType: MCPAnalyticsEventType.mcpToolsList,
414
430
  timestamp: startTime,
@@ -419,7 +435,15 @@ export async function handleListToolsRequest(
419
435
  await applyResolvedMetadata(event, data, request, extra)
420
436
  }
421
437
 
422
- const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger)
438
+ const tools = await getTracedToolsList(
439
+ server,
440
+ originalListToolsHandler,
441
+ request,
442
+ extra,
443
+ event,
444
+ logger,
445
+ requestAttribution
446
+ )
423
447
 
424
448
  if (!data) {
425
449
  logger(
@@ -435,7 +459,7 @@ export async function handleListToolsRequest(
435
459
  event.error = captureException('No tools were sent to MCP client.')
436
460
  event.isError = true
437
461
  event.duration = Date.now() - startTime.getTime()
438
- captureEvent(server, event, data.logger)
462
+ captureEvent(server, event, data.logger, requestAttribution)
439
463
  return { tools }
440
464
  }
441
465
 
@@ -443,7 +467,7 @@ export async function handleListToolsRequest(
443
467
  event.listedToolNames = collectListedToolNames(tools)
444
468
  event.isError = false
445
469
  event.duration = Date.now() - startTime.getTime()
446
- captureEvent(server, event, data.logger)
470
+ captureEvent(server, event, data.logger, requestAttribution)
447
471
  return { tools }
448
472
  }
449
473
 
@@ -473,7 +497,8 @@ async function getTracedToolsList(
473
497
  request: MCPRequestLike,
474
498
  extra: CompatibleRequestHandlerExtra | undefined,
475
499
  event: McpEvent,
476
- logger: LoggerFn
500
+ logger: LoggerFn,
501
+ requestAttribution: SessionInfo
477
502
  ): Promise<ListToolsResult['tools']> {
478
503
  try {
479
504
  const data = getServerTrackingData(server)
@@ -521,7 +546,7 @@ async function getTracedToolsList(
521
546
  event.error = captureException(error)
522
547
  event.isError = true
523
548
  event.duration = event.timestamp ? Date.now() - event.timestamp.getTime() : 0
524
- captureEvent(server, event, logger)
549
+ captureEvent(server, event, logger, requestAttribution)
525
550
  throw error
526
551
  }
527
552
  }
@@ -630,9 +655,10 @@ function mintStatelessSessionOnInitialize(
630
655
  */
631
656
  function upgradeMintedTokenToNegotiated(
632
657
  server: MCPServerLike,
633
- data: MCPAnalyticsData,
634
658
  mintedSessionId: string,
635
- negotiatedProtocolVersion: string | undefined
659
+ sessionInfo: SessionInfo,
660
+ negotiatedProtocolVersion: string | undefined,
661
+ logger: LoggerFn
636
662
  ): void {
637
663
  try {
638
664
  const transport = server.transport
@@ -641,13 +667,13 @@ function upgradeMintedTokenToNegotiated(
641
667
  }
642
668
  const token = encodeSessionId({
643
669
  sessionId: mintedSessionId,
644
- clientName: data.sessionInfo.clientName,
645
- clientVersion: data.sessionInfo.clientVersion,
670
+ clientName: sessionInfo.clientName,
671
+ clientVersion: sessionInfo.clientVersion,
646
672
  protocolVersion: negotiatedProtocolVersion,
647
673
  })
648
674
  writeSessionIdToTransport(transport, token)
649
675
  } catch (error) {
650
- data.logger(`Warning: PostHog MCP analytics failed to upgrade the stateless session token - ${error}`)
676
+ logger(`Warning: PostHog MCP analytics failed to upgrade the stateless session token - ${error}`)
651
677
  }
652
678
  }
653
679
 
@@ -689,7 +715,17 @@ export async function handleInitializeRequest(
689
715
  // Mint first so the `$mcp_initialize` event below already carries the minted id.
690
716
  const mintedSessionId = mintStatelessSessionOnInitialize(server, data, request, extra)
691
717
  const sessionId = getSessionId(server, extra)
692
- await handleIdentify(server, data, sessionId, request, extra)
718
+ // Snapshot before identify, metadata, or the initialize handler can yield to
719
+ // another request using the same instrumented server.
720
+ const sessionInfo = getSessionInfo(server, data, sessionId)
721
+ const initializeClientInfo = readInitializeClientInfo(request)
722
+ const requestSessionInfo: SessionInfo = {
723
+ ...sessionInfo,
724
+ clientName: initializeClientInfo?.name ?? sessionInfo.clientName,
725
+ clientVersion: initializeClientInfo?.version ?? sessionInfo.clientVersion,
726
+ }
727
+ const identity = await handleIdentify(server, data, sessionId, request, requestSessionInfo, extra)
728
+ const requestAttribution = withIdentity(requestSessionInfo, identity)
693
729
 
694
730
  const event: McpEvent = {
695
731
  sessionId,
@@ -712,12 +748,19 @@ export async function handleInitializeRequest(
712
748
  // re-mint the token so pods replaying it report the negotiated version too.
713
749
  const negotiatedProtocolVersion = readProtocolVersion(result, request)
714
750
  event.protocolVersion = negotiatedProtocolVersion
715
- data.sessionInfo.protocolVersion = negotiatedProtocolVersion
716
- setServerTrackingData(server, data)
751
+ // Do not let a delayed initialize overwrite whichever session became current
752
+ // while its callbacks or the original handler were awaiting.
753
+ if (data.sessionId === sessionId) {
754
+ data.sessionInfo = { ...data.sessionInfo, protocolVersion: negotiatedProtocolVersion }
755
+ setServerTrackingData(server, data)
756
+ }
717
757
  if (mintedSessionId) {
718
- upgradeMintedTokenToNegotiated(server, data, mintedSessionId, negotiatedProtocolVersion)
758
+ upgradeMintedTokenToNegotiated(server, mintedSessionId, requestSessionInfo, negotiatedProtocolVersion, data.logger)
719
759
  }
720
- captureEvent(server, event, data.logger)
760
+ captureEvent(server, event, data.logger, {
761
+ ...requestAttribution,
762
+ protocolVersion: negotiatedProtocolVersion,
763
+ })
721
764
  return result
722
765
  }
723
766
 
@@ -9,6 +9,7 @@ import type {
9
9
  MCPRequestLike,
10
10
  MCPServerLike,
11
11
  McpEvent,
12
+ SessionInfo,
12
13
  UserIdentity,
13
14
  } from '../types'
14
15
  import { MCPAnalyticsEventType } from './event-types'
@@ -128,10 +129,13 @@ export async function handleIdentify(
128
129
  data: MCPAnalyticsData,
129
130
  sessionId: string,
130
131
  request: MCPRequestLike,
132
+ requestAttribution: SessionInfo,
131
133
  extra?: CompatibleRequestHandlerExtra
132
- ): Promise<void> {
134
+ ): Promise<UserIdentity | undefined> {
135
+ const identityBeforeRequest = data.identifiedSessions.get(sessionId)
136
+ const sessionSourceBeforeIdentify = data.sessionSource
133
137
  if (!data.options.identify) {
134
- return
138
+ return identityBeforeRequest
135
139
  }
136
140
 
137
141
  const identifyEvent: McpEvent = {
@@ -162,23 +166,34 @@ export async function handleIdentify(
162
166
  // announced); revisit with the stateless-by-default rework.
163
167
  const changed = previousIdentity !== undefined && !areIdentitiesEqual(previousIdentity, mergedIdentity)
164
168
  const firstSeen = previousIdentity === undefined
165
- const announcedAtInitialize = data.sessionSource === 'token' && request.method !== 'initialize'
169
+ const announcedAtInitialize = sessionSourceBeforeIdentify === 'token' && request.method !== 'initialize'
166
170
  const shouldPublish = changed || (firstSeen && !announcedAtInitialize)
167
171
 
168
172
  data.identifiedSessions.set(sessionId, mergedIdentity)
169
173
 
170
174
  if (shouldPublish) {
171
175
  data.logger(`Identified session ${sessionId}`)
172
- captureEvent(server, identifyEvent, data.logger)
176
+ captureEvent(server, identifyEvent, data.logger, withIdentity(requestAttribution, mergedIdentity))
173
177
  }
174
- } else {
175
- data.logger(`Warning: Supplied identify function returned null for session ${sessionId}`)
178
+ return mergedIdentity
176
179
  }
180
+
181
+ data.logger(`Warning: Supplied identify function returned null for session ${sessionId}`)
177
182
  } catch (error) {
178
183
  data.logger(
179
184
  `Error: User supplied identify function threw an error while identifying session ${sessionId} - ${error}`
180
185
  )
181
186
  }
187
+ return identityBeforeRequest
188
+ }
189
+
190
+ export function withIdentity(sessionInfo: SessionInfo, identity: UserIdentity | undefined): SessionInfo {
191
+ return {
192
+ ...sessionInfo,
193
+ identifyActorGivenId: identity?.distinctId,
194
+ identifyActorData: identity?.properties || {},
195
+ identifyActorGroups: identity?.groups,
196
+ }
182
197
  }
183
198
 
184
199
  /**
@@ -125,5 +125,5 @@ export function getSessionInfo(
125
125
 
126
126
  data.sessionInfo = sessionInfo
127
127
  setServerTrackingData(server, data)
128
- return data.sessionInfo
128
+ return { ...sessionInfo }
129
129
  }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '0.10.6'
1
+ export const version = '0.10.7'