@posthog/mcp 0.11.4 → 0.11.6

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.
package/README.md CHANGED
@@ -116,6 +116,39 @@ identify: async (request, extra) => {
116
116
  }
117
117
  ```
118
118
 
119
+ ### What `$mcp_intent` records, and how to turn it off
120
+
121
+ `context` defaults to **on**: the SDK adds a `context` parameter to every tool it advertises, asks
122
+ the agent to say why it is calling, and records the answer as `$mcp_intent`. It is stripped before
123
+ your tool runs wherever the SDK can confirm the parameter is its own.
124
+
125
+ On a server that builds a fresh instance per HTTP request — `createMcpHandler`, or `@rekog/mcp-nest`
126
+ in its stateless mode — that confirmation is not available: ownership is learned while serving
127
+ `tools/list`, and the instance handling a `tools/call` never served one. There the SDK records the
128
+ argument but does **not** strip it, because deleting an argument your tool declared would cost you
129
+ the call, while an extra key usually costs nothing.
130
+
131
+ What matters is instance lifetime, not statelessness. A server that is stateless at the transport
132
+ (`sessionIdGenerator: undefined`) but keeps one long-lived server object learns ownership from the
133
+ first `tools/list` and keeps it, so none of the above applies to it.
134
+
135
+ The consequence worth knowing: if **your own** tool declares a parameter named `context` and the SDK
136
+ cannot tell that it is yours, its value is recorded as `$mcp_intent`. It never leaves your project,
137
+ and it is capped at 2048 characters. Two ways out, both one line:
138
+
139
+ ```ts
140
+ instrument(server, posthog, { context: false }) // no injection, no capture
141
+
142
+ instrument(server, posthog, { // keep it, drop the property
143
+ beforeSend: (event) => {
144
+ delete event.properties.$mcp_intent
145
+ return event
146
+ },
147
+ })
148
+ ```
149
+
150
+ `intentFallback` is the third option: supply the intent yourself when the agent did not send one.
151
+
119
152
  ### If you switched to `instrument(server.server)`
120
153
 
121
154
  Before v2 support landed, the compatibility gate rejected high-level v2 servers, and the usual
@@ -95,9 +95,7 @@ export declare function isToolAdvertised(server: MCPServerLike, toolName: string
95
95
  * Captures each `tools/list` and injects the SDK-managed tools (context
96
96
  * parameter, conversation id, `get_more_tools`) into the returned list.
97
97
  */
98
- export declare function handleListToolsRequest(server: MCPServerLike, originalListToolsHandler: MCPRequestHandler, request: MCPRequestLike, extra: CompatibleRequestHandlerExtra | undefined, logger: LoggerFn): Promise<{
99
- tools: CompatibleToolsListLike['tools'];
100
- }>;
98
+ export declare function handleListToolsRequest(server: MCPServerLike, originalListToolsHandler: MCPRequestHandler, request: MCPRequestLike, extra: CompatibleRequestHandlerExtra | undefined, logger: LoggerFn): Promise<CompatibleToolsListLike>;
101
99
  export declare function cacheToolDescriptions(cache: Map<string, string>, tools: CompatibleToolsListLike['tools'] | undefined): void;
102
100
  /**
103
101
  * Category declared on a tool's `_meta` block (the MCP spec allows arbitrary
@@ -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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"}
@@ -56,10 +56,11 @@ async function captureToolCall(params) {
56
56
  const { server, data, request, extra, execute, parameterOwnership, eventType, explicitContextIntent, takeCapturedError } = params;
57
57
  const resolvedEventType = eventType ?? external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpToolsCall;
58
58
  const ownership = getActiveAnalyticsParameterOwnership(data, request.params?.name, parameterOwnership, resolvedEventType === external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpMissingCapability);
59
+ const canCaptureContextIntent = resolvedEventType !== external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpMissingCapability && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context) && (ownership.context || !ownership.contextOwnershipKnown);
59
60
  const conversation = (0, external_conversation_id_js_namespaceObject.resolveConversationId)(ownership.conversationId, request.params?.arguments);
60
61
  const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership);
61
62
  const startTime = new Date();
62
- const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType);
63
+ const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType, canCaptureContextIntent);
63
64
  if (preparedEvent && explicitContextIntent) (0, external_intent_js_namespaceObject.setExplicitContextIntent)(preparedEvent.event, explicitContextIntent);
64
65
  let result;
65
66
  try {
@@ -76,6 +77,7 @@ function getActiveAnalyticsParameterOwnership(data, toolName, override, isMissin
76
77
  const listed = toolName ? data.toolAnalyticsParameterOwnership.get(toolName) : void 0;
77
78
  const ownership = override ?? listed;
78
79
  return {
80
+ contextOwnershipKnown: void 0 !== ownership,
79
81
  context: !isMissingCapabilityTool && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context) && ownership?.context === true,
80
82
  conversationId: true === data.options.enableConversationId && ownership?.conversationId === true,
81
83
  outputInstructions: true === data.options.enableConversationId && listed?.outputInstructions === true
@@ -93,12 +95,10 @@ function cloneRequestWithoutOwnedAnalyticsArguments(request, ownership) {
93
95
  }
94
96
  };
95
97
  }
96
- async function prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, eventType) {
98
+ async function prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, eventType, canCaptureContextIntent) {
97
99
  try {
98
100
  const sessionId = (0, external_session_js_namespaceObject.getSessionId)(server, extra, conversation.conversationId);
99
101
  const sessionInfo = (0, external_session_js_namespaceObject.getSessionInfo)(server, data, sessionId);
100
- const identity = await (0, external_internal_js_namespaceObject.handleIdentify)(server, data, sessionId, request, sessionInfo, extra, !!conversation.conversationId);
101
- const requestAttribution = (0, external_internal_js_namespaceObject.withIdentity)(sessionInfo, identity);
102
102
  const toolName = request.params?.name;
103
103
  const event = {
104
104
  sessionId,
@@ -112,8 +112,10 @@ async function prepareToolCallEvent(server, data, request, downstreamRequest, ex
112
112
  };
113
113
  (0, external_client_identity_js_namespaceObject.stampClientIdentity)(event, request, extra, server);
114
114
  (0, external_transport_identity_js_namespaceObject.stampTransportIdentity)(event, extra);
115
+ const identity = await (0, external_internal_js_namespaceObject.handleIdentify)(server, data, sessionId, request, sessionInfo, extra, !!conversation.conversationId);
116
+ const requestAttribution = (0, external_internal_js_namespaceObject.withIdentity)(sessionInfo, identity);
115
117
  await (0, external_tracing_helpers_js_namespaceObject.applyResolvedMetadata)(event, data, request, extra);
116
- (0, external_intent_js_namespaceObject.setEventIntent)(event, await (0, external_intent_js_namespaceObject.resolveToolCallIntent)(data, request, ownership.context, extra));
118
+ (0, external_intent_js_namespaceObject.setEventIntent)(event, await (0, external_intent_js_namespaceObject.resolveToolCallIntent)(data, request, canCaptureContextIntent, extra));
117
119
  return {
118
120
  event,
119
121
  requestAttribution
@@ -223,7 +225,7 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
223
225
  const data = (0, external_internal_js_namespaceObject.getServerTrackingData)(server);
224
226
  const startTime = new Date();
225
227
  const sessionId = (0, external_session_js_namespaceObject.getSessionId)(server, extra);
226
- const requestAttribution = (0, external_session_js_namespaceObject.getSessionInfo)(server, data, sessionId);
228
+ const sessionInfo = (0, external_session_js_namespaceObject.getSessionInfo)(server, data, sessionId);
227
229
  const event = {
228
230
  sessionId,
229
231
  parameters: (0, external_mcp_payloads_js_namespaceObject.buildCapturedMcpParameters)(request),
@@ -232,13 +234,14 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
232
234
  };
233
235
  (0, external_client_identity_js_namespaceObject.stampClientIdentity)(event, request, extra, server);
234
236
  (0, external_transport_identity_js_namespaceObject.stampTransportIdentity)(event, extra);
237
+ const identity = data ? await (0, external_internal_js_namespaceObject.handleIdentify)(server, data, sessionId, request, sessionInfo, extra) : void 0;
238
+ const requestAttribution = (0, external_internal_js_namespaceObject.withIdentity)(sessionInfo, identity);
235
239
  if (data) await (0, external_tracing_helpers_js_namespaceObject.applyResolvedMetadata)(event, data, request, extra);
236
- const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger, requestAttribution);
240
+ const response = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger, requestAttribution);
241
+ const tools = response.tools;
237
242
  if (!data) {
238
243
  logger('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
239
- return {
240
- tools
241
- };
244
+ return response;
242
245
  }
243
246
  if (0 === tools.length) {
244
247
  data.logger('Warning: No tools found in the original list. This is likely due to the tools not being registered before PostHog MCP analytics.instrument().');
@@ -246,20 +249,14 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
246
249
  event.isError = true;
247
250
  event.duration = Date.now() - startTime.getTime();
248
251
  (0, external_capture_js_namespaceObject.captureEvent)(server, event, data.logger, requestAttribution);
249
- return {
250
- tools
251
- };
252
+ return response;
252
253
  }
253
- event.response = {
254
- tools
255
- };
254
+ event.response = response;
256
255
  event.listedToolNames = collectListedToolNames(tools);
257
256
  event.isError = false;
258
257
  event.duration = Date.now() - startTime.getTime();
259
258
  (0, external_capture_js_namespaceObject.captureEvent)(server, event, data.logger, requestAttribution);
260
- return {
261
- tools
262
- };
259
+ return response;
263
260
  }
264
261
  function cacheToolAnalyticsParameterOwnership(cache, tools) {
265
262
  for (const tool of tools)if (tool?.name) cache.set(tool.name, (0, external_analytics_parameters_js_namespaceObject.getAnalyticsParameterOwnership)(tool.inputSchema, tool.outputSchema));
@@ -300,7 +297,10 @@ async function getTracedToolsList(server, originalListToolsHandler, request, ext
300
297
  cacheToolDescriptions(data.toolDescriptions, tools);
301
298
  cacheToolCategories(data.toolCategories, tools);
302
299
  }
303
- return tools;
300
+ return {
301
+ ...originalResponse,
302
+ tools
303
+ };
304
304
  } catch (error) {
305
305
  logger(`Warning: Original list tools handler failed, this suggests an error PostHog MCP analytics did not cause - ${error}`);
306
306
  event.error = (0, external_exceptions_js_namespaceObject.captureException)(error);
@@ -395,8 +395,6 @@ async function handleInitializeRequest(server, originalInitializeHandler, reques
395
395
  clientName: initializeClientInfo?.name ?? sessionInfo.clientName,
396
396
  clientVersion: initializeClientInfo?.version ?? sessionInfo.clientVersion
397
397
  };
398
- const identity = await (0, external_internal_js_namespaceObject.handleIdentify)(server, data, sessionId, request, requestSessionInfo, extra);
399
- const requestAttribution = (0, external_internal_js_namespaceObject.withIdentity)(requestSessionInfo, identity);
400
398
  const event = {
401
399
  sessionId,
402
400
  resourceName: request.params?.name || 'Unknown Tool Name',
@@ -405,7 +403,11 @@ async function handleInitializeRequest(server, originalInitializeHandler, reques
405
403
  timestamp: new Date()
406
404
  };
407
405
  (0, external_client_identity_js_namespaceObject.stampClientIdentity)(event, request, extra, server);
406
+ if (initializeClientInfo?.name) event.clientName = initializeClientInfo.name;
407
+ if (initializeClientInfo?.version) event.clientVersion = initializeClientInfo.version;
408
408
  (0, external_transport_identity_js_namespaceObject.stampTransportIdentity)(event, extra);
409
+ const identity = await (0, external_internal_js_namespaceObject.handleIdentify)(server, data, sessionId, request, requestSessionInfo, extra);
410
+ const requestAttribution = (0, external_internal_js_namespaceObject.withIdentity)(requestSessionInfo, identity);
409
411
  await (0, external_tracing_helpers_js_namespaceObject.applyResolvedMetadata)(event, data, request, extra);
410
412
  const result = await originalInitializeHandler(request, extra);
411
413
  event.response = result;
@@ -20,10 +20,11 @@ async function captureToolCall(params) {
20
20
  const { server, data, request, extra, execute, parameterOwnership, eventType, explicitContextIntent, takeCapturedError } = params;
21
21
  const resolvedEventType = eventType ?? MCPAnalyticsEventType.mcpToolsCall;
22
22
  const ownership = getActiveAnalyticsParameterOwnership(data, request.params?.name, parameterOwnership, resolvedEventType === MCPAnalyticsEventType.mcpMissingCapability);
23
+ const canCaptureContextIntent = resolvedEventType !== MCPAnalyticsEventType.mcpMissingCapability && isContextEnabled(data.options.context) && (ownership.context || !ownership.contextOwnershipKnown);
23
24
  const conversation = resolveConversationId(ownership.conversationId, request.params?.arguments);
24
25
  const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership);
25
26
  const startTime = new Date();
26
- const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType);
27
+ const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType, canCaptureContextIntent);
27
28
  if (preparedEvent && explicitContextIntent) setExplicitContextIntent(preparedEvent.event, explicitContextIntent);
28
29
  let result;
29
30
  try {
@@ -40,6 +41,7 @@ function getActiveAnalyticsParameterOwnership(data, toolName, override, isMissin
40
41
  const listed = toolName ? data.toolAnalyticsParameterOwnership.get(toolName) : void 0;
41
42
  const ownership = override ?? listed;
42
43
  return {
44
+ contextOwnershipKnown: void 0 !== ownership,
43
45
  context: !isMissingCapabilityTool && isContextEnabled(data.options.context) && ownership?.context === true,
44
46
  conversationId: true === data.options.enableConversationId && ownership?.conversationId === true,
45
47
  outputInstructions: true === data.options.enableConversationId && listed?.outputInstructions === true
@@ -57,12 +59,10 @@ function cloneRequestWithoutOwnedAnalyticsArguments(request, ownership) {
57
59
  }
58
60
  };
59
61
  }
60
- async function prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, eventType) {
62
+ async function prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, eventType, canCaptureContextIntent) {
61
63
  try {
62
64
  const sessionId = getSessionId(server, extra, conversation.conversationId);
63
65
  const sessionInfo = getSessionInfo(server, data, sessionId);
64
- const identity = await handleIdentify(server, data, sessionId, request, sessionInfo, extra, !!conversation.conversationId);
65
- const requestAttribution = withIdentity(sessionInfo, identity);
66
66
  const toolName = request.params?.name;
67
67
  const event = {
68
68
  sessionId,
@@ -76,8 +76,10 @@ async function prepareToolCallEvent(server, data, request, downstreamRequest, ex
76
76
  };
77
77
  stampClientIdentity(event, request, extra, server);
78
78
  stampTransportIdentity(event, extra);
79
+ const identity = await handleIdentify(server, data, sessionId, request, sessionInfo, extra, !!conversation.conversationId);
80
+ const requestAttribution = withIdentity(sessionInfo, identity);
79
81
  await applyResolvedMetadata(event, data, request, extra);
80
- setEventIntent(event, await resolveToolCallIntent(data, request, ownership.context, extra));
82
+ setEventIntent(event, await resolveToolCallIntent(data, request, canCaptureContextIntent, extra));
81
83
  return {
82
84
  event,
83
85
  requestAttribution
@@ -187,7 +189,7 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
187
189
  const data = getServerTrackingData(server);
188
190
  const startTime = new Date();
189
191
  const sessionId = getSessionId(server, extra);
190
- const requestAttribution = getSessionInfo(server, data, sessionId);
192
+ const sessionInfo = getSessionInfo(server, data, sessionId);
191
193
  const event = {
192
194
  sessionId,
193
195
  parameters: buildCapturedMcpParameters(request),
@@ -196,13 +198,14 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
196
198
  };
197
199
  stampClientIdentity(event, request, extra, server);
198
200
  stampTransportIdentity(event, extra);
201
+ const identity = data ? await handleIdentify(server, data, sessionId, request, sessionInfo, extra) : void 0;
202
+ const requestAttribution = withIdentity(sessionInfo, identity);
199
203
  if (data) await applyResolvedMetadata(event, data, request, extra);
200
- const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger, requestAttribution);
204
+ const response = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger, requestAttribution);
205
+ const tools = response.tools;
201
206
  if (!data) {
202
207
  logger('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
203
- return {
204
- tools
205
- };
208
+ return response;
206
209
  }
207
210
  if (0 === tools.length) {
208
211
  data.logger('Warning: No tools found in the original list. This is likely due to the tools not being registered before PostHog MCP analytics.instrument().');
@@ -210,20 +213,14 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
210
213
  event.isError = true;
211
214
  event.duration = Date.now() - startTime.getTime();
212
215
  captureEvent(server, event, data.logger, requestAttribution);
213
- return {
214
- tools
215
- };
216
+ return response;
216
217
  }
217
- event.response = {
218
- tools
219
- };
218
+ event.response = response;
220
219
  event.listedToolNames = collectListedToolNames(tools);
221
220
  event.isError = false;
222
221
  event.duration = Date.now() - startTime.getTime();
223
222
  captureEvent(server, event, data.logger, requestAttribution);
224
- return {
225
- tools
226
- };
223
+ return response;
227
224
  }
228
225
  function cacheToolAnalyticsParameterOwnership(cache, tools) {
229
226
  for (const tool of tools)if (tool?.name) cache.set(tool.name, getAnalyticsParameterOwnership(tool.inputSchema, tool.outputSchema));
@@ -264,7 +261,10 @@ async function getTracedToolsList(server, originalListToolsHandler, request, ext
264
261
  cacheToolDescriptions(data.toolDescriptions, tools);
265
262
  cacheToolCategories(data.toolCategories, tools);
266
263
  }
267
- return tools;
264
+ return {
265
+ ...originalResponse,
266
+ tools
267
+ };
268
268
  } catch (error) {
269
269
  logger(`Warning: Original list tools handler failed, this suggests an error PostHog MCP analytics did not cause - ${error}`);
270
270
  event.error = captureException(error);
@@ -359,8 +359,6 @@ async function handleInitializeRequest(server, originalInitializeHandler, reques
359
359
  clientName: initializeClientInfo?.name ?? sessionInfo.clientName,
360
360
  clientVersion: initializeClientInfo?.version ?? sessionInfo.clientVersion
361
361
  };
362
- const identity = await handleIdentify(server, data, sessionId, request, requestSessionInfo, extra);
363
- const requestAttribution = withIdentity(requestSessionInfo, identity);
364
362
  const event = {
365
363
  sessionId,
366
364
  resourceName: request.params?.name || 'Unknown Tool Name',
@@ -369,7 +367,11 @@ async function handleInitializeRequest(server, originalInitializeHandler, reques
369
367
  timestamp: new Date()
370
368
  };
371
369
  stampClientIdentity(event, request, extra, server);
370
+ if (initializeClientInfo?.name) event.clientName = initializeClientInfo.name;
371
+ if (initializeClientInfo?.version) event.clientVersion = initializeClientInfo.version;
372
372
  stampTransportIdentity(event, extra);
373
+ const identity = await handleIdentify(server, data, sessionId, request, requestSessionInfo, extra);
374
+ const requestAttribution = withIdentity(requestSessionInfo, identity);
373
375
  await applyResolvedMetadata(event, data, request, extra);
374
376
  const result = await originalInitializeHandler(request, extra);
375
377
  event.response = result;
@@ -3,7 +3,7 @@ interface ResolvedIntent {
3
3
  intent: string;
4
4
  source: MCPAnalyticsIntentSource;
5
5
  }
6
- export declare function resolveToolCallIntent(data: MCPAnalyticsData, request: MCPRequestLike, analyticsOwnsContext: boolean, extra?: CompatibleRequestHandlerExtra): Promise<ResolvedIntent | null>;
6
+ export declare function resolveToolCallIntent(data: MCPAnalyticsData, request: MCPRequestLike, canCaptureContext: boolean, extra?: CompatibleRequestHandlerExtra): Promise<ResolvedIntent | null>;
7
7
  export declare function setEventIntent(event: McpEvent, resolvedIntent: ResolvedIntent | null): void;
8
8
  export declare function setExplicitContextIntent(event: McpEvent, context: string): void;
9
9
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../../src/extensions/intent.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,wBAAwB,EACxB,cAAc,EACd,QAAQ,EACT,MAAM,UAAU,CAAA;AAGjB,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,wBAAwB,CAAA;CACjC;AAkCD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,cAAc,EACvB,oBAAoB,EAAE,OAAO,EAC7B,KAAK,CAAC,EAAE,6BAA6B,GACpC,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAOhC;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,cAAc,GAAG,IAAI,GAAG,IAAI,CAO3F;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAQ/E"}
1
+ {"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../../src/extensions/intent.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,wBAAwB,EACxB,cAAc,EACd,QAAQ,EACT,MAAM,UAAU,CAAA;AAGjB,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,wBAAwB,CAAA;CACjC;AAkCD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,cAAc,EACvB,iBAAiB,EAAE,OAAO,EAC1B,KAAK,CAAC,EAAE,6BAA6B,GACpC,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAOhC;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,cAAc,GAAG,IAAI,GAAG,IAAI,CAO3F;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAQ/E"}
@@ -51,9 +51,9 @@ async function runIntentFallback(data, request, extra) {
51
51
  return null;
52
52
  }
53
53
  }
54
- async function resolveToolCallIntent(data, request, analyticsOwnsContext, extra) {
54
+ async function resolveToolCallIntent(data, request, canCaptureContext, extra) {
55
55
  const contextArgument = getContextArgument(request);
56
- if (analyticsOwnsContext && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context) && contextArgument) return {
56
+ if (canCaptureContext && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context) && contextArgument) return {
57
57
  intent: contextArgument,
58
58
  source: 'context_parameter'
59
59
  };
@@ -21,9 +21,9 @@ async function runIntentFallback(data, request, extra) {
21
21
  return null;
22
22
  }
23
23
  }
24
- async function resolveToolCallIntent(data, request, analyticsOwnsContext, extra) {
24
+ async function resolveToolCallIntent(data, request, canCaptureContext, extra) {
25
25
  const contextArgument = getContextArgument(request);
26
- if (analyticsOwnsContext && isContextEnabled(data.options.context) && contextArgument) return {
26
+ if (canCaptureContext && isContextEnabled(data.options.context) && contextArgument) return {
27
27
  intent: contextArgument,
28
28
  source: 'context_parameter'
29
29
  };
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const version = "0.11.4";
1
+ export declare const version = "0.11.6";
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.11.4';
29
+ const version = '0.11.6';
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.11.4';
1
+ const version = '0.11.6';
2
2
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/mcp",
3
- "version": "0.11.4",
3
+ "version": "0.11.6",
4
4
  "bugs": {
5
5
  "url": "https://github.com/PostHog/posthog-js/issues"
6
6
  },
@@ -28,7 +28,7 @@
28
28
  "module": "dist/index.mjs",
29
29
  "types": "dist/index.d.ts",
30
30
  "dependencies": {
31
- "@posthog/core": "^1.47.0"
31
+ "@posthog/core": "^1.48.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@babel/preset-env": "^7.27.1",
@@ -40,8 +40,8 @@
40
40
  "@types/node": "^20.0.0",
41
41
  "jest": "29.7.0",
42
42
  "zod": "^3.25.0",
43
- "posthog-node": "^5.48.1",
44
- "@posthog-tooling/tsconfig-base": "1.1.1"
43
+ "@posthog-tooling/tsconfig-base": "1.1.1",
44
+ "posthog-node": "^5.49.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@modelcontextprotocol/sdk": ">=1.26.0",
@@ -109,6 +109,14 @@ export async function captureToolCall(params: TraceToolCallParams): Promise<unkn
109
109
  parameterOwnership,
110
110
  resolvedEventType === MCPAnalyticsEventType.mcpMissingCapability
111
111
  )
112
+ // Reading the argument and removing it are separate questions: deleting one the
113
+ // application declared costs the customer their call, so the strip below still
114
+ // requires positive ownership, while reading it when ownership is unresolved
115
+ // costs at worst a mislabelled property. ADR-0011.
116
+ const canCaptureContextIntent =
117
+ resolvedEventType !== MCPAnalyticsEventType.mcpMissingCapability &&
118
+ isContextEnabled(data.options.context) &&
119
+ (ownership.context || !ownership.contextOwnershipKnown)
112
120
  const conversation = resolveConversationId(ownership.conversationId, request.params?.arguments)
113
121
  const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership)
114
122
 
@@ -124,7 +132,8 @@ export async function captureToolCall(params: TraceToolCallParams): Promise<unkn
124
132
  startTime,
125
133
  conversation,
126
134
  ownership,
127
- resolvedEventType
135
+ resolvedEventType,
136
+ canCaptureContextIntent
128
137
  )
129
138
  if (preparedEvent && explicitContextIntent) {
130
139
  setExplicitContextIntent(preparedEvent.event, explicitContextIntent)
@@ -150,15 +159,24 @@ interface PreparedToolEvent {
150
159
  requestAttribution: SessionInfo
151
160
  }
152
161
 
162
+ /**
163
+ * Ownership for one request, plus whether it could be resolved at all — "no
164
+ * answer" must not read the same as "the application owns it".
165
+ */
166
+ interface ActiveAnalyticsParameterOwnership extends AnalyticsParameterOwnership {
167
+ contextOwnershipKnown: boolean
168
+ }
169
+
153
170
  function getActiveAnalyticsParameterOwnership(
154
171
  data: MCPAnalyticsData,
155
172
  toolName: string | undefined,
156
173
  override: AnalyticsParameterOwnership | undefined,
157
174
  isMissingCapabilityTool: boolean
158
- ): AnalyticsParameterOwnership {
175
+ ): ActiveAnalyticsParameterOwnership {
159
176
  const listed = toolName ? data.toolAnalyticsParameterOwnership.get(toolName) : undefined
160
177
  const ownership = override ?? listed
161
178
  return {
179
+ contextOwnershipKnown: ownership !== undefined,
162
180
  context: !isMissingCapabilityTool && isContextEnabled(data.options.context) && ownership?.context === true,
163
181
  conversationId: data.options.enableConversationId === true && ownership?.conversationId === true,
164
182
  // Deliberately read off `listed`, never the override: only the advertised
@@ -199,23 +217,14 @@ async function prepareToolCallEvent(
199
217
  startTime: Date,
200
218
  conversation: ConversationIdResolution,
201
219
  ownership: AnalyticsParameterOwnership,
202
- eventType: MCPAnalyticsEventType
220
+ eventType: MCPAnalyticsEventType,
221
+ canCaptureContextIntent: boolean
203
222
  ): Promise<PreparedToolEvent | null> {
204
223
  try {
205
224
  const sessionId = getSessionId(server, extra, conversation.conversationId)
206
225
  // Snapshot token/client/protocol metadata synchronously, before identify or
207
226
  // metadata callbacks can yield and let another request replace shared state.
208
227
  const sessionInfo = getSessionInfo(server, data, sessionId)
209
- const identity = await handleIdentify(
210
- server,
211
- data,
212
- sessionId,
213
- request,
214
- sessionInfo,
215
- extra,
216
- !!conversation.conversationId
217
- )
218
- const requestAttribution = withIdentity(sessionInfo, identity)
219
228
 
220
229
  const toolName = request.params?.name
221
230
  const event: McpEvent = {
@@ -230,14 +239,30 @@ async function prepareToolCallEvent(
230
239
  }
231
240
  // Modern (stateless) clients carry client name/version + protocol version in
232
241
  // `_meta` on every request rather than at `initialize`; stamp them onto this
233
- // event now so concurrent requests can't cross-attribute it.
242
+ // event now so concurrent requests can't cross-attribute it. Stamped before
243
+ // the identify await below for the same reason the snapshot is: the chain's
244
+ // last link is the server's own `getClientVersion()`, which a concurrent
245
+ // `initialize` can replace while an identify callback is in flight — and
246
+ // `captureEvent` prefers a stamped value over `sessionInfo`, so a late stamp
247
+ // would win with the wrong client's name.
234
248
  stampClientIdentity(event, request, extra, server)
235
249
  // Which *surface* of the client made this call lives only in the request
236
250
  // headers (HTTP transports); `clientInfo` can't tell a vendor's products apart.
237
251
  stampTransportIdentity(event, extra)
238
252
 
253
+ const identity = await handleIdentify(
254
+ server,
255
+ data,
256
+ sessionId,
257
+ request,
258
+ sessionInfo,
259
+ extra,
260
+ !!conversation.conversationId
261
+ )
262
+ const requestAttribution = withIdentity(sessionInfo, identity)
263
+
239
264
  await applyResolvedMetadata(event, data, request, extra)
240
- setEventIntent(event, await resolveToolCallIntent(data, request, ownership.context, extra))
265
+ setEventIntent(event, await resolveToolCallIntent(data, request, canCaptureContextIntent, extra))
241
266
  return { event, requestAttribution }
242
267
  } catch (error) {
243
268
  data.logger(
@@ -511,27 +536,39 @@ export async function handleListToolsRequest(
511
536
  request: MCPRequestLike,
512
537
  extra: CompatibleRequestHandlerExtra | undefined,
513
538
  logger: LoggerFn
514
- ): Promise<{ tools: CompatibleToolsListLike['tools'] }> {
539
+ ): Promise<CompatibleToolsListLike> {
515
540
  const data = getServerTrackingData(server)
516
541
  const startTime = new Date()
517
542
  const sessionId = getSessionId(server, extra)
518
- // Snapshot before metadata resolution or the list handler can yield to a
519
- // concurrent request using the same instrumented server.
520
- const requestAttribution = getSessionInfo(server, data, sessionId)
543
+ // Snapshot before identify, metadata resolution, or the list handler can yield
544
+ // to a concurrent request using the same instrumented server.
545
+ const sessionInfo = getSessionInfo(server, data, sessionId)
521
546
  const event: McpEvent = {
522
547
  sessionId,
523
548
  parameters: buildCapturedMcpParameters(request),
524
549
  eventType: MCPAnalyticsEventType.mcpToolsList,
525
550
  timestamp: startTime,
526
551
  }
552
+ // Stamp before the identify await below. The last link of the identity chain is
553
+ // the server's own `getClientVersion()`, which a concurrent `initialize` can
554
+ // replace while a slow identify callback is in flight — and `captureEvent`
555
+ // prefers a stamped value over the `sessionInfo` snapshot, so a late stamp
556
+ // would win with the wrong client's name.
527
557
  stampClientIdentity(event, request, extra, server)
528
558
  stampTransportIdentity(event, extra)
529
559
 
560
+ // `getSessionInfo` only surfaces an identity some earlier request already
561
+ // cached on this instance. On a per-request instance that cache is always
562
+ // empty, so without resolving `identify` here too, `tools/list` would be the
563
+ // one request path that never attributes to a person.
564
+ const identity = data ? await handleIdentify(server, data, sessionId, request, sessionInfo, extra) : undefined
565
+ const requestAttribution = withIdentity(sessionInfo, identity)
566
+
530
567
  if (data) {
531
568
  await applyResolvedMetadata(event, data, request, extra)
532
569
  }
533
570
 
534
- const tools = await getTracedToolsList(
571
+ const response = await getTracedToolsList(
535
572
  server,
536
573
  originalListToolsHandler,
537
574
  request,
@@ -540,12 +577,13 @@ export async function handleListToolsRequest(
540
577
  logger,
541
578
  requestAttribution
542
579
  )
580
+ const tools = response.tools
543
581
 
544
582
  if (!data) {
545
583
  logger(
546
584
  'Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.'
547
585
  )
548
- return { tools }
586
+ return response
549
587
  }
550
588
 
551
589
  if (tools.length === 0) {
@@ -556,15 +594,15 @@ export async function handleListToolsRequest(
556
594
  event.isError = true
557
595
  event.duration = Date.now() - startTime.getTime()
558
596
  captureEvent(server, event, data.logger, requestAttribution)
559
- return { tools }
597
+ return response
560
598
  }
561
599
 
562
- event.response = { tools }
600
+ event.response = response
563
601
  event.listedToolNames = collectListedToolNames(tools)
564
602
  event.isError = false
565
603
  event.duration = Date.now() - startTime.getTime()
566
604
  captureEvent(server, event, data.logger, requestAttribution)
567
- return { tools }
605
+ return response
568
606
  }
569
607
 
570
608
  function cacheToolAnalyticsParameterOwnership(
@@ -595,7 +633,7 @@ async function getTracedToolsList(
595
633
  event: McpEvent,
596
634
  logger: LoggerFn,
597
635
  requestAttribution: SessionInfo
598
- ): Promise<CompatibleToolsListLike['tools']> {
636
+ ): Promise<CompatibleToolsListLike> {
599
637
  try {
600
638
  const data = getServerTrackingData(server)
601
639
  const originalResponse = (await originalListToolsHandler(request, extra)) as CompatibleToolsListLike
@@ -637,7 +675,8 @@ async function getTracedToolsList(
637
675
  cacheToolCategories(data.toolCategories, tools)
638
676
  }
639
677
 
640
- return tools
678
+ // Spread, never enumerate — fields later revisions add survive by default.
679
+ return { ...originalResponse, tools }
641
680
  } catch (error) {
642
681
  logger(
643
682
  `Warning: Original list tools handler failed, this suggests an error PostHog MCP analytics did not cause - ${error}`
@@ -837,9 +876,6 @@ export async function handleInitializeRequest(
837
876
  clientName: initializeClientInfo?.name ?? sessionInfo.clientName,
838
877
  clientVersion: initializeClientInfo?.version ?? sessionInfo.clientVersion,
839
878
  }
840
- const identity = await handleIdentify(server, data, sessionId, request, requestSessionInfo, extra)
841
- const requestAttribution = withIdentity(requestSessionInfo, identity)
842
-
843
879
  const event: McpEvent = {
844
880
  sessionId,
845
881
  resourceName: request.params?.name || 'Unknown Tool Name',
@@ -847,12 +883,30 @@ export async function handleInitializeRequest(
847
883
  parameters: buildCapturedMcpParameters(request),
848
884
  timestamp: new Date(),
849
885
  }
850
- // Harmless for a legacy `initialize` (client info rides the body there, and the
851
- // negotiated protocol version below overrides any `_meta` one); picks up client
852
- // info if a client also sends it in `_meta`.
886
+ // Picks up client info if a client also sends it in `_meta` (the negotiated
887
+ // protocol version below overrides any `_meta` one). Stamped before the
888
+ // identify await for the same reason the snapshot is taken there: the chain's
889
+ // last link is the server's own `getClientVersion()`, which a concurrent
890
+ // `initialize` can replace while an identify callback is in flight.
853
891
  stampClientIdentity(event, request, extra, server)
892
+ // A legacy `initialize` carries its client in the request body, which is the
893
+ // one place the chain does not read. That body is this request's own answer,
894
+ // so it outranks the accessor — which on a server that already handshaked
895
+ // still names the *previous* client, because the SDK only records this one
896
+ // when its handler runs further below. Without this, the second and every
897
+ // later handshake on a shared instance reports the first client's name, since
898
+ // `captureEvent` prefers a stamped value over `requestSessionInfo`.
899
+ if (initializeClientInfo?.name) {
900
+ event.clientName = initializeClientInfo.name
901
+ }
902
+ if (initializeClientInfo?.version) {
903
+ event.clientVersion = initializeClientInfo.version
904
+ }
854
905
  stampTransportIdentity(event, extra)
855
906
 
907
+ const identity = await handleIdentify(server, data, sessionId, request, requestSessionInfo, extra)
908
+ const requestAttribution = withIdentity(requestSessionInfo, identity)
909
+
856
910
  await applyResolvedMetadata(event, data, request, extra)
857
911
 
858
912
  const result = await originalInitializeHandler(request, extra)
@@ -52,11 +52,11 @@ async function runIntentFallback(
52
52
  export async function resolveToolCallIntent(
53
53
  data: MCPAnalyticsData,
54
54
  request: MCPRequestLike,
55
- analyticsOwnsContext: boolean,
55
+ canCaptureContext: boolean,
56
56
  extra?: CompatibleRequestHandlerExtra
57
57
  ): Promise<ResolvedIntent | null> {
58
58
  const contextArgument = getContextArgument(request)
59
- if (analyticsOwnsContext && isContextEnabled(data.options.context) && contextArgument) {
59
+ if (canCaptureContext && isContextEnabled(data.options.context) && contextArgument) {
60
60
  return { intent: contextArgument, source: 'context_parameter' }
61
61
  }
62
62
 
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '0.11.4'
1
+ export const version = '0.11.6'