@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 +34 -12
- package/dist/extensions/instrumentation.d.ts.map +1 -1
- package/dist/extensions/instrumentation.js +33 -9
- package/dist/extensions/instrumentation.mjs +34 -10
- package/dist/extensions/model-parameters.d.ts +1 -1
- package/dist/extensions/model-parameters.d.ts.map +1 -1
- package/dist/extensions/model-parameters.js +1 -1
- package/dist/extensions/model-parameters.mjs +1 -1
- package/dist/extensions/posthog-mcp.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.mjs +3 -2
- package/dist/types.d.ts +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.mjs +1 -1
- package/package.json +4 -4
- package/src/extensions/instrumentation.ts +55 -22
- package/src/extensions/model-parameters.ts +4 -2
- package/src/extensions/posthog-mcp.ts +1 -1
- package/src/index.ts +6 -2
- package/src/types.ts +3 -3
- package/src/version.ts +1 -1
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 **
|
|
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 **
|
|
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
|
-
|
|
177
|
-
|
|
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
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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,
|
|
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
|
|
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":"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,
|
|
1
|
+
{"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"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"}
|
|
@@ -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 =
|
|
68
|
-
const conversation = (
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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 =
|
|
27
|
-
const conversation =
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
/**
|
|
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,
|
|
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.
|
|
29
|
+
* `llm_model` argument as fallback. On by default; `false` disables it.
|
|
30
30
|
*/
|
|
31
31
|
captureModel?: MCPAnalyticsOptions['captureModel'];
|
|
32
32
|
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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:
|
|
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:
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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.
|
|
1
|
+
export declare const version = "0.17.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
package/dist/version.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const version = '0.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
56
|
-
"posthog-
|
|
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
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
199
|
-
*
|
|
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
|
-
|
|
202
|
-
|
|
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
|
-
|
|
215
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
/**
|
|
31
|
+
/** Model capture is enabled unless explicitly disabled. */
|
|
32
32
|
export function isCaptureModelEnabled(captureModel: MCPAnalyticsOptions['captureModel']): boolean {
|
|
33
|
-
return
|
|
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.
|
|
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:
|
|
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: {
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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.
|
|
1
|
+
export const version = '0.17.0'
|