@posthog/mcp 0.11.3 → 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/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 +3 -3
- 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
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/mcp",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.4",
|
|
4
4
|
"bugs": {
|
|
5
5
|
"url": "https://github.com/PostHog/posthog-js/issues"
|
|
6
6
|
},
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"@types/node": "^20.0.0",
|
|
41
41
|
"jest": "29.7.0",
|
|
42
42
|
"zod": "^3.25.0",
|
|
43
|
-
"
|
|
44
|
-
"posthog-
|
|
43
|
+
"posthog-node": "^5.48.1",
|
|
44
|
+
"@posthog-tooling/tsconfig-base": "1.1.1"
|
|
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'
|
|
@@ -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'
|