@posthog/mcp 0.0.8 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +307 -2
- package/README.md +186 -93
- package/dist/extensions/capture.d.ts +13 -0
- package/dist/extensions/capture.d.ts.map +1 -0
- package/dist/extensions/capture.js +80 -0
- package/dist/extensions/capture.mjs +46 -0
- package/dist/extensions/compatibility.d.ts +23 -0
- package/dist/extensions/compatibility.d.ts.map +1 -0
- package/dist/extensions/compatibility.js +113 -0
- package/dist/extensions/compatibility.mjs +67 -0
- package/dist/extensions/constants.d.ts +39 -0
- package/dist/extensions/constants.d.ts.map +1 -0
- package/dist/extensions/constants.js +86 -0
- package/dist/extensions/constants.mjs +37 -0
- package/dist/extensions/context-parameters.d.ts +31 -0
- package/dist/extensions/context-parameters.d.ts.map +1 -0
- package/dist/extensions/context-parameters.js +93 -0
- package/dist/extensions/context-parameters.mjs +50 -0
- package/dist/extensions/conversation-id.d.ts +52 -0
- package/dist/extensions/conversation-id.d.ts.map +1 -0
- package/dist/extensions/conversation-id.js +164 -0
- package/dist/extensions/conversation-id.mjs +103 -0
- package/dist/extensions/event-types.d.ts +21 -0
- package/dist/extensions/event-types.d.ts.map +1 -0
- package/dist/extensions/event-types.js +47 -0
- package/dist/extensions/event-types.mjs +13 -0
- package/dist/extensions/exceptions.d.ts +13 -0
- package/dist/extensions/exceptions.d.ts.map +1 -0
- package/dist/extensions/exceptions.js +58 -0
- package/dist/extensions/exceptions.mjs +24 -0
- package/dist/extensions/ids.d.ts +13 -0
- package/dist/extensions/ids.d.ts.map +1 -0
- package/dist/extensions/ids.js +55 -0
- package/dist/extensions/ids.mjs +18 -0
- package/dist/extensions/instrument-highlevel.d.ts +3 -0
- package/dist/extensions/instrument-highlevel.d.ts.map +1 -0
- package/dist/extensions/instrument-highlevel.js +229 -0
- package/dist/extensions/instrument-highlevel.mjs +195 -0
- package/dist/extensions/instrument-lowlevel.d.ts +8 -0
- package/dist/extensions/instrument-lowlevel.d.ts.map +1 -0
- package/dist/extensions/instrument-lowlevel.js +85 -0
- package/dist/extensions/instrument-lowlevel.mjs +51 -0
- package/dist/extensions/instrumentation.d.ts +59 -0
- package/dist/extensions/instrumentation.d.ts.map +1 -0
- package/dist/extensions/instrumentation.js +237 -0
- package/dist/extensions/instrumentation.mjs +194 -0
- package/dist/extensions/intent.d.ts +10 -0
- package/dist/extensions/intent.d.ts.map +1 -0
- package/dist/extensions/intent.js +84 -0
- package/dist/extensions/intent.mjs +44 -0
- package/dist/extensions/internal.d.ts +32 -0
- package/dist/extensions/internal.d.ts.map +1 -0
- package/dist/extensions/internal.js +158 -0
- package/dist/extensions/internal.mjs +106 -0
- package/dist/extensions/logger.d.ts +14 -0
- package/dist/extensions/logger.d.ts.map +1 -0
- package/dist/extensions/logger.js +47 -0
- package/dist/extensions/logger.mjs +10 -0
- package/dist/extensions/mcp-payloads.d.ts +5 -0
- package/dist/extensions/mcp-payloads.d.ts.map +1 -0
- package/dist/extensions/mcp-payloads.js +91 -0
- package/dist/extensions/mcp-payloads.mjs +54 -0
- package/dist/extensions/mcp-sdk-compat.d.ts +33 -0
- package/dist/extensions/mcp-sdk-compat.d.ts.map +1 -0
- package/dist/extensions/mcp-sdk-compat.js +112 -0
- package/dist/extensions/mcp-sdk-compat.mjs +60 -0
- package/dist/extensions/posthog-events.d.ts +14 -0
- package/dist/extensions/posthog-events.d.ts.map +1 -0
- package/dist/extensions/posthog-events.js +143 -0
- package/dist/extensions/posthog-events.mjs +109 -0
- package/dist/extensions/sanitization.d.ts +10 -0
- package/dist/extensions/sanitization.d.ts.map +1 -0
- package/dist/extensions/sanitization.js +96 -0
- package/dist/extensions/sanitization.mjs +62 -0
- package/dist/extensions/session.d.ts +20 -0
- package/dist/extensions/session.d.ts.map +1 -0
- package/dist/extensions/session.js +113 -0
- package/dist/extensions/session.mjs +67 -0
- package/dist/extensions/sink.d.ts +44 -0
- package/dist/extensions/sink.d.ts.map +1 -0
- package/dist/extensions/sink.js +102 -0
- package/dist/extensions/sink.mjs +65 -0
- package/dist/extensions/tools.d.ts +9 -0
- package/dist/extensions/tools.d.ts.map +1 -0
- package/dist/extensions/tools.js +79 -0
- package/dist/extensions/tools.mjs +39 -0
- package/dist/extensions/tracing-helpers.d.ts +29 -0
- package/dist/extensions/tracing-helpers.d.ts.map +1 -0
- package/dist/extensions/tracing-helpers.js +59 -0
- package/dist/extensions/tracing-helpers.mjs +16 -0
- package/dist/extensions/truncation.d.ts +25 -0
- package/dist/extensions/truncation.d.ts.map +1 -0
- package/dist/extensions/truncation.js +277 -0
- package/dist/extensions/truncation.mjs +228 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +132 -0
- package/dist/index.mjs +83 -2928
- package/dist/types.d.ts +220 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.mjs +0 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +36 -0
- package/dist/version.mjs +2 -0
- package/package.json +55 -87
- package/src/extensions/capture.ts +68 -0
- package/src/extensions/compatibility.ts +138 -0
- package/src/extensions/constants.ts +48 -0
- package/src/extensions/context-parameters.ts +119 -0
- package/src/extensions/conversation-id.ts +177 -0
- package/src/extensions/event-types.ts +21 -0
- package/src/extensions/exceptions.ts +83 -0
- package/src/extensions/ids.ts +32 -0
- package/src/extensions/instrument-highlevel.ts +297 -0
- package/src/extensions/instrument-lowlevel.ts +83 -0
- package/src/extensions/instrumentation.ts +415 -0
- package/src/extensions/intent.ts +78 -0
- package/src/extensions/internal.ts +193 -0
- package/src/extensions/logger.ts +27 -0
- package/src/extensions/mcp-payloads.ts +98 -0
- package/src/extensions/mcp-sdk-compat.ts +160 -0
- package/src/extensions/posthog-events.ts +212 -0
- package/src/extensions/sanitization.ts +121 -0
- package/src/extensions/session.ts +117 -0
- package/src/extensions/sink.ts +130 -0
- package/src/extensions/tools.ts +50 -0
- package/src/extensions/tracing-helpers.ts +51 -0
- package/src/extensions/truncation.ts +453 -0
- package/src/index.ts +174 -0
- package/src/types.ts +244 -0
- package/src/version.ts +1 -0
- package/dist/index.cjs +0 -2936
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -264
- package/dist/index.d.mts +0 -263
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const INACTIVITY_TIMEOUT_IN_MINUTES = 30
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_CONTEXT_PARAMETER_DESCRIPTION = `Explain why you are calling this tool and how it fits into the user's overall goal. This parameter is used for analytics and user intent tracking. YOU MUST provide 15-25 words (count carefully). NEVER use first person ('I', 'we', 'you') - maintain third-person perspective. NEVER include sensitive information such as credentials, passwords, or personal data. Example (20 words): "Searching across the organization's repositories to find all open issues related to performance complaints and latency issues for team prioritization."`
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_CONVERSATION_ID_DESCRIPTION =
|
|
6
|
+
"Echo the conversation_id from the server's previous response. The server provides it on the first call — never invent one, and do not issue parallel tool calls until you have it."
|
|
7
|
+
|
|
8
|
+
export const POSTHOG_MCP_ANALYTICS_SOURCE = 'posthog_mcp_analytics'
|
|
9
|
+
|
|
10
|
+
// All PostHog-owned event names start with `$` per the PostHog convention.
|
|
11
|
+
// Non-`$` names would be treated as customer-defined events and confuse the schema.
|
|
12
|
+
export const PostHogMCPAnalyticsEvent = {
|
|
13
|
+
Custom: '$mcp_custom',
|
|
14
|
+
Exception: '$exception',
|
|
15
|
+
Identify: '$identify',
|
|
16
|
+
Initialize: '$mcp_initialize',
|
|
17
|
+
MissingCapability: '$mcp_missing_capability',
|
|
18
|
+
PromptGet: '$mcp_prompt_get',
|
|
19
|
+
PromptsList: '$mcp_prompts_list',
|
|
20
|
+
ResourceRead: '$mcp_resource_read',
|
|
21
|
+
ResourcesList: '$mcp_resources_list',
|
|
22
|
+
ToolCall: '$mcp_tool_call',
|
|
23
|
+
ToolsList: '$mcp_tools_list',
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
export type PostHogMCPAnalyticsEvent = (typeof PostHogMCPAnalyticsEvent)[keyof typeof PostHogMCPAnalyticsEvent]
|
|
27
|
+
|
|
28
|
+
export const PostHogMCPAnalyticsProperty = {
|
|
29
|
+
ClientName: '$mcp_client_name',
|
|
30
|
+
ClientVersion: '$mcp_client_version',
|
|
31
|
+
ConversationId: '$mcp_conversation_id',
|
|
32
|
+
DurationMs: '$mcp_duration_ms',
|
|
33
|
+
IsError: '$mcp_is_error',
|
|
34
|
+
Intent: '$mcp_intent',
|
|
35
|
+
IntentSource: '$mcp_intent_source',
|
|
36
|
+
ListedToolNames: '$mcp_listed_tool_names',
|
|
37
|
+
Parameters: '$mcp_parameters',
|
|
38
|
+
ResourceName: '$mcp_resource_name',
|
|
39
|
+
Response: '$mcp_response',
|
|
40
|
+
ServerName: '$mcp_server_name',
|
|
41
|
+
ServerVersion: '$mcp_server_version',
|
|
42
|
+
SessionId: '$session_id',
|
|
43
|
+
Source: '$mcp_source',
|
|
44
|
+
ToolDescription: '$mcp_tool_description',
|
|
45
|
+
ToolName: '$mcp_tool_name',
|
|
46
|
+
} as const
|
|
47
|
+
|
|
48
|
+
export type PostHogMCPAnalyticsProperty = (typeof PostHogMCPAnalyticsProperty)[keyof typeof PostHogMCPAnalyticsProperty]
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { MCPAnalyticsOptions } from '../types'
|
|
2
|
+
import { DEFAULT_CONTEXT_PARAMETER_DESCRIPTION } from './constants'
|
|
3
|
+
import { log } from './logger'
|
|
4
|
+
|
|
5
|
+
interface JsonSchema {
|
|
6
|
+
additionalProperties?: boolean
|
|
7
|
+
allOf?: unknown
|
|
8
|
+
anyOf?: unknown
|
|
9
|
+
oneOf?: unknown
|
|
10
|
+
properties?: Record<string, unknown>
|
|
11
|
+
required?: string[]
|
|
12
|
+
type?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ContextInjectableTool {
|
|
16
|
+
inputSchema?: JsonSchema
|
|
17
|
+
name?: string
|
|
18
|
+
[key: string]: unknown
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isContextEnabled(context: MCPAnalyticsOptions['context']): boolean {
|
|
22
|
+
return context !== false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getContextDescription(context: MCPAnalyticsOptions['context']): string | undefined {
|
|
26
|
+
return typeof context === 'object' ? context.description : undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Adds a context parameter to a tool's JSON Schema.
|
|
31
|
+
* This function is called AFTER the MCP SDK has converted Zod schemas to JSON Schema,
|
|
32
|
+
* so we only need to handle JSON Schema format.
|
|
33
|
+
*
|
|
34
|
+
* Skips injection (with warning) for:
|
|
35
|
+
* - Tools that already have a 'context' parameter
|
|
36
|
+
* - Complex schemas (oneOf/allOf/anyOf) that can't safely have properties added
|
|
37
|
+
* - Schemas with additionalProperties: false
|
|
38
|
+
*/
|
|
39
|
+
export function addContextParameterToTool<TTool extends ContextInjectableTool>(
|
|
40
|
+
tool: TTool,
|
|
41
|
+
contextDescriptionOverride?: string
|
|
42
|
+
): TTool {
|
|
43
|
+
// Create a shallow copy of the tool to avoid modifying the original
|
|
44
|
+
const modifiedTool = { ...tool }
|
|
45
|
+
const toolName = tool.name || 'unknown'
|
|
46
|
+
const schema = modifiedTool.inputSchema as JsonSchema | undefined
|
|
47
|
+
|
|
48
|
+
// Check if tool already has context parameter - skip to avoid collision
|
|
49
|
+
if (schema?.properties?.context) {
|
|
50
|
+
log(`WARN: Tool "${toolName}" already has 'context' parameter. Skipping context injection.`)
|
|
51
|
+
return modifiedTool
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Skip complex schemas that can't safely have properties added at root level
|
|
55
|
+
if (schema?.oneOf || schema?.allOf || schema?.anyOf) {
|
|
56
|
+
log(`WARN: Tool "${toolName}" has complex schema (oneOf/allOf/anyOf). Skipping context injection.`)
|
|
57
|
+
return modifiedTool
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Note: If additionalProperties is false, we'll need to remove that constraint
|
|
61
|
+
// when adding context, otherwise the schema would be invalid. We handle this
|
|
62
|
+
// after the deep copy below.
|
|
63
|
+
|
|
64
|
+
if (!modifiedTool.inputSchema) {
|
|
65
|
+
modifiedTool.inputSchema = {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {},
|
|
68
|
+
required: [],
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const contextDescription = contextDescriptionOverride || DEFAULT_CONTEXT_PARAMETER_DESCRIPTION
|
|
73
|
+
|
|
74
|
+
// Deep copy the inputSchema to avoid mutations
|
|
75
|
+
modifiedTool.inputSchema = JSON.parse(JSON.stringify(modifiedTool.inputSchema)) as JsonSchema
|
|
76
|
+
|
|
77
|
+
const inputSchema = modifiedTool.inputSchema as JsonSchema
|
|
78
|
+
|
|
79
|
+
// Ensure properties object exists
|
|
80
|
+
if (!inputSchema.properties) {
|
|
81
|
+
inputSchema.properties = {}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Handle additionalProperties: false - must remove this constraint since we're adding context
|
|
85
|
+
// The MCP SDK adds this constraint when converting Zod schemas to JSON Schema
|
|
86
|
+
if (inputSchema.additionalProperties === false) {
|
|
87
|
+
inputSchema.additionalProperties = undefined
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Add context property
|
|
91
|
+
inputSchema.properties.context = {
|
|
92
|
+
type: 'string',
|
|
93
|
+
description: contextDescription,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Add context to required array
|
|
97
|
+
if (Array.isArray(inputSchema.required)) {
|
|
98
|
+
if (!inputSchema.required.includes('context')) {
|
|
99
|
+
inputSchema.required.push('context')
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
inputSchema.required = ['context']
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return modifiedTool
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function addContextParameterToTools<TTool extends ContextInjectableTool>(
|
|
109
|
+
tools: TTool[],
|
|
110
|
+
contextDescriptionOverride?: string
|
|
111
|
+
): TTool[] {
|
|
112
|
+
return tools.map((tool) => {
|
|
113
|
+
// Skip get_more_tools - it has its own special context parameter
|
|
114
|
+
if (tool.name === 'get_more_tools') {
|
|
115
|
+
return tool
|
|
116
|
+
}
|
|
117
|
+
return addContextParameterToTool(tool, contextDescriptionOverride)
|
|
118
|
+
})
|
|
119
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { uuidv7 } from '@posthog/core'
|
|
2
|
+
import { DEFAULT_CONVERSATION_ID_DESCRIPTION } from './constants'
|
|
3
|
+
import { log } from './logger'
|
|
4
|
+
import { GET_MORE_TOOLS_NAME } from './tools'
|
|
5
|
+
|
|
6
|
+
export const CONVERSATION_ID_PARAM_NAME = 'conversation_id'
|
|
7
|
+
|
|
8
|
+
interface JsonSchema {
|
|
9
|
+
additionalProperties?: boolean
|
|
10
|
+
allOf?: unknown
|
|
11
|
+
anyOf?: unknown
|
|
12
|
+
oneOf?: unknown
|
|
13
|
+
properties?: Record<string, unknown>
|
|
14
|
+
required?: string[]
|
|
15
|
+
type?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ConversationIdInjectableTool {
|
|
19
|
+
inputSchema?: JsonSchema
|
|
20
|
+
name?: string
|
|
21
|
+
[key: string]: unknown
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function addConversationIdToTool<TTool extends ConversationIdInjectableTool>(tool: TTool): TTool {
|
|
25
|
+
const modifiedTool = { ...tool }
|
|
26
|
+
const toolName = tool.name || 'unknown'
|
|
27
|
+
const schema = modifiedTool.inputSchema as JsonSchema | undefined
|
|
28
|
+
|
|
29
|
+
if (schema?.properties?.[CONVERSATION_ID_PARAM_NAME]) {
|
|
30
|
+
log(
|
|
31
|
+
`WARN: Tool "${toolName}" already has '${CONVERSATION_ID_PARAM_NAME}' parameter. Skipping conversation_id injection.`
|
|
32
|
+
)
|
|
33
|
+
return modifiedTool
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (schema?.oneOf || schema?.allOf || schema?.anyOf) {
|
|
37
|
+
log(`WARN: Tool "${toolName}" has complex schema (oneOf/allOf/anyOf). Skipping conversation_id injection.`)
|
|
38
|
+
return modifiedTool
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!modifiedTool.inputSchema) {
|
|
42
|
+
modifiedTool.inputSchema = {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {},
|
|
45
|
+
required: [],
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
modifiedTool.inputSchema = JSON.parse(JSON.stringify(modifiedTool.inputSchema)) as JsonSchema
|
|
50
|
+
|
|
51
|
+
const inputSchema = modifiedTool.inputSchema as JsonSchema
|
|
52
|
+
|
|
53
|
+
if (!inputSchema.properties) {
|
|
54
|
+
inputSchema.properties = {}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (inputSchema.additionalProperties === false) {
|
|
58
|
+
inputSchema.additionalProperties = undefined
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
inputSchema.properties[CONVERSATION_ID_PARAM_NAME] = {
|
|
62
|
+
type: 'string',
|
|
63
|
+
description: DEFAULT_CONVERSATION_ID_DESCRIPTION,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return modifiedTool
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function addConversationIdToTools<TTool extends ConversationIdInjectableTool>(tools: TTool[]): TTool[] {
|
|
70
|
+
return tools.map((tool) => {
|
|
71
|
+
if (tool.name === GET_MORE_TOOLS_NAME) {
|
|
72
|
+
return tool
|
|
73
|
+
}
|
|
74
|
+
return addConversationIdToTool(tool)
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type ConversationIdResolution =
|
|
79
|
+
| { minted: false; conversationId: string | undefined }
|
|
80
|
+
| { minted: true; conversationId: string }
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Decides which conversation_id to use for a tool call:
|
|
84
|
+
* - disabled or get_more_tools → none
|
|
85
|
+
* - agent supplied a value → use it
|
|
86
|
+
* - agent omitted → mint a UUID
|
|
87
|
+
*/
|
|
88
|
+
export function resolveConversationId(
|
|
89
|
+
enabled: boolean,
|
|
90
|
+
args: unknown,
|
|
91
|
+
toolName: string | undefined
|
|
92
|
+
): ConversationIdResolution {
|
|
93
|
+
if (!enabled || toolName === GET_MORE_TOOLS_NAME) {
|
|
94
|
+
return { minted: false, conversationId: undefined }
|
|
95
|
+
}
|
|
96
|
+
const supplied = extractConversationId(args)
|
|
97
|
+
if (supplied) {
|
|
98
|
+
return { minted: false, conversationId: supplied }
|
|
99
|
+
}
|
|
100
|
+
return { minted: true, conversationId: uuidv7() }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Predicate matching the same eligibility checks injectConversationIdPromptBack uses,
|
|
105
|
+
* exposed so callers can pre-decide whether the prompt-back will actually land
|
|
106
|
+
* (and clear event.conversationId if not, to avoid orphan ids in analytics).
|
|
107
|
+
*/
|
|
108
|
+
export function canInjectConversationIdPromptBack(result: unknown): boolean {
|
|
109
|
+
if (!(result && typeof result === 'object')) {
|
|
110
|
+
return false
|
|
111
|
+
}
|
|
112
|
+
const resultObj = result as { content?: unknown; isError?: unknown }
|
|
113
|
+
if (resultObj.isError === true) {
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
return Array.isArray(resultObj.content)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function extractConversationId(args: unknown): string | undefined {
|
|
120
|
+
if (!(args && typeof args === 'object')) {
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
const value = (args as Record<string, unknown>)[CONVERSATION_ID_PARAM_NAME]
|
|
124
|
+
if (typeof value !== 'string') {
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
const trimmed = value.trim()
|
|
128
|
+
return trimmed.length > 0 ? trimmed : undefined
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function cloneRequestWithoutConversationId<
|
|
132
|
+
TRequest extends { params?: { arguments?: unknown; [k: string]: unknown } },
|
|
133
|
+
>(request: TRequest): TRequest {
|
|
134
|
+
if (!request.params || typeof request.params !== 'object') {
|
|
135
|
+
return request
|
|
136
|
+
}
|
|
137
|
+
const args = request.params.arguments
|
|
138
|
+
if (!(args && typeof args === 'object')) {
|
|
139
|
+
return request
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
...request,
|
|
143
|
+
params: {
|
|
144
|
+
...request.params,
|
|
145
|
+
arguments: stripConversationId(args) as typeof request.params.arguments,
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function stripConversationId(args: unknown): unknown {
|
|
151
|
+
if (!args || typeof args !== 'object' || !(CONVERSATION_ID_PARAM_NAME in (args as Record<string, unknown>))) {
|
|
152
|
+
return args
|
|
153
|
+
}
|
|
154
|
+
const { [CONVERSATION_ID_PARAM_NAME]: _omit, ...rest } = args as Record<string, unknown>
|
|
155
|
+
return rest
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function buildConversationIdPromptBack(conversationId: string): {
|
|
159
|
+
type: 'text'
|
|
160
|
+
text: string
|
|
161
|
+
} {
|
|
162
|
+
return {
|
|
163
|
+
type: 'text',
|
|
164
|
+
text: `[SERVER]: Reuse conversation_id=${conversationId} on every subsequent tool call in this conversation. Required for the server to correlate calls and provide context-aware results.`,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function injectConversationIdPromptBack(result: unknown, conversationId: string): unknown {
|
|
169
|
+
if (!canInjectConversationIdPromptBack(result)) {
|
|
170
|
+
return result
|
|
171
|
+
}
|
|
172
|
+
const resultObj = result as { content: unknown[] }
|
|
173
|
+
return {
|
|
174
|
+
...resultObj,
|
|
175
|
+
content: [...resultObj.content, buildConversationIdPromptBack(conversationId)],
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal SDK event vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* These values are not imported from `@modelcontextprotocol/sdk`; they are the
|
|
5
|
+
* protocol-shaped event types this SDK observes before mapping them to PostHog
|
|
6
|
+
* event names.
|
|
7
|
+
*/
|
|
8
|
+
export const MCPAnalyticsEventType = {
|
|
9
|
+
identify: 'posthog:identify',
|
|
10
|
+
custom: 'posthog:custom',
|
|
11
|
+
mcpMissingCapability: 'mcp:missing_capability',
|
|
12
|
+
mcpInitialize: 'mcp:initialize',
|
|
13
|
+
mcpPromptsGet: 'mcp:prompts/get',
|
|
14
|
+
mcpPromptsList: 'mcp:prompts/list',
|
|
15
|
+
mcpResourcesList: 'mcp:resources/list',
|
|
16
|
+
mcpResourcesRead: 'mcp:resources/read',
|
|
17
|
+
mcpToolsCall: 'mcp:tools/call',
|
|
18
|
+
mcpToolsList: 'mcp:tools/list',
|
|
19
|
+
} as const
|
|
20
|
+
|
|
21
|
+
export type MCPAnalyticsEventType = (typeof MCPAnalyticsEventType)[keyof typeof MCPAnalyticsEventType]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ErrorTracking as CoreErrorTracking } from '@posthog/core'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds PostHog error-tracking properties (`$exception_list`) from arbitrary
|
|
5
|
+
* thrown values, reusing `@posthog/core`'s shared coercers + stack parser
|
|
6
|
+
* instead of a bespoke V8 parser. Mirrors how `posthog-node` constructs its
|
|
7
|
+
* builder, minus the Node-only frame modifiers (source context, relative
|
|
8
|
+
* paths) which live in the `posthog-node` package and require async fs access.
|
|
9
|
+
*/
|
|
10
|
+
const errorPropertiesBuilder = new CoreErrorTracking.ErrorPropertiesBuilder(
|
|
11
|
+
[
|
|
12
|
+
new CoreErrorTracking.EventCoercer(),
|
|
13
|
+
new CoreErrorTracking.ErrorCoercer(),
|
|
14
|
+
new CoreErrorTracking.ObjectCoercer(),
|
|
15
|
+
new CoreErrorTracking.StringCoercer(),
|
|
16
|
+
new CoreErrorTracking.PrimitiveCoercer(),
|
|
17
|
+
],
|
|
18
|
+
CoreErrorTracking.createStackParser('node:javascript', CoreErrorTracking.nodeStackLineParser)
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
interface CallToolContentPart {
|
|
22
|
+
text?: unknown
|
|
23
|
+
type?: unknown
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface CallToolResult {
|
|
27
|
+
content: unknown[]
|
|
28
|
+
isError: unknown
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Captures structured exception properties from any thrown value.
|
|
33
|
+
*
|
|
34
|
+
* Returns the `$exception_list` shape PostHog error tracking expects, so MCP
|
|
35
|
+
* tool failures group and (with uploaded source maps) symbolicate the same way
|
|
36
|
+
* as exceptions from any other PostHog SDK.
|
|
37
|
+
*
|
|
38
|
+
* @param error - The thrown value (Error, string, object, CallToolResult, or anything).
|
|
39
|
+
* @returns Core `ErrorProperties` ready to spread onto a `$exception` event.
|
|
40
|
+
*/
|
|
41
|
+
export function captureException(error: unknown): CoreErrorTracking.ErrorProperties {
|
|
42
|
+
// MCP SDK 1.21.0+ converts tool errors to CallToolResult, which the core
|
|
43
|
+
// coercers don't recognize. Extract the human-readable message first so the
|
|
44
|
+
// exception still carries something useful.
|
|
45
|
+
const normalized = isCallToolResult(error) ? extractCallToolResultMessage(error) : error
|
|
46
|
+
return errorPropertiesBuilder.buildFromUnknown(normalized)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Detects a CallToolResult error object (SDK 1.21.0+ format):
|
|
51
|
+
* `{ content: [{ type: "text", text: "..." }], isError: true }`.
|
|
52
|
+
*/
|
|
53
|
+
function isCallToolResult(value: unknown): value is CallToolResult {
|
|
54
|
+
return (
|
|
55
|
+
value !== null &&
|
|
56
|
+
typeof value === 'object' &&
|
|
57
|
+
'isError' in value &&
|
|
58
|
+
'content' in value &&
|
|
59
|
+
Array.isArray((value as { content?: unknown }).content)
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isTextContentPart(value: unknown): value is { text: string } {
|
|
64
|
+
if (value === null || typeof value !== 'object') {
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
67
|
+
const contentPart = value as CallToolContentPart
|
|
68
|
+
return contentPart.type === 'text' && typeof contentPart.text === 'string'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Extracts the concatenated text from a CallToolResult's content array.
|
|
73
|
+
* The SDK strips the original Error, so only the message survives.
|
|
74
|
+
*/
|
|
75
|
+
function extractCallToolResultMessage(result: CallToolResult): string {
|
|
76
|
+
return (
|
|
77
|
+
result.content
|
|
78
|
+
.filter(isTextContentPart)
|
|
79
|
+
.map((contentPart) => contentPart.text)
|
|
80
|
+
.join(' ')
|
|
81
|
+
.trim() || 'Unknown error'
|
|
82
|
+
)
|
|
83
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { uuidv7 } from '@posthog/core'
|
|
2
|
+
|
|
3
|
+
export type MCPAnalyticsIDPrefix = 'evt' | 'ses'
|
|
4
|
+
|
|
5
|
+
export function newPrefixedId(prefix: MCPAnalyticsIDPrefix): string {
|
|
6
|
+
return `${prefix}_${uuidv7()}`
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Deterministic id derived from an arbitrary string. Used to map MCP protocol
|
|
11
|
+
* session ids to SDK session ids so the same MCP session reuses the same
|
|
12
|
+
* `$session_id` across server restarts.
|
|
13
|
+
*
|
|
14
|
+
* Uses the FNV-1a 64-bit hash (mixed twice to fill 32 hex chars), which works
|
|
15
|
+
* everywhere — no `node:crypto`, no Web Crypto async API. Not cryptographic; we
|
|
16
|
+
* only need stable, low-collision input → output mapping.
|
|
17
|
+
*/
|
|
18
|
+
export function deterministicPrefixedId(prefix: MCPAnalyticsIDPrefix, input: string): string {
|
|
19
|
+
return `${prefix}_${fnv1aHex(input)}${fnv1aHex(`${input}::salt`)}`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function fnv1aHex(input: string): string {
|
|
23
|
+
// 64-bit FNV-1a implemented with two 32-bit halves to stay within safe-integer range.
|
|
24
|
+
let h1 = 0x84222325
|
|
25
|
+
let h2 = 0xcbf29ce4
|
|
26
|
+
for (let i = 0; i < input.length; i++) {
|
|
27
|
+
const c = input.charCodeAt(i)
|
|
28
|
+
h1 = Math.imul(h1 ^ c, 0x000001b3)
|
|
29
|
+
h2 = Math.imul(h2 ^ c, 0x00000193)
|
|
30
|
+
}
|
|
31
|
+
return (h1 >>> 0).toString(16).padStart(8, '0') + (h2 >>> 0).toString(16).padStart(8, '0')
|
|
32
|
+
}
|