@posthog/mcp 0.10.5 → 0.10.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.
Files changed (31) hide show
  1. package/dist/extensions/context-parameters.d.ts.map +1 -1
  2. package/dist/extensions/context-parameters.js +1 -4
  3. package/dist/extensions/context-parameters.mjs +1 -4
  4. package/dist/extensions/conversation-id.d.ts +1 -1
  5. package/dist/extensions/conversation-id.d.ts.map +1 -1
  6. package/dist/extensions/conversation-id.js +2 -2
  7. package/dist/extensions/conversation-id.mjs +2 -2
  8. package/dist/extensions/instrument-highlevel.d.ts.map +1 -1
  9. package/dist/extensions/instrument-highlevel.js +5 -5
  10. package/dist/extensions/instrument-highlevel.mjs +6 -6
  11. package/dist/extensions/instrument-lowlevel.d.ts.map +1 -1
  12. package/dist/extensions/instrument-lowlevel.js +9 -4
  13. package/dist/extensions/instrument-lowlevel.mjs +10 -5
  14. package/dist/extensions/instrumentation.d.ts +6 -6
  15. package/dist/extensions/instrumentation.d.ts.map +1 -1
  16. package/dist/extensions/instrumentation.js +60 -15
  17. package/dist/extensions/instrumentation.mjs +56 -14
  18. package/dist/extensions/intent.d.ts.map +1 -1
  19. package/dist/extensions/intent.js +1 -1
  20. package/dist/extensions/intent.mjs +1 -1
  21. package/dist/version.d.ts +1 -1
  22. package/dist/version.js +1 -1
  23. package/dist/version.mjs +1 -1
  24. package/package.json +3 -3
  25. package/src/extensions/context-parameters.ts +1 -7
  26. package/src/extensions/conversation-id.ts +2 -1
  27. package/src/extensions/instrument-highlevel.ts +8 -13
  28. package/src/extensions/instrument-lowlevel.ts +17 -6
  29. package/src/extensions/instrumentation.ts +90 -18
  30. package/src/extensions/intent.ts +1 -6
  31. package/src/version.ts +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"context-parameters.d.ts","sourceRoot":"","sources":["../../src/extensions/context-parameters.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AACnD,OAAO,EAGL,KAAK,6BAA6B,EACnC,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAO,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AAE7C,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,6BAA6B,CAAA;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,CAAC,SAAS,CAAC,GAAG,OAAO,CAEjF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,mBAAmB,CAAC,SAAS,CAAC,GAAG,MAAM,GAAG,SAAS,CAEjG;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,SAAS,qBAAqB,EAC3E,IAAI,EAAE,KAAK,EACX,0BAA0B,CAAC,EAAE,MAAM,EACnC,MAAM,GAAE,QAAc,GACrB,KAAK,CA6DP;AAED,wBAAgB,0BAA0B,CAAC,KAAK,SAAS,qBAAqB,EAC5E,KAAK,EAAE,KAAK,EAAE,EACd,0BAA0B,CAAC,EAAE,MAAM,EACnC,MAAM,GAAE,QAAc,GACrB,KAAK,EAAE,CAQT"}
1
+ {"version":3,"file":"context-parameters.d.ts","sourceRoot":"","sources":["../../src/extensions/context-parameters.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AACnD,OAAO,EAGL,KAAK,6BAA6B,EACnC,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAO,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AAE7C,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,6BAA6B,CAAA;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,CAAC,SAAS,CAAC,GAAG,OAAO,CAEjF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,mBAAmB,CAAC,SAAS,CAAC,GAAG,MAAM,GAAG,SAAS,CAEjG;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,SAAS,qBAAqB,EAC3E,IAAI,EAAE,KAAK,EACX,0BAA0B,CAAC,EAAE,MAAM,EACnC,MAAM,GAAE,QAAc,GACrB,KAAK,CA6DP;AAED,wBAAgB,0BAA0B,CAAC,KAAK,SAAS,qBAAqB,EAC5E,KAAK,EAAE,KAAK,EAAE,EACd,0BAA0B,CAAC,EAAE,MAAM,EACnC,MAAM,GAAE,QAAc,GACrB,KAAK,EAAE,CAET"}
@@ -70,10 +70,7 @@ function addContextParameterToTool(tool, contextDescriptionOverride, logger = ex
70
70
  return modifiedTool;
71
71
  }
72
72
  function addContextParameterToTools(tools, contextDescriptionOverride, logger = external_logger_js_namespaceObject.log) {
73
- return tools.map((tool)=>{
74
- if ('get_more_tools' === tool.name) return tool;
75
- return addContextParameterToTool(tool, contextDescriptionOverride, logger);
76
- });
73
+ return tools.map((tool)=>addContextParameterToTool(tool, contextDescriptionOverride, logger));
77
74
  }
78
75
  exports.addContextParameterToTool = __webpack_exports__.addContextParameterToTool;
79
76
  exports.addContextParameterToTools = __webpack_exports__.addContextParameterToTools;
@@ -39,9 +39,6 @@ function addContextParameterToTool(tool, contextDescriptionOverride, logger = lo
39
39
  return modifiedTool;
40
40
  }
41
41
  function addContextParameterToTools(tools, contextDescriptionOverride, logger = log) {
42
- return tools.map((tool)=>{
43
- if ('get_more_tools' === tool.name) return tool;
44
- return addContextParameterToTool(tool, contextDescriptionOverride, logger);
45
- });
42
+ return tools.map((tool)=>addContextParameterToTool(tool, contextDescriptionOverride, logger));
46
43
  }
47
44
  export { addContextParameterToTool, addContextParameterToTools, getContextDescription, isContextEnabled };
@@ -7,7 +7,7 @@ export interface ConversationIdInjectableTool {
7
7
  [key: string]: unknown;
8
8
  }
9
9
  export declare function addConversationIdToTool<TTool extends ConversationIdInjectableTool>(tool: TTool, logger?: LoggerFn): TTool;
10
- export declare function addConversationIdToTools<TTool extends ConversationIdInjectableTool>(tools: TTool[], missingCapabilityToolName?: string, logger?: LoggerFn): TTool[];
10
+ export declare function addConversationIdToTools<TTool extends ConversationIdInjectableTool>(tools: TTool[], missingCapabilityToolName?: string, skipMissingCapabilityTool?: boolean, logger?: LoggerFn): TTool[];
11
11
  export type ConversationIdResolution = {
12
12
  minted: false;
13
13
  conversationId: string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-id.d.ts","sourceRoot":"","sources":["../../src/extensions/conversation-id.ts"],"names":[],"mappings":"AAMA,OAAO,EAGL,KAAK,6BAA6B,EACnC,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAO,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AAG7C,eAAO,MAAM,0BAA0B,oBAAoB,CAAA;AAE3D,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,6BAA6B,CAAA;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,wBAAgB,uBAAuB,CAAC,KAAK,SAAS,4BAA4B,EAChF,IAAI,EAAE,KAAK,EACX,MAAM,GAAE,QAAc,GACrB,KAAK,CA4CP;AAED,wBAAgB,wBAAwB,CAAC,KAAK,SAAS,4BAA4B,EACjF,KAAK,EAAE,KAAK,EAAE,EACd,yBAAyB,GAAE,MAA4B,EACvD,MAAM,GAAE,QAAc,GACrB,KAAK,EAAE,CAOT;AAED,MAAM,MAAM,wBAAwB,GAChC;IAAE,MAAM,EAAE,KAAK,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACrD;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5C;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,yBAAyB,GAAE,MAA4B,GACtD,wBAAwB,CAS1B;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAS1E;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAUvE;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAM1D;AAED,wBAAgB,6BAA6B,CAAC,cAAc,EAAE,MAAM,GAAG;IACrE,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb,CAKA;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAS/F"}
1
+ {"version":3,"file":"conversation-id.d.ts","sourceRoot":"","sources":["../../src/extensions/conversation-id.ts"],"names":[],"mappings":"AAMA,OAAO,EAGL,KAAK,6BAA6B,EACnC,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAO,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AAG7C,eAAO,MAAM,0BAA0B,oBAAoB,CAAA;AAE3D,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,6BAA6B,CAAA;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,wBAAgB,uBAAuB,CAAC,KAAK,SAAS,4BAA4B,EAChF,IAAI,EAAE,KAAK,EACX,MAAM,GAAE,QAAc,GACrB,KAAK,CA4CP;AAED,wBAAgB,wBAAwB,CAAC,KAAK,SAAS,4BAA4B,EACjF,KAAK,EAAE,KAAK,EAAE,EACd,yBAAyB,GAAE,MAA4B,EACvD,yBAAyB,UAAO,EAChC,MAAM,GAAE,QAAc,GACrB,KAAK,EAAE,CAOT;AAED,MAAM,MAAM,wBAAwB,GAChC;IAAE,MAAM,EAAE,KAAK,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACrD;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5C;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,yBAAyB,GAAE,MAA4B,GACtD,wBAAwB,CAS1B;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAS1E;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAUvE;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAM1D;AAED,wBAAgB,6BAA6B,CAAC,cAAc,EAAE,MAAM,GAAG;IACrE,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb,CAKA;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAS/F"}
@@ -65,9 +65,9 @@ function addConversationIdToTool(tool, logger = external_logger_js_namespaceObje
65
65
  };
66
66
  return modifiedTool;
67
67
  }
68
- function addConversationIdToTools(tools, missingCapabilityToolName = external_tools_js_namespaceObject.GET_MORE_TOOLS_NAME, logger = external_logger_js_namespaceObject.log) {
68
+ function addConversationIdToTools(tools, missingCapabilityToolName = external_tools_js_namespaceObject.GET_MORE_TOOLS_NAME, skipMissingCapabilityTool = true, logger = external_logger_js_namespaceObject.log) {
69
69
  return tools.map((tool)=>{
70
- if (tool.name === missingCapabilityToolName) return tool;
70
+ if (skipMissingCapabilityTool && tool.name === missingCapabilityToolName) return tool;
71
71
  return addConversationIdToTool(tool, logger);
72
72
  });
73
73
  }
@@ -29,9 +29,9 @@ function addConversationIdToTool(tool, logger = log) {
29
29
  };
30
30
  return modifiedTool;
31
31
  }
32
- function addConversationIdToTools(tools, missingCapabilityToolName = GET_MORE_TOOLS_NAME, logger = log) {
32
+ function addConversationIdToTools(tools, missingCapabilityToolName = GET_MORE_TOOLS_NAME, skipMissingCapabilityTool = true, logger = log) {
33
33
  return tools.map((tool)=>{
34
- if (tool.name === missingCapabilityToolName) return tool;
34
+ if (skipMissingCapabilityTool && tool.name === missingCapabilityToolName) return tool;
35
35
  return addConversationIdToTool(tool, logger);
36
36
  });
37
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"instrument-highlevel.d.ts","sourceRoot":"","sources":["../../src/extensions/instrument-highlevel.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAEV,sBAAsB,EAIvB,MAAM,UAAU,CAAA;AASjB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAmQxC,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CA2BhG"}
1
+ {"version":3,"file":"instrument-highlevel.d.ts","sourceRoot":"","sources":["../../src/extensions/instrument-highlevel.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAEV,sBAAsB,EAIvB,MAAM,UAAU,CAAA;AASjB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AA8PxC,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CA2BhG"}
@@ -107,7 +107,6 @@ function setupListenerToRegisteredTools(server, logger) {
107
107
  function addTracingToToolCallbackInternal(tool, toolName, server, getCurrentInputSchema, logger) {
108
108
  const originalCallback = (0, external_mcp_sdk_compat_js_namespaceObject.getToolFunction)(tool);
109
109
  const options = (0, external_internal_js_namespaceObject.getServerTrackingData)(server.server)?.options;
110
- const missingToolName = (0, external_tools_js_namespaceObject.resolveMissingCapabilityToolName)(options);
111
110
  if (wrappedCallbacks.has(originalCallback)) {
112
111
  logger(`Tool ${toolName} callback already wrapped, skipping re-wrap`);
113
112
  return tool;
@@ -128,8 +127,8 @@ function addTracingToToolCallbackInternal(tool, toolName, server, getCurrentInpu
128
127
  }
129
128
  const inputSchema = getCurrentInputSchema();
130
129
  const cleanedArgs = (0, external_analytics_parameters_js_namespaceObject.stripOwnedAnalyticsArguments)(args, {
131
- context: toolName !== missingToolName && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(options?.context) && (0, external_analytics_parameters_js_namespaceObject.analyticsOwnsParameter)(inputSchema, 'context'),
132
- conversationId: toolName !== missingToolName && options?.enableConversationId === true && (0, external_analytics_parameters_js_namespaceObject.analyticsOwnsParameter)(inputSchema, 'conversation_id')
130
+ context: (0, external_context_parameters_js_namespaceObject.isContextEnabled)(options?.context) && (0, external_analytics_parameters_js_namespaceObject.analyticsOwnsParameter)(inputSchema, 'context'),
131
+ conversationId: options?.enableConversationId === true && (0, external_analytics_parameters_js_namespaceObject.analyticsOwnsParameter)(inputSchema, 'conversation_id')
133
132
  });
134
133
  try {
135
134
  if (void 0 === cleanedArgs) {
@@ -155,7 +154,9 @@ async function handleToolCallRequest(highLevelServer, server, originalCallToolHa
155
154
  logger('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
156
155
  return await originalCallToolHandler(request, extra);
157
156
  }
158
- if (request.params?.name === (0, external_tools_js_namespaceObject.resolveMissingCapabilityToolName)(data.options)) {
157
+ const toolName = request.params?.name;
158
+ const isMissingCapabilityCandidate = data.options.reportMissing && toolName === (0, external_tools_js_namespaceObject.resolveMissingCapabilityToolName)(data.options);
159
+ if (isMissingCapabilityCandidate && await (0, external_instrumentation_js_namespaceObject.isToolAdvertised)(server, toolName, extra, data.logger) === false) {
159
160
  const context = (0, external_tracing_helpers_js_namespaceObject.getContextArgument)(request) || '';
160
161
  return await (0, external_instrumentation_js_namespaceObject.captureToolCall)({
161
162
  server,
@@ -169,7 +170,6 @@ async function handleToolCallRequest(highLevelServer, server, originalCallToolHa
169
170
  }, data.logger)
170
171
  });
171
172
  }
172
- const toolName = request.params?.name;
173
173
  const registeredTool = toolName ? highLevelServer._registeredTools[toolName] : void 0;
174
174
  return await (0, external_instrumentation_js_namespaceObject.captureToolCall)({
175
175
  server,
@@ -4,7 +4,7 @@ import { MCPAnalyticsEventType } from "./event-types.mjs";
4
4
  import { getServerTrackingData } from "./internal.mjs";
5
5
  import { createWrappedTool, getToolFunction, hasToolFunction } from "./mcp-sdk-compat.mjs";
6
6
  import { handleReportMissing, resolveMissingCapabilityToolName } from "./tools.mjs";
7
- import { captureToolCall, handleInitializeRequest, handleListToolsRequest, patchRequestHandlers, readToolMetaCategory } from "./instrumentation.mjs";
7
+ import { captureToolCall, handleInitializeRequest, handleListToolsRequest, isToolAdvertised, patchRequestHandlers, readToolMetaCategory } from "./instrumentation.mjs";
8
8
  import { getContextArgument } from "./tracing-helpers.mjs";
9
9
  const wrappedCallbacks = new WeakMap();
10
10
  const MCP_ANALYTICS_PROCESSED = Symbol('__posthog_mcp_analytics_processed__');
@@ -79,7 +79,6 @@ function setupListenerToRegisteredTools(server, logger) {
79
79
  function addTracingToToolCallbackInternal(tool, toolName, server, getCurrentInputSchema, logger) {
80
80
  const originalCallback = getToolFunction(tool);
81
81
  const options = getServerTrackingData(server.server)?.options;
82
- const missingToolName = resolveMissingCapabilityToolName(options);
83
82
  if (wrappedCallbacks.has(originalCallback)) {
84
83
  logger(`Tool ${toolName} callback already wrapped, skipping re-wrap`);
85
84
  return tool;
@@ -100,8 +99,8 @@ function addTracingToToolCallbackInternal(tool, toolName, server, getCurrentInpu
100
99
  }
101
100
  const inputSchema = getCurrentInputSchema();
102
101
  const cleanedArgs = stripOwnedAnalyticsArguments(args, {
103
- context: toolName !== missingToolName && isContextEnabled(options?.context) && analyticsOwnsParameter(inputSchema, 'context'),
104
- conversationId: toolName !== missingToolName && options?.enableConversationId === true && analyticsOwnsParameter(inputSchema, 'conversation_id')
102
+ context: isContextEnabled(options?.context) && analyticsOwnsParameter(inputSchema, 'context'),
103
+ conversationId: options?.enableConversationId === true && analyticsOwnsParameter(inputSchema, 'conversation_id')
105
104
  });
106
105
  try {
107
106
  if (void 0 === cleanedArgs) {
@@ -127,7 +126,9 @@ async function handleToolCallRequest(highLevelServer, server, originalCallToolHa
127
126
  logger('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
128
127
  return await originalCallToolHandler(request, extra);
129
128
  }
130
- if (request.params?.name === resolveMissingCapabilityToolName(data.options)) {
129
+ const toolName = request.params?.name;
130
+ const isMissingCapabilityCandidate = data.options.reportMissing && toolName === resolveMissingCapabilityToolName(data.options);
131
+ if (isMissingCapabilityCandidate && await isToolAdvertised(server, toolName, extra, data.logger) === false) {
131
132
  const context = getContextArgument(request) || '';
132
133
  return await captureToolCall({
133
134
  server,
@@ -141,7 +142,6 @@ async function handleToolCallRequest(highLevelServer, server, originalCallToolHa
141
142
  }, data.logger)
142
143
  });
143
144
  }
144
- const toolName = request.params?.name;
145
145
  const registeredTool = toolName ? highLevelServer._registeredTools[toolName] : void 0;
146
146
  return await captureToolCall({
147
147
  server,
@@ -1 +1 @@
1
- {"version":3,"file":"instrument-lowlevel.d.ts","sourceRoot":"","sources":["../../src/extensions/instrument-lowlevel.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAiD,aAAa,EAAE,MAAM,UAAU,CAAA;AAG5F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAiBxC;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CAoBtF"}
1
+ {"version":3,"file":"instrument-lowlevel.d.ts","sourceRoot":"","sources":["../../src/extensions/instrument-lowlevel.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAiD,aAAa,EAAE,MAAM,UAAU,CAAA;AAG5F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAkBxC;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CA0BtF"}
@@ -34,13 +34,16 @@ const external_instrumentation_js_namespaceObject = require("./instrumentation.j
34
34
  const external_tracing_helpers_js_namespaceObject = require("./tracing-helpers.js");
35
35
  function instrumentLowLevelServer(server, logger) {
36
36
  try {
37
+ const hadCallToolHandler = server._requestHandlers.has('tools/call');
37
38
  const handlers = {
38
39
  initialize: (server, originalHandler, request, extra)=>(0, external_instrumentation_js_namespaceObject.handleInitializeRequest)(server, originalHandler, request, extra, logger),
39
- 'tools/list': (server, originalHandler, request, extra)=>(0, external_instrumentation_js_namespaceObject.handleListToolsRequest)(server, originalHandler, request, extra, logger)
40
+ 'tools/list': (server, originalHandler, request, extra)=>(0, external_instrumentation_js_namespaceObject.handleListToolsRequest)(server, originalHandler, request, extra, logger),
41
+ 'tools/call': (server, originalHandler, request, extra)=>handleToolCallRequest(server, originalHandler, request, extra, logger)
40
42
  };
41
43
  (0, external_instrumentation_js_namespaceObject.patchRequestHandlers)(server, handlers);
42
- const originalCallToolHandler = server._requestHandlers.get('tools/call');
43
- server.setRequestHandler(types_js_namespaceObject.CallToolRequestSchema, async (request, extra)=>await handleToolCallRequest(server, originalCallToolHandler, request, extra, logger));
44
+ if (!hadCallToolHandler) server.setRequestHandler(types_js_namespaceObject.CallToolRequestSchema, async (request)=>{
45
+ throw new Error(`Unknown tool: ${request.params?.name || 'unknown'}`);
46
+ });
44
47
  } catch (error) {
45
48
  logger(`Warning: Failed to setup tool call instrumentation - ${error}`);
46
49
  throw error;
@@ -52,7 +55,9 @@ async function handleToolCallRequest(server, originalCallToolHandler, request, e
52
55
  logger('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
53
56
  return await originalCallToolHandler?.(request, extra);
54
57
  }
55
- if (request.params?.name === (0, external_tools_js_namespaceObject.resolveMissingCapabilityToolName)(data.options)) {
58
+ const toolName = request.params?.name;
59
+ const isMissingCapabilityCandidate = data.options.reportMissing && toolName === (0, external_tools_js_namespaceObject.resolveMissingCapabilityToolName)(data.options);
60
+ if (isMissingCapabilityCandidate && await (0, external_instrumentation_js_namespaceObject.isToolAdvertised)(server, toolName, extra, data.logger) === false) {
56
61
  const context = (0, external_tracing_helpers_js_namespaceObject.getContextArgument)(request) || '';
57
62
  return await (0, external_instrumentation_js_namespaceObject.captureToolCall)({
58
63
  server,
@@ -2,17 +2,20 @@ import { CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { MCPAnalyticsEventType } from "./event-types.mjs";
3
3
  import { getServerTrackingData } from "./internal.mjs";
4
4
  import { handleReportMissing, resolveMissingCapabilityToolName } from "./tools.mjs";
5
- import { captureToolCall, handleInitializeRequest, handleListToolsRequest, patchRequestHandlers } from "./instrumentation.mjs";
5
+ import { captureToolCall, handleInitializeRequest, handleListToolsRequest, isToolAdvertised, patchRequestHandlers } from "./instrumentation.mjs";
6
6
  import { getContextArgument } from "./tracing-helpers.mjs";
7
7
  function instrumentLowLevelServer(server, logger) {
8
8
  try {
9
+ const hadCallToolHandler = server._requestHandlers.has('tools/call');
9
10
  const handlers = {
10
11
  initialize: (server, originalHandler, request, extra)=>handleInitializeRequest(server, originalHandler, request, extra, logger),
11
- 'tools/list': (server, originalHandler, request, extra)=>handleListToolsRequest(server, originalHandler, request, extra, logger)
12
+ 'tools/list': (server, originalHandler, request, extra)=>handleListToolsRequest(server, originalHandler, request, extra, logger),
13
+ 'tools/call': (server, originalHandler, request, extra)=>handleToolCallRequest(server, originalHandler, request, extra, logger)
12
14
  };
13
15
  patchRequestHandlers(server, handlers);
14
- const originalCallToolHandler = server._requestHandlers.get('tools/call');
15
- server.setRequestHandler(CallToolRequestSchema, async (request, extra)=>await handleToolCallRequest(server, originalCallToolHandler, request, extra, logger));
16
+ if (!hadCallToolHandler) server.setRequestHandler(CallToolRequestSchema, async (request)=>{
17
+ throw new Error(`Unknown tool: ${request.params?.name || 'unknown'}`);
18
+ });
16
19
  } catch (error) {
17
20
  logger(`Warning: Failed to setup tool call instrumentation - ${error}`);
18
21
  throw error;
@@ -24,7 +27,9 @@ async function handleToolCallRequest(server, originalCallToolHandler, request, e
24
27
  logger('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
25
28
  return await originalCallToolHandler?.(request, extra);
26
29
  }
27
- if (request.params?.name === resolveMissingCapabilityToolName(data.options)) {
30
+ const toolName = request.params?.name;
31
+ const isMissingCapabilityCandidate = data.options.reportMissing && toolName === resolveMissingCapabilityToolName(data.options);
32
+ if (isMissingCapabilityCandidate && await isToolAdvertised(server, toolName, extra, data.logger) === false) {
28
33
  const context = getContextArgument(request) || '';
29
34
  return await captureToolCall({
30
35
  server,
@@ -55,14 +55,14 @@ export declare function captureToolCall(params: TraceToolCallParams): Promise<un
55
55
  * supplies `request` and `extra` per call.
56
56
  */
57
57
  export type HandlerPatch = (server: MCPServerLike, originalHandler: MCPRequestHandler, request: MCPRequestLike, extra: CompatibleRequestHandlerExtra | undefined) => Promise<unknown>;
58
+ export declare function patchRequestHandlers(server: MCPServerLike, patches: Record<string, HandlerPatch>): void;
58
59
  /**
59
- * Applies the `patches` (keyed by method, e.g. `initialize`, `tools/list`) to the
60
- * handlers already registered, and patches `setRequestHandler` so matching
61
- * handlers registered later are patched too. The latter is what makes adapters
62
- * that register handlers post-construction work — e.g. `@rekog/mcp-nest` hands a
63
- * bare server to instrument() and only then registers its handlers.
60
+ * Checks the server's raw listing for a real owner of a candidate virtual tool.
61
+ * This does not depend on a previous client request and does not call the
62
+ * instrumented list wrapper, so it neither injects PostHog tools nor captures a
63
+ * synthetic tools/list event. `undefined` fails open to the real dispatcher.
64
64
  */
65
- export declare function patchRequestHandlers(server: MCPServerLike, patches: Record<string, HandlerPatch>): void;
65
+ export declare function isToolAdvertised(server: MCPServerLike, toolName: string, extra: CompatibleRequestHandlerExtra | undefined, logger: LoggerFn): Promise<boolean | undefined>;
66
66
  /**
67
67
  * Captures each `tools/list` and injects the SDK-managed tools (context
68
68
  * parameter, conversation id, `get_more_tools`) into the returned list.
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,KAAK,EACV,2BAA2B,EAC3B,6BAA6B,EAC7B,gBAAgB,EAChB,cAAc,EACd,aAAa,EAGd,MAAM,UAAU,CAAA;AAYjB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAIrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAQxC;;;;;;GAMG;AAEH,KAAK,iBAAiB,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,6BAA6B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAE7G,2EAA2E;AAC3E,KAAK,YAAY,GAAG,CAAC,iBAAiB,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAE3E,UAAU,mBAAmB;IAC3B,MAAM,EAAE,aAAa,CAAA;IACrB,IAAI,EAAE,gBAAgB,CAAA;IACtB,OAAO,EAAE,cAAc,CAAA;IACvB,KAAK,CAAC,EAAE,6BAA6B,CAAA;IACrC,OAAO,EAAE,YAAY,CAAA;IACrB,2FAA2F;IAC3F,kBAAkB,CAAC,EAAE,2BAA2B,CAAA;IAChD;;;;OAIG;IACH,SAAS,CAAC,EAAE,qBAAqB,CAAA;IACjC;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAA;CAClC;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CAmDnF;AAuJD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CACzB,MAAM,EAAE,aAAa,EACrB,eAAe,EAAE,iBAAiB,EAClC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,6BAA6B,GAAG,SAAS,KAC7C,OAAO,CAAC,OAAO,CAAC,CAAA;AAErB;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,IAAI,CAqBvG;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,aAAa,EACrB,wBAAwB,EAAE,iBAAiB,EAC3C,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,6BAA6B,GAAG,SAAS,EAChD,MAAM,EAAE,QAAQ,GACf,OAAO,CAAC;IAAE,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAyC9C;AAyED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,CASnH;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAGtE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,CAUjH;AA8GD;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,aAAa,EACrB,yBAAyB,EAAE,iBAAiB,EAC5C,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,6BAA6B,GAAG,SAAS,EAChD,MAAM,EAAE,QAAQ,GACf,OAAO,CAAC,OAAO,CAAC,CA0ClB"}
1
+ {"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,KAAK,EACV,2BAA2B,EAC3B,6BAA6B,EAC7B,gBAAgB,EAChB,cAAc,EACd,aAAa,EAGd,MAAM,UAAU,CAAA;AAYjB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAIrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAQxC;;;;;;GAMG;AAEH,KAAK,iBAAiB,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,6BAA6B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAE7G,2EAA2E;AAC3E,KAAK,YAAY,GAAG,CAAC,iBAAiB,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAE3E,UAAU,mBAAmB;IAC3B,MAAM,EAAE,aAAa,CAAA;IACrB,IAAI,EAAE,gBAAgB,CAAA;IACtB,OAAO,EAAE,cAAc,CAAA;IACvB,KAAK,CAAC,EAAE,6BAA6B,CAAA;IACrC,OAAO,EAAE,YAAY,CAAA;IACrB,2FAA2F;IAC3F,kBAAkB,CAAC,EAAE,2BAA2B,CAAA;IAChD;;;;OAIG;IACH,SAAS,CAAC,EAAE,qBAAqB,CAAA;IACjC;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAA;CAClC;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CA0DnF;AAuJD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CACzB,MAAM,EAAE,aAAa,EACrB,eAAe,EAAE,iBAAiB,EAClC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,6BAA6B,GAAG,SAAS,KAC7C,OAAO,CAAC,OAAO,CAAC,CAAA;AAwBrB,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,IAAI,CA8BvG;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,6BAA6B,GAAG,SAAS,EAChD,MAAM,EAAE,QAAQ,GACf,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAoB9B;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,aAAa,EACrB,wBAAwB,EAAE,iBAAiB,EAC3C,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,6BAA6B,GAAG,SAAS,EAChD,MAAM,EAAE,QAAQ,GACf,OAAO,CAAC;IAAE,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAyC9C;AAiFD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,CASnH;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAGtE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,CAUjH;AA8GD;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,aAAa,EACrB,yBAAyB,EAAE,iBAAiB,EAC5C,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,6BAA6B,GAAG,SAAS,EAChD,MAAM,EAAE,QAAQ,GACf,OAAO,CAAC,OAAO,CAAC,CA0ClB"}
@@ -24,11 +24,12 @@ var __webpack_require__ = {};
24
24
  var __webpack_exports__ = {};
25
25
  __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
- readToolMetaCategory: ()=>readToolMetaCategory,
27
+ isToolAdvertised: ()=>isToolAdvertised,
28
28
  cacheToolDescriptions: ()=>cacheToolDescriptions,
29
- cacheToolCategories: ()=>cacheToolCategories,
30
29
  handleInitializeRequest: ()=>handleInitializeRequest,
31
30
  captureToolCall: ()=>captureToolCall,
31
+ readToolMetaCategory: ()=>readToolMetaCategory,
32
+ cacheToolCategories: ()=>cacheToolCategories,
32
33
  handleListToolsRequest: ()=>handleListToolsRequest,
33
34
  patchRequestHandlers: ()=>patchRequestHandlers
34
35
  });
@@ -49,11 +50,12 @@ const external_tools_js_namespaceObject = require("./tools.js");
49
50
  const external_tracing_helpers_js_namespaceObject = require("./tracing-helpers.js");
50
51
  async function captureToolCall(params) {
51
52
  const { server, data, request, extra, execute, parameterOwnership, eventType, explicitContextIntent, takeCapturedError } = params;
52
- const ownership = getActiveAnalyticsParameterOwnership(data, request.params?.name, parameterOwnership);
53
- const conversation = (0, external_conversation_id_js_namespaceObject.resolveConversationId)(ownership.conversationId, request.params?.arguments, request.params?.name, (0, external_tools_js_namespaceObject.resolveMissingCapabilityToolName)(data.options));
53
+ const resolvedEventType = eventType ?? external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpToolsCall;
54
+ const ownership = getActiveAnalyticsParameterOwnership(data, request.params?.name, parameterOwnership, resolvedEventType === external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpMissingCapability);
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) : '');
54
56
  const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership);
55
57
  const startTime = new Date();
56
- const event = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, eventType ?? external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpToolsCall);
58
+ const event = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType);
57
59
  if (event && explicitContextIntent) (0, external_intent_js_namespaceObject.setExplicitContextIntent)(event, explicitContextIntent);
58
60
  let result;
59
61
  try {
@@ -66,9 +68,8 @@ async function captureToolCall(params) {
66
68
  publishSuccessfulToolEvent(server, event, finalResult, startTime, data.logger, takeCapturedError);
67
69
  return finalResult;
68
70
  }
69
- function getActiveAnalyticsParameterOwnership(data, toolName, override) {
71
+ function getActiveAnalyticsParameterOwnership(data, toolName, override, isMissingCapabilityTool) {
70
72
  const ownership = override ?? (toolName ? data.toolAnalyticsParameterOwnership.get(toolName) : void 0);
71
- const isMissingCapabilityTool = toolName === (0, external_tools_js_namespaceObject.resolveMissingCapabilityToolName)(data.options);
72
73
  return {
73
74
  context: !isMissingCapabilityTool && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context) && ownership?.context === true,
74
75
  conversationId: !isMissingCapabilityTool && true === data.options.enableConversationId && ownership?.conversationId === true
@@ -143,20 +144,53 @@ function publishFailedToolEvent(server, event, error, startTime, conversation, l
143
144
  logger(`Warning: PostHog MCP analytics failed to publish failed tool event - ${publishError}`);
144
145
  }
145
146
  }
147
+ const originalRequestHandlers = new WeakMap();
148
+ function rememberOriginalRequestHandler(server, handlerName, originalHandler) {
149
+ let handlers = originalRequestHandlers.get(server);
150
+ if (!handlers) {
151
+ handlers = new Map();
152
+ originalRequestHandlers.set(server, handlers);
153
+ }
154
+ handlers.set(handlerName, originalHandler);
155
+ }
146
156
  function patchRequestHandlers(server, patches) {
147
157
  for (const [handlerName, patch] of Object.entries(patches)){
148
158
  const originalHandler = server._requestHandlers.get(handlerName);
149
- if (originalHandler) server._requestHandlers.set(handlerName, (request, extra)=>patch(server, originalHandler, request, extra));
159
+ if (originalHandler) {
160
+ rememberOriginalRequestHandler(server, handlerName, originalHandler);
161
+ server._requestHandlers.set(handlerName, (request, extra)=>patch(server, originalHandler, request, extra));
162
+ }
150
163
  }
151
164
  const originalSetRequestHandler = server.setRequestHandler.bind(server);
152
165
  server.setRequestHandler = (requestSchema, originalHandler)=>{
153
166
  const shape = (0, external_mcp_sdk_compat_js_namespaceObject.getObjectShape)(requestSchema);
154
167
  const handlerName = shape?.method ? (0, external_mcp_sdk_compat_js_namespaceObject.getLiteralValue)(shape.method) : void 0;
155
168
  const patch = 'string' == typeof handlerName ? patches[handlerName] : void 0;
156
- if (!patch) return originalSetRequestHandler(requestSchema, originalHandler);
157
- return originalSetRequestHandler(requestSchema, (request, extra)=>patch(server, originalHandler, request, extra));
169
+ if (!patch || 'string' != typeof handlerName) return originalSetRequestHandler(requestSchema, originalHandler);
170
+ const result = originalSetRequestHandler(requestSchema, originalHandler);
171
+ const registeredHandler = server._requestHandlers.get(handlerName);
172
+ if (registeredHandler) {
173
+ rememberOriginalRequestHandler(server, handlerName, registeredHandler);
174
+ server._requestHandlers.set(handlerName, (request, extra)=>patch(server, registeredHandler, request, extra));
175
+ }
176
+ return result;
158
177
  };
159
178
  }
179
+ async function isToolAdvertised(server, toolName, extra, logger) {
180
+ const listHandler = originalRequestHandlers.get(server)?.get('tools/list');
181
+ if (!listHandler || !server._requestHandlers.has('tools/list')) return;
182
+ try {
183
+ const response = await listHandler({
184
+ method: 'tools/list',
185
+ params: {}
186
+ }, extra);
187
+ if (!response || !Array.isArray(response.tools)) return;
188
+ return response.tools.some((tool)=>tool?.name === toolName);
189
+ } catch (error) {
190
+ logger(`Warning: PostHog MCP analytics could not determine whether "${toolName}" is advertised; delegating to the server - ${error}`);
191
+ return;
192
+ }
193
+ }
160
194
  async function handleListToolsRequest(server, originalListToolsHandler, request, extra, logger) {
161
195
  const data = (0, external_internal_js_namespaceObject.getServerTrackingData)(server);
162
196
  const startTime = new Date();
@@ -208,14 +242,23 @@ async function getTracedToolsList(server, originalListToolsHandler, request, ext
208
242
  try {
209
243
  const data = (0, external_internal_js_namespaceObject.getServerTrackingData)(server);
210
244
  const originalResponse = await originalListToolsHandler(request, extra);
211
- let tools = originalResponse.tools || [];
245
+ let tools = [
246
+ ...originalResponse.tools || []
247
+ ];
212
248
  if (data) cacheToolAnalyticsParameterOwnership(data.toolAnalyticsParameterOwnership, tools);
213
249
  if (data && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context)) tools = (0, external_context_parameters_js_namespaceObject.addContextParameterToTools)(tools, (0, external_context_parameters_js_namespaceObject.getContextDescription)(data.options.context), data.logger);
214
- if (data?.options.enableConversationId) tools = (0, external_conversation_id_js_namespaceObject.addConversationIdToTools)(tools, (0, external_tools_js_namespaceObject.resolveMissingCapabilityToolName)(data.options), data.logger);
215
- if (data?.options.reportMissing) {
250
+ if (data) {
216
251
  const missingToolName = (0, external_tools_js_namespaceObject.resolveMissingCapabilityToolName)(data.options);
217
- const alreadyPresent = tools.some((tool)=>tool?.name === missingToolName);
218
- if (!alreadyPresent) tools.push((0, external_tools_js_namespaceObject.getReportMissingToolDescriptor)(missingToolName));
252
+ let injectedMissingCapabilityTool = false;
253
+ if (data.options.reportMissing) {
254
+ const alreadyPresent = tools.some((tool)=>tool?.name === missingToolName);
255
+ if (alreadyPresent) data.logger(`Warning: Cannot inject missing-capability tool "${missingToolName}" because a real tool already uses that name. The real tool will not be intercepted.`);
256
+ else {
257
+ tools.push((0, external_tools_js_namespaceObject.getReportMissingToolDescriptor)(missingToolName));
258
+ injectedMissingCapabilityTool = true;
259
+ }
260
+ }
261
+ if (data.options.enableConversationId) tools = (0, external_conversation_id_js_namespaceObject.addConversationIdToTools)(tools, missingToolName, injectedMissingCapabilityTool, data.logger);
219
262
  }
220
263
  if (data) {
221
264
  cacheToolDescriptions(data.toolDescriptions, tools);
@@ -339,6 +382,7 @@ exports.cacheToolDescriptions = __webpack_exports__.cacheToolDescriptions;
339
382
  exports.captureToolCall = __webpack_exports__.captureToolCall;
340
383
  exports.handleInitializeRequest = __webpack_exports__.handleInitializeRequest;
341
384
  exports.handleListToolsRequest = __webpack_exports__.handleListToolsRequest;
385
+ exports.isToolAdvertised = __webpack_exports__.isToolAdvertised;
342
386
  exports.patchRequestHandlers = __webpack_exports__.patchRequestHandlers;
343
387
  exports.readToolMetaCategory = __webpack_exports__.readToolMetaCategory;
344
388
  for(var __webpack_i__ in __webpack_exports__)if (-1 === [
@@ -347,6 +391,7 @@ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
347
391
  "captureToolCall",
348
392
  "handleInitializeRequest",
349
393
  "handleListToolsRequest",
394
+ "isToolAdvertised",
350
395
  "patchRequestHandlers",
351
396
  "readToolMetaCategory"
352
397
  ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
@@ -15,11 +15,12 @@ import { getReportMissingToolDescriptor, resolveMissingCapabilityToolName } from
15
15
  import { applyResolvedMetadata, isToolResultError } from "./tracing-helpers.mjs";
16
16
  async function captureToolCall(params) {
17
17
  const { server, data, request, extra, execute, parameterOwnership, eventType, explicitContextIntent, takeCapturedError } = params;
18
- const ownership = getActiveAnalyticsParameterOwnership(data, request.params?.name, parameterOwnership);
19
- const conversation = resolveConversationId(ownership.conversationId, request.params?.arguments, request.params?.name, resolveMissingCapabilityToolName(data.options));
18
+ const resolvedEventType = eventType ?? MCPAnalyticsEventType.mcpToolsCall;
19
+ const ownership = getActiveAnalyticsParameterOwnership(data, request.params?.name, parameterOwnership, resolvedEventType === MCPAnalyticsEventType.mcpMissingCapability);
20
+ const conversation = resolveConversationId(ownership.conversationId, request.params?.arguments, request.params?.name, resolvedEventType === MCPAnalyticsEventType.mcpMissingCapability ? resolveMissingCapabilityToolName(data.options) : '');
20
21
  const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership);
21
22
  const startTime = new Date();
22
- const event = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, eventType ?? MCPAnalyticsEventType.mcpToolsCall);
23
+ const event = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType);
23
24
  if (event && explicitContextIntent) setExplicitContextIntent(event, explicitContextIntent);
24
25
  let result;
25
26
  try {
@@ -32,9 +33,8 @@ async function captureToolCall(params) {
32
33
  publishSuccessfulToolEvent(server, event, finalResult, startTime, data.logger, takeCapturedError);
33
34
  return finalResult;
34
35
  }
35
- function getActiveAnalyticsParameterOwnership(data, toolName, override) {
36
+ function getActiveAnalyticsParameterOwnership(data, toolName, override, isMissingCapabilityTool) {
36
37
  const ownership = override ?? (toolName ? data.toolAnalyticsParameterOwnership.get(toolName) : void 0);
37
- const isMissingCapabilityTool = toolName === resolveMissingCapabilityToolName(data.options);
38
38
  return {
39
39
  context: !isMissingCapabilityTool && isContextEnabled(data.options.context) && ownership?.context === true,
40
40
  conversationId: !isMissingCapabilityTool && true === data.options.enableConversationId && ownership?.conversationId === true
@@ -109,20 +109,53 @@ function publishFailedToolEvent(server, event, error, startTime, conversation, l
109
109
  logger(`Warning: PostHog MCP analytics failed to publish failed tool event - ${publishError}`);
110
110
  }
111
111
  }
112
+ const originalRequestHandlers = new WeakMap();
113
+ function rememberOriginalRequestHandler(server, handlerName, originalHandler) {
114
+ let handlers = originalRequestHandlers.get(server);
115
+ if (!handlers) {
116
+ handlers = new Map();
117
+ originalRequestHandlers.set(server, handlers);
118
+ }
119
+ handlers.set(handlerName, originalHandler);
120
+ }
112
121
  function patchRequestHandlers(server, patches) {
113
122
  for (const [handlerName, patch] of Object.entries(patches)){
114
123
  const originalHandler = server._requestHandlers.get(handlerName);
115
- if (originalHandler) server._requestHandlers.set(handlerName, (request, extra)=>patch(server, originalHandler, request, extra));
124
+ if (originalHandler) {
125
+ rememberOriginalRequestHandler(server, handlerName, originalHandler);
126
+ server._requestHandlers.set(handlerName, (request, extra)=>patch(server, originalHandler, request, extra));
127
+ }
116
128
  }
117
129
  const originalSetRequestHandler = server.setRequestHandler.bind(server);
118
130
  server.setRequestHandler = (requestSchema, originalHandler)=>{
119
131
  const shape = getObjectShape(requestSchema);
120
132
  const handlerName = shape?.method ? getLiteralValue(shape.method) : void 0;
121
133
  const patch = 'string' == typeof handlerName ? patches[handlerName] : void 0;
122
- if (!patch) return originalSetRequestHandler(requestSchema, originalHandler);
123
- return originalSetRequestHandler(requestSchema, (request, extra)=>patch(server, originalHandler, request, extra));
134
+ if (!patch || 'string' != typeof handlerName) return originalSetRequestHandler(requestSchema, originalHandler);
135
+ const result = originalSetRequestHandler(requestSchema, originalHandler);
136
+ const registeredHandler = server._requestHandlers.get(handlerName);
137
+ if (registeredHandler) {
138
+ rememberOriginalRequestHandler(server, handlerName, registeredHandler);
139
+ server._requestHandlers.set(handlerName, (request, extra)=>patch(server, registeredHandler, request, extra));
140
+ }
141
+ return result;
124
142
  };
125
143
  }
144
+ async function isToolAdvertised(server, toolName, extra, logger) {
145
+ const listHandler = originalRequestHandlers.get(server)?.get('tools/list');
146
+ if (!listHandler || !server._requestHandlers.has('tools/list')) return;
147
+ try {
148
+ const response = await listHandler({
149
+ method: 'tools/list',
150
+ params: {}
151
+ }, extra);
152
+ if (!response || !Array.isArray(response.tools)) return;
153
+ return response.tools.some((tool)=>tool?.name === toolName);
154
+ } catch (error) {
155
+ logger(`Warning: PostHog MCP analytics could not determine whether "${toolName}" is advertised; delegating to the server - ${error}`);
156
+ return;
157
+ }
158
+ }
126
159
  async function handleListToolsRequest(server, originalListToolsHandler, request, extra, logger) {
127
160
  const data = getServerTrackingData(server);
128
161
  const startTime = new Date();
@@ -174,14 +207,23 @@ async function getTracedToolsList(server, originalListToolsHandler, request, ext
174
207
  try {
175
208
  const data = getServerTrackingData(server);
176
209
  const originalResponse = await originalListToolsHandler(request, extra);
177
- let tools = originalResponse.tools || [];
210
+ let tools = [
211
+ ...originalResponse.tools || []
212
+ ];
178
213
  if (data) cacheToolAnalyticsParameterOwnership(data.toolAnalyticsParameterOwnership, tools);
179
214
  if (data && isContextEnabled(data.options.context)) tools = addContextParameterToTools(tools, getContextDescription(data.options.context), data.logger);
180
- if (data?.options.enableConversationId) tools = addConversationIdToTools(tools, resolveMissingCapabilityToolName(data.options), data.logger);
181
- if (data?.options.reportMissing) {
215
+ if (data) {
182
216
  const missingToolName = resolveMissingCapabilityToolName(data.options);
183
- const alreadyPresent = tools.some((tool)=>tool?.name === missingToolName);
184
- if (!alreadyPresent) tools.push(getReportMissingToolDescriptor(missingToolName));
217
+ let injectedMissingCapabilityTool = false;
218
+ if (data.options.reportMissing) {
219
+ const alreadyPresent = tools.some((tool)=>tool?.name === missingToolName);
220
+ if (alreadyPresent) data.logger(`Warning: Cannot inject missing-capability tool "${missingToolName}" because a real tool already uses that name. The real tool will not be intercepted.`);
221
+ else {
222
+ tools.push(getReportMissingToolDescriptor(missingToolName));
223
+ injectedMissingCapabilityTool = true;
224
+ }
225
+ }
226
+ if (data.options.enableConversationId) tools = addConversationIdToTools(tools, missingToolName, injectedMissingCapabilityTool, data.logger);
185
227
  }
186
228
  if (data) {
187
229
  cacheToolDescriptions(data.toolDescriptions, tools);
@@ -300,4 +342,4 @@ function readProtocolVersion(result, request) {
300
342
  const requested = request.params?.protocolVersion;
301
343
  return 'string' == typeof requested && requested.length > 0 ? requested : void 0;
302
344
  }
303
- export { cacheToolCategories, cacheToolDescriptions, captureToolCall, handleInitializeRequest, handleListToolsRequest, patchRequestHandlers, readToolMetaCategory };
345
+ export { cacheToolCategories, cacheToolDescriptions, captureToolCall, handleInitializeRequest, handleListToolsRequest, isToolAdvertised, patchRequestHandlers, readToolMetaCategory };
@@ -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,CAYhC;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,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"}
@@ -53,7 +53,7 @@ async function runIntentFallback(data, request, extra) {
53
53
  }
54
54
  async function resolveToolCallIntent(data, request, analyticsOwnsContext, extra) {
55
55
  const contextArgument = getContextArgument(request);
56
- if (analyticsOwnsContext && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context) && request.params?.name !== 'get_more_tools' && contextArgument) return {
56
+ if (analyticsOwnsContext && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context) && contextArgument) return {
57
57
  intent: contextArgument,
58
58
  source: 'context_parameter'
59
59
  };
@@ -23,7 +23,7 @@ async function runIntentFallback(data, request, extra) {
23
23
  }
24
24
  async function resolveToolCallIntent(data, request, analyticsOwnsContext, extra) {
25
25
  const contextArgument = getContextArgument(request);
26
- if (analyticsOwnsContext && isContextEnabled(data.options.context) && request.params?.name !== 'get_more_tools' && contextArgument) return {
26
+ if (analyticsOwnsContext && 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.10.5";
1
+ export declare const version = "0.10.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.10.5';
29
+ const version = '0.10.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.10.5';
1
+ const version = '0.10.6';
2
2
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/mcp",
3
- "version": "0.10.5",
3
+ "version": "0.10.6",
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-tooling/tsconfig-base": "1.1.1",
43
- "posthog-node": "^5.47.8"
42
+ "posthog-node": "^5.47.8",
43
+ "@posthog-tooling/tsconfig-base": "1.1.1"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@modelcontextprotocol/sdk": ">=1.26.0",
@@ -108,11 +108,5 @@ export function addContextParameterToTools<TTool extends ContextInjectableTool>(
108
108
  contextDescriptionOverride?: string,
109
109
  logger: LoggerFn = log
110
110
  ): TTool[] {
111
- return tools.map((tool) => {
112
- // Skip get_more_tools - it has its own special context parameter
113
- if (tool.name === 'get_more_tools') {
114
- return tool
115
- }
116
- return addContextParameterToTool(tool, contextDescriptionOverride, logger)
117
- })
111
+ return tools.map((tool) => addContextParameterToTool(tool, contextDescriptionOverride, logger))
118
112
  }
@@ -73,10 +73,11 @@ export function addConversationIdToTool<TTool extends ConversationIdInjectableTo
73
73
  export function addConversationIdToTools<TTool extends ConversationIdInjectableTool>(
74
74
  tools: TTool[],
75
75
  missingCapabilityToolName: string = GET_MORE_TOOLS_NAME,
76
+ skipMissingCapabilityTool = true,
76
77
  logger: LoggerFn = log
77
78
  ): TTool[] {
78
79
  return tools.map((tool) => {
79
- if (tool.name === missingCapabilityToolName) {
80
+ if (skipMissingCapabilityTool && tool.name === missingCapabilityToolName) {
80
81
  return tool
81
82
  }
82
83
  return addConversationIdToTool(tool, logger)
@@ -27,6 +27,7 @@ import {
27
27
  handleListToolsRequest,
28
28
  patchRequestHandlers,
29
29
  captureToolCall,
30
+ isToolAdvertised,
30
31
  readToolMetaCategory,
31
32
  type HandlerPatch,
32
33
  } from './instrumentation'
@@ -162,8 +163,6 @@ function addTracingToToolCallbackInternal(
162
163
  ): RegisteredTool {
163
164
  const originalCallback = getToolFunction(tool)
164
165
  const options = getServerTrackingData(server.server as MCPServerLike)?.options
165
- const missingToolName = resolveMissingCapabilityToolName(options)
166
-
167
166
  if (wrappedCallbacks.has(originalCallback)) {
168
167
  logger(`Tool ${toolName} callback already wrapped, skipping re-wrap`)
169
168
  return tool
@@ -188,16 +187,9 @@ function addTracingToToolCallbackInternal(
188
187
 
189
188
  const inputSchema = getCurrentInputSchema()
190
189
  const cleanedArgs = stripOwnedAnalyticsArguments(args, {
191
- context:
192
- toolName !== missingToolName &&
193
- isContextEnabled(options?.context) &&
194
- analyticsOwnsParameter(inputSchema, 'context'),
195
- conversationId:
196
- toolName !== missingToolName &&
197
- options?.enableConversationId === true &&
198
- analyticsOwnsParameter(inputSchema, 'conversation_id'),
190
+ context: isContextEnabled(options?.context) && analyticsOwnsParameter(inputSchema, 'context'),
191
+ conversationId: options?.enableConversationId === true && analyticsOwnsParameter(inputSchema, 'conversation_id'),
199
192
  })
200
-
201
193
  try {
202
194
  if (cleanedArgs === undefined) {
203
195
  const handler = originalCallback as (extra: CompatibleRequestHandlerExtra) => Promise<CallToolResult>
@@ -242,7 +234,11 @@ async function handleToolCallRequest(
242
234
  return await originalCallToolHandler(request, extra)
243
235
  }
244
236
 
245
- if (request.params?.name === resolveMissingCapabilityToolName(data.options)) {
237
+ const toolName = request.params?.name
238
+ const isMissingCapabilityCandidate =
239
+ data.options.reportMissing && toolName === resolveMissingCapabilityToolName(data.options)
240
+
241
+ if (isMissingCapabilityCandidate && (await isToolAdvertised(server, toolName, extra, data.logger)) === false) {
246
242
  const context = getContextArgument(request) || ''
247
243
  return await captureToolCall({
248
244
  server,
@@ -255,7 +251,6 @@ async function handleToolCallRequest(
255
251
  })
256
252
  }
257
253
 
258
- const toolName = request.params?.name
259
254
  const registeredTool = toolName ? highLevelServer._registeredTools[toolName] : undefined
260
255
 
261
256
  // Strip SDK-owned arguments before the MCP SDK validates the registered Zod
@@ -14,6 +14,7 @@ import {
14
14
  handleListToolsRequest,
15
15
  patchRequestHandlers,
16
16
  captureToolCall,
17
+ isToolAdvertised,
17
18
  type HandlerPatch,
18
19
  } from './instrumentation'
19
20
  import { getContextArgument } from './tracing-helpers'
@@ -32,19 +33,25 @@ type MCPRequestExtra = Parameters<MCPRequestHandler>[1]
32
33
  export function instrumentLowLevelServer(server: MCPServerLike, logger: LoggerFn): void {
33
34
  try {
34
35
  // Patch already existing handlers, and patch setRequestHandler to capture dynamically created handlers.
36
+ const hadCallToolHandler = server._requestHandlers.has('tools/call')
35
37
  const handlers: Record<string, HandlerPatch> = {
36
38
  initialize: (server, originalHandler, request, extra) =>
37
39
  handleInitializeRequest(server, originalHandler, request, extra, logger),
38
40
  'tools/list': (server, originalHandler, request, extra) =>
39
41
  handleListToolsRequest(server, originalHandler, request, extra, logger),
42
+ 'tools/call': (server, originalHandler, request, extra) =>
43
+ handleToolCallRequest(server, originalHandler, request, extra, logger),
40
44
  }
41
45
  patchRequestHandlers(server, handlers)
42
46
 
43
- const originalCallToolHandler = server._requestHandlers.get('tools/call')
44
- server.setRequestHandler(
45
- CallToolRequestSchema,
46
- async (request, extra) => await handleToolCallRequest(server, originalCallToolHandler, request, extra, logger)
47
- )
47
+ if (!hadCallToolHandler) {
48
+ // Register a raw fallback through the patched setter so reportMissing works
49
+ // even before an application dispatcher is attached. A later registration
50
+ // replaces it and is wrapped by patchRequestHandlers.
51
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
52
+ throw new Error(`Unknown tool: ${request.params?.name || 'unknown'}`)
53
+ })
54
+ }
48
55
  } catch (error) {
49
56
  logger(`Warning: Failed to setup tool call instrumentation - ${error}`)
50
57
  throw error
@@ -66,7 +73,11 @@ async function handleToolCallRequest(
66
73
  return await originalCallToolHandler?.(request, extra)
67
74
  }
68
75
 
69
- if (request.params?.name === resolveMissingCapabilityToolName(data.options)) {
76
+ const toolName = request.params?.name
77
+ const isMissingCapabilityCandidate =
78
+ data.options.reportMissing && toolName === resolveMissingCapabilityToolName(data.options)
79
+
80
+ if (isMissingCapabilityCandidate && (await isToolAdvertised(server, toolName, extra, data.logger)) === false) {
70
81
  const context = getContextArgument(request) || ''
71
82
  return await captureToolCall({
72
83
  server,
@@ -98,13 +98,20 @@ export async function captureToolCall(params: TraceToolCallParams): Promise<unkn
98
98
  explicitContextIntent,
99
99
  takeCapturedError,
100
100
  } = params
101
-
102
- const ownership = getActiveAnalyticsParameterOwnership(data, request.params?.name, parameterOwnership)
101
+ const resolvedEventType = eventType ?? MCPAnalyticsEventType.mcpToolsCall
102
+ const ownership = getActiveAnalyticsParameterOwnership(
103
+ data,
104
+ request.params?.name,
105
+ parameterOwnership,
106
+ resolvedEventType === MCPAnalyticsEventType.mcpMissingCapability
107
+ )
103
108
  const conversation = resolveConversationId(
104
109
  ownership.conversationId,
105
110
  request.params?.arguments,
106
111
  request.params?.name,
107
- resolveMissingCapabilityToolName(data.options)
112
+ resolvedEventType === MCPAnalyticsEventType.mcpMissingCapability
113
+ ? resolveMissingCapabilityToolName(data.options)
114
+ : ''
108
115
  )
109
116
  const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership)
110
117
 
@@ -120,7 +127,7 @@ export async function captureToolCall(params: TraceToolCallParams): Promise<unkn
120
127
  startTime,
121
128
  conversation,
122
129
  ownership,
123
- eventType ?? MCPAnalyticsEventType.mcpToolsCall
130
+ resolvedEventType
124
131
  )
125
132
  if (event && explicitContextIntent) {
126
133
  setExplicitContextIntent(event, explicitContextIntent)
@@ -142,10 +149,10 @@ export async function captureToolCall(params: TraceToolCallParams): Promise<unkn
142
149
  function getActiveAnalyticsParameterOwnership(
143
150
  data: MCPAnalyticsData,
144
151
  toolName: string | undefined,
145
- override?: AnalyticsParameterOwnership
152
+ override: AnalyticsParameterOwnership | undefined,
153
+ isMissingCapabilityTool: boolean
146
154
  ): AnalyticsParameterOwnership {
147
155
  const ownership = override ?? (toolName ? data.toolAnalyticsParameterOwnership.get(toolName) : undefined)
148
- const isMissingCapabilityTool = toolName === resolveMissingCapabilityToolName(data.options)
149
156
  return {
150
157
  context: !isMissingCapabilityTool && isContextEnabled(data.options.context) && ownership?.context === true,
151
158
  conversationId:
@@ -307,11 +314,27 @@ export type HandlerPatch = (
307
314
  * that register handlers post-construction work — e.g. `@rekog/mcp-nest` hands a
308
315
  * bare server to instrument() and only then registers its handlers.
309
316
  */
317
+ const originalRequestHandlers = new WeakMap<MCPServerLike, Map<string, MCPRequestHandler>>()
318
+
319
+ function rememberOriginalRequestHandler(
320
+ server: MCPServerLike,
321
+ handlerName: string,
322
+ originalHandler: MCPRequestHandler
323
+ ): void {
324
+ let handlers = originalRequestHandlers.get(server)
325
+ if (!handlers) {
326
+ handlers = new Map()
327
+ originalRequestHandlers.set(server, handlers)
328
+ }
329
+ handlers.set(handlerName, originalHandler)
330
+ }
331
+
310
332
  export function patchRequestHandlers(server: MCPServerLike, patches: Record<string, HandlerPatch>): void {
311
333
  // Monkey patch existing handlers.
312
334
  for (const [handlerName, patch] of Object.entries(patches)) {
313
335
  const originalHandler = server._requestHandlers.get(handlerName)
314
336
  if (originalHandler) {
337
+ rememberOriginalRequestHandler(server, handlerName, originalHandler)
315
338
  server._requestHandlers.set(handlerName, (request, extra) => patch(server, originalHandler, request, extra))
316
339
  }
317
340
  }
@@ -322,14 +345,55 @@ export function patchRequestHandlers(server: MCPServerLike, patches: Record<stri
322
345
  const shape = getObjectShape(requestSchema)
323
346
  const handlerName = shape?.method ? getLiteralValue(shape.method) : undefined
324
347
  const patch = typeof handlerName === 'string' ? patches[handlerName] : undefined
325
- if (!patch) {
348
+ if (!patch || typeof handlerName !== 'string') {
326
349
  return originalSetRequestHandler(requestSchema, originalHandler)
327
350
  }
328
351
 
329
- return originalSetRequestHandler(requestSchema, (request, extra) => patch(server, originalHandler, request, extra))
352
+ // Register first so the MCP SDK's request/result validation stays inside
353
+ // our analytics wrapper, matching handlers that existed before instrument().
354
+ const result = originalSetRequestHandler(requestSchema, originalHandler)
355
+ const registeredHandler = server._requestHandlers.get(handlerName)
356
+ if (registeredHandler) {
357
+ rememberOriginalRequestHandler(server, handlerName, registeredHandler)
358
+ server._requestHandlers.set(handlerName, (request, extra) => patch(server, registeredHandler, request, extra))
359
+ }
360
+ return result
330
361
  }) as MCPServerLike['setRequestHandler']
331
362
  }
332
363
 
364
+ /**
365
+ * Checks the server's raw listing for a real owner of a candidate virtual tool.
366
+ * This does not depend on a previous client request and does not call the
367
+ * instrumented list wrapper, so it neither injects PostHog tools nor captures a
368
+ * synthetic tools/list event. `undefined` fails open to the real dispatcher.
369
+ */
370
+ export async function isToolAdvertised(
371
+ server: MCPServerLike,
372
+ toolName: string,
373
+ extra: CompatibleRequestHandlerExtra | undefined,
374
+ logger: LoggerFn
375
+ ): Promise<boolean | undefined> {
376
+ const listHandler = originalRequestHandlers.get(server)?.get('tools/list')
377
+ if (!listHandler || !server._requestHandlers.has('tools/list')) {
378
+ return undefined
379
+ }
380
+
381
+ try {
382
+ const response = (await listHandler({ method: 'tools/list', params: {} }, extra)) as ListToolsResult
383
+ if (!response || !Array.isArray(response.tools)) {
384
+ return undefined
385
+ }
386
+ // Match the page the current list instrumentation can expose. Pagination
387
+ // passthrough is handled separately from missing-capability ownership.
388
+ return response.tools.some((tool) => tool?.name === toolName)
389
+ } catch (error) {
390
+ logger(
391
+ `Warning: PostHog MCP analytics could not determine whether "${toolName}" is advertised; delegating to the server - ${error}`
392
+ )
393
+ return undefined
394
+ }
395
+ }
396
+
333
397
  /**
334
398
  * Captures each `tools/list` and injects the SDK-managed tools (context
335
399
  * parameter, conversation id, `get_more_tools`) into the returned list.
@@ -414,25 +478,33 @@ async function getTracedToolsList(
414
478
  try {
415
479
  const data = getServerTrackingData(server)
416
480
  const originalResponse = (await originalListToolsHandler(request, extra)) as ListToolsResult
417
- let tools = originalResponse.tools || []
481
+ // Injection must not mutate arrays reused or frozen by the server.
482
+ let tools = [...(originalResponse.tools || [])]
418
483
 
419
484
  if (data) {
420
485
  cacheToolAnalyticsParameterOwnership(data.toolAnalyticsParameterOwnership, tools)
421
486
  }
422
-
423
487
  if (data && isContextEnabled(data.options.context)) {
424
488
  tools = addContextParameterToTools(tools, getContextDescription(data.options.context), data.logger)
425
489
  }
426
490
 
427
- if (data?.options.enableConversationId) {
428
- tools = addConversationIdToTools(tools, resolveMissingCapabilityToolName(data.options), data.logger)
429
- }
430
-
431
- if (data?.options.reportMissing) {
491
+ if (data) {
432
492
  const missingToolName = resolveMissingCapabilityToolName(data.options)
433
- const alreadyPresent = tools.some((tool) => tool?.name === missingToolName)
434
- if (!alreadyPresent) {
435
- tools.push(getReportMissingToolDescriptor(missingToolName))
493
+ let injectedMissingCapabilityTool = false
494
+ if (data.options.reportMissing) {
495
+ const alreadyPresent = tools.some((tool) => tool?.name === missingToolName)
496
+ if (alreadyPresent) {
497
+ data.logger(
498
+ `Warning: Cannot inject missing-capability tool "${missingToolName}" because a real tool already uses that name. The real tool will not be intercepted.`
499
+ )
500
+ } else {
501
+ tools.push(getReportMissingToolDescriptor(missingToolName))
502
+ injectedMissingCapabilityTool = true
503
+ }
504
+ }
505
+
506
+ if (data.options.enableConversationId) {
507
+ tools = addConversationIdToTools(tools, missingToolName, injectedMissingCapabilityTool, data.logger)
436
508
  }
437
509
  }
438
510
 
@@ -56,12 +56,7 @@ export async function resolveToolCallIntent(
56
56
  extra?: CompatibleRequestHandlerExtra
57
57
  ): Promise<ResolvedIntent | null> {
58
58
  const contextArgument = getContextArgument(request)
59
- if (
60
- analyticsOwnsContext &&
61
- isContextEnabled(data.options.context) &&
62
- request.params?.name !== 'get_more_tools' &&
63
- contextArgument
64
- ) {
59
+ if (analyticsOwnsContext && isContextEnabled(data.options.context) && contextArgument) {
65
60
  return { intent: contextArgument, source: 'context_parameter' }
66
61
  }
67
62
 
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '0.10.5'
1
+ export const version = '0.10.6'