@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 +21 -0
- package/dist/extensions/client-identity.d.ts +17 -1
- package/dist/extensions/client-identity.d.ts.map +1 -1
- package/dist/extensions/client-identity.js +21 -3
- package/dist/extensions/client-identity.mjs +13 -1
- package/dist/extensions/instrumentation.d.ts.map +1 -1
- package/dist/extensions/instrumentation.js +4 -3
- package/dist/extensions/instrumentation.mjs +5 -4
- 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 +1 -1
- package/src/extensions/client-identity.ts +39 -2
- package/src/extensions/instrumentation.ts +27 -4
- 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
|
|
@@ -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
|
|
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;
|
|
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
|
-
|
|
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":"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":"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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;
|
|
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.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.
|
|
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.
|
|
1
|
+
const version = '0.11.4';
|
|
2
2
|
export { version };
|
package/package.json
CHANGED
|
@@ -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
|
|
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 = [
|
|
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
|
-
|
|
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.
|
|
1
|
+
export const version = '0.11.4'
|