@posthog/mcp 0.11.2 → 0.11.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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
@@ -11,7 +11,11 @@ import type { CompatibleRequestHandlerExtra, McpEvent, MCPRequestLike, MCPServer
11
11
  * the time a handler runs they are here and `params._meta` is empty.
12
12
  * 2. `request.params._meta` — where 2026-07-28 puts them on the wire, and
13
13
  * where they still are on any server that does not lift them.
14
- * 3. the server's own accessors — `getClientVersion()` from a legacy
14
+ * 3. the `MCP-Protocol-Version` request header, which 2025-11-25 requires a
15
+ * client to send on every request after `initialize` — the only per-request
16
+ * carrier a legacy-era request has, and therefore the only one that
17
+ * survives a per-request server instance.
18
+ * 4. the server's own accessors — `getClientVersion()` from a legacy
15
19
  * `initialize` handshake, `getNegotiatedProtocolVersion()` on v2.
16
20
  *
17
21
  * The 2026-07-28 revision removed the `initialize` handshake and the
@@ -22,6 +26,8 @@ import type { CompatibleRequestHandlerExtra, McpEvent, MCPRequestLike, MCPServer
22
26
  */
23
27
  export declare const META_CLIENT_INFO_KEY = "io.modelcontextprotocol/clientInfo";
24
28
  export declare const META_PROTOCOL_VERSION_KEY = "io.modelcontextprotocol/protocolVersion";
29
+ /** Required on every post-`initialize` request by 2025-11-25 (and sent by modern clients too). */
30
+ export declare const PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
25
31
  export interface MetaClientInfo {
26
32
  clientName?: string;
27
33
  clientVersion?: string;
@@ -52,6 +58,16 @@ export declare function readMetaClientInfo(request: MCPRequestLike): MetaClientI
52
58
  * everything.
53
59
  */
54
60
  export declare function readEnvelopeClientInfo(extra: CompatibleRequestHandlerExtra | undefined): MetaClientInfo | undefined;
61
+ /**
62
+ * Reads the protocol version off the request headers.
63
+ *
64
+ * This is what closes the gap on **2025-11-25 traffic served by a per-request
65
+ * server**: that era carries identity at the handshake, and the instance
66
+ * handling a later `tools/call` never saw it. The header rides every request, so
67
+ * it survives where the handshake does not. Carries no client name — that still
68
+ * depends on the replayed session token.
69
+ */
70
+ export declare function readHeaderProtocolVersion(extra: CompatibleRequestHandlerExtra | undefined): MetaClientInfo | undefined;
55
71
  /**
56
72
  * Asks the server itself. `getClientVersion()` answers on any server that
57
73
  * handled an `initialize` (and v2 hosts such as `createMcpHandler` backfill it
@@ -1 +1 @@
1
- {"version":3,"file":"client-identity.d.ts","sourceRoot":"","sources":["../../src/extensions/client-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAGtG;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,oBAAoB,uCAAuC,CAAA;AACxE,eAAO,MAAM,yBAAyB,4CAA4C,CAAA;AAElF,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,8DAA8D;AAC9D,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,cAAc,CAAA;IACvB,KAAK,CAAC,EAAE,6BAA6B,CAAA;IACrC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAuCD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,GAAG,SAAS,CAEtF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,6BAA6B,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS,CAEnH;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAsBpF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,qBAAqB,GAAG,cAAc,GAAG,SAAS,CAYnH;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,cAAc,EACvB,KAAK,CAAC,EAAE,6BAA6B,EACrC,MAAM,CAAC,EAAE,aAAa,GACrB,IAAI,CAcN"}
1
+ {"version":3,"file":"client-identity.d.ts","sourceRoot":"","sources":["../../src/extensions/client-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAKtG;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,oBAAoB,uCAAuC,CAAA;AACxE,eAAO,MAAM,yBAAyB,4CAA4C,CAAA;AAClF,kGAAkG;AAClG,eAAO,MAAM,uBAAuB,yBAAyB,CAAA;AAI7D,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,8DAA8D;AAC9D,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,cAAc,CAAA;IACvB,KAAK,CAAC,EAAE,6BAA6B,CAAA;IACrC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAuCD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,GAAG,SAAS,CAEtF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,6BAA6B,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS,CAEnH;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,6BAA6B,GAAG,SAAS,GAC/C,cAAc,GAAG,SAAS,CAS5B;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAsBpF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,qBAAqB,GAAG,cAAc,GAAG,SAAS,CAiBnH;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,cAAc,EACvB,KAAK,CAAC,EAAE,6BAA6B,EACrC,MAAM,CAAC,EAAE,aAAa,GACrB,IAAI,CAcN"}
@@ -24,17 +24,23 @@ var __webpack_require__ = {};
24
24
  var __webpack_exports__ = {};
25
25
  __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
- resolveClientIdentity: ()=>resolveClientIdentity,
28
27
  readMetaClientInfo: ()=>readMetaClientInfo,
29
- readServerClientIdentity: ()=>readServerClientIdentity,
30
- readEnvelopeClientInfo: ()=>readEnvelopeClientInfo,
28
+ readHeaderProtocolVersion: ()=>readHeaderProtocolVersion,
31
29
  META_PROTOCOL_VERSION_KEY: ()=>META_PROTOCOL_VERSION_KEY,
30
+ PROTOCOL_VERSION_HEADER: ()=>PROTOCOL_VERSION_HEADER,
32
31
  stampClientIdentity: ()=>stampClientIdentity,
32
+ resolveClientIdentity: ()=>resolveClientIdentity,
33
+ readServerClientIdentity: ()=>readServerClientIdentity,
34
+ readEnvelopeClientInfo: ()=>readEnvelopeClientInfo,
33
35
  META_CLIENT_INFO_KEY: ()=>META_CLIENT_INFO_KEY
34
36
  });
35
37
  const external_detect_js_namespaceObject = require("./detect.js");
38
+ const external_headers_js_namespaceObject = require("./headers.js");
39
+ const external_request_headers_js_namespaceObject = require("./request-headers.js");
36
40
  const META_CLIENT_INFO_KEY = 'io.modelcontextprotocol/clientInfo';
37
41
  const META_PROTOCOL_VERSION_KEY = 'io.modelcontextprotocol/protocolVersion';
42
+ const PROTOCOL_VERSION_HEADER = 'mcp-protocol-version';
43
+ const MAX_PROTOCOL_VERSION_LENGTH = 64;
38
44
  function nonEmptyString(value) {
39
45
  return 'string' == typeof value && value.length > 0 ? value : void 0;
40
46
  }
@@ -64,6 +70,13 @@ function readMetaClientInfo(request) {
64
70
  function readEnvelopeClientInfo(extra) {
65
71
  return readReservedKeys(extra?.mcpReq?.envelope);
66
72
  }
73
+ function readHeaderProtocolVersion(extra) {
74
+ const protocolVersion = (0, external_headers_js_namespaceObject.readHeaderValue)((0, external_request_headers_js_namespaceObject.getRequestHeaders)(extra), PROTOCOL_VERSION_HEADER);
75
+ if (!protocolVersion || protocolVersion.length > MAX_PROTOCOL_VERSION_LENGTH) return;
76
+ return {
77
+ protocolVersion
78
+ };
79
+ }
67
80
  function readServerClientIdentity(server) {
68
81
  if (!server) return;
69
82
  const result = {};
@@ -81,6 +94,7 @@ function resolveClientIdentity({ request, extra, server }) {
81
94
  const chain = [
82
95
  readEnvelopeClientInfo(extra),
83
96
  readMetaClientInfo(request),
97
+ readHeaderProtocolVersion(extra),
84
98
  readServerClientIdentity(server)
85
99
  ];
86
100
  const result = {};
@@ -104,7 +118,9 @@ function stampClientIdentity(event, request, extra, server) {
104
118
  }
105
119
  exports.META_CLIENT_INFO_KEY = __webpack_exports__.META_CLIENT_INFO_KEY;
106
120
  exports.META_PROTOCOL_VERSION_KEY = __webpack_exports__.META_PROTOCOL_VERSION_KEY;
121
+ exports.PROTOCOL_VERSION_HEADER = __webpack_exports__.PROTOCOL_VERSION_HEADER;
107
122
  exports.readEnvelopeClientInfo = __webpack_exports__.readEnvelopeClientInfo;
123
+ exports.readHeaderProtocolVersion = __webpack_exports__.readHeaderProtocolVersion;
108
124
  exports.readMetaClientInfo = __webpack_exports__.readMetaClientInfo;
109
125
  exports.readServerClientIdentity = __webpack_exports__.readServerClientIdentity;
110
126
  exports.resolveClientIdentity = __webpack_exports__.resolveClientIdentity;
@@ -112,7 +128,9 @@ exports.stampClientIdentity = __webpack_exports__.stampClientIdentity;
112
128
  for(var __webpack_i__ in __webpack_exports__)if (-1 === [
113
129
  "META_CLIENT_INFO_KEY",
114
130
  "META_PROTOCOL_VERSION_KEY",
131
+ "PROTOCOL_VERSION_HEADER",
115
132
  "readEnvelopeClientInfo",
133
+ "readHeaderProtocolVersion",
116
134
  "readMetaClientInfo",
117
135
  "readServerClientIdentity",
118
136
  "resolveClientIdentity",
@@ -1,6 +1,10 @@
1
1
  import { hasClientVersionAccessor, hasNegotiatedProtocolVersionAccessor } from "./detect.mjs";
2
+ import { readHeaderValue } from "./headers.mjs";
3
+ import { getRequestHeaders } from "./request-headers.mjs";
2
4
  const META_CLIENT_INFO_KEY = 'io.modelcontextprotocol/clientInfo';
3
5
  const META_PROTOCOL_VERSION_KEY = 'io.modelcontextprotocol/protocolVersion';
6
+ const PROTOCOL_VERSION_HEADER = 'mcp-protocol-version';
7
+ const MAX_PROTOCOL_VERSION_LENGTH = 64;
4
8
  function nonEmptyString(value) {
5
9
  return 'string' == typeof value && value.length > 0 ? value : void 0;
6
10
  }
@@ -30,6 +34,13 @@ function readMetaClientInfo(request) {
30
34
  function readEnvelopeClientInfo(extra) {
31
35
  return readReservedKeys(extra?.mcpReq?.envelope);
32
36
  }
37
+ function readHeaderProtocolVersion(extra) {
38
+ const protocolVersion = readHeaderValue(getRequestHeaders(extra), PROTOCOL_VERSION_HEADER);
39
+ if (!protocolVersion || protocolVersion.length > MAX_PROTOCOL_VERSION_LENGTH) return;
40
+ return {
41
+ protocolVersion
42
+ };
43
+ }
33
44
  function readServerClientIdentity(server) {
34
45
  if (!server) return;
35
46
  const result = {};
@@ -47,6 +58,7 @@ function resolveClientIdentity({ request, extra, server }) {
47
58
  const chain = [
48
59
  readEnvelopeClientInfo(extra),
49
60
  readMetaClientInfo(request),
61
+ readHeaderProtocolVersion(extra),
50
62
  readServerClientIdentity(server)
51
63
  ];
52
64
  const result = {};
@@ -68,4 +80,4 @@ function stampClientIdentity(event, request, extra, server) {
68
80
  if (info.clientVersion) event.clientVersion = info.clientVersion;
69
81
  if (info.protocolVersion) event.protocolVersion = info.protocolVersion;
70
82
  }
71
- export { META_CLIENT_INFO_KEY, META_PROTOCOL_VERSION_KEY, readEnvelopeClientInfo, readMetaClientInfo, readServerClientIdentity, resolveClientIdentity, stampClientIdentity };
83
+ export { META_CLIENT_INFO_KEY, META_PROTOCOL_VERSION_KEY, PROTOCOL_VERSION_HEADER, readEnvelopeClientInfo, readHeaderProtocolVersion, readMetaClientInfo, readServerClientIdentity, resolveClientIdentity, stampClientIdentity };
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"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"}
@@ -69,7 +69,7 @@ async function captureToolCall(params) {
69
69
  throw error;
70
70
  }
71
71
  const finalResult = applyConversationInstructions(preparedEvent?.event ?? null, result, conversation, ownership);
72
- publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError);
72
+ publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError, result);
73
73
  return finalResult;
74
74
  }
75
75
  function getActiveAnalyticsParameterOwnership(data, toolName, override, isMissingCapabilityTool) {
@@ -140,14 +140,14 @@ function applyConversationInstructions(event, result, conversation, ownership) {
140
140
  if (!delivered && conversation.minted && event) event.conversationId = void 0;
141
141
  return updated;
142
142
  }
143
- function publishSuccessfulToolEvent(server, preparedEvent, result, startTime, logger, takeCapturedError) {
143
+ function publishSuccessfulToolEvent(server, preparedEvent, result, startTime, logger, takeCapturedError, resultBeforeInstructions) {
144
144
  if (!preparedEvent) return;
145
145
  const { event, requestAttribution } = preparedEvent;
146
146
  try {
147
147
  if ((0, external_tracing_helpers_js_namespaceObject.isToolResultError)(result)) {
148
148
  event.isError = true;
149
149
  const capturedError = takeCapturedError?.();
150
- event.error = (0, external_exceptions_js_namespaceObject.captureException)(capturedError ?? result);
150
+ event.error = (0, external_exceptions_js_namespaceObject.captureException)(capturedError ?? resultBeforeInstructions ?? result);
151
151
  } else event.isError = false;
152
152
  event.response = result;
153
153
  event.duration = Date.now() - startTime.getTime();
@@ -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";
@@ -33,7 +33,7 @@ async function captureToolCall(params) {
33
33
  throw error;
34
34
  }
35
35
  const finalResult = applyConversationInstructions(preparedEvent?.event ?? null, result, conversation, ownership);
36
- publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError);
36
+ publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError, result);
37
37
  return finalResult;
38
38
  }
39
39
  function getActiveAnalyticsParameterOwnership(data, toolName, override, isMissingCapabilityTool) {
@@ -104,14 +104,14 @@ function applyConversationInstructions(event, result, conversation, ownership) {
104
104
  if (!delivered && conversation.minted && event) event.conversationId = void 0;
105
105
  return updated;
106
106
  }
107
- function publishSuccessfulToolEvent(server, preparedEvent, result, startTime, logger, takeCapturedError) {
107
+ function publishSuccessfulToolEvent(server, preparedEvent, result, startTime, logger, takeCapturedError, resultBeforeInstructions) {
108
108
  if (!preparedEvent) return;
109
109
  const { event, requestAttribution } = preparedEvent;
110
110
  try {
111
111
  if (isToolResultError(result)) {
112
112
  event.isError = true;
113
113
  const capturedError = takeCapturedError?.();
114
- event.error = captureException(capturedError ?? result);
114
+ event.error = captureException(capturedError ?? resultBeforeInstructions ?? result);
115
115
  } else event.isError = false;
116
116
  event.response = result;
117
117
  event.duration = Date.now() - startTime.getTime();
@@ -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;
@@ -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;AAQjB,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"}
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
- getSessionId: ()=>getSessionId,
29
+ newSessionId: ()=>newSessionId,
30
+ MODERN_PROTOCOL_REVISION: ()=>MODERN_PROTOCOL_REVISION,
29
31
  deriveSessionIdFromMCPSession: ()=>deriveSessionIdFromMCPSession,
30
- getSessionInfo: ()=>getSessionInfo,
31
- newSessionId: ()=>newSessionId
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: version,
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.2";
1
+ export declare const version = "0.11.4";
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.2';
29
+ const version = '0.11.4';
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.2';
1
+ const version = '0.11.4';
2
2
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/mcp",
3
- "version": "0.11.2",
3
+ "version": "0.11.4",
4
4
  "bugs": {
5
5
  "url": "https://github.com/PostHog/posthog-js/issues"
6
6
  },
@@ -1,5 +1,7 @@
1
1
  import type { CompatibleRequestHandlerExtra, McpEvent, MCPRequestLike, MCPServerLike } from '../types'
2
2
  import { hasClientVersionAccessor, hasNegotiatedProtocolVersionAccessor } from './detect'
3
+ import { readHeaderValue } from './headers'
4
+ import { getRequestHeaders } from './request-headers'
3
5
 
4
6
  /**
5
7
  * Client identity — who is calling, and which spec revision they speak.
@@ -13,7 +15,11 @@ import { hasClientVersionAccessor, hasNegotiatedProtocolVersionAccessor } from '
13
15
  * the time a handler runs they are here and `params._meta` is empty.
14
16
  * 2. `request.params._meta` — where 2026-07-28 puts them on the wire, and
15
17
  * where they still are on any server that does not lift them.
16
- * 3. the server's own accessors — `getClientVersion()` from a legacy
18
+ * 3. the `MCP-Protocol-Version` request header, which 2025-11-25 requires a
19
+ * client to send on every request after `initialize` — the only per-request
20
+ * carrier a legacy-era request has, and therefore the only one that
21
+ * survives a per-request server instance.
22
+ * 4. the server's own accessors — `getClientVersion()` from a legacy
17
23
  * `initialize` handshake, `getNegotiatedProtocolVersion()` on v2.
18
24
  *
19
25
  * The 2026-07-28 revision removed the `initialize` handshake and the
@@ -24,6 +30,10 @@ import { hasClientVersionAccessor, hasNegotiatedProtocolVersionAccessor } from '
24
30
  */
25
31
  export const META_CLIENT_INFO_KEY = 'io.modelcontextprotocol/clientInfo'
26
32
  export const META_PROTOCOL_VERSION_KEY = 'io.modelcontextprotocol/protocolVersion'
33
+ /** Required on every post-`initialize` request by 2025-11-25 (and sent by modern clients too). */
34
+ export const PROTOCOL_VERSION_HEADER = 'mcp-protocol-version'
35
+ /** A version string is a date-like token; anything longer is junk we should not record. */
36
+ const MAX_PROTOCOL_VERSION_LENGTH = 64
27
37
 
28
38
  export interface MetaClientInfo {
29
39
  clientName?: string
@@ -100,6 +110,28 @@ export function readEnvelopeClientInfo(extra: CompatibleRequestHandlerExtra | un
100
110
  return readReservedKeys((extra as { mcpReq?: { envelope?: unknown } } | undefined)?.mcpReq?.envelope)
101
111
  }
102
112
 
113
+ /**
114
+ * Reads the protocol version off the request headers.
115
+ *
116
+ * This is what closes the gap on **2025-11-25 traffic served by a per-request
117
+ * server**: that era carries identity at the handshake, and the instance
118
+ * handling a later `tools/call` never saw it. The header rides every request, so
119
+ * it survives where the handshake does not. Carries no client name — that still
120
+ * depends on the replayed session token.
121
+ */
122
+ export function readHeaderProtocolVersion(
123
+ extra: CompatibleRequestHandlerExtra | undefined
124
+ ): MetaClientInfo | undefined {
125
+ // `getRequestHeaders` answers *where* the headers are on either SDK major;
126
+ // `readHeaderValue` answers how to read one value out of the bag it returns —
127
+ // repeated values, mixed case, blank strings. Two questions, one place each.
128
+ const protocolVersion = readHeaderValue(getRequestHeaders(extra), PROTOCOL_VERSION_HEADER)
129
+ if (!protocolVersion || protocolVersion.length > MAX_PROTOCOL_VERSION_LENGTH) {
130
+ return undefined
131
+ }
132
+ return { protocolVersion }
133
+ }
134
+
103
135
  /**
104
136
  * Asks the server itself. `getClientVersion()` answers on any server that
105
137
  * handled an `initialize` (and v2 hosts such as `createMcpHandler` backfill it
@@ -148,7 +180,12 @@ export function readServerClientIdentity(server: unknown): MetaClientInfo | unde
148
180
  * keeps it a fallback: any request that identifies itself wins outright.
149
181
  */
150
182
  export function resolveClientIdentity({ request, extra, server }: ClientIdentitySources): MetaClientInfo | undefined {
151
- const chain = [readEnvelopeClientInfo(extra), readMetaClientInfo(request), readServerClientIdentity(server)]
183
+ const chain = [
184
+ readEnvelopeClientInfo(extra),
185
+ readMetaClientInfo(request),
186
+ readHeaderProtocolVersion(extra),
187
+ readServerClientIdentity(server),
188
+ ]
152
189
  const result: MetaClientInfo = {}
153
190
  for (const source of chain) {
154
191
  if (!source) {
@@ -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'
@@ -139,7 +139,9 @@ export async function captureToolCall(params: TraceToolCallParams): Promise<unkn
139
139
  }
140
140
 
141
141
  const finalResult = applyConversationInstructions(preparedEvent?.event ?? null, result, conversation, ownership)
142
- publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError)
142
+ // `result`, not `finalResult`: the error is read from what the tool produced,
143
+ // before the conversation handle was written into it. See below.
144
+ publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError, result)
143
145
  return finalResult
144
146
  }
145
147
 
@@ -288,13 +290,26 @@ function applyConversationInstructions(
288
290
  return updated
289
291
  }
290
292
 
293
+ /**
294
+ * @param result - what the caller receives, conversation handle included.
295
+ * @param resultBeforeInstructions - the same result as the tool produced it.
296
+ *
297
+ * The two differ once `enableConversationId` mints a handle, and the difference
298
+ * matters for errors. A tool that fails returns its message in `content`, and on
299
+ * MCP SDK v2 that flattened `isError` result is the only description of the
300
+ * failure we get — the throw never reaches our callback wrapper. Reading the
301
+ * error off the *delivered* result would therefore append the prompt-back to it,
302
+ * putting a fresh uuid inside `$mcp_error_message` on every failed call and
303
+ * splitting one recurring failure into as many groups as there were calls.
304
+ */
291
305
  function publishSuccessfulToolEvent(
292
306
  server: MCPServerLike,
293
307
  preparedEvent: PreparedToolEvent | null,
294
308
  result: unknown,
295
309
  startTime: Date,
296
310
  logger: LoggerFn,
297
- takeCapturedError?: () => unknown
311
+ takeCapturedError?: () => unknown,
312
+ resultBeforeInstructions?: unknown
298
313
  ): void {
299
314
  if (!preparedEvent) {
300
315
  return
@@ -304,7 +319,7 @@ function publishSuccessfulToolEvent(
304
319
  if (isToolResultError(result)) {
305
320
  event.isError = true
306
321
  const capturedError = takeCapturedError?.()
307
- event.error = captureException(capturedError ?? result)
322
+ event.error = captureException(capturedError ?? resultBeforeInstructions ?? result)
308
323
  } else {
309
324
  event.isError = false
310
325
  }
@@ -699,6 +714,14 @@ function mintStatelessSessionOnInitialize(
699
714
  if (!headers) {
700
715
  return undefined // not an HTTP transport (stdio/in-memory) — nothing to mint into
701
716
  }
717
+ // 2026-07-28 removed sessions from the protocol: a server MUST NOT mint or
718
+ // echo `Mcp-Session-Id` under it. Today that era never reaches here anyway,
719
+ // because it has no `initialize` — but relying on that is relying on an SDK
720
+ // routing detail to keep us spec-compliant, so the era is asked directly.
721
+ // Per request, never per server: the same v2 server serves both.
722
+ if (isModernEraRequest(request, extra, server)) {
723
+ return undefined
724
+ }
702
725
  if (readMcpSessionHeader(headers)) {
703
726
  return undefined // client already replays a session id (ours or the transport's)
704
727
  }
@@ -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.2'
1
+ export const version = '0.11.4'