@posthog/mcp 0.10.3 → 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/analytics-parameters.d.ts +5 -0
- package/dist/extensions/analytics-parameters.d.ts.map +1 -1
- package/dist/extensions/analytics-parameters.js +57 -3
- package/dist/extensions/analytics-parameters.mjs +47 -2
- 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 +4 -3
- 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 -8
- package/dist/extensions/conversation-id.d.ts.map +1 -1
- package/dist/extensions/conversation-id.js +6 -22
- package/dist/extensions/conversation-id.mjs +5 -17
- 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 +31 -51
- package/dist/extensions/instrument-highlevel.mjs +30 -50
- 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 +7 -8
- package/dist/extensions/instrumentation.d.ts.map +1 -1
- package/dist/extensions/instrumentation.js +57 -32
- package/dist/extensions/instrumentation.mjs +58 -33
- package/dist/extensions/intent.d.ts +1 -1
- package/dist/extensions/intent.d.ts.map +1 -1
- package/dist/extensions/intent.js +3 -4
- package/dist/extensions/intent.mjs +3 -4
- 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 +19 -16
- package/dist/index.mjs +20 -17
- package/dist/types.d.ts +7 -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 +4 -4
- package/src/extensions/analytics-parameters.ts +67 -1
- package/src/extensions/capture.ts +3 -3
- package/src/extensions/compatibility.ts +16 -16
- package/src/extensions/context-parameters.ts +9 -7
- package/src/extensions/conversation-id.ts +12 -25
- package/src/extensions/instrument-highlevel.ts +52 -75
- package/src/extensions/instrument-lowlevel.ts +14 -10
- package/src/extensions/instrumentation.ts +101 -36
- package/src/extensions/intent.ts +8 -3
- 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 +22 -19
- package/src/types.ts +8 -0
- package/src/version.ts +1 -1
|
@@ -1,4 +1,21 @@
|
|
|
1
|
+
import type { AnalyticsParameterOwnership } from '../types'
|
|
2
|
+
import { getObjectShape, isZodRawShapeCompat } from './mcp-sdk-compat'
|
|
3
|
+
|
|
4
|
+
const JSON_SCHEMA_KEYWORDS = [
|
|
5
|
+
'$defs',
|
|
6
|
+
'$ref',
|
|
7
|
+
'additionalProperties',
|
|
8
|
+
'allOf',
|
|
9
|
+
'anyOf',
|
|
10
|
+
'definitions',
|
|
11
|
+
'oneOf',
|
|
12
|
+
'properties',
|
|
13
|
+
'required',
|
|
14
|
+
'type',
|
|
15
|
+
]
|
|
16
|
+
|
|
1
17
|
export interface AnalyticsInjectableJsonSchema {
|
|
18
|
+
$ref?: unknown
|
|
2
19
|
additionalProperties?: boolean
|
|
3
20
|
allOf?: unknown
|
|
4
21
|
anyOf?: unknown
|
|
@@ -19,5 +36,54 @@ export function canInjectAnalyticsParameter(
|
|
|
19
36
|
schema: AnalyticsInjectableJsonSchema | undefined,
|
|
20
37
|
parameterName: string
|
|
21
38
|
): boolean {
|
|
22
|
-
return
|
|
39
|
+
return (
|
|
40
|
+
!hasAnalyticsParameter(schema, parameterName) && !schema?.$ref && !schema?.oneOf && !schema?.allOf && !schema?.anyOf
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function analyticsOwnsParameter(inputSchema: unknown, parameterName: string): boolean {
|
|
45
|
+
if (!inputSchema || typeof inputSchema !== 'object') {
|
|
46
|
+
return canInjectAnalyticsParameter(undefined, parameterName)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (isZodRawShapeCompat(inputSchema)) {
|
|
50
|
+
return canInjectAnalyticsParameter({ properties: inputSchema }, parameterName)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const shape = getObjectShape(inputSchema)
|
|
54
|
+
if (shape) {
|
|
55
|
+
return canInjectAnalyticsParameter({ properties: shape }, parameterName)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const schema = inputSchema as Record<string, unknown>
|
|
59
|
+
if (JSON_SCHEMA_KEYWORDS.some((keyword) => Object.prototype.hasOwnProperty.call(schema, keyword))) {
|
|
60
|
+
return canInjectAnalyticsParameter(schema, parameterName)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if ('_def' in schema || '_zod' in schema || '~standard' in schema) {
|
|
64
|
+
// The MCP SDK advertises non-object schemas as an empty object schema.
|
|
65
|
+
return canInjectAnalyticsParameter(undefined, parameterName)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return canInjectAnalyticsParameter(schema, parameterName)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function getAnalyticsParameterOwnership(inputSchema: unknown): AnalyticsParameterOwnership {
|
|
72
|
+
return {
|
|
73
|
+
context: analyticsOwnsParameter(inputSchema, 'context'),
|
|
74
|
+
conversationId: analyticsOwnsParameter(inputSchema, 'conversation_id'),
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function stripOwnedAnalyticsArguments(args: unknown, ownership: AnalyticsParameterOwnership): unknown {
|
|
79
|
+
let cleanedArgs = args
|
|
80
|
+
if (ownership.context && cleanedArgs && typeof cleanedArgs === 'object' && 'context' in cleanedArgs) {
|
|
81
|
+
const { context: _context, ...rest } = cleanedArgs
|
|
82
|
+
cleanedArgs = rest
|
|
83
|
+
}
|
|
84
|
+
if (ownership.conversationId && cleanedArgs && typeof cleanedArgs === 'object' && 'conversation_id' in cleanedArgs) {
|
|
85
|
+
const { conversation_id: _conversationId, ...rest } = cleanedArgs
|
|
86
|
+
cleanedArgs = rest
|
|
87
|
+
}
|
|
88
|
+
return cleanedArgs
|
|
23
89
|
}
|
|
@@ -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
|
|
@@ -33,12 +33,13 @@ export function getContextDescription(context: MCPAnalyticsOptions['context']):
|
|
|
33
33
|
*
|
|
34
34
|
* Skips injection (with warning) for:
|
|
35
35
|
* - Tools that already have a 'context' parameter
|
|
36
|
-
* - Complex schemas (oneOf/allOf/anyOf) that can't safely have properties added
|
|
36
|
+
* - Complex schemas (oneOf/allOf/anyOf/$ref) that can't safely have properties added
|
|
37
37
|
* - Schemas with additionalProperties: false
|
|
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,14 @@ 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
111
|
return tools.map((tool) => {
|
|
110
112
|
// Skip get_more_tools - it has its own special context parameter
|
|
111
113
|
if (tool.name === 'get_more_tools') {
|
|
112
114
|
return tool
|
|
113
115
|
}
|
|
114
|
-
return addContextParameterToTool(tool, contextDescriptionOverride)
|
|
116
|
+
return addContextParameterToTool(tool, contextDescriptionOverride, logger)
|
|
115
117
|
})
|
|
116
118
|
}
|
|
@@ -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,14 @@ 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
|
+
logger: LoggerFn = log
|
|
71
77
|
): TTool[] {
|
|
72
78
|
return tools.map((tool) => {
|
|
73
79
|
if (tool.name === missingCapabilityToolName) {
|
|
74
80
|
return tool
|
|
75
81
|
}
|
|
76
|
-
return addConversationIdToTool(tool)
|
|
82
|
+
return addConversationIdToTool(tool, logger)
|
|
77
83
|
})
|
|
78
84
|
}
|
|
79
85
|
|
|
@@ -131,25 +137,6 @@ export function extractConversationId(args: unknown): string | undefined {
|
|
|
131
137
|
return trimmed.length > 0 ? trimmed : undefined
|
|
132
138
|
}
|
|
133
139
|
|
|
134
|
-
export function cloneRequestWithoutConversationId<
|
|
135
|
-
TRequest extends { params?: { arguments?: unknown; [k: string]: unknown } },
|
|
136
|
-
>(request: TRequest): TRequest {
|
|
137
|
-
if (!request.params || typeof request.params !== 'object') {
|
|
138
|
-
return request
|
|
139
|
-
}
|
|
140
|
-
const args = request.params.arguments
|
|
141
|
-
if (!(args && typeof args === 'object')) {
|
|
142
|
-
return request
|
|
143
|
-
}
|
|
144
|
-
return {
|
|
145
|
-
...request,
|
|
146
|
-
params: {
|
|
147
|
-
...request.params,
|
|
148
|
-
arguments: stripConversationId(args) as typeof request.params.arguments,
|
|
149
|
-
},
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
140
|
export function stripConversationId(args: unknown): unknown {
|
|
154
141
|
if (!args || typeof args !== 'object' || !(CONVERSATION_ID_PARAM_NAME in (args as Record<string, unknown>))) {
|
|
155
142
|
return args
|
|
@@ -11,19 +11,16 @@ import type {
|
|
|
11
11
|
RegisteredTool,
|
|
12
12
|
ToolCallback,
|
|
13
13
|
} from '../types'
|
|
14
|
-
import {
|
|
15
|
-
|
|
14
|
+
import {
|
|
15
|
+
analyticsOwnsParameter,
|
|
16
|
+
getAnalyticsParameterOwnership,
|
|
17
|
+
stripOwnedAnalyticsArguments,
|
|
18
|
+
} from './analytics-parameters'
|
|
16
19
|
import { isContextEnabled } from './context-parameters'
|
|
17
20
|
import { MCPAnalyticsEventType } from './event-types'
|
|
18
21
|
import { getServerTrackingData } from './internal'
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
createWrappedTool,
|
|
22
|
-
getObjectShape,
|
|
23
|
-
getToolFunction,
|
|
24
|
-
hasToolFunction,
|
|
25
|
-
isZodRawShapeCompat,
|
|
26
|
-
} from './mcp-sdk-compat'
|
|
22
|
+
import type { LoggerFn } from './logger'
|
|
23
|
+
import { createWrappedTool, getToolFunction, hasToolFunction } from './mcp-sdk-compat'
|
|
27
24
|
import { handleReportMissing, resolveMissingCapabilityToolName } from './tools'
|
|
28
25
|
import {
|
|
29
26
|
handleInitializeRequest,
|
|
@@ -31,6 +28,7 @@ import {
|
|
|
31
28
|
patchRequestHandlers,
|
|
32
29
|
captureToolCall,
|
|
33
30
|
readToolMetaCategory,
|
|
31
|
+
type HandlerPatch,
|
|
34
32
|
} from './instrumentation'
|
|
35
33
|
import { getContextArgument } from './tracing-helpers'
|
|
36
34
|
|
|
@@ -52,55 +50,24 @@ function isCallbackUpdate(value: unknown): value is { callback: ToolCallback } {
|
|
|
52
50
|
return !!value && typeof value === 'object' && 'callback' in value && typeof value.callback === 'function'
|
|
53
51
|
}
|
|
54
52
|
|
|
55
|
-
function analyticsOwnsParameter(inputSchema: unknown, parameterName: string): boolean {
|
|
56
|
-
if (!inputSchema || typeof inputSchema !== 'object') {
|
|
57
|
-
return canInjectAnalyticsParameter(undefined, parameterName)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (isZodRawShapeCompat(inputSchema)) {
|
|
61
|
-
return canInjectAnalyticsParameter({ properties: inputSchema }, parameterName)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const shape = getObjectShape(inputSchema)
|
|
65
|
-
if (shape) {
|
|
66
|
-
return canInjectAnalyticsParameter({ properties: shape }, parameterName)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const schema = inputSchema as Record<string, unknown>
|
|
70
|
-
if ('_def' in schema || '_zod' in schema || '~standard' in schema) {
|
|
71
|
-
// The MCP SDK advertises non-object schemas as an empty object schema.
|
|
72
|
-
return canInjectAnalyticsParameter(undefined, parameterName)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return canInjectAnalyticsParameter(schema, parameterName)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function stripOwnedAnalyticsArguments(
|
|
79
|
-
args: unknown,
|
|
80
|
-
ownership: { context: boolean; conversationId: boolean }
|
|
81
|
-
): unknown {
|
|
82
|
-
let cleanedArgs = args
|
|
83
|
-
if (ownership.context && cleanedArgs && typeof cleanedArgs === 'object' && 'context' in cleanedArgs) {
|
|
84
|
-
const { context: _context, ...rest } = cleanedArgs
|
|
85
|
-
cleanedArgs = rest
|
|
86
|
-
}
|
|
87
|
-
return ownership.conversationId ? stripConversationId(cleanedArgs) : cleanedArgs
|
|
88
|
-
}
|
|
89
|
-
|
|
90
53
|
function addTracingToToolRegistry(
|
|
91
54
|
tools: Record<string, RegisteredTool>,
|
|
92
|
-
server: HighLevelMCPServerLike
|
|
55
|
+
server: HighLevelMCPServerLike,
|
|
56
|
+
logger: LoggerFn
|
|
93
57
|
): Record<string, RegisteredTool> {
|
|
94
58
|
return Object.fromEntries(
|
|
95
|
-
Object.entries(tools).map(([name, tool]) => [
|
|
59
|
+
Object.entries(tools).map(([name, tool]) => [
|
|
60
|
+
name,
|
|
61
|
+
addTracingToToolCallbackInternal(tool, name, server, () => tool.inputSchema, logger),
|
|
62
|
+
])
|
|
96
63
|
)
|
|
97
64
|
}
|
|
98
65
|
|
|
99
|
-
function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
|
|
66
|
+
function setupListenerToRegisteredTools(server: HighLevelMCPServerLike, logger: LoggerFn): void {
|
|
100
67
|
try {
|
|
101
68
|
const data = getServerTrackingData(server.server as MCPServerLike)
|
|
102
69
|
if (!data) {
|
|
103
|
-
|
|
70
|
+
logger('Warning: Cannot setup listener - no tracking data found')
|
|
104
71
|
return
|
|
105
72
|
}
|
|
106
73
|
|
|
@@ -112,19 +79,19 @@ function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
|
|
|
112
79
|
data.toolDescriptions.set(property, value.description)
|
|
113
80
|
}
|
|
114
81
|
if ((value as ProcessedRegisteredTool)[MCP_ANALYTICS_PROCESSED]) {
|
|
115
|
-
|
|
82
|
+
logger(`Tool ${String(property)} already processed, skipping proxy wrapping`)
|
|
116
83
|
return Reflect.set(target, property, value)
|
|
117
84
|
}
|
|
118
85
|
|
|
119
86
|
if (wrappedCallbacks.has(getToolFunction(value))) {
|
|
120
|
-
|
|
87
|
+
logger(`Tool ${String(property)} callback already wrapped, skipping proxy wrapping`)
|
|
121
88
|
return Reflect.set(target, property, value)
|
|
122
89
|
}
|
|
123
90
|
|
|
124
91
|
// The MCP SDK's registry copy is currently stale after update(), but keep this lookup lazy so
|
|
125
92
|
// ownership follows the current schema once the registry reflects live tool state.
|
|
126
93
|
const getCurrentInputSchema = () => value.inputSchema
|
|
127
|
-
const nextValue = addTracingToToolCallbackInternal(value, property, server, getCurrentInputSchema)
|
|
94
|
+
const nextValue = addTracingToToolCallbackInternal(value, property, server, getCurrentInputSchema, logger)
|
|
128
95
|
|
|
129
96
|
if (typeof nextValue.update === 'function') {
|
|
130
97
|
const originalUpdate = nextValue.update
|
|
@@ -136,7 +103,8 @@ function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
|
|
|
136
103
|
{ callback: updateObj.callback },
|
|
137
104
|
property,
|
|
138
105
|
server,
|
|
139
|
-
getCurrentInputSchema
|
|
106
|
+
getCurrentInputSchema,
|
|
107
|
+
logger
|
|
140
108
|
)
|
|
141
109
|
updateObj.callback = getToolFunction(wrappedTool)
|
|
142
110
|
}
|
|
@@ -149,7 +117,7 @@ function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
|
|
|
149
117
|
|
|
150
118
|
return Reflect.set(target, property, value)
|
|
151
119
|
} catch (error) {
|
|
152
|
-
|
|
120
|
+
logger(`Warning: Error in proxy set handler for tool ${String(property)} - ${error}`)
|
|
153
121
|
return Reflect.set(target, property, value)
|
|
154
122
|
}
|
|
155
123
|
},
|
|
@@ -170,9 +138,9 @@ function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
|
|
|
170
138
|
const originalTools = server._registeredTools || {}
|
|
171
139
|
server._registeredTools = new Proxy(originalTools, handler)
|
|
172
140
|
|
|
173
|
-
|
|
141
|
+
logger('Successfully set up listener for new tool registrations')
|
|
174
142
|
} catch (error) {
|
|
175
|
-
|
|
143
|
+
logger(`Warning: Failed to setup listener for registered tools - ${error}`)
|
|
176
144
|
}
|
|
177
145
|
}
|
|
178
146
|
|
|
@@ -189,19 +157,20 @@ function addTracingToToolCallbackInternal(
|
|
|
189
157
|
tool: RegisteredTool,
|
|
190
158
|
toolName: string,
|
|
191
159
|
server: HighLevelMCPServerLike,
|
|
192
|
-
getCurrentInputSchema: () => unknown
|
|
160
|
+
getCurrentInputSchema: () => unknown,
|
|
161
|
+
logger: LoggerFn
|
|
193
162
|
): RegisteredTool {
|
|
194
163
|
const originalCallback = getToolFunction(tool)
|
|
195
164
|
const options = getServerTrackingData(server.server as MCPServerLike)?.options
|
|
196
165
|
const missingToolName = resolveMissingCapabilityToolName(options)
|
|
197
166
|
|
|
198
167
|
if (wrappedCallbacks.has(originalCallback)) {
|
|
199
|
-
|
|
168
|
+
logger(`Tool ${toolName} callback already wrapped, skipping re-wrap`)
|
|
200
169
|
return tool
|
|
201
170
|
}
|
|
202
171
|
|
|
203
172
|
if ((tool as ProcessedRegisteredTool)[MCP_ANALYTICS_PROCESSED]) {
|
|
204
|
-
|
|
173
|
+
logger(`Tool ${toolName} already processed, skipping re-wrap`)
|
|
205
174
|
return tool
|
|
206
175
|
}
|
|
207
176
|
|
|
@@ -258,14 +227,16 @@ function addTracingToToolCallbackInternal(
|
|
|
258
227
|
}
|
|
259
228
|
|
|
260
229
|
async function handleToolCallRequest(
|
|
230
|
+
highLevelServer: HighLevelMCPServerLike,
|
|
261
231
|
server: MCPServerLike,
|
|
262
232
|
originalCallToolHandler: MCPRequestHandler,
|
|
263
233
|
request: MCPRequest,
|
|
264
|
-
extra: MCPRequestExtra
|
|
234
|
+
extra: MCPRequestExtra,
|
|
235
|
+
logger: LoggerFn
|
|
265
236
|
): Promise<unknown> {
|
|
266
237
|
const data = getServerTrackingData(server)
|
|
267
238
|
if (!data) {
|
|
268
|
-
|
|
239
|
+
logger(
|
|
269
240
|
'Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.'
|
|
270
241
|
)
|
|
271
242
|
return await originalCallToolHandler(request, extra)
|
|
@@ -280,20 +251,23 @@ async function handleToolCallRequest(
|
|
|
280
251
|
extra,
|
|
281
252
|
eventType: MCPAnalyticsEventType.mcpMissingCapability,
|
|
282
253
|
explicitContextIntent: context,
|
|
283
|
-
execute: async () => handleReportMissing({ context }),
|
|
254
|
+
execute: async () => handleReportMissing({ context }, data.logger),
|
|
284
255
|
})
|
|
285
256
|
}
|
|
286
257
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
//
|
|
258
|
+
const toolName = request.params?.name
|
|
259
|
+
const registeredTool = toolName ? highLevelServer._registeredTools[toolName] : undefined
|
|
260
|
+
|
|
261
|
+
// Strip SDK-owned arguments before the MCP SDK validates the registered Zod
|
|
262
|
+
// schema. The callback wrapper repeats the ownership-aware cleanup as a
|
|
263
|
+
// defensive fallback for direct callback invocation.
|
|
291
264
|
return await captureToolCall({
|
|
292
265
|
server,
|
|
293
266
|
data,
|
|
294
267
|
request,
|
|
295
268
|
extra,
|
|
296
|
-
execute: () => originalCallToolHandler(
|
|
269
|
+
execute: (downstreamRequest) => originalCallToolHandler(downstreamRequest as MCPRequest, extra),
|
|
270
|
+
parameterOwnership: registeredTool ? getAnalyticsParameterOwnership(registeredTool.inputSchema) : undefined,
|
|
297
271
|
takeCapturedError: () => {
|
|
298
272
|
const captured = extra?.__mcp_analytics_error
|
|
299
273
|
if (extra) {
|
|
@@ -304,29 +278,32 @@ async function handleToolCallRequest(
|
|
|
304
278
|
})
|
|
305
279
|
}
|
|
306
280
|
|
|
307
|
-
export function instrumentHighLevelServer(server: HighLevelMCPServerLike): void {
|
|
281
|
+
export function instrumentHighLevelServer(server: HighLevelMCPServerLike, logger: LoggerFn): void {
|
|
308
282
|
try {
|
|
309
283
|
const lowLevelServer = server.server
|
|
310
284
|
const mcpAnalyticsData = getServerTrackingData(lowLevelServer)
|
|
311
285
|
|
|
312
286
|
// Patch already existing handlers, and patch setRequestHandler to capture dynamically created handlers.
|
|
313
|
-
const handlers = {
|
|
314
|
-
initialize:
|
|
315
|
-
|
|
316
|
-
'tools/
|
|
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),
|
|
317
294
|
}
|
|
318
295
|
patchRequestHandlers(lowLevelServer, handlers)
|
|
319
296
|
|
|
320
|
-
server._registeredTools = addTracingToToolRegistry(server._registeredTools, server)
|
|
297
|
+
server._registeredTools = addTracingToToolRegistry(server._registeredTools, server, logger)
|
|
321
298
|
|
|
322
299
|
if (mcpAnalyticsData) {
|
|
323
300
|
seedToolDescriptionsFromRegistry(mcpAnalyticsData.toolDescriptions, server._registeredTools)
|
|
324
301
|
seedToolCategoriesFromRegistry(mcpAnalyticsData.toolCategories, server._registeredTools)
|
|
325
302
|
}
|
|
326
303
|
|
|
327
|
-
setupListenerToRegisteredTools(server)
|
|
304
|
+
setupListenerToRegisteredTools(server, logger)
|
|
328
305
|
} catch (error) {
|
|
329
|
-
|
|
306
|
+
logger(`Warning: Failed to setup tool call instrumentation - ${error}`)
|
|
330
307
|
}
|
|
331
308
|
}
|
|
332
309
|
|