@posthog/mcp 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"instrument-highlevel.d.ts","sourceRoot":"","sources":["../../src/extensions/instrument-highlevel.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAiC,sBAAsB,EAAiC,MAAM,UAAU,CAAA;AAmRpH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI,CAqB9E"}
1
+ {"version":3,"file":"instrument-highlevel.d.ts","sourceRoot":"","sources":["../../src/extensions/instrument-highlevel.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAiC,sBAAsB,EAAiC,MAAM,UAAU,CAAA;AAqPpH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI,CAwB9E"}
@@ -63,7 +63,6 @@ function setupListenerToRegisteredTools(server) {
63
63
  return Reflect.set(target, property, value);
64
64
  }
65
65
  const nextValue = addTracingToToolCallbackInternal(value, property, server);
66
- (0, external_instrumentation_js_namespaceObject.instrumentToolsListHandler)(server.server);
67
66
  if ('function' == typeof nextValue.update) {
68
67
  const originalUpdate = nextValue.update;
69
68
  nextValue.update = function(...updateArgs) {
@@ -151,32 +150,11 @@ function addTracingToToolCallbackInternal(tool, toolName, server) {
151
150
  wrappedTool[MCP_ANALYTICS_PROCESSED] = true;
152
151
  return wrappedTool;
153
152
  }
154
- function setupToolsCallHandlerWrapping(server) {
155
- const lowLevelServer = server.server;
156
- const existingHandler = lowLevelServer._requestHandlers.get('tools/call');
157
- if (existingHandler) {
158
- const wrappedHandler = createToolsCallWrapper(existingHandler, lowLevelServer);
159
- lowLevelServer._requestHandlers.set('tools/call', wrappedHandler);
160
- }
161
- const originalSetRequestHandler = lowLevelServer.setRequestHandler.bind(lowLevelServer);
162
- lowLevelServer.setRequestHandler = (requestSchema, handler)=>{
163
- const shape = (0, external_mcp_sdk_compat_js_namespaceObject.getObjectShape)(requestSchema);
164
- const method = shape?.method ? (0, external_mcp_sdk_compat_js_namespaceObject.getLiteralValue)(shape.method) : void 0;
165
- if ('tools/call' === method) {
166
- const wrappedHandler = createToolsCallWrapper(handler, lowLevelServer);
167
- return originalSetRequestHandler(requestSchema, wrappedHandler);
168
- }
169
- return originalSetRequestHandler(requestSchema, handler);
170
- };
171
- }
172
- function createToolsCallWrapper(originalHandler, server) {
173
- return async (request, extra)=>await handleWrappedToolsCall(originalHandler, server, request, extra);
174
- }
175
- async function handleWrappedToolsCall(originalHandler, server, request, extra) {
153
+ async function handleToolCallRequest(server, originalCallToolHandler, request, extra) {
176
154
  const data = (0, external_internal_js_namespaceObject.getServerTrackingData)(server);
177
155
  if (!data) {
178
156
  (0, external_logger_js_namespaceObject.log)('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
179
- return await originalHandler(request, extra);
157
+ return await originalCallToolHandler(request, extra);
180
158
  }
181
159
  if (request.params?.name === (0, external_tools_js_namespaceObject.resolveMissingCapabilityToolName)(data.options)) {
182
160
  const context = (0, external_tracing_helpers_js_namespaceObject.getContextArgument)(request) || '';
@@ -197,7 +175,7 @@ async function handleWrappedToolsCall(originalHandler, server, request, extra) {
197
175
  data,
198
176
  request,
199
177
  extra,
200
- execute: ()=>originalHandler(request, extra),
178
+ execute: ()=>originalCallToolHandler(request, extra),
201
179
  takeCapturedError: ()=>{
202
180
  const captured = extra?.__mcp_analytics_error;
203
181
  if (extra) extra.__mcp_analytics_error = void 0;
@@ -207,15 +185,19 @@ async function handleWrappedToolsCall(originalHandler, server, request, extra) {
207
185
  }
208
186
  function instrumentHighLevelServer(server) {
209
187
  try {
210
- const mcpAnalyticsData = (0, external_internal_js_namespaceObject.getServerTrackingData)(server.server);
211
- setupToolsCallHandlerWrapping(server);
212
- (0, external_instrumentation_js_namespaceObject.instrumentInitializeHandler)(server.server);
188
+ const lowLevelServer = server.server;
189
+ const mcpAnalyticsData = (0, external_internal_js_namespaceObject.getServerTrackingData)(lowLevelServer);
190
+ const handlers = {
191
+ initialize: external_instrumentation_js_namespaceObject.handleInitializeRequest,
192
+ 'tools/list': external_instrumentation_js_namespaceObject.handleListToolsRequest,
193
+ 'tools/call': handleToolCallRequest
194
+ };
195
+ (0, external_instrumentation_js_namespaceObject.patchRequestHandlers)(lowLevelServer, handlers);
213
196
  server._registeredTools = addTracingToToolRegistry(server._registeredTools, server);
214
197
  if (mcpAnalyticsData) {
215
198
  seedToolDescriptionsFromRegistry(mcpAnalyticsData.toolDescriptions, server._registeredTools);
216
199
  seedToolCategoriesFromRegistry(mcpAnalyticsData.toolCategories, server._registeredTools);
217
200
  }
218
- (0, external_instrumentation_js_namespaceObject.instrumentToolsListHandler)(server.server);
219
201
  setupListenerToRegisteredTools(server);
220
202
  } catch (error) {
221
203
  (0, external_logger_js_namespaceObject.log)(`Warning: Failed to setup tool call instrumentation - ${error}`);
@@ -2,9 +2,9 @@ import { stripConversationId } from "./conversation-id.mjs";
2
2
  import { MCPAnalyticsEventType } from "./event-types.mjs";
3
3
  import { getServerTrackingData } from "./internal.mjs";
4
4
  import { log } from "./logger.mjs";
5
- import { createWrappedTool, getLiteralValue, getObjectShape, getToolFunction, hasToolFunction } from "./mcp-sdk-compat.mjs";
5
+ import { createWrappedTool, getToolFunction, hasToolFunction } from "./mcp-sdk-compat.mjs";
6
6
  import { handleReportMissing, resolveMissingCapabilityToolName } from "./tools.mjs";
7
- import { captureToolCall, instrumentInitializeHandler, instrumentToolsListHandler, readToolMetaCategory } from "./instrumentation.mjs";
7
+ import { captureToolCall, handleInitializeRequest, handleListToolsRequest, patchRequestHandlers, readToolMetaCategory } from "./instrumentation.mjs";
8
8
  import { getContextArgument } from "./tracing-helpers.mjs";
9
9
  const wrappedCallbacks = new WeakMap();
10
10
  const MCP_ANALYTICS_PROCESSED = Symbol('__posthog_mcp_analytics_processed__');
@@ -35,7 +35,6 @@ function setupListenerToRegisteredTools(server) {
35
35
  return Reflect.set(target, property, value);
36
36
  }
37
37
  const nextValue = addTracingToToolCallbackInternal(value, property, server);
38
- instrumentToolsListHandler(server.server);
39
38
  if ('function' == typeof nextValue.update) {
40
39
  const originalUpdate = nextValue.update;
41
40
  nextValue.update = function(...updateArgs) {
@@ -123,32 +122,11 @@ function addTracingToToolCallbackInternal(tool, toolName, server) {
123
122
  wrappedTool[MCP_ANALYTICS_PROCESSED] = true;
124
123
  return wrappedTool;
125
124
  }
126
- function setupToolsCallHandlerWrapping(server) {
127
- const lowLevelServer = server.server;
128
- const existingHandler = lowLevelServer._requestHandlers.get('tools/call');
129
- if (existingHandler) {
130
- const wrappedHandler = createToolsCallWrapper(existingHandler, lowLevelServer);
131
- lowLevelServer._requestHandlers.set('tools/call', wrappedHandler);
132
- }
133
- const originalSetRequestHandler = lowLevelServer.setRequestHandler.bind(lowLevelServer);
134
- lowLevelServer.setRequestHandler = (requestSchema, handler)=>{
135
- const shape = getObjectShape(requestSchema);
136
- const method = shape?.method ? getLiteralValue(shape.method) : void 0;
137
- if ('tools/call' === method) {
138
- const wrappedHandler = createToolsCallWrapper(handler, lowLevelServer);
139
- return originalSetRequestHandler(requestSchema, wrappedHandler);
140
- }
141
- return originalSetRequestHandler(requestSchema, handler);
142
- };
143
- }
144
- function createToolsCallWrapper(originalHandler, server) {
145
- return async (request, extra)=>await handleWrappedToolsCall(originalHandler, server, request, extra);
146
- }
147
- async function handleWrappedToolsCall(originalHandler, server, request, extra) {
125
+ async function handleToolCallRequest(server, originalCallToolHandler, request, extra) {
148
126
  const data = getServerTrackingData(server);
149
127
  if (!data) {
150
128
  log('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
151
- return await originalHandler(request, extra);
129
+ return await originalCallToolHandler(request, extra);
152
130
  }
153
131
  if (request.params?.name === resolveMissingCapabilityToolName(data.options)) {
154
132
  const context = getContextArgument(request) || '';
@@ -169,7 +147,7 @@ async function handleWrappedToolsCall(originalHandler, server, request, extra) {
169
147
  data,
170
148
  request,
171
149
  extra,
172
- execute: ()=>originalHandler(request, extra),
150
+ execute: ()=>originalCallToolHandler(request, extra),
173
151
  takeCapturedError: ()=>{
174
152
  const captured = extra?.__mcp_analytics_error;
175
153
  if (extra) extra.__mcp_analytics_error = void 0;
@@ -179,15 +157,19 @@ async function handleWrappedToolsCall(originalHandler, server, request, extra) {
179
157
  }
180
158
  function instrumentHighLevelServer(server) {
181
159
  try {
182
- const mcpAnalyticsData = getServerTrackingData(server.server);
183
- setupToolsCallHandlerWrapping(server);
184
- instrumentInitializeHandler(server.server);
160
+ const lowLevelServer = server.server;
161
+ const mcpAnalyticsData = getServerTrackingData(lowLevelServer);
162
+ const handlers = {
163
+ initialize: handleInitializeRequest,
164
+ 'tools/list': handleListToolsRequest,
165
+ 'tools/call': handleToolCallRequest
166
+ };
167
+ patchRequestHandlers(lowLevelServer, handlers);
185
168
  server._registeredTools = addTracingToToolRegistry(server._registeredTools, server);
186
169
  if (mcpAnalyticsData) {
187
170
  seedToolDescriptionsFromRegistry(mcpAnalyticsData.toolDescriptions, server._registeredTools);
188
171
  seedToolCategoriesFromRegistry(mcpAnalyticsData.toolCategories, server._registeredTools);
189
172
  }
190
- instrumentToolsListHandler(server.server);
191
173
  setupListenerToRegisteredTools(server);
192
174
  } catch (error) {
193
175
  log(`Warning: Failed to setup tool call instrumentation - ${error}`);
@@ -1 +1 @@
1
- {"version":3,"file":"instrument-lowlevel.d.ts","sourceRoot":"","sources":["../../src/extensions/instrument-lowlevel.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAiD,aAAa,EAAE,MAAM,UAAU,CAAA;AAc5F;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAcpE"}
1
+ {"version":3,"file":"instrument-lowlevel.d.ts","sourceRoot":"","sources":["../../src/extensions/instrument-lowlevel.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAiD,aAAa,EAAE,MAAM,UAAU,CAAA;AAmB5F;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAkBpE"}
@@ -35,8 +35,11 @@ const external_instrumentation_js_namespaceObject = require("./instrumentation.j
35
35
  const external_tracing_helpers_js_namespaceObject = require("./tracing-helpers.js");
36
36
  function instrumentLowLevelServer(server) {
37
37
  try {
38
- (0, external_instrumentation_js_namespaceObject.instrumentInitializeHandler)(server);
39
- (0, external_instrumentation_js_namespaceObject.instrumentToolsListHandler)(server);
38
+ const handlers = {
39
+ initialize: external_instrumentation_js_namespaceObject.handleInitializeRequest,
40
+ 'tools/list': external_instrumentation_js_namespaceObject.handleListToolsRequest
41
+ };
42
+ (0, external_instrumentation_js_namespaceObject.patchRequestHandlers)(server, handlers);
40
43
  const originalCallToolHandler = server._requestHandlers.get('tools/call');
41
44
  server.setRequestHandler(types_js_namespaceObject.CallToolRequestSchema, async (request, extra)=>await handleToolCallRequest(server, originalCallToolHandler, request, extra));
42
45
  } catch (error) {
@@ -3,12 +3,15 @@ import { MCPAnalyticsEventType } from "./event-types.mjs";
3
3
  import { getServerTrackingData } from "./internal.mjs";
4
4
  import { log } from "./logger.mjs";
5
5
  import { handleReportMissing, resolveMissingCapabilityToolName } from "./tools.mjs";
6
- import { captureToolCall, instrumentInitializeHandler, instrumentToolsListHandler } from "./instrumentation.mjs";
6
+ import { captureToolCall, handleInitializeRequest, handleListToolsRequest, patchRequestHandlers } from "./instrumentation.mjs";
7
7
  import { getContextArgument } from "./tracing-helpers.mjs";
8
8
  function instrumentLowLevelServer(server) {
9
9
  try {
10
- instrumentInitializeHandler(server);
11
- instrumentToolsListHandler(server);
10
+ const handlers = {
11
+ initialize: handleInitializeRequest,
12
+ 'tools/list': handleListToolsRequest
13
+ };
14
+ patchRequestHandlers(server, handlers);
12
15
  const originalCallToolHandler = server._requestHandlers.get('tools/call');
13
16
  server.setRequestHandler(CallToolRequestSchema, async (request, extra)=>await handleToolCallRequest(server, originalCallToolHandler, request, extra));
14
17
  } catch (error) {
@@ -1,6 +1,14 @@
1
- import { type ListToolsResult } from '@modelcontextprotocol/sdk/types.js';
1
+ import type { ListToolsResult } from '@modelcontextprotocol/sdk/types.js';
2
2
  import type { CompatibleRequestHandlerExtra, MCPAnalyticsData, MCPRequestLike, MCPServerLike } from '../types';
3
3
  import { MCPAnalyticsEventType } from './event-types';
4
+ /**
5
+ * Single instrumentation core shared by the low-level (`Server`) and high-level
6
+ * (`McpServer`) wrappers. The two entrypoints differ only in how they reach the
7
+ * underlying tool — they both funnel the tool-call lifecycle through
8
+ * {@link captureToolCall}, so error handling, conversation-id minting, session
9
+ * attribution, and event capture are defined exactly once.
10
+ */
11
+ type MCPRequestHandler = (request: MCPRequestLike, extra?: CompatibleRequestHandlerExtra) => Promise<unknown>;
4
12
  /**
5
13
  * Runs the underlying tool. Receives the request with the SDK-injected
6
14
  * `conversation_id` stripped; an adapter is free to ignore it (the high-level
@@ -43,19 +51,26 @@ interface TraceToolCallParams {
43
51
  */
44
52
  export declare function captureToolCall(params: TraceToolCallParams): Promise<unknown>;
45
53
  /**
46
- * Wraps the server's `tools/list` handler so each listing is captured and the
47
- * SDK-managed tools (context parameter, conversation id, `get_more_tools`) are
48
- * injected. Idempotent per server. Works for both low-level and high-level
49
- * servers — the high-level wrapper passes its underlying `server`.
50
- *
51
- * The handler can reach us two ways, and we cover both:
52
- * 1. It already exists when instrument() runs (tools registered first) — wrap
53
- * the entry in `_requestHandlers` directly.
54
- * 2. It's registered after instrument() (e.g. `@rekog/mcp-nest` registers it at
55
- * the low level once it gets the server) — intercept `setRequestHandler` and
56
- * wrap the handler as it lands, mirroring how `tools/call` already works.
54
+ * A method's patch: runs the original handler and captures analytics. `server`
55
+ * and `originalHandler` are bound by {@link patchRequestHandlers}; the SDK
56
+ * supplies `request` and `extra` per call.
57
+ */
58
+ export type HandlerPatch = (server: MCPServerLike, originalHandler: MCPRequestHandler, request: MCPRequestLike, extra: CompatibleRequestHandlerExtra | undefined) => Promise<unknown>;
59
+ /**
60
+ * Applies the `patches` (keyed by method, e.g. `initialize`, `tools/list`) to the
61
+ * handlers already registered, and patches `setRequestHandler` so matching
62
+ * handlers registered later are patched too. The latter is what makes adapters
63
+ * that register handlers post-construction work — e.g. `@rekog/mcp-nest` hands a
64
+ * bare server to instrument() and only then registers its handlers.
65
+ */
66
+ export declare function patchRequestHandlers(server: MCPServerLike, patches: Record<string, HandlerPatch>): void;
67
+ /**
68
+ * Captures each `tools/list` and injects the SDK-managed tools (context
69
+ * parameter, conversation id, `get_more_tools`) into the returned list.
57
70
  */
58
- export declare function instrumentToolsListHandler(server: MCPServerLike): void;
71
+ export declare function handleListToolsRequest(server: MCPServerLike, originalListToolsHandler: MCPRequestHandler, request: MCPRequestLike, extra?: CompatibleRequestHandlerExtra): Promise<{
72
+ tools: ListToolsResult['tools'];
73
+ }>;
59
74
  export declare function cacheToolDescriptions(cache: Map<string, string>, tools: ListToolsResult['tools'] | undefined): void;
60
75
  /**
61
76
  * Category declared on a tool's `_meta` block (the MCP spec allows arbitrary
@@ -65,9 +80,9 @@ export declare function cacheToolDescriptions(cache: Map<string, string>, tools:
65
80
  export declare function readToolMetaCategory(meta: unknown): string | undefined;
66
81
  export declare function cacheToolCategories(cache: Map<string, string>, tools: ListToolsResult['tools'] | undefined): void;
67
82
  /**
68
- * Wraps the server's `initialize` handler so the connection handshake is
69
- * captured (and identity resolved) before the original handler runs.
83
+ * Captures the connection handshake (and resolves identity) on `initialize`
84
+ * before the original handler runs.
70
85
  */
71
- export declare function instrumentInitializeHandler(server: MCPServerLike): void;
86
+ export declare function handleInitializeRequest(server: MCPServerLike, originalInitializeHandler: MCPRequestHandler, request: MCPRequestLike, extra?: CompatibleRequestHandlerExtra): Promise<unknown>;
72
87
  export {};
73
88
  //# sourceMappingURL=instrumentation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"AAIA,OAAO,EAA2B,KAAK,eAAe,EAAE,MAAM,oCAAoC,CAAA;AAClG,OAAO,KAAK,EAAE,6BAA6B,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAY,MAAM,UAAU,CAAA;AAWxH,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAqBrD;;;;GAIG;AACH,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;;;;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,CAuCnF;AAwHD;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAiCtE;AAuGD,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;AAID;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAiCvE"}
1
+ {"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,KAAK,EAAE,6BAA6B,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAY,MAAM,UAAU,CAAA;AAWxH,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAWrD;;;;;;GAMG;AAEH,KAAK,iBAAiB,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,6BAA6B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAE7G;;;;GAIG;AACH,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;;;;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,CAuCnF;AAgHD;;;;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;AAErB;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,IAAI,CAqBvG;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,aAAa,EACrB,wBAAwB,EAAE,iBAAiB,EAC3C,OAAO,EAAE,cAAc,EACvB,KAAK,CAAC,EAAE,6BAA6B,GACpC,OAAO,CAAC;IAAE,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAwC9C;AAwDD,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;AAID;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,aAAa,EACrB,yBAAyB,EAAE,iBAAiB,EAC5C,OAAO,EAAE,cAAc,EACvB,KAAK,CAAC,EAAE,6BAA6B,GACpC,OAAO,CAAC,OAAO,CAAC,CA0BlB"}
@@ -27,11 +27,11 @@ __webpack_require__.d(__webpack_exports__, {
27
27
  readToolMetaCategory: ()=>readToolMetaCategory,
28
28
  cacheToolDescriptions: ()=>cacheToolDescriptions,
29
29
  cacheToolCategories: ()=>cacheToolCategories,
30
+ handleInitializeRequest: ()=>handleInitializeRequest,
30
31
  captureToolCall: ()=>captureToolCall,
31
- instrumentInitializeHandler: ()=>instrumentInitializeHandler,
32
- instrumentToolsListHandler: ()=>instrumentToolsListHandler
32
+ handleListToolsRequest: ()=>handleListToolsRequest,
33
+ patchRequestHandlers: ()=>patchRequestHandlers
33
34
  });
34
- const types_js_namespaceObject = require("@modelcontextprotocol/sdk/types.js");
35
35
  const external_context_parameters_js_namespaceObject = require("./context-parameters.js");
36
36
  const external_conversation_id_js_namespaceObject = require("./conversation-id.js");
37
37
  const external_capture_js_namespaceObject = require("./capture.js");
@@ -119,24 +119,19 @@ function publishFailedToolEvent(server, event, error, startTime, conversation) {
119
119
  (0, external_logger_js_namespaceObject.log)(`Warning: PostHog MCP analytics failed to publish failed tool event - ${publishError}`);
120
120
  }
121
121
  }
122
- const listToolsTracingSetup = new WeakMap();
123
- function instrumentToolsListHandler(server) {
124
- if (!server._capabilities?.tools) return;
125
- if (listToolsTracingSetup.get(server)) return;
126
- listToolsTracingSetup.set(server, true);
127
- try {
128
- const existingHandler = server._requestHandlers.get('tools/list');
129
- if (existingHandler) server._requestHandlers.set('tools/list', (request, extra)=>handleListToolsRequest(server, existingHandler, request, extra));
130
- const originalSetRequestHandler = server.setRequestHandler.bind(server);
131
- server.setRequestHandler = (requestSchema, handler)=>{
132
- const shape = (0, external_mcp_sdk_compat_js_namespaceObject.getObjectShape)(requestSchema);
133
- const method = shape?.method ? (0, external_mcp_sdk_compat_js_namespaceObject.getLiteralValue)(shape.method) : void 0;
134
- if ('tools/list' === method) return originalSetRequestHandler(requestSchema, (request, extra)=>handleListToolsRequest(server, handler, request, extra));
135
- return originalSetRequestHandler(requestSchema, handler);
136
- };
137
- } catch (error) {
138
- (0, external_logger_js_namespaceObject.log)(`Warning: Failed to override list tools handler - ${error}`);
122
+ function patchRequestHandlers(server, patches) {
123
+ for (const [handlerName, patch] of Object.entries(patches)){
124
+ const originalHandler = server._requestHandlers.get(handlerName);
125
+ if (originalHandler) server._requestHandlers.set(handlerName, (request, extra)=>patch(server, originalHandler, request, extra));
139
126
  }
127
+ const originalSetRequestHandler = server.setRequestHandler.bind(server);
128
+ server.setRequestHandler = (requestSchema, originalHandler)=>{
129
+ const shape = (0, external_mcp_sdk_compat_js_namespaceObject.getObjectShape)(requestSchema);
130
+ const handlerName = shape?.method ? (0, external_mcp_sdk_compat_js_namespaceObject.getLiteralValue)(shape.method) : void 0;
131
+ const patch = 'string' == typeof handlerName ? patches[handlerName] : void 0;
132
+ if (!patch) return originalSetRequestHandler(requestSchema, originalHandler);
133
+ return originalSetRequestHandler(requestSchema, (request, extra)=>patch(server, originalHandler, request, extra));
134
+ };
140
135
  }
141
136
  async function handleListToolsRequest(server, originalListToolsHandler, request, extra) {
142
137
  const data = (0, external_internal_js_namespaceObject.getServerTrackingData)(server);
@@ -222,43 +217,41 @@ function cacheToolCategories(cache, tools) {
222
217
  if (category) cache.set(tool.name, category);
223
218
  }
224
219
  }
225
- function instrumentInitializeHandler(server) {
226
- const originalInitializeHandler = server._requestHandlers.get('initialize');
227
- if (!originalInitializeHandler) return;
228
- server.setRequestHandler(types_js_namespaceObject.InitializeRequestSchema, async (request, extra)=>{
229
- const data = (0, external_internal_js_namespaceObject.getServerTrackingData)(server);
230
- if (!data) {
231
- (0, external_logger_js_namespaceObject.log)('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
232
- return await originalInitializeHandler(request, extra);
233
- }
234
- const sessionId = (0, external_session_js_namespaceObject.getServerSessionId)(server, extra);
235
- await (0, external_internal_js_namespaceObject.handleIdentify)(server, data, sessionId, request, extra);
236
- const event = {
237
- sessionId,
238
- resourceName: request.params?.name || 'Unknown Tool Name',
239
- eventType: external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpInitialize,
240
- parameters: (0, external_mcp_payloads_js_namespaceObject.buildCapturedMcpParameters)(request),
241
- timestamp: new Date()
242
- };
243
- await (0, external_tracing_helpers_js_namespaceObject.applyResolvedMetadata)(event, data, request, extra);
244
- const result = await originalInitializeHandler(request, extra);
245
- event.response = result;
246
- (0, external_capture_js_namespaceObject.captureEvent)(server, event);
247
- return result;
248
- });
220
+ async function handleInitializeRequest(server, originalInitializeHandler, request, extra) {
221
+ const data = (0, external_internal_js_namespaceObject.getServerTrackingData)(server);
222
+ if (!data) {
223
+ (0, external_logger_js_namespaceObject.log)('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
224
+ return await originalInitializeHandler(request, extra);
225
+ }
226
+ const sessionId = (0, external_session_js_namespaceObject.getServerSessionId)(server, extra);
227
+ await (0, external_internal_js_namespaceObject.handleIdentify)(server, data, sessionId, request, extra);
228
+ const event = {
229
+ sessionId,
230
+ resourceName: request.params?.name || 'Unknown Tool Name',
231
+ eventType: external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpInitialize,
232
+ parameters: (0, external_mcp_payloads_js_namespaceObject.buildCapturedMcpParameters)(request),
233
+ timestamp: new Date()
234
+ };
235
+ await (0, external_tracing_helpers_js_namespaceObject.applyResolvedMetadata)(event, data, request, extra);
236
+ const result = await originalInitializeHandler(request, extra);
237
+ event.response = result;
238
+ (0, external_capture_js_namespaceObject.captureEvent)(server, event);
239
+ return result;
249
240
  }
250
241
  exports.cacheToolCategories = __webpack_exports__.cacheToolCategories;
251
242
  exports.cacheToolDescriptions = __webpack_exports__.cacheToolDescriptions;
252
243
  exports.captureToolCall = __webpack_exports__.captureToolCall;
253
- exports.instrumentInitializeHandler = __webpack_exports__.instrumentInitializeHandler;
254
- exports.instrumentToolsListHandler = __webpack_exports__.instrumentToolsListHandler;
244
+ exports.handleInitializeRequest = __webpack_exports__.handleInitializeRequest;
245
+ exports.handleListToolsRequest = __webpack_exports__.handleListToolsRequest;
246
+ exports.patchRequestHandlers = __webpack_exports__.patchRequestHandlers;
255
247
  exports.readToolMetaCategory = __webpack_exports__.readToolMetaCategory;
256
248
  for(var __webpack_i__ in __webpack_exports__)if (-1 === [
257
249
  "cacheToolCategories",
258
250
  "cacheToolDescriptions",
259
251
  "captureToolCall",
260
- "instrumentInitializeHandler",
261
- "instrumentToolsListHandler",
252
+ "handleInitializeRequest",
253
+ "handleListToolsRequest",
254
+ "patchRequestHandlers",
262
255
  "readToolMetaCategory"
263
256
  ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
264
257
  Object.defineProperty(exports, '__esModule', {
@@ -1,4 +1,3 @@
1
- import { InitializeRequestSchema } from "@modelcontextprotocol/sdk/types.js";
2
1
  import { addContextParameterToTools, getContextDescription, isContextEnabled } from "./context-parameters.mjs";
3
2
  import { addConversationIdToTools, canInjectConversationIdPromptBack, cloneRequestWithoutConversationId, injectConversationIdPromptBack, resolveConversationId } from "./conversation-id.mjs";
4
3
  import { captureEvent } from "./capture.mjs";
@@ -86,24 +85,19 @@ function publishFailedToolEvent(server, event, error, startTime, conversation) {
86
85
  log(`Warning: PostHog MCP analytics failed to publish failed tool event - ${publishError}`);
87
86
  }
88
87
  }
89
- const listToolsTracingSetup = new WeakMap();
90
- function instrumentToolsListHandler(server) {
91
- if (!server._capabilities?.tools) return;
92
- if (listToolsTracingSetup.get(server)) return;
93
- listToolsTracingSetup.set(server, true);
94
- try {
95
- const existingHandler = server._requestHandlers.get('tools/list');
96
- if (existingHandler) server._requestHandlers.set('tools/list', (request, extra)=>handleListToolsRequest(server, existingHandler, request, extra));
97
- const originalSetRequestHandler = server.setRequestHandler.bind(server);
98
- server.setRequestHandler = (requestSchema, handler)=>{
99
- const shape = getObjectShape(requestSchema);
100
- const method = shape?.method ? getLiteralValue(shape.method) : void 0;
101
- if ('tools/list' === method) return originalSetRequestHandler(requestSchema, (request, extra)=>handleListToolsRequest(server, handler, request, extra));
102
- return originalSetRequestHandler(requestSchema, handler);
103
- };
104
- } catch (error) {
105
- log(`Warning: Failed to override list tools handler - ${error}`);
88
+ function patchRequestHandlers(server, patches) {
89
+ for (const [handlerName, patch] of Object.entries(patches)){
90
+ const originalHandler = server._requestHandlers.get(handlerName);
91
+ if (originalHandler) server._requestHandlers.set(handlerName, (request, extra)=>patch(server, originalHandler, request, extra));
106
92
  }
93
+ const originalSetRequestHandler = server.setRequestHandler.bind(server);
94
+ server.setRequestHandler = (requestSchema, originalHandler)=>{
95
+ const shape = getObjectShape(requestSchema);
96
+ const handlerName = shape?.method ? getLiteralValue(shape.method) : void 0;
97
+ const patch = 'string' == typeof handlerName ? patches[handlerName] : void 0;
98
+ if (!patch) return originalSetRequestHandler(requestSchema, originalHandler);
99
+ return originalSetRequestHandler(requestSchema, (request, extra)=>patch(server, originalHandler, request, extra));
100
+ };
107
101
  }
108
102
  async function handleListToolsRequest(server, originalListToolsHandler, request, extra) {
109
103
  const data = getServerTrackingData(server);
@@ -189,29 +183,25 @@ function cacheToolCategories(cache, tools) {
189
183
  if (category) cache.set(tool.name, category);
190
184
  }
191
185
  }
192
- function instrumentInitializeHandler(server) {
193
- const originalInitializeHandler = server._requestHandlers.get('initialize');
194
- if (!originalInitializeHandler) return;
195
- server.setRequestHandler(InitializeRequestSchema, async (request, extra)=>{
196
- const data = getServerTrackingData(server);
197
- if (!data) {
198
- log('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
199
- return await originalInitializeHandler(request, extra);
200
- }
201
- const sessionId = getServerSessionId(server, extra);
202
- await handleIdentify(server, data, sessionId, request, extra);
203
- const event = {
204
- sessionId,
205
- resourceName: request.params?.name || 'Unknown Tool Name',
206
- eventType: MCPAnalyticsEventType.mcpInitialize,
207
- parameters: buildCapturedMcpParameters(request),
208
- timestamp: new Date()
209
- };
210
- await applyResolvedMetadata(event, data, request, extra);
211
- const result = await originalInitializeHandler(request, extra);
212
- event.response = result;
213
- captureEvent(server, event);
214
- return result;
215
- });
186
+ async function handleInitializeRequest(server, originalInitializeHandler, request, extra) {
187
+ const data = getServerTrackingData(server);
188
+ if (!data) {
189
+ log('Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.');
190
+ return await originalInitializeHandler(request, extra);
191
+ }
192
+ const sessionId = getServerSessionId(server, extra);
193
+ await handleIdentify(server, data, sessionId, request, extra);
194
+ const event = {
195
+ sessionId,
196
+ resourceName: request.params?.name || 'Unknown Tool Name',
197
+ eventType: MCPAnalyticsEventType.mcpInitialize,
198
+ parameters: buildCapturedMcpParameters(request),
199
+ timestamp: new Date()
200
+ };
201
+ await applyResolvedMetadata(event, data, request, extra);
202
+ const result = await originalInitializeHandler(request, extra);
203
+ event.response = result;
204
+ captureEvent(server, event);
205
+ return result;
216
206
  }
217
- export { cacheToolCategories, cacheToolDescriptions, captureToolCall, instrumentInitializeHandler, instrumentToolsListHandler, readToolMetaCategory };
207
+ export { cacheToolCategories, cacheToolDescriptions, captureToolCall, handleInitializeRequest, handleListToolsRequest, patchRequestHandlers, readToolMetaCategory };
@@ -1 +1 @@
1
- {"version":3,"file":"sink.d.ts","sourceRoot":"","sources":["../../src/extensions/sink.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAG3C,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAE7D,OAAO,EAAE,KAAK,mBAAmB,EAA6B,MAAM,kBAAkB,CAAA;AAKtF,yEAAyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,0BAA0B,EAAE,OAAO,CAAA;IACnC,kFAAkF;IAClF,UAAU,CAAC,EAAE,YAAY,CAAA;CAC1B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,mBAAmB,EAAE,CAAA;CAAE,GAAG,IAAI,CAAC,CA2BnE;AA4BD;;;;;;;;GAQG;AACH,qBAAa,YAAY;IACX,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,OAAO;IAE7C;;;;OAIG;IACG,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;CA0B1E"}
1
+ {"version":3,"file":"sink.d.ts","sourceRoot":"","sources":["../../src/extensions/sink.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAG3C,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAE7D,OAAO,EAAE,KAAK,mBAAmB,EAA6B,MAAM,kBAAkB,CAAA;AAKtF,yEAAyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,0BAA0B,EAAE,OAAO,CAAA;IACnC,kFAAkF;IAClF,UAAU,CAAC,EAAE,YAAY,CAAA;CAC1B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,mBAAmB,EAAE,CAAA;CAAE,GAAG,IAAI,CAAC,CA2BnE;AA4BD;;;;;;;;GAQG;AACH,qBAAa,YAAY;IACX,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,OAAO;IAE7C;;;;OAIG;IACG,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;CAmC1E"}
@@ -78,13 +78,19 @@ class McpEventSink {
78
78
  if (!result) return;
79
79
  const { event: fullEvent, captures } = result;
80
80
  try {
81
- for (const captureEvent of captures)this.posthog.capture({
82
- distinctId: captureEvent.distinct_id,
83
- event: captureEvent.event,
84
- properties: captureEvent.properties,
85
- timestamp: new Date(captureEvent.timestamp),
86
- uuid: (0, core_namespaceObject.uuidv7)()
87
- });
81
+ for (const captureEvent of captures){
82
+ const groups = captureEvent.properties.$groups;
83
+ this.posthog.capture({
84
+ distinctId: captureEvent.distinct_id,
85
+ event: captureEvent.event,
86
+ properties: captureEvent.properties,
87
+ ...groups ? {
88
+ groups
89
+ } : {},
90
+ timestamp: new Date(captureEvent.timestamp),
91
+ uuid: (0, core_namespaceObject.uuidv7)()
92
+ });
93
+ }
88
94
  (0, external_logger_js_namespaceObject.log)(`Captured PostHog event ${fullEvent.id} | ${fullEvent.eventType} | ${fullEvent.duration} ms | ${fullEvent.identifyActorGivenId || 'anonymous'}`);
89
95
  } catch (err) {
90
96
  (0, external_logger_js_namespaceObject.log)(`Failed to capture PostHog event ${fullEvent.id}: ${err}`);
@@ -49,13 +49,19 @@ class McpEventSink {
49
49
  if (!result) return;
50
50
  const { event: fullEvent, captures } = result;
51
51
  try {
52
- for (const captureEvent of captures)this.posthog.capture({
53
- distinctId: captureEvent.distinct_id,
54
- event: captureEvent.event,
55
- properties: captureEvent.properties,
56
- timestamp: new Date(captureEvent.timestamp),
57
- uuid: uuidv7()
58
- });
52
+ for (const captureEvent of captures){
53
+ const groups = captureEvent.properties.$groups;
54
+ this.posthog.capture({
55
+ distinctId: captureEvent.distinct_id,
56
+ event: captureEvent.event,
57
+ properties: captureEvent.properties,
58
+ ...groups ? {
59
+ groups
60
+ } : {},
61
+ timestamp: new Date(captureEvent.timestamp),
62
+ uuid: uuidv7()
63
+ });
64
+ }
59
65
  log(`Captured PostHog event ${fullEvent.id} | ${fullEvent.eventType} | ${fullEvent.duration} ms | ${fullEvent.identifyActorGivenId || 'anonymous'}`);
60
66
  } catch (err) {
61
67
  log(`Failed to capture PostHog event ${fullEvent.id}: ${err}`);
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const version = "0.4.2";
1
+ export declare const version = "0.4.4";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -26,7 +26,7 @@ __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
27
  version: ()=>version
28
28
  });
29
- const version = '0.4.2';
29
+ const version = '0.4.4';
30
30
  exports.version = __webpack_exports__.version;
31
31
  for(var __webpack_i__ in __webpack_exports__)if (-1 === [
32
32
  "version"
package/dist/version.mjs CHANGED
@@ -1,2 +1,2 @@
1
- const version = '0.4.2';
1
+ const version = '0.4.4';
2
2
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/mcp",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "bugs": {
5
5
  "url": "https://github.com/PostHog/posthog-js/issues"
6
6
  },
@@ -28,7 +28,7 @@
28
28
  "module": "dist/index.mjs",
29
29
  "types": "dist/index.d.ts",
30
30
  "dependencies": {
31
- "@posthog/core": "^1.37.3"
31
+ "@posthog/core": "^1.38.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@babel/preset-env": "^7.27.1",
@@ -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-tooling/tsconfig-base": "1.1.1",
43
- "posthog-node": "^5.38.5"
42
+ "posthog-node": "^5.38.6",
43
+ "@posthog-tooling/tsconfig-base": "1.1.1"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@modelcontextprotocol/sdk": ">=1.26.0",
@@ -8,11 +8,12 @@ import { stripConversationId } from './conversation-id'
8
8
  import { MCPAnalyticsEventType } from './event-types'
9
9
  import { getServerTrackingData } from './internal'
10
10
  import { log } from './logger'
11
- import { createWrappedTool, getLiteralValue, getObjectShape, getToolFunction, hasToolFunction } from './mcp-sdk-compat'
11
+ import { createWrappedTool, getToolFunction, hasToolFunction } from './mcp-sdk-compat'
12
12
  import { handleReportMissing, resolveMissingCapabilityToolName } from './tools'
13
13
  import {
14
- instrumentInitializeHandler,
15
- instrumentToolsListHandler,
14
+ handleInitializeRequest,
15
+ handleListToolsRequest,
16
+ patchRequestHandlers,
16
17
  captureToolCall,
17
18
  readToolMetaCategory,
18
19
  } from './instrumentation'
@@ -72,8 +73,6 @@ function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
72
73
 
73
74
  const nextValue = addTracingToToolCallbackInternal(value, property, server)
74
75
 
75
- instrumentToolsListHandler(server.server as MCPServerLike)
76
-
77
76
  if (typeof nextValue.update === 'function') {
78
77
  const originalUpdate = nextValue.update
79
78
  nextValue.update = function (...updateArgs: unknown[]) {
@@ -130,7 +129,7 @@ function setupListenerToRegisteredTools(server: HighLevelMCPServerLike): void {
130
129
  * (the high-level SDK turns thrown errors into `isError` results otherwise).
131
130
  *
132
131
  * This is purely the tool-facing concern; event capture lives in
133
- * {@link captureToolCall} via {@link handleWrappedToolsCall}.
132
+ * {@link captureToolCall} via {@link handleToolCallRequest}.
134
133
  */
135
134
  function addTracingToToolCallbackInternal(
136
135
  tool: RegisteredTool,
@@ -202,38 +201,9 @@ function addTracingToToolCallbackInternal(
202
201
  return wrappedTool
203
202
  }
204
203
 
205
- function setupToolsCallHandlerWrapping(server: HighLevelMCPServerLike): void {
206
- const lowLevelServer = server.server as MCPServerLike
207
-
208
- const existingHandler = lowLevelServer._requestHandlers.get('tools/call')
209
- if (existingHandler) {
210
- const wrappedHandler = createToolsCallWrapper(existingHandler, lowLevelServer)
211
- lowLevelServer._requestHandlers.set('tools/call', wrappedHandler)
212
- }
213
-
214
- const originalSetRequestHandler = lowLevelServer.setRequestHandler.bind(lowLevelServer)
215
-
216
- lowLevelServer.setRequestHandler = ((requestSchema: unknown, handler: MCPRequestHandler) => {
217
- const shape = getObjectShape(requestSchema)
218
- const method = shape?.method ? getLiteralValue(shape.method) : undefined
219
-
220
- if (method === 'tools/call') {
221
- const wrappedHandler = createToolsCallWrapper(handler, lowLevelServer)
222
- return originalSetRequestHandler(requestSchema, wrappedHandler)
223
- }
224
-
225
- return originalSetRequestHandler(requestSchema, handler)
226
- }) as MCPServerLike['setRequestHandler']
227
- }
228
-
229
- function createToolsCallWrapper(originalHandler: MCPRequestHandler, server: MCPServerLike): MCPRequestHandler {
230
- return async (request: MCPRequest, extra: MCPRequestExtra) =>
231
- await handleWrappedToolsCall(originalHandler, server, request, extra)
232
- }
233
-
234
- async function handleWrappedToolsCall(
235
- originalHandler: MCPRequestHandler,
204
+ async function handleToolCallRequest(
236
205
  server: MCPServerLike,
206
+ originalCallToolHandler: MCPRequestHandler,
237
207
  request: MCPRequest,
238
208
  extra: MCPRequestExtra
239
209
  ): Promise<unknown> {
@@ -242,7 +212,7 @@ async function handleWrappedToolsCall(
242
212
  log(
243
213
  'Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.'
244
214
  )
245
- return await originalHandler(request, extra)
215
+ return await originalCallToolHandler(request, extra)
246
216
  }
247
217
 
248
218
  if (request.params?.name === resolveMissingCapabilityToolName(data.options)) {
@@ -267,7 +237,7 @@ async function handleWrappedToolsCall(
267
237
  data,
268
238
  request,
269
239
  extra,
270
- execute: () => originalHandler(request, extra),
240
+ execute: () => originalCallToolHandler(request, extra),
271
241
  takeCapturedError: () => {
272
242
  const captured = extra?.__mcp_analytics_error
273
243
  if (extra) {
@@ -280,11 +250,16 @@ async function handleWrappedToolsCall(
280
250
 
281
251
  export function instrumentHighLevelServer(server: HighLevelMCPServerLike): void {
282
252
  try {
283
- const mcpAnalyticsData = getServerTrackingData(server.server)
284
-
285
- setupToolsCallHandlerWrapping(server)
286
-
287
- instrumentInitializeHandler(server.server)
253
+ const lowLevelServer = server.server
254
+ const mcpAnalyticsData = getServerTrackingData(lowLevelServer)
255
+
256
+ // Patch already existing handlers, and patch setRequestHandler to capture dynamically created handlers.
257
+ const handlers = {
258
+ initialize: handleInitializeRequest,
259
+ 'tools/list': handleListToolsRequest,
260
+ 'tools/call': handleToolCallRequest,
261
+ }
262
+ patchRequestHandlers(lowLevelServer, handlers)
288
263
 
289
264
  server._registeredTools = addTracingToToolRegistry(server._registeredTools, server)
290
265
 
@@ -293,8 +268,6 @@ export function instrumentHighLevelServer(server: HighLevelMCPServerLike): void
293
268
  seedToolCategoriesFromRegistry(mcpAnalyticsData.toolCategories, server._registeredTools)
294
269
  }
295
270
 
296
- instrumentToolsListHandler(server.server)
297
-
298
271
  setupListenerToRegisteredTools(server)
299
272
  } catch (error) {
300
273
  log(`Warning: Failed to setup tool call instrumentation - ${error}`)
@@ -8,7 +8,12 @@ import { MCPAnalyticsEventType } from './event-types'
8
8
  import { getServerTrackingData } from './internal'
9
9
  import { log } from './logger'
10
10
  import { handleReportMissing, resolveMissingCapabilityToolName } from './tools'
11
- import { instrumentInitializeHandler, instrumentToolsListHandler, captureToolCall } from './instrumentation'
11
+ import {
12
+ handleInitializeRequest,
13
+ handleListToolsRequest,
14
+ patchRequestHandlers,
15
+ captureToolCall,
16
+ } from './instrumentation'
12
17
  import { getContextArgument } from './tracing-helpers'
13
18
 
14
19
  type MCPRequestHandler = NonNullable<
@@ -24,8 +29,12 @@ type MCPRequestExtra = Parameters<MCPRequestHandler>[1]
24
29
  */
25
30
  export function instrumentLowLevelServer(server: MCPServerLike): void {
26
31
  try {
27
- instrumentInitializeHandler(server)
28
- instrumentToolsListHandler(server)
32
+ // Patch already existing handlers, and patch setRequestHandler to capture dynamically created handlers.
33
+ const handlers = {
34
+ initialize: handleInitializeRequest,
35
+ 'tools/list': handleListToolsRequest,
36
+ }
37
+ patchRequestHandlers(server, handlers)
29
38
 
30
39
  const originalCallToolHandler = server._requestHandlers.get('tools/call')
31
40
  server.setRequestHandler(
@@ -2,7 +2,7 @@
2
2
  // Copyright (c) 2025 MCPcat
3
3
  // Licensed under the MIT License: https://github.com/MCPCat/mcpcat-typescript-sdk/blob/main/LICENSE
4
4
 
5
- import { InitializeRequestSchema, type ListToolsResult } from '@modelcontextprotocol/sdk/types.js'
5
+ import type { ListToolsResult } from '@modelcontextprotocol/sdk/types.js'
6
6
  import type { CompatibleRequestHandlerExtra, MCPAnalyticsData, MCPRequestLike, MCPServerLike, McpEvent } from '../types'
7
7
  import { addContextParameterToTools, getContextDescription, isContextEnabled } from './context-parameters'
8
8
  import {
@@ -228,63 +228,53 @@ function publishFailedToolEvent(
228
228
 
229
229
  // --- tools/list -----------------------------------------------------------
230
230
 
231
- type MCPServerWithCapabilities = MCPServerLike & {
232
- _capabilities?: {
233
- tools?: unknown
234
- }
235
- }
236
-
237
- const listToolsTracingSetup = new WeakMap<MCPServerLike, boolean>()
231
+ /**
232
+ * A method's patch: runs the original handler and captures analytics. `server`
233
+ * and `originalHandler` are bound by {@link patchRequestHandlers}; the SDK
234
+ * supplies `request` and `extra` per call.
235
+ */
236
+ export type HandlerPatch = (
237
+ server: MCPServerLike,
238
+ originalHandler: MCPRequestHandler,
239
+ request: MCPRequestLike,
240
+ extra: CompatibleRequestHandlerExtra | undefined
241
+ ) => Promise<unknown>
238
242
 
239
243
  /**
240
- * Wraps the server's `tools/list` handler so each listing is captured and the
241
- * SDK-managed tools (context parameter, conversation id, `get_more_tools`) are
242
- * injected. Idempotent per server. Works for both low-level and high-level
243
- * servers — the high-level wrapper passes its underlying `server`.
244
- *
245
- * The handler can reach us two ways, and we cover both:
246
- * 1. It already exists when instrument() runs (tools registered first) — wrap
247
- * the entry in `_requestHandlers` directly.
248
- * 2. It's registered after instrument() (e.g. `@rekog/mcp-nest` registers it at
249
- * the low level once it gets the server) — intercept `setRequestHandler` and
250
- * wrap the handler as it lands, mirroring how `tools/call` already works.
244
+ * Applies the `patches` (keyed by method, e.g. `initialize`, `tools/list`) to the
245
+ * handlers already registered, and patches `setRequestHandler` so matching
246
+ * handlers registered later are patched too. The latter is what makes adapters
247
+ * that register handlers post-construction work — e.g. `@rekog/mcp-nest` hands a
248
+ * bare server to instrument() and only then registers its handlers.
251
249
  */
252
- export function instrumentToolsListHandler(server: MCPServerLike): void {
253
- if (!(server as MCPServerWithCapabilities)._capabilities?.tools) {
254
- return
255
- }
256
- if (listToolsTracingSetup.get(server)) {
257
- return
250
+ export function patchRequestHandlers(server: MCPServerLike, patches: Record<string, HandlerPatch>): void {
251
+ // Monkey patch existing handlers.
252
+ for (const [handlerName, patch] of Object.entries(patches)) {
253
+ const originalHandler = server._requestHandlers.get(handlerName)
254
+ if (originalHandler) {
255
+ server._requestHandlers.set(handlerName, (request, extra) => patch(server, originalHandler, request, extra))
256
+ }
258
257
  }
259
- listToolsTracingSetup.set(server, true)
260
258
 
261
- try {
262
- // A handler already registered before instrument() — wrap it in place.
263
- const existingHandler = server._requestHandlers.get('tools/list')
264
- if (existingHandler) {
265
- server._requestHandlers.set('tools/list', (request, extra) =>
266
- handleListToolsRequest(server, existingHandler, request, extra)
267
- )
259
+ // Monkey patch dynamically added handlers (registered after instrument()).
260
+ const originalSetRequestHandler = server.setRequestHandler.bind(server)
261
+ server.setRequestHandler = ((requestSchema: unknown, originalHandler: MCPRequestHandler) => {
262
+ const shape = getObjectShape(requestSchema)
263
+ const handlerName = shape?.method ? getLiteralValue(shape.method) : undefined
264
+ const patch = typeof handlerName === 'string' ? patches[handlerName] : undefined
265
+ if (!patch) {
266
+ return originalSetRequestHandler(requestSchema, originalHandler)
268
267
  }
269
268
 
270
- // A handler registered after instrument() — intercept the registration.
271
- const originalSetRequestHandler = server.setRequestHandler.bind(server)
272
- server.setRequestHandler = ((requestSchema: unknown, handler: MCPRequestHandler) => {
273
- const shape = getObjectShape(requestSchema)
274
- const method = shape?.method ? getLiteralValue(shape.method) : undefined
275
- if (method === 'tools/list') {
276
- return originalSetRequestHandler(requestSchema, (request, extra) =>
277
- handleListToolsRequest(server, handler, request, extra)
278
- )
279
- }
280
- return originalSetRequestHandler(requestSchema, handler)
281
- }) as MCPServerLike['setRequestHandler']
282
- } catch (error) {
283
- log(`Warning: Failed to override list tools handler - ${error}`)
284
- }
269
+ return originalSetRequestHandler(requestSchema, (request, extra) => patch(server, originalHandler, request, extra))
270
+ }) as MCPServerLike['setRequestHandler']
285
271
  }
286
272
 
287
- async function handleListToolsRequest(
273
+ /**
274
+ * Captures each `tools/list` and injects the SDK-managed tools (context
275
+ * parameter, conversation id, `get_more_tools`) into the returned list.
276
+ */
277
+ export async function handleListToolsRequest(
288
278
  server: MCPServerLike,
289
279
  originalListToolsHandler: MCPRequestHandler,
290
280
  request: MCPRequestLike,
@@ -421,40 +411,38 @@ export function cacheToolCategories(cache: Map<string, string>, tools: ListTools
421
411
  // --- initialize -----------------------------------------------------------
422
412
 
423
413
  /**
424
- * Wraps the server's `initialize` handler so the connection handshake is
425
- * captured (and identity resolved) before the original handler runs.
414
+ * Captures the connection handshake (and resolves identity) on `initialize`
415
+ * before the original handler runs.
426
416
  */
427
- export function instrumentInitializeHandler(server: MCPServerLike): void {
428
- const originalInitializeHandler = server._requestHandlers.get('initialize')
429
- if (!originalInitializeHandler) {
430
- return
417
+ export async function handleInitializeRequest(
418
+ server: MCPServerLike,
419
+ originalInitializeHandler: MCPRequestHandler,
420
+ request: MCPRequestLike,
421
+ extra?: CompatibleRequestHandlerExtra
422
+ ): Promise<unknown> {
423
+ const data = getServerTrackingData(server)
424
+ if (!data) {
425
+ log(
426
+ 'Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.'
427
+ )
428
+ return await originalInitializeHandler(request, extra)
431
429
  }
432
430
 
433
- server.setRequestHandler(InitializeRequestSchema, async (request, extra) => {
434
- const data = getServerTrackingData(server)
435
- if (!data) {
436
- log(
437
- 'Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called instrument(server, options) before using tool calls.'
438
- )
439
- return await originalInitializeHandler(request, extra)
440
- }
431
+ const sessionId = getServerSessionId(server, extra)
432
+ await handleIdentify(server, data, sessionId, request, extra)
441
433
 
442
- const sessionId = getServerSessionId(server, extra)
443
- await handleIdentify(server, data, sessionId, request, extra)
444
-
445
- const event: McpEvent = {
446
- sessionId,
447
- resourceName: request.params?.name || 'Unknown Tool Name',
448
- eventType: MCPAnalyticsEventType.mcpInitialize,
449
- parameters: buildCapturedMcpParameters(request),
450
- timestamp: new Date(),
451
- }
434
+ const event: McpEvent = {
435
+ sessionId,
436
+ resourceName: request.params?.name || 'Unknown Tool Name',
437
+ eventType: MCPAnalyticsEventType.mcpInitialize,
438
+ parameters: buildCapturedMcpParameters(request),
439
+ timestamp: new Date(),
440
+ }
452
441
 
453
- await applyResolvedMetadata(event, data, request, extra)
442
+ await applyResolvedMetadata(event, data, request, extra)
454
443
 
455
- const result = await originalInitializeHandler(request, extra)
456
- event.response = result
457
- captureEvent(server, event)
458
- return result
459
- })
444
+ const result = await originalInitializeHandler(request, extra)
445
+ event.response = result
446
+ captureEvent(server, event)
447
+ return result
460
448
  }
@@ -114,10 +114,19 @@ export class McpEventSink {
114
114
  const { event: fullEvent, captures } = result
115
115
  try {
116
116
  for (const captureEvent of captures) {
117
+ // `$groups` is written into `properties` by `buildPostHogCaptureEvents`,
118
+ // but posthog-node's `prepareEventMessage` overwrites the outgoing
119
+ // `$groups` property with the top-level `groups` field
120
+ // (`$groups: eventMessage.groups || groups`). Without passing `groups`
121
+ // here the group association would be silently dropped. See
122
+ // https://github.com/PostHog/posthog-js/issues/3888.
123
+ const groups = captureEvent.properties.$groups as Record<string, string> | undefined
124
+
117
125
  this.posthog.capture({
118
126
  distinctId: captureEvent.distinct_id,
119
127
  event: captureEvent.event,
120
128
  properties: captureEvent.properties,
129
+ ...(groups ? { groups } : {}),
121
130
  timestamp: new Date(captureEvent.timestamp),
122
131
  uuid: uuidv7(),
123
132
  })
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '0.4.2'
1
+ export const version = '0.4.4'