@posthog/mcp 0.11.3 → 0.11.5
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 +54 -0
- package/dist/extensions/instrumentation.d.ts +1 -3
- package/dist/extensions/instrumentation.d.ts.map +1 -1
- package/dist/extensions/instrumentation.js +22 -21
- package/dist/extensions/instrumentation.mjs +23 -22
- package/dist/extensions/intent.d.ts +1 -1
- package/dist/extensions/intent.d.ts.map +1 -1
- package/dist/extensions/intent.js +2 -2
- package/dist/extensions/intent.mjs +2 -2
- package/dist/extensions/session.d.ts +25 -1
- package/dist/extensions/session.d.ts.map +1 -1
- package/dist/extensions/session.js +24 -3
- package/dist/extensions/session.mjs +18 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.mjs +1 -1
- package/package.json +3 -3
- package/src/extensions/instrumentation.ts +75 -19
- package/src/extensions/intent.ts +2 -2
- package/src/extensions/session.ts +56 -0
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -73,6 +73,27 @@ Details: [docs/ARCHITECTURE.md §4](./docs/ARCHITECTURE.md).
|
|
|
73
73
|
`@modelcontextprotocol/{core,server,client}` v2 — and on both the high-level `McpServer` and the
|
|
74
74
|
low-level `Server`. Shapes are detected at runtime, so neither major is a dependency here.
|
|
75
75
|
|
|
76
|
+
### Sessions and client identity on a v2 server
|
|
77
|
+
|
|
78
|
+
Protocol revision is a property of each **request**, not of the server: a v2 server serves both
|
|
79
|
+
`2025-11-25` and `2026-07-28` traffic, and the SDK is instrumented once for both.
|
|
80
|
+
|
|
81
|
+
- **On `2026-07-28`** there is no `initialize` and no session header — the revision removed
|
|
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`.
|
|
84
|
+
- **On `2025-11-25`**, the session id and the client's name and version are exchanged once at
|
|
85
|
+
`initialize`. If your server builds a fresh `McpServer` per HTTP request — which
|
|
86
|
+
`createMcpHandler` does by default — the instance serving a later `tools/call` never saw that
|
|
87
|
+
handshake. The SDK bridges it by minting the `Mcp-Session-Id` token described above, which the
|
|
88
|
+
client replays on every request.
|
|
89
|
+
|
|
90
|
+
**That token only reaches the client if the transport builds response headers _after_ the handler
|
|
91
|
+
runs.** `@rekog/mcp-nest` with `enableJsonResponse: true` does; `createMcpHandler`'s legacy leg
|
|
92
|
+
does not, and there is no setting we can reach from inside the server. On that leg, expect
|
|
93
|
+
`$mcp_client_name` and `$mcp_client_version` to be absent for `2025-11-25` traffic — the protocol
|
|
94
|
+
version still arrives, because clients send it on the `MCP-Protocol-Version` header of every
|
|
95
|
+
request.
|
|
96
|
+
|
|
76
97
|
### Reading request headers in a callback
|
|
77
98
|
|
|
78
99
|
If your `identify`, `intentFallback`, `eventProperties` or `beforeSend` reads HTTP headers, it has
|
|
@@ -95,6 +116,39 @@ identify: async (request, extra) => {
|
|
|
95
116
|
}
|
|
96
117
|
```
|
|
97
118
|
|
|
119
|
+
### What `$mcp_intent` records, and how to turn it off
|
|
120
|
+
|
|
121
|
+
`context` defaults to **on**: the SDK adds a `context` parameter to every tool it advertises, asks
|
|
122
|
+
the agent to say why it is calling, and records the answer as `$mcp_intent`. It is stripped before
|
|
123
|
+
your tool runs wherever the SDK can confirm the parameter is its own.
|
|
124
|
+
|
|
125
|
+
On a server that builds a fresh instance per HTTP request — `createMcpHandler`, or `@rekog/mcp-nest`
|
|
126
|
+
in its stateless mode — that confirmation is not available: ownership is learned while serving
|
|
127
|
+
`tools/list`, and the instance handling a `tools/call` never served one. There the SDK records the
|
|
128
|
+
argument but does **not** strip it, because deleting an argument your tool declared would cost you
|
|
129
|
+
the call, while an extra key usually costs nothing.
|
|
130
|
+
|
|
131
|
+
What matters is instance lifetime, not statelessness. A server that is stateless at the transport
|
|
132
|
+
(`sessionIdGenerator: undefined`) but keeps one long-lived server object learns ownership from the
|
|
133
|
+
first `tools/list` and keeps it, so none of the above applies to it.
|
|
134
|
+
|
|
135
|
+
The consequence worth knowing: if **your own** tool declares a parameter named `context` and the SDK
|
|
136
|
+
cannot tell that it is yours, its value is recorded as `$mcp_intent`. It never leaves your project,
|
|
137
|
+
and it is capped at 2048 characters. Two ways out, both one line:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
instrument(server, posthog, { context: false }) // no injection, no capture
|
|
141
|
+
|
|
142
|
+
instrument(server, posthog, { // keep it, drop the property
|
|
143
|
+
beforeSend: (event) => {
|
|
144
|
+
delete event.properties.$mcp_intent
|
|
145
|
+
return event
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`intentFallback` is the third option: supply the intent yourself when the agent did not send one.
|
|
151
|
+
|
|
98
152
|
### If you switched to `instrument(server.server)`
|
|
99
153
|
|
|
100
154
|
Before v2 support landed, the compatibility gate rejected high-level v2 servers, and the usual
|
|
@@ -95,9 +95,7 @@ export declare function isToolAdvertised(server: MCPServerLike, toolName: string
|
|
|
95
95
|
* Captures each `tools/list` and injects the SDK-managed tools (context
|
|
96
96
|
* parameter, conversation id, `get_more_tools`) into the returned list.
|
|
97
97
|
*/
|
|
98
|
-
export declare function handleListToolsRequest(server: MCPServerLike, originalListToolsHandler: MCPRequestHandler, request: MCPRequestLike, extra: CompatibleRequestHandlerExtra | undefined, logger: LoggerFn): Promise<
|
|
99
|
-
tools: CompatibleToolsListLike['tools'];
|
|
100
|
-
}>;
|
|
98
|
+
export declare function handleListToolsRequest(server: MCPServerLike, originalListToolsHandler: MCPRequestHandler, request: MCPRequestLike, extra: CompatibleRequestHandlerExtra | undefined, logger: LoggerFn): Promise<CompatibleToolsListLike>;
|
|
101
99
|
export declare function cacheToolDescriptions(cache: Map<string, string>, tools: CompatibleToolsListLike['tools'] | undefined): void;
|
|
102
100
|
/**
|
|
103
101
|
* Category declared on a tool's `_meta` block (the MCP spec allows arbitrary
|
|
@@ -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,gBAAgB,EAChB,cAAc,EACd,aAAa,EAId,MAAM,UAAU,CAAA;AAcjB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAIrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AASxC;;;;;;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;;;;OAIG;IACH,SAAS,CAAC,EAAE,qBAAqB,CAAA;IACjC;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B;;;;;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":"AAKA,OAAO,KAAK,EACV,2BAA2B,EAC3B,6BAA6B,EAC7B,uBAAuB,EACvB,gBAAgB,EAChB,cAAc,EACd,aAAa,EAId,MAAM,UAAU,CAAA;AAcjB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAIrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AASxC;;;;;;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;;;;OAIG;IACH,SAAS,CAAC,EAAE,qBAAqB,CAAA;IACjC;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAA;CAClC;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CA8DnF;AAiOD;;;;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;AAsFD,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,CA4DlB"}
|
|
@@ -56,10 +56,11 @@ async function captureToolCall(params) {
|
|
|
56
56
|
const { server, data, request, extra, execute, parameterOwnership, eventType, explicitContextIntent, takeCapturedError } = params;
|
|
57
57
|
const resolvedEventType = eventType ?? external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpToolsCall;
|
|
58
58
|
const ownership = getActiveAnalyticsParameterOwnership(data, request.params?.name, parameterOwnership, resolvedEventType === external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpMissingCapability);
|
|
59
|
+
const canCaptureContextIntent = resolvedEventType !== external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpMissingCapability && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context) && (ownership.context || !ownership.contextOwnershipKnown);
|
|
59
60
|
const conversation = (0, external_conversation_id_js_namespaceObject.resolveConversationId)(ownership.conversationId, request.params?.arguments);
|
|
60
61
|
const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership);
|
|
61
62
|
const startTime = new Date();
|
|
62
|
-
const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType);
|
|
63
|
+
const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType, canCaptureContextIntent);
|
|
63
64
|
if (preparedEvent && explicitContextIntent) (0, external_intent_js_namespaceObject.setExplicitContextIntent)(preparedEvent.event, explicitContextIntent);
|
|
64
65
|
let result;
|
|
65
66
|
try {
|
|
@@ -69,13 +70,14 @@ async function captureToolCall(params) {
|
|
|
69
70
|
throw error;
|
|
70
71
|
}
|
|
71
72
|
const finalResult = applyConversationInstructions(preparedEvent?.event ?? null, result, conversation, ownership);
|
|
72
|
-
publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError);
|
|
73
|
+
publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError, result);
|
|
73
74
|
return finalResult;
|
|
74
75
|
}
|
|
75
76
|
function getActiveAnalyticsParameterOwnership(data, toolName, override, isMissingCapabilityTool) {
|
|
76
77
|
const listed = toolName ? data.toolAnalyticsParameterOwnership.get(toolName) : void 0;
|
|
77
78
|
const ownership = override ?? listed;
|
|
78
79
|
return {
|
|
80
|
+
contextOwnershipKnown: void 0 !== ownership,
|
|
79
81
|
context: !isMissingCapabilityTool && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context) && ownership?.context === true,
|
|
80
82
|
conversationId: true === data.options.enableConversationId && ownership?.conversationId === true,
|
|
81
83
|
outputInstructions: true === data.options.enableConversationId && listed?.outputInstructions === true
|
|
@@ -93,7 +95,7 @@ function cloneRequestWithoutOwnedAnalyticsArguments(request, ownership) {
|
|
|
93
95
|
}
|
|
94
96
|
};
|
|
95
97
|
}
|
|
96
|
-
async function prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, eventType) {
|
|
98
|
+
async function prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, eventType, canCaptureContextIntent) {
|
|
97
99
|
try {
|
|
98
100
|
const sessionId = (0, external_session_js_namespaceObject.getSessionId)(server, extra, conversation.conversationId);
|
|
99
101
|
const sessionInfo = (0, external_session_js_namespaceObject.getSessionInfo)(server, data, sessionId);
|
|
@@ -113,7 +115,7 @@ async function prepareToolCallEvent(server, data, request, downstreamRequest, ex
|
|
|
113
115
|
(0, external_client_identity_js_namespaceObject.stampClientIdentity)(event, request, extra, server);
|
|
114
116
|
(0, external_transport_identity_js_namespaceObject.stampTransportIdentity)(event, extra);
|
|
115
117
|
await (0, external_tracing_helpers_js_namespaceObject.applyResolvedMetadata)(event, data, request, extra);
|
|
116
|
-
(0, external_intent_js_namespaceObject.setEventIntent)(event, await (0, external_intent_js_namespaceObject.resolveToolCallIntent)(data, request,
|
|
118
|
+
(0, external_intent_js_namespaceObject.setEventIntent)(event, await (0, external_intent_js_namespaceObject.resolveToolCallIntent)(data, request, canCaptureContextIntent, extra));
|
|
117
119
|
return {
|
|
118
120
|
event,
|
|
119
121
|
requestAttribution
|
|
@@ -140,14 +142,14 @@ function applyConversationInstructions(event, result, conversation, ownership) {
|
|
|
140
142
|
if (!delivered && conversation.minted && event) event.conversationId = void 0;
|
|
141
143
|
return updated;
|
|
142
144
|
}
|
|
143
|
-
function publishSuccessfulToolEvent(server, preparedEvent, result, startTime, logger, takeCapturedError) {
|
|
145
|
+
function publishSuccessfulToolEvent(server, preparedEvent, result, startTime, logger, takeCapturedError, resultBeforeInstructions) {
|
|
144
146
|
if (!preparedEvent) return;
|
|
145
147
|
const { event, requestAttribution } = preparedEvent;
|
|
146
148
|
try {
|
|
147
149
|
if ((0, external_tracing_helpers_js_namespaceObject.isToolResultError)(result)) {
|
|
148
150
|
event.isError = true;
|
|
149
151
|
const capturedError = takeCapturedError?.();
|
|
150
|
-
event.error = (0, external_exceptions_js_namespaceObject.captureException)(capturedError ?? result);
|
|
152
|
+
event.error = (0, external_exceptions_js_namespaceObject.captureException)(capturedError ?? resultBeforeInstructions ?? result);
|
|
151
153
|
} else event.isError = false;
|
|
152
154
|
event.response = result;
|
|
153
155
|
event.duration = Date.now() - startTime.getTime();
|
|
@@ -223,7 +225,7 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
|
|
|
223
225
|
const data = (0, external_internal_js_namespaceObject.getServerTrackingData)(server);
|
|
224
226
|
const startTime = new Date();
|
|
225
227
|
const sessionId = (0, external_session_js_namespaceObject.getSessionId)(server, extra);
|
|
226
|
-
const
|
|
228
|
+
const sessionInfo = (0, external_session_js_namespaceObject.getSessionInfo)(server, data, sessionId);
|
|
227
229
|
const event = {
|
|
228
230
|
sessionId,
|
|
229
231
|
parameters: (0, external_mcp_payloads_js_namespaceObject.buildCapturedMcpParameters)(request),
|
|
@@ -232,13 +234,14 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
|
|
|
232
234
|
};
|
|
233
235
|
(0, external_client_identity_js_namespaceObject.stampClientIdentity)(event, request, extra, server);
|
|
234
236
|
(0, external_transport_identity_js_namespaceObject.stampTransportIdentity)(event, extra);
|
|
237
|
+
const identity = data ? await (0, external_internal_js_namespaceObject.handleIdentify)(server, data, sessionId, request, sessionInfo, extra) : void 0;
|
|
238
|
+
const requestAttribution = (0, external_internal_js_namespaceObject.withIdentity)(sessionInfo, identity);
|
|
235
239
|
if (data) await (0, external_tracing_helpers_js_namespaceObject.applyResolvedMetadata)(event, data, request, extra);
|
|
236
|
-
const
|
|
240
|
+
const response = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger, requestAttribution);
|
|
241
|
+
const tools = response.tools;
|
|
237
242
|
if (!data) {
|
|
238
243
|
logger('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
|
|
239
|
-
return
|
|
240
|
-
tools
|
|
241
|
-
};
|
|
244
|
+
return response;
|
|
242
245
|
}
|
|
243
246
|
if (0 === tools.length) {
|
|
244
247
|
data.logger('Warning: No tools found in the original list. This is likely due to the tools not being registered before PostHog MCP analytics.instrument().');
|
|
@@ -246,20 +249,14 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
|
|
|
246
249
|
event.isError = true;
|
|
247
250
|
event.duration = Date.now() - startTime.getTime();
|
|
248
251
|
(0, external_capture_js_namespaceObject.captureEvent)(server, event, data.logger, requestAttribution);
|
|
249
|
-
return
|
|
250
|
-
tools
|
|
251
|
-
};
|
|
252
|
+
return response;
|
|
252
253
|
}
|
|
253
|
-
event.response =
|
|
254
|
-
tools
|
|
255
|
-
};
|
|
254
|
+
event.response = response;
|
|
256
255
|
event.listedToolNames = collectListedToolNames(tools);
|
|
257
256
|
event.isError = false;
|
|
258
257
|
event.duration = Date.now() - startTime.getTime();
|
|
259
258
|
(0, external_capture_js_namespaceObject.captureEvent)(server, event, data.logger, requestAttribution);
|
|
260
|
-
return
|
|
261
|
-
tools
|
|
262
|
-
};
|
|
259
|
+
return response;
|
|
263
260
|
}
|
|
264
261
|
function cacheToolAnalyticsParameterOwnership(cache, tools) {
|
|
265
262
|
for (const tool of tools)if (tool?.name) cache.set(tool.name, (0, external_analytics_parameters_js_namespaceObject.getAnalyticsParameterOwnership)(tool.inputSchema, tool.outputSchema));
|
|
@@ -300,7 +297,10 @@ async function getTracedToolsList(server, originalListToolsHandler, request, ext
|
|
|
300
297
|
cacheToolDescriptions(data.toolDescriptions, tools);
|
|
301
298
|
cacheToolCategories(data.toolCategories, tools);
|
|
302
299
|
}
|
|
303
|
-
return
|
|
300
|
+
return {
|
|
301
|
+
...originalResponse,
|
|
302
|
+
tools
|
|
303
|
+
};
|
|
304
304
|
} catch (error) {
|
|
305
305
|
logger(`Warning: Original list tools handler failed, this suggests an error PostHog MCP analytics did not cause - ${error}`);
|
|
306
306
|
event.error = (0, external_exceptions_js_namespaceObject.captureException)(error);
|
|
@@ -329,6 +329,7 @@ function mintStatelessSessionOnInitialize(server, data, request, extra) {
|
|
|
329
329
|
try {
|
|
330
330
|
const headers = (0, external_request_headers_js_namespaceObject.getRequestHeaders)(extra);
|
|
331
331
|
if (!headers) return;
|
|
332
|
+
if ((0, external_session_js_namespaceObject.isModernEraRequest)(request, extra, server)) return;
|
|
332
333
|
if ((0, external_session_token_js_namespaceObject.readMcpSessionHeader)(headers)) return;
|
|
333
334
|
const transport = server.transport;
|
|
334
335
|
if (!transport || extra?.sessionId || transport.sessionId) return;
|
|
@@ -12,7 +12,7 @@ import { getServerTrackingData, handleIdentify, setServerTrackingData, withIdent
|
|
|
12
12
|
import { buildCapturedMcpParameters } from "./mcp-payloads.mjs";
|
|
13
13
|
import { readRequestHandlerMethod } from "./mcp-sdk-compat.mjs";
|
|
14
14
|
import { getRequestHeaders } from "./request-headers.mjs";
|
|
15
|
-
import { getSessionId, getSessionInfo, newSessionId } from "./session.mjs";
|
|
15
|
+
import { getSessionId, getSessionInfo, isModernEraRequest, newSessionId } from "./session.mjs";
|
|
16
16
|
import { encodeSessionId, readMcpSessionHeader, writeSessionIdToTransport } from "./session-token.mjs";
|
|
17
17
|
import { getReportMissingToolDescriptor, resolveMissingCapabilityToolName } from "./tools.mjs";
|
|
18
18
|
import { applyResolvedMetadata, isToolResultError } from "./tracing-helpers.mjs";
|
|
@@ -20,10 +20,11 @@ async function captureToolCall(params) {
|
|
|
20
20
|
const { server, data, request, extra, execute, parameterOwnership, eventType, explicitContextIntent, takeCapturedError } = params;
|
|
21
21
|
const resolvedEventType = eventType ?? MCPAnalyticsEventType.mcpToolsCall;
|
|
22
22
|
const ownership = getActiveAnalyticsParameterOwnership(data, request.params?.name, parameterOwnership, resolvedEventType === MCPAnalyticsEventType.mcpMissingCapability);
|
|
23
|
+
const canCaptureContextIntent = resolvedEventType !== MCPAnalyticsEventType.mcpMissingCapability && isContextEnabled(data.options.context) && (ownership.context || !ownership.contextOwnershipKnown);
|
|
23
24
|
const conversation = resolveConversationId(ownership.conversationId, request.params?.arguments);
|
|
24
25
|
const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership);
|
|
25
26
|
const startTime = new Date();
|
|
26
|
-
const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType);
|
|
27
|
+
const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType, canCaptureContextIntent);
|
|
27
28
|
if (preparedEvent && explicitContextIntent) setExplicitContextIntent(preparedEvent.event, explicitContextIntent);
|
|
28
29
|
let result;
|
|
29
30
|
try {
|
|
@@ -33,13 +34,14 @@ async function captureToolCall(params) {
|
|
|
33
34
|
throw error;
|
|
34
35
|
}
|
|
35
36
|
const finalResult = applyConversationInstructions(preparedEvent?.event ?? null, result, conversation, ownership);
|
|
36
|
-
publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError);
|
|
37
|
+
publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError, result);
|
|
37
38
|
return finalResult;
|
|
38
39
|
}
|
|
39
40
|
function getActiveAnalyticsParameterOwnership(data, toolName, override, isMissingCapabilityTool) {
|
|
40
41
|
const listed = toolName ? data.toolAnalyticsParameterOwnership.get(toolName) : void 0;
|
|
41
42
|
const ownership = override ?? listed;
|
|
42
43
|
return {
|
|
44
|
+
contextOwnershipKnown: void 0 !== ownership,
|
|
43
45
|
context: !isMissingCapabilityTool && isContextEnabled(data.options.context) && ownership?.context === true,
|
|
44
46
|
conversationId: true === data.options.enableConversationId && ownership?.conversationId === true,
|
|
45
47
|
outputInstructions: true === data.options.enableConversationId && listed?.outputInstructions === true
|
|
@@ -57,7 +59,7 @@ function cloneRequestWithoutOwnedAnalyticsArguments(request, ownership) {
|
|
|
57
59
|
}
|
|
58
60
|
};
|
|
59
61
|
}
|
|
60
|
-
async function prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, eventType) {
|
|
62
|
+
async function prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, eventType, canCaptureContextIntent) {
|
|
61
63
|
try {
|
|
62
64
|
const sessionId = getSessionId(server, extra, conversation.conversationId);
|
|
63
65
|
const sessionInfo = getSessionInfo(server, data, sessionId);
|
|
@@ -77,7 +79,7 @@ async function prepareToolCallEvent(server, data, request, downstreamRequest, ex
|
|
|
77
79
|
stampClientIdentity(event, request, extra, server);
|
|
78
80
|
stampTransportIdentity(event, extra);
|
|
79
81
|
await applyResolvedMetadata(event, data, request, extra);
|
|
80
|
-
setEventIntent(event, await resolveToolCallIntent(data, request,
|
|
82
|
+
setEventIntent(event, await resolveToolCallIntent(data, request, canCaptureContextIntent, extra));
|
|
81
83
|
return {
|
|
82
84
|
event,
|
|
83
85
|
requestAttribution
|
|
@@ -104,14 +106,14 @@ function applyConversationInstructions(event, result, conversation, ownership) {
|
|
|
104
106
|
if (!delivered && conversation.minted && event) event.conversationId = void 0;
|
|
105
107
|
return updated;
|
|
106
108
|
}
|
|
107
|
-
function publishSuccessfulToolEvent(server, preparedEvent, result, startTime, logger, takeCapturedError) {
|
|
109
|
+
function publishSuccessfulToolEvent(server, preparedEvent, result, startTime, logger, takeCapturedError, resultBeforeInstructions) {
|
|
108
110
|
if (!preparedEvent) return;
|
|
109
111
|
const { event, requestAttribution } = preparedEvent;
|
|
110
112
|
try {
|
|
111
113
|
if (isToolResultError(result)) {
|
|
112
114
|
event.isError = true;
|
|
113
115
|
const capturedError = takeCapturedError?.();
|
|
114
|
-
event.error = captureException(capturedError ?? result);
|
|
116
|
+
event.error = captureException(capturedError ?? resultBeforeInstructions ?? result);
|
|
115
117
|
} else event.isError = false;
|
|
116
118
|
event.response = result;
|
|
117
119
|
event.duration = Date.now() - startTime.getTime();
|
|
@@ -187,7 +189,7 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
|
|
|
187
189
|
const data = getServerTrackingData(server);
|
|
188
190
|
const startTime = new Date();
|
|
189
191
|
const sessionId = getSessionId(server, extra);
|
|
190
|
-
const
|
|
192
|
+
const sessionInfo = getSessionInfo(server, data, sessionId);
|
|
191
193
|
const event = {
|
|
192
194
|
sessionId,
|
|
193
195
|
parameters: buildCapturedMcpParameters(request),
|
|
@@ -196,13 +198,14 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
|
|
|
196
198
|
};
|
|
197
199
|
stampClientIdentity(event, request, extra, server);
|
|
198
200
|
stampTransportIdentity(event, extra);
|
|
201
|
+
const identity = data ? await handleIdentify(server, data, sessionId, request, sessionInfo, extra) : void 0;
|
|
202
|
+
const requestAttribution = withIdentity(sessionInfo, identity);
|
|
199
203
|
if (data) await applyResolvedMetadata(event, data, request, extra);
|
|
200
|
-
const
|
|
204
|
+
const response = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger, requestAttribution);
|
|
205
|
+
const tools = response.tools;
|
|
201
206
|
if (!data) {
|
|
202
207
|
logger('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
|
|
203
|
-
return
|
|
204
|
-
tools
|
|
205
|
-
};
|
|
208
|
+
return response;
|
|
206
209
|
}
|
|
207
210
|
if (0 === tools.length) {
|
|
208
211
|
data.logger('Warning: No tools found in the original list. This is likely due to the tools not being registered before PostHog MCP analytics.instrument().');
|
|
@@ -210,20 +213,14 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
|
|
|
210
213
|
event.isError = true;
|
|
211
214
|
event.duration = Date.now() - startTime.getTime();
|
|
212
215
|
captureEvent(server, event, data.logger, requestAttribution);
|
|
213
|
-
return
|
|
214
|
-
tools
|
|
215
|
-
};
|
|
216
|
+
return response;
|
|
216
217
|
}
|
|
217
|
-
event.response =
|
|
218
|
-
tools
|
|
219
|
-
};
|
|
218
|
+
event.response = response;
|
|
220
219
|
event.listedToolNames = collectListedToolNames(tools);
|
|
221
220
|
event.isError = false;
|
|
222
221
|
event.duration = Date.now() - startTime.getTime();
|
|
223
222
|
captureEvent(server, event, data.logger, requestAttribution);
|
|
224
|
-
return
|
|
225
|
-
tools
|
|
226
|
-
};
|
|
223
|
+
return response;
|
|
227
224
|
}
|
|
228
225
|
function cacheToolAnalyticsParameterOwnership(cache, tools) {
|
|
229
226
|
for (const tool of tools)if (tool?.name) cache.set(tool.name, getAnalyticsParameterOwnership(tool.inputSchema, tool.outputSchema));
|
|
@@ -264,7 +261,10 @@ async function getTracedToolsList(server, originalListToolsHandler, request, ext
|
|
|
264
261
|
cacheToolDescriptions(data.toolDescriptions, tools);
|
|
265
262
|
cacheToolCategories(data.toolCategories, tools);
|
|
266
263
|
}
|
|
267
|
-
return
|
|
264
|
+
return {
|
|
265
|
+
...originalResponse,
|
|
266
|
+
tools
|
|
267
|
+
};
|
|
268
268
|
} catch (error) {
|
|
269
269
|
logger(`Warning: Original list tools handler failed, this suggests an error PostHog MCP analytics did not cause - ${error}`);
|
|
270
270
|
event.error = captureException(error);
|
|
@@ -293,6 +293,7 @@ function mintStatelessSessionOnInitialize(server, data, request, extra) {
|
|
|
293
293
|
try {
|
|
294
294
|
const headers = getRequestHeaders(extra);
|
|
295
295
|
if (!headers) return;
|
|
296
|
+
if (isModernEraRequest(request, extra, server)) return;
|
|
296
297
|
if (readMcpSessionHeader(headers)) return;
|
|
297
298
|
const transport = server.transport;
|
|
298
299
|
if (!transport || extra?.sessionId || transport.sessionId) return;
|
|
@@ -3,7 +3,7 @@ interface ResolvedIntent {
|
|
|
3
3
|
intent: string;
|
|
4
4
|
source: MCPAnalyticsIntentSource;
|
|
5
5
|
}
|
|
6
|
-
export declare function resolveToolCallIntent(data: MCPAnalyticsData, request: MCPRequestLike,
|
|
6
|
+
export declare function resolveToolCallIntent(data: MCPAnalyticsData, request: MCPRequestLike, canCaptureContext: boolean, extra?: CompatibleRequestHandlerExtra): Promise<ResolvedIntent | null>;
|
|
7
7
|
export declare function setEventIntent(event: McpEvent, resolvedIntent: ResolvedIntent | null): void;
|
|
8
8
|
export declare function setExplicitContextIntent(event: McpEvent, context: string): void;
|
|
9
9
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../../src/extensions/intent.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,wBAAwB,EACxB,cAAc,EACd,QAAQ,EACT,MAAM,UAAU,CAAA;AAGjB,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,wBAAwB,CAAA;CACjC;AAkCD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,cAAc,EACvB,
|
|
1
|
+
{"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../../src/extensions/intent.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,wBAAwB,EACxB,cAAc,EACd,QAAQ,EACT,MAAM,UAAU,CAAA;AAGjB,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,wBAAwB,CAAA;CACjC;AAkCD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,cAAc,EACvB,iBAAiB,EAAE,OAAO,EAC1B,KAAK,CAAC,EAAE,6BAA6B,GACpC,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAOhC;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,cAAc,GAAG,IAAI,GAAG,IAAI,CAO3F;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAQ/E"}
|
|
@@ -51,9 +51,9 @@ async function runIntentFallback(data, request, extra) {
|
|
|
51
51
|
return null;
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
async function resolveToolCallIntent(data, request,
|
|
54
|
+
async function resolveToolCallIntent(data, request, canCaptureContext, extra) {
|
|
55
55
|
const contextArgument = getContextArgument(request);
|
|
56
|
-
if (
|
|
56
|
+
if (canCaptureContext && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context) && contextArgument) return {
|
|
57
57
|
intent: contextArgument,
|
|
58
58
|
source: 'context_parameter'
|
|
59
59
|
};
|
|
@@ -21,9 +21,9 @@ async function runIntentFallback(data, request, extra) {
|
|
|
21
21
|
return null;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
async function resolveToolCallIntent(data, request,
|
|
24
|
+
async function resolveToolCallIntent(data, request, canCaptureContext, extra) {
|
|
25
25
|
const contextArgument = getContextArgument(request);
|
|
26
|
-
if (
|
|
26
|
+
if (canCaptureContext && isContextEnabled(data.options.context) && contextArgument) return {
|
|
27
27
|
intent: contextArgument,
|
|
28
28
|
source: 'context_parameter'
|
|
29
29
|
};
|
|
@@ -1,4 +1,28 @@
|
|
|
1
|
-
import type { CompatibleRequestHandlerExtra, MCPAnalyticsData, MCPServerLike, SessionInfo } from '../types';
|
|
1
|
+
import type { CompatibleRequestHandlerExtra, MCPAnalyticsData, MCPRequestLike, MCPServerLike, SessionInfo } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* The revision that removed protocol-level sessions. A request declaring this or
|
|
4
|
+
* anything later must not be answered with an `Mcp-Session-Id`.
|
|
5
|
+
*
|
|
6
|
+
* Compared as a string, which is safe and stable because MCP revisions are
|
|
7
|
+
* ISO dates: any future revision sorts above this one and is treated as modern,
|
|
8
|
+
* which is the right default — sessions were removed, not re-added.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MODERN_PROTOCOL_REVISION = "2026-07-28";
|
|
11
|
+
/**
|
|
12
|
+
* Whether *this request* is governed by 2026-07-28 or later.
|
|
13
|
+
*
|
|
14
|
+
* Era is a property of the request, never of the installed SDK: one v2 server
|
|
15
|
+
* serves both, request by request, and v2's exported `LATEST_PROTOCOL_VERSION`
|
|
16
|
+
* still reads `2025-11-25`. So the version is resolved from the request itself —
|
|
17
|
+
* an `initialize` body declares the version it is asking for, and every other
|
|
18
|
+
* request carries it in the envelope, `_meta`, or the `MCP-Protocol-Version`
|
|
19
|
+
* header — and only then compared.
|
|
20
|
+
*
|
|
21
|
+
* Unknown means legacy. A request that declares nothing is a v1 client on a
|
|
22
|
+
* transport that has always had a session header, and taking it away from them
|
|
23
|
+
* would be the regression.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isModernEraRequest(request: MCPRequestLike, extra?: CompatibleRequestHandlerExtra, server?: MCPServerLike): boolean;
|
|
2
26
|
export declare function newSessionId(): string;
|
|
3
27
|
/**
|
|
4
28
|
* Derives the SDK session id deterministically from the MCP sessionId, so the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/extensions/session.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,aAAa,EAEb,WAAW,EACZ,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/extensions/session.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,cAAc,EACd,aAAa,EAEb,WAAW,EACZ,MAAM,UAAU,CAAA;AASjB;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,eAAe,CAAA;AAcpD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,cAAc,EACvB,KAAK,CAAC,EAAE,6BAA6B,EACrC,MAAM,CAAC,EAAE,aAAa,GACrB,OAAO,CAYT;AAED,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,+BAA+B,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,aAAa,EACrB,KAAK,CAAC,EAAE,6BAA6B,EACrC,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,CAgCR;AAuED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,gBAAgB,GAAG,SAAS,EAClC,SAAS,CAAC,EAAE,MAAM,GACjB,WAAW,CAmCb"}
|
|
@@ -24,18 +24,35 @@ var __webpack_require__ = {};
|
|
|
24
24
|
var __webpack_exports__ = {};
|
|
25
25
|
__webpack_require__.r(__webpack_exports__);
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
isModernEraRequest: ()=>isModernEraRequest,
|
|
27
28
|
deriveSessionIdFromConversation: ()=>deriveSessionIdFromConversation,
|
|
28
|
-
|
|
29
|
+
newSessionId: ()=>newSessionId,
|
|
30
|
+
MODERN_PROTOCOL_REVISION: ()=>MODERN_PROTOCOL_REVISION,
|
|
29
31
|
deriveSessionIdFromMCPSession: ()=>deriveSessionIdFromMCPSession,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
getSessionId: ()=>getSessionId,
|
|
33
|
+
getSessionInfo: ()=>getSessionInfo
|
|
32
34
|
});
|
|
33
35
|
const external_version_js_namespaceObject = require("../version.js");
|
|
34
36
|
const external_constants_js_namespaceObject = require("./constants.js");
|
|
35
37
|
const external_ids_js_namespaceObject = require("./ids.js");
|
|
38
|
+
const external_client_identity_js_namespaceObject = require("./client-identity.js");
|
|
36
39
|
const external_internal_js_namespaceObject = require("./internal.js");
|
|
37
40
|
const external_request_headers_js_namespaceObject = require("./request-headers.js");
|
|
38
41
|
const external_session_token_js_namespaceObject = require("./session-token.js");
|
|
42
|
+
const MODERN_PROTOCOL_REVISION = '2026-07-28';
|
|
43
|
+
const REVISION_SHAPE = /^\d{4}-\d{2}-\d{2}$/;
|
|
44
|
+
const DRAFT_REVISION = 'draft';
|
|
45
|
+
function isModernEraRequest(request, extra, server) {
|
|
46
|
+
const requested = request.params?.protocolVersion;
|
|
47
|
+
const version = ('string' == typeof requested && requested.length > 0 ? requested : void 0) ?? (0, external_client_identity_js_namespaceObject.resolveClientIdentity)({
|
|
48
|
+
request,
|
|
49
|
+
extra,
|
|
50
|
+
server
|
|
51
|
+
})?.protocolVersion;
|
|
52
|
+
if (!version) return false;
|
|
53
|
+
if (version === DRAFT_REVISION) return true;
|
|
54
|
+
return REVISION_SHAPE.test(version) && version >= MODERN_PROTOCOL_REVISION;
|
|
55
|
+
}
|
|
39
56
|
function newSessionId() {
|
|
40
57
|
return (0, external_ids_js_namespaceObject.newPrefixedId)('ses');
|
|
41
58
|
}
|
|
@@ -111,16 +128,20 @@ function getSessionInfo(server, data, sessionId) {
|
|
|
111
128
|
...sessionInfo
|
|
112
129
|
};
|
|
113
130
|
}
|
|
131
|
+
exports.MODERN_PROTOCOL_REVISION = __webpack_exports__.MODERN_PROTOCOL_REVISION;
|
|
114
132
|
exports.deriveSessionIdFromConversation = __webpack_exports__.deriveSessionIdFromConversation;
|
|
115
133
|
exports.deriveSessionIdFromMCPSession = __webpack_exports__.deriveSessionIdFromMCPSession;
|
|
116
134
|
exports.getSessionId = __webpack_exports__.getSessionId;
|
|
117
135
|
exports.getSessionInfo = __webpack_exports__.getSessionInfo;
|
|
136
|
+
exports.isModernEraRequest = __webpack_exports__.isModernEraRequest;
|
|
118
137
|
exports.newSessionId = __webpack_exports__.newSessionId;
|
|
119
138
|
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
139
|
+
"MODERN_PROTOCOL_REVISION",
|
|
120
140
|
"deriveSessionIdFromConversation",
|
|
121
141
|
"deriveSessionIdFromMCPSession",
|
|
122
142
|
"getSessionId",
|
|
123
143
|
"getSessionInfo",
|
|
144
|
+
"isModernEraRequest",
|
|
124
145
|
"newSessionId"
|
|
125
146
|
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
126
147
|
Object.defineProperty(exports, '__esModule', {
|
|
@@ -1,9 +1,24 @@
|
|
|
1
|
-
import { version } from "../version.mjs";
|
|
1
|
+
import { version as external_version_mjs_version } from "../version.mjs";
|
|
2
2
|
import { INACTIVITY_TIMEOUT_IN_MINUTES } from "./constants.mjs";
|
|
3
3
|
import { deterministicPrefixedId, newPrefixedId } from "./ids.mjs";
|
|
4
|
+
import { resolveClientIdentity } from "./client-identity.mjs";
|
|
4
5
|
import { getServerTrackingData, setServerTrackingData } from "./internal.mjs";
|
|
5
6
|
import { getRequestHeaders } from "./request-headers.mjs";
|
|
6
7
|
import { decodeSessionId, readMcpSessionHeader } from "./session-token.mjs";
|
|
8
|
+
const MODERN_PROTOCOL_REVISION = '2026-07-28';
|
|
9
|
+
const REVISION_SHAPE = /^\d{4}-\d{2}-\d{2}$/;
|
|
10
|
+
const DRAFT_REVISION = 'draft';
|
|
11
|
+
function isModernEraRequest(request, extra, server) {
|
|
12
|
+
const requested = request.params?.protocolVersion;
|
|
13
|
+
const version = ('string' == typeof requested && requested.length > 0 ? requested : void 0) ?? resolveClientIdentity({
|
|
14
|
+
request,
|
|
15
|
+
extra,
|
|
16
|
+
server
|
|
17
|
+
})?.protocolVersion;
|
|
18
|
+
if (!version) return false;
|
|
19
|
+
if (version === DRAFT_REVISION) return true;
|
|
20
|
+
return REVISION_SHAPE.test(version) && version >= MODERN_PROTOCOL_REVISION;
|
|
21
|
+
}
|
|
7
22
|
function newSessionId() {
|
|
8
23
|
return newPrefixedId('ses');
|
|
9
24
|
}
|
|
@@ -62,7 +77,7 @@ function getSessionInfo(server, data, sessionId) {
|
|
|
62
77
|
const sessionInfo = {
|
|
63
78
|
ipAddress: void 0,
|
|
64
79
|
sdkLanguage: 'TypeScript',
|
|
65
|
-
sdkVersion:
|
|
80
|
+
sdkVersion: external_version_mjs_version,
|
|
66
81
|
serverName: server._serverInfo?.name,
|
|
67
82
|
serverVersion: server._serverInfo?.version,
|
|
68
83
|
clientName: clientInfo?.name,
|
|
@@ -79,4 +94,4 @@ function getSessionInfo(server, data, sessionId) {
|
|
|
79
94
|
...sessionInfo
|
|
80
95
|
};
|
|
81
96
|
}
|
|
82
|
-
export { deriveSessionIdFromConversation, deriveSessionIdFromMCPSession, getSessionId, getSessionInfo, newSessionId };
|
|
97
|
+
export { MODERN_PROTOCOL_REVISION, deriveSessionIdFromConversation, deriveSessionIdFromMCPSession, getSessionId, getSessionInfo, isModernEraRequest, newSessionId };
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version = "0.11.
|
|
1
|
+
export declare const version = "0.11.5";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
|
@@ -26,7 +26,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
27
|
version: ()=>version
|
|
28
28
|
});
|
|
29
|
-
const version = '0.11.
|
|
29
|
+
const version = '0.11.5';
|
|
30
30
|
exports.version = __webpack_exports__.version;
|
|
31
31
|
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
32
32
|
"version"
|
package/dist/version.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const version = '0.11.
|
|
1
|
+
const version = '0.11.5';
|
|
2
2
|
export { version };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/mcp",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.5",
|
|
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.
|
|
31
|
+
"@posthog/core": "^1.48.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@babel/preset-env": "^7.27.1",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"jest": "29.7.0",
|
|
42
42
|
"zod": "^3.25.0",
|
|
43
43
|
"@posthog-tooling/tsconfig-base": "1.1.1",
|
|
44
|
-
"posthog-node": "^5.
|
|
44
|
+
"posthog-node": "^5.49.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@modelcontextprotocol/sdk": ">=1.26.0",
|
|
@@ -35,7 +35,7 @@ import type { LoggerFn } from './logger'
|
|
|
35
35
|
import { buildCapturedMcpParameters } from './mcp-payloads'
|
|
36
36
|
import { readRequestHandlerMethod } from './mcp-sdk-compat'
|
|
37
37
|
import { getRequestHeaders } from './request-headers'
|
|
38
|
-
import { getSessionId, getSessionInfo, newSessionId } from './session'
|
|
38
|
+
import { getSessionId, getSessionInfo, isModernEraRequest, newSessionId } from './session'
|
|
39
39
|
import { encodeSessionId, readMcpSessionHeader, writeSessionIdToTransport } from './session-token'
|
|
40
40
|
import { getReportMissingToolDescriptor, resolveMissingCapabilityToolName } from './tools'
|
|
41
41
|
import { applyResolvedMetadata, isToolResultError } from './tracing-helpers'
|
|
@@ -109,6 +109,14 @@ export async function captureToolCall(params: TraceToolCallParams): Promise<unkn
|
|
|
109
109
|
parameterOwnership,
|
|
110
110
|
resolvedEventType === MCPAnalyticsEventType.mcpMissingCapability
|
|
111
111
|
)
|
|
112
|
+
// Reading the argument and removing it are separate questions: deleting one the
|
|
113
|
+
// application declared costs the customer their call, so the strip below still
|
|
114
|
+
// requires positive ownership, while reading it when ownership is unresolved
|
|
115
|
+
// costs at worst a mislabelled property. ADR-0011.
|
|
116
|
+
const canCaptureContextIntent =
|
|
117
|
+
resolvedEventType !== MCPAnalyticsEventType.mcpMissingCapability &&
|
|
118
|
+
isContextEnabled(data.options.context) &&
|
|
119
|
+
(ownership.context || !ownership.contextOwnershipKnown)
|
|
112
120
|
const conversation = resolveConversationId(ownership.conversationId, request.params?.arguments)
|
|
113
121
|
const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership)
|
|
114
122
|
|
|
@@ -124,7 +132,8 @@ export async function captureToolCall(params: TraceToolCallParams): Promise<unkn
|
|
|
124
132
|
startTime,
|
|
125
133
|
conversation,
|
|
126
134
|
ownership,
|
|
127
|
-
resolvedEventType
|
|
135
|
+
resolvedEventType,
|
|
136
|
+
canCaptureContextIntent
|
|
128
137
|
)
|
|
129
138
|
if (preparedEvent && explicitContextIntent) {
|
|
130
139
|
setExplicitContextIntent(preparedEvent.event, explicitContextIntent)
|
|
@@ -139,7 +148,9 @@ export async function captureToolCall(params: TraceToolCallParams): Promise<unkn
|
|
|
139
148
|
}
|
|
140
149
|
|
|
141
150
|
const finalResult = applyConversationInstructions(preparedEvent?.event ?? null, result, conversation, ownership)
|
|
142
|
-
|
|
151
|
+
// `result`, not `finalResult`: the error is read from what the tool produced,
|
|
152
|
+
// before the conversation handle was written into it. See below.
|
|
153
|
+
publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError, result)
|
|
143
154
|
return finalResult
|
|
144
155
|
}
|
|
145
156
|
|
|
@@ -148,15 +159,24 @@ interface PreparedToolEvent {
|
|
|
148
159
|
requestAttribution: SessionInfo
|
|
149
160
|
}
|
|
150
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Ownership for one request, plus whether it could be resolved at all — "no
|
|
164
|
+
* answer" must not read the same as "the application owns it".
|
|
165
|
+
*/
|
|
166
|
+
interface ActiveAnalyticsParameterOwnership extends AnalyticsParameterOwnership {
|
|
167
|
+
contextOwnershipKnown: boolean
|
|
168
|
+
}
|
|
169
|
+
|
|
151
170
|
function getActiveAnalyticsParameterOwnership(
|
|
152
171
|
data: MCPAnalyticsData,
|
|
153
172
|
toolName: string | undefined,
|
|
154
173
|
override: AnalyticsParameterOwnership | undefined,
|
|
155
174
|
isMissingCapabilityTool: boolean
|
|
156
|
-
):
|
|
175
|
+
): ActiveAnalyticsParameterOwnership {
|
|
157
176
|
const listed = toolName ? data.toolAnalyticsParameterOwnership.get(toolName) : undefined
|
|
158
177
|
const ownership = override ?? listed
|
|
159
178
|
return {
|
|
179
|
+
contextOwnershipKnown: ownership !== undefined,
|
|
160
180
|
context: !isMissingCapabilityTool && isContextEnabled(data.options.context) && ownership?.context === true,
|
|
161
181
|
conversationId: data.options.enableConversationId === true && ownership?.conversationId === true,
|
|
162
182
|
// Deliberately read off `listed`, never the override: only the advertised
|
|
@@ -197,7 +217,8 @@ async function prepareToolCallEvent(
|
|
|
197
217
|
startTime: Date,
|
|
198
218
|
conversation: ConversationIdResolution,
|
|
199
219
|
ownership: AnalyticsParameterOwnership,
|
|
200
|
-
eventType: MCPAnalyticsEventType
|
|
220
|
+
eventType: MCPAnalyticsEventType,
|
|
221
|
+
canCaptureContextIntent: boolean
|
|
201
222
|
): Promise<PreparedToolEvent | null> {
|
|
202
223
|
try {
|
|
203
224
|
const sessionId = getSessionId(server, extra, conversation.conversationId)
|
|
@@ -235,7 +256,7 @@ async function prepareToolCallEvent(
|
|
|
235
256
|
stampTransportIdentity(event, extra)
|
|
236
257
|
|
|
237
258
|
await applyResolvedMetadata(event, data, request, extra)
|
|
238
|
-
setEventIntent(event, await resolveToolCallIntent(data, request,
|
|
259
|
+
setEventIntent(event, await resolveToolCallIntent(data, request, canCaptureContextIntent, extra))
|
|
239
260
|
return { event, requestAttribution }
|
|
240
261
|
} catch (error) {
|
|
241
262
|
data.logger(
|
|
@@ -288,13 +309,26 @@ function applyConversationInstructions(
|
|
|
288
309
|
return updated
|
|
289
310
|
}
|
|
290
311
|
|
|
312
|
+
/**
|
|
313
|
+
* @param result - what the caller receives, conversation handle included.
|
|
314
|
+
* @param resultBeforeInstructions - the same result as the tool produced it.
|
|
315
|
+
*
|
|
316
|
+
* The two differ once `enableConversationId` mints a handle, and the difference
|
|
317
|
+
* matters for errors. A tool that fails returns its message in `content`, and on
|
|
318
|
+
* MCP SDK v2 that flattened `isError` result is the only description of the
|
|
319
|
+
* failure we get — the throw never reaches our callback wrapper. Reading the
|
|
320
|
+
* error off the *delivered* result would therefore append the prompt-back to it,
|
|
321
|
+
* putting a fresh uuid inside `$mcp_error_message` on every failed call and
|
|
322
|
+
* splitting one recurring failure into as many groups as there were calls.
|
|
323
|
+
*/
|
|
291
324
|
function publishSuccessfulToolEvent(
|
|
292
325
|
server: MCPServerLike,
|
|
293
326
|
preparedEvent: PreparedToolEvent | null,
|
|
294
327
|
result: unknown,
|
|
295
328
|
startTime: Date,
|
|
296
329
|
logger: LoggerFn,
|
|
297
|
-
takeCapturedError?: () => unknown
|
|
330
|
+
takeCapturedError?: () => unknown,
|
|
331
|
+
resultBeforeInstructions?: unknown
|
|
298
332
|
): void {
|
|
299
333
|
if (!preparedEvent) {
|
|
300
334
|
return
|
|
@@ -304,7 +338,7 @@ function publishSuccessfulToolEvent(
|
|
|
304
338
|
if (isToolResultError(result)) {
|
|
305
339
|
event.isError = true
|
|
306
340
|
const capturedError = takeCapturedError?.()
|
|
307
|
-
event.error = captureException(capturedError ?? result)
|
|
341
|
+
event.error = captureException(capturedError ?? resultBeforeInstructions ?? result)
|
|
308
342
|
} else {
|
|
309
343
|
event.isError = false
|
|
310
344
|
}
|
|
@@ -496,27 +530,39 @@ export async function handleListToolsRequest(
|
|
|
496
530
|
request: MCPRequestLike,
|
|
497
531
|
extra: CompatibleRequestHandlerExtra | undefined,
|
|
498
532
|
logger: LoggerFn
|
|
499
|
-
): Promise<
|
|
533
|
+
): Promise<CompatibleToolsListLike> {
|
|
500
534
|
const data = getServerTrackingData(server)
|
|
501
535
|
const startTime = new Date()
|
|
502
536
|
const sessionId = getSessionId(server, extra)
|
|
503
|
-
// Snapshot before metadata resolution or the list handler can yield
|
|
504
|
-
// concurrent request using the same instrumented server.
|
|
505
|
-
const
|
|
537
|
+
// Snapshot before identify, metadata resolution, or the list handler can yield
|
|
538
|
+
// to a concurrent request using the same instrumented server.
|
|
539
|
+
const sessionInfo = getSessionInfo(server, data, sessionId)
|
|
506
540
|
const event: McpEvent = {
|
|
507
541
|
sessionId,
|
|
508
542
|
parameters: buildCapturedMcpParameters(request),
|
|
509
543
|
eventType: MCPAnalyticsEventType.mcpToolsList,
|
|
510
544
|
timestamp: startTime,
|
|
511
545
|
}
|
|
546
|
+
// Stamp before the identify await below. The last link of the identity chain is
|
|
547
|
+
// the server's own `getClientVersion()`, which a concurrent `initialize` can
|
|
548
|
+
// replace while a slow identify callback is in flight — and `captureEvent`
|
|
549
|
+
// prefers a stamped value over the `sessionInfo` snapshot, so a late stamp
|
|
550
|
+
// would win with the wrong client's name.
|
|
512
551
|
stampClientIdentity(event, request, extra, server)
|
|
513
552
|
stampTransportIdentity(event, extra)
|
|
514
553
|
|
|
554
|
+
// `getSessionInfo` only surfaces an identity some earlier request already
|
|
555
|
+
// cached on this instance. On a per-request instance that cache is always
|
|
556
|
+
// empty, so without resolving `identify` here too, `tools/list` would be the
|
|
557
|
+
// one request path that never attributes to a person.
|
|
558
|
+
const identity = data ? await handleIdentify(server, data, sessionId, request, sessionInfo, extra) : undefined
|
|
559
|
+
const requestAttribution = withIdentity(sessionInfo, identity)
|
|
560
|
+
|
|
515
561
|
if (data) {
|
|
516
562
|
await applyResolvedMetadata(event, data, request, extra)
|
|
517
563
|
}
|
|
518
564
|
|
|
519
|
-
const
|
|
565
|
+
const response = await getTracedToolsList(
|
|
520
566
|
server,
|
|
521
567
|
originalListToolsHandler,
|
|
522
568
|
request,
|
|
@@ -525,12 +571,13 @@ export async function handleListToolsRequest(
|
|
|
525
571
|
logger,
|
|
526
572
|
requestAttribution
|
|
527
573
|
)
|
|
574
|
+
const tools = response.tools
|
|
528
575
|
|
|
529
576
|
if (!data) {
|
|
530
577
|
logger(
|
|
531
578
|
'Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.'
|
|
532
579
|
)
|
|
533
|
-
return
|
|
580
|
+
return response
|
|
534
581
|
}
|
|
535
582
|
|
|
536
583
|
if (tools.length === 0) {
|
|
@@ -541,15 +588,15 @@ export async function handleListToolsRequest(
|
|
|
541
588
|
event.isError = true
|
|
542
589
|
event.duration = Date.now() - startTime.getTime()
|
|
543
590
|
captureEvent(server, event, data.logger, requestAttribution)
|
|
544
|
-
return
|
|
591
|
+
return response
|
|
545
592
|
}
|
|
546
593
|
|
|
547
|
-
event.response =
|
|
594
|
+
event.response = response
|
|
548
595
|
event.listedToolNames = collectListedToolNames(tools)
|
|
549
596
|
event.isError = false
|
|
550
597
|
event.duration = Date.now() - startTime.getTime()
|
|
551
598
|
captureEvent(server, event, data.logger, requestAttribution)
|
|
552
|
-
return
|
|
599
|
+
return response
|
|
553
600
|
}
|
|
554
601
|
|
|
555
602
|
function cacheToolAnalyticsParameterOwnership(
|
|
@@ -580,7 +627,7 @@ async function getTracedToolsList(
|
|
|
580
627
|
event: McpEvent,
|
|
581
628
|
logger: LoggerFn,
|
|
582
629
|
requestAttribution: SessionInfo
|
|
583
|
-
): Promise<CompatibleToolsListLike
|
|
630
|
+
): Promise<CompatibleToolsListLike> {
|
|
584
631
|
try {
|
|
585
632
|
const data = getServerTrackingData(server)
|
|
586
633
|
const originalResponse = (await originalListToolsHandler(request, extra)) as CompatibleToolsListLike
|
|
@@ -622,7 +669,8 @@ async function getTracedToolsList(
|
|
|
622
669
|
cacheToolCategories(data.toolCategories, tools)
|
|
623
670
|
}
|
|
624
671
|
|
|
625
|
-
|
|
672
|
+
// Spread, never enumerate — fields later revisions add survive by default.
|
|
673
|
+
return { ...originalResponse, tools }
|
|
626
674
|
} catch (error) {
|
|
627
675
|
logger(
|
|
628
676
|
`Warning: Original list tools handler failed, this suggests an error PostHog MCP analytics did not cause - ${error}`
|
|
@@ -699,6 +747,14 @@ function mintStatelessSessionOnInitialize(
|
|
|
699
747
|
if (!headers) {
|
|
700
748
|
return undefined // not an HTTP transport (stdio/in-memory) — nothing to mint into
|
|
701
749
|
}
|
|
750
|
+
// 2026-07-28 removed sessions from the protocol: a server MUST NOT mint or
|
|
751
|
+
// echo `Mcp-Session-Id` under it. Today that era never reaches here anyway,
|
|
752
|
+
// because it has no `initialize` — but relying on that is relying on an SDK
|
|
753
|
+
// routing detail to keep us spec-compliant, so the era is asked directly.
|
|
754
|
+
// Per request, never per server: the same v2 server serves both.
|
|
755
|
+
if (isModernEraRequest(request, extra, server)) {
|
|
756
|
+
return undefined
|
|
757
|
+
}
|
|
702
758
|
if (readMcpSessionHeader(headers)) {
|
|
703
759
|
return undefined // client already replays a session id (ours or the transport's)
|
|
704
760
|
}
|
package/src/extensions/intent.ts
CHANGED
|
@@ -52,11 +52,11 @@ async function runIntentFallback(
|
|
|
52
52
|
export async function resolveToolCallIntent(
|
|
53
53
|
data: MCPAnalyticsData,
|
|
54
54
|
request: MCPRequestLike,
|
|
55
|
-
|
|
55
|
+
canCaptureContext: boolean,
|
|
56
56
|
extra?: CompatibleRequestHandlerExtra
|
|
57
57
|
): Promise<ResolvedIntent | null> {
|
|
58
58
|
const contextArgument = getContextArgument(request)
|
|
59
|
-
if (
|
|
59
|
+
if (canCaptureContext && isContextEnabled(data.options.context) && contextArgument) {
|
|
60
60
|
return { intent: contextArgument, source: 'context_parameter' }
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -7,17 +7,73 @@ import { version } from '../version'
|
|
|
7
7
|
import type {
|
|
8
8
|
CompatibleRequestHandlerExtra,
|
|
9
9
|
MCPAnalyticsData,
|
|
10
|
+
MCPRequestLike,
|
|
10
11
|
MCPServerLike,
|
|
11
12
|
ServerClientInfoLike,
|
|
12
13
|
SessionInfo,
|
|
13
14
|
} from '../types'
|
|
14
15
|
import { INACTIVITY_TIMEOUT_IN_MINUTES } from './constants'
|
|
15
16
|
import { deterministicPrefixedId, newPrefixedId } from './ids'
|
|
17
|
+
import { resolveClientIdentity } from './client-identity'
|
|
16
18
|
import { getServerTrackingData, setServerTrackingData } from './internal'
|
|
17
19
|
import { getRequestHeaders } from './request-headers'
|
|
18
20
|
import { decodeSessionId, readMcpSessionHeader } from './session-token'
|
|
19
21
|
import type { SessionTokenPayload } from './session-token'
|
|
20
22
|
|
|
23
|
+
/**
|
|
24
|
+
* The revision that removed protocol-level sessions. A request declaring this or
|
|
25
|
+
* anything later must not be answered with an `Mcp-Session-Id`.
|
|
26
|
+
*
|
|
27
|
+
* Compared as a string, which is safe and stable because MCP revisions are
|
|
28
|
+
* ISO dates: any future revision sorts above this one and is treated as modern,
|
|
29
|
+
* which is the right default — sessions were removed, not re-added.
|
|
30
|
+
*/
|
|
31
|
+
export const MODERN_PROTOCOL_REVISION = '2026-07-28'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Revisions are ISO dates, and only a date-shaped token may be compared as one.
|
|
35
|
+
* Without this the comparison is lexicographic over arbitrary input, so any
|
|
36
|
+
* string starting above `'2'` — every junk value beginning with a letter —
|
|
37
|
+
* sorts as modern and silently loses the session header. Unknown must mean
|
|
38
|
+
* legacy, so anything that is not a date is not compared at all.
|
|
39
|
+
*/
|
|
40
|
+
const REVISION_SHAPE = /^\d{4}-\d{2}-\d{2}$/
|
|
41
|
+
|
|
42
|
+
/** The spec's rolling draft sits ahead of every dated revision, sessions included. */
|
|
43
|
+
const DRAFT_REVISION = 'draft'
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Whether *this request* is governed by 2026-07-28 or later.
|
|
47
|
+
*
|
|
48
|
+
* Era is a property of the request, never of the installed SDK: one v2 server
|
|
49
|
+
* serves both, request by request, and v2's exported `LATEST_PROTOCOL_VERSION`
|
|
50
|
+
* still reads `2025-11-25`. So the version is resolved from the request itself —
|
|
51
|
+
* an `initialize` body declares the version it is asking for, and every other
|
|
52
|
+
* request carries it in the envelope, `_meta`, or the `MCP-Protocol-Version`
|
|
53
|
+
* header — and only then compared.
|
|
54
|
+
*
|
|
55
|
+
* Unknown means legacy. A request that declares nothing is a v1 client on a
|
|
56
|
+
* transport that has always had a session header, and taking it away from them
|
|
57
|
+
* would be the regression.
|
|
58
|
+
*/
|
|
59
|
+
export function isModernEraRequest(
|
|
60
|
+
request: MCPRequestLike,
|
|
61
|
+
extra?: CompatibleRequestHandlerExtra,
|
|
62
|
+
server?: MCPServerLike
|
|
63
|
+
): boolean {
|
|
64
|
+
const requested = request.params?.protocolVersion
|
|
65
|
+
const version =
|
|
66
|
+
(typeof requested === 'string' && requested.length > 0 ? requested : undefined) ??
|
|
67
|
+
resolveClientIdentity({ request, extra, server })?.protocolVersion
|
|
68
|
+
if (!version) {
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
if (version === DRAFT_REVISION) {
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
return REVISION_SHAPE.test(version) && version >= MODERN_PROTOCOL_REVISION
|
|
75
|
+
}
|
|
76
|
+
|
|
21
77
|
export function newSessionId(): string {
|
|
22
78
|
return newPrefixedId('ses')
|
|
23
79
|
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '0.11.
|
|
1
|
+
export const version = '0.11.5'
|