@posthog/mcp 0.10.4 → 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 (70) hide show
  1. package/dist/extensions/capture.d.ts +2 -1
  2. package/dist/extensions/capture.d.ts.map +1 -1
  3. package/dist/extensions/capture.js +2 -3
  4. package/dist/extensions/capture.mjs +2 -3
  5. package/dist/extensions/compatibility.d.ts +3 -2
  6. package/dist/extensions/compatibility.d.ts.map +1 -1
  7. package/dist/extensions/compatibility.js +19 -20
  8. package/dist/extensions/compatibility.mjs +15 -16
  9. package/dist/extensions/context-parameters.d.ts +3 -2
  10. package/dist/extensions/context-parameters.d.ts.map +1 -1
  11. package/dist/extensions/context-parameters.js +4 -8
  12. package/dist/extensions/context-parameters.mjs +4 -7
  13. package/dist/extensions/conversation-id.d.ts +3 -2
  14. package/dist/extensions/conversation-id.d.ts.map +1 -1
  15. package/dist/extensions/conversation-id.js +5 -6
  16. package/dist/extensions/conversation-id.mjs +5 -5
  17. package/dist/extensions/instrument-highlevel.d.ts +2 -1
  18. package/dist/extensions/instrument-highlevel.d.ts.map +1 -1
  19. package/dist/extensions/instrument-highlevel.js +29 -30
  20. package/dist/extensions/instrument-highlevel.mjs +30 -31
  21. package/dist/extensions/instrument-lowlevel.d.ts +2 -1
  22. package/dist/extensions/instrument-lowlevel.d.ts.map +1 -1
  23. package/dist/extensions/instrument-lowlevel.js +15 -11
  24. package/dist/extensions/instrument-lowlevel.mjs +16 -12
  25. package/dist/extensions/instrumentation.d.ts +9 -8
  26. package/dist/extensions/instrumentation.d.ts.map +1 -1
  27. package/dist/extensions/instrumentation.js +84 -40
  28. package/dist/extensions/instrumentation.mjs +80 -39
  29. package/dist/extensions/intent.d.ts.map +1 -1
  30. package/dist/extensions/intent.js +2 -3
  31. package/dist/extensions/intent.mjs +2 -3
  32. package/dist/extensions/internal.d.ts.map +1 -1
  33. package/dist/extensions/internal.js +5 -6
  34. package/dist/extensions/internal.mjs +5 -6
  35. package/dist/extensions/logger.d.ts +12 -4
  36. package/dist/extensions/logger.d.ts.map +1 -1
  37. package/dist/extensions/logger.js +12 -3
  38. package/dist/extensions/logger.mjs +10 -4
  39. package/dist/extensions/sink.d.ts +4 -2
  40. package/dist/extensions/sink.d.ts.map +1 -1
  41. package/dist/extensions/sink.js +11 -10
  42. package/dist/extensions/sink.mjs +11 -10
  43. package/dist/extensions/tools.d.ts +2 -1
  44. package/dist/extensions/tools.d.ts.map +1 -1
  45. package/dist/extensions/tools.js +2 -2
  46. package/dist/extensions/tools.mjs +2 -2
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +18 -16
  49. package/dist/index.mjs +19 -17
  50. package/dist/types.d.ts +2 -0
  51. package/dist/types.d.ts.map +1 -1
  52. package/dist/version.d.ts +1 -1
  53. package/dist/version.js +1 -1
  54. package/dist/version.mjs +1 -1
  55. package/package.json +3 -3
  56. package/src/extensions/capture.ts +3 -3
  57. package/src/extensions/compatibility.ts +16 -16
  58. package/src/extensions/context-parameters.ts +8 -12
  59. package/src/extensions/conversation-id.ts +14 -7
  60. package/src/extensions/instrument-highlevel.ts +45 -44
  61. package/src/extensions/instrument-lowlevel.ts +30 -15
  62. package/src/extensions/instrumentation.ts +119 -42
  63. package/src/extensions/intent.ts +2 -8
  64. package/src/extensions/internal.ts +7 -6
  65. package/src/extensions/logger.ts +22 -7
  66. package/src/extensions/sink.ts +17 -12
  67. package/src/extensions/tools.ts +3 -3
  68. package/src/index.ts +21 -19
  69. package/src/types.ts +2 -0
  70. package/src/version.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/mcp",
3
- "version": "0.10.4",
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",
@@ -6,7 +6,7 @@
6
6
  import type { MCPServerLike, McpEvent } from '../types'
7
7
  import { MCPAnalyticsEventType } from './event-types'
8
8
  import { getServerTrackingData } from './internal'
9
- import { log } from './logger'
9
+ import type { LoggerFn } from './logger'
10
10
  import { getSessionInfo } from './session'
11
11
 
12
12
  /**
@@ -19,10 +19,10 @@ import { getSessionInfo } from './session'
19
19
  * Auto-capture callers (tool calls, listings, identify) intentionally ignore the
20
20
  * return value, keeping the tool path isolated from analytics latency/errors.
21
21
  */
22
- export function captureEvent(server: MCPServerLike, eventInput: McpEvent): Promise<void> | undefined {
22
+ export function captureEvent(server: MCPServerLike, eventInput: McpEvent, logger: LoggerFn): Promise<void> | undefined {
23
23
  const data = getServerTrackingData(server)
24
24
  if (!data) {
25
- log('Warning: Server tracking data not found. Event will not be published.')
25
+ logger('Warning: Server tracking data not found. Event will not be published.')
26
26
  return
27
27
  }
28
28
 
@@ -4,7 +4,7 @@
4
4
  // Licensed under the MIT License: https://github.com/agentcathq/agentcat-typescript-sdk/blob/main/LICENSE
5
5
 
6
6
  import type { HighLevelMCPServerLike, MCPServerLike } from '../types'
7
- import { log } from './logger'
7
+ import type { LoggerFn } from './logger'
8
8
 
9
9
  type ServerRecord = Record<string, unknown>
10
10
 
@@ -22,8 +22,8 @@ type ServerRecord = Record<string, unknown>
22
22
  */
23
23
 
24
24
  // Function to log compatibility information
25
- export function logCompatibilityWarning(): void {
26
- log(
25
+ export function logCompatibilityWarning(logger: LoggerFn): void {
26
+ logger(
27
27
  'PostHog MCP analytics SDK Compatibility: This version only supports Model Context Protocol TypeScript SDK v1.11 and above. Please upgrade if using an older version.'
28
28
  )
29
29
  }
@@ -41,9 +41,9 @@ export function isLowLevelServer(server: unknown): server is ServerRecord {
41
41
  }
42
42
 
43
43
  // Type guard function that validates server compatibility and returns typed server
44
- export function isCompatibleServerType(server: unknown): MCPServerLike | HighLevelMCPServerLike {
44
+ export function isCompatibleServerType(server: unknown, logger: LoggerFn): MCPServerLike | HighLevelMCPServerLike {
45
45
  if (!server || typeof server !== 'object') {
46
- logCompatibilityWarning()
46
+ logCompatibilityWarning(logger)
47
47
  throw new Error(
48
48
  "PostHog MCP analytics SDK compatibility error: Server must be an object. Ensure you're using MCP SDK v1.11 or higher."
49
49
  )
@@ -52,13 +52,13 @@ export function isCompatibleServerType(server: unknown): MCPServerLike | HighLev
52
52
  if (isHighLevelServer(server)) {
53
53
  // Validate high-level server requirements
54
54
  if (!server._registeredTools || typeof server._registeredTools !== 'object') {
55
- logCompatibilityWarning()
55
+ logCompatibilityWarning(logger)
56
56
  throw new Error(
57
57
  'PostHog MCP analytics SDK compatibility error: High-level server must have _registeredTools object. This requires MCP SDK v1.11 or higher.'
58
58
  )
59
59
  }
60
60
  if (typeof server.tool !== 'function') {
61
- logCompatibilityWarning()
61
+ logCompatibilityWarning(logger)
62
62
  throw new Error(
63
63
  'PostHog MCP analytics SDK compatibility error: High-level server must have tool() method. This requires MCP SDK v1.11 or higher.'
64
64
  )
@@ -66,19 +66,19 @@ export function isCompatibleServerType(server: unknown): MCPServerLike | HighLev
66
66
 
67
67
  // Validate the underlying low-level server
68
68
  const targetServer = server.server
69
- validateLowLevelServer(targetServer)
69
+ validateLowLevelServer(targetServer, logger)
70
70
 
71
71
  return server as unknown as HighLevelMCPServerLike
72
72
  }
73
73
  // Direct low-level server validation
74
- validateLowLevelServer(server)
74
+ validateLowLevelServer(server, logger)
75
75
  return server as MCPServerLike
76
76
  }
77
77
 
78
78
  // Helper function to validate low-level server requirements
79
- function validateLowLevelServer(server: unknown): void {
79
+ function validateLowLevelServer(server: unknown, logger: LoggerFn): void {
80
80
  if (!server || typeof server !== 'object') {
81
- logCompatibilityWarning()
81
+ logCompatibilityWarning(logger)
82
82
  throw new Error(
83
83
  "PostHog MCP analytics SDK compatibility error: Server must be an object. Ensure you're using MCP SDK v1.11 or higher."
84
84
  )
@@ -87,14 +87,14 @@ function validateLowLevelServer(server: unknown): void {
87
87
  const serverRecord = server as ServerRecord
88
88
 
89
89
  if (typeof serverRecord.setRequestHandler !== 'function') {
90
- logCompatibilityWarning()
90
+ logCompatibilityWarning(logger)
91
91
  throw new Error(
92
92
  'PostHog MCP analytics SDK compatibility error: Server must have a setRequestHandler method. This requires MCP SDK v1.11 or higher.'
93
93
  )
94
94
  }
95
95
 
96
96
  if (!(serverRecord._requestHandlers && serverRecord._requestHandlers instanceof Map)) {
97
- logCompatibilityWarning()
97
+ logCompatibilityWarning(logger)
98
98
  throw new Error(
99
99
  'PostHog MCP analytics SDK compatibility error: Server._requestHandlers is not accessible. This requires MCP SDK v1.11 or higher.'
100
100
  )
@@ -102,14 +102,14 @@ function validateLowLevelServer(server: unknown): void {
102
102
 
103
103
  // Validate that _requestHandlers contains functions with compatible signatures
104
104
  if (typeof serverRecord._requestHandlers.get !== 'function') {
105
- logCompatibilityWarning()
105
+ logCompatibilityWarning(logger)
106
106
  throw new Error(
107
107
  'PostHog MCP analytics SDK compatibility error: Server._requestHandlers must be a Map with a get method. This requires MCP SDK v1.11 or higher.'
108
108
  )
109
109
  }
110
110
 
111
111
  if (typeof serverRecord.getClientVersion !== 'function') {
112
- logCompatibilityWarning()
112
+ logCompatibilityWarning(logger)
113
113
  throw new Error(
114
114
  'PostHog MCP analytics SDK compatibility error: Server.getClientVersion must be a function. This requires MCP SDK v1.11 or higher.'
115
115
  )
@@ -120,7 +120,7 @@ function validateLowLevelServer(server: unknown): void {
120
120
  typeof serverRecord._serverInfo !== 'object' ||
121
121
  !('name' in serverRecord._serverInfo)
122
122
  ) {
123
- logCompatibilityWarning()
123
+ logCompatibilityWarning(logger)
124
124
  throw new Error(
125
125
  'PostHog MCP analytics SDK compatibility error: Server._serverInfo is not accessible or missing name. This requires MCP SDK v1.11 or higher.'
126
126
  )
@@ -10,7 +10,7 @@ import {
10
10
  type AnalyticsInjectableJsonSchema,
11
11
  } from './analytics-parameters'
12
12
  import { DEFAULT_CONTEXT_PARAMETER_DESCRIPTION } from './constants'
13
- import { log } from './logger'
13
+ import { log, type LoggerFn } from './logger'
14
14
 
15
15
  export interface ContextInjectableTool {
16
16
  inputSchema?: AnalyticsInjectableJsonSchema
@@ -38,7 +38,8 @@ export function getContextDescription(context: MCPAnalyticsOptions['context']):
38
38
  */
39
39
  export function addContextParameterToTool<TTool extends ContextInjectableTool>(
40
40
  tool: TTool,
41
- contextDescriptionOverride?: string
41
+ contextDescriptionOverride?: string,
42
+ logger: LoggerFn = log
42
43
  ): TTool {
43
44
  // Create a shallow copy of the tool to avoid modifying the original
44
45
  const modifiedTool = { ...tool }
@@ -47,9 +48,9 @@ export function addContextParameterToTool<TTool extends ContextInjectableTool>(
47
48
 
48
49
  if (!canInjectAnalyticsParameter(schema, 'context')) {
49
50
  if (hasAnalyticsParameter(schema, 'context')) {
50
- log(`WARN: Tool "${toolName}" already has 'context' parameter. Skipping context injection.`)
51
+ logger(`WARN: Tool "${toolName}" already has 'context' parameter. Skipping context injection.`)
51
52
  } else {
52
- log(`WARN: Tool "${toolName}" has complex schema (oneOf/allOf/anyOf/$ref). Skipping context injection.`)
53
+ logger(`WARN: Tool "${toolName}" has complex schema (oneOf/allOf/anyOf/$ref). Skipping context injection.`)
53
54
  }
54
55
  return modifiedTool
55
56
  }
@@ -104,13 +105,8 @@ export function addContextParameterToTool<TTool extends ContextInjectableTool>(
104
105
 
105
106
  export function addContextParameterToTools<TTool extends ContextInjectableTool>(
106
107
  tools: TTool[],
107
- contextDescriptionOverride?: string
108
+ contextDescriptionOverride?: string,
109
+ logger: LoggerFn = log
108
110
  ): TTool[] {
109
- return tools.map((tool) => {
110
- // Skip get_more_tools - it has its own special context parameter
111
- if (tool.name === 'get_more_tools') {
112
- return tool
113
- }
114
- return addContextParameterToTool(tool, contextDescriptionOverride)
115
- })
111
+ return tools.map((tool) => addContextParameterToTool(tool, contextDescriptionOverride, logger))
116
112
  }
@@ -10,7 +10,7 @@ import {
10
10
  type AnalyticsInjectableJsonSchema,
11
11
  } from './analytics-parameters'
12
12
  import { DEFAULT_CONVERSATION_ID_DESCRIPTION } from './constants'
13
- import { log } from './logger'
13
+ import { log, type LoggerFn } from './logger'
14
14
  import { GET_MORE_TOOLS_NAME } from './tools'
15
15
 
16
16
  export const CONVERSATION_ID_PARAM_NAME = 'conversation_id'
@@ -21,18 +21,23 @@ export interface ConversationIdInjectableTool {
21
21
  [key: string]: unknown
22
22
  }
23
23
 
24
- export function addConversationIdToTool<TTool extends ConversationIdInjectableTool>(tool: TTool): TTool {
24
+ export function addConversationIdToTool<TTool extends ConversationIdInjectableTool>(
25
+ tool: TTool,
26
+ logger: LoggerFn = log
27
+ ): TTool {
25
28
  const modifiedTool = { ...tool }
26
29
  const toolName = tool.name || 'unknown'
27
30
  const schema = modifiedTool.inputSchema as AnalyticsInjectableJsonSchema | undefined
28
31
 
29
32
  if (!canInjectAnalyticsParameter(schema, CONVERSATION_ID_PARAM_NAME)) {
30
33
  if (hasAnalyticsParameter(schema, CONVERSATION_ID_PARAM_NAME)) {
31
- log(
34
+ logger(
32
35
  `WARN: Tool "${toolName}" already has '${CONVERSATION_ID_PARAM_NAME}' parameter. Skipping conversation_id injection.`
33
36
  )
34
37
  } else {
35
- log(`WARN: Tool "${toolName}" has complex schema (oneOf/allOf/anyOf/$ref). Skipping conversation_id injection.`)
38
+ logger(
39
+ `WARN: Tool "${toolName}" has complex schema (oneOf/allOf/anyOf/$ref). Skipping conversation_id injection.`
40
+ )
36
41
  }
37
42
  return modifiedTool
38
43
  }
@@ -67,13 +72,15 @@ export function addConversationIdToTool<TTool extends ConversationIdInjectableTo
67
72
 
68
73
  export function addConversationIdToTools<TTool extends ConversationIdInjectableTool>(
69
74
  tools: TTool[],
70
- missingCapabilityToolName: string = GET_MORE_TOOLS_NAME
75
+ missingCapabilityToolName: string = GET_MORE_TOOLS_NAME,
76
+ skipMissingCapabilityTool = true,
77
+ logger: LoggerFn = log
71
78
  ): TTool[] {
72
79
  return tools.map((tool) => {
73
- if (tool.name === missingCapabilityToolName) {
80
+ if (skipMissingCapabilityTool && tool.name === missingCapabilityToolName) {
74
81
  return tool
75
82
  }
76
- return addConversationIdToTool(tool)
83
+ return addConversationIdToTool(tool, logger)
77
84
  })
78
85
  }
79
86
 
@@ -19,7 +19,7 @@ import {
19
19
  import { isContextEnabled } from './context-parameters'
20
20
  import { MCPAnalyticsEventType } from './event-types'
21
21
  import { getServerTrackingData } from './internal'
22
- import { log } from './logger'
22
+ import type { LoggerFn } from './logger'
23
23
  import { createWrappedTool, getToolFunction, hasToolFunction } from './mcp-sdk-compat'
24
24
  import { handleReportMissing, resolveMissingCapabilityToolName } from './tools'
25
25
  import {
@@ -27,7 +27,9 @@ import {
27
27
  handleListToolsRequest,
28
28
  patchRequestHandlers,
29
29
  captureToolCall,
30
+ isToolAdvertised,
30
31
  readToolMetaCategory,
32
+ type HandlerPatch,
31
33
  } from './instrumentation'
32
34
  import { getContextArgument } from './tracing-helpers'
33
35
 
@@ -51,18 +53,22 @@ function isCallbackUpdate(value: unknown): value is { callback: ToolCallback } {
51
53
 
52
54
  function addTracingToToolRegistry(
53
55
  tools: Record<string, RegisteredTool>,
54
- server: HighLevelMCPServerLike
56
+ server: HighLevelMCPServerLike,
57
+ logger: LoggerFn
55
58
  ): Record<string, RegisteredTool> {
56
59
  return Object.fromEntries(
57
- Object.entries(tools).map(([name, tool]) => [name, addTracingToToolCallbackInternal(tool, name, server)])
60
+ Object.entries(tools).map(([name, tool]) => [
61
+ name,
62
+ addTracingToToolCallbackInternal(tool, name, server, () => tool.inputSchema, logger),
63
+ ])
58
64
  )
59
65
  }
60
66
 
61
- function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
67
+ function setupListenerToRegisteredTools(server: HighLevelMCPServerLike, logger: LoggerFn): void {
62
68
  try {
63
69
  const data = getServerTrackingData(server.server as MCPServerLike)
64
70
  if (!data) {
65
- log('Warning: Cannot setup listener - no tracking data found')
71
+ logger('Warning: Cannot setup listener - no tracking data found')
66
72
  return
67
73
  }
68
74
 
@@ -74,19 +80,19 @@ function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
74
80
  data.toolDescriptions.set(property, value.description)
75
81
  }
76
82
  if ((value as ProcessedRegisteredTool)[MCP_ANALYTICS_PROCESSED]) {
77
- log(`Tool ${String(property)} already processed, skipping proxy wrapping`)
83
+ logger(`Tool ${String(property)} already processed, skipping proxy wrapping`)
78
84
  return Reflect.set(target, property, value)
79
85
  }
80
86
 
81
87
  if (wrappedCallbacks.has(getToolFunction(value))) {
82
- log(`Tool ${String(property)} callback already wrapped, skipping proxy wrapping`)
88
+ logger(`Tool ${String(property)} callback already wrapped, skipping proxy wrapping`)
83
89
  return Reflect.set(target, property, value)
84
90
  }
85
91
 
86
92
  // The MCP SDK's registry copy is currently stale after update(), but keep this lookup lazy so
87
93
  // ownership follows the current schema once the registry reflects live tool state.
88
94
  const getCurrentInputSchema = () => value.inputSchema
89
- const nextValue = addTracingToToolCallbackInternal(value, property, server, getCurrentInputSchema)
95
+ const nextValue = addTracingToToolCallbackInternal(value, property, server, getCurrentInputSchema, logger)
90
96
 
91
97
  if (typeof nextValue.update === 'function') {
92
98
  const originalUpdate = nextValue.update
@@ -98,7 +104,8 @@ function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
98
104
  { callback: updateObj.callback },
99
105
  property,
100
106
  server,
101
- getCurrentInputSchema
107
+ getCurrentInputSchema,
108
+ logger
102
109
  )
103
110
  updateObj.callback = getToolFunction(wrappedTool)
104
111
  }
@@ -111,7 +118,7 @@ function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
111
118
 
112
119
  return Reflect.set(target, property, value)
113
120
  } catch (error) {
114
- log(`Warning: Error in proxy set handler for tool ${String(property)} - ${error}`)
121
+ logger(`Warning: Error in proxy set handler for tool ${String(property)} - ${error}`)
115
122
  return Reflect.set(target, property, value)
116
123
  }
117
124
  },
@@ -132,9 +139,9 @@ function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
132
139
  const originalTools = server._registeredTools || {}
133
140
  server._registeredTools = new Proxy(originalTools, handler)
134
141
 
135
- log('Successfully set up listener for new tool registrations')
142
+ logger('Successfully set up listener for new tool registrations')
136
143
  } catch (error) {
137
- log(`Warning: Failed to setup listener for registered tools - ${error}`)
144
+ logger(`Warning: Failed to setup listener for registered tools - ${error}`)
138
145
  }
139
146
  }
140
147
 
@@ -151,19 +158,18 @@ function addTracingToToolCallbackInternal(
151
158
  tool: RegisteredTool,
152
159
  toolName: string,
153
160
  server: HighLevelMCPServerLike,
154
- getCurrentInputSchema: () => unknown = () => tool.inputSchema
161
+ getCurrentInputSchema: () => unknown,
162
+ logger: LoggerFn
155
163
  ): RegisteredTool {
156
164
  const originalCallback = getToolFunction(tool)
157
165
  const options = getServerTrackingData(server.server as MCPServerLike)?.options
158
- const missingToolName = resolveMissingCapabilityToolName(options)
159
-
160
166
  if (wrappedCallbacks.has(originalCallback)) {
161
- log(`Tool ${toolName} callback already wrapped, skipping re-wrap`)
167
+ logger(`Tool ${toolName} callback already wrapped, skipping re-wrap`)
162
168
  return tool
163
169
  }
164
170
 
165
171
  if ((tool as ProcessedRegisteredTool)[MCP_ANALYTICS_PROCESSED]) {
166
- log(`Tool ${toolName} already processed, skipping re-wrap`)
172
+ logger(`Tool ${toolName} already processed, skipping re-wrap`)
167
173
  return tool
168
174
  }
169
175
 
@@ -181,16 +187,9 @@ function addTracingToToolCallbackInternal(
181
187
 
182
188
  const inputSchema = getCurrentInputSchema()
183
189
  const cleanedArgs = stripOwnedAnalyticsArguments(args, {
184
- context:
185
- toolName !== missingToolName &&
186
- isContextEnabled(options?.context) &&
187
- analyticsOwnsParameter(inputSchema, 'context'),
188
- conversationId:
189
- toolName !== missingToolName &&
190
- options?.enableConversationId === true &&
191
- analyticsOwnsParameter(inputSchema, 'conversation_id'),
190
+ context: isContextEnabled(options?.context) && analyticsOwnsParameter(inputSchema, 'context'),
191
+ conversationId: options?.enableConversationId === true && analyticsOwnsParameter(inputSchema, 'conversation_id'),
192
192
  })
193
-
194
193
  try {
195
194
  if (cleanedArgs === undefined) {
196
195
  const handler = originalCallback as (extra: CompatibleRequestHandlerExtra) => Promise<CallToolResult>
@@ -224,17 +223,22 @@ async function handleToolCallRequest(
224
223
  server: MCPServerLike,
225
224
  originalCallToolHandler: MCPRequestHandler,
226
225
  request: MCPRequest,
227
- extra: MCPRequestExtra
226
+ extra: MCPRequestExtra,
227
+ logger: LoggerFn
228
228
  ): Promise<unknown> {
229
229
  const data = getServerTrackingData(server)
230
230
  if (!data) {
231
- log(
231
+ logger(
232
232
  'Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.'
233
233
  )
234
234
  return await originalCallToolHandler(request, extra)
235
235
  }
236
236
 
237
- 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) {
238
242
  const context = getContextArgument(request) || ''
239
243
  return await captureToolCall({
240
244
  server,
@@ -243,11 +247,10 @@ async function handleToolCallRequest(
243
247
  extra,
244
248
  eventType: MCPAnalyticsEventType.mcpMissingCapability,
245
249
  explicitContextIntent: context,
246
- execute: async () => handleReportMissing({ context }),
250
+ execute: async () => handleReportMissing({ context }, data.logger),
247
251
  })
248
252
  }
249
253
 
250
- const toolName = request.params?.name
251
254
  const registeredTool = toolName ? highLevelServer._registeredTools[toolName] : undefined
252
255
 
253
256
  // Strip SDK-owned arguments before the MCP SDK validates the registered Zod
@@ -270,34 +273,32 @@ async function handleToolCallRequest(
270
273
  })
271
274
  }
272
275
 
273
- export function instrumentHighLevelServer(server: HighLevelMCPServerLike): void {
276
+ export function instrumentHighLevelServer(server: HighLevelMCPServerLike, logger: LoggerFn): void {
274
277
  try {
275
278
  const lowLevelServer = server.server
276
279
  const mcpAnalyticsData = getServerTrackingData(lowLevelServer)
277
280
 
278
281
  // Patch already existing handlers, and patch setRequestHandler to capture dynamically created handlers.
279
- const handlers = {
280
- initialize: handleInitializeRequest,
281
- 'tools/list': handleListToolsRequest,
282
- 'tools/call': (
283
- trackedServer: MCPServerLike,
284
- originalHandler: MCPRequestHandler,
285
- request: MCPRequest,
286
- extra: MCPRequestExtra
287
- ) => handleToolCallRequest(server, trackedServer, originalHandler, request, extra),
282
+ const handlers: Record<string, HandlerPatch> = {
283
+ initialize: (trackedServer, originalHandler, request, extra) =>
284
+ handleInitializeRequest(trackedServer, originalHandler, request, extra, logger),
285
+ 'tools/list': (trackedServer, originalHandler, request, extra) =>
286
+ handleListToolsRequest(trackedServer, originalHandler, request, extra, logger),
287
+ 'tools/call': (trackedServer, originalHandler, request, extra) =>
288
+ handleToolCallRequest(server, trackedServer, originalHandler, request, extra, logger),
288
289
  }
289
290
  patchRequestHandlers(lowLevelServer, handlers)
290
291
 
291
- server._registeredTools = addTracingToToolRegistry(server._registeredTools, server)
292
+ server._registeredTools = addTracingToToolRegistry(server._registeredTools, server, logger)
292
293
 
293
294
  if (mcpAnalyticsData) {
294
295
  seedToolDescriptionsFromRegistry(mcpAnalyticsData.toolDescriptions, server._registeredTools)
295
296
  seedToolCategoriesFromRegistry(mcpAnalyticsData.toolCategories, server._registeredTools)
296
297
  }
297
298
 
298
- setupListenerToRegisteredTools(server)
299
+ setupListenerToRegisteredTools(server, logger)
299
300
  } catch (error) {
300
- log(`Warning: Failed to setup tool call instrumentation - ${error}`)
301
+ logger(`Warning: Failed to setup tool call instrumentation - ${error}`)
301
302
  }
302
303
  }
303
304
 
@@ -7,13 +7,15 @@ import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'
7
7
  import type { CompatibleRequestHandlerExtra, MCPRequestLike, MCPServerLike } from '../types'
8
8
  import { MCPAnalyticsEventType } from './event-types'
9
9
  import { getServerTrackingData } from './internal'
10
- import { log } from './logger'
10
+ import type { LoggerFn } from './logger'
11
11
  import { handleReportMissing, resolveMissingCapabilityToolName } from './tools'
12
12
  import {
13
13
  handleInitializeRequest,
14
14
  handleListToolsRequest,
15
15
  patchRequestHandlers,
16
16
  captureToolCall,
17
+ isToolAdvertised,
18
+ type HandlerPatch,
17
19
  } from './instrumentation'
18
20
  import { getContextArgument } from './tracing-helpers'
19
21
 
@@ -28,22 +30,30 @@ type MCPRequestExtra = Parameters<MCPRequestHandler>[1]
28
30
  * `tools/call`. The tool-call lifecycle is delegated to {@link captureToolCall},
29
31
  * shared with the high-level wrapper.
30
32
  */
31
- export function instrumentLowLevelServer(server: MCPServerLike): void {
33
+ export function instrumentLowLevelServer(server: MCPServerLike, logger: LoggerFn): void {
32
34
  try {
33
35
  // Patch already existing handlers, and patch setRequestHandler to capture dynamically created handlers.
34
- const handlers = {
35
- initialize: handleInitializeRequest,
36
- 'tools/list': handleListToolsRequest,
36
+ const hadCallToolHandler = server._requestHandlers.has('tools/call')
37
+ const handlers: Record<string, HandlerPatch> = {
38
+ initialize: (server, originalHandler, request, extra) =>
39
+ handleInitializeRequest(server, originalHandler, request, extra, logger),
40
+ 'tools/list': (server, originalHandler, request, extra) =>
41
+ handleListToolsRequest(server, originalHandler, request, extra, logger),
42
+ 'tools/call': (server, originalHandler, request, extra) =>
43
+ handleToolCallRequest(server, originalHandler, request, extra, logger),
37
44
  }
38
45
  patchRequestHandlers(server, handlers)
39
46
 
40
- const originalCallToolHandler = server._requestHandlers.get('tools/call')
41
- server.setRequestHandler(
42
- CallToolRequestSchema,
43
- async (request, extra) => await handleToolCallRequest(server, originalCallToolHandler, request, extra)
44
- )
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
+ }
45
55
  } catch (error) {
46
- log(`Warning: Failed to setup tool call instrumentation - ${error}`)
56
+ logger(`Warning: Failed to setup tool call instrumentation - ${error}`)
47
57
  throw error
48
58
  }
49
59
  }
@@ -52,17 +62,22 @@ async function handleToolCallRequest(
52
62
  server: MCPServerLike,
53
63
  originalCallToolHandler: MCPRequestHandler | undefined,
54
64
  request: MCPRequest,
55
- extra: MCPRequestExtra
65
+ extra: MCPRequestExtra,
66
+ logger: LoggerFn
56
67
  ): Promise<unknown> {
57
68
  const data = getServerTrackingData(server)
58
69
  if (!data) {
59
- log(
70
+ logger(
60
71
  'Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.'
61
72
  )
62
73
  return await originalCallToolHandler?.(request, extra)
63
74
  }
64
75
 
65
- 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) {
66
81
  const context = getContextArgument(request) || ''
67
82
  return await captureToolCall({
68
83
  server,
@@ -71,7 +86,7 @@ async function handleToolCallRequest(
71
86
  extra,
72
87
  eventType: MCPAnalyticsEventType.mcpMissingCapability,
73
88
  explicitContextIntent: context,
74
- execute: async () => handleReportMissing({ context }),
89
+ execute: async () => handleReportMissing({ context }, data.logger),
75
90
  })
76
91
  }
77
92