@posthog/mcp 0.11.4 → 0.11.5

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,7 +95,7 @@ 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);
@@ -113,7 +115,7 @@ async function prepareToolCallEvent(server, data, request, downstreamRequest, ex
113
115
  (0, external_client_identity_js_namespaceObject.stampClientIdentity)(event, request, extra, server);
114
116
  (0, external_transport_identity_js_namespaceObject.stampTransportIdentity)(event, extra);
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);
@@ -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,7 +59,7 @@ 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);
@@ -77,7 +79,7 @@ async function prepareToolCallEvent(server, data, request, downstreamRequest, ex
77
79
  stampClientIdentity(event, request, extra, server);
78
80
  stampTransportIdentity(event, extra);
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);
@@ -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.5";
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.5';
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.5';
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.5",
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,7 +217,8 @@ 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)
@@ -237,7 +256,7 @@ async function prepareToolCallEvent(
237
256
  stampTransportIdentity(event, extra)
238
257
 
239
258
  await applyResolvedMetadata(event, data, request, extra)
240
- setEventIntent(event, await resolveToolCallIntent(data, request, ownership.context, extra))
259
+ setEventIntent(event, await resolveToolCallIntent(data, request, canCaptureContextIntent, extra))
241
260
  return { event, requestAttribution }
242
261
  } catch (error) {
243
262
  data.logger(
@@ -511,27 +530,39 @@ export async function handleListToolsRequest(
511
530
  request: MCPRequestLike,
512
531
  extra: CompatibleRequestHandlerExtra | undefined,
513
532
  logger: LoggerFn
514
- ): Promise<{ tools: CompatibleToolsListLike['tools'] }> {
533
+ ): Promise<CompatibleToolsListLike> {
515
534
  const data = getServerTrackingData(server)
516
535
  const startTime = new Date()
517
536
  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)
537
+ // Snapshot before identify, metadata resolution, or the list handler can yield
538
+ // to a concurrent request using the same instrumented server.
539
+ const sessionInfo = getSessionInfo(server, data, sessionId)
521
540
  const event: McpEvent = {
522
541
  sessionId,
523
542
  parameters: buildCapturedMcpParameters(request),
524
543
  eventType: MCPAnalyticsEventType.mcpToolsList,
525
544
  timestamp: startTime,
526
545
  }
546
+ // Stamp before the identify await below. The last link of the identity chain is
547
+ // the server's own `getClientVersion()`, which a concurrent `initialize` can
548
+ // replace while a slow identify callback is in flight — and `captureEvent`
549
+ // prefers a stamped value over the `sessionInfo` snapshot, so a late stamp
550
+ // would win with the wrong client's name.
527
551
  stampClientIdentity(event, request, extra, server)
528
552
  stampTransportIdentity(event, extra)
529
553
 
554
+ // `getSessionInfo` only surfaces an identity some earlier request already
555
+ // cached on this instance. On a per-request instance that cache is always
556
+ // empty, so without resolving `identify` here too, `tools/list` would be the
557
+ // one request path that never attributes to a person.
558
+ const identity = data ? await handleIdentify(server, data, sessionId, request, sessionInfo, extra) : undefined
559
+ const requestAttribution = withIdentity(sessionInfo, identity)
560
+
530
561
  if (data) {
531
562
  await applyResolvedMetadata(event, data, request, extra)
532
563
  }
533
564
 
534
- const tools = await getTracedToolsList(
565
+ const response = await getTracedToolsList(
535
566
  server,
536
567
  originalListToolsHandler,
537
568
  request,
@@ -540,12 +571,13 @@ export async function handleListToolsRequest(
540
571
  logger,
541
572
  requestAttribution
542
573
  )
574
+ const tools = response.tools
543
575
 
544
576
  if (!data) {
545
577
  logger(
546
578
  'Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.'
547
579
  )
548
- return { tools }
580
+ return response
549
581
  }
550
582
 
551
583
  if (tools.length === 0) {
@@ -556,15 +588,15 @@ export async function handleListToolsRequest(
556
588
  event.isError = true
557
589
  event.duration = Date.now() - startTime.getTime()
558
590
  captureEvent(server, event, data.logger, requestAttribution)
559
- return { tools }
591
+ return response
560
592
  }
561
593
 
562
- event.response = { tools }
594
+ event.response = response
563
595
  event.listedToolNames = collectListedToolNames(tools)
564
596
  event.isError = false
565
597
  event.duration = Date.now() - startTime.getTime()
566
598
  captureEvent(server, event, data.logger, requestAttribution)
567
- return { tools }
599
+ return response
568
600
  }
569
601
 
570
602
  function cacheToolAnalyticsParameterOwnership(
@@ -595,7 +627,7 @@ async function getTracedToolsList(
595
627
  event: McpEvent,
596
628
  logger: LoggerFn,
597
629
  requestAttribution: SessionInfo
598
- ): Promise<CompatibleToolsListLike['tools']> {
630
+ ): Promise<CompatibleToolsListLike> {
599
631
  try {
600
632
  const data = getServerTrackingData(server)
601
633
  const originalResponse = (await originalListToolsHandler(request, extra)) as CompatibleToolsListLike
@@ -637,7 +669,8 @@ async function getTracedToolsList(
637
669
  cacheToolCategories(data.toolCategories, tools)
638
670
  }
639
671
 
640
- return tools
672
+ // Spread, never enumerate — fields later revisions add survive by default.
673
+ return { ...originalResponse, tools }
641
674
  } catch (error) {
642
675
  logger(
643
676
  `Warning: Original list tools handler failed, this suggests an error PostHog MCP analytics did not cause - ${error}`
@@ -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.5'