@posthog/mcp 0.0.9 → 0.1.17

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 (138) hide show
  1. package/LICENSE +307 -3
  2. package/README.md +182 -90
  3. package/dist/extensions/capture.d.ts +13 -0
  4. package/dist/extensions/capture.d.ts.map +1 -0
  5. package/dist/extensions/capture.js +80 -0
  6. package/dist/extensions/capture.mjs +46 -0
  7. package/dist/extensions/compatibility.d.ts +23 -0
  8. package/dist/extensions/compatibility.d.ts.map +1 -0
  9. package/dist/extensions/compatibility.js +113 -0
  10. package/dist/extensions/compatibility.mjs +67 -0
  11. package/dist/extensions/constants.d.ts +39 -0
  12. package/dist/extensions/constants.d.ts.map +1 -0
  13. package/dist/extensions/constants.js +86 -0
  14. package/dist/extensions/constants.mjs +37 -0
  15. package/dist/extensions/context-parameters.d.ts +31 -0
  16. package/dist/extensions/context-parameters.d.ts.map +1 -0
  17. package/dist/extensions/context-parameters.js +93 -0
  18. package/dist/extensions/context-parameters.mjs +50 -0
  19. package/dist/extensions/conversation-id.d.ts +52 -0
  20. package/dist/extensions/conversation-id.d.ts.map +1 -0
  21. package/dist/extensions/conversation-id.js +164 -0
  22. package/dist/extensions/conversation-id.mjs +103 -0
  23. package/dist/extensions/event-types.d.ts +21 -0
  24. package/dist/extensions/event-types.d.ts.map +1 -0
  25. package/dist/extensions/event-types.js +47 -0
  26. package/dist/extensions/event-types.mjs +13 -0
  27. package/dist/extensions/exceptions.d.ts +13 -0
  28. package/dist/extensions/exceptions.d.ts.map +1 -0
  29. package/dist/extensions/exceptions.js +58 -0
  30. package/dist/extensions/exceptions.mjs +24 -0
  31. package/dist/extensions/ids.d.ts +13 -0
  32. package/dist/extensions/ids.d.ts.map +1 -0
  33. package/dist/extensions/ids.js +55 -0
  34. package/dist/extensions/ids.mjs +18 -0
  35. package/dist/extensions/instrument-highlevel.d.ts +3 -0
  36. package/dist/extensions/instrument-highlevel.d.ts.map +1 -0
  37. package/dist/extensions/instrument-highlevel.js +229 -0
  38. package/dist/extensions/instrument-highlevel.mjs +195 -0
  39. package/dist/extensions/instrument-lowlevel.d.ts +8 -0
  40. package/dist/extensions/instrument-lowlevel.d.ts.map +1 -0
  41. package/dist/extensions/instrument-lowlevel.js +85 -0
  42. package/dist/extensions/instrument-lowlevel.mjs +51 -0
  43. package/dist/extensions/instrumentation.d.ts +59 -0
  44. package/dist/extensions/instrumentation.d.ts.map +1 -0
  45. package/dist/extensions/instrumentation.js +237 -0
  46. package/dist/extensions/instrumentation.mjs +194 -0
  47. package/dist/extensions/intent.d.ts +10 -0
  48. package/dist/extensions/intent.d.ts.map +1 -0
  49. package/dist/extensions/intent.js +84 -0
  50. package/dist/extensions/intent.mjs +44 -0
  51. package/dist/extensions/internal.d.ts +32 -0
  52. package/dist/extensions/internal.d.ts.map +1 -0
  53. package/dist/extensions/internal.js +158 -0
  54. package/dist/extensions/internal.mjs +106 -0
  55. package/dist/extensions/logger.d.ts +14 -0
  56. package/dist/extensions/logger.d.ts.map +1 -0
  57. package/dist/extensions/logger.js +47 -0
  58. package/dist/extensions/logger.mjs +10 -0
  59. package/dist/extensions/mcp-payloads.d.ts +5 -0
  60. package/dist/extensions/mcp-payloads.d.ts.map +1 -0
  61. package/dist/extensions/mcp-payloads.js +91 -0
  62. package/dist/extensions/mcp-payloads.mjs +54 -0
  63. package/dist/extensions/mcp-sdk-compat.d.ts +33 -0
  64. package/dist/extensions/mcp-sdk-compat.d.ts.map +1 -0
  65. package/dist/extensions/mcp-sdk-compat.js +112 -0
  66. package/dist/extensions/mcp-sdk-compat.mjs +60 -0
  67. package/dist/extensions/posthog-events.d.ts +14 -0
  68. package/dist/extensions/posthog-events.d.ts.map +1 -0
  69. package/dist/extensions/posthog-events.js +143 -0
  70. package/dist/extensions/posthog-events.mjs +109 -0
  71. package/dist/extensions/sanitization.d.ts +10 -0
  72. package/dist/extensions/sanitization.d.ts.map +1 -0
  73. package/dist/extensions/sanitization.js +96 -0
  74. package/dist/extensions/sanitization.mjs +62 -0
  75. package/dist/extensions/session.d.ts +20 -0
  76. package/dist/extensions/session.d.ts.map +1 -0
  77. package/dist/extensions/session.js +113 -0
  78. package/dist/extensions/session.mjs +67 -0
  79. package/dist/extensions/sink.d.ts +44 -0
  80. package/dist/extensions/sink.d.ts.map +1 -0
  81. package/dist/extensions/sink.js +102 -0
  82. package/dist/extensions/sink.mjs +65 -0
  83. package/dist/extensions/tools.d.ts +9 -0
  84. package/dist/extensions/tools.d.ts.map +1 -0
  85. package/dist/extensions/tools.js +79 -0
  86. package/dist/extensions/tools.mjs +39 -0
  87. package/dist/extensions/tracing-helpers.d.ts +29 -0
  88. package/dist/extensions/tracing-helpers.d.ts.map +1 -0
  89. package/dist/extensions/tracing-helpers.js +59 -0
  90. package/dist/extensions/tracing-helpers.mjs +16 -0
  91. package/dist/extensions/truncation.d.ts +25 -0
  92. package/dist/extensions/truncation.d.ts.map +1 -0
  93. package/dist/extensions/truncation.js +277 -0
  94. package/dist/extensions/truncation.mjs +228 -0
  95. package/dist/index.d.ts +36 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +132 -0
  98. package/dist/index.mjs +83 -2929
  99. package/dist/types.d.ts +220 -0
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +18 -0
  102. package/dist/types.mjs +0 -0
  103. package/dist/version.d.ts +2 -0
  104. package/dist/version.d.ts.map +1 -0
  105. package/dist/version.js +36 -0
  106. package/dist/version.mjs +2 -0
  107. package/package.json +55 -87
  108. package/src/extensions/capture.ts +68 -0
  109. package/src/extensions/compatibility.ts +138 -0
  110. package/src/extensions/constants.ts +48 -0
  111. package/src/extensions/context-parameters.ts +119 -0
  112. package/src/extensions/conversation-id.ts +177 -0
  113. package/src/extensions/event-types.ts +21 -0
  114. package/src/extensions/exceptions.ts +83 -0
  115. package/src/extensions/ids.ts +32 -0
  116. package/src/extensions/instrument-highlevel.ts +297 -0
  117. package/src/extensions/instrument-lowlevel.ts +83 -0
  118. package/src/extensions/instrumentation.ts +415 -0
  119. package/src/extensions/intent.ts +78 -0
  120. package/src/extensions/internal.ts +193 -0
  121. package/src/extensions/logger.ts +27 -0
  122. package/src/extensions/mcp-payloads.ts +98 -0
  123. package/src/extensions/mcp-sdk-compat.ts +160 -0
  124. package/src/extensions/posthog-events.ts +212 -0
  125. package/src/extensions/sanitization.ts +121 -0
  126. package/src/extensions/session.ts +117 -0
  127. package/src/extensions/sink.ts +130 -0
  128. package/src/extensions/tools.ts +50 -0
  129. package/src/extensions/tracing-helpers.ts +51 -0
  130. package/src/extensions/truncation.ts +453 -0
  131. package/src/index.ts +174 -0
  132. package/src/types.ts +244 -0
  133. package/src/version.ts +1 -0
  134. package/dist/index.cjs +0 -2937
  135. package/dist/index.cjs.map +0 -1
  136. package/dist/index.d.cts +0 -264
  137. package/dist/index.d.mts +0 -263
  138. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,297 @@
1
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
2
+ import type { CompatibleRequestHandlerExtra, HighLevelMCPServerLike, MCPServerLike, RegisteredTool } from '../types'
3
+ import { stripConversationId } from './conversation-id'
4
+ import { MCPAnalyticsEventType } from './event-types'
5
+ import { getServerTrackingData } from './internal'
6
+ import { log } from './logger'
7
+ import { createWrappedTool, getLiteralValue, getObjectShape, getToolFunction, hasToolFunction } from './mcp-sdk-compat'
8
+ import { GET_MORE_TOOLS_NAME, handleReportMissing } from './tools'
9
+ import { instrumentInitializeHandler, instrumentToolsListHandler, captureToolCall } from './instrumentation'
10
+ import { getContextArgument } from './tracing-helpers'
11
+
12
+ type MCPRequestHandler = NonNullable<
13
+ MCPServerLike['_requestHandlers'] extends Map<string, infer THandler> ? THandler : never
14
+ >
15
+ type MCPRequest = Parameters<MCPRequestHandler>[0]
16
+ type MCPRequestExtra = Parameters<MCPRequestHandler>[1]
17
+
18
+ const wrappedCallbacks = new WeakMap<object, boolean>()
19
+
20
+ const MCP_ANALYTICS_PROCESSED = Symbol('__posthog_mcp_analytics_processed__')
21
+
22
+ type ProcessedRegisteredTool = RegisteredTool & {
23
+ [MCP_ANALYTICS_PROCESSED]?: boolean
24
+ }
25
+
26
+ function isCallbackUpdate(value: unknown): value is { callback: unknown } {
27
+ return !!value && typeof value === 'object' && 'callback' in value && typeof value.callback === 'function'
28
+ }
29
+
30
+ function addTracingToToolRegistry(
31
+ tools: Record<string, RegisteredTool>,
32
+ server: HighLevelMCPServerLike
33
+ ): Record<string, RegisteredTool> {
34
+ return Object.fromEntries(
35
+ Object.entries(tools).map(([name, tool]) => [name, addTracingToToolCallbackInternal(tool, name, server)])
36
+ )
37
+ }
38
+
39
+ function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
40
+ try {
41
+ const data = getServerTrackingData(server.server as MCPServerLike)
42
+ if (!data) {
43
+ log('Warning: Cannot setup listener - no tracking data found')
44
+ return
45
+ }
46
+
47
+ const handler: ProxyHandler<Record<string, RegisteredTool>> = {
48
+ set(target: Record<string, RegisteredTool>, property: string | symbol, value: RegisteredTool): boolean {
49
+ try {
50
+ if (typeof property === 'string' && value && typeof value === 'object' && hasToolFunction(value)) {
51
+ if (typeof value.description === 'string') {
52
+ data.toolDescriptions.set(property, value.description)
53
+ }
54
+ if ((value as ProcessedRegisteredTool)[MCP_ANALYTICS_PROCESSED]) {
55
+ log(`Tool ${String(property)} already processed, skipping proxy wrapping`)
56
+ return Reflect.set(target, property, value)
57
+ }
58
+
59
+ if (wrappedCallbacks.has(getToolFunction(value))) {
60
+ log(`Tool ${String(property)} callback already wrapped, skipping proxy wrapping`)
61
+ return Reflect.set(target, property, value)
62
+ }
63
+
64
+ const nextValue = addTracingToToolCallbackInternal(value, property, server)
65
+
66
+ instrumentToolsListHandler(server.server as MCPServerLike)
67
+
68
+ if (typeof nextValue.update === 'function') {
69
+ const originalUpdate = nextValue.update
70
+ nextValue.update = function (...updateArgs: unknown[]) {
71
+ if (updateArgs[0]) {
72
+ const updateObj = updateArgs[0]
73
+ if (isCallbackUpdate(updateObj)) {
74
+ const wrappedTool = addTracingToToolCallbackInternal(
75
+ { callback: updateObj.callback } as RegisteredTool,
76
+ property,
77
+ server
78
+ )
79
+ updateObj.callback = getToolFunction(wrappedTool)
80
+ }
81
+ }
82
+ return originalUpdate.apply(this, updateArgs)
83
+ }
84
+ }
85
+ return Reflect.set(target, property, nextValue)
86
+ }
87
+
88
+ return Reflect.set(target, property, value)
89
+ } catch (error) {
90
+ log(`Warning: Error in proxy set handler for tool ${String(property)} - ${error}`)
91
+ return Reflect.set(target, property, value)
92
+ }
93
+ },
94
+
95
+ get(target: Record<string, RegisteredTool>, property: string | symbol): unknown {
96
+ return Reflect.get(target, property)
97
+ },
98
+
99
+ deleteProperty(target: Record<string, RegisteredTool>, property: string | symbol): boolean {
100
+ return Reflect.deleteProperty(target, property)
101
+ },
102
+
103
+ has(target: Record<string, RegisteredTool>, property: string | symbol): boolean {
104
+ return Reflect.has(target, property)
105
+ },
106
+ }
107
+
108
+ const originalTools = server._registeredTools || {}
109
+ server._registeredTools = new Proxy(originalTools, handler)
110
+
111
+ log('Successfully set up listener for new tool registrations')
112
+ } catch (error) {
113
+ log(`Warning: Failed to setup listener for registered tools - ${error}`)
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Wraps a registered tool's callback so the SDK-injected `context` and
119
+ * `conversation_id` arguments are stripped before the tool sees them, and any
120
+ * thrown error is stashed on `extra` for the request-handler wrapper to capture
121
+ * (the high-level SDK turns thrown errors into `isError` results otherwise).
122
+ *
123
+ * This is purely the tool-facing concern; event capture lives in
124
+ * {@link captureToolCall} via {@link handleWrappedToolsCall}.
125
+ */
126
+ function addTracingToToolCallbackInternal(
127
+ tool: RegisteredTool,
128
+ toolName: string,
129
+ _server: HighLevelMCPServerLike
130
+ ): RegisteredTool {
131
+ const originalCallback = getToolFunction(tool)
132
+
133
+ if (wrappedCallbacks.has(originalCallback)) {
134
+ log(`Tool ${toolName} callback already wrapped, skipping re-wrap`)
135
+ return tool
136
+ }
137
+
138
+ if ((tool as ProcessedRegisteredTool)[MCP_ANALYTICS_PROCESSED]) {
139
+ log(`Tool ${toolName} already processed, skipping re-wrap`)
140
+ return tool
141
+ }
142
+
143
+ const wrappedCallback = async (...params: unknown[]): Promise<CallToolResult> => {
144
+ let args: unknown
145
+ let extra: CompatibleRequestHandlerExtra
146
+
147
+ if (params.length === 2) {
148
+ args = params[0]
149
+ extra = params[1] as CompatibleRequestHandlerExtra
150
+ } else {
151
+ args = undefined
152
+ extra = params[0] as CompatibleRequestHandlerExtra
153
+ }
154
+
155
+ const removeContextFromArgs = (input: unknown): unknown => {
156
+ if (input && typeof input === 'object' && 'context' in input) {
157
+ const { context: _context, ...rest } = input
158
+ return rest
159
+ }
160
+ return input
161
+ }
162
+
163
+ const cleanedArgs = toolName === GET_MORE_TOOLS_NAME ? args : stripConversationId(removeContextFromArgs(args))
164
+
165
+ try {
166
+ if (cleanedArgs === undefined) {
167
+ const handler = originalCallback as (extra: CompatibleRequestHandlerExtra) => Promise<CallToolResult>
168
+ return await handler(extra)
169
+ }
170
+ const handler = originalCallback as (
171
+ args: unknown,
172
+ extra: CompatibleRequestHandlerExtra
173
+ ) => Promise<CallToolResult>
174
+ return await handler(cleanedArgs, extra)
175
+ } catch (error) {
176
+ if (error instanceof Error) {
177
+ extra.__mcp_analytics_error = error
178
+ }
179
+ throw error
180
+ }
181
+ }
182
+
183
+ wrappedCallbacks.set(originalCallback, true)
184
+ wrappedCallbacks.set(wrappedCallback, true)
185
+
186
+ const wrappedTool = createWrappedTool(tool, wrappedCallback)
187
+
188
+ ;(wrappedTool as ProcessedRegisteredTool)[MCP_ANALYTICS_PROCESSED] = true
189
+
190
+ return wrappedTool
191
+ }
192
+
193
+ function setupToolsCallHandlerWrapping(server: HighLevelMCPServerLike): void {
194
+ const lowLevelServer = server.server as MCPServerLike
195
+
196
+ const existingHandler = lowLevelServer._requestHandlers.get('tools/call')
197
+ if (existingHandler) {
198
+ const wrappedHandler = createToolsCallWrapper(existingHandler, lowLevelServer)
199
+ lowLevelServer._requestHandlers.set('tools/call', wrappedHandler)
200
+ }
201
+
202
+ const originalSetRequestHandler = lowLevelServer.setRequestHandler.bind(lowLevelServer)
203
+
204
+ lowLevelServer.setRequestHandler = ((requestSchema: unknown, handler: MCPRequestHandler) => {
205
+ const shape = getObjectShape(requestSchema)
206
+ const method = shape?.method ? getLiteralValue(shape.method) : undefined
207
+
208
+ if (method === 'tools/call') {
209
+ const wrappedHandler = createToolsCallWrapper(handler, lowLevelServer)
210
+ return originalSetRequestHandler(requestSchema, wrappedHandler)
211
+ }
212
+
213
+ return originalSetRequestHandler(requestSchema, handler)
214
+ }) as MCPServerLike['setRequestHandler']
215
+ }
216
+
217
+ function createToolsCallWrapper(originalHandler: MCPRequestHandler, server: MCPServerLike): MCPRequestHandler {
218
+ return async (request: MCPRequest, extra: MCPRequestExtra) =>
219
+ await handleWrappedToolsCall(originalHandler, server, request, extra)
220
+ }
221
+
222
+ async function handleWrappedToolsCall(
223
+ originalHandler: MCPRequestHandler,
224
+ server: MCPServerLike,
225
+ request: MCPRequest,
226
+ extra: MCPRequestExtra
227
+ ): Promise<unknown> {
228
+ const data = getServerTrackingData(server)
229
+ if (!data) {
230
+ log(
231
+ 'Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.'
232
+ )
233
+ return await originalHandler(request, extra)
234
+ }
235
+
236
+ if (request.params?.name === GET_MORE_TOOLS_NAME) {
237
+ const context = getContextArgument(request) || ''
238
+ return await captureToolCall({
239
+ server,
240
+ data,
241
+ request,
242
+ extra,
243
+ eventType: MCPAnalyticsEventType.mcpMissingCapability,
244
+ explicitContextIntent: context,
245
+ execute: async () => handleReportMissing({ context }),
246
+ })
247
+ }
248
+
249
+ // The high-level handler re-derives arguments from the original request and
250
+ // strips the injected params inside the wrapped callback, so we hand it the
251
+ // original request rather than the conversation-stripped one. Errors thrown
252
+ // by the tool are stashed on `extra` by the callback wrapper; surface them.
253
+ return await captureToolCall({
254
+ server,
255
+ data,
256
+ request,
257
+ extra,
258
+ execute: () => originalHandler(request, extra),
259
+ takeCapturedError: () => {
260
+ const captured = extra?.__mcp_analytics_error
261
+ if (extra) {
262
+ extra.__mcp_analytics_error = undefined
263
+ }
264
+ return captured
265
+ },
266
+ })
267
+ }
268
+
269
+ export function instrumentHighLevelServer(server: HighLevelMCPServerLike): void {
270
+ try {
271
+ const mcpAnalyticsData = getServerTrackingData(server.server)
272
+
273
+ setupToolsCallHandlerWrapping(server)
274
+
275
+ instrumentInitializeHandler(server.server)
276
+
277
+ server._registeredTools = addTracingToToolRegistry(server._registeredTools, server)
278
+
279
+ if (mcpAnalyticsData) {
280
+ seedToolDescriptionsFromRegistry(mcpAnalyticsData.toolDescriptions, server._registeredTools)
281
+ }
282
+
283
+ instrumentToolsListHandler(server.server)
284
+
285
+ setupListenerToRegisteredTools(server)
286
+ } catch (error) {
287
+ log(`Warning: Failed to setup tool call instrumentation - ${error}`)
288
+ }
289
+ }
290
+
291
+ function seedToolDescriptionsFromRegistry(cache: Map<string, string>, tools: Record<string, RegisteredTool>): void {
292
+ for (const [name, tool] of Object.entries(tools)) {
293
+ if (typeof tool?.description === 'string') {
294
+ cache.set(name, tool.description)
295
+ }
296
+ }
297
+ }
@@ -0,0 +1,83 @@
1
+ import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'
2
+ import type { CompatibleRequestHandlerExtra, MCPRequestLike, MCPServerLike } from '../types'
3
+ import { MCPAnalyticsEventType } from './event-types'
4
+ import { getServerTrackingData } from './internal'
5
+ import { log } from './logger'
6
+ import { GET_MORE_TOOLS_NAME, handleReportMissing } from './tools'
7
+ import { instrumentInitializeHandler, instrumentToolsListHandler, captureToolCall } from './instrumentation'
8
+ import { getContextArgument } from './tracing-helpers'
9
+
10
+ type MCPRequestHandler = NonNullable<
11
+ MCPServerLike['_requestHandlers'] extends Map<string, infer THandler> ? THandler : never
12
+ >
13
+ type MCPRequest = Parameters<MCPRequestHandler>[0]
14
+ type MCPRequestExtra = Parameters<MCPRequestHandler>[1]
15
+
16
+ /**
17
+ * Instruments a low-level `Server`: wraps `initialize`, `tools/list`, and
18
+ * `tools/call`. The tool-call lifecycle is delegated to {@link captureToolCall},
19
+ * shared with the high-level wrapper.
20
+ */
21
+ export function instrumentLowLevelServer(server: MCPServerLike): void {
22
+ try {
23
+ instrumentInitializeHandler(server)
24
+ instrumentToolsListHandler(server)
25
+
26
+ const originalCallToolHandler = server._requestHandlers.get('tools/call')
27
+ server.setRequestHandler(
28
+ CallToolRequestSchema,
29
+ async (request, extra) => await handleToolCallRequest(server, originalCallToolHandler, request, extra)
30
+ )
31
+ } catch (error) {
32
+ log(`Warning: Failed to setup tool call instrumentation - ${error}`)
33
+ throw error
34
+ }
35
+ }
36
+
37
+ async function handleToolCallRequest(
38
+ server: MCPServerLike,
39
+ originalCallToolHandler: MCPRequestHandler | undefined,
40
+ request: MCPRequest,
41
+ extra: MCPRequestExtra
42
+ ): Promise<unknown> {
43
+ const data = getServerTrackingData(server)
44
+ if (!data) {
45
+ log(
46
+ 'Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.'
47
+ )
48
+ return await originalCallToolHandler?.(request, extra)
49
+ }
50
+
51
+ if (request.params?.name === GET_MORE_TOOLS_NAME) {
52
+ const context = getContextArgument(request) || ''
53
+ return await captureToolCall({
54
+ server,
55
+ data,
56
+ request,
57
+ extra,
58
+ eventType: MCPAnalyticsEventType.mcpMissingCapability,
59
+ explicitContextIntent: context,
60
+ execute: async () => handleReportMissing({ context }),
61
+ })
62
+ }
63
+
64
+ return await captureToolCall({
65
+ server,
66
+ data,
67
+ request,
68
+ extra,
69
+ execute: (downstreamRequest: MCPRequestLike) =>
70
+ runOriginalToolHandler(originalCallToolHandler, downstreamRequest, extra),
71
+ })
72
+ }
73
+
74
+ function runOriginalToolHandler(
75
+ handler: MCPRequestHandler | undefined,
76
+ request: MCPRequestLike,
77
+ extra: CompatibleRequestHandlerExtra | undefined
78
+ ): Promise<unknown> {
79
+ if (!handler) {
80
+ throw new Error(`Unknown tool: ${request.params?.name || 'unknown'}`)
81
+ }
82
+ return handler(request, extra)
83
+ }