@posthog/mcp 0.10.4 → 0.10.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extensions/capture.d.ts +2 -1
- package/dist/extensions/capture.d.ts.map +1 -1
- package/dist/extensions/capture.js +2 -3
- package/dist/extensions/capture.mjs +2 -3
- package/dist/extensions/compatibility.d.ts +3 -2
- package/dist/extensions/compatibility.d.ts.map +1 -1
- package/dist/extensions/compatibility.js +19 -20
- package/dist/extensions/compatibility.mjs +15 -16
- package/dist/extensions/context-parameters.d.ts +3 -2
- package/dist/extensions/context-parameters.d.ts.map +1 -1
- package/dist/extensions/context-parameters.js +4 -5
- package/dist/extensions/context-parameters.mjs +4 -4
- package/dist/extensions/conversation-id.d.ts +3 -2
- package/dist/extensions/conversation-id.d.ts.map +1 -1
- package/dist/extensions/conversation-id.js +4 -5
- package/dist/extensions/conversation-id.mjs +4 -4
- package/dist/extensions/instrument-highlevel.d.ts +2 -1
- package/dist/extensions/instrument-highlevel.d.ts.map +1 -1
- package/dist/extensions/instrument-highlevel.js +24 -25
- package/dist/extensions/instrument-highlevel.mjs +24 -25
- package/dist/extensions/instrument-lowlevel.d.ts +2 -1
- package/dist/extensions/instrument-lowlevel.d.ts.map +1 -1
- package/dist/extensions/instrument-lowlevel.js +8 -9
- package/dist/extensions/instrument-lowlevel.mjs +8 -9
- package/dist/extensions/instrumentation.d.ts +3 -2
- package/dist/extensions/instrumentation.d.ts.map +1 -1
- package/dist/extensions/instrumentation.js +25 -26
- package/dist/extensions/instrumentation.mjs +25 -26
- package/dist/extensions/intent.d.ts.map +1 -1
- package/dist/extensions/intent.js +1 -2
- package/dist/extensions/intent.mjs +1 -2
- package/dist/extensions/internal.d.ts.map +1 -1
- package/dist/extensions/internal.js +5 -6
- package/dist/extensions/internal.mjs +5 -6
- package/dist/extensions/logger.d.ts +12 -4
- package/dist/extensions/logger.d.ts.map +1 -1
- package/dist/extensions/logger.js +12 -3
- package/dist/extensions/logger.mjs +10 -4
- package/dist/extensions/sink.d.ts +4 -2
- package/dist/extensions/sink.d.ts.map +1 -1
- package/dist/extensions/sink.js +11 -10
- package/dist/extensions/sink.mjs +11 -10
- package/dist/extensions/tools.d.ts +2 -1
- package/dist/extensions/tools.d.ts.map +1 -1
- package/dist/extensions/tools.js +2 -2
- package/dist/extensions/tools.mjs +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -16
- package/dist/index.mjs +19 -17
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.mjs +1 -1
- package/package.json +1 -1
- package/src/extensions/capture.ts +3 -3
- package/src/extensions/compatibility.ts +16 -16
- package/src/extensions/context-parameters.ts +8 -6
- package/src/extensions/conversation-id.ts +12 -6
- package/src/extensions/instrument-highlevel.ts +37 -31
- package/src/extensions/instrument-lowlevel.ts +14 -10
- package/src/extensions/instrumentation.ts +30 -25
- package/src/extensions/intent.ts +1 -2
- package/src/extensions/internal.ts +7 -6
- package/src/extensions/logger.ts +22 -7
- package/src/extensions/sink.ts +17 -12
- package/src/extensions/tools.ts +3 -3
- package/src/index.ts +21 -19
- package/src/types.ts +2 -0
- package/src/version.ts +1 -1
|
@@ -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 {
|
|
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 {
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
patchRequestHandlers,
|
|
29
29
|
captureToolCall,
|
|
30
30
|
readToolMetaCategory,
|
|
31
|
+
type HandlerPatch,
|
|
31
32
|
} from './instrumentation'
|
|
32
33
|
import { getContextArgument } from './tracing-helpers'
|
|
33
34
|
|
|
@@ -51,18 +52,22 @@ function isCallbackUpdate(value: unknown): value is { callback: ToolCallback } {
|
|
|
51
52
|
|
|
52
53
|
function addTracingToToolRegistry(
|
|
53
54
|
tools: Record<string, RegisteredTool>,
|
|
54
|
-
server: HighLevelMCPServerLike
|
|
55
|
+
server: HighLevelMCPServerLike,
|
|
56
|
+
logger: LoggerFn
|
|
55
57
|
): Record<string, RegisteredTool> {
|
|
56
58
|
return Object.fromEntries(
|
|
57
|
-
Object.entries(tools).map(([name, tool]) => [
|
|
59
|
+
Object.entries(tools).map(([name, tool]) => [
|
|
60
|
+
name,
|
|
61
|
+
addTracingToToolCallbackInternal(tool, name, server, () => tool.inputSchema, logger),
|
|
62
|
+
])
|
|
58
63
|
)
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
|
|
66
|
+
function setupListenerToRegisteredTools(server: HighLevelMCPServerLike, logger: LoggerFn): void {
|
|
62
67
|
try {
|
|
63
68
|
const data = getServerTrackingData(server.server as MCPServerLike)
|
|
64
69
|
if (!data) {
|
|
65
|
-
|
|
70
|
+
logger('Warning: Cannot setup listener - no tracking data found')
|
|
66
71
|
return
|
|
67
72
|
}
|
|
68
73
|
|
|
@@ -74,19 +79,19 @@ function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
|
|
|
74
79
|
data.toolDescriptions.set(property, value.description)
|
|
75
80
|
}
|
|
76
81
|
if ((value as ProcessedRegisteredTool)[MCP_ANALYTICS_PROCESSED]) {
|
|
77
|
-
|
|
82
|
+
logger(`Tool ${String(property)} already processed, skipping proxy wrapping`)
|
|
78
83
|
return Reflect.set(target, property, value)
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
if (wrappedCallbacks.has(getToolFunction(value))) {
|
|
82
|
-
|
|
87
|
+
logger(`Tool ${String(property)} callback already wrapped, skipping proxy wrapping`)
|
|
83
88
|
return Reflect.set(target, property, value)
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
// The MCP SDK's registry copy is currently stale after update(), but keep this lookup lazy so
|
|
87
92
|
// ownership follows the current schema once the registry reflects live tool state.
|
|
88
93
|
const getCurrentInputSchema = () => value.inputSchema
|
|
89
|
-
const nextValue = addTracingToToolCallbackInternal(value, property, server, getCurrentInputSchema)
|
|
94
|
+
const nextValue = addTracingToToolCallbackInternal(value, property, server, getCurrentInputSchema, logger)
|
|
90
95
|
|
|
91
96
|
if (typeof nextValue.update === 'function') {
|
|
92
97
|
const originalUpdate = nextValue.update
|
|
@@ -98,7 +103,8 @@ function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
|
|
|
98
103
|
{ callback: updateObj.callback },
|
|
99
104
|
property,
|
|
100
105
|
server,
|
|
101
|
-
getCurrentInputSchema
|
|
106
|
+
getCurrentInputSchema,
|
|
107
|
+
logger
|
|
102
108
|
)
|
|
103
109
|
updateObj.callback = getToolFunction(wrappedTool)
|
|
104
110
|
}
|
|
@@ -111,7 +117,7 @@ function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
|
|
|
111
117
|
|
|
112
118
|
return Reflect.set(target, property, value)
|
|
113
119
|
} catch (error) {
|
|
114
|
-
|
|
120
|
+
logger(`Warning: Error in proxy set handler for tool ${String(property)} - ${error}`)
|
|
115
121
|
return Reflect.set(target, property, value)
|
|
116
122
|
}
|
|
117
123
|
},
|
|
@@ -132,9 +138,9 @@ function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
|
|
|
132
138
|
const originalTools = server._registeredTools || {}
|
|
133
139
|
server._registeredTools = new Proxy(originalTools, handler)
|
|
134
140
|
|
|
135
|
-
|
|
141
|
+
logger('Successfully set up listener for new tool registrations')
|
|
136
142
|
} catch (error) {
|
|
137
|
-
|
|
143
|
+
logger(`Warning: Failed to setup listener for registered tools - ${error}`)
|
|
138
144
|
}
|
|
139
145
|
}
|
|
140
146
|
|
|
@@ -151,19 +157,20 @@ function addTracingToToolCallbackInternal(
|
|
|
151
157
|
tool: RegisteredTool,
|
|
152
158
|
toolName: string,
|
|
153
159
|
server: HighLevelMCPServerLike,
|
|
154
|
-
getCurrentInputSchema: () => unknown
|
|
160
|
+
getCurrentInputSchema: () => unknown,
|
|
161
|
+
logger: LoggerFn
|
|
155
162
|
): RegisteredTool {
|
|
156
163
|
const originalCallback = getToolFunction(tool)
|
|
157
164
|
const options = getServerTrackingData(server.server as MCPServerLike)?.options
|
|
158
165
|
const missingToolName = resolveMissingCapabilityToolName(options)
|
|
159
166
|
|
|
160
167
|
if (wrappedCallbacks.has(originalCallback)) {
|
|
161
|
-
|
|
168
|
+
logger(`Tool ${toolName} callback already wrapped, skipping re-wrap`)
|
|
162
169
|
return tool
|
|
163
170
|
}
|
|
164
171
|
|
|
165
172
|
if ((tool as ProcessedRegisteredTool)[MCP_ANALYTICS_PROCESSED]) {
|
|
166
|
-
|
|
173
|
+
logger(`Tool ${toolName} already processed, skipping re-wrap`)
|
|
167
174
|
return tool
|
|
168
175
|
}
|
|
169
176
|
|
|
@@ -224,11 +231,12 @@ async function handleToolCallRequest(
|
|
|
224
231
|
server: MCPServerLike,
|
|
225
232
|
originalCallToolHandler: MCPRequestHandler,
|
|
226
233
|
request: MCPRequest,
|
|
227
|
-
extra: MCPRequestExtra
|
|
234
|
+
extra: MCPRequestExtra,
|
|
235
|
+
logger: LoggerFn
|
|
228
236
|
): Promise<unknown> {
|
|
229
237
|
const data = getServerTrackingData(server)
|
|
230
238
|
if (!data) {
|
|
231
|
-
|
|
239
|
+
logger(
|
|
232
240
|
'Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.'
|
|
233
241
|
)
|
|
234
242
|
return await originalCallToolHandler(request, extra)
|
|
@@ -243,7 +251,7 @@ async function handleToolCallRequest(
|
|
|
243
251
|
extra,
|
|
244
252
|
eventType: MCPAnalyticsEventType.mcpMissingCapability,
|
|
245
253
|
explicitContextIntent: context,
|
|
246
|
-
execute: async () => handleReportMissing({ context }),
|
|
254
|
+
execute: async () => handleReportMissing({ context }, data.logger),
|
|
247
255
|
})
|
|
248
256
|
}
|
|
249
257
|
|
|
@@ -270,34 +278,32 @@ async function handleToolCallRequest(
|
|
|
270
278
|
})
|
|
271
279
|
}
|
|
272
280
|
|
|
273
|
-
export function instrumentHighLevelServer(server: HighLevelMCPServerLike): void {
|
|
281
|
+
export function instrumentHighLevelServer(server: HighLevelMCPServerLike, logger: LoggerFn): void {
|
|
274
282
|
try {
|
|
275
283
|
const lowLevelServer = server.server
|
|
276
284
|
const mcpAnalyticsData = getServerTrackingData(lowLevelServer)
|
|
277
285
|
|
|
278
286
|
// Patch already existing handlers, and patch setRequestHandler to capture dynamically created handlers.
|
|
279
|
-
const handlers = {
|
|
280
|
-
initialize:
|
|
281
|
-
|
|
282
|
-
'tools/
|
|
283
|
-
trackedServer
|
|
284
|
-
|
|
285
|
-
request
|
|
286
|
-
extra: MCPRequestExtra
|
|
287
|
-
) => handleToolCallRequest(server, trackedServer, originalHandler, request, extra),
|
|
287
|
+
const handlers: Record<string, HandlerPatch> = {
|
|
288
|
+
initialize: (trackedServer, originalHandler, request, extra) =>
|
|
289
|
+
handleInitializeRequest(trackedServer, originalHandler, request, extra, logger),
|
|
290
|
+
'tools/list': (trackedServer, originalHandler, request, extra) =>
|
|
291
|
+
handleListToolsRequest(trackedServer, originalHandler, request, extra, logger),
|
|
292
|
+
'tools/call': (trackedServer, originalHandler, request, extra) =>
|
|
293
|
+
handleToolCallRequest(server, trackedServer, originalHandler, request, extra, logger),
|
|
288
294
|
}
|
|
289
295
|
patchRequestHandlers(lowLevelServer, handlers)
|
|
290
296
|
|
|
291
|
-
server._registeredTools = addTracingToToolRegistry(server._registeredTools, server)
|
|
297
|
+
server._registeredTools = addTracingToToolRegistry(server._registeredTools, server, logger)
|
|
292
298
|
|
|
293
299
|
if (mcpAnalyticsData) {
|
|
294
300
|
seedToolDescriptionsFromRegistry(mcpAnalyticsData.toolDescriptions, server._registeredTools)
|
|
295
301
|
seedToolCategoriesFromRegistry(mcpAnalyticsData.toolCategories, server._registeredTools)
|
|
296
302
|
}
|
|
297
303
|
|
|
298
|
-
setupListenerToRegisteredTools(server)
|
|
304
|
+
setupListenerToRegisteredTools(server, logger)
|
|
299
305
|
} catch (error) {
|
|
300
|
-
|
|
306
|
+
logger(`Warning: Failed to setup tool call instrumentation - ${error}`)
|
|
301
307
|
}
|
|
302
308
|
}
|
|
303
309
|
|
|
@@ -7,13 +7,14 @@ 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 {
|
|
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
|
+
type HandlerPatch,
|
|
17
18
|
} from './instrumentation'
|
|
18
19
|
import { getContextArgument } from './tracing-helpers'
|
|
19
20
|
|
|
@@ -28,22 +29,24 @@ type MCPRequestExtra = Parameters<MCPRequestHandler>[1]
|
|
|
28
29
|
* `tools/call`. The tool-call lifecycle is delegated to {@link captureToolCall},
|
|
29
30
|
* shared with the high-level wrapper.
|
|
30
31
|
*/
|
|
31
|
-
export function instrumentLowLevelServer(server: MCPServerLike): void {
|
|
32
|
+
export function instrumentLowLevelServer(server: MCPServerLike, logger: LoggerFn): void {
|
|
32
33
|
try {
|
|
33
34
|
// Patch already existing handlers, and patch setRequestHandler to capture dynamically created handlers.
|
|
34
|
-
const handlers = {
|
|
35
|
-
initialize:
|
|
36
|
-
|
|
35
|
+
const handlers: Record<string, HandlerPatch> = {
|
|
36
|
+
initialize: (server, originalHandler, request, extra) =>
|
|
37
|
+
handleInitializeRequest(server, originalHandler, request, extra, logger),
|
|
38
|
+
'tools/list': (server, originalHandler, request, extra) =>
|
|
39
|
+
handleListToolsRequest(server, originalHandler, request, extra, logger),
|
|
37
40
|
}
|
|
38
41
|
patchRequestHandlers(server, handlers)
|
|
39
42
|
|
|
40
43
|
const originalCallToolHandler = server._requestHandlers.get('tools/call')
|
|
41
44
|
server.setRequestHandler(
|
|
42
45
|
CallToolRequestSchema,
|
|
43
|
-
async (request, extra) => await handleToolCallRequest(server, originalCallToolHandler, request, extra)
|
|
46
|
+
async (request, extra) => await handleToolCallRequest(server, originalCallToolHandler, request, extra, logger)
|
|
44
47
|
)
|
|
45
48
|
} catch (error) {
|
|
46
|
-
|
|
49
|
+
logger(`Warning: Failed to setup tool call instrumentation - ${error}`)
|
|
47
50
|
throw error
|
|
48
51
|
}
|
|
49
52
|
}
|
|
@@ -52,11 +55,12 @@ async function handleToolCallRequest(
|
|
|
52
55
|
server: MCPServerLike,
|
|
53
56
|
originalCallToolHandler: MCPRequestHandler | undefined,
|
|
54
57
|
request: MCPRequest,
|
|
55
|
-
extra: MCPRequestExtra
|
|
58
|
+
extra: MCPRequestExtra,
|
|
59
|
+
logger: LoggerFn
|
|
56
60
|
): Promise<unknown> {
|
|
57
61
|
const data = getServerTrackingData(server)
|
|
58
62
|
if (!data) {
|
|
59
|
-
|
|
63
|
+
logger(
|
|
60
64
|
'Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.'
|
|
61
65
|
)
|
|
62
66
|
return await originalCallToolHandler?.(request, extra)
|
|
@@ -71,7 +75,7 @@ async function handleToolCallRequest(
|
|
|
71
75
|
extra,
|
|
72
76
|
eventType: MCPAnalyticsEventType.mcpMissingCapability,
|
|
73
77
|
explicitContextIntent: context,
|
|
74
|
-
execute: async () => handleReportMissing({ context }),
|
|
78
|
+
execute: async () => handleReportMissing({ context }, data.logger),
|
|
75
79
|
})
|
|
76
80
|
}
|
|
77
81
|
|
|
@@ -28,7 +28,7 @@ import { MCPAnalyticsEventType } from './event-types'
|
|
|
28
28
|
import { captureException } from './exceptions'
|
|
29
29
|
import { resolveToolCallIntent, setEventIntent, setExplicitContextIntent } from './intent'
|
|
30
30
|
import { getServerTrackingData, handleIdentify, setServerTrackingData } from './internal'
|
|
31
|
-
import {
|
|
31
|
+
import type { LoggerFn } from './logger'
|
|
32
32
|
import { buildCapturedMcpParameters } from './mcp-payloads'
|
|
33
33
|
import { getLiteralValue, getObjectShape } from './mcp-sdk-compat'
|
|
34
34
|
import { getSessionId, newSessionId } from './session'
|
|
@@ -130,12 +130,12 @@ export async function captureToolCall(params: TraceToolCallParams): Promise<unkn
|
|
|
130
130
|
try {
|
|
131
131
|
result = await execute(downstreamRequest)
|
|
132
132
|
} catch (error) {
|
|
133
|
-
publishFailedToolEvent(server, event, error, startTime, conversation)
|
|
133
|
+
publishFailedToolEvent(server, event, error, startTime, conversation, data.logger)
|
|
134
134
|
throw error
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
const finalResult = applyConversationPromptBack(event, result, conversation)
|
|
138
|
-
publishSuccessfulToolEvent(server, event, finalResult, startTime, takeCapturedError)
|
|
138
|
+
publishSuccessfulToolEvent(server, event, finalResult, startTime, data.logger, takeCapturedError)
|
|
139
139
|
return finalResult
|
|
140
140
|
}
|
|
141
141
|
|
|
@@ -206,7 +206,7 @@ async function prepareToolCallEvent(
|
|
|
206
206
|
setEventIntent(event, await resolveToolCallIntent(data, request, ownership.context, extra))
|
|
207
207
|
return event
|
|
208
208
|
} catch (error) {
|
|
209
|
-
|
|
209
|
+
data.logger(
|
|
210
210
|
`Warning: PostHog MCP analytics instrumentation failed for tool ${request.params?.name}, the tool will still run - ${error}`
|
|
211
211
|
)
|
|
212
212
|
return null
|
|
@@ -240,6 +240,7 @@ function publishSuccessfulToolEvent(
|
|
|
240
240
|
event: McpEvent | null,
|
|
241
241
|
result: unknown,
|
|
242
242
|
startTime: Date,
|
|
243
|
+
logger: LoggerFn,
|
|
243
244
|
takeCapturedError?: () => unknown
|
|
244
245
|
): void {
|
|
245
246
|
if (!event) {
|
|
@@ -255,9 +256,9 @@ function publishSuccessfulToolEvent(
|
|
|
255
256
|
}
|
|
256
257
|
event.response = result
|
|
257
258
|
event.duration = Date.now() - startTime.getTime()
|
|
258
|
-
captureEvent(server, event)
|
|
259
|
+
captureEvent(server, event, logger)
|
|
259
260
|
} catch (error) {
|
|
260
|
-
|
|
261
|
+
logger(`Warning: PostHog MCP analytics failed to publish tool event - ${error}`)
|
|
261
262
|
}
|
|
262
263
|
}
|
|
263
264
|
|
|
@@ -266,7 +267,8 @@ function publishFailedToolEvent(
|
|
|
266
267
|
event: McpEvent | null,
|
|
267
268
|
error: unknown,
|
|
268
269
|
startTime: Date,
|
|
269
|
-
conversation: ConversationIdResolution
|
|
270
|
+
conversation: ConversationIdResolution,
|
|
271
|
+
logger: LoggerFn
|
|
270
272
|
): void {
|
|
271
273
|
if (!event) {
|
|
272
274
|
return
|
|
@@ -278,9 +280,9 @@ function publishFailedToolEvent(
|
|
|
278
280
|
event.isError = true
|
|
279
281
|
event.error = captureException(error)
|
|
280
282
|
event.duration = Date.now() - startTime.getTime()
|
|
281
|
-
captureEvent(server, event)
|
|
283
|
+
captureEvent(server, event, logger)
|
|
282
284
|
} catch (publishError) {
|
|
283
|
-
|
|
285
|
+
logger(`Warning: PostHog MCP analytics failed to publish failed tool event - ${publishError}`)
|
|
284
286
|
}
|
|
285
287
|
}
|
|
286
288
|
|
|
@@ -336,7 +338,8 @@ export async function handleListToolsRequest(
|
|
|
336
338
|
server: MCPServerLike,
|
|
337
339
|
originalListToolsHandler: MCPRequestHandler,
|
|
338
340
|
request: MCPRequestLike,
|
|
339
|
-
extra
|
|
341
|
+
extra: CompatibleRequestHandlerExtra | undefined,
|
|
342
|
+
logger: LoggerFn
|
|
340
343
|
): Promise<{ tools: ListToolsResult['tools'] }> {
|
|
341
344
|
const data = getServerTrackingData(server)
|
|
342
345
|
const startTime = new Date()
|
|
@@ -352,23 +355,23 @@ export async function handleListToolsRequest(
|
|
|
352
355
|
await applyResolvedMetadata(event, data, request, extra)
|
|
353
356
|
}
|
|
354
357
|
|
|
355
|
-
const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event)
|
|
358
|
+
const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger)
|
|
356
359
|
|
|
357
360
|
if (!data) {
|
|
358
|
-
|
|
361
|
+
logger(
|
|
359
362
|
'Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.'
|
|
360
363
|
)
|
|
361
364
|
return { tools }
|
|
362
365
|
}
|
|
363
366
|
|
|
364
367
|
if (tools.length === 0) {
|
|
365
|
-
|
|
368
|
+
data.logger(
|
|
366
369
|
'Warning: No tools found in the original list. This is likely due to the tools not being registered before PostHog MCP analytics.instrument().'
|
|
367
370
|
)
|
|
368
371
|
event.error = captureException('No tools were sent to MCP client.')
|
|
369
372
|
event.isError = true
|
|
370
373
|
event.duration = Date.now() - startTime.getTime()
|
|
371
|
-
captureEvent(server, event)
|
|
374
|
+
captureEvent(server, event, data.logger)
|
|
372
375
|
return { tools }
|
|
373
376
|
}
|
|
374
377
|
|
|
@@ -376,7 +379,7 @@ export async function handleListToolsRequest(
|
|
|
376
379
|
event.listedToolNames = collectListedToolNames(tools)
|
|
377
380
|
event.isError = false
|
|
378
381
|
event.duration = Date.now() - startTime.getTime()
|
|
379
|
-
captureEvent(server, event)
|
|
382
|
+
captureEvent(server, event, data.logger)
|
|
380
383
|
return { tools }
|
|
381
384
|
}
|
|
382
385
|
|
|
@@ -405,7 +408,8 @@ async function getTracedToolsList(
|
|
|
405
408
|
originalListToolsHandler: MCPRequestHandler,
|
|
406
409
|
request: MCPRequestLike,
|
|
407
410
|
extra: CompatibleRequestHandlerExtra | undefined,
|
|
408
|
-
event: McpEvent
|
|
411
|
+
event: McpEvent,
|
|
412
|
+
logger: LoggerFn
|
|
409
413
|
): Promise<ListToolsResult['tools']> {
|
|
410
414
|
try {
|
|
411
415
|
const data = getServerTrackingData(server)
|
|
@@ -417,11 +421,11 @@ async function getTracedToolsList(
|
|
|
417
421
|
}
|
|
418
422
|
|
|
419
423
|
if (data && isContextEnabled(data.options.context)) {
|
|
420
|
-
tools = addContextParameterToTools(tools, getContextDescription(data.options.context))
|
|
424
|
+
tools = addContextParameterToTools(tools, getContextDescription(data.options.context), data.logger)
|
|
421
425
|
}
|
|
422
426
|
|
|
423
427
|
if (data?.options.enableConversationId) {
|
|
424
|
-
tools = addConversationIdToTools(tools, resolveMissingCapabilityToolName(data.options))
|
|
428
|
+
tools = addConversationIdToTools(tools, resolveMissingCapabilityToolName(data.options), data.logger)
|
|
425
429
|
}
|
|
426
430
|
|
|
427
431
|
if (data?.options.reportMissing) {
|
|
@@ -439,13 +443,13 @@ async function getTracedToolsList(
|
|
|
439
443
|
|
|
440
444
|
return tools
|
|
441
445
|
} catch (error) {
|
|
442
|
-
|
|
446
|
+
logger(
|
|
443
447
|
`Warning: Original list tools handler failed, this suggests an error PostHog MCP analytics did not cause - ${error}`
|
|
444
448
|
)
|
|
445
449
|
event.error = captureException(error)
|
|
446
450
|
event.isError = true
|
|
447
451
|
event.duration = event.timestamp ? Date.now() - event.timestamp.getTime() : 0
|
|
448
|
-
captureEvent(server, event)
|
|
452
|
+
captureEvent(server, event, logger)
|
|
449
453
|
throw error
|
|
450
454
|
}
|
|
451
455
|
}
|
|
@@ -541,7 +545,7 @@ function mintStatelessSessionOnInitialize(
|
|
|
541
545
|
setServerTrackingData(server, data)
|
|
542
546
|
return sessionId
|
|
543
547
|
} catch (error) {
|
|
544
|
-
|
|
548
|
+
data.logger(`Warning: PostHog MCP analytics failed to mint a stateless session id - ${error}`)
|
|
545
549
|
return undefined
|
|
546
550
|
}
|
|
547
551
|
}
|
|
@@ -571,7 +575,7 @@ function upgradeMintedTokenToNegotiated(
|
|
|
571
575
|
})
|
|
572
576
|
writeSessionIdToTransport(transport, token)
|
|
573
577
|
} catch (error) {
|
|
574
|
-
|
|
578
|
+
data.logger(`Warning: PostHog MCP analytics failed to upgrade the stateless session token - ${error}`)
|
|
575
579
|
}
|
|
576
580
|
}
|
|
577
581
|
|
|
@@ -599,11 +603,12 @@ export async function handleInitializeRequest(
|
|
|
599
603
|
server: MCPServerLike,
|
|
600
604
|
originalInitializeHandler: MCPRequestHandler,
|
|
601
605
|
request: MCPRequestLike,
|
|
602
|
-
extra
|
|
606
|
+
extra: CompatibleRequestHandlerExtra | undefined,
|
|
607
|
+
logger: LoggerFn
|
|
603
608
|
): Promise<unknown> {
|
|
604
609
|
const data = getServerTrackingData(server)
|
|
605
610
|
if (!data) {
|
|
606
|
-
|
|
611
|
+
logger(
|
|
607
612
|
'Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.'
|
|
608
613
|
)
|
|
609
614
|
return await originalInitializeHandler(request, extra)
|
|
@@ -640,7 +645,7 @@ export async function handleInitializeRequest(
|
|
|
640
645
|
if (mintedSessionId) {
|
|
641
646
|
upgradeMintedTokenToNegotiated(server, data, mintedSessionId, negotiatedProtocolVersion)
|
|
642
647
|
}
|
|
643
|
-
captureEvent(server, event)
|
|
648
|
+
captureEvent(server, event, data.logger)
|
|
644
649
|
return result
|
|
645
650
|
}
|
|
646
651
|
|
package/src/extensions/intent.ts
CHANGED
|
@@ -11,7 +11,6 @@ import type {
|
|
|
11
11
|
McpEvent,
|
|
12
12
|
} from '../types'
|
|
13
13
|
import { isContextEnabled } from './context-parameters'
|
|
14
|
-
import { log } from './logger'
|
|
15
14
|
|
|
16
15
|
interface ResolvedIntent {
|
|
17
16
|
intent: string
|
|
@@ -45,7 +44,7 @@ async function runIntentFallback(
|
|
|
45
44
|
const intent = normalizeIntent(await data.options.intentFallback(request, extra))
|
|
46
45
|
return intent ? { intent, source: 'inferred' } : null
|
|
47
46
|
} catch (error) {
|
|
48
|
-
|
|
47
|
+
data.logger(`intentFallback callback error: ${error}`)
|
|
49
48
|
return null
|
|
50
49
|
}
|
|
51
50
|
}
|
|
@@ -12,7 +12,6 @@ import type {
|
|
|
12
12
|
UserIdentity,
|
|
13
13
|
} from '../types'
|
|
14
14
|
import { MCPAnalyticsEventType } from './event-types'
|
|
15
|
-
import { log } from './logger'
|
|
16
15
|
import { captureEvent } from './capture'
|
|
17
16
|
import { stampMetaClientInfo } from './client-identity'
|
|
18
17
|
|
|
@@ -169,14 +168,16 @@ export async function handleIdentify(
|
|
|
169
168
|
data.identifiedSessions.set(sessionId, mergedIdentity)
|
|
170
169
|
|
|
171
170
|
if (shouldPublish) {
|
|
172
|
-
|
|
173
|
-
captureEvent(server, identifyEvent)
|
|
171
|
+
data.logger(`Identified session ${sessionId}`)
|
|
172
|
+
captureEvent(server, identifyEvent, data.logger)
|
|
174
173
|
}
|
|
175
174
|
} else {
|
|
176
|
-
|
|
175
|
+
data.logger(`Warning: Supplied identify function returned null for session ${sessionId}`)
|
|
177
176
|
}
|
|
178
177
|
} catch (error) {
|
|
179
|
-
|
|
178
|
+
data.logger(
|
|
179
|
+
`Error: User supplied identify function threw an error while identifying session ${sessionId} - ${error}`
|
|
180
|
+
)
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
183
|
|
|
@@ -196,7 +197,7 @@ export async function resolveEventProperties(
|
|
|
196
197
|
try {
|
|
197
198
|
return (await data.options.eventProperties(request, extra)) ?? null
|
|
198
199
|
} catch (e) {
|
|
199
|
-
|
|
200
|
+
data.logger(`eventProperties callback error: ${e}`)
|
|
200
201
|
return null
|
|
201
202
|
}
|
|
202
203
|
}
|
package/src/extensions/logger.ts
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
*
|
|
9
9
|
* MCP servers running over the STDIO transport use stdout/stderr to exchange
|
|
10
10
|
* protocol messages, so we cannot use the default `console.log`. We accept a
|
|
11
|
-
* `logger` option on the public API
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* `logger` option on the public API or the legacy logger configured through
|
|
12
|
+
* `setLogger`; when neither is configured, log calls are silently dropped.
|
|
13
|
+
* Errors that affect tracking should still be observable in apps that want
|
|
14
|
+
* them, so the consumer can plug in any function (e.g. `console.error` for
|
|
15
|
+
* non-STDIO transports, a file logger, etc.).
|
|
15
16
|
*/
|
|
16
17
|
export type LoggerFn = (message: string) => void
|
|
17
18
|
|
|
@@ -21,12 +22,26 @@ export function setLogger(logger: LoggerFn | undefined): void {
|
|
|
21
22
|
activeLogger = logger
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
if (
|
|
25
|
+
function writeLog(logger: LoggerFn | undefined, message: string): void {
|
|
26
|
+
if (logger) {
|
|
26
27
|
try {
|
|
27
|
-
|
|
28
|
+
logger(message)
|
|
28
29
|
} catch {
|
|
29
30
|
// never let logging blow up the tracking pipeline
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates a safe logger for one instrumented server. A server-specific logger
|
|
37
|
+
* remains isolated, while an omitted logger preserves the legacy `setLogger`
|
|
38
|
+
* fallback.
|
|
39
|
+
*/
|
|
40
|
+
export function createLogger(logger: LoggerFn | undefined): LoggerFn {
|
|
41
|
+
return logger ? (message) => writeLog(logger, message) : log
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Logs through the legacy module-level logger configured by {@link setLogger}. */
|
|
45
|
+
export function log(message: string): void {
|
|
46
|
+
writeLog(activeLogger, message)
|
|
47
|
+
}
|
package/src/extensions/sink.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { PostHog } from 'posthog-node'
|
|
|
7
7
|
import { uuidv7 } from '@posthog/core'
|
|
8
8
|
|
|
9
9
|
import type { BeforeSendFn, Event, McpEvent } from '../types'
|
|
10
|
-
import { log } from './logger'
|
|
10
|
+
import { log, type LoggerFn } from './logger'
|
|
11
11
|
import { type PostHogCaptureEvent, buildPostHogCaptureEvents } from './posthog-events'
|
|
12
12
|
import { newPrefixedId } from './ids'
|
|
13
13
|
import { sanitizeEvent } from './sanitization'
|
|
@@ -33,21 +33,22 @@ export interface McpCaptureOptions {
|
|
|
33
33
|
*/
|
|
34
34
|
export async function processMcpEvent(
|
|
35
35
|
event: McpEvent,
|
|
36
|
-
options: McpCaptureOptions
|
|
36
|
+
options: McpCaptureOptions,
|
|
37
|
+
logger: LoggerFn = log
|
|
37
38
|
): Promise<{ event: Event; captures: PostHogCaptureEvent[] } | null> {
|
|
38
39
|
let processed: McpEvent = event
|
|
39
40
|
|
|
40
41
|
try {
|
|
41
42
|
processed = sanitizeEvent(processed)
|
|
42
43
|
} catch (err) {
|
|
43
|
-
|
|
44
|
+
logger(`Failed to sanitize event: ${err}`)
|
|
44
45
|
return null
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
try {
|
|
48
49
|
processed = truncateEvent(processed)
|
|
49
50
|
} catch (err) {
|
|
50
|
-
|
|
51
|
+
logger(`Failed to truncate event: ${err}`)
|
|
51
52
|
return null
|
|
52
53
|
}
|
|
53
54
|
|
|
@@ -58,7 +59,7 @@ export async function processMcpEvent(
|
|
|
58
59
|
enableExceptionAutocapture: options.enableExceptionAutocapture,
|
|
59
60
|
})
|
|
60
61
|
|
|
61
|
-
const captures = await applyBeforeSend(built, options.beforeSend)
|
|
62
|
+
const captures = await applyBeforeSend(built, options.beforeSend, logger)
|
|
62
63
|
|
|
63
64
|
return { event: fullEvent, captures }
|
|
64
65
|
}
|
|
@@ -69,7 +70,8 @@ export async function processMcpEvent(
|
|
|
69
70
|
*/
|
|
70
71
|
async function applyBeforeSend(
|
|
71
72
|
captures: PostHogCaptureEvent[],
|
|
72
|
-
beforeSend: BeforeSendFn | undefined
|
|
73
|
+
beforeSend: BeforeSendFn | undefined,
|
|
74
|
+
logger: LoggerFn
|
|
73
75
|
): Promise<PostHogCaptureEvent[]> {
|
|
74
76
|
if (!beforeSend) {
|
|
75
77
|
return captures
|
|
@@ -83,7 +85,7 @@ async function applyBeforeSend(
|
|
|
83
85
|
kept.push(result)
|
|
84
86
|
}
|
|
85
87
|
} catch (err) {
|
|
86
|
-
|
|
88
|
+
logger(`beforeSend threw for event ${capture.event}; dropping it: ${err}`)
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
91
|
return kept
|
|
@@ -99,7 +101,10 @@ async function applyBeforeSend(
|
|
|
99
101
|
* `@posthog/ai`).
|
|
100
102
|
*/
|
|
101
103
|
export class McpEventSink {
|
|
102
|
-
constructor(
|
|
104
|
+
constructor(
|
|
105
|
+
private readonly posthog: PostHog,
|
|
106
|
+
private readonly logger: LoggerFn = log
|
|
107
|
+
) {}
|
|
103
108
|
|
|
104
109
|
/**
|
|
105
110
|
* Push an MCP event through the pipeline (sanitize → truncate → fan out →
|
|
@@ -107,7 +112,7 @@ export class McpEventSink {
|
|
|
107
112
|
* dropped, never re-thrown into tool code.
|
|
108
113
|
*/
|
|
109
114
|
async capture(event: McpEvent, options: McpCaptureOptions): Promise<void> {
|
|
110
|
-
const result = await processMcpEvent(event, options)
|
|
115
|
+
const result = await processMcpEvent(event, options, this.logger)
|
|
111
116
|
if (!result) {
|
|
112
117
|
return
|
|
113
118
|
}
|
|
@@ -132,13 +137,13 @@ export class McpEventSink {
|
|
|
132
137
|
uuid: uuidv7(),
|
|
133
138
|
})
|
|
134
139
|
}
|
|
135
|
-
|
|
140
|
+
this.logger(
|
|
136
141
|
`Captured PostHog event ${fullEvent.id} | ${fullEvent.eventType} | ${fullEvent.duration} ms | ${
|
|
137
|
-
fullEvent.identifyActorGivenId
|
|
142
|
+
fullEvent.identifyActorGivenId ? 'identified' : 'anonymous'
|
|
138
143
|
}`
|
|
139
144
|
)
|
|
140
145
|
} catch (err) {
|
|
141
|
-
|
|
146
|
+
this.logger(`Failed to capture PostHog event ${fullEvent.id}: ${err}`)
|
|
142
147
|
}
|
|
143
148
|
}
|
|
144
149
|
}
|