@posthog/mcp 0.10.6 → 0.10.8

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.
Files changed (46) hide show
  1. package/dist/extensions/analytics-parameters.d.ts +7 -2
  2. package/dist/extensions/analytics-parameters.d.ts.map +1 -1
  3. package/dist/extensions/analytics-parameters.js +4 -2
  4. package/dist/extensions/analytics-parameters.mjs +4 -2
  5. package/dist/extensions/capture.d.ts +2 -2
  6. package/dist/extensions/capture.d.ts.map +1 -1
  7. package/dist/extensions/capture.js +2 -2
  8. package/dist/extensions/capture.mjs +2 -2
  9. package/dist/extensions/conversation-id.d.ts +37 -8
  10. package/dist/extensions/conversation-id.d.ts.map +1 -1
  11. package/dist/extensions/conversation-id.js +8 -13
  12. package/dist/extensions/conversation-id.mjs +8 -13
  13. package/dist/extensions/instrument-highlevel.d.ts.map +1 -1
  14. package/dist/extensions/instrument-highlevel.js +1 -1
  15. package/dist/extensions/instrument-highlevel.mjs +1 -1
  16. package/dist/extensions/instrumentation.d.ts.map +1 -1
  17. package/dist/extensions/instrumentation.js +86 -42
  18. package/dist/extensions/instrumentation.mjs +88 -44
  19. package/dist/extensions/internal.d.ts +10 -2
  20. package/dist/extensions/internal.d.ts.map +1 -1
  21. package/dist/extensions/internal.js +25 -9
  22. package/dist/extensions/internal.mjs +19 -6
  23. package/dist/extensions/output-instructions.d.ts +70 -0
  24. package/dist/extensions/output-instructions.d.ts.map +1 -0
  25. package/dist/extensions/output-instructions.js +131 -0
  26. package/dist/extensions/output-instructions.mjs +82 -0
  27. package/dist/extensions/session.d.ts +26 -5
  28. package/dist/extensions/session.d.ts.map +1 -1
  29. package/dist/extensions/session.js +35 -15
  30. package/dist/extensions/session.mjs +33 -16
  31. package/dist/types.d.ts +23 -1
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/version.d.ts +1 -1
  34. package/dist/version.js +1 -1
  35. package/dist/version.mjs +1 -1
  36. package/package.json +3 -3
  37. package/src/extensions/analytics-parameters.ts +15 -2
  38. package/src/extensions/capture.ts +13 -6
  39. package/src/extensions/conversation-id.ts +51 -29
  40. package/src/extensions/instrument-highlevel.ts +3 -1
  41. package/src/extensions/instrumentation.ts +151 -60
  42. package/src/extensions/internal.ts +30 -7
  43. package/src/extensions/output-instructions.ts +231 -0
  44. package/src/extensions/session.ts +128 -29
  45. package/src/types.ts +23 -1
  46. package/src/version.ts +1 -1
@@ -2,14 +2,15 @@ import { getAnalyticsParameterOwnership, stripOwnedAnalyticsArguments } from "./
2
2
  import { addContextParameterToTools, getContextDescription, isContextEnabled } from "./context-parameters.mjs";
3
3
  import { addConversationIdToTools, canInjectConversationIdPromptBack, injectConversationIdPromptBack, resolveConversationId } from "./conversation-id.mjs";
4
4
  import { stampMetaClientInfo } from "./client-identity.mjs";
5
+ import { addInstructionsToOutputSchemas, mirrorInstructionsIntoStructuredContent } from "./output-instructions.mjs";
5
6
  import { captureEvent } from "./capture.mjs";
6
7
  import { MCPAnalyticsEventType } from "./event-types.mjs";
7
8
  import { captureException } from "./exceptions.mjs";
8
9
  import { resolveToolCallIntent, setEventIntent, setExplicitContextIntent } from "./intent.mjs";
9
- import { getServerTrackingData, handleIdentify, setServerTrackingData } from "./internal.mjs";
10
+ import { getServerTrackingData, handleIdentify, setServerTrackingData, withIdentity } from "./internal.mjs";
10
11
  import { buildCapturedMcpParameters } from "./mcp-payloads.mjs";
11
12
  import { getLiteralValue, getObjectShape } from "./mcp-sdk-compat.mjs";
12
- import { getSessionId, newSessionId } from "./session.mjs";
13
+ import { getSessionId, getSessionInfo, newSessionId } from "./session.mjs";
13
14
  import { encodeSessionId, readMcpSessionHeader, writeSessionIdToTransport } from "./session-token.mjs";
14
15
  import { getReportMissingToolDescriptor, resolveMissingCapabilityToolName } from "./tools.mjs";
15
16
  import { applyResolvedMetadata, isToolResultError } from "./tracing-helpers.mjs";
@@ -17,27 +18,29 @@ async function captureToolCall(params) {
17
18
  const { server, data, request, extra, execute, parameterOwnership, eventType, explicitContextIntent, takeCapturedError } = params;
18
19
  const resolvedEventType = eventType ?? MCPAnalyticsEventType.mcpToolsCall;
19
20
  const ownership = getActiveAnalyticsParameterOwnership(data, request.params?.name, parameterOwnership, resolvedEventType === MCPAnalyticsEventType.mcpMissingCapability);
20
- const conversation = resolveConversationId(ownership.conversationId, request.params?.arguments, request.params?.name, resolvedEventType === MCPAnalyticsEventType.mcpMissingCapability ? resolveMissingCapabilityToolName(data.options) : '');
21
+ const conversation = resolveConversationId(ownership.conversationId, request.params?.arguments);
21
22
  const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership);
22
23
  const startTime = new Date();
23
- const event = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType);
24
- if (event && explicitContextIntent) setExplicitContextIntent(event, explicitContextIntent);
24
+ const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType);
25
+ if (preparedEvent && explicitContextIntent) setExplicitContextIntent(preparedEvent.event, explicitContextIntent);
25
26
  let result;
26
27
  try {
27
28
  result = await execute(downstreamRequest);
28
29
  } catch (error) {
29
- publishFailedToolEvent(server, event, error, startTime, conversation, data.logger);
30
+ publishFailedToolEvent(server, preparedEvent, error, startTime, conversation, data.logger);
30
31
  throw error;
31
32
  }
32
- const finalResult = applyConversationPromptBack(event, result, conversation);
33
- publishSuccessfulToolEvent(server, event, finalResult, startTime, data.logger, takeCapturedError);
33
+ const finalResult = applyConversationInstructions(preparedEvent?.event ?? null, result, conversation, ownership);
34
+ publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError);
34
35
  return finalResult;
35
36
  }
36
37
  function getActiveAnalyticsParameterOwnership(data, toolName, override, isMissingCapabilityTool) {
37
- const ownership = override ?? (toolName ? data.toolAnalyticsParameterOwnership.get(toolName) : void 0);
38
+ const listed = toolName ? data.toolAnalyticsParameterOwnership.get(toolName) : void 0;
39
+ const ownership = override ?? listed;
38
40
  return {
39
41
  context: !isMissingCapabilityTool && isContextEnabled(data.options.context) && ownership?.context === true,
40
- conversationId: !isMissingCapabilityTool && true === data.options.enableConversationId && ownership?.conversationId === true
42
+ conversationId: true === data.options.enableConversationId && ownership?.conversationId === true,
43
+ outputInstructions: true === data.options.enableConversationId && listed?.outputInstructions === true
41
44
  };
42
45
  }
43
46
  function cloneRequestWithoutOwnedAnalyticsArguments(request, ownership) {
@@ -54,8 +57,10 @@ function cloneRequestWithoutOwnedAnalyticsArguments(request, ownership) {
54
57
  }
55
58
  async function prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, eventType) {
56
59
  try {
57
- const sessionId = getSessionId(server, extra);
58
- await handleIdentify(server, data, sessionId, request, extra);
60
+ const sessionId = getSessionId(server, extra, conversation.conversationId);
61
+ const sessionInfo = getSessionInfo(server, data, sessionId);
62
+ const identity = await handleIdentify(server, data, sessionId, request, sessionInfo, extra, !!conversation.conversationId);
63
+ const requestAttribution = withIdentity(sessionInfo, identity);
59
64
  const toolName = request.params?.name;
60
65
  const event = {
61
66
  sessionId,
@@ -70,20 +75,35 @@ async function prepareToolCallEvent(server, data, request, downstreamRequest, ex
70
75
  stampMetaClientInfo(event, request);
71
76
  await applyResolvedMetadata(event, data, request, extra);
72
77
  setEventIntent(event, await resolveToolCallIntent(data, request, ownership.context, extra));
73
- return event;
78
+ return {
79
+ event,
80
+ requestAttribution
81
+ };
74
82
  } catch (error) {
75
83
  data.logger(`Warning: PostHog MCP analytics instrumentation failed for tool ${request.params?.name}, the tool will still run - ${error}`);
76
84
  return null;
77
85
  }
78
86
  }
79
- function applyConversationPromptBack(event, result, conversation) {
80
- if (!conversation.minted) return result;
81
- if (canInjectConversationIdPromptBack(result)) return injectConversationIdPromptBack(result, conversation.conversationId);
82
- if (event) event.conversationId = void 0;
83
- return result;
87
+ function applyConversationInstructions(event, result, conversation, ownership) {
88
+ const conversationId = conversation.conversationId;
89
+ if (!conversationId) return result;
90
+ let updated = result;
91
+ let delivered = false;
92
+ if (ownership.outputInstructions) {
93
+ const mirrored = mirrorInstructionsIntoStructuredContent(updated, conversationId);
94
+ delivered = mirrored !== updated;
95
+ updated = mirrored;
96
+ }
97
+ if (conversation.minted && canInjectConversationIdPromptBack(updated)) {
98
+ updated = injectConversationIdPromptBack(updated, conversationId);
99
+ delivered = true;
100
+ }
101
+ if (!delivered && conversation.minted && event) event.conversationId = void 0;
102
+ return updated;
84
103
  }
85
- function publishSuccessfulToolEvent(server, event, result, startTime, logger, takeCapturedError) {
86
- if (!event) return;
104
+ function publishSuccessfulToolEvent(server, preparedEvent, result, startTime, logger, takeCapturedError) {
105
+ if (!preparedEvent) return;
106
+ const { event, requestAttribution } = preparedEvent;
87
107
  try {
88
108
  if (isToolResultError(result)) {
89
109
  event.isError = true;
@@ -92,19 +112,20 @@ function publishSuccessfulToolEvent(server, event, result, startTime, logger, ta
92
112
  } else event.isError = false;
93
113
  event.response = result;
94
114
  event.duration = Date.now() - startTime.getTime();
95
- captureEvent(server, event, logger);
115
+ captureEvent(server, event, logger, requestAttribution);
96
116
  } catch (error) {
97
117
  logger(`Warning: PostHog MCP analytics failed to publish tool event - ${error}`);
98
118
  }
99
119
  }
100
- function publishFailedToolEvent(server, event, error, startTime, conversation, logger) {
101
- if (!event) return;
120
+ function publishFailedToolEvent(server, preparedEvent, error, startTime, conversation, logger) {
121
+ if (!preparedEvent) return;
122
+ const { event, requestAttribution } = preparedEvent;
102
123
  try {
103
124
  if (conversation.minted) event.conversationId = void 0;
104
125
  event.isError = true;
105
126
  event.error = captureException(error);
106
127
  event.duration = Date.now() - startTime.getTime();
107
- captureEvent(server, event, logger);
128
+ captureEvent(server, event, logger, requestAttribution);
108
129
  } catch (publishError) {
109
130
  logger(`Warning: PostHog MCP analytics failed to publish failed tool event - ${publishError}`);
110
131
  }
@@ -159,15 +180,17 @@ async function isToolAdvertised(server, toolName, extra, logger) {
159
180
  async function handleListToolsRequest(server, originalListToolsHandler, request, extra, logger) {
160
181
  const data = getServerTrackingData(server);
161
182
  const startTime = new Date();
183
+ const sessionId = getSessionId(server, extra);
184
+ const requestAttribution = getSessionInfo(server, data, sessionId);
162
185
  const event = {
163
- sessionId: getSessionId(server, extra),
186
+ sessionId,
164
187
  parameters: buildCapturedMcpParameters(request),
165
188
  eventType: MCPAnalyticsEventType.mcpToolsList,
166
189
  timestamp: startTime
167
190
  };
168
191
  stampMetaClientInfo(event, request);
169
192
  if (data) await applyResolvedMetadata(event, data, request, extra);
170
- const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger);
193
+ const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger, requestAttribution);
171
194
  if (!data) {
172
195
  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
196
  return {
@@ -179,7 +202,7 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
179
202
  event.error = captureException('No tools were sent to MCP client.');
180
203
  event.isError = true;
181
204
  event.duration = Date.now() - startTime.getTime();
182
- captureEvent(server, event, data.logger);
205
+ captureEvent(server, event, data.logger, requestAttribution);
183
206
  return {
184
207
  tools
185
208
  };
@@ -190,20 +213,20 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
190
213
  event.listedToolNames = collectListedToolNames(tools);
191
214
  event.isError = false;
192
215
  event.duration = Date.now() - startTime.getTime();
193
- captureEvent(server, event, data.logger);
216
+ captureEvent(server, event, data.logger, requestAttribution);
194
217
  return {
195
218
  tools
196
219
  };
197
220
  }
198
221
  function cacheToolAnalyticsParameterOwnership(cache, tools) {
199
- for (const tool of tools)if (tool?.name) cache.set(tool.name, getAnalyticsParameterOwnership(tool.inputSchema));
222
+ for (const tool of tools)if (tool?.name) cache.set(tool.name, getAnalyticsParameterOwnership(tool.inputSchema, tool.outputSchema));
200
223
  }
201
224
  function collectListedToolNames(tools) {
202
225
  if (!tools || 0 === tools.length) return;
203
226
  const names = tools.map((tool)=>tool?.name).filter((name)=>'string' == typeof name);
204
227
  return names.length > 0 ? names : void 0;
205
228
  }
206
- async function getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger) {
229
+ async function getTracedToolsList(server, originalListToolsHandler, request, extra, event, logger, requestAttribution) {
207
230
  try {
208
231
  const data = getServerTrackingData(server);
209
232
  const originalResponse = await originalListToolsHandler(request, extra);
@@ -214,16 +237,21 @@ async function getTracedToolsList(server, originalListToolsHandler, request, ext
214
237
  if (data && isContextEnabled(data.options.context)) tools = addContextParameterToTools(tools, getContextDescription(data.options.context), data.logger);
215
238
  if (data) {
216
239
  const missingToolName = resolveMissingCapabilityToolName(data.options);
217
- let injectedMissingCapabilityTool = false;
218
240
  if (data.options.reportMissing) {
219
241
  const alreadyPresent = tools.some((tool)=>tool?.name === missingToolName);
220
242
  if (alreadyPresent) data.logger(`Warning: Cannot inject missing-capability tool "${missingToolName}" because a real tool already uses that name. The real tool will not be intercepted.`);
221
243
  else {
222
- tools.push(getReportMissingToolDescriptor(missingToolName));
223
- injectedMissingCapabilityTool = true;
244
+ const virtualTool = getReportMissingToolDescriptor(missingToolName);
245
+ tools.push(virtualTool);
246
+ cacheToolAnalyticsParameterOwnership(data.toolAnalyticsParameterOwnership, [
247
+ virtualTool
248
+ ]);
224
249
  }
225
250
  }
226
- if (data.options.enableConversationId) tools = addConversationIdToTools(tools, missingToolName, injectedMissingCapabilityTool, data.logger);
251
+ if (data.options.enableConversationId) {
252
+ tools = addConversationIdToTools(tools, data.logger);
253
+ tools = addInstructionsToOutputSchemas(tools, data.logger);
254
+ }
227
255
  }
228
256
  if (data) {
229
257
  cacheToolDescriptions(data.toolDescriptions, tools);
@@ -235,7 +263,7 @@ async function getTracedToolsList(server, originalListToolsHandler, request, ext
235
263
  event.error = captureException(error);
236
264
  event.isError = true;
237
265
  event.duration = event.timestamp ? Date.now() - event.timestamp.getTime() : 0;
238
- captureEvent(server, event, logger);
266
+ captureEvent(server, event, logger, requestAttribution);
239
267
  throw error;
240
268
  }
241
269
  }
@@ -284,19 +312,19 @@ function mintStatelessSessionOnInitialize(server, data, request, extra) {
284
312
  return;
285
313
  }
286
314
  }
287
- function upgradeMintedTokenToNegotiated(server, data, mintedSessionId, negotiatedProtocolVersion) {
315
+ function upgradeMintedTokenToNegotiated(server, mintedSessionId, sessionInfo, negotiatedProtocolVersion, logger) {
288
316
  try {
289
317
  const transport = server.transport;
290
318
  if (!transport) return;
291
319
  const token = encodeSessionId({
292
320
  sessionId: mintedSessionId,
293
- clientName: data.sessionInfo.clientName,
294
- clientVersion: data.sessionInfo.clientVersion,
321
+ clientName: sessionInfo.clientName,
322
+ clientVersion: sessionInfo.clientVersion,
295
323
  protocolVersion: negotiatedProtocolVersion
296
324
  });
297
325
  writeSessionIdToTransport(transport, token);
298
326
  } catch (error) {
299
- data.logger(`Warning: PostHog MCP analytics failed to upgrade the stateless session token - ${error}`);
327
+ logger(`Warning: PostHog MCP analytics failed to upgrade the stateless session token - ${error}`);
300
328
  }
301
329
  }
302
330
  function readInitializeClientInfo(request) {
@@ -316,7 +344,15 @@ async function handleInitializeRequest(server, originalInitializeHandler, reques
316
344
  }
317
345
  const mintedSessionId = mintStatelessSessionOnInitialize(server, data, request, extra);
318
346
  const sessionId = getSessionId(server, extra);
319
- await handleIdentify(server, data, sessionId, request, extra);
347
+ const sessionInfo = getSessionInfo(server, data, sessionId);
348
+ const initializeClientInfo = readInitializeClientInfo(request);
349
+ const requestSessionInfo = {
350
+ ...sessionInfo,
351
+ clientName: initializeClientInfo?.name ?? sessionInfo.clientName,
352
+ clientVersion: initializeClientInfo?.version ?? sessionInfo.clientVersion
353
+ };
354
+ const identity = await handleIdentify(server, data, sessionId, request, requestSessionInfo, extra);
355
+ const requestAttribution = withIdentity(requestSessionInfo, identity);
320
356
  const event = {
321
357
  sessionId,
322
358
  resourceName: request.params?.name || 'Unknown Tool Name',
@@ -330,10 +366,18 @@ async function handleInitializeRequest(server, originalInitializeHandler, reques
330
366
  event.response = result;
331
367
  const negotiatedProtocolVersion = readProtocolVersion(result, request);
332
368
  event.protocolVersion = negotiatedProtocolVersion;
333
- data.sessionInfo.protocolVersion = negotiatedProtocolVersion;
334
- setServerTrackingData(server, data);
335
- if (mintedSessionId) upgradeMintedTokenToNegotiated(server, data, mintedSessionId, negotiatedProtocolVersion);
336
- captureEvent(server, event, data.logger);
369
+ if (data.sessionId === sessionId) {
370
+ data.sessionInfo = {
371
+ ...data.sessionInfo,
372
+ protocolVersion: negotiatedProtocolVersion
373
+ };
374
+ setServerTrackingData(server, data);
375
+ }
376
+ if (mintedSessionId) upgradeMintedTokenToNegotiated(server, mintedSessionId, requestSessionInfo, negotiatedProtocolVersion, data.logger);
377
+ captureEvent(server, event, data.logger, {
378
+ ...requestAttribution,
379
+ protocolVersion: negotiatedProtocolVersion
380
+ });
337
381
  return result;
338
382
  }
339
383
  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,15 @@ 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<void>;
27
+ export declare function handleIdentify(server: MCPServerLike, data: MCPAnalyticsData, sessionId: string, request: MCPRequestLike, requestAttribution: SessionInfo, extra?: CompatibleRequestHandlerExtra,
28
+ /**
29
+ * True when this session id came from an agent-carried `conversation_id`
30
+ * rather than the transport. Such a session is brand new and was never
31
+ * announced at `initialize`, whatever `data.sessionSource` still says about
32
+ * the connection.
33
+ */
34
+ sessionFromConversation?: boolean): Promise<UserIdentity | undefined>;
35
+ export declare function withIdentity(sessionInfo: SessionInfo, identity: UserIdentity | undefined): SessionInfo;
28
36
  /**
29
37
  * Resolves the `eventProperties` callback. Returns null when no callback is
30
38
  * 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,IAAI,CAAC,CAkDf;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"}
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;AACrC;;;;;GAKG;AACH,uBAAuB,UAAQ,GAC9B,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAuDnC;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
- getServerTrackingData: ()=>getServerTrackingData,
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
- if (!data.options.identify) return;
98
+ async function handleIdentify(server, data, sessionId, request, requestAttribution, extra, sessionFromConversation = false) {
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' === data.sessionSource && 'initialize' !== request.method;
120
+ const announcedAtInitialize = !sessionFromConversation && '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
- } else data.logger(`Warning: Supplied identify function returned null for session ${sessionId}`);
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
- if (!data.options.identify) return;
63
+ async function handleIdentify(server, data, sessionId, request, requestAttribution, extra, sessionFromConversation = false) {
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' === data.sessionSource && 'initialize' !== request.method;
85
+ const announcedAtInitialize = !sessionFromConversation && '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
- } else data.logger(`Warning: Supplied identify function returned null for session ${sessionId}`);
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 };
@@ -0,0 +1,70 @@
1
+ import type { LoggerFn } from './logger';
2
+ /**
3
+ * The `conversation_id` session handle reaches the agent as a text block appended to the
4
+ * tool result. That block is invisible to any client that reads
5
+ * `structuredContent` instead of `content` — which is what clients do whenever a
6
+ * tool declares an `outputSchema`. Measured against Claude Code, correlation for
7
+ * such tools drops to zero.
8
+ *
9
+ * The fix is to mirror the session handle into `structuredContent` as well, which this
10
+ * module does in two halves that must stay in that order:
11
+ *
12
+ * 1. declare `_mcp_instructions` on the tool's advertised `outputSchema`
13
+ * 2. write it into the result's `structuredContent`
14
+ *
15
+ * The declaration is what makes the write safe. The MCP client ajv-validates
16
+ * `structuredContent` against the schema from `tools/list`, and
17
+ * `zod-to-json-schema` emits `additionalProperties: false` for a plain
18
+ * `z.object`, so an undeclared key is not ignored — it fails the entire tool
19
+ * result. Only tools that got the declaration are ever written to.
20
+ */
21
+ export declare const MCP_INSTRUCTIONS_KEY = "_mcp_instructions";
22
+ export interface OutputInstructionsInjectableTool {
23
+ name?: string;
24
+ outputSchema?: unknown;
25
+ [key: string]: unknown;
26
+ }
27
+ /**
28
+ * True when we can safely declare {@link MCP_INSTRUCTIONS_KEY} on this tool's
29
+ * advertised output schema.
30
+ *
31
+ * Requires a plain-object schema we can extend. A tool with no `outputSchema`
32
+ * has nothing to mirror into and keeps working through `content`; a composed
33
+ * schema (`oneOf`/`allOf`/`anyOf`/`$ref`) has no single `properties` bag to add
34
+ * to. Both stay content-only, matching the policy on the input side.
35
+ */
36
+ export declare function canDeclareOutputInstructions(outputSchema: unknown): boolean;
37
+ /**
38
+ * Returns a copy of the tool with an optional {@link MCP_INSTRUCTIONS_KEY}
39
+ * property added to its output schema, or the tool untouched when it has no
40
+ * output schema to extend.
41
+ *
42
+ * The property is never added to `required` — a result without it must stay
43
+ * valid, since every tool result predating this change lacks it.
44
+ */
45
+ export declare function addInstructionsToOutputSchema<TTool extends OutputInstructionsInjectableTool>(tool: TTool, logger?: LoggerFn): TTool;
46
+ /**
47
+ * Declares {@link MCP_INSTRUCTIONS_KEY} across a tool listing. Tools without an
48
+ * output schema, and tools whose schema we cannot extend, pass through unchanged.
49
+ */
50
+ export declare function addInstructionsToOutputSchemas<TTool extends OutputInstructionsInjectableTool>(tools: TTool[], logger?: LoggerFn): TTool[];
51
+ export interface ConversationInstructions {
52
+ conversation_id: string;
53
+ instructions: string;
54
+ }
55
+ /** The payload mirrored into `structuredContent` for a tool we declared the key on. */
56
+ export declare function buildConversationInstructions(conversationId: string): ConversationInstructions;
57
+ /**
58
+ * Adds {@link MCP_INSTRUCTIONS_KEY} to a result's `structuredContent`, which is
59
+ * where clients look once a tool declares an `outputSchema` — the `content` text
60
+ * block carrying the session handle is invisible to them.
61
+ *
62
+ * Unlike the text block, this rides *every* response rather than only the one
63
+ * that minted the session handle, so an agent that drops it can read it back.
64
+ *
65
+ * Returns the result untouched when there is no plain-object `structuredContent`
66
+ * to extend, or when the tool already produced its own key — customer data wins.
67
+ * Never mutates the input.
68
+ */
69
+ export declare function mirrorInstructionsIntoStructuredContent(result: unknown, conversationId: string): unknown;
70
+ //# sourceMappingURL=output-instructions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-instructions.d.ts","sourceRoot":"","sources":["../../src/extensions/output-instructions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAIxC;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,oBAAoB,sBAAsB,CAAA;AAQvD,MAAM,WAAW,gCAAgC;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,CAgC3E;AAwCD;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,SAAS,gCAAgC,EAC1F,IAAI,EAAE,KAAK,EACX,MAAM,GAAE,QAAc,GACrB,KAAK,CA8CP;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,SAAS,gCAAgC,EAC3F,KAAK,EAAE,KAAK,EAAE,EACd,MAAM,GAAE,QAAc,GACrB,KAAK,EAAE,CAET;AAED,MAAM,WAAW,wBAAwB;IACvC,eAAe,EAAE,MAAM,CAAA;IACvB,YAAY,EAAE,MAAM,CAAA;CACrB;AAID,uFAAuF;AACvF,wBAAgB,6BAA6B,CAAC,cAAc,EAAE,MAAM,GAAG,wBAAwB,CAE9F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uCAAuC,CAAC,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAkBxG"}