@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.
- 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 -8
- package/dist/extensions/context-parameters.mjs +4 -7
- 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 +5 -6
- package/dist/extensions/conversation-id.mjs +5 -5
- 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 +29 -30
- package/dist/extensions/instrument-highlevel.mjs +30 -31
- 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 +15 -11
- package/dist/extensions/instrument-lowlevel.mjs +16 -12
- package/dist/extensions/instrumentation.d.ts +9 -8
- package/dist/extensions/instrumentation.d.ts.map +1 -1
- package/dist/extensions/instrumentation.js +84 -40
- package/dist/extensions/instrumentation.mjs +80 -39
- package/dist/extensions/intent.d.ts.map +1 -1
- package/dist/extensions/intent.js +2 -3
- package/dist/extensions/intent.mjs +2 -3
- 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 +3 -3
- package/src/extensions/capture.ts +3 -3
- package/src/extensions/compatibility.ts +16 -16
- package/src/extensions/context-parameters.ts +8 -12
- package/src/extensions/conversation-id.ts +14 -7
- package/src/extensions/instrument-highlevel.ts +45 -44
- package/src/extensions/instrument-lowlevel.ts +30 -15
- package/src/extensions/instrumentation.ts +119 -42
- package/src/extensions/intent.ts +2 -8
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/mcp",
|
|
3
|
-
"version": "0.10.
|
|
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
|
-
"
|
|
43
|
-
"posthog-
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
51
|
+
logger(`WARN: Tool "${toolName}" already has 'context' parameter. Skipping context injection.`)
|
|
51
52
|
} else {
|
|
52
|
-
|
|
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>(
|
|
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
|
-
|
|
34
|
+
logger(
|
|
32
35
|
`WARN: Tool "${toolName}" already has '${CONVERSATION_ID_PARAM_NAME}' parameter. Skipping conversation_id injection.`
|
|
33
36
|
)
|
|
34
37
|
} else {
|
|
35
|
-
|
|
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 {
|
|
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]) => [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
+
logger('Successfully set up listener for new tool registrations')
|
|
136
143
|
} catch (error) {
|
|
137
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
281
|
-
|
|
282
|
-
'tools/
|
|
283
|
-
trackedServer
|
|
284
|
-
|
|
285
|
-
request
|
|
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
|
-
|
|
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 {
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|