@posthog/mcp 0.10.6 → 0.10.7
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/dist/extensions/capture.d.ts +2 -2
- package/dist/extensions/capture.d.ts.map +1 -1
- package/dist/extensions/capture.js +2 -2
- package/dist/extensions/capture.mjs +2 -2
- package/dist/extensions/instrumentation.d.ts.map +1 -1
- package/dist/extensions/instrumentation.js +53 -28
- package/dist/extensions/instrumentation.mjs +55 -30
- package/dist/extensions/internal.d.ts +3 -2
- package/dist/extensions/internal.d.ts.map +1 -1
- package/dist/extensions/internal.js +25 -9
- package/dist/extensions/internal.mjs +19 -6
- package/dist/extensions/session.js +3 -1
- package/dist/extensions/session.mjs +3 -1
- 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/capture.ts +13 -6
- package/src/extensions/instrumentation.ts +76 -33
- package/src/extensions/internal.ts +21 -6
- package/src/extensions/session.ts +1 -1
- package/src/version.ts +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MCPServerLike, McpEvent } from '../types';
|
|
1
|
+
import type { MCPServerLike, McpEvent, SessionInfo } from '../types';
|
|
2
2
|
import type { LoggerFn } from './logger';
|
|
3
3
|
/**
|
|
4
4
|
* Materializes an `McpEvent` against the server's tracking data + session info,
|
|
@@ -10,5 +10,5 @@ import type { LoggerFn } from './logger';
|
|
|
10
10
|
* Auto-capture callers (tool calls, listings, identify) intentionally ignore the
|
|
11
11
|
* return value, keeping the tool path isolated from analytics latency/errors.
|
|
12
12
|
*/
|
|
13
|
-
export declare function captureEvent(server: MCPServerLike, eventInput: McpEvent, logger: LoggerFn): Promise<void> | undefined;
|
|
13
|
+
export declare function captureEvent(server: MCPServerLike, eventInput: McpEvent, logger: LoggerFn, requestAttribution?: SessionInfo): Promise<void> | undefined;
|
|
14
14
|
//# sourceMappingURL=capture.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/extensions/capture.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/extensions/capture.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAGpE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAGxC;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,aAAa,EACrB,UAAU,EAAE,QAAQ,EACpB,MAAM,EAAE,QAAQ,EAChB,kBAAkB,CAAC,EAAE,WAAW,GAC/B,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAoE3B"}
|
|
@@ -29,12 +29,12 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
29
29
|
const external_event_types_js_namespaceObject = require("./event-types.js");
|
|
30
30
|
const external_internal_js_namespaceObject = require("./internal.js");
|
|
31
31
|
const external_session_js_namespaceObject = require("./session.js");
|
|
32
|
-
function captureEvent(server, eventInput, logger) {
|
|
32
|
+
function captureEvent(server, eventInput, logger, requestAttribution) {
|
|
33
33
|
const data = (0, external_internal_js_namespaceObject.getServerTrackingData)(server);
|
|
34
34
|
if (!data) return void logger('Warning: Server tracking data not found. Event will not be published.');
|
|
35
35
|
const sink = data.sink;
|
|
36
36
|
if (!sink) return;
|
|
37
|
-
const sessionInfo = (0, external_session_js_namespaceObject.getSessionInfo)(server, data, eventInput.sessionId);
|
|
37
|
+
const sessionInfo = requestAttribution ?? (0, external_session_js_namespaceObject.getSessionInfo)(server, data, eventInput.sessionId);
|
|
38
38
|
const duration = eventInput.duration || (eventInput.timestamp ? Date.now() - eventInput.timestamp.getTime() : void 0);
|
|
39
39
|
const fullEvent = {
|
|
40
40
|
id: eventInput.id || '',
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { MCPAnalyticsEventType } from "./event-types.mjs";
|
|
2
2
|
import { getServerTrackingData } from "./internal.mjs";
|
|
3
3
|
import { getSessionInfo } from "./session.mjs";
|
|
4
|
-
function captureEvent(server, eventInput, logger) {
|
|
4
|
+
function captureEvent(server, eventInput, logger, requestAttribution) {
|
|
5
5
|
const data = getServerTrackingData(server);
|
|
6
6
|
if (!data) return void logger('Warning: Server tracking data not found. Event will not be published.');
|
|
7
7
|
const sink = data.sink;
|
|
8
8
|
if (!sink) return;
|
|
9
|
-
const sessionInfo = getSessionInfo(server, data, eventInput.sessionId);
|
|
9
|
+
const sessionInfo = requestAttribution ?? getSessionInfo(server, data, eventInput.sessionId);
|
|
10
10
|
const duration = eventInput.duration || (eventInput.timestamp ? Date.now() - eventInput.timestamp.getTime() : void 0);
|
|
11
11
|
const fullEvent = {
|
|
12
12
|
id: eventInput.id || '',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,KAAK,EACV,2BAA2B,EAC3B,6BAA6B,EAC7B,gBAAgB,EAChB,cAAc,EACd,aAAa,
|
|
1
|
+
{"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,KAAK,EACV,2BAA2B,EAC3B,6BAA6B,EAC7B,gBAAgB,EAChB,cAAc,EACd,aAAa,EAId,MAAM,UAAU,CAAA;AAYjB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAIrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAQxC;;;;;;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,CA0DnF;AAkKD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CACzB,MAAM,EAAE,aAAa,EACrB,eAAe,EAAE,iBAAiB,EAClC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,6BAA6B,GAAG,SAAS,KAC7C,OAAO,CAAC,OAAO,CAAC,CAAA;AAwBrB,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,IAAI,CA8BvG;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,6BAA6B,GAAG,SAAS,EAChD,MAAM,EAAE,QAAQ,GACf,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAoB9B;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,aAAa,EACrB,wBAAwB,EAAE,iBAAiB,EAC3C,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,6BAA6B,GAAG,SAAS,EAChD,MAAM,EAAE,QAAQ,GACf,OAAO,CAAC;IAAE,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAqD9C;AAkFD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,CASnH;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAGtE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,CAUjH;AA+GD;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,aAAa,EACrB,yBAAyB,EAAE,iBAAiB,EAC5C,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,6BAA6B,GAAG,SAAS,EAChD,MAAM,EAAE,QAAQ,GACf,OAAO,CAAC,OAAO,CAAC,CA2DlB"}
|
|
@@ -55,17 +55,17 @@ async function captureToolCall(params) {
|
|
|
55
55
|
const conversation = (0, external_conversation_id_js_namespaceObject.resolveConversationId)(ownership.conversationId, request.params?.arguments, request.params?.name, resolvedEventType === external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpMissingCapability ? (0, external_tools_js_namespaceObject.resolveMissingCapabilityToolName)(data.options) : '');
|
|
56
56
|
const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership);
|
|
57
57
|
const startTime = new Date();
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
58
|
+
const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType);
|
|
59
|
+
if (preparedEvent && explicitContextIntent) (0, external_intent_js_namespaceObject.setExplicitContextIntent)(preparedEvent.event, explicitContextIntent);
|
|
60
60
|
let result;
|
|
61
61
|
try {
|
|
62
62
|
result = await execute(downstreamRequest);
|
|
63
63
|
} catch (error) {
|
|
64
|
-
publishFailedToolEvent(server,
|
|
64
|
+
publishFailedToolEvent(server, preparedEvent, error, startTime, conversation, data.logger);
|
|
65
65
|
throw error;
|
|
66
66
|
}
|
|
67
|
-
const finalResult = applyConversationPromptBack(event, result, conversation);
|
|
68
|
-
publishSuccessfulToolEvent(server,
|
|
67
|
+
const finalResult = applyConversationPromptBack(preparedEvent?.event ?? null, result, conversation);
|
|
68
|
+
publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError);
|
|
69
69
|
return finalResult;
|
|
70
70
|
}
|
|
71
71
|
function getActiveAnalyticsParameterOwnership(data, toolName, override, isMissingCapabilityTool) {
|
|
@@ -90,7 +90,9 @@ function cloneRequestWithoutOwnedAnalyticsArguments(request, ownership) {
|
|
|
90
90
|
async function prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, eventType) {
|
|
91
91
|
try {
|
|
92
92
|
const sessionId = (0, external_session_js_namespaceObject.getSessionId)(server, extra);
|
|
93
|
-
|
|
93
|
+
const sessionInfo = (0, external_session_js_namespaceObject.getSessionInfo)(server, data, sessionId);
|
|
94
|
+
const identity = await (0, external_internal_js_namespaceObject.handleIdentify)(server, data, sessionId, request, sessionInfo, extra);
|
|
95
|
+
const requestAttribution = (0, external_internal_js_namespaceObject.withIdentity)(sessionInfo, identity);
|
|
94
96
|
const toolName = request.params?.name;
|
|
95
97
|
const event = {
|
|
96
98
|
sessionId,
|
|
@@ -105,7 +107,10 @@ async function prepareToolCallEvent(server, data, request, downstreamRequest, ex
|
|
|
105
107
|
(0, external_client_identity_js_namespaceObject.stampMetaClientInfo)(event, request);
|
|
106
108
|
await (0, external_tracing_helpers_js_namespaceObject.applyResolvedMetadata)(event, data, request, extra);
|
|
107
109
|
(0, external_intent_js_namespaceObject.setEventIntent)(event, await (0, external_intent_js_namespaceObject.resolveToolCallIntent)(data, request, ownership.context, extra));
|
|
108
|
-
return
|
|
110
|
+
return {
|
|
111
|
+
event,
|
|
112
|
+
requestAttribution
|
|
113
|
+
};
|
|
109
114
|
} catch (error) {
|
|
110
115
|
data.logger(`Warning: PostHog MCP analytics instrumentation failed for tool ${request.params?.name}, the tool will still run - ${error}`);
|
|
111
116
|
return null;
|
|
@@ -117,8 +122,9 @@ function applyConversationPromptBack(event, result, conversation) {
|
|
|
117
122
|
if (event) event.conversationId = void 0;
|
|
118
123
|
return result;
|
|
119
124
|
}
|
|
120
|
-
function publishSuccessfulToolEvent(server,
|
|
121
|
-
if (!
|
|
125
|
+
function publishSuccessfulToolEvent(server, preparedEvent, result, startTime, logger, takeCapturedError) {
|
|
126
|
+
if (!preparedEvent) return;
|
|
127
|
+
const { event, requestAttribution } = preparedEvent;
|
|
122
128
|
try {
|
|
123
129
|
if ((0, external_tracing_helpers_js_namespaceObject.isToolResultError)(result)) {
|
|
124
130
|
event.isError = true;
|
|
@@ -127,19 +133,20 @@ function publishSuccessfulToolEvent(server, event, result, startTime, logger, ta
|
|
|
127
133
|
} else event.isError = false;
|
|
128
134
|
event.response = result;
|
|
129
135
|
event.duration = Date.now() - startTime.getTime();
|
|
130
|
-
(0, external_capture_js_namespaceObject.captureEvent)(server, event, logger);
|
|
136
|
+
(0, external_capture_js_namespaceObject.captureEvent)(server, event, logger, requestAttribution);
|
|
131
137
|
} catch (error) {
|
|
132
138
|
logger(`Warning: PostHog MCP analytics failed to publish tool event - ${error}`);
|
|
133
139
|
}
|
|
134
140
|
}
|
|
135
|
-
function publishFailedToolEvent(server,
|
|
136
|
-
if (!
|
|
141
|
+
function publishFailedToolEvent(server, preparedEvent, error, startTime, conversation, logger) {
|
|
142
|
+
if (!preparedEvent) return;
|
|
143
|
+
const { event, requestAttribution } = preparedEvent;
|
|
137
144
|
try {
|
|
138
145
|
if (conversation.minted) event.conversationId = void 0;
|
|
139
146
|
event.isError = true;
|
|
140
147
|
event.error = (0, external_exceptions_js_namespaceObject.captureException)(error);
|
|
141
148
|
event.duration = Date.now() - startTime.getTime();
|
|
142
|
-
(0, external_capture_js_namespaceObject.captureEvent)(server, event, logger);
|
|
149
|
+
(0, external_capture_js_namespaceObject.captureEvent)(server, event, logger, requestAttribution);
|
|
143
150
|
} catch (publishError) {
|
|
144
151
|
logger(`Warning: PostHog MCP analytics failed to publish failed tool event - ${publishError}`);
|
|
145
152
|
}
|
|
@@ -194,15 +201,17 @@ async function isToolAdvertised(server, toolName, extra, logger) {
|
|
|
194
201
|
async function handleListToolsRequest(server, originalListToolsHandler, request, extra, logger) {
|
|
195
202
|
const data = (0, external_internal_js_namespaceObject.getServerTrackingData)(server);
|
|
196
203
|
const startTime = new Date();
|
|
204
|
+
const sessionId = (0, external_session_js_namespaceObject.getSessionId)(server, extra);
|
|
205
|
+
const requestAttribution = (0, external_session_js_namespaceObject.getSessionInfo)(server, data, sessionId);
|
|
197
206
|
const event = {
|
|
198
|
-
sessionId
|
|
207
|
+
sessionId,
|
|
199
208
|
parameters: (0, external_mcp_payloads_js_namespaceObject.buildCapturedMcpParameters)(request),
|
|
200
209
|
eventType: external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpToolsList,
|
|
201
210
|
timestamp: startTime
|
|
202
211
|
};
|
|
203
212
|
(0, external_client_identity_js_namespaceObject.stampMetaClientInfo)(event, request);
|
|
204
213
|
if (data) await (0, external_tracing_helpers_js_namespaceObject.applyResolvedMetadata)(event, data, request, extra);
|
|
205
|
-
const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger);
|
|
214
|
+
const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger, requestAttribution);
|
|
206
215
|
if (!data) {
|
|
207
216
|
logger('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
|
|
208
217
|
return {
|
|
@@ -214,7 +223,7 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
|
|
|
214
223
|
event.error = (0, external_exceptions_js_namespaceObject.captureException)('No tools were sent to MCP client.');
|
|
215
224
|
event.isError = true;
|
|
216
225
|
event.duration = Date.now() - startTime.getTime();
|
|
217
|
-
(0, external_capture_js_namespaceObject.captureEvent)(server, event, data.logger);
|
|
226
|
+
(0, external_capture_js_namespaceObject.captureEvent)(server, event, data.logger, requestAttribution);
|
|
218
227
|
return {
|
|
219
228
|
tools
|
|
220
229
|
};
|
|
@@ -225,7 +234,7 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
|
|
|
225
234
|
event.listedToolNames = collectListedToolNames(tools);
|
|
226
235
|
event.isError = false;
|
|
227
236
|
event.duration = Date.now() - startTime.getTime();
|
|
228
|
-
(0, external_capture_js_namespaceObject.captureEvent)(server, event, data.logger);
|
|
237
|
+
(0, external_capture_js_namespaceObject.captureEvent)(server, event, data.logger, requestAttribution);
|
|
229
238
|
return {
|
|
230
239
|
tools
|
|
231
240
|
};
|
|
@@ -238,7 +247,7 @@ function collectListedToolNames(tools) {
|
|
|
238
247
|
const names = tools.map((tool)=>tool?.name).filter((name)=>'string' == typeof name);
|
|
239
248
|
return names.length > 0 ? names : void 0;
|
|
240
249
|
}
|
|
241
|
-
async function getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger) {
|
|
250
|
+
async function getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger, requestAttribution) {
|
|
242
251
|
try {
|
|
243
252
|
const data = (0, external_internal_js_namespaceObject.getServerTrackingData)(server);
|
|
244
253
|
const originalResponse = await originalListToolsHandler(request, extra);
|
|
@@ -270,7 +279,7 @@ async function getTracedToolsList(server, originalListToolsHandler, request, ext
|
|
|
270
279
|
event.error = (0, external_exceptions_js_namespaceObject.captureException)(error);
|
|
271
280
|
event.isError = true;
|
|
272
281
|
event.duration = event.timestamp ? Date.now() - event.timestamp.getTime() : 0;
|
|
273
|
-
(0, external_capture_js_namespaceObject.captureEvent)(server, event, logger);
|
|
282
|
+
(0, external_capture_js_namespaceObject.captureEvent)(server, event, logger, requestAttribution);
|
|
274
283
|
throw error;
|
|
275
284
|
}
|
|
276
285
|
}
|
|
@@ -319,19 +328,19 @@ function mintStatelessSessionOnInitialize(server, data, request, extra) {
|
|
|
319
328
|
return;
|
|
320
329
|
}
|
|
321
330
|
}
|
|
322
|
-
function upgradeMintedTokenToNegotiated(server,
|
|
331
|
+
function upgradeMintedTokenToNegotiated(server, mintedSessionId, sessionInfo, negotiatedProtocolVersion, logger) {
|
|
323
332
|
try {
|
|
324
333
|
const transport = server.transport;
|
|
325
334
|
if (!transport) return;
|
|
326
335
|
const token = (0, external_session_token_js_namespaceObject.encodeSessionId)({
|
|
327
336
|
sessionId: mintedSessionId,
|
|
328
|
-
clientName:
|
|
329
|
-
clientVersion:
|
|
337
|
+
clientName: sessionInfo.clientName,
|
|
338
|
+
clientVersion: sessionInfo.clientVersion,
|
|
330
339
|
protocolVersion: negotiatedProtocolVersion
|
|
331
340
|
});
|
|
332
341
|
(0, external_session_token_js_namespaceObject.writeSessionIdToTransport)(transport, token);
|
|
333
342
|
} catch (error) {
|
|
334
|
-
|
|
343
|
+
logger(`Warning: PostHog MCP analytics failed to upgrade the stateless session token - ${error}`);
|
|
335
344
|
}
|
|
336
345
|
}
|
|
337
346
|
function readInitializeClientInfo(request) {
|
|
@@ -351,7 +360,15 @@ async function handleInitializeRequest(server, originalInitializeHandler, reques
|
|
|
351
360
|
}
|
|
352
361
|
const mintedSessionId = mintStatelessSessionOnInitialize(server, data, request, extra);
|
|
353
362
|
const sessionId = (0, external_session_js_namespaceObject.getSessionId)(server, extra);
|
|
354
|
-
|
|
363
|
+
const sessionInfo = (0, external_session_js_namespaceObject.getSessionInfo)(server, data, sessionId);
|
|
364
|
+
const initializeClientInfo = readInitializeClientInfo(request);
|
|
365
|
+
const requestSessionInfo = {
|
|
366
|
+
...sessionInfo,
|
|
367
|
+
clientName: initializeClientInfo?.name ?? sessionInfo.clientName,
|
|
368
|
+
clientVersion: initializeClientInfo?.version ?? sessionInfo.clientVersion
|
|
369
|
+
};
|
|
370
|
+
const identity = await (0, external_internal_js_namespaceObject.handleIdentify)(server, data, sessionId, request, requestSessionInfo, extra);
|
|
371
|
+
const requestAttribution = (0, external_internal_js_namespaceObject.withIdentity)(requestSessionInfo, identity);
|
|
355
372
|
const event = {
|
|
356
373
|
sessionId,
|
|
357
374
|
resourceName: request.params?.name || 'Unknown Tool Name',
|
|
@@ -365,10 +382,18 @@ async function handleInitializeRequest(server, originalInitializeHandler, reques
|
|
|
365
382
|
event.response = result;
|
|
366
383
|
const negotiatedProtocolVersion = readProtocolVersion(result, request);
|
|
367
384
|
event.protocolVersion = negotiatedProtocolVersion;
|
|
368
|
-
data.
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
385
|
+
if (data.sessionId === sessionId) {
|
|
386
|
+
data.sessionInfo = {
|
|
387
|
+
...data.sessionInfo,
|
|
388
|
+
protocolVersion: negotiatedProtocolVersion
|
|
389
|
+
};
|
|
390
|
+
(0, external_internal_js_namespaceObject.setServerTrackingData)(server, data);
|
|
391
|
+
}
|
|
392
|
+
if (mintedSessionId) upgradeMintedTokenToNegotiated(server, mintedSessionId, requestSessionInfo, negotiatedProtocolVersion, data.logger);
|
|
393
|
+
(0, external_capture_js_namespaceObject.captureEvent)(server, event, data.logger, {
|
|
394
|
+
...requestAttribution,
|
|
395
|
+
protocolVersion: negotiatedProtocolVersion
|
|
396
|
+
});
|
|
372
397
|
return result;
|
|
373
398
|
}
|
|
374
399
|
function readProtocolVersion(result, request) {
|
|
@@ -6,10 +6,10 @@ import { captureEvent } from "./capture.mjs";
|
|
|
6
6
|
import { MCPAnalyticsEventType } from "./event-types.mjs";
|
|
7
7
|
import { captureException } from "./exceptions.mjs";
|
|
8
8
|
import { resolveToolCallIntent, setEventIntent, setExplicitContextIntent } from "./intent.mjs";
|
|
9
|
-
import { getServerTrackingData, handleIdentify, setServerTrackingData } from "./internal.mjs";
|
|
9
|
+
import { getServerTrackingData, handleIdentify, setServerTrackingData, withIdentity } from "./internal.mjs";
|
|
10
10
|
import { buildCapturedMcpParameters } from "./mcp-payloads.mjs";
|
|
11
11
|
import { getLiteralValue, getObjectShape } from "./mcp-sdk-compat.mjs";
|
|
12
|
-
import { getSessionId, newSessionId } from "./session.mjs";
|
|
12
|
+
import { getSessionId, getSessionInfo, newSessionId } from "./session.mjs";
|
|
13
13
|
import { encodeSessionId, readMcpSessionHeader, writeSessionIdToTransport } from "./session-token.mjs";
|
|
14
14
|
import { getReportMissingToolDescriptor, resolveMissingCapabilityToolName } from "./tools.mjs";
|
|
15
15
|
import { applyResolvedMetadata, isToolResultError } from "./tracing-helpers.mjs";
|
|
@@ -20,17 +20,17 @@ async function captureToolCall(params) {
|
|
|
20
20
|
const conversation = resolveConversationId(ownership.conversationId, request.params?.arguments, request.params?.name, resolvedEventType === MCPAnalyticsEventType.mcpMissingCapability ? resolveMissingCapabilityToolName(data.options) : '');
|
|
21
21
|
const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership);
|
|
22
22
|
const startTime = new Date();
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
23
|
+
const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType);
|
|
24
|
+
if (preparedEvent && explicitContextIntent) setExplicitContextIntent(preparedEvent.event, explicitContextIntent);
|
|
25
25
|
let result;
|
|
26
26
|
try {
|
|
27
27
|
result = await execute(downstreamRequest);
|
|
28
28
|
} catch (error) {
|
|
29
|
-
publishFailedToolEvent(server,
|
|
29
|
+
publishFailedToolEvent(server, preparedEvent, error, startTime, conversation, data.logger);
|
|
30
30
|
throw error;
|
|
31
31
|
}
|
|
32
|
-
const finalResult = applyConversationPromptBack(event, result, conversation);
|
|
33
|
-
publishSuccessfulToolEvent(server,
|
|
32
|
+
const finalResult = applyConversationPromptBack(preparedEvent?.event ?? null, result, conversation);
|
|
33
|
+
publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError);
|
|
34
34
|
return finalResult;
|
|
35
35
|
}
|
|
36
36
|
function getActiveAnalyticsParameterOwnership(data, toolName, override, isMissingCapabilityTool) {
|
|
@@ -55,7 +55,9 @@ function cloneRequestWithoutOwnedAnalyticsArguments(request, ownership) {
|
|
|
55
55
|
async function prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, eventType) {
|
|
56
56
|
try {
|
|
57
57
|
const sessionId = getSessionId(server, extra);
|
|
58
|
-
|
|
58
|
+
const sessionInfo = getSessionInfo(server, data, sessionId);
|
|
59
|
+
const identity = await handleIdentify(server, data, sessionId, request, sessionInfo, extra);
|
|
60
|
+
const requestAttribution = withIdentity(sessionInfo, identity);
|
|
59
61
|
const toolName = request.params?.name;
|
|
60
62
|
const event = {
|
|
61
63
|
sessionId,
|
|
@@ -70,7 +72,10 @@ async function prepareToolCallEvent(server, data, request, downstreamRequest, ex
|
|
|
70
72
|
stampMetaClientInfo(event, request);
|
|
71
73
|
await applyResolvedMetadata(event, data, request, extra);
|
|
72
74
|
setEventIntent(event, await resolveToolCallIntent(data, request, ownership.context, extra));
|
|
73
|
-
return
|
|
75
|
+
return {
|
|
76
|
+
event,
|
|
77
|
+
requestAttribution
|
|
78
|
+
};
|
|
74
79
|
} catch (error) {
|
|
75
80
|
data.logger(`Warning: PostHog MCP analytics instrumentation failed for tool ${request.params?.name}, the tool will still run - ${error}`);
|
|
76
81
|
return null;
|
|
@@ -82,8 +87,9 @@ function applyConversationPromptBack(event, result, conversation) {
|
|
|
82
87
|
if (event) event.conversationId = void 0;
|
|
83
88
|
return result;
|
|
84
89
|
}
|
|
85
|
-
function publishSuccessfulToolEvent(server,
|
|
86
|
-
if (!
|
|
90
|
+
function publishSuccessfulToolEvent(server, preparedEvent, result, startTime, logger, takeCapturedError) {
|
|
91
|
+
if (!preparedEvent) return;
|
|
92
|
+
const { event, requestAttribution } = preparedEvent;
|
|
87
93
|
try {
|
|
88
94
|
if (isToolResultError(result)) {
|
|
89
95
|
event.isError = true;
|
|
@@ -92,19 +98,20 @@ function publishSuccessfulToolEvent(server, event, result, startTime, logger, ta
|
|
|
92
98
|
} else event.isError = false;
|
|
93
99
|
event.response = result;
|
|
94
100
|
event.duration = Date.now() - startTime.getTime();
|
|
95
|
-
captureEvent(server, event, logger);
|
|
101
|
+
captureEvent(server, event, logger, requestAttribution);
|
|
96
102
|
} catch (error) {
|
|
97
103
|
logger(`Warning: PostHog MCP analytics failed to publish tool event - ${error}`);
|
|
98
104
|
}
|
|
99
105
|
}
|
|
100
|
-
function publishFailedToolEvent(server,
|
|
101
|
-
if (!
|
|
106
|
+
function publishFailedToolEvent(server, preparedEvent, error, startTime, conversation, logger) {
|
|
107
|
+
if (!preparedEvent) return;
|
|
108
|
+
const { event, requestAttribution } = preparedEvent;
|
|
102
109
|
try {
|
|
103
110
|
if (conversation.minted) event.conversationId = void 0;
|
|
104
111
|
event.isError = true;
|
|
105
112
|
event.error = captureException(error);
|
|
106
113
|
event.duration = Date.now() - startTime.getTime();
|
|
107
|
-
captureEvent(server, event, logger);
|
|
114
|
+
captureEvent(server, event, logger, requestAttribution);
|
|
108
115
|
} catch (publishError) {
|
|
109
116
|
logger(`Warning: PostHog MCP analytics failed to publish failed tool event - ${publishError}`);
|
|
110
117
|
}
|
|
@@ -159,15 +166,17 @@ async function isToolAdvertised(server, toolName, extra, logger) {
|
|
|
159
166
|
async function handleListToolsRequest(server, originalListToolsHandler, request, extra, logger) {
|
|
160
167
|
const data = getServerTrackingData(server);
|
|
161
168
|
const startTime = new Date();
|
|
169
|
+
const sessionId = getSessionId(server, extra);
|
|
170
|
+
const requestAttribution = getSessionInfo(server, data, sessionId);
|
|
162
171
|
const event = {
|
|
163
|
-
sessionId
|
|
172
|
+
sessionId,
|
|
164
173
|
parameters: buildCapturedMcpParameters(request),
|
|
165
174
|
eventType: MCPAnalyticsEventType.mcpToolsList,
|
|
166
175
|
timestamp: startTime
|
|
167
176
|
};
|
|
168
177
|
stampMetaClientInfo(event, request);
|
|
169
178
|
if (data) await applyResolvedMetadata(event, data, request, extra);
|
|
170
|
-
const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger);
|
|
179
|
+
const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger, requestAttribution);
|
|
171
180
|
if (!data) {
|
|
172
181
|
logger('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
|
|
173
182
|
return {
|
|
@@ -179,7 +188,7 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
|
|
|
179
188
|
event.error = captureException('No tools were sent to MCP client.');
|
|
180
189
|
event.isError = true;
|
|
181
190
|
event.duration = Date.now() - startTime.getTime();
|
|
182
|
-
captureEvent(server, event, data.logger);
|
|
191
|
+
captureEvent(server, event, data.logger, requestAttribution);
|
|
183
192
|
return {
|
|
184
193
|
tools
|
|
185
194
|
};
|
|
@@ -190,7 +199,7 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
|
|
|
190
199
|
event.listedToolNames = collectListedToolNames(tools);
|
|
191
200
|
event.isError = false;
|
|
192
201
|
event.duration = Date.now() - startTime.getTime();
|
|
193
|
-
captureEvent(server, event, data.logger);
|
|
202
|
+
captureEvent(server, event, data.logger, requestAttribution);
|
|
194
203
|
return {
|
|
195
204
|
tools
|
|
196
205
|
};
|
|
@@ -203,7 +212,7 @@ function collectListedToolNames(tools) {
|
|
|
203
212
|
const names = tools.map((tool)=>tool?.name).filter((name)=>'string' == typeof name);
|
|
204
213
|
return names.length > 0 ? names : void 0;
|
|
205
214
|
}
|
|
206
|
-
async function getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger) {
|
|
215
|
+
async function getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger, requestAttribution) {
|
|
207
216
|
try {
|
|
208
217
|
const data = getServerTrackingData(server);
|
|
209
218
|
const originalResponse = await originalListToolsHandler(request, extra);
|
|
@@ -235,7 +244,7 @@ async function getTracedToolsList(server, originalListToolsHandler, request, ext
|
|
|
235
244
|
event.error = captureException(error);
|
|
236
245
|
event.isError = true;
|
|
237
246
|
event.duration = event.timestamp ? Date.now() - event.timestamp.getTime() : 0;
|
|
238
|
-
captureEvent(server, event, logger);
|
|
247
|
+
captureEvent(server, event, logger, requestAttribution);
|
|
239
248
|
throw error;
|
|
240
249
|
}
|
|
241
250
|
}
|
|
@@ -284,19 +293,19 @@ function mintStatelessSessionOnInitialize(server, data, request, extra) {
|
|
|
284
293
|
return;
|
|
285
294
|
}
|
|
286
295
|
}
|
|
287
|
-
function upgradeMintedTokenToNegotiated(server,
|
|
296
|
+
function upgradeMintedTokenToNegotiated(server, mintedSessionId, sessionInfo, negotiatedProtocolVersion, logger) {
|
|
288
297
|
try {
|
|
289
298
|
const transport = server.transport;
|
|
290
299
|
if (!transport) return;
|
|
291
300
|
const token = encodeSessionId({
|
|
292
301
|
sessionId: mintedSessionId,
|
|
293
|
-
clientName:
|
|
294
|
-
clientVersion:
|
|
302
|
+
clientName: sessionInfo.clientName,
|
|
303
|
+
clientVersion: sessionInfo.clientVersion,
|
|
295
304
|
protocolVersion: negotiatedProtocolVersion
|
|
296
305
|
});
|
|
297
306
|
writeSessionIdToTransport(transport, token);
|
|
298
307
|
} catch (error) {
|
|
299
|
-
|
|
308
|
+
logger(`Warning: PostHog MCP analytics failed to upgrade the stateless session token - ${error}`);
|
|
300
309
|
}
|
|
301
310
|
}
|
|
302
311
|
function readInitializeClientInfo(request) {
|
|
@@ -316,7 +325,15 @@ async function handleInitializeRequest(server, originalInitializeHandler, reques
|
|
|
316
325
|
}
|
|
317
326
|
const mintedSessionId = mintStatelessSessionOnInitialize(server, data, request, extra);
|
|
318
327
|
const sessionId = getSessionId(server, extra);
|
|
319
|
-
|
|
328
|
+
const sessionInfo = getSessionInfo(server, data, sessionId);
|
|
329
|
+
const initializeClientInfo = readInitializeClientInfo(request);
|
|
330
|
+
const requestSessionInfo = {
|
|
331
|
+
...sessionInfo,
|
|
332
|
+
clientName: initializeClientInfo?.name ?? sessionInfo.clientName,
|
|
333
|
+
clientVersion: initializeClientInfo?.version ?? sessionInfo.clientVersion
|
|
334
|
+
};
|
|
335
|
+
const identity = await handleIdentify(server, data, sessionId, request, requestSessionInfo, extra);
|
|
336
|
+
const requestAttribution = withIdentity(requestSessionInfo, identity);
|
|
320
337
|
const event = {
|
|
321
338
|
sessionId,
|
|
322
339
|
resourceName: request.params?.name || 'Unknown Tool Name',
|
|
@@ -330,10 +347,18 @@ async function handleInitializeRequest(server, originalInitializeHandler, reques
|
|
|
330
347
|
event.response = result;
|
|
331
348
|
const negotiatedProtocolVersion = readProtocolVersion(result, request);
|
|
332
349
|
event.protocolVersion = negotiatedProtocolVersion;
|
|
333
|
-
data.
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
350
|
+
if (data.sessionId === sessionId) {
|
|
351
|
+
data.sessionInfo = {
|
|
352
|
+
...data.sessionInfo,
|
|
353
|
+
protocolVersion: negotiatedProtocolVersion
|
|
354
|
+
};
|
|
355
|
+
setServerTrackingData(server, data);
|
|
356
|
+
}
|
|
357
|
+
if (mintedSessionId) upgradeMintedTokenToNegotiated(server, mintedSessionId, requestSessionInfo, negotiatedProtocolVersion, data.logger);
|
|
358
|
+
captureEvent(server, event, data.logger, {
|
|
359
|
+
...requestAttribution,
|
|
360
|
+
protocolVersion: negotiatedProtocolVersion
|
|
361
|
+
});
|
|
337
362
|
return result;
|
|
338
363
|
}
|
|
339
364
|
function readProtocolVersion(result, request) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CompatibleRequestHandlerExtra, MCPAnalyticsData, MCPRequestLike, MCPServerLike, UserIdentity } from '../types';
|
|
1
|
+
import type { CompatibleRequestHandlerExtra, MCPAnalyticsData, MCPRequestLike, MCPServerLike, SessionInfo, UserIdentity } from '../types';
|
|
2
2
|
/**
|
|
3
3
|
* Bounded LRU cache for session identities, capped at `maxSize` entries so a
|
|
4
4
|
* long-lived server can't accumulate identities for unboundedly many sessions.
|
|
@@ -24,7 +24,8 @@ export declare function mergeIdentities(previous: UserIdentity | undefined, next
|
|
|
24
24
|
* `$identify` event at most once per session: at `initialize`, when a
|
|
25
25
|
* long-lived server first sees the identity, or when it materially changes.
|
|
26
26
|
*/
|
|
27
|
-
export declare function handleIdentify(server: MCPServerLike, data: MCPAnalyticsData, sessionId: string, request: MCPRequestLike, extra?: CompatibleRequestHandlerExtra): Promise<
|
|
27
|
+
export declare function handleIdentify(server: MCPServerLike, data: MCPAnalyticsData, sessionId: string, request: MCPRequestLike, requestAttribution: SessionInfo, extra?: CompatibleRequestHandlerExtra): Promise<UserIdentity | undefined>;
|
|
28
|
+
export declare function withIdentity(sessionInfo: SessionInfo, identity: UserIdentity | undefined): SessionInfo;
|
|
28
29
|
/**
|
|
29
30
|
* Resolves the `eventProperties` callback. Returns null when no callback is
|
|
30
31
|
* configured, or when it returns nullish or throws — a throw never propagates
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../src/extensions/internal.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,cAAc,EACd,aAAa,EAEb,YAAY,EACb,MAAM,UAAU,CAAA;AAKjB;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;gBAErB,OAAO,SAAO;IAI1B,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAWhD,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI;IAapD,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAI/B,IAAI,IAAI,MAAM;CAGf;AAID,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,gBAAgB,GAAG,SAAS,CAEzF;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAEzF;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,OAAO,CA4B5E;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,SAAS,EAAE,IAAI,EAAE,YAAY,GAAG,YAAY,CAapG;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,gBAAgB,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,EACvB,KAAK,CAAC,EAAE,6BAA6B,GACpC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../src/extensions/internal.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,cAAc,EACd,aAAa,EAEb,WAAW,EACX,YAAY,EACb,MAAM,UAAU,CAAA;AAKjB;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;gBAErB,OAAO,SAAO;IAI1B,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAWhD,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI;IAapD,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAI/B,IAAI,IAAI,MAAM;CAGf;AAID,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,gBAAgB,GAAG,SAAS,CAEzF;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAEzF;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,OAAO,CA4B5E;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,SAAS,EAAE,IAAI,EAAE,YAAY,GAAG,YAAY,CAapG;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,gBAAgB,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,EACvB,kBAAkB,EAAE,WAAW,EAC/B,KAAK,CAAC,EAAE,6BAA6B,GACpC,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAsDnC;AAED,wBAAgB,YAAY,CAAC,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,GAAG,SAAS,GAAG,WAAW,CAOtG;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,cAAc,EACvB,KAAK,CAAC,EAAE,6BAA6B,GACpC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAUzC"}
|
|
@@ -26,11 +26,12 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
27
|
mergeIdentities: ()=>mergeIdentities,
|
|
28
28
|
areIdentitiesEqual: ()=>areIdentitiesEqual,
|
|
29
|
+
getServerTrackingData: ()=>getServerTrackingData,
|
|
30
|
+
setServerTrackingData: ()=>setServerTrackingData,
|
|
29
31
|
resolveEventProperties: ()=>resolveEventProperties,
|
|
32
|
+
withIdentity: ()=>withIdentity,
|
|
30
33
|
handleIdentify: ()=>handleIdentify,
|
|
31
|
-
|
|
32
|
-
IdentityCache: ()=>IdentityCache,
|
|
33
|
-
setServerTrackingData: ()=>setServerTrackingData
|
|
34
|
+
IdentityCache: ()=>IdentityCache
|
|
34
35
|
});
|
|
35
36
|
const external_event_types_js_namespaceObject = require("./event-types.js");
|
|
36
37
|
const external_capture_js_namespaceObject = require("./capture.js");
|
|
@@ -94,8 +95,10 @@ function mergeIdentities(previous, next) {
|
|
|
94
95
|
groups: next.groups ?? previous.groups
|
|
95
96
|
};
|
|
96
97
|
}
|
|
97
|
-
async function handleIdentify(server, data, sessionId, request, extra) {
|
|
98
|
-
|
|
98
|
+
async function handleIdentify(server, data, sessionId, request, requestAttribution, extra) {
|
|
99
|
+
const identityBeforeRequest = data.identifiedSessions.get(sessionId);
|
|
100
|
+
const sessionSourceBeforeIdentify = data.sessionSource;
|
|
101
|
+
if (!data.options.identify) return identityBeforeRequest;
|
|
99
102
|
const identifyEvent = {
|
|
100
103
|
sessionId,
|
|
101
104
|
resourceName: getRequestResourceName(request),
|
|
@@ -114,17 +117,28 @@ async function handleIdentify(server, data, sessionId, request, extra) {
|
|
|
114
117
|
const mergedIdentity = mergeIdentities(previousIdentity, identityResult);
|
|
115
118
|
const changed = void 0 !== previousIdentity && !areIdentitiesEqual(previousIdentity, mergedIdentity);
|
|
116
119
|
const firstSeen = void 0 === previousIdentity;
|
|
117
|
-
const announcedAtInitialize = 'token' ===
|
|
120
|
+
const announcedAtInitialize = 'token' === sessionSourceBeforeIdentify && 'initialize' !== request.method;
|
|
118
121
|
const shouldPublish = changed || firstSeen && !announcedAtInitialize;
|
|
119
122
|
data.identifiedSessions.set(sessionId, mergedIdentity);
|
|
120
123
|
if (shouldPublish) {
|
|
121
124
|
data.logger(`Identified session ${sessionId}`);
|
|
122
|
-
(0, external_capture_js_namespaceObject.captureEvent)(server, identifyEvent, data.logger);
|
|
125
|
+
(0, external_capture_js_namespaceObject.captureEvent)(server, identifyEvent, data.logger, withIdentity(requestAttribution, mergedIdentity));
|
|
123
126
|
}
|
|
124
|
-
|
|
127
|
+
return mergedIdentity;
|
|
128
|
+
}
|
|
129
|
+
data.logger(`Warning: Supplied identify function returned null for session ${sessionId}`);
|
|
125
130
|
} catch (error) {
|
|
126
131
|
data.logger(`Error: User supplied identify function threw an error while identifying session ${sessionId} - ${error}`);
|
|
127
132
|
}
|
|
133
|
+
return identityBeforeRequest;
|
|
134
|
+
}
|
|
135
|
+
function withIdentity(sessionInfo, identity) {
|
|
136
|
+
return {
|
|
137
|
+
...sessionInfo,
|
|
138
|
+
identifyActorGivenId: identity?.distinctId,
|
|
139
|
+
identifyActorData: identity?.properties || {},
|
|
140
|
+
identifyActorGroups: identity?.groups
|
|
141
|
+
};
|
|
128
142
|
}
|
|
129
143
|
async function resolveEventProperties(data, request, extra) {
|
|
130
144
|
if (!data.options.eventProperties) return null;
|
|
@@ -148,6 +162,7 @@ exports.handleIdentify = __webpack_exports__.handleIdentify;
|
|
|
148
162
|
exports.mergeIdentities = __webpack_exports__.mergeIdentities;
|
|
149
163
|
exports.resolveEventProperties = __webpack_exports__.resolveEventProperties;
|
|
150
164
|
exports.setServerTrackingData = __webpack_exports__.setServerTrackingData;
|
|
165
|
+
exports.withIdentity = __webpack_exports__.withIdentity;
|
|
151
166
|
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
152
167
|
"IdentityCache",
|
|
153
168
|
"areIdentitiesEqual",
|
|
@@ -155,7 +170,8 @@ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
|
155
170
|
"handleIdentify",
|
|
156
171
|
"mergeIdentities",
|
|
157
172
|
"resolveEventProperties",
|
|
158
|
-
"setServerTrackingData"
|
|
173
|
+
"setServerTrackingData",
|
|
174
|
+
"withIdentity"
|
|
159
175
|
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
160
176
|
Object.defineProperty(exports, '__esModule', {
|
|
161
177
|
value: true
|
|
@@ -60,8 +60,10 @@ function mergeIdentities(previous, next) {
|
|
|
60
60
|
groups: next.groups ?? previous.groups
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
|
-
async function handleIdentify(server, data, sessionId, request, extra) {
|
|
64
|
-
|
|
63
|
+
async function handleIdentify(server, data, sessionId, request, requestAttribution, extra) {
|
|
64
|
+
const identityBeforeRequest = data.identifiedSessions.get(sessionId);
|
|
65
|
+
const sessionSourceBeforeIdentify = data.sessionSource;
|
|
66
|
+
if (!data.options.identify) return identityBeforeRequest;
|
|
65
67
|
const identifyEvent = {
|
|
66
68
|
sessionId,
|
|
67
69
|
resourceName: getRequestResourceName(request),
|
|
@@ -80,17 +82,28 @@ async function handleIdentify(server, data, sessionId, request, extra) {
|
|
|
80
82
|
const mergedIdentity = mergeIdentities(previousIdentity, identityResult);
|
|
81
83
|
const changed = void 0 !== previousIdentity && !areIdentitiesEqual(previousIdentity, mergedIdentity);
|
|
82
84
|
const firstSeen = void 0 === previousIdentity;
|
|
83
|
-
const announcedAtInitialize = 'token' ===
|
|
85
|
+
const announcedAtInitialize = 'token' === sessionSourceBeforeIdentify && 'initialize' !== request.method;
|
|
84
86
|
const shouldPublish = changed || firstSeen && !announcedAtInitialize;
|
|
85
87
|
data.identifiedSessions.set(sessionId, mergedIdentity);
|
|
86
88
|
if (shouldPublish) {
|
|
87
89
|
data.logger(`Identified session ${sessionId}`);
|
|
88
|
-
captureEvent(server, identifyEvent, data.logger);
|
|
90
|
+
captureEvent(server, identifyEvent, data.logger, withIdentity(requestAttribution, mergedIdentity));
|
|
89
91
|
}
|
|
90
|
-
|
|
92
|
+
return mergedIdentity;
|
|
93
|
+
}
|
|
94
|
+
data.logger(`Warning: Supplied identify function returned null for session ${sessionId}`);
|
|
91
95
|
} catch (error) {
|
|
92
96
|
data.logger(`Error: User supplied identify function threw an error while identifying session ${sessionId} - ${error}`);
|
|
93
97
|
}
|
|
98
|
+
return identityBeforeRequest;
|
|
99
|
+
}
|
|
100
|
+
function withIdentity(sessionInfo, identity) {
|
|
101
|
+
return {
|
|
102
|
+
...sessionInfo,
|
|
103
|
+
identifyActorGivenId: identity?.distinctId,
|
|
104
|
+
identifyActorData: identity?.properties || {},
|
|
105
|
+
identifyActorGroups: identity?.groups
|
|
106
|
+
};
|
|
94
107
|
}
|
|
95
108
|
async function resolveEventProperties(data, request, extra) {
|
|
96
109
|
if (!data.options.eventProperties) return null;
|
|
@@ -107,4 +120,4 @@ function getRequestResourceName(request) {
|
|
|
107
120
|
if (!params || 'object' != typeof params || !('name' in params)) return 'Unknown';
|
|
108
121
|
return 'string' == typeof params.name ? params.name : 'Unknown';
|
|
109
122
|
}
|
|
110
|
-
export { IdentityCache, areIdentitiesEqual, getServerTrackingData, handleIdentify, mergeIdentities, resolveEventProperties, setServerTrackingData };
|
|
123
|
+
export { IdentityCache, areIdentitiesEqual, getServerTrackingData, handleIdentify, mergeIdentities, resolveEventProperties, setServerTrackingData, withIdentity };
|
|
@@ -90,7 +90,9 @@ function getSessionInfo(server, data, sessionId) {
|
|
|
90
90
|
if (!data) return sessionInfo;
|
|
91
91
|
data.sessionInfo = sessionInfo;
|
|
92
92
|
(0, external_internal_js_namespaceObject.setServerTrackingData)(server, data);
|
|
93
|
-
return
|
|
93
|
+
return {
|
|
94
|
+
...sessionInfo
|
|
95
|
+
};
|
|
94
96
|
}
|
|
95
97
|
exports.deriveSessionIdFromMCPSession = __webpack_exports__.deriveSessionIdFromMCPSession;
|
|
96
98
|
exports.getSessionId = __webpack_exports__.getSessionId;
|
|
@@ -59,6 +59,8 @@ function getSessionInfo(server, data, sessionId) {
|
|
|
59
59
|
if (!data) return sessionInfo;
|
|
60
60
|
data.sessionInfo = sessionInfo;
|
|
61
61
|
setServerTrackingData(server, data);
|
|
62
|
-
return
|
|
62
|
+
return {
|
|
63
|
+
...sessionInfo
|
|
64
|
+
};
|
|
63
65
|
}
|
|
64
66
|
export { deriveSessionIdFromMCPSession, getSessionId, getSessionInfo, newSessionId };
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version = "0.10.
|
|
1
|
+
export declare const version = "0.10.7";
|
|
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.10.
|
|
29
|
+
const version = '0.10.7';
|
|
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.10.
|
|
1
|
+
const version = '0.10.7';
|
|
2
2
|
export { version };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/mcp",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.7",
|
|
4
4
|
"bugs": {
|
|
5
5
|
"url": "https://github.com/PostHog/posthog-js/issues"
|
|
6
6
|
},
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"@types/node": "^20.0.0",
|
|
40
40
|
"jest": "29.7.0",
|
|
41
41
|
"zod": "^3.25.0",
|
|
42
|
-
"posthog-
|
|
43
|
-
"
|
|
42
|
+
"@posthog-tooling/tsconfig-base": "1.1.1",
|
|
43
|
+
"posthog-node": "^5.47.9"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@modelcontextprotocol/sdk": ">=1.26.0",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Copyright (c) 2025 AgentCat, Inc. (formerly MCPcat)
|
|
4
4
|
// Licensed under the MIT License: https://github.com/agentcathq/agentcat-typescript-sdk/blob/main/LICENSE
|
|
5
5
|
|
|
6
|
-
import type { MCPServerLike, McpEvent } from '../types'
|
|
6
|
+
import type { MCPServerLike, McpEvent, SessionInfo } from '../types'
|
|
7
7
|
import { MCPAnalyticsEventType } from './event-types'
|
|
8
8
|
import { getServerTrackingData } from './internal'
|
|
9
9
|
import type { LoggerFn } from './logger'
|
|
@@ -19,7 +19,12 @@ import { getSessionInfo } from './session'
|
|
|
19
19
|
* Auto-capture callers (tool calls, listings, identify) intentionally ignore the
|
|
20
20
|
* return value, keeping the tool path isolated from analytics latency/errors.
|
|
21
21
|
*/
|
|
22
|
-
export function captureEvent(
|
|
22
|
+
export function captureEvent(
|
|
23
|
+
server: MCPServerLike,
|
|
24
|
+
eventInput: McpEvent,
|
|
25
|
+
logger: LoggerFn,
|
|
26
|
+
requestAttribution?: SessionInfo
|
|
27
|
+
): Promise<void> | undefined {
|
|
23
28
|
const data = getServerTrackingData(server)
|
|
24
29
|
if (!data) {
|
|
25
30
|
logger('Warning: Server tracking data not found. Event will not be published.')
|
|
@@ -31,7 +36,10 @@ export function captureEvent(server: MCPServerLike, eventInput: McpEvent, logger
|
|
|
31
36
|
return
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
// Instrumented request paths pass an immutable snapshot resolved before any
|
|
40
|
+
// user callback or handler can yield to a concurrent request. Custom capture
|
|
41
|
+
// has no request context, so it intentionally falls back to current state.
|
|
42
|
+
const sessionInfo = requestAttribution ?? getSessionInfo(server, data, eventInput.sessionId)
|
|
35
43
|
|
|
36
44
|
const duration =
|
|
37
45
|
eventInput.duration || (eventInput.timestamp ? Date.now() - eventInput.timestamp.getTime() : undefined)
|
|
@@ -48,9 +56,8 @@ export function captureEvent(server: MCPServerLike, eventInput: McpEvent, logger
|
|
|
48
56
|
sdkVersion: sessionInfo.sdkVersion,
|
|
49
57
|
serverName: sessionInfo.serverName,
|
|
50
58
|
serverVersion: sessionInfo.serverVersion,
|
|
51
|
-
// Prefer
|
|
52
|
-
//
|
|
53
|
-
// request from another client can't misattribute this one.
|
|
59
|
+
// Prefer client metadata stamped onto this event from the request's `_meta`
|
|
60
|
+
// over sessionInfo, so concurrent stateless requests stay isolated.
|
|
54
61
|
clientName: eventInput.clientName ?? sessionInfo.clientName,
|
|
55
62
|
clientVersion: eventInput.clientVersion ?? sessionInfo.clientVersion,
|
|
56
63
|
identifyActorGivenId: sessionInfo.identifyActorGivenId,
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
MCPServerLike,
|
|
13
13
|
McpEvent,
|
|
14
14
|
ServerClientInfoLike,
|
|
15
|
+
SessionInfo,
|
|
15
16
|
} from '../types'
|
|
16
17
|
import { getAnalyticsParameterOwnership, stripOwnedAnalyticsArguments } from './analytics-parameters'
|
|
17
18
|
import { addContextParameterToTools, getContextDescription, isContextEnabled } from './context-parameters'
|
|
@@ -27,11 +28,11 @@ import { captureEvent } from './capture'
|
|
|
27
28
|
import { MCPAnalyticsEventType } from './event-types'
|
|
28
29
|
import { captureException } from './exceptions'
|
|
29
30
|
import { resolveToolCallIntent, setEventIntent, setExplicitContextIntent } from './intent'
|
|
30
|
-
import { getServerTrackingData, handleIdentify, setServerTrackingData } from './internal'
|
|
31
|
+
import { getServerTrackingData, handleIdentify, setServerTrackingData, withIdentity } from './internal'
|
|
31
32
|
import type { LoggerFn } from './logger'
|
|
32
33
|
import { buildCapturedMcpParameters } from './mcp-payloads'
|
|
33
34
|
import { getLiteralValue, getObjectShape } from './mcp-sdk-compat'
|
|
34
|
-
import { getSessionId, newSessionId } from './session'
|
|
35
|
+
import { getSessionId, getSessionInfo, newSessionId } from './session'
|
|
35
36
|
import { encodeSessionId, readMcpSessionHeader, writeSessionIdToTransport } from './session-token'
|
|
36
37
|
import { getReportMissingToolDescriptor, resolveMissingCapabilityToolName } from './tools'
|
|
37
38
|
import { applyResolvedMetadata, isToolResultError } from './tracing-helpers'
|
|
@@ -118,7 +119,7 @@ export async function captureToolCall(params: TraceToolCallParams): Promise<unkn
|
|
|
118
119
|
// Prepare the event in isolation: if identity/metadata/intent resolution
|
|
119
120
|
// throws, we drop instrumentation for this call but still run the tool.
|
|
120
121
|
const startTime = new Date()
|
|
121
|
-
const
|
|
122
|
+
const preparedEvent = await prepareToolCallEvent(
|
|
122
123
|
server,
|
|
123
124
|
data,
|
|
124
125
|
request,
|
|
@@ -129,23 +130,28 @@ export async function captureToolCall(params: TraceToolCallParams): Promise<unkn
|
|
|
129
130
|
ownership,
|
|
130
131
|
resolvedEventType
|
|
131
132
|
)
|
|
132
|
-
if (
|
|
133
|
-
setExplicitContextIntent(event, explicitContextIntent)
|
|
133
|
+
if (preparedEvent && explicitContextIntent) {
|
|
134
|
+
setExplicitContextIntent(preparedEvent.event, explicitContextIntent)
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
let result: unknown
|
|
137
138
|
try {
|
|
138
139
|
result = await execute(downstreamRequest)
|
|
139
140
|
} catch (error) {
|
|
140
|
-
publishFailedToolEvent(server,
|
|
141
|
+
publishFailedToolEvent(server, preparedEvent, error, startTime, conversation, data.logger)
|
|
141
142
|
throw error
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
const finalResult = applyConversationPromptBack(event, result, conversation)
|
|
145
|
-
publishSuccessfulToolEvent(server,
|
|
145
|
+
const finalResult = applyConversationPromptBack(preparedEvent?.event ?? null, result, conversation)
|
|
146
|
+
publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError)
|
|
146
147
|
return finalResult
|
|
147
148
|
}
|
|
148
149
|
|
|
150
|
+
interface PreparedToolEvent {
|
|
151
|
+
event: McpEvent
|
|
152
|
+
requestAttribution: SessionInfo
|
|
153
|
+
}
|
|
154
|
+
|
|
149
155
|
function getActiveAnalyticsParameterOwnership(
|
|
150
156
|
data: MCPAnalyticsData,
|
|
151
157
|
toolName: string | undefined,
|
|
@@ -188,10 +194,14 @@ async function prepareToolCallEvent(
|
|
|
188
194
|
conversation: ConversationIdResolution,
|
|
189
195
|
ownership: AnalyticsParameterOwnership,
|
|
190
196
|
eventType: MCPAnalyticsEventType
|
|
191
|
-
): Promise<
|
|
197
|
+
): Promise<PreparedToolEvent | null> {
|
|
192
198
|
try {
|
|
193
199
|
const sessionId = getSessionId(server, extra)
|
|
194
|
-
|
|
200
|
+
// Snapshot token/client/protocol metadata synchronously, before identify or
|
|
201
|
+
// metadata callbacks can yield and let another request replace shared state.
|
|
202
|
+
const sessionInfo = getSessionInfo(server, data, sessionId)
|
|
203
|
+
const identity = await handleIdentify(server, data, sessionId, request, sessionInfo, extra)
|
|
204
|
+
const requestAttribution = withIdentity(sessionInfo, identity)
|
|
195
205
|
|
|
196
206
|
const toolName = request.params?.name
|
|
197
207
|
const event: McpEvent = {
|
|
@@ -211,7 +221,7 @@ async function prepareToolCallEvent(
|
|
|
211
221
|
|
|
212
222
|
await applyResolvedMetadata(event, data, request, extra)
|
|
213
223
|
setEventIntent(event, await resolveToolCallIntent(data, request, ownership.context, extra))
|
|
214
|
-
return event
|
|
224
|
+
return { event, requestAttribution }
|
|
215
225
|
} catch (error) {
|
|
216
226
|
data.logger(
|
|
217
227
|
`Warning: PostHog MCP analytics instrumentation failed for tool ${request.params?.name}, the tool will still run - ${error}`
|
|
@@ -244,15 +254,16 @@ function applyConversationPromptBack(
|
|
|
244
254
|
|
|
245
255
|
function publishSuccessfulToolEvent(
|
|
246
256
|
server: MCPServerLike,
|
|
247
|
-
|
|
257
|
+
preparedEvent: PreparedToolEvent | null,
|
|
248
258
|
result: unknown,
|
|
249
259
|
startTime: Date,
|
|
250
260
|
logger: LoggerFn,
|
|
251
261
|
takeCapturedError?: () => unknown
|
|
252
262
|
): void {
|
|
253
|
-
if (!
|
|
263
|
+
if (!preparedEvent) {
|
|
254
264
|
return
|
|
255
265
|
}
|
|
266
|
+
const { event, requestAttribution } = preparedEvent
|
|
256
267
|
try {
|
|
257
268
|
if (isToolResultError(result)) {
|
|
258
269
|
event.isError = true
|
|
@@ -263,7 +274,7 @@ function publishSuccessfulToolEvent(
|
|
|
263
274
|
}
|
|
264
275
|
event.response = result
|
|
265
276
|
event.duration = Date.now() - startTime.getTime()
|
|
266
|
-
captureEvent(server, event, logger)
|
|
277
|
+
captureEvent(server, event, logger, requestAttribution)
|
|
267
278
|
} catch (error) {
|
|
268
279
|
logger(`Warning: PostHog MCP analytics failed to publish tool event - ${error}`)
|
|
269
280
|
}
|
|
@@ -271,15 +282,16 @@ function publishSuccessfulToolEvent(
|
|
|
271
282
|
|
|
272
283
|
function publishFailedToolEvent(
|
|
273
284
|
server: MCPServerLike,
|
|
274
|
-
|
|
285
|
+
preparedEvent: PreparedToolEvent | null,
|
|
275
286
|
error: unknown,
|
|
276
287
|
startTime: Date,
|
|
277
288
|
conversation: ConversationIdResolution,
|
|
278
289
|
logger: LoggerFn
|
|
279
290
|
): void {
|
|
280
|
-
if (!
|
|
291
|
+
if (!preparedEvent) {
|
|
281
292
|
return
|
|
282
293
|
}
|
|
294
|
+
const { event, requestAttribution } = preparedEvent
|
|
283
295
|
try {
|
|
284
296
|
if (conversation.minted) {
|
|
285
297
|
event.conversationId = undefined
|
|
@@ -287,7 +299,7 @@ function publishFailedToolEvent(
|
|
|
287
299
|
event.isError = true
|
|
288
300
|
event.error = captureException(error)
|
|
289
301
|
event.duration = Date.now() - startTime.getTime()
|
|
290
|
-
captureEvent(server, event, logger)
|
|
302
|
+
captureEvent(server, event, logger, requestAttribution)
|
|
291
303
|
} catch (publishError) {
|
|
292
304
|
logger(`Warning: PostHog MCP analytics failed to publish failed tool event - ${publishError}`)
|
|
293
305
|
}
|
|
@@ -407,8 +419,12 @@ export async function handleListToolsRequest(
|
|
|
407
419
|
): Promise<{ tools: ListToolsResult['tools'] }> {
|
|
408
420
|
const data = getServerTrackingData(server)
|
|
409
421
|
const startTime = new Date()
|
|
422
|
+
const sessionId = getSessionId(server, extra)
|
|
423
|
+
// Snapshot before metadata resolution or the list handler can yield to a
|
|
424
|
+
// concurrent request using the same instrumented server.
|
|
425
|
+
const requestAttribution = getSessionInfo(server, data, sessionId)
|
|
410
426
|
const event: McpEvent = {
|
|
411
|
-
sessionId
|
|
427
|
+
sessionId,
|
|
412
428
|
parameters: buildCapturedMcpParameters(request),
|
|
413
429
|
eventType: MCPAnalyticsEventType.mcpToolsList,
|
|
414
430
|
timestamp: startTime,
|
|
@@ -419,7 +435,15 @@ export async function handleListToolsRequest(
|
|
|
419
435
|
await applyResolvedMetadata(event, data, request, extra)
|
|
420
436
|
}
|
|
421
437
|
|
|
422
|
-
const tools = await getTracedToolsList(
|
|
438
|
+
const tools = await getTracedToolsList(
|
|
439
|
+
server,
|
|
440
|
+
originalListToolsHandler,
|
|
441
|
+
request,
|
|
442
|
+
extra,
|
|
443
|
+
event,
|
|
444
|
+
logger,
|
|
445
|
+
requestAttribution
|
|
446
|
+
)
|
|
423
447
|
|
|
424
448
|
if (!data) {
|
|
425
449
|
logger(
|
|
@@ -435,7 +459,7 @@ export async function handleListToolsRequest(
|
|
|
435
459
|
event.error = captureException('No tools were sent to MCP client.')
|
|
436
460
|
event.isError = true
|
|
437
461
|
event.duration = Date.now() - startTime.getTime()
|
|
438
|
-
captureEvent(server, event, data.logger)
|
|
462
|
+
captureEvent(server, event, data.logger, requestAttribution)
|
|
439
463
|
return { tools }
|
|
440
464
|
}
|
|
441
465
|
|
|
@@ -443,7 +467,7 @@ export async function handleListToolsRequest(
|
|
|
443
467
|
event.listedToolNames = collectListedToolNames(tools)
|
|
444
468
|
event.isError = false
|
|
445
469
|
event.duration = Date.now() - startTime.getTime()
|
|
446
|
-
captureEvent(server, event, data.logger)
|
|
470
|
+
captureEvent(server, event, data.logger, requestAttribution)
|
|
447
471
|
return { tools }
|
|
448
472
|
}
|
|
449
473
|
|
|
@@ -473,7 +497,8 @@ async function getTracedToolsList(
|
|
|
473
497
|
request: MCPRequestLike,
|
|
474
498
|
extra: CompatibleRequestHandlerExtra | undefined,
|
|
475
499
|
event: McpEvent,
|
|
476
|
-
logger: LoggerFn
|
|
500
|
+
logger: LoggerFn,
|
|
501
|
+
requestAttribution: SessionInfo
|
|
477
502
|
): Promise<ListToolsResult['tools']> {
|
|
478
503
|
try {
|
|
479
504
|
const data = getServerTrackingData(server)
|
|
@@ -521,7 +546,7 @@ async function getTracedToolsList(
|
|
|
521
546
|
event.error = captureException(error)
|
|
522
547
|
event.isError = true
|
|
523
548
|
event.duration = event.timestamp ? Date.now() - event.timestamp.getTime() : 0
|
|
524
|
-
captureEvent(server, event, logger)
|
|
549
|
+
captureEvent(server, event, logger, requestAttribution)
|
|
525
550
|
throw error
|
|
526
551
|
}
|
|
527
552
|
}
|
|
@@ -630,9 +655,10 @@ function mintStatelessSessionOnInitialize(
|
|
|
630
655
|
*/
|
|
631
656
|
function upgradeMintedTokenToNegotiated(
|
|
632
657
|
server: MCPServerLike,
|
|
633
|
-
data: MCPAnalyticsData,
|
|
634
658
|
mintedSessionId: string,
|
|
635
|
-
|
|
659
|
+
sessionInfo: SessionInfo,
|
|
660
|
+
negotiatedProtocolVersion: string | undefined,
|
|
661
|
+
logger: LoggerFn
|
|
636
662
|
): void {
|
|
637
663
|
try {
|
|
638
664
|
const transport = server.transport
|
|
@@ -641,13 +667,13 @@ function upgradeMintedTokenToNegotiated(
|
|
|
641
667
|
}
|
|
642
668
|
const token = encodeSessionId({
|
|
643
669
|
sessionId: mintedSessionId,
|
|
644
|
-
clientName:
|
|
645
|
-
clientVersion:
|
|
670
|
+
clientName: sessionInfo.clientName,
|
|
671
|
+
clientVersion: sessionInfo.clientVersion,
|
|
646
672
|
protocolVersion: negotiatedProtocolVersion,
|
|
647
673
|
})
|
|
648
674
|
writeSessionIdToTransport(transport, token)
|
|
649
675
|
} catch (error) {
|
|
650
|
-
|
|
676
|
+
logger(`Warning: PostHog MCP analytics failed to upgrade the stateless session token - ${error}`)
|
|
651
677
|
}
|
|
652
678
|
}
|
|
653
679
|
|
|
@@ -689,7 +715,17 @@ export async function handleInitializeRequest(
|
|
|
689
715
|
// Mint first so the `$mcp_initialize` event below already carries the minted id.
|
|
690
716
|
const mintedSessionId = mintStatelessSessionOnInitialize(server, data, request, extra)
|
|
691
717
|
const sessionId = getSessionId(server, extra)
|
|
692
|
-
|
|
718
|
+
// Snapshot before identify, metadata, or the initialize handler can yield to
|
|
719
|
+
// another request using the same instrumented server.
|
|
720
|
+
const sessionInfo = getSessionInfo(server, data, sessionId)
|
|
721
|
+
const initializeClientInfo = readInitializeClientInfo(request)
|
|
722
|
+
const requestSessionInfo: SessionInfo = {
|
|
723
|
+
...sessionInfo,
|
|
724
|
+
clientName: initializeClientInfo?.name ?? sessionInfo.clientName,
|
|
725
|
+
clientVersion: initializeClientInfo?.version ?? sessionInfo.clientVersion,
|
|
726
|
+
}
|
|
727
|
+
const identity = await handleIdentify(server, data, sessionId, request, requestSessionInfo, extra)
|
|
728
|
+
const requestAttribution = withIdentity(requestSessionInfo, identity)
|
|
693
729
|
|
|
694
730
|
const event: McpEvent = {
|
|
695
731
|
sessionId,
|
|
@@ -712,12 +748,19 @@ export async function handleInitializeRequest(
|
|
|
712
748
|
// re-mint the token so pods replaying it report the negotiated version too.
|
|
713
749
|
const negotiatedProtocolVersion = readProtocolVersion(result, request)
|
|
714
750
|
event.protocolVersion = negotiatedProtocolVersion
|
|
715
|
-
|
|
716
|
-
|
|
751
|
+
// Do not let a delayed initialize overwrite whichever session became current
|
|
752
|
+
// while its callbacks or the original handler were awaiting.
|
|
753
|
+
if (data.sessionId === sessionId) {
|
|
754
|
+
data.sessionInfo = { ...data.sessionInfo, protocolVersion: negotiatedProtocolVersion }
|
|
755
|
+
setServerTrackingData(server, data)
|
|
756
|
+
}
|
|
717
757
|
if (mintedSessionId) {
|
|
718
|
-
upgradeMintedTokenToNegotiated(server,
|
|
758
|
+
upgradeMintedTokenToNegotiated(server, mintedSessionId, requestSessionInfo, negotiatedProtocolVersion, data.logger)
|
|
719
759
|
}
|
|
720
|
-
captureEvent(server, event, data.logger
|
|
760
|
+
captureEvent(server, event, data.logger, {
|
|
761
|
+
...requestAttribution,
|
|
762
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
763
|
+
})
|
|
721
764
|
return result
|
|
722
765
|
}
|
|
723
766
|
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
MCPRequestLike,
|
|
10
10
|
MCPServerLike,
|
|
11
11
|
McpEvent,
|
|
12
|
+
SessionInfo,
|
|
12
13
|
UserIdentity,
|
|
13
14
|
} from '../types'
|
|
14
15
|
import { MCPAnalyticsEventType } from './event-types'
|
|
@@ -128,10 +129,13 @@ export async function handleIdentify(
|
|
|
128
129
|
data: MCPAnalyticsData,
|
|
129
130
|
sessionId: string,
|
|
130
131
|
request: MCPRequestLike,
|
|
132
|
+
requestAttribution: SessionInfo,
|
|
131
133
|
extra?: CompatibleRequestHandlerExtra
|
|
132
|
-
): Promise<
|
|
134
|
+
): Promise<UserIdentity | undefined> {
|
|
135
|
+
const identityBeforeRequest = data.identifiedSessions.get(sessionId)
|
|
136
|
+
const sessionSourceBeforeIdentify = data.sessionSource
|
|
133
137
|
if (!data.options.identify) {
|
|
134
|
-
return
|
|
138
|
+
return identityBeforeRequest
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
const identifyEvent: McpEvent = {
|
|
@@ -162,23 +166,34 @@ export async function handleIdentify(
|
|
|
162
166
|
// announced); revisit with the stateless-by-default rework.
|
|
163
167
|
const changed = previousIdentity !== undefined && !areIdentitiesEqual(previousIdentity, mergedIdentity)
|
|
164
168
|
const firstSeen = previousIdentity === undefined
|
|
165
|
-
const announcedAtInitialize =
|
|
169
|
+
const announcedAtInitialize = sessionSourceBeforeIdentify === 'token' && request.method !== 'initialize'
|
|
166
170
|
const shouldPublish = changed || (firstSeen && !announcedAtInitialize)
|
|
167
171
|
|
|
168
172
|
data.identifiedSessions.set(sessionId, mergedIdentity)
|
|
169
173
|
|
|
170
174
|
if (shouldPublish) {
|
|
171
175
|
data.logger(`Identified session ${sessionId}`)
|
|
172
|
-
captureEvent(server, identifyEvent, data.logger)
|
|
176
|
+
captureEvent(server, identifyEvent, data.logger, withIdentity(requestAttribution, mergedIdentity))
|
|
173
177
|
}
|
|
174
|
-
|
|
175
|
-
data.logger(`Warning: Supplied identify function returned null for session ${sessionId}`)
|
|
178
|
+
return mergedIdentity
|
|
176
179
|
}
|
|
180
|
+
|
|
181
|
+
data.logger(`Warning: Supplied identify function returned null for session ${sessionId}`)
|
|
177
182
|
} catch (error) {
|
|
178
183
|
data.logger(
|
|
179
184
|
`Error: User supplied identify function threw an error while identifying session ${sessionId} - ${error}`
|
|
180
185
|
)
|
|
181
186
|
}
|
|
187
|
+
return identityBeforeRequest
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function withIdentity(sessionInfo: SessionInfo, identity: UserIdentity | undefined): SessionInfo {
|
|
191
|
+
return {
|
|
192
|
+
...sessionInfo,
|
|
193
|
+
identifyActorGivenId: identity?.distinctId,
|
|
194
|
+
identifyActorData: identity?.properties || {},
|
|
195
|
+
identifyActorGroups: identity?.groups,
|
|
196
|
+
}
|
|
182
197
|
}
|
|
183
198
|
|
|
184
199
|
/**
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '0.10.
|
|
1
|
+
export const version = '0.10.7'
|