@posthog/mcp 0.16.4 → 0.17.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/README.md CHANGED
@@ -80,7 +80,7 @@ Protocol revision is a property of each **request**, not of the server: a v2 ser
80
80
 
81
81
  - **On `2026-07-28`** there is no `initialize` and no session header — the revision removed
82
82
  protocol-level sessions, and this SDK will not mint one. Session correlation therefore comes from
83
- `enableConversationId`, which is **opt-in**. Without it every request is its own `$session_id`.
83
+ `enableConversationId`, which is **on by default**. Without it every request is its own `$session_id`.
84
84
  The `get_more_tools` and `send_feedback` virtual tools also use this handle, including calls handled
85
85
  by a fresh server instance.
86
86
  - **On `2025-11-25`**, the session id and the client's name and version are exchanged once at
@@ -160,9 +160,31 @@ over-redacting ordinary prose. It also applies only to `$mcp_intent`: structured
160
160
  results are left as-is, because the same shapes are often legitimate data there. If you need a stronger
161
161
  guarantee, `context: false` and the `beforeSend` hook above remain the ways to drop the field entirely.
162
162
 
163
+ ### Defaults and opt-outs
164
+
165
+ On fresh low-level instances, tools with an `outputSchema` deliver new handles through `content` only. Clients that consume only `structuredContent` will not echo those handles, so correlation is not guaranteed for that combination. Unknown ownership can also cause a prompt-back block to be appended for a schema that discovery would not extend; disable `enableConversationId` if that contract is unsuitable.
166
+
167
+ A valid echoed conversation handle takes precedence over transport sessions. A request that already carries a transport session or a PostHog session token does not mint a new handle or append a prompt-back block. Requests carrying neither use the conversation fallback.
168
+
169
+ Intent, model capture, conversation correlation, and exception capture are enabled by default.
170
+ Missing-capability reporting and feedback collection remain disabled.
171
+
172
+ ```ts
173
+ instrument(server, posthog, { captureModel: false, enableConversationId: false })
174
+ ```
175
+
176
+ Model capture adds a required `llm_model` argument to compatible tool schemas. Dispatch never
177
+ enforces it, so servers keep working; strict-schema clients see the new field. Set
178
+ `captureModel: false` to leave schemas untouched. Conversation correlation adds an optional
179
+ `conversation_id` argument and returns a handle in eligible tool results. Clients must echo that
180
+ handle to group later calls; calls without it mint new handles. Set `enableConversationId: false`
181
+ to retain transport-based session grouping and unchanged response content. Custom `PostHogMCP`
182
+ dispatchers enable model capture by default, but continue to supply their own session IDs.
183
+ The reasoning behind these defaults is in [ADR-0013](./docs/adr/0013-analytics-capture-is-on-by-default.md).
184
+
163
185
  ### What `$mcp_llm_model` records, and when it stays empty
164
186
 
165
- `captureModel` is **off** by default. Turn it on and the SDK records the best model id visible to the
187
+ `captureModel` is **on** by default. The SDK records the best model id visible to the
166
188
  server as `$mcp_llm_model`. Recognized client metadata wins with source `client_metadata`. Otherwise,
167
189
  the SDK injects a required `llm_model` parameter and records the answer with source `self_reported`.
168
190
 
@@ -173,32 +195,32 @@ Missing, blank, and `unknown` values are recorded as nothing.
173
195
  The recognized metadata path is Codex's `x-codex-turn-metadata.model` field inside request `_meta`.
174
196
  Other clients keep using self-report until they expose a stable model field.
175
197
 
176
- Unlike `context`, the self-report fallback degrades to **silence** rather than to a kept argument.
177
- Its capture and stripping require the SDK to have confirmed the parameter is its own:
198
+ Like `context`, self-report follows the ownership rule of ADR-0011: reading `llm_model` fails
199
+ open where the SDK cannot tell who declared it, stripping it requires proof that the SDK did:
178
200
 
179
201
  - `instrument(server)` on a high-level `McpServer` resolves ownership for your registered tools per
180
202
  request from the live tool registry, so those work even on a fresh instance.
181
203
  - The `get_more_tools` virtual tool works on any instance and on either server type: the SDK writes
182
204
  that descriptor itself, so what it declares is known without a listing.
183
- - Instrumenting a low-level `Server` learns ownership of **your** tools while serving `tools/list`.
184
- On a server that builds a fresh instance per HTTP request — `createMcpHandler`, or
185
- `@rekog/mcp-nest` in its stateless mode — the instance handling a `tools/call` never served one,
186
- so for those tools it neither strips `llm_model` nor records `$mcp_llm_model`. Nothing breaks and
187
- no wrong value is stored; the property is simply absent while agents still pay a token for the
188
- extra field.
205
+ - Instrumenting a low-level `Server` learns ownership while serving `tools/list`. A fresh instance
206
+ that never served one — `createMcpHandler`, or `@rekog/mcp-nest` in its stateless mode — has no
207
+ answer, so it records `llm_model` as the self-reported model and strips nothing. A tool that
208
+ declares its own `llm_model` on such an instance is therefore recorded under `$mcp_llm_model`
209
+ until a listing says otherwise; `captureModel: false` or dropping the property in `beforeSend`
210
+ are the escapes. The SDK never replays your listing handler on the call path to find out.
189
211
 
190
212
  As with `context`, what matters is instance lifetime rather than statelessness: a transport-stateless
191
213
  server (`sessionIdGenerator: undefined`) that keeps one long-lived server object learns ownership
192
214
  from the first `tools/list` and keeps it.
193
215
 
194
- For a custom dispatcher, enable the same option on `PostHogMCP`. Its `prepareToolList()` helper
216
+ For a custom dispatcher, `PostHogMCP` enables the same option by default. Its `prepareToolList()` helper
195
217
  injects the field and records ownership by tool name; `prepareToolCall()` returns `llmModel` and
196
218
  `llmModelSource` while removing the SDK-owned argument before dispatch. Pass both fields to
197
219
  `captureToolCall()`. Pass the original tool descriptor on each call so this also works when
198
220
  `tools/list` and `tools/call` reach different server replicas:
199
221
 
200
222
  ```ts
201
- const posthog = new PostHogMCP(process.env.POSTHOG_PROJECT_TOKEN, { captureModel: true })
223
+ const posthog = new PostHogMCP(process.env.POSTHOG_PROJECT_TOKEN)
202
224
 
203
225
  const tools = posthog.prepareToolList(serverTools)
204
226
  const originalTool = serverTools.find((tool) => tool.name === toolName)
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,2BAA2B,EAC3B,6BAA6B,EAC7B,uBAAuB,EACvB,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,aAAa,EAId,MAAM,UAAU,CAAA;AAqBjB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAIrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAUxC;;;;;;GAMG;AAEH,KAAK,iBAAiB,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,6BAA6B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAE7G,2EAA2E;AAC3E,KAAK,YAAY,GAAG,CAAC,iBAAiB,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAE3E,UAAU,mBAAmB;IAC3B,MAAM,EAAE,aAAa,CAAA;IACrB,IAAI,EAAE,gBAAgB,CAAA;IACtB,OAAO,EAAE,cAAc,CAAA;IACvB,KAAK,CAAC,EAAE,6BAA6B,CAAA;IACrC,OAAO,EAAE,YAAY,CAAA;IACrB,2FAA2F;IAC3F,kBAAkB,CAAC,EAAE,2BAA2B,CAAA;IAChD;;;;;OAKG;IACH,SAAS,CAAC,EAAE,qBAAqB,CAAA;IACjC;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,UAAU,CAAA;IACjC;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAA;CAClC;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CAyEnF;AA+QD,KAAK,iBAAiB,GAAG,OAAO,qBAAqB,CAAC,gBAAgB,GAAG,OAAO,qBAAqB,CAAC,gBAAgB,CAAA;AAuDtH,sFAAsF;AACtF,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,iBAAiB,EAAE,MAAM,EAAE,QAAQ,GAAG,YAAY,CAGjG;AAgDD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CACzB,MAAM,EAAE,aAAa,EACrB,eAAe,EAAE,iBAAiB,EAClC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,6BAA6B,GAAG,SAAS,KAC7C,OAAO,CAAC,OAAO,CAAC,CAAA;AAkBrB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,iBAAiB,EAClC,KAAK,EAAE,YAAY,GAClB,IAAI,CAGN;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,IAAI,CA0CvG;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,6BAA6B,GAAG,SAAS,EAChD,MAAM,EAAE,QAAQ,GACf,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAoB9B;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,aAAa,EACrB,wBAAwB,EAAE,iBAAiB,EAC3C,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,6BAA6B,GAAG,SAAS,EAChD,MAAM,EAAE,QAAQ,GACf,OAAO,CAAC,uBAAuB,CAAC,CAmElC;AAoID,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC1B,KAAK,EAAE,uBAAuB,CAAC,OAAO,CAAC,GAAG,SAAS,GAClD,IAAI,CASN;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAGtE;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC1B,KAAK,EAAE,uBAAuB,CAAC,OAAO,CAAC,GAAG,SAAS,GAClD,IAAI,CAUN;AAuHD;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,aAAa,EACrB,yBAAyB,EAAE,iBAAiB,EAC5C,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,6BAA6B,GAAG,SAAS,EAChD,MAAM,EAAE,QAAQ,GACf,OAAO,CAAC,OAAO,CAAC,CA2ElB"}
@@ -64,9 +64,9 @@ async function captureToolCall(params) {
64
64
  const resolvedEventType = eventType ?? external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpToolsCall;
65
65
  const isVirtualAnalyticsTool = resolvedEventType === external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpMissingCapability || resolvedEventType === external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpFeedback;
66
66
  const ownership = getActiveAnalyticsParameterOwnership(data, request.params?.name, parameterOwnership, isVirtualAnalyticsTool);
67
- const canCaptureContextIntent = !isVirtualAnalyticsTool && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context) && (ownership.context || !ownership.contextOwnershipKnown);
68
- const conversation = (0, external_conversation_id_js_namespaceObject.resolveConversationId)(ownership.conversationId, request.params?.arguments);
69
- const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership);
67
+ const canCaptureContextIntent = ownership.read.context;
68
+ const conversation = resolveToolConversation(ownership.read.conversationId, request.params?.arguments, extra);
69
+ const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership.strip);
70
70
  const startTime = new Date();
71
71
  const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType, canCaptureContextIntent);
72
72
  if (preparedEvent && explicitContextIntent) (0, external_intent_js_namespaceObject.setExplicitContextIntent)(preparedEvent.event, explicitContextIntent);
@@ -86,15 +86,39 @@ async function captureToolCall(params) {
86
86
  publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError, result);
87
87
  return finalResult;
88
88
  }
89
+ function resolveToolConversation(enabled, args, extra) {
90
+ const conversation = (0, external_conversation_id_js_namespaceObject.resolveConversationId)(enabled, args);
91
+ if (!conversation.minted) return conversation;
92
+ const carriedSession = extra?.sessionId || (0, external_session_token_js_namespaceObject.decodeSessionId)((0, external_session_token_js_namespaceObject.readMcpSessionHeader)((0, external_request_headers_js_namespaceObject.getRequestHeaders)(extra)));
93
+ return carriedSession ? {
94
+ minted: false,
95
+ conversationId: void 0
96
+ } : conversation;
97
+ }
89
98
  function getActiveAnalyticsParameterOwnership(data, toolName, override, isVirtualAnalyticsTool) {
90
99
  const listed = toolName ? data.toolAnalyticsParameterOwnership.get(toolName) : void 0;
91
100
  const ownership = override ?? listed;
101
+ const enabled = {
102
+ context: !isVirtualAnalyticsTool && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context),
103
+ conversationId: true === data.options.enableConversationId,
104
+ llmModel: (0, external_model_parameters_js_namespaceObject.isCaptureModelEnabled)(data.options.captureModel)
105
+ };
106
+ return {
107
+ strip: enabledArgumentOwnership(enabled, ownership, false),
108
+ read: enabledArgumentOwnership(enabled, ownership, true),
109
+ outputInstructions: enabled.conversationId && listed?.outputInstructions === true
110
+ };
111
+ }
112
+ function enabledArgumentOwnership(enabled, ownership, whenUnknown) {
113
+ const resolved = ownership ?? {
114
+ context: whenUnknown,
115
+ conversationId: whenUnknown,
116
+ llmModel: whenUnknown
117
+ };
92
118
  return {
93
- contextOwnershipKnown: void 0 !== ownership,
94
- context: !isVirtualAnalyticsTool && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context) && ownership?.context === true,
95
- conversationId: true === data.options.enableConversationId && ownership?.conversationId === true,
96
- llmModel: (0, external_model_parameters_js_namespaceObject.isCaptureModelEnabled)(data.options.captureModel) && ownership?.llmModel === true,
97
- outputInstructions: true === data.options.enableConversationId && listed?.outputInstructions === true
119
+ context: enabled.context && resolved.context,
120
+ conversationId: enabled.conversationId && resolved.conversationId,
121
+ llmModel: enabled.llmModel && resolved.llmModel
98
122
  };
99
123
  }
100
124
  function cloneRequestWithoutOwnedAnalyticsArguments(request, ownership) {
@@ -131,7 +155,7 @@ async function prepareToolCallEvent(server, data, request, downstreamRequest, ex
131
155
  await (0, external_tracing_helpers_js_namespaceObject.applyResolvedMetadata)(event, data, request, extra);
132
156
  (0, external_intent_js_namespaceObject.setEventIntent)(event, await (0, external_intent_js_namespaceObject.resolveToolCallIntent)(data, request, canCaptureContextIntent, extra));
133
157
  if ((0, external_model_parameters_js_namespaceObject.isCaptureModelEnabled)(data.options.captureModel)) {
134
- const resolvedModel = (0, external_model_parameters_js_namespaceObject.resolveModel)(request, ownership.llmModel);
158
+ const resolvedModel = (0, external_model_parameters_js_namespaceObject.resolveModel)(request, ownership.read.llmModel);
135
159
  (0, external_model_parameters_js_namespaceObject.setEventModel)(event, resolvedModel?.model, resolvedModel?.source);
136
160
  }
137
161
  return {
@@ -14,7 +14,7 @@ import { buildCapturedMcpParameters } from "./mcp-payloads.mjs";
14
14
  import { readRequestHandlerMethod } from "./mcp-sdk-compat.mjs";
15
15
  import { getRequestHeaders } from "./request-headers.mjs";
16
16
  import { getSessionId, getSessionInfo, isModernEraRequest, newSessionId } from "./session.mjs";
17
- import { encodeSessionId, readMcpSessionHeader, writeSessionIdToTransport } from "./session-token.mjs";
17
+ import { decodeSessionId, encodeSessionId, readMcpSessionHeader, writeSessionIdToTransport } from "./session-token.mjs";
18
18
  import { SEND_FEEDBACK_TOOL_NAME, getFeedbackToolDescriptor, resolveCollectFeedbackOptions } from "./feedback.mjs";
19
19
  import { getReportMissingToolDescriptor, resolveMissingCapabilityToolName } from "./tools.mjs";
20
20
  import { applyResolvedMetadata, isToolResultError } from "./tracing-helpers.mjs";
@@ -23,9 +23,9 @@ async function captureToolCall(params) {
23
23
  const resolvedEventType = eventType ?? MCPAnalyticsEventType.mcpToolsCall;
24
24
  const isVirtualAnalyticsTool = resolvedEventType === MCPAnalyticsEventType.mcpMissingCapability || resolvedEventType === MCPAnalyticsEventType.mcpFeedback;
25
25
  const ownership = getActiveAnalyticsParameterOwnership(data, request.params?.name, parameterOwnership, isVirtualAnalyticsTool);
26
- const canCaptureContextIntent = !isVirtualAnalyticsTool && isContextEnabled(data.options.context) && (ownership.context || !ownership.contextOwnershipKnown);
27
- const conversation = resolveConversationId(ownership.conversationId, request.params?.arguments);
28
- const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership);
26
+ const canCaptureContextIntent = ownership.read.context;
27
+ const conversation = resolveToolConversation(ownership.read.conversationId, request.params?.arguments, extra);
28
+ const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership.strip);
29
29
  const startTime = new Date();
30
30
  const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType, canCaptureContextIntent);
31
31
  if (preparedEvent && explicitContextIntent) setExplicitContextIntent(preparedEvent.event, explicitContextIntent);
@@ -45,15 +45,39 @@ async function captureToolCall(params) {
45
45
  publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError, result);
46
46
  return finalResult;
47
47
  }
48
+ function resolveToolConversation(enabled, args, extra) {
49
+ const conversation = resolveConversationId(enabled, args);
50
+ if (!conversation.minted) return conversation;
51
+ const carriedSession = extra?.sessionId || decodeSessionId(readMcpSessionHeader(getRequestHeaders(extra)));
52
+ return carriedSession ? {
53
+ minted: false,
54
+ conversationId: void 0
55
+ } : conversation;
56
+ }
48
57
  function getActiveAnalyticsParameterOwnership(data, toolName, override, isVirtualAnalyticsTool) {
49
58
  const listed = toolName ? data.toolAnalyticsParameterOwnership.get(toolName) : void 0;
50
59
  const ownership = override ?? listed;
60
+ const enabled = {
61
+ context: !isVirtualAnalyticsTool && isContextEnabled(data.options.context),
62
+ conversationId: true === data.options.enableConversationId,
63
+ llmModel: isCaptureModelEnabled(data.options.captureModel)
64
+ };
65
+ return {
66
+ strip: enabledArgumentOwnership(enabled, ownership, false),
67
+ read: enabledArgumentOwnership(enabled, ownership, true),
68
+ outputInstructions: enabled.conversationId && listed?.outputInstructions === true
69
+ };
70
+ }
71
+ function enabledArgumentOwnership(enabled, ownership, whenUnknown) {
72
+ const resolved = ownership ?? {
73
+ context: whenUnknown,
74
+ conversationId: whenUnknown,
75
+ llmModel: whenUnknown
76
+ };
51
77
  return {
52
- contextOwnershipKnown: void 0 !== ownership,
53
- context: !isVirtualAnalyticsTool && isContextEnabled(data.options.context) && ownership?.context === true,
54
- conversationId: true === data.options.enableConversationId && ownership?.conversationId === true,
55
- llmModel: isCaptureModelEnabled(data.options.captureModel) && ownership?.llmModel === true,
56
- outputInstructions: true === data.options.enableConversationId && listed?.outputInstructions === true
78
+ context: enabled.context && resolved.context,
79
+ conversationId: enabled.conversationId && resolved.conversationId,
80
+ llmModel: enabled.llmModel && resolved.llmModel
57
81
  };
58
82
  }
59
83
  function cloneRequestWithoutOwnedAnalyticsArguments(request, ownership) {
@@ -90,7 +114,7 @@ async function prepareToolCallEvent(server, data, request, downstreamRequest, ex
90
114
  await applyResolvedMetadata(event, data, request, extra);
91
115
  setEventIntent(event, await resolveToolCallIntent(data, request, canCaptureContextIntent, extra));
92
116
  if (isCaptureModelEnabled(data.options.captureModel)) {
93
- const resolvedModel = resolveModel(request, ownership.llmModel);
117
+ const resolvedModel = resolveModel(request, ownership.read.llmModel);
94
118
  setEventModel(event, resolvedModel?.model, resolvedModel?.source);
95
119
  }
96
120
  return {
@@ -24,7 +24,7 @@ export interface ModelInjectableTool {
24
24
  name?: string;
25
25
  [key: string]: unknown;
26
26
  }
27
- /** Unlike `context`, model capture is opt-in: off unless explicitly enabled. */
27
+ /** Model capture is enabled unless explicitly disabled. */
28
28
  export declare function isCaptureModelEnabled(captureModel: MCPAnalyticsOptions['captureModel']): boolean;
29
29
  export declare function getModelDescription(captureModel: MCPAnalyticsOptions['captureModel']): string | undefined;
30
30
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"model-parameters.d.ts","sourceRoot":"","sources":["../../src/extensions/model-parameters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AACtG,OAAO,EAA+B,KAAK,6BAA6B,EAAE,MAAM,wBAAwB,CAAA;AAExG,OAAO,EAAO,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AAE7C;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,6BAA6B,CAAA;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,gFAAgF;AAChF,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,mBAAmB,CAAC,cAAc,CAAC,GAAG,OAAO,CAEhG;AAED,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,mBAAmB,CAAC,cAAc,CAAC,GAAG,MAAM,GAAG,SAAS,CAEzG;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,SAAS,mBAAmB,EACvE,IAAI,EAAE,KAAK,EACX,wBAAwB,CAAC,EAAE,MAAM,EACjC,MAAM,GAAE,QAAc,GACrB,KAAK,CAQP;AAED,wBAAgB,wBAAwB,CAAC,KAAK,SAAS,mBAAmB,EACxE,KAAK,EAAE,KAAK,EAAE,EACd,wBAAwB,CAAC,EAAE,MAAM,EACjC,MAAM,GAAE,QAAc,GACrB,KAAK,EAAE,CAET;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAE5E;AAID,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,uBAAuB,CAAA;CAChC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,OAAO,GAAG,aAAa,GAAG,SAAS,CAiB3G;AAiBD,wBAAgB,aAAa,CAC3B,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,GAAE,uBAAyC,GAChD,IAAI,CAON"}
1
+ {"version":3,"file":"model-parameters.d.ts","sourceRoot":"","sources":["../../src/extensions/model-parameters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AACtG,OAAO,EAA+B,KAAK,6BAA6B,EAAE,MAAM,wBAAwB,CAAA;AAExG,OAAO,EAAO,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AAE7C;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,6BAA6B,CAAA;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,2DAA2D;AAC3D,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,mBAAmB,CAAC,cAAc,CAAC,GAAG,OAAO,CAIhG;AAED,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,mBAAmB,CAAC,cAAc,CAAC,GAAG,MAAM,GAAG,SAAS,CAEzG;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,SAAS,mBAAmB,EACvE,IAAI,EAAE,KAAK,EACX,wBAAwB,CAAC,EAAE,MAAM,EACjC,MAAM,GAAE,QAAc,GACrB,KAAK,CAQP;AAED,wBAAgB,wBAAwB,CAAC,KAAK,SAAS,mBAAmB,EACxE,KAAK,EAAE,KAAK,EAAE,EACd,wBAAwB,CAAC,EAAE,MAAM,EACjC,MAAM,GAAE,QAAc,GACrB,KAAK,EAAE,CAET;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAE5E;AAID,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,uBAAuB,CAAA;CAChC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,OAAO,GAAG,aAAa,GAAG,SAAS,CAiB3G;AAiBD,wBAAgB,aAAa,CAC3B,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,GAAE,uBAAyC,GAChD,IAAI,CAON"}
@@ -40,7 +40,7 @@ const external_analytics_parameters_js_namespaceObject = require("./analytics-pa
40
40
  const external_constants_js_namespaceObject = require("./constants.js");
41
41
  const external_logger_js_namespaceObject = require("./logger.js");
42
42
  function isCaptureModelEnabled(captureModel) {
43
- return true === captureModel || 'object' == typeof captureModel && null !== captureModel;
43
+ return void 0 === captureModel || true === captureModel || 'object' == typeof captureModel && null !== captureModel;
44
44
  }
45
45
  function getModelDescription(captureModel) {
46
46
  return 'object' == typeof captureModel && null !== captureModel ? captureModel.description : void 0;
@@ -2,7 +2,7 @@ import { addAnalyticsParameterToTool } from "./analytics-parameters.mjs";
2
2
  import { DEFAULT_MODEL_PARAMETER_DESCRIPTION } from "./constants.mjs";
3
3
  import { log } from "./logger.mjs";
4
4
  function isCaptureModelEnabled(captureModel) {
5
- return true === captureModel || 'object' == typeof captureModel && null !== captureModel;
5
+ return void 0 === captureModel || true === captureModel || 'object' == typeof captureModel && null !== captureModel;
6
6
  }
7
7
  function getModelDescription(captureModel) {
8
8
  return 'object' == typeof captureModel && null !== captureModel ? captureModel.description : void 0;
@@ -26,7 +26,7 @@ export interface PostHogMCPOptions extends PostHogOptions {
26
26
  collectFeedback?: CollectFeedbackConfig;
27
27
  /**
28
28
  * Capture the calling model from recognized client metadata, with an injected
29
- * `llm_model` argument as fallback. Off by default.
29
+ * `llm_model` argument as fallback. On by default; `false` disables it.
30
30
  */
31
31
  captureModel?: MCPAnalyticsOptions['captureModel'];
32
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAQ3C,OAAO,EAAE,6BAA6B,EAAkB,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAIlG,OAAO,KAAK,EAGV,YAAY,EAEZ,mBAAmB,EAGpB,MAAM,SAAS,CAAA;AAEhB;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,iBAAS,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAE,mBAAwB,GAAG,YAAY,CA4CtG;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,iBAAS,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAKjH;AAwFD,OAAO,EAAE,6BAA6B,EAAE,YAAY,EAAE,CAAA;AAItD,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACL,4BAA4B,EAC5B,wBAAwB,EACxB,2BAA2B,GAC5B,MAAM,wBAAwB,CAAA;AAG/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACnF,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAI/C,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAA;AAC3D,YAAY,EACV,mBAAmB,EACnB,2BAA2B,EAC3B,sBAAsB,EACtB,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,YAAY,EACZ,gBAAgB,EAChB,0BAA0B,EAC1B,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,mBAAmB,EACnB,4BAA4B,EAC5B,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,EACtB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,GACb,MAAM,SAAS,CAAA;AAChB,MAAM,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAA;AAC9D,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAQ3C,OAAO,EAAE,6BAA6B,EAAkB,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAIlG,OAAO,KAAK,EAGV,YAAY,EAEZ,mBAAmB,EAGpB,MAAM,SAAS,CAAA;AAEhB;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,iBAAS,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAE,mBAAwB,GAAG,YAAY,CA4CtG;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,iBAAS,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAKjH;AA4FD,OAAO,EAAE,6BAA6B,EAAE,YAAY,EAAE,CAAA;AAItD,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACL,4BAA4B,EAC5B,wBAAwB,EACxB,2BAA2B,GAC5B,MAAM,wBAAwB,CAAA;AAG/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACnF,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAI/C,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAA;AAC3D,YAAY,EACV,mBAAmB,EACnB,2BAA2B,EAC3B,sBAAsB,EACtB,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,YAAY,EACZ,gBAAgB,EAChB,0BAA0B,EAC1B,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,mBAAmB,EACnB,4BAA4B,EAC5B,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,EACtB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,GACb,MAAM,SAAS,CAAA;AAChB,MAAM,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAA;AAC9D,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,CAAA"}
package/dist/index.js CHANGED
@@ -106,7 +106,8 @@ function getLowLevelServer(server) {
106
106
  const DEFAULT_OPTIONS = {
107
107
  reportMissing: false,
108
108
  collectFeedback: false,
109
- enableConversationId: false
109
+ enableConversationId: true,
110
+ captureModel: true
110
111
  };
111
112
  function buildTrackingData(lowLevelServer, options, sink, logger) {
112
113
  return {
@@ -121,7 +122,7 @@ function buildTrackingData(lowLevelServer, options, sink, logger) {
121
122
  sessionInfo: (0, session_js_namespaceObject.getSessionInfo)(lowLevelServer, void 0),
122
123
  options: {
123
124
  ...DEFAULT_OPTIONS,
124
- ...options
125
+ ...Object.fromEntries(Object.entries(options).filter(([, value])=>void 0 !== value))
125
126
  },
126
127
  sessionSource: 'generated'
127
128
  };
package/dist/index.mjs CHANGED
@@ -52,7 +52,8 @@ function getLowLevelServer(server) {
52
52
  const DEFAULT_OPTIONS = {
53
53
  reportMissing: false,
54
54
  collectFeedback: false,
55
- enableConversationId: false
55
+ enableConversationId: true,
56
+ captureModel: true
56
57
  };
57
58
  function buildTrackingData(lowLevelServer, options, sink, logger) {
58
59
  return {
@@ -67,7 +68,7 @@ function buildTrackingData(lowLevelServer, options, sink, logger) {
67
68
  sessionInfo: getSessionInfo(lowLevelServer, void 0),
68
69
  options: {
69
70
  ...DEFAULT_OPTIONS,
70
- ...options
71
+ ...Object.fromEntries(Object.entries(options).filter(([, value])=>void 0 !== value))
71
72
  },
72
73
  sessionSource: 'generated'
73
74
  };
package/dist/types.d.ts CHANGED
@@ -110,7 +110,7 @@ export interface MCPAnalyticsOptions {
110
110
  */
111
111
  missingCapabilityToolName?: string;
112
112
  /**
113
- * Opt in to session correlation for the MCP **2026-07-28** revision, which removed
113
+ * Enable session correlation for the MCP **2026-07-28** revision, which removed
114
114
  * protocol-level sessions: no `initialize`, no `mcp-session-id` header, and a fresh
115
115
  * server instance per HTTP request. With none of those left to anchor on, the only
116
116
  * thing that can carry a session across calls is the agent itself.
@@ -120,7 +120,7 @@ export interface MCPAnalyticsOptions {
120
120
  * that handle — so calls correlate across reconnects, restarts, and per-request
121
121
  * instances.
122
122
  *
123
- * Off by default, and fully inert when off: no parameter is injected, no schema is
123
+ * On by default, and fully inert when disabled: no parameter is injected, no schema is
124
124
  * touched, no prompt-back is appended, and `$session_id` resolves exactly as it did
125
125
  * before (the request's own session id, else this instance's).
126
126
  */
@@ -136,7 +136,7 @@ export interface MCPAnalyticsOptions {
136
136
  /**
137
137
  * Capture the calling model as `$mcp_llm_model`. Recognized client metadata
138
138
  * takes precedence, with an injected `llm_model` parameter as the fallback.
139
- * Off by default.
139
+ * On by default; set to `false` to disable capture.
140
140
  *
141
141
  * MCP does not standardize model identity. Some clients expose it through
142
142
  * vendor metadata; other harnesses inject it into the agent's system prompt
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const version = "0.16.4";
1
+ export declare const version = "0.17.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -27,7 +27,7 @@ var __webpack_require__ = {};
27
27
  })();
28
28
  var __webpack_exports__ = {};
29
29
  __webpack_require__.r(__webpack_exports__);
30
- const version = '0.16.4';
30
+ const version = '0.17.0';
31
31
  __webpack_require__.d(__webpack_exports__, {}, {
32
32
  version: version
33
33
  });
package/dist/version.mjs CHANGED
@@ -1,2 +1,2 @@
1
- const version = '0.16.4';
1
+ const version = '0.17.0';
2
2
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/mcp",
3
- "version": "0.16.4",
3
+ "version": "0.17.0",
4
4
  "bugs": {
5
5
  "url": "https://github.com/PostHog/posthog-js/issues"
6
6
  },
@@ -28,7 +28,7 @@
28
28
  "module": "dist/index.mjs",
29
29
  "types": "dist/index.d.ts",
30
30
  "dependencies": {
31
- "@posthog/core": "^1.54.3"
31
+ "@posthog/core": "^1.54.4"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@babel/preset-env": "^7.27.1",
@@ -52,8 +52,8 @@
52
52
  "zod4": "npm:zod@^4.4.3",
53
53
  "@vitest/coverage-v8": "^1.6.1",
54
54
  "vitest": "^1.6.1",
55
- "@posthog-tooling/tsconfig-base": "1.1.1",
56
- "posthog-node": "^5.52.4"
55
+ "posthog-node": "^5.52.4",
56
+ "@posthog-tooling/tsconfig-base": "1.1.1"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@modelcontextprotocol/sdk": ">=1.26.0",
@@ -44,7 +44,7 @@ import { buildCapturedMcpParameters } from './mcp-payloads'
44
44
  import { readRequestHandlerMethod } from './mcp-sdk-compat'
45
45
  import { getRequestHeaders } from './request-headers'
46
46
  import { getSessionId, getSessionInfo, isModernEraRequest, newSessionId } from './session'
47
- import { encodeSessionId, readMcpSessionHeader, writeSessionIdToTransport } from './session-token'
47
+ import { decodeSessionId, encodeSessionId, readMcpSessionHeader, writeSessionIdToTransport } from './session-token'
48
48
  import { getFeedbackToolDescriptor, resolveCollectFeedbackOptions, SEND_FEEDBACK_TOOL_NAME } from './feedback'
49
49
  import { getReportMissingToolDescriptor, resolveMissingCapabilityToolName } from './tools'
50
50
  import { applyResolvedMetadata, isToolResultError } from './tracing-helpers'
@@ -141,13 +141,11 @@ export async function captureToolCall(params: TraceToolCallParams): Promise<unkn
141
141
  // Reading the argument and removing it are separate questions: deleting one the
142
142
  // application declared costs the customer their call, so the strip below still
143
143
  // requires positive ownership, while reading it when ownership is unresolved
144
- // costs at worst a mislabelled property. ADR-0011.
145
- const canCaptureContextIntent =
146
- !isVirtualAnalyticsTool &&
147
- isContextEnabled(data.options.context) &&
148
- (ownership.context || !ownership.contextOwnershipKnown)
149
- const conversation = resolveConversationId(ownership.conversationId, request.params?.arguments)
150
- const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership)
144
+ // can mislabel a property. Conversation minting also writes a prompt-back;
145
+ // that separate tradeoff and its carried-session guard are recorded in ADR-0013.
146
+ const canCaptureContextIntent = ownership.read.context
147
+ const conversation = resolveToolConversation(ownership.read.conversationId, request.params?.arguments, extra)
148
+ const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership.strip)
151
149
 
152
150
  // Prepare the event in isolation: if identity/metadata/intent resolution
153
151
  // throws, we drop instrumentation for this call but still run the tool.
@@ -189,17 +187,36 @@ export async function captureToolCall(params: TraceToolCallParams): Promise<unkn
189
187
  return finalResult
190
188
  }
191
189
 
190
+ function resolveToolConversation(
191
+ enabled: boolean,
192
+ args: unknown,
193
+ extra?: CompatibleRequestHandlerExtra
194
+ ): ConversationIdResolution {
195
+ const conversation = resolveConversationId(enabled, args)
196
+ // Echoed handles span reconnects; a new handle must not replace a session
197
+ // already carried by this request or add a needless prompt-back to its result.
198
+ if (!conversation.minted) return conversation
199
+ const carriedSession = extra?.sessionId || decodeSessionId(readMcpSessionHeader(getRequestHeaders(extra)))
200
+ return carriedSession ? { minted: false, conversationId: undefined } : conversation
201
+ }
202
+
192
203
  interface PreparedToolEvent {
193
204
  event: McpEvent
194
205
  requestAttribution: SessionInfo
195
206
  }
196
207
 
197
208
  /**
198
- * Ownership for one request, plus whether it could be resolved at all — "no
199
- * answer" must not read the same as "the application owns it".
209
+ * Ownership for one request. The `strip` flags gate stripping and require
210
+ * positive ownership; `read` gates capture and additionally fails open where
211
+ * ownership could not be resolved — "no answer" must not read the same as "the
212
+ * application owns it". ADR-0011.
200
213
  */
201
- interface ActiveAnalyticsParameterOwnership extends AnalyticsParameterOwnership {
202
- contextOwnershipKnown: boolean
214
+ type ArgumentOwnership = Pick<AnalyticsParameterOwnership, 'context' | 'conversationId' | 'llmModel'>
215
+
216
+ interface ActiveAnalyticsParameterOwnership {
217
+ strip: ArgumentOwnership
218
+ read: ArgumentOwnership
219
+ outputInstructions: boolean
203
220
  }
204
221
 
205
222
  function getActiveAnalyticsParameterOwnership(
@@ -210,11 +227,14 @@ function getActiveAnalyticsParameterOwnership(
210
227
  ): ActiveAnalyticsParameterOwnership {
211
228
  const listed = toolName ? data.toolAnalyticsParameterOwnership.get(toolName) : undefined
212
229
  const ownership = override ?? listed
230
+ const enabled = {
231
+ context: !isVirtualAnalyticsTool && isContextEnabled(data.options.context),
232
+ conversationId: data.options.enableConversationId === true,
233
+ llmModel: isCaptureModelEnabled(data.options.captureModel),
234
+ }
213
235
  return {
214
- contextOwnershipKnown: ownership !== undefined,
215
- context: !isVirtualAnalyticsTool && isContextEnabled(data.options.context) && ownership?.context === true,
216
- conversationId: data.options.enableConversationId === true && ownership?.conversationId === true,
217
- llmModel: isCaptureModelEnabled(data.options.captureModel) && ownership?.llmModel === true,
236
+ strip: enabledArgumentOwnership(enabled, ownership, false),
237
+ read: enabledArgumentOwnership(enabled, ownership, true),
218
238
  // Deliberately read off `listed`, never the override: only the advertised
219
239
  // JSON Schema can say whether `tools/list` declared `_mcp_instructions` (an
220
240
  // override is built from the live registry, which holds Zod on the
@@ -222,13 +242,26 @@ function getActiveAnalyticsParameterOwnership(
222
242
  // and fails closed — writing an undeclared key fails the customer's entire
223
243
  // tool result under `additionalProperties: false`. See ADR-0004 for the
224
244
  // per-request-instance gap this leaves and the planned fix.
225
- outputInstructions: data.options.enableConversationId === true && listed?.outputInstructions === true,
245
+ outputInstructions: enabled.conversationId && listed?.outputInstructions === true,
246
+ }
247
+ }
248
+
249
+ function enabledArgumentOwnership(
250
+ enabled: ArgumentOwnership,
251
+ ownership: ArgumentOwnership | undefined,
252
+ whenUnknown: boolean
253
+ ): ArgumentOwnership {
254
+ const resolved = ownership ?? { context: whenUnknown, conversationId: whenUnknown, llmModel: whenUnknown }
255
+ return {
256
+ context: enabled.context && resolved.context,
257
+ conversationId: enabled.conversationId && resolved.conversationId,
258
+ llmModel: enabled.llmModel && resolved.llmModel,
226
259
  }
227
260
  }
228
261
 
229
262
  function cloneRequestWithoutOwnedAnalyticsArguments(
230
263
  request: MCPRequestLike,
231
- ownership: AnalyticsParameterOwnership
264
+ ownership: ArgumentOwnership
232
265
  ): MCPRequestLike {
233
266
  const args = request.params?.arguments
234
267
  const cleanedArgs = stripOwnedAnalyticsArguments(args, ownership)
@@ -252,7 +285,7 @@ async function prepareToolCallEvent(
252
285
  extra: CompatibleRequestHandlerExtra | undefined,
253
286
  startTime: Date,
254
287
  conversation: ConversationIdResolution,
255
- ownership: AnalyticsParameterOwnership,
288
+ ownership: ActiveAnalyticsParameterOwnership,
256
289
  eventType: MCPAnalyticsEventType,
257
290
  canCaptureContextIntent: boolean
258
291
  ): Promise<PreparedToolEvent | null> {
@@ -300,9 +333,9 @@ async function prepareToolCallEvent(
300
333
  await applyResolvedMetadata(event, data, request, extra)
301
334
  setEventIntent(event, await resolveToolCallIntent(data, request, canCaptureContextIntent, extra))
302
335
  // Client metadata does not collide with an application's tool arguments.
303
- // Self-report still requires positive ownership before we read `llm_model`.
336
+ // Self-report reads `llm_model` under the same fail-open rule as `context`.
304
337
  if (isCaptureModelEnabled(data.options.captureModel)) {
305
- const resolvedModel = resolveModel(request, ownership.llmModel)
338
+ const resolvedModel = resolveModel(request, ownership.read.llmModel)
306
339
  setEventModel(event, resolvedModel?.model, resolvedModel?.source)
307
340
  }
308
341
  return { event, requestAttribution }
@@ -328,7 +361,7 @@ function applyConversationInstructions(
328
361
  event: McpEvent | null,
329
362
  result: unknown,
330
363
  conversation: ConversationIdResolution,
331
- ownership: AnalyticsParameterOwnership
364
+ ownership: Pick<AnalyticsParameterOwnership, 'outputInstructions'>
332
365
  ): unknown {
333
366
  const conversationId = conversation.conversationId
334
367
  if (!conversationId) {
@@ -28,9 +28,11 @@ export interface ModelInjectableTool {
28
28
  [key: string]: unknown
29
29
  }
30
30
 
31
- /** Unlike `context`, model capture is opt-in: off unless explicitly enabled. */
31
+ /** Model capture is enabled unless explicitly disabled. */
32
32
  export function isCaptureModelEnabled(captureModel: MCPAnalyticsOptions['captureModel']): boolean {
33
- return captureModel === true || (typeof captureModel === 'object' && captureModel !== null)
33
+ return (
34
+ captureModel === undefined || captureModel === true || (typeof captureModel === 'object' && captureModel !== null)
35
+ )
34
36
  }
35
37
 
36
38
  export function getModelDescription(captureModel: MCPAnalyticsOptions['captureModel']): string | undefined {
@@ -71,7 +71,7 @@ export interface PostHogMCPOptions extends PostHogOptions {
71
71
  collectFeedback?: CollectFeedbackConfig
72
72
  /**
73
73
  * Capture the calling model from recognized client metadata, with an injected
74
- * `llm_model` argument as fallback. Off by default.
74
+ * `llm_model` argument as fallback. On by default; `false` disables it.
75
75
  */
76
76
  captureModel?: MCPAnalyticsOptions['captureModel']
77
77
  }
package/src/index.ts CHANGED
@@ -142,7 +142,8 @@ function getLowLevelServer(server: MCPServerLike | HighLevelMCPServerLike): MCPS
142
142
  const DEFAULT_OPTIONS = {
143
143
  reportMissing: false,
144
144
  collectFeedback: false,
145
- enableConversationId: false,
145
+ enableConversationId: true,
146
+ captureModel: true,
146
147
  } satisfies Partial<MCPAnalyticsOptions>
147
148
 
148
149
  function buildTrackingData(
@@ -161,7 +162,10 @@ function buildTrackingData(
161
162
  toolCategories: new Map<string, string>(),
162
163
  toolDescriptions: new Map<string, string>(),
163
164
  sessionInfo: getSessionInfo(lowLevelServer, undefined),
164
- options: { ...DEFAULT_OPTIONS, ...options },
165
+ options: {
166
+ ...DEFAULT_OPTIONS,
167
+ ...Object.fromEntries(Object.entries(options).filter(([, value]) => value !== undefined)),
168
+ },
165
169
  sessionSource: 'generated',
166
170
  }
167
171
  }
package/src/types.ts CHANGED
@@ -122,7 +122,7 @@ export interface MCPAnalyticsOptions {
122
122
  */
123
123
  missingCapabilityToolName?: string
124
124
  /**
125
- * Opt in to session correlation for the MCP **2026-07-28** revision, which removed
125
+ * Enable session correlation for the MCP **2026-07-28** revision, which removed
126
126
  * protocol-level sessions: no `initialize`, no `mcp-session-id` header, and a fresh
127
127
  * server instance per HTTP request. With none of those left to anchor on, the only
128
128
  * thing that can carry a session across calls is the agent itself.
@@ -132,7 +132,7 @@ export interface MCPAnalyticsOptions {
132
132
  * that handle — so calls correlate across reconnects, restarts, and per-request
133
133
  * instances.
134
134
  *
135
- * Off by default, and fully inert when off: no parameter is injected, no schema is
135
+ * On by default, and fully inert when disabled: no parameter is injected, no schema is
136
136
  * touched, no prompt-back is appended, and `$session_id` resolves exactly as it did
137
137
  * before (the request's own session id, else this instance's).
138
138
  */
@@ -148,7 +148,7 @@ export interface MCPAnalyticsOptions {
148
148
  /**
149
149
  * Capture the calling model as `$mcp_llm_model`. Recognized client metadata
150
150
  * takes precedence, with an injected `llm_model` parameter as the fallback.
151
- * Off by default.
151
+ * On by default; set to `false` to disable capture.
152
152
  *
153
153
  * MCP does not standardize model identity. Some clients expose it through
154
154
  * vendor metadata; other harnesses inject it into the agent's system prompt
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '0.16.4'
1
+ export const version = '0.17.0'