@posthog/mcp 0.10.9 → 0.11.1

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.
Files changed (93) hide show
  1. package/README.md +39 -0
  2. package/dist/extensions/capture.d.ts.map +1 -1
  3. package/dist/extensions/capture.js +2 -0
  4. package/dist/extensions/capture.mjs +2 -0
  5. package/dist/extensions/compatibility.d.ts.map +1 -1
  6. package/dist/extensions/compatibility.js +14 -18
  7. package/dist/extensions/compatibility.mjs +10 -14
  8. package/dist/extensions/constants.d.ts +2 -0
  9. package/dist/extensions/constants.d.ts.map +1 -1
  10. package/dist/extensions/constants.js +3 -1
  11. package/dist/extensions/constants.mjs +3 -1
  12. package/dist/extensions/context-parameters.d.ts.map +1 -1
  13. package/dist/extensions/conversation-id.d.ts +2 -13
  14. package/dist/extensions/conversation-id.d.ts.map +1 -1
  15. package/dist/extensions/detect.d.ts +45 -0
  16. package/dist/extensions/detect.d.ts.map +1 -0
  17. package/dist/extensions/detect.js +90 -0
  18. package/dist/extensions/detect.mjs +35 -0
  19. package/dist/extensions/headers.d.ts +33 -0
  20. package/dist/extensions/headers.d.ts.map +1 -0
  21. package/dist/extensions/headers.js +54 -0
  22. package/dist/extensions/headers.mjs +17 -0
  23. package/dist/extensions/instrument-highlevel.d.ts.map +1 -1
  24. package/dist/extensions/instrument-lowlevel.d.ts.map +1 -1
  25. package/dist/extensions/instrumentation.d.ts +7 -0
  26. package/dist/extensions/instrumentation.d.ts.map +1 -1
  27. package/dist/extensions/instrumentation.js +7 -2
  28. package/dist/extensions/instrumentation.mjs +7 -2
  29. package/dist/extensions/internal.d.ts.map +1 -1
  30. package/dist/extensions/internal.js +2 -0
  31. package/dist/extensions/internal.mjs +2 -0
  32. package/dist/extensions/output-instructions.d.ts +6 -8
  33. package/dist/extensions/output-instructions.d.ts.map +1 -1
  34. package/dist/extensions/posthog-events.js +4 -0
  35. package/dist/extensions/posthog-events.mjs +4 -0
  36. package/dist/extensions/posthog-mcp.d.ts.map +1 -1
  37. package/dist/extensions/posthog-mcp.js +3 -0
  38. package/dist/extensions/posthog-mcp.mjs +3 -0
  39. package/dist/extensions/request-headers.d.ts +36 -0
  40. package/dist/extensions/request-headers.d.ts.map +1 -0
  41. package/dist/extensions/request-headers.js +62 -0
  42. package/dist/extensions/request-headers.mjs +28 -0
  43. package/dist/extensions/session-token.d.ts.map +1 -1
  44. package/dist/extensions/session-token.js +5 -14
  45. package/dist/extensions/session-token.mjs +2 -11
  46. package/dist/extensions/session.d.ts +4 -3
  47. package/dist/extensions/session.d.ts.map +1 -1
  48. package/dist/extensions/session.js +2 -1
  49. package/dist/extensions/session.mjs +2 -1
  50. package/dist/extensions/sink.d.ts +4 -6
  51. package/dist/extensions/sink.d.ts.map +1 -1
  52. package/dist/extensions/tools.d.ts.map +1 -1
  53. package/dist/extensions/transport-identity.d.ts +64 -0
  54. package/dist/extensions/transport-identity.d.ts.map +1 -0
  55. package/dist/extensions/transport-identity.js +64 -0
  56. package/dist/extensions/transport-identity.mjs +21 -0
  57. package/dist/extensions/truncation.d.ts.map +1 -1
  58. package/dist/extensions/truncation.js +2 -0
  59. package/dist/extensions/truncation.mjs +2 -0
  60. package/dist/index.d.ts +2 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +11 -7
  63. package/dist/index.mjs +2 -1
  64. package/dist/types.d.ts +52 -0
  65. package/dist/types.d.ts.map +1 -1
  66. package/dist/version.d.ts +1 -1
  67. package/dist/version.js +1 -1
  68. package/dist/version.mjs +1 -1
  69. package/package.json +2 -2
  70. package/src/extensions/capture.ts +6 -0
  71. package/src/extensions/compatibility.ts +25 -33
  72. package/src/extensions/constants.ts +2 -0
  73. package/src/extensions/context-parameters.ts +3 -11
  74. package/src/extensions/conversation-id.ts +5 -17
  75. package/src/extensions/detect.ts +78 -0
  76. package/src/extensions/headers.ts +52 -0
  77. package/src/extensions/instrument-highlevel.ts +0 -1
  78. package/src/extensions/instrument-lowlevel.ts +0 -1
  79. package/src/extensions/instrumentation.ts +32 -39
  80. package/src/extensions/internal.ts +6 -5
  81. package/src/extensions/output-instructions.ts +6 -8
  82. package/src/extensions/posthog-events.ts +14 -0
  83. package/src/extensions/posthog-mcp.ts +7 -5
  84. package/src/extensions/request-headers.ts +76 -0
  85. package/src/extensions/session-token.ts +3 -15
  86. package/src/extensions/session.ts +23 -36
  87. package/src/extensions/sink.ts +4 -6
  88. package/src/extensions/tools.ts +3 -6
  89. package/src/extensions/transport-identity.ts +100 -0
  90. package/src/extensions/truncation.ts +4 -0
  91. package/src/index.ts +4 -0
  92. package/src/types.ts +51 -0
  93. package/src/version.ts +1 -1
@@ -14,6 +14,7 @@ import type {
14
14
  import { INACTIVITY_TIMEOUT_IN_MINUTES } from './constants'
15
15
  import { deterministicPrefixedId, newPrefixedId } from './ids'
16
16
  import { getServerTrackingData, setServerTrackingData } from './internal'
17
+ import { getRequestHeaders } from './request-headers'
17
18
  import { decodeSessionId, readMcpSessionHeader } from './session-token'
18
19
  import type { SessionTokenPayload } from './session-token'
19
20
 
@@ -30,9 +31,10 @@ export function deriveSessionIdFromMCPSession(mcpSessionId: string): string {
30
31
  }
31
32
 
32
33
  /**
33
- * Derives the SDK session id from the agent's conversation handle. Deterministic
34
- * and unsalted on purpose: two pods that never met must agree on the session, and
35
- * the 2026-07-28 revision leaves them no shared state to agree through.
34
+ * Derives the SDK session id from the agent's conversation handle (ADR-0004).
35
+ * Deterministic and unsalted on purpose: two pods that never met must agree on
36
+ * the session, and the 2026-07-28 revision leaves them no shared state to agree
37
+ * through.
36
38
  *
37
39
  * Hashed rather than used verbatim so an MCP session can never collide with a
38
40
  * Session Replay id — a bare uuidv7 would render a "View recording" button that
@@ -69,26 +71,16 @@ export function getSessionId(
69
71
  throw new Error('Server tracking data not found')
70
72
  }
71
73
 
72
- // 1. The agent's conversation handle. It outranks both steps below because it
73
- // is the only id that survives reconnects, restarts, and the per-request
74
- // server instances the 2026-07-28 revision introduces. Hashed rather than used
75
- // verbatim so it can never collide with Session Replay ids.
76
- //
77
- // This returns instead of falling through to the shared-state writes at the
78
- // end: the handle belongs to this one request, and persisting it would leak
79
- // one chat's session onto a concurrent chat's `tools/list`.
80
- //
81
- // `lastActivity` therefore does not advance either, so a conversation that
82
- // always echoes lets the in-memory fallback age past the inactivity timeout —
83
- // and a later call carrying no handle rotates it. That is the honest reading:
84
- // the fallback session really has been idle for that whole time.
74
+ // 1. The agent's conversation handle — the only id that survives reconnects,
75
+ // restarts, and the per-request server instances of the 2026-07-28 revision
76
+ // (ADR-0004). Returns before the shared-state writes at the end: the handle
77
+ // belongs to this one request, and persisting it (or advancing lastActivity)
78
+ // would leak one chat's session onto a concurrent chat's `tools/list`.
85
79
  if (conversationId) {
86
- // The handle decides the *session*, but the request may still carry our token,
87
- // and on a stateless instance that never processed `initialize` its payload is
88
- // the only place client identity exists. Restore that before returning: the
89
- // early return is about not persisting a session, not about ignoring what the
90
- // request told us about the client. Without this, tool calls and `$identify`
91
- // go out unattributed on exactly the deployments this branch exists for.
80
+ // The session comes from the handle, but on an instance that never processed
81
+ // `initialize` the request's token is still the only source of client
82
+ // identity — without this, tool calls and `$identify` go out unattributed
83
+ // on exactly the deployments this branch exists for.
92
84
  applyTokenClientIdentity(data, extra)
93
85
  return deriveSessionIdFromConversation(conversationId)
94
86
  }
@@ -110,20 +102,16 @@ export function getSessionId(
110
102
  /**
111
103
  * Restores the client name/version and protocol version baked into our token at
112
104
  * mint time, and hands the decoded token back so the caller can also take the
113
- * session id from it.
114
- *
115
- * Split out from step 2 because the two halves of the token have different
116
- * scopes. The session id it carries is per-chat, so a conversation-anchored
117
- * request must not adopt it. The client identity is per-connection — the same
118
- * client for every request on it — so a conversation-anchored request should,
119
- * and on a stateless instance that never saw `initialize` this is the only
120
- * source of it.
105
+ * session id from it. Split from step 2 because the token's two halves have
106
+ * different scopes: its session id is per-chat (a conversation-anchored request
107
+ * must not adopt it), its client identity is per-connection (every request on
108
+ * the connection should).
121
109
  */
122
110
  function applyTokenClientIdentity(
123
111
  data: MCPAnalyticsData,
124
112
  extra?: CompatibleRequestHandlerExtra
125
113
  ): SessionTokenPayload | undefined {
126
- const token = decodeSessionId(readMcpSessionHeader(extra?.requestInfo?.headers))
114
+ const token = decodeSessionId(readMcpSessionHeader(getRequestHeaders(extra)))
127
115
  if (!token) {
128
116
  return undefined
129
117
  }
@@ -139,9 +127,8 @@ function applyTokenClientIdentity(
139
127
  * issued. Returns undefined when the request carried neither, which is what
140
128
  * sends the caller on to 3.
141
129
  *
142
- * Both are 2025-11-25 mechanisms. The 2026-07-28 revision removed that header
143
- * outright — servers MUST NOT mint or echo it — so this entire step becomes
144
- * legacy-only once era detection lands.
130
+ * Both are 2025-11-25 mechanisms; the 2026-07-28 revision removed the header
131
+ * outright, so this step is legacy-only once era detection lands (ADR-0003).
145
132
  */
146
133
  function readSessionIdFromRequest(data: MCPAnalyticsData, extra?: CompatibleRequestHandlerExtra): string | undefined {
147
134
  // 2a. A token we minted at `initialize` and the client replayed. It rides the
@@ -203,8 +190,8 @@ export function getSessionInfo(
203
190
  const actorInfo = data?.identifiedSessions.get(sessionId ?? data.sessionId)
204
191
 
205
192
  const sessionInfo: SessionInfo = {
206
- ipAddress: undefined, // grab from django
207
- sdkLanguage: 'TypeScript', // hardcoded for now
193
+ ipAddress: undefined,
194
+ sdkLanguage: 'TypeScript',
208
195
  sdkVersion: version,
209
196
  serverName: server._serverInfo?.name,
210
197
  serverVersion: server._serverInfo?.version,
@@ -92,9 +92,8 @@ async function applyBeforeSend(
92
92
  }
93
93
 
94
94
  /**
95
- * Wraps a user-supplied `posthog-node` client. Runs every MCP event through the
96
- * sanitize → truncate pipeline, fans it out into the `$mcp_*` / `$exception`
97
- * events, applies `beforeSend`, and hands each to `posthog.capture()`.
95
+ * Wraps a user-supplied `posthog-node` client: runs every MCP event through
96
+ * {@link processMcpEvent} and hands each surviving payload to `posthog.capture()`.
98
97
  *
99
98
  * The SDK does not own the client lifecycle — the host application constructs
100
99
  * the `PostHog` instance and is responsible for `shutdown()` (matching
@@ -107,9 +106,8 @@ export class McpEventSink {
107
106
  ) {}
108
107
 
109
108
  /**
110
- * Push an MCP event through the pipeline (sanitize → truncate → fan out →
111
- * beforeSend → capture). Errors at any stage are logged and the event is
112
- * dropped, never re-thrown into tool code.
109
+ * Errors at any stage are logged and the event is dropped, never re-thrown
110
+ * into tool code.
113
111
  */
114
112
  async capture(event: McpEvent, options: McpCaptureOptions): Promise<void> {
115
113
  const result = await processMcpEvent(event, options, this.logger)
@@ -36,15 +36,12 @@ export function getReportMissingToolDescriptor(name: string = GET_MORE_TOOLS_NAM
36
36
  },
37
37
  annotations: {
38
38
  title: 'Get More Tools',
39
- // Doesn't mutate state on the MCP server
40
39
  readOnlyHint: true,
41
- // Interacts with external entities because we store this in analytics
40
+ // Interacts with an external entity: the report lands in analytics.
42
41
  openWorldHint: true,
43
- // A tool like `get_more_tools` would usually NOT be idempontent, but since we are
44
- // only using this to keep track of missing tools/feedback/analytics, it is actually idempontent.
45
- // It's also preferable to track it as idempontent to make agents more prone to call it proactively.
42
+ // Only records the gap, so repeat calls are harmless — and advertising it
43
+ // as idempotent makes agents more willing to call it proactively.
46
44
  idempotentHint: true,
47
- // Never deletes any data from the MCP server
48
45
  destructiveHint: false,
49
46
  },
50
47
  }
@@ -0,0 +1,100 @@
1
+ import type { CompatibleRequestHandlerExtra, McpEvent } from '../types'
2
+ import { readHeaderValue } from './headers'
3
+ import { getRequestHeaders } from './request-headers'
4
+
5
+ /**
6
+ * Transport-level client identity: the two request headers that say *which
7
+ * product* is calling, where `clientInfo` only says which client library is.
8
+ *
9
+ * MCP's own identity fields are too coarse to attribute usage to a surface. A
10
+ * vendor ships many products on one client: Anthropic reports
11
+ * `clientInfo.name = "claude-code"` from the CLI, the Agent SDK, the VS Code
12
+ * extension, the desktop app and more, so `$mcp_client_name` collapses all of
13
+ * them into one bucket. The distinguishing detail exists only in the User-Agent
14
+ * parenthetical — `claude-code/2.1.0 (cli)` vs `claude-code/2.1.0 (sdk-ts)` vs
15
+ * `claude-code/2.1.0 (claude-vscode)` — alongside vendor-specific headers like
16
+ * `x-anthropic-client`. Capturing them is the only way a server owner can tell
17
+ * their surfaces apart.
18
+ *
19
+ * We capture the raw strings and classify nothing. No vendor table, no product
20
+ * labels: friendly names are resolved at query time server-side, so labels can
21
+ * improve (and new surfaces can appear) without waiting on an SDK release, and
22
+ * there is exactly one resolver rather than one per installed SDK version.
23
+ *
24
+ * This is deliberately separate from `client-identity.ts`. That module reads the
25
+ * request body's `_meta` and therefore works on every transport, including stdio
26
+ * and in-memory. Headers exist only on HTTP transports, so everything here is a
27
+ * silent no-op elsewhere and those events stay byte-identical to before.
28
+ */
29
+
30
+ /** Header carrying the client's product/surface, e.g. `claude-code/2.1.0 (cli)`. */
31
+ export const CLIENT_USER_AGENT_HEADER = 'user-agent'
32
+
33
+ /**
34
+ * Vendor-specific client header. Anthropic's clients send it alongside the
35
+ * User-Agent; it is captured verbatim as a second, independent signal rather
36
+ * than merged into one, so a query-time resolver can prefer whichever the
37
+ * vendor keeps stable.
38
+ */
39
+ export const VENDOR_CLIENT_HEADER = 'x-anthropic-client'
40
+
41
+ export interface TransportIdentity {
42
+ clientUserAgent?: string
43
+ vendorClient?: string
44
+ }
45
+
46
+ /**
47
+ * Reads the transport identity headers off the request. Returns `undefined`
48
+ * when the request carries neither — including every stdio and in-memory
49
+ * request, which has no HTTP headers at all. Never throws.
50
+ *
51
+ * Sourced through `getRequestHeaders` rather than `extra.requestInfo.headers`
52
+ * directly: MCP SDK v2 puts the request at `extra.http.req` as a WHATWG
53
+ * `Request`, so a v1-shaped read finds nothing and this whole feature would be
54
+ * silently inert on exactly the servers being adopted now.
55
+ */
56
+ export function readTransportIdentity(extra: CompatibleRequestHandlerExtra | undefined): TransportIdentity | undefined {
57
+ const headers = getRequestHeaders(extra)
58
+ if (!headers) {
59
+ return undefined
60
+ }
61
+ const result: TransportIdentity = {}
62
+
63
+ const clientUserAgent = readHeaderValue(headers, CLIENT_USER_AGENT_HEADER)
64
+ if (clientUserAgent) {
65
+ result.clientUserAgent = clientUserAgent
66
+ }
67
+
68
+ const vendorClient = readHeaderValue(headers, VENDOR_CLIENT_HEADER)
69
+ if (vendorClient) {
70
+ result.vendorClient = vendorClient
71
+ }
72
+
73
+ return result.clientUserAgent || result.vendorClient ? result : undefined
74
+ }
75
+
76
+ /**
77
+ * Stamps the transport identity onto the event being built for *this* request,
78
+ * so it carries `$mcp_client_user_agent` and `$mcp_vendor_client`.
79
+ *
80
+ * Headers are per-request, so this writes to the event — a per-request object —
81
+ * and never to the server-wide `sessionInfo`. One instrumented server can
82
+ * multiplex concurrent requests from different clients, and caching a header
83
+ * into shared state would attribute one client's surface to another's event.
84
+ * `client-identity.ts` and `capture.ts` guard the same hazard.
85
+ *
86
+ * Values are capped downstream by `truncateEvent`, which runs on every capture
87
+ * path, so a hostile 1MB header cannot inflate an event.
88
+ */
89
+ export function stampTransportIdentity(event: McpEvent, extra: CompatibleRequestHandlerExtra | undefined): void {
90
+ const identity = readTransportIdentity(extra)
91
+ if (!identity) {
92
+ return
93
+ }
94
+ if (identity.clientUserAgent) {
95
+ event.clientUserAgent = identity.clientUserAgent
96
+ }
97
+ if (identity.vendorClient) {
98
+ event.vendorClient = identity.vendorClient
99
+ }
100
+ }
@@ -430,6 +430,10 @@ export function truncateEvent<T extends Event | McpEvent>(event: T): T {
430
430
  result.serverVersion = truncateString(result.serverVersion, MAX_METADATA_LENGTH)
431
431
  result.clientName = truncateString(result.clientName, MAX_METADATA_LENGTH)
432
432
  result.clientVersion = truncateString(result.clientVersion, MAX_METADATA_LENGTH)
433
+ // Client-supplied HTTP headers, so unlike the fields above they are attacker-
434
+ // controlled in length. Any real User-Agent fits well inside the metadata cap.
435
+ result.clientUserAgent = truncateString(result.clientUserAgent, MAX_METADATA_LENGTH)
436
+ result.vendorClient = truncateString(result.vendorClient, MAX_METADATA_LENGTH)
433
437
  result.errorType = truncateString(result.errorType, MAX_METADATA_LENGTH)
434
438
 
435
439
  // Error field limits — operate on the core `$exception_list` shape
package/src/index.ts CHANGED
@@ -210,6 +210,9 @@ export {
210
210
  PostHogMCPAnalyticsEvent,
211
211
  PostHogMCPAnalyticsProperty,
212
212
  } from './extensions/constants'
213
+ // Host callbacks receive the SDK's `extra`/`ctx` unchanged, and the two SDK
214
+ // majors carry HTTP headers in different places and shapes. This reads either.
215
+ export { getRequestHeaders } from './extensions/request-headers'
213
216
  export { PostHogMCP, type PostHogMCPOptions } from './extensions/posthog-mcp'
214
217
  export { getMoreToolsResult } from './extensions/tools'
215
218
  export { setLogger } from './extensions/logger'
@@ -229,6 +232,7 @@ export type {
229
232
  MissingCapabilityCaptureData,
230
233
  PreparedToolCall,
231
234
  PrepareToolListOptions,
235
+ RequestHeaderBag,
232
236
  ToolCallCaptureData,
233
237
  ToolsListCaptureData,
234
238
  UserIdentity,
package/src/types.ts CHANGED
@@ -151,6 +151,15 @@ export type BeforeSendFn = (event: PostHogCaptureEvent) => MaybePromise<PostHogC
151
151
  export interface Event {
152
152
  actorId?: string
153
153
  clientName?: string
154
+ /**
155
+ * Raw `user-agent` request header → `$mcp_client_user_agent`. HTTP transports
156
+ * only. The only place a client's *surface* is distinguishable: one vendor
157
+ * ships many products under a single `clientName` (Anthropic's CLI, Agent SDK
158
+ * and VS Code extension all report `claude-code`), and only the User-Agent
159
+ * parenthetical tells them apart. Captured verbatim — surfaces are resolved to
160
+ * friendly labels at query time, never in this SDK.
161
+ */
162
+ clientUserAgent?: string
154
163
  clientVersion?: string
155
164
  conversationId?: string
156
165
  duration?: number
@@ -199,6 +208,12 @@ export interface Event {
199
208
  toolDescription?: string
200
209
  userIntent?: string
201
210
  userIntentSource?: MCPAnalyticsIntentSource
211
+ /**
212
+ * Raw vendor client header (`x-anthropic-client`) → `$mcp_vendor_client`. HTTP
213
+ * transports only. A second, independent surface signal alongside
214
+ * {@link Event.clientUserAgent}; captured verbatim, never classified.
215
+ */
216
+ vendorClient?: string
202
217
  }
203
218
 
204
219
  /** A partially-built MCP event as it flows through the SDK before capture. */
@@ -210,14 +225,32 @@ export interface CompatibleRequestInfoLike {
210
225
  [key: string]: unknown
211
226
  }
212
227
 
228
+ /**
229
+ * The HTTP request an MCP SDK v2 server attaches to the handler context, as a
230
+ * WHATWG `Request` — so `headers` answers to `.get()`, not to indexing.
231
+ */
232
+ export interface CompatibleHttpRequestLike {
233
+ req?: { headers?: unknown; [key: string]: unknown }
234
+ [key: string]: unknown
235
+ }
236
+
213
237
  export interface CompatibleRequestHandlerExtra {
214
238
  headers?: Record<string, string | string[]>
215
239
  sessionId?: string
216
240
  /** Present on HTTP transports only — headers ride every request, unlike `clientInfo`. */
217
241
  requestInfo?: CompatibleRequestInfoLike
242
+ /** Where MCP SDK v2 puts the same thing. Read both through `getRequestHeaders`. */
243
+ http?: CompatibleHttpRequestLike
218
244
  [key: string]: unknown
219
245
  }
220
246
 
247
+ /**
248
+ * HTTP headers normalised to lowercase keys, as `getRequestHeaders` returns
249
+ * them — a plain bag, so a host's existing `headers['authorization']` keeps
250
+ * working when only the source of the headers changes.
251
+ */
252
+ export type RequestHeaderBag = Record<string, string | string[]>
253
+
221
254
  export interface ServerClientInfoLike {
222
255
  name?: string
223
256
  version?: string
@@ -344,6 +377,24 @@ export interface McpCaptureCommon {
344
377
  * not just the initialize event — the `PostHogMCP` client holds no per-session state.
345
378
  */
346
379
  protocolVersion?: string
380
+ /**
381
+ * Raw `user-agent` request header → `$mcp_client_user_agent`. The
382
+ * `instrument()` path reads this off the transport automatically; a custom
383
+ * dispatcher has no `extra`, so pass `req.headers['user-agent']` yourself.
384
+ *
385
+ * Worth wiring up: it is the only signal that separates a vendor's surfaces
386
+ * (Anthropic's CLI, Agent SDK and VS Code extension all report
387
+ * `clientName: "claude-code"`, and only the User-Agent parenthetical —
388
+ * `(cli)` / `(sdk-ts)` / `(claude-vscode)` — tells them apart). Send it raw;
389
+ * PostHog resolves friendly product labels at query time.
390
+ */
391
+ clientUserAgent?: string
392
+ /**
393
+ * Raw vendor client header → `$mcp_vendor_client`. Anthropic's clients send
394
+ * `x-anthropic-client`; pass it as a second, independent surface signal
395
+ * alongside {@link McpCaptureCommon.clientUserAgent}. Sent verbatim.
396
+ */
397
+ vendorClient?: string
347
398
  /** Person properties → `$set` (e.g. `{ name, email, plan }`). */
348
399
  setProperties?: JsonRecord
349
400
  /** Group memberships → `$groups`. */
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '0.10.9'
1
+ export const version = '0.11.1'