@posthog/mcp 0.18.0 → 0.19.0
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/constants.d.ts +3 -1
- package/dist/extensions/constants.d.ts.map +1 -1
- package/dist/extensions/constants.js +3 -1
- package/dist/extensions/constants.mjs +3 -1
- package/dist/extensions/instrument-highlevel.d.ts.map +1 -1
- package/dist/extensions/instrument-highlevel.js +1 -0
- package/dist/extensions/instrument-highlevel.mjs +1 -0
- package/dist/extensions/instrumentation.d.ts +1 -0
- package/dist/extensions/instrumentation.d.ts.map +1 -1
- package/dist/extensions/instrumentation.js +27 -2
- package/dist/extensions/instrumentation.mjs +28 -3
- package/dist/extensions/internal.d.ts +9 -8
- package/dist/extensions/internal.d.ts.map +1 -1
- package/dist/extensions/internal.js +17 -12
- package/dist/extensions/internal.mjs +15 -13
- package/dist/extensions/mcp-sdk-compat.d.ts +7 -0
- package/dist/extensions/mcp-sdk-compat.d.ts.map +1 -1
- package/dist/extensions/mcp-sdk-compat.js +21 -3
- package/dist/extensions/mcp-sdk-compat.mjs +17 -2
- package/dist/extensions/tool-input.d.ts +9 -0
- package/dist/extensions/tool-input.d.ts.map +1 -0
- package/dist/extensions/tool-input.js +117 -0
- package/dist/extensions/tool-input.mjs +79 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.mjs +3 -1
- package/dist/types.d.ts +31 -1
- 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/constants.ts +3 -1
- package/src/extensions/instrument-highlevel.ts +1 -0
- package/src/extensions/instrumentation.ts +37 -1
- package/src/extensions/internal.ts +18 -17
- package/src/extensions/mcp-sdk-compat.ts +38 -1
- package/src/extensions/tool-input.ts +92 -0
- package/src/index.ts +6 -1
- package/src/types.ts +32 -1
- package/src/version.ts +1 -1
|
@@ -109,7 +109,44 @@ function isZodTypeLike(value: unknown): boolean {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
export function isZodRawShapeCompat(schema: unknown): schema is Record<string, unknown> {
|
|
112
|
-
|
|
112
|
+
// A Zod v4 pipe exposes its `in` and `out` schemas as own fields, so a Zod schema is never a raw shape
|
|
113
|
+
return !!schema && typeof schema === 'object' && !isZodTypeLike(schema) && Object.values(schema).some(isZodTypeLike)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface ZodWrapperDef {
|
|
117
|
+
type?: unknown
|
|
118
|
+
schema?: unknown
|
|
119
|
+
in?: unknown
|
|
120
|
+
out?: unknown
|
|
121
|
+
innerType?: unknown
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const MAX_UNWRAP_DEPTH = 8
|
|
125
|
+
|
|
126
|
+
function zodDef(schema: unknown): ZodWrapperDef | undefined {
|
|
127
|
+
return (isZ4Schema(schema) ? (schema as ZodV4Internal)._zod?.def : (schema as ZodV3Internal)._def) as
|
|
128
|
+
| ZodWrapperDef
|
|
129
|
+
| undefined
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Follows Zod wrappers to the schema that parses the caller's input: v3 effects
|
|
134
|
+
* (refine, transform, preprocess) and pipelines, v4 pipes (which include
|
|
135
|
+
* transforms), and optional, nullable, default, catch, and readonly wrappers.
|
|
136
|
+
* A v4 `z.preprocess` is a pipe whose input side is the transform, so its output side is followed.
|
|
137
|
+
*/
|
|
138
|
+
export function unwrapInputSchema(schema: unknown): unknown {
|
|
139
|
+
let current = schema
|
|
140
|
+
for (let depth = 0; depth < MAX_UNWRAP_DEPTH && isZodTypeLike(current); depth++) {
|
|
141
|
+
const def = zodDef(current)
|
|
142
|
+
const pipeInput = def?.in !== undefined && zodDef(def.in)?.type === 'transform' ? def.out : def?.in
|
|
143
|
+
const inner = def?.schema ?? pipeInput ?? def?.innerType
|
|
144
|
+
if (!inner) {
|
|
145
|
+
break
|
|
146
|
+
}
|
|
147
|
+
current = inner
|
|
148
|
+
}
|
|
149
|
+
return current
|
|
113
150
|
}
|
|
114
151
|
|
|
115
152
|
export function getObjectShape(schema: unknown): Record<string, unknown> | undefined {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { InputAliasMap, JsonRecord, ShouldRecordInputKeyFn, ToolInputOptions } from '../types'
|
|
2
|
+
import { PostHogMCPAnalyticsProperty } from './constants'
|
|
3
|
+
import { getObjectShape, isZodRawShapeCompat, unwrapInputSchema } from './mcp-sdk-compat'
|
|
4
|
+
|
|
5
|
+
const MAX_INPUT_KEYS = 20
|
|
6
|
+
const MAX_KEY_LENGTH = 64
|
|
7
|
+
const ANALYTICS_KEYS = new Set(['context', 'llm_model', 'conversation_id'])
|
|
8
|
+
|
|
9
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
10
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function declaredProperties(schema: unknown): Record<string, unknown> | undefined {
|
|
14
|
+
if (!isRecord(schema)) return undefined
|
|
15
|
+
const properties = schema.properties
|
|
16
|
+
if (isZodRawShapeCompat(schema)) return schema
|
|
17
|
+
return getObjectShape(unwrapInputSchema(schema)) ?? (isRecord(properties) ? properties : undefined)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const recordDeclaredOnly: ShouldRecordInputKeyFn = (_key, { declared }) => declared
|
|
21
|
+
|
|
22
|
+
function shouldRecord(fn: ShouldRecordInputKeyFn, key: string, declared: boolean): boolean {
|
|
23
|
+
try {
|
|
24
|
+
return fn(key, { declared }) === true
|
|
25
|
+
} catch {
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function aliasNames(aliases: InputAliasMap | undefined): string[] {
|
|
31
|
+
if (!isRecord(aliases)) return []
|
|
32
|
+
return Object.values(aliases).flatMap((names) =>
|
|
33
|
+
Array.isArray(names) ? names.filter((name): name is string => typeof name === 'string') : []
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Each alias the server needed: the canonical name is absent and this is the first of its
|
|
39
|
+
* aliases present, the same order a normalizer that fills the canonical from its aliases uses.
|
|
40
|
+
*/
|
|
41
|
+
function describeAliasesUsed(aliases: InputAliasMap | undefined, input: Record<string, unknown>): string[] {
|
|
42
|
+
if (!isRecord(aliases)) return []
|
|
43
|
+
const used: string[] = []
|
|
44
|
+
for (const [canonical, names] of Object.entries(aliases)) {
|
|
45
|
+
if (!Array.isArray(names) || Object.prototype.hasOwnProperty.call(input, canonical)) continue
|
|
46
|
+
const alias = names.find((name) => typeof name === 'string' && Object.prototype.hasOwnProperty.call(input, name))
|
|
47
|
+
if (alias && alias.length <= MAX_KEY_LENGTH && canonical.length <= MAX_KEY_LENGTH) {
|
|
48
|
+
used.push(`${alias}:${canonical}`)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return used.sort().slice(0, MAX_INPUT_KEYS)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Describe the original arguments without reading their values.
|
|
56
|
+
* Pass a server-owned JSON Schema or Zod object schema, never a schema from the caller.
|
|
57
|
+
* By default unknown names become `[redacted]` because an argument name can contain private data;
|
|
58
|
+
* `shouldRecordInputKey` replaces that rule.
|
|
59
|
+
*/
|
|
60
|
+
export function getToolInputProperties(input: unknown, inputSchema?: unknown, options?: ToolInputOptions): JsonRecord {
|
|
61
|
+
try {
|
|
62
|
+
if (!isRecord(input)) return {}
|
|
63
|
+
const prototype = Object.getPrototypeOf(input)
|
|
64
|
+
if (prototype !== null && prototype !== Object.prototype) return {}
|
|
65
|
+
const properties = declaredProperties(inputSchema)
|
|
66
|
+
const known = new Set([...Object.keys(properties ?? {}), ...aliasNames(options?.inputAliases)])
|
|
67
|
+
const keys = Object.keys(input).filter((key) => known.has(key) || !ANALYTICS_KEYS.has(key))
|
|
68
|
+
const record = options?.shouldRecordInputKey ?? recordDeclaredOnly
|
|
69
|
+
const declared: string[] = []
|
|
70
|
+
const undeclared: string[] = []
|
|
71
|
+
let hasRedacted = false
|
|
72
|
+
for (const key of keys) {
|
|
73
|
+
const isDeclared = known.has(key)
|
|
74
|
+
if (key.length <= MAX_KEY_LENGTH && shouldRecord(record, key, isDeclared)) {
|
|
75
|
+
;(isDeclared ? declared : undeclared).push(key)
|
|
76
|
+
} else {
|
|
77
|
+
hasRedacted = true
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const visibleKeys = [...declared.sort(), ...undeclared.sort()].slice(0, MAX_INPUT_KEYS)
|
|
81
|
+
if (hasRedacted && visibleKeys.length < MAX_INPUT_KEYS) {
|
|
82
|
+
visibleKeys.push('[redacted]')
|
|
83
|
+
}
|
|
84
|
+
const aliasesUsed = describeAliasesUsed(options?.inputAliases, input)
|
|
85
|
+
return {
|
|
86
|
+
[PostHogMCPAnalyticsProperty.InputKeys]: visibleKeys,
|
|
87
|
+
...(aliasesUsed.length > 0 ? { [PostHogMCPAnalyticsProperty.InputAliasesUsed]: aliasesUsed } : {}),
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
return {}
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { PostHog } from 'posthog-node'
|
|
|
7
7
|
import { isCompatibleServerType, isHighLevelServer } from './extensions/compatibility'
|
|
8
8
|
import { McpEventSink } from './extensions/sink'
|
|
9
9
|
import { MCPAnalyticsEventType } from './extensions/event-types'
|
|
10
|
-
import { IdentityCache, getServerTrackingData, setServerTrackingData } from './extensions/internal'
|
|
10
|
+
import { BoundedCache, IdentityCache, getServerTrackingData, setServerTrackingData } from './extensions/internal'
|
|
11
11
|
import { createLogger } from './extensions/logger'
|
|
12
12
|
import { captureEvent } from './extensions/capture'
|
|
13
13
|
import { applyMcpLibIdentity } from './extensions/lib-identity'
|
|
@@ -161,6 +161,7 @@ function buildTrackingData(
|
|
|
161
161
|
toolAnalyticsParameterOwnership: new Map(),
|
|
162
162
|
toolCategories: new Map<string, string>(),
|
|
163
163
|
toolDescriptions: new Map<string, string>(),
|
|
164
|
+
toolInputSchemas: new BoundedCache(),
|
|
164
165
|
sessionInfo: getSessionInfo(lowLevelServer, undefined),
|
|
165
166
|
options: {
|
|
166
167
|
...DEFAULT_OPTIONS,
|
|
@@ -230,6 +231,7 @@ export {
|
|
|
230
231
|
// Host callbacks receive the SDK's `extra`/`ctx` unchanged, and the two SDK
|
|
231
232
|
// majors carry HTTP headers in different places and shapes. This reads either.
|
|
232
233
|
export { getRequestHeaders } from './extensions/request-headers'
|
|
234
|
+
export { getToolInputProperties } from './extensions/tool-input'
|
|
233
235
|
export { PostHogMCP, type PostHogMCPOptions } from './extensions/posthog-mcp'
|
|
234
236
|
export { getMoreToolsResult } from './extensions/tools'
|
|
235
237
|
export { sendFeedbackResult, SEND_FEEDBACK_TOOL_NAME } from './extensions/feedback'
|
|
@@ -249,6 +251,7 @@ export type {
|
|
|
249
251
|
CaptureEventData,
|
|
250
252
|
CollectFeedbackConfig,
|
|
251
253
|
InitializeCaptureData,
|
|
254
|
+
InputAliasMap,
|
|
252
255
|
McpAnalytics,
|
|
253
256
|
McpCaptureCommon,
|
|
254
257
|
MCPAnalyticsContextOptions,
|
|
@@ -262,7 +265,9 @@ export type {
|
|
|
262
265
|
PrepareToolCallOptions,
|
|
263
266
|
PrepareToolListOptions,
|
|
264
267
|
RequestHeaderBag,
|
|
268
|
+
ShouldRecordInputKeyFn,
|
|
265
269
|
ToolCallCaptureData,
|
|
270
|
+
ToolInputOptions,
|
|
266
271
|
ToolsListCaptureData,
|
|
267
272
|
UserIdentity,
|
|
268
273
|
} from './types'
|
package/src/types.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import type { ErrorTracking } from '@posthog/core'
|
|
7
7
|
import type { AnalyticsInjectableJsonSchema } from './extensions/analytics-parameters'
|
|
8
8
|
import type { MCPAnalyticsEventType } from './extensions/event-types'
|
|
9
|
-
import type { IdentityCache } from './extensions/internal'
|
|
9
|
+
import type { BoundedCache, IdentityCache } from './extensions/internal'
|
|
10
10
|
import type { PostHogCaptureEvent } from './extensions/posthog-events'
|
|
11
11
|
import type { McpEventSink } from './extensions/sink'
|
|
12
12
|
import type { LoggerFn } from './extensions/logger'
|
|
@@ -186,6 +186,12 @@ export interface MCPAnalyticsOptions {
|
|
|
186
186
|
* suppress specific events. A throw drops that event.
|
|
187
187
|
*/
|
|
188
188
|
beforeSend?: BeforeSendFn
|
|
189
|
+
/**
|
|
190
|
+
* Decide which argument names `$mcp_input_keys` records on tool-call events.
|
|
191
|
+
* By default only names the tool's input schema declares are recorded; every
|
|
192
|
+
* other name becomes one `[redacted]` entry, because a name can carry private data.
|
|
193
|
+
*/
|
|
194
|
+
shouldRecordInputKey?: ShouldRecordInputKeyFn
|
|
189
195
|
/**
|
|
190
196
|
* Attach extra event properties on every auto-captured event. Spread into the PostHog
|
|
191
197
|
* event properties as-is; values must be JSON-serializable.
|
|
@@ -304,6 +310,30 @@ export type RegisteredTool = {
|
|
|
304
310
|
*/
|
|
305
311
|
export type BeforeSendFn = (event: PostHogCaptureEvent) => MaybePromise<PostHogCaptureEvent | null | undefined>
|
|
306
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Decides whether one top-level argument name appears in `$mcp_input_keys`.
|
|
315
|
+
* `declared` is true when the server's input schema declares the name.
|
|
316
|
+
* Return `true` to record the name; any other result, or a throw, records `[redacted]`.
|
|
317
|
+
*/
|
|
318
|
+
export type ShouldRecordInputKeyFn = (key: string, details: { declared: boolean }) => boolean
|
|
319
|
+
|
|
320
|
+
export interface ToolInputOptions {
|
|
321
|
+
/**
|
|
322
|
+
* Replace the default rule, which records only declared names. The SDK still
|
|
323
|
+
* drops names longer than 64 characters and records at most 20 names.
|
|
324
|
+
*/
|
|
325
|
+
shouldRecordInputKey?: ShouldRecordInputKeyFn
|
|
326
|
+
/**
|
|
327
|
+
* The alternative argument names the server accepts, as canonical name to aliases in the
|
|
328
|
+
* order the server tries them, for example `{ id: ['experimentId'] }`. Must be owned by the
|
|
329
|
+
* server, never taken from the caller. Alias names count as declared in `$mcp_input_keys`,
|
|
330
|
+
* and `$mcp_input_aliases_used` records each alias the server needed, as `alias:canonical`.
|
|
331
|
+
*/
|
|
332
|
+
inputAliases?: InputAliasMap
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export type InputAliasMap = Readonly<Record<string, readonly string[]>>
|
|
336
|
+
|
|
307
337
|
export interface Event {
|
|
308
338
|
actorId?: string
|
|
309
339
|
clientName?: string
|
|
@@ -515,6 +545,7 @@ export interface MCPAnalyticsData {
|
|
|
515
545
|
toolAnalyticsParameterOwnership: Map<string, AnalyticsParameterOwnership>
|
|
516
546
|
toolCategories: Map<string, string>
|
|
517
547
|
toolDescriptions: Map<string, string>
|
|
548
|
+
toolInputSchemas: BoundedCache<Map<string, unknown>>
|
|
518
549
|
}
|
|
519
550
|
|
|
520
551
|
export interface CaptureEventData {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '0.
|
|
1
|
+
export const version = '0.19.0'
|