@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.
- package/LICENSE +307 -3
- package/README.md +182 -90
- package/dist/extensions/capture.d.ts +13 -0
- package/dist/extensions/capture.d.ts.map +1 -0
- package/dist/extensions/capture.js +80 -0
- package/dist/extensions/capture.mjs +46 -0
- package/dist/extensions/compatibility.d.ts +23 -0
- package/dist/extensions/compatibility.d.ts.map +1 -0
- package/dist/extensions/compatibility.js +113 -0
- package/dist/extensions/compatibility.mjs +67 -0
- package/dist/extensions/constants.d.ts +39 -0
- package/dist/extensions/constants.d.ts.map +1 -0
- package/dist/extensions/constants.js +86 -0
- package/dist/extensions/constants.mjs +37 -0
- package/dist/extensions/context-parameters.d.ts +31 -0
- package/dist/extensions/context-parameters.d.ts.map +1 -0
- package/dist/extensions/context-parameters.js +93 -0
- package/dist/extensions/context-parameters.mjs +50 -0
- package/dist/extensions/conversation-id.d.ts +52 -0
- package/dist/extensions/conversation-id.d.ts.map +1 -0
- package/dist/extensions/conversation-id.js +164 -0
- package/dist/extensions/conversation-id.mjs +103 -0
- package/dist/extensions/event-types.d.ts +21 -0
- package/dist/extensions/event-types.d.ts.map +1 -0
- package/dist/extensions/event-types.js +47 -0
- package/dist/extensions/event-types.mjs +13 -0
- package/dist/extensions/exceptions.d.ts +13 -0
- package/dist/extensions/exceptions.d.ts.map +1 -0
- package/dist/extensions/exceptions.js +58 -0
- package/dist/extensions/exceptions.mjs +24 -0
- package/dist/extensions/ids.d.ts +13 -0
- package/dist/extensions/ids.d.ts.map +1 -0
- package/dist/extensions/ids.js +55 -0
- package/dist/extensions/ids.mjs +18 -0
- package/dist/extensions/instrument-highlevel.d.ts +3 -0
- package/dist/extensions/instrument-highlevel.d.ts.map +1 -0
- package/dist/extensions/instrument-highlevel.js +229 -0
- package/dist/extensions/instrument-highlevel.mjs +195 -0
- package/dist/extensions/instrument-lowlevel.d.ts +8 -0
- package/dist/extensions/instrument-lowlevel.d.ts.map +1 -0
- package/dist/extensions/instrument-lowlevel.js +85 -0
- package/dist/extensions/instrument-lowlevel.mjs +51 -0
- package/dist/extensions/instrumentation.d.ts +59 -0
- package/dist/extensions/instrumentation.d.ts.map +1 -0
- package/dist/extensions/instrumentation.js +237 -0
- package/dist/extensions/instrumentation.mjs +194 -0
- package/dist/extensions/intent.d.ts +10 -0
- package/dist/extensions/intent.d.ts.map +1 -0
- package/dist/extensions/intent.js +84 -0
- package/dist/extensions/intent.mjs +44 -0
- package/dist/extensions/internal.d.ts +32 -0
- package/dist/extensions/internal.d.ts.map +1 -0
- package/dist/extensions/internal.js +158 -0
- package/dist/extensions/internal.mjs +106 -0
- package/dist/extensions/logger.d.ts +14 -0
- package/dist/extensions/logger.d.ts.map +1 -0
- package/dist/extensions/logger.js +47 -0
- package/dist/extensions/logger.mjs +10 -0
- package/dist/extensions/mcp-payloads.d.ts +5 -0
- package/dist/extensions/mcp-payloads.d.ts.map +1 -0
- package/dist/extensions/mcp-payloads.js +91 -0
- package/dist/extensions/mcp-payloads.mjs +54 -0
- package/dist/extensions/mcp-sdk-compat.d.ts +33 -0
- package/dist/extensions/mcp-sdk-compat.d.ts.map +1 -0
- package/dist/extensions/mcp-sdk-compat.js +112 -0
- package/dist/extensions/mcp-sdk-compat.mjs +60 -0
- package/dist/extensions/posthog-events.d.ts +14 -0
- package/dist/extensions/posthog-events.d.ts.map +1 -0
- package/dist/extensions/posthog-events.js +143 -0
- package/dist/extensions/posthog-events.mjs +109 -0
- package/dist/extensions/sanitization.d.ts +10 -0
- package/dist/extensions/sanitization.d.ts.map +1 -0
- package/dist/extensions/sanitization.js +96 -0
- package/dist/extensions/sanitization.mjs +62 -0
- package/dist/extensions/session.d.ts +20 -0
- package/dist/extensions/session.d.ts.map +1 -0
- package/dist/extensions/session.js +113 -0
- package/dist/extensions/session.mjs +67 -0
- package/dist/extensions/sink.d.ts +44 -0
- package/dist/extensions/sink.d.ts.map +1 -0
- package/dist/extensions/sink.js +102 -0
- package/dist/extensions/sink.mjs +65 -0
- package/dist/extensions/tools.d.ts +9 -0
- package/dist/extensions/tools.d.ts.map +1 -0
- package/dist/extensions/tools.js +79 -0
- package/dist/extensions/tools.mjs +39 -0
- package/dist/extensions/tracing-helpers.d.ts +29 -0
- package/dist/extensions/tracing-helpers.d.ts.map +1 -0
- package/dist/extensions/tracing-helpers.js +59 -0
- package/dist/extensions/tracing-helpers.mjs +16 -0
- package/dist/extensions/truncation.d.ts +25 -0
- package/dist/extensions/truncation.d.ts.map +1 -0
- package/dist/extensions/truncation.js +277 -0
- package/dist/extensions/truncation.mjs +228 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +132 -0
- package/dist/index.mjs +83 -2929
- package/dist/types.d.ts +220 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.mjs +0 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +36 -0
- package/dist/version.mjs +2 -0
- package/package.json +55 -87
- package/src/extensions/capture.ts +68 -0
- package/src/extensions/compatibility.ts +138 -0
- package/src/extensions/constants.ts +48 -0
- package/src/extensions/context-parameters.ts +119 -0
- package/src/extensions/conversation-id.ts +177 -0
- package/src/extensions/event-types.ts +21 -0
- package/src/extensions/exceptions.ts +83 -0
- package/src/extensions/ids.ts +32 -0
- package/src/extensions/instrument-highlevel.ts +297 -0
- package/src/extensions/instrument-lowlevel.ts +83 -0
- package/src/extensions/instrumentation.ts +415 -0
- package/src/extensions/intent.ts +78 -0
- package/src/extensions/internal.ts +193 -0
- package/src/extensions/logger.ts +27 -0
- package/src/extensions/mcp-payloads.ts +98 -0
- package/src/extensions/mcp-sdk-compat.ts +160 -0
- package/src/extensions/posthog-events.ts +212 -0
- package/src/extensions/sanitization.ts +121 -0
- package/src/extensions/session.ts +117 -0
- package/src/extensions/sink.ts +130 -0
- package/src/extensions/tools.ts +50 -0
- package/src/extensions/tracing-helpers.ts +51 -0
- package/src/extensions/truncation.ts +453 -0
- package/src/index.ts +174 -0
- package/src/types.ts +244 -0
- package/src/version.ts +1 -0
- package/dist/index.cjs +0 -2937
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -264
- package/dist/index.d.mts +0 -263
- 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
|
+
}
|