@posthog/mcp 0.10.5 → 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.
Files changed (44) hide show
  1. package/dist/extensions/capture.d.ts +2 -2
  2. package/dist/extensions/capture.d.ts.map +1 -1
  3. package/dist/extensions/capture.js +2 -2
  4. package/dist/extensions/capture.mjs +2 -2
  5. package/dist/extensions/context-parameters.d.ts.map +1 -1
  6. package/dist/extensions/context-parameters.js +1 -4
  7. package/dist/extensions/context-parameters.mjs +1 -4
  8. package/dist/extensions/conversation-id.d.ts +1 -1
  9. package/dist/extensions/conversation-id.d.ts.map +1 -1
  10. package/dist/extensions/conversation-id.js +2 -2
  11. package/dist/extensions/conversation-id.mjs +2 -2
  12. package/dist/extensions/instrument-highlevel.d.ts.map +1 -1
  13. package/dist/extensions/instrument-highlevel.js +5 -5
  14. package/dist/extensions/instrument-highlevel.mjs +6 -6
  15. package/dist/extensions/instrument-lowlevel.d.ts.map +1 -1
  16. package/dist/extensions/instrument-lowlevel.js +9 -4
  17. package/dist/extensions/instrument-lowlevel.mjs +10 -5
  18. package/dist/extensions/instrumentation.d.ts +6 -6
  19. package/dist/extensions/instrumentation.d.ts.map +1 -1
  20. package/dist/extensions/instrumentation.js +112 -42
  21. package/dist/extensions/instrumentation.mjs +110 -43
  22. package/dist/extensions/intent.d.ts.map +1 -1
  23. package/dist/extensions/intent.js +1 -1
  24. package/dist/extensions/intent.mjs +1 -1
  25. package/dist/extensions/internal.d.ts +3 -2
  26. package/dist/extensions/internal.d.ts.map +1 -1
  27. package/dist/extensions/internal.js +25 -9
  28. package/dist/extensions/internal.mjs +19 -6
  29. package/dist/extensions/session.js +3 -1
  30. package/dist/extensions/session.mjs +3 -1
  31. package/dist/version.d.ts +1 -1
  32. package/dist/version.js +1 -1
  33. package/dist/version.mjs +1 -1
  34. package/package.json +2 -2
  35. package/src/extensions/capture.ts +13 -6
  36. package/src/extensions/context-parameters.ts +1 -7
  37. package/src/extensions/conversation-id.ts +2 -1
  38. package/src/extensions/instrument-highlevel.ts +8 -13
  39. package/src/extensions/instrument-lowlevel.ts +17 -6
  40. package/src/extensions/instrumentation.ts +166 -51
  41. package/src/extensions/intent.ts +1 -6
  42. package/src/extensions/internal.ts +21 -6
  43. package/src/extensions/session.ts +1 -1
  44. package/src/version.ts +1 -1
@@ -23,7 +23,7 @@ async function runIntentFallback(data, request, extra) {
23
23
  }
24
24
  async function resolveToolCallIntent(data, request, analyticsOwnsContext, extra) {
25
25
  const contextArgument = getContextArgument(request);
26
- if (analyticsOwnsContext && isContextEnabled(data.options.context) && request.params?.name !== 'get_more_tools' && contextArgument) return {
26
+ if (analyticsOwnsContext && isContextEnabled(data.options.context) && contextArgument) return {
27
27
  intent: contextArgument,
28
28
  source: 'context_parameter'
29
29
  };
@@ -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<void>;
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,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,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
- 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) {
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 = '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) {
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 = '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 };
@@ -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 data.sessionInfo;
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 data.sessionInfo;
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.5";
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.5';
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.5';
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.5",
3
+ "version": "0.10.7",
4
4
  "bugs": {
5
5
  "url": "https://github.com/PostHog/posthog-js/issues"
6
6
  },
@@ -40,7 +40,7 @@
40
40
  "jest": "29.7.0",
41
41
  "zod": "^3.25.0",
42
42
  "@posthog-tooling/tsconfig-base": "1.1.1",
43
- "posthog-node": "^5.47.8"
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(server: MCPServerLike, eventInput: McpEvent, logger: LoggerFn): Promise<void> | undefined {
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
- const sessionInfo = getSessionInfo(server, data, eventInput.sessionId)
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 identity the event captured for *this* request (from `_meta`, see
52
- // stampMetaClientInfo) over the server-wide sessionInfo, so a concurrent
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,
@@ -108,11 +108,5 @@ export function addContextParameterToTools<TTool extends ContextInjectableTool>(
108
108
  contextDescriptionOverride?: string,
109
109
  logger: LoggerFn = log
110
110
  ): TTool[] {
111
- return tools.map((tool) => {
112
- // Skip get_more_tools - it has its own special context parameter
113
- if (tool.name === 'get_more_tools') {
114
- return tool
115
- }
116
- return addContextParameterToTool(tool, contextDescriptionOverride, logger)
117
- })
111
+ return tools.map((tool) => addContextParameterToTool(tool, contextDescriptionOverride, logger))
118
112
  }
@@ -73,10 +73,11 @@ export function addConversationIdToTool<TTool extends ConversationIdInjectableTo
73
73
  export function addConversationIdToTools<TTool extends ConversationIdInjectableTool>(
74
74
  tools: TTool[],
75
75
  missingCapabilityToolName: string = GET_MORE_TOOLS_NAME,
76
+ skipMissingCapabilityTool = true,
76
77
  logger: LoggerFn = log
77
78
  ): TTool[] {
78
79
  return tools.map((tool) => {
79
- if (tool.name === missingCapabilityToolName) {
80
+ if (skipMissingCapabilityTool && tool.name === missingCapabilityToolName) {
80
81
  return tool
81
82
  }
82
83
  return addConversationIdToTool(tool, logger)
@@ -27,6 +27,7 @@ import {
27
27
  handleListToolsRequest,
28
28
  patchRequestHandlers,
29
29
  captureToolCall,
30
+ isToolAdvertised,
30
31
  readToolMetaCategory,
31
32
  type HandlerPatch,
32
33
  } from './instrumentation'
@@ -162,8 +163,6 @@ function addTracingToToolCallbackInternal(
162
163
  ): RegisteredTool {
163
164
  const originalCallback = getToolFunction(tool)
164
165
  const options = getServerTrackingData(server.server as MCPServerLike)?.options
165
- const missingToolName = resolveMissingCapabilityToolName(options)
166
-
167
166
  if (wrappedCallbacks.has(originalCallback)) {
168
167
  logger(`Tool ${toolName} callback already wrapped, skipping re-wrap`)
169
168
  return tool
@@ -188,16 +187,9 @@ function addTracingToToolCallbackInternal(
188
187
 
189
188
  const inputSchema = getCurrentInputSchema()
190
189
  const cleanedArgs = stripOwnedAnalyticsArguments(args, {
191
- context:
192
- toolName !== missingToolName &&
193
- isContextEnabled(options?.context) &&
194
- analyticsOwnsParameter(inputSchema, 'context'),
195
- conversationId:
196
- toolName !== missingToolName &&
197
- options?.enableConversationId === true &&
198
- analyticsOwnsParameter(inputSchema, 'conversation_id'),
190
+ context: isContextEnabled(options?.context) && analyticsOwnsParameter(inputSchema, 'context'),
191
+ conversationId: options?.enableConversationId === true && analyticsOwnsParameter(inputSchema, 'conversation_id'),
199
192
  })
200
-
201
193
  try {
202
194
  if (cleanedArgs === undefined) {
203
195
  const handler = originalCallback as (extra: CompatibleRequestHandlerExtra) => Promise<CallToolResult>
@@ -242,7 +234,11 @@ async function handleToolCallRequest(
242
234
  return await originalCallToolHandler(request, extra)
243
235
  }
244
236
 
245
- if (request.params?.name === resolveMissingCapabilityToolName(data.options)) {
237
+ const toolName = request.params?.name
238
+ const isMissingCapabilityCandidate =
239
+ data.options.reportMissing && toolName === resolveMissingCapabilityToolName(data.options)
240
+
241
+ if (isMissingCapabilityCandidate && (await isToolAdvertised(server, toolName, extra, data.logger)) === false) {
246
242
  const context = getContextArgument(request) || ''
247
243
  return await captureToolCall({
248
244
  server,
@@ -255,7 +251,6 @@ async function handleToolCallRequest(
255
251
  })
256
252
  }
257
253
 
258
- const toolName = request.params?.name
259
254
  const registeredTool = toolName ? highLevelServer._registeredTools[toolName] : undefined
260
255
 
261
256
  // Strip SDK-owned arguments before the MCP SDK validates the registered Zod
@@ -14,6 +14,7 @@ import {
14
14
  handleListToolsRequest,
15
15
  patchRequestHandlers,
16
16
  captureToolCall,
17
+ isToolAdvertised,
17
18
  type HandlerPatch,
18
19
  } from './instrumentation'
19
20
  import { getContextArgument } from './tracing-helpers'
@@ -32,19 +33,25 @@ type MCPRequestExtra = Parameters<MCPRequestHandler>[1]
32
33
  export function instrumentLowLevelServer(server: MCPServerLike, logger: LoggerFn): void {
33
34
  try {
34
35
  // Patch already existing handlers, and patch setRequestHandler to capture dynamically created handlers.
36
+ const hadCallToolHandler = server._requestHandlers.has('tools/call')
35
37
  const handlers: Record<string, HandlerPatch> = {
36
38
  initialize: (server, originalHandler, request, extra) =>
37
39
  handleInitializeRequest(server, originalHandler, request, extra, logger),
38
40
  'tools/list': (server, originalHandler, request, extra) =>
39
41
  handleListToolsRequest(server, originalHandler, request, extra, logger),
42
+ 'tools/call': (server, originalHandler, request, extra) =>
43
+ handleToolCallRequest(server, originalHandler, request, extra, logger),
40
44
  }
41
45
  patchRequestHandlers(server, handlers)
42
46
 
43
- const originalCallToolHandler = server._requestHandlers.get('tools/call')
44
- server.setRequestHandler(
45
- CallToolRequestSchema,
46
- async (request, extra) => await handleToolCallRequest(server, originalCallToolHandler, request, extra, logger)
47
- )
47
+ if (!hadCallToolHandler) {
48
+ // Register a raw fallback through the patched setter so reportMissing works
49
+ // even before an application dispatcher is attached. A later registration
50
+ // replaces it and is wrapped by patchRequestHandlers.
51
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
52
+ throw new Error(`Unknown tool: ${request.params?.name || 'unknown'}`)
53
+ })
54
+ }
48
55
  } catch (error) {
49
56
  logger(`Warning: Failed to setup tool call instrumentation - ${error}`)
50
57
  throw error
@@ -66,7 +73,11 @@ async function handleToolCallRequest(
66
73
  return await originalCallToolHandler?.(request, extra)
67
74
  }
68
75
 
69
- if (request.params?.name === resolveMissingCapabilityToolName(data.options)) {
76
+ const toolName = request.params?.name
77
+ const isMissingCapabilityCandidate =
78
+ data.options.reportMissing && toolName === resolveMissingCapabilityToolName(data.options)
79
+
80
+ if (isMissingCapabilityCandidate && (await isToolAdvertised(server, toolName, extra, data.logger)) === false) {
70
81
  const context = getContextArgument(request) || ''
71
82
  return await captureToolCall({
72
83
  server,