@posthog/mcp 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,2695 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let node_module = require("node:module");
3
+ let posthog_node = require("posthog-node");
4
+ let node_crypto = require("node:crypto");
5
+ let _modelcontextprotocol_sdk_types_js = require("@modelcontextprotocol/sdk/types.js");
6
+ //#region src/modules/logging.ts
7
+ let fsModule$1 = null;
8
+ let logFilePath = null;
9
+ let initAttempted = false;
10
+ let useConsoleFallback = false;
11
+ /**
12
+ * Attempts to initialize Node.js file logging.
13
+ * Falls back to console.log in edge environments where fs/os modules are unavailable.
14
+ */
15
+ function tryInitSync() {
16
+ if (initAttempted) return;
17
+ initAttempted = true;
18
+ try {
19
+ const require = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
20
+ const fs = require("node:fs");
21
+ const os = require("node:os");
22
+ const path = require("node:path");
23
+ const home = os.homedir?.();
24
+ if (home) {
25
+ fsModule$1 = fs;
26
+ logFilePath = path.join(home, "posthog-mcp-analytics.log");
27
+ } else useConsoleFallback = true;
28
+ } catch {
29
+ useConsoleFallback = true;
30
+ fsModule$1 = null;
31
+ logFilePath = null;
32
+ }
33
+ }
34
+ function writeToLog(message) {
35
+ tryInitSync();
36
+ const logEntry = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}`;
37
+ if (useConsoleFallback) return;
38
+ if (!(logFilePath && fsModule$1)) return;
39
+ try {
40
+ if (fsModule$1.existsSync(logFilePath)) fsModule$1.appendFileSync(logFilePath, `${logEntry}\n`);
41
+ else fsModule$1.writeFileSync(logFilePath, `${logEntry}\n`);
42
+ } catch {}
43
+ }
44
+ //#endregion
45
+ //#region src/modules/compatibility.ts
46
+ /**
47
+ * PostHog MCP analytics Compatibility Module
48
+ *
49
+ * This module ensures compatibility with Model Context Protocol TypeScript SDK.
50
+ * PostHog MCP analytics only supports MCP SDK version 1.11 and above.
51
+ *
52
+ * Version 1.11+ is required because it introduced stable APIs for:
53
+ * - Tool registration and handling
54
+ * - Request handler access patterns
55
+ * - Client version detection
56
+ * - Server info structure
57
+ */
58
+ function logCompatibilityWarning() {
59
+ writeToLog("PostHog MCP analytics SDK Compatibility: This version only supports Model Context Protocol TypeScript SDK v1.11 and above. Please upgrade if using an older version.");
60
+ }
61
+ function isHighLevelServer(server) {
62
+ return !!server && typeof server === "object" && "server" in server && !!server.server && typeof server.server === "object";
63
+ }
64
+ function isCompatibleServerType(server) {
65
+ if (!server || typeof server !== "object") {
66
+ logCompatibilityWarning();
67
+ throw new Error("PostHog MCP analytics SDK compatibility error: Server must be an object. Ensure you're using MCP SDK v1.11 or higher.");
68
+ }
69
+ if (isHighLevelServer(server)) {
70
+ if (!server._registeredTools || typeof server._registeredTools !== "object") {
71
+ logCompatibilityWarning();
72
+ throw new Error("PostHog MCP analytics SDK compatibility error: High-level server must have _registeredTools object. This requires MCP SDK v1.11 or higher.");
73
+ }
74
+ if (typeof server.tool !== "function") {
75
+ logCompatibilityWarning();
76
+ throw new Error("PostHog MCP analytics SDK compatibility error: High-level server must have tool() method. This requires MCP SDK v1.11 or higher.");
77
+ }
78
+ const targetServer = server.server;
79
+ validateLowLevelServer(targetServer);
80
+ return server;
81
+ }
82
+ validateLowLevelServer(server);
83
+ return server;
84
+ }
85
+ function validateLowLevelServer(server) {
86
+ if (!server || typeof server !== "object") {
87
+ logCompatibilityWarning();
88
+ throw new Error("PostHog MCP analytics SDK compatibility error: Server must be an object. Ensure you're using MCP SDK v1.11 or higher.");
89
+ }
90
+ const serverRecord = server;
91
+ if (typeof serverRecord.setRequestHandler !== "function") {
92
+ logCompatibilityWarning();
93
+ throw new Error("PostHog MCP analytics SDK compatibility error: Server must have a setRequestHandler method. This requires MCP SDK v1.11 or higher.");
94
+ }
95
+ if (!(serverRecord._requestHandlers && serverRecord._requestHandlers instanceof Map)) {
96
+ logCompatibilityWarning();
97
+ throw new Error("PostHog MCP analytics SDK compatibility error: Server._requestHandlers is not accessible. This requires MCP SDK v1.11 or higher.");
98
+ }
99
+ if (typeof serverRecord._requestHandlers.get !== "function") {
100
+ logCompatibilityWarning();
101
+ throw new Error("PostHog MCP analytics SDK compatibility error: Server._requestHandlers must be a Map with a get method. This requires MCP SDK v1.11 or higher.");
102
+ }
103
+ if (typeof serverRecord.getClientVersion !== "function") {
104
+ logCompatibilityWarning();
105
+ throw new Error("PostHog MCP analytics SDK compatibility error: Server.getClientVersion must be a function. This requires MCP SDK v1.11 or higher.");
106
+ }
107
+ if (!serverRecord._serverInfo || typeof serverRecord._serverInfo !== "object" || !("name" in serverRecord._serverInfo)) {
108
+ logCompatibilityWarning();
109
+ throw new Error("PostHog MCP analytics SDK compatibility error: Server._serverInfo is not accessible or missing name. This requires MCP SDK v1.11 or higher.");
110
+ }
111
+ }
112
+ function getMCPCompatibleErrorMessage(error) {
113
+ if (error instanceof Error) try {
114
+ return JSON.stringify(error, Object.getOwnPropertyNames(error));
115
+ } catch {
116
+ return "Unknown error";
117
+ }
118
+ else if (typeof error === "string") return error;
119
+ else if (typeof error === "object" && error !== null) return JSON.stringify(error);
120
+ return "Unknown error";
121
+ }
122
+ //#endregion
123
+ //#region src/modules/ids.ts
124
+ function newPrefixedId(prefix) {
125
+ return `${prefix}_${(0, node_crypto.randomUUID)()}`;
126
+ }
127
+ function deterministicPrefixedId(prefix, input) {
128
+ return `${prefix}_${(0, node_crypto.createHash)("sha256").update(input).digest("hex").slice(0, 32)}`;
129
+ }
130
+ //#endregion
131
+ //#region src/modules/event-types.ts
132
+ const MCPAnalyticsEventType = {
133
+ identify: "posthog:identify",
134
+ custom: "posthog:custom",
135
+ mcpInitialize: "mcp:initialize",
136
+ mcpPromptsGet: "mcp:prompts/get",
137
+ mcpPromptsList: "mcp:prompts/list",
138
+ mcpResourcesList: "mcp:resources/list",
139
+ mcpResourcesRead: "mcp:resources/read",
140
+ mcpToolsCall: "mcp:tools/call",
141
+ mcpToolsList: "mcp:tools/list"
142
+ };
143
+ //#endregion
144
+ //#region src/modules/validation.ts
145
+ const TAG_KEY_REGEX = /^[a-zA-Z0-9$_.:\- ]+$/;
146
+ const MAX_TAG_KEY_LENGTH = 32;
147
+ const MAX_TAG_VALUE_LENGTH = 200;
148
+ const MAX_TAG_ENTRIES = 50;
149
+ /**
150
+ * Validates and filters a tags object against PostHog MCP analytics tag constraints.
151
+ * Invalid entries are logged as warnings and dropped.
152
+ * Returns null if no valid entries remain.
153
+ */
154
+ function validateTags(tags) {
155
+ const entries = Object.entries(tags);
156
+ if (entries.length === 0) return null;
157
+ const valid = [];
158
+ for (const [key, value] of entries) {
159
+ if (typeof key !== "string" || !TAG_KEY_REGEX.test(key)) {
160
+ writeToLog(`Dropping invalid tag: "${String(key)}" — key contains invalid characters or is empty`);
161
+ continue;
162
+ }
163
+ if (key.length > MAX_TAG_KEY_LENGTH) {
164
+ writeToLog(`Dropping invalid tag: "${key}" — key exceeds max length of ${MAX_TAG_KEY_LENGTH}`);
165
+ continue;
166
+ }
167
+ if (typeof value !== "string") {
168
+ writeToLog(`Dropping invalid tag: "${key}" — non-string value (got ${typeof value})`);
169
+ continue;
170
+ }
171
+ if (value.length > MAX_TAG_VALUE_LENGTH) {
172
+ writeToLog(`Dropping invalid tag: "${key}" — value exceeds max length of ${MAX_TAG_VALUE_LENGTH}`);
173
+ continue;
174
+ }
175
+ if (value.includes("\n")) {
176
+ writeToLog(`Dropping invalid tag: "${key}" — value contains newline character`);
177
+ continue;
178
+ }
179
+ valid.push([key, value]);
180
+ }
181
+ if (valid.length === 0) return null;
182
+ if (valid.length > MAX_TAG_ENTRIES) {
183
+ writeToLog(`Dropping ${valid.length - MAX_TAG_ENTRIES} tag(s) — exceeds maximum of ${MAX_TAG_ENTRIES} entries per event`);
184
+ valid.length = MAX_TAG_ENTRIES;
185
+ }
186
+ return Object.fromEntries(valid);
187
+ }
188
+ //#endregion
189
+ //#region src/modules/internal.ts
190
+ /**
191
+ * Simple LRU cache for session identities.
192
+ * Prevents memory leaks by capping at maxSize entries.
193
+ * This cache persists across server instance restarts.
194
+ */
195
+ var IdentityCache = class {
196
+ constructor(maxSize = 1e3) {
197
+ this.cache = /* @__PURE__ */ new Map();
198
+ this.maxSize = maxSize;
199
+ }
200
+ get(sessionId) {
201
+ const entry = this.cache.get(sessionId);
202
+ if (entry) {
203
+ entry.timestamp = Date.now();
204
+ this.cache.delete(sessionId);
205
+ this.cache.set(sessionId, entry);
206
+ return entry.identity;
207
+ }
208
+ }
209
+ set(sessionId, identity) {
210
+ this.cache.delete(sessionId);
211
+ if (this.cache.size >= this.maxSize) {
212
+ const oldestKey = this.cache.keys().next().value;
213
+ if (oldestKey !== void 0) this.cache.delete(oldestKey);
214
+ }
215
+ this.cache.set(sessionId, {
216
+ identity,
217
+ timestamp: Date.now()
218
+ });
219
+ }
220
+ has(sessionId) {
221
+ return this.cache.has(sessionId);
222
+ }
223
+ size() {
224
+ return this.cache.size;
225
+ }
226
+ };
227
+ const _globalIdentityCache = new IdentityCache(1e3);
228
+ const _serverTracking = /* @__PURE__ */ new WeakMap();
229
+ function getServerTrackingData(server) {
230
+ return _serverTracking.get(server);
231
+ }
232
+ function setServerTrackingData(server, data) {
233
+ _serverTracking.set(server, data);
234
+ }
235
+ /**
236
+ * Deep comparison of two UserIdentity objects
237
+ */
238
+ function areIdentitiesEqual(a, b) {
239
+ if (a.userId !== b.userId) return false;
240
+ if (a.userName !== b.userName) return false;
241
+ const aData = a.userData || {};
242
+ const bData = b.userData || {};
243
+ const aKeys = Object.keys(aData);
244
+ const bKeys = Object.keys(bData);
245
+ if (aKeys.length !== bKeys.length) return false;
246
+ for (const key of aKeys) {
247
+ if (!(key in bData)) return false;
248
+ if (JSON.stringify(aData[key]) !== JSON.stringify(bData[key])) return false;
249
+ }
250
+ return true;
251
+ }
252
+ /**
253
+ * Merges two UserIdentity objects, overwriting userId and userName,
254
+ * but merging userData fields
255
+ */
256
+ function mergeIdentities(previous, next) {
257
+ if (!previous) return next;
258
+ return {
259
+ userId: next.userId,
260
+ userName: next.userName,
261
+ userData: {
262
+ ...previous.userData || {},
263
+ ...next.userData || {}
264
+ }
265
+ };
266
+ }
267
+ /**
268
+ * Handles user identification for a request.
269
+ * Calls the identify function if configured, compares with previous identity,
270
+ * and publishes an identify event only if the identity has changed.
271
+ *
272
+ * @param server - The MCP server instance
273
+ * @param data - The server tracking data
274
+ * @param request - The request object to pass to identify function
275
+ * @param extra - Optional extra parameters containing headers, sessionId, etc.
276
+ */
277
+ async function handleIdentify(server, data, request, extra) {
278
+ if (!data.options.identify) return;
279
+ const sessionId = data.sessionId;
280
+ const identifyEvent = {
281
+ sessionId,
282
+ resourceName: getRequestResourceName(request),
283
+ eventType: MCPAnalyticsEventType.identify,
284
+ parameters: {
285
+ request,
286
+ extra
287
+ },
288
+ timestamp: /* @__PURE__ */ new Date(),
289
+ redactionFn: data.options.redactSensitiveInformation
290
+ };
291
+ try {
292
+ const identityResult = await data.options.identify(request, extra);
293
+ if (identityResult) {
294
+ const currentSessionId = data.sessionId;
295
+ const previousIdentity = _globalIdentityCache.get(currentSessionId);
296
+ const mergedIdentity = mergeIdentities(previousIdentity, identityResult);
297
+ const hasChanged = !(previousIdentity && areIdentitiesEqual(previousIdentity, mergedIdentity));
298
+ _globalIdentityCache.set(currentSessionId, mergedIdentity);
299
+ data.identifiedSessions.set(data.sessionId, mergedIdentity);
300
+ if (hasChanged) {
301
+ writeToLog(`Identified session ${currentSessionId} with identity: ${JSON.stringify(mergedIdentity)}`);
302
+ publishEvent(server, identifyEvent);
303
+ }
304
+ } else writeToLog(`Warning: Supplied identify function returned null for session ${sessionId}`);
305
+ } catch (error) {
306
+ writeToLog(`Error: User supplied identify function threw an error while identifying session ${sessionId} - ${error}`);
307
+ }
308
+ }
309
+ /**
310
+ * Resolves the eventTags callback, validates the result, and returns validated tags.
311
+ * Returns null if no callback configured, callback returns nullish, or callback throws.
312
+ */
313
+ async function resolveEventTags(data, request, extra) {
314
+ if (!data.options.eventTags) return null;
315
+ try {
316
+ const raw = await data.options.eventTags(request, extra) ?? null;
317
+ if (!raw) return null;
318
+ return validateTags(raw);
319
+ } catch (e) {
320
+ writeToLog(`eventTags callback error: ${e}`);
321
+ return null;
322
+ }
323
+ }
324
+ /**
325
+ * Resolves the eventProperties callback and returns the result.
326
+ * Returns null if no callback configured, callback returns nullish, or callback throws.
327
+ */
328
+ async function resolveEventProperties(data, request, extra) {
329
+ if (!data.options.eventProperties) return null;
330
+ try {
331
+ return await data.options.eventProperties(request, extra) ?? null;
332
+ } catch (e) {
333
+ writeToLog(`eventProperties callback error: ${e}`);
334
+ return null;
335
+ }
336
+ }
337
+ function getRequestResourceName(request) {
338
+ if (!request || typeof request !== "object" || !("params" in request)) return "Unknown";
339
+ const params = request.params;
340
+ if (!params || typeof params !== "object" || !("name" in params)) return "Unknown";
341
+ return typeof params.name === "string" ? params.name : "Unknown";
342
+ }
343
+ //#endregion
344
+ //#region src/modules/constants.ts
345
+ const DEFAULT_CONTEXT_PARAMETER_DESCRIPTION = `Explain why you are calling this tool and how it fits into the user's overall goal. This parameter is used for analytics and user intent tracking. YOU MUST provide 15-25 words (count carefully). NEVER use first person ('I', 'we', 'you') - maintain third-person perspective. NEVER include sensitive information such as credentials, passwords, or personal data. Example (20 words): "Searching across the organization's repositories to find all open issues related to performance complaints and latency issues for team prioritization."`;
346
+ const POSTHOG_MCP_ANALYTICS_SOURCE = "posthog_mcp_analytics";
347
+ const PostHogMCPAnalyticsProperty = {
348
+ AiInputState: "$ai_input_state",
349
+ AiIsError: "$ai_is_error",
350
+ AiLatency: "$ai_latency",
351
+ AiOutputState: "$ai_output_state",
352
+ AiProduct: "$ai_product",
353
+ AiSessionId: "$ai_session_id",
354
+ AiSpanId: "$ai_span_id",
355
+ AiSpanName: "$ai_span_name",
356
+ AiTraceId: "$ai_trace_id",
357
+ ClientName: "$mcp_client_name",
358
+ ClientVersion: "$mcp_client_version",
359
+ DurationMs: "$mcp_duration_ms",
360
+ IsError: "$mcp_is_error",
361
+ Intent: "$mcp_intent",
362
+ Parameters: "$mcp_parameters",
363
+ ResourceName: "$mcp_resource_name",
364
+ Response: "$mcp_response",
365
+ ServerName: "$mcp_server_name",
366
+ ServerVersion: "$mcp_server_version",
367
+ SessionId: "$session_id",
368
+ Source: "$mcp_source",
369
+ ToolName: "$mcp_tool_name"
370
+ };
371
+ //#endregion
372
+ //#region src/modules/posthog-events.ts
373
+ const MCP_EVENT_PREFIX_REGEX = /^mcp:/;
374
+ const SLASH_REGEX = /\//g;
375
+ function getDistinctId(event) {
376
+ return event.identifyActorGivenId || event.sessionId || "anonymous";
377
+ }
378
+ function getTimestamp(event) {
379
+ return event.timestamp ? event.timestamp.toISOString() : (/* @__PURE__ */ new Date()).toISOString();
380
+ }
381
+ function buildPostHogCaptureEvents(event, options = {}) {
382
+ const batch = [buildCaptureEvent(event, options)];
383
+ if (event.isError && event.error) batch.push(buildExceptionEvent(event));
384
+ if (shouldBuildAISpan(event, options)) batch.push(buildAISpanEvent(event));
385
+ return batch;
386
+ }
387
+ function buildCaptureEvent(event, options) {
388
+ const distinctId = getDistinctId(event);
389
+ const eventName = mapEventType(event.eventType);
390
+ const timestamp = getTimestamp(event);
391
+ const properties = {
392
+ [PostHogMCPAnalyticsProperty.SessionId]: event.sessionId,
393
+ [PostHogMCPAnalyticsProperty.Source]: POSTHOG_MCP_ANALYTICS_SOURCE
394
+ };
395
+ addCommonEventProperties(event, properties);
396
+ addTraceReferenceProperties(event, properties, options);
397
+ addCustomEventProperties(event, properties);
398
+ return {
399
+ event: eventName,
400
+ distinct_id: distinctId,
401
+ properties,
402
+ timestamp,
403
+ type: "capture"
404
+ };
405
+ }
406
+ function shouldBuildAISpan(event, options) {
407
+ return options.enableAITracing === true && event.eventType === MCPAnalyticsEventType.mcpToolsCall;
408
+ }
409
+ function getAITraceId(event) {
410
+ return event.sessionId;
411
+ }
412
+ function getAISpanId(event) {
413
+ return event.id;
414
+ }
415
+ function addTraceReferenceProperties(event, properties, options) {
416
+ if (!shouldBuildAISpan(event, options)) return;
417
+ properties[PostHogMCPAnalyticsProperty.AiTraceId] = getAITraceId(event);
418
+ properties[PostHogMCPAnalyticsProperty.AiSpanId] = getAISpanId(event);
419
+ }
420
+ function addCommonEventProperties(event, properties) {
421
+ if (event.resourceName) {
422
+ properties[PostHogMCPAnalyticsProperty.ResourceName] = event.resourceName;
423
+ if (event.eventType === MCPAnalyticsEventType.mcpToolsCall) properties[PostHogMCPAnalyticsProperty.ToolName] = event.resourceName;
424
+ }
425
+ if (event.duration !== void 0) properties[PostHogMCPAnalyticsProperty.DurationMs] = event.duration;
426
+ if (event.serverName) properties[PostHogMCPAnalyticsProperty.ServerName] = event.serverName;
427
+ if (event.serverVersion) properties[PostHogMCPAnalyticsProperty.ServerVersion] = event.serverVersion;
428
+ if (event.clientName) properties[PostHogMCPAnalyticsProperty.ClientName] = event.clientName;
429
+ if (event.clientVersion) properties[PostHogMCPAnalyticsProperty.ClientVersion] = event.clientVersion;
430
+ if (event.userIntent) properties[PostHogMCPAnalyticsProperty.Intent] = event.userIntent;
431
+ if (event.isError !== void 0) properties[PostHogMCPAnalyticsProperty.IsError] = event.isError;
432
+ if (event.parameters !== void 0) properties[PostHogMCPAnalyticsProperty.Parameters] = event.parameters;
433
+ if (event.response !== void 0) properties[PostHogMCPAnalyticsProperty.Response] = event.response;
434
+ const $set = {};
435
+ if (event.identifyActorName) $set.name = event.identifyActorName;
436
+ if (event.identifyActorData) Object.assign($set, event.identifyActorData);
437
+ if (Object.keys($set).length > 0) properties.$set = $set;
438
+ }
439
+ function addCustomEventProperties(event, properties) {
440
+ if (event.tags) for (const [key, value] of Object.entries(event.tags)) properties[key] = value;
441
+ if (event.properties) for (const [key, value] of Object.entries(event.properties)) properties[key] = value;
442
+ }
443
+ function buildExceptionEvent(event) {
444
+ const distinctId = getDistinctId(event);
445
+ const timestamp = getTimestamp(event);
446
+ const properties = {
447
+ $exception_source: "backend",
448
+ [PostHogMCPAnalyticsProperty.SessionId]: event.sessionId
449
+ };
450
+ if (event.error) {
451
+ if (event.error.message) properties.$exception_message = event.error.message;
452
+ if (event.error.type) properties.$exception_type = event.error.type;
453
+ if (event.error.stack) properties.$exception_stacktrace = event.error.stack;
454
+ }
455
+ if (event.resourceName) {
456
+ properties[PostHogMCPAnalyticsProperty.ResourceName] = event.resourceName;
457
+ if (event.eventType === MCPAnalyticsEventType.mcpToolsCall) properties[PostHogMCPAnalyticsProperty.ToolName] = event.resourceName;
458
+ }
459
+ if (event.serverName) properties[PostHogMCPAnalyticsProperty.ServerName] = event.serverName;
460
+ if (event.serverVersion) properties[PostHogMCPAnalyticsProperty.ServerVersion] = event.serverVersion;
461
+ if (event.clientName) properties[PostHogMCPAnalyticsProperty.ClientName] = event.clientName;
462
+ if (event.clientVersion) properties[PostHogMCPAnalyticsProperty.ClientVersion] = event.clientVersion;
463
+ return {
464
+ event: "$exception",
465
+ distinct_id: distinctId,
466
+ properties,
467
+ timestamp,
468
+ type: "capture"
469
+ };
470
+ }
471
+ function buildAISpanEvent(event) {
472
+ const distinctId = getDistinctId(event);
473
+ const timestamp = getTimestamp(event);
474
+ const properties = {
475
+ [PostHogMCPAnalyticsProperty.AiSessionId]: `posthog_mcp_analytics_${event.sessionId}`,
476
+ [PostHogMCPAnalyticsProperty.AiTraceId]: getAITraceId(event),
477
+ [PostHogMCPAnalyticsProperty.AiSpanId]: getAISpanId(event),
478
+ [PostHogMCPAnalyticsProperty.AiSpanName]: event.resourceName || "unknown_tool",
479
+ [PostHogMCPAnalyticsProperty.AiIsError]: event.isError,
480
+ [PostHogMCPAnalyticsProperty.SessionId]: event.sessionId,
481
+ [PostHogMCPAnalyticsProperty.Source]: POSTHOG_MCP_ANALYTICS_SOURCE
482
+ };
483
+ if (event.duration !== void 0) properties[PostHogMCPAnalyticsProperty.AiLatency] = event.duration / 1e3;
484
+ if (event.isError && event.error) properties.$ai_error = event.error;
485
+ if (event.parameters !== void 0) properties[PostHogMCPAnalyticsProperty.AiInputState] = event.parameters;
486
+ if (event.response !== void 0) properties[PostHogMCPAnalyticsProperty.AiOutputState] = event.response;
487
+ if (event.serverName) properties[PostHogMCPAnalyticsProperty.ServerName] = event.serverName;
488
+ if (event.clientName) properties[PostHogMCPAnalyticsProperty.ClientName] = event.clientName;
489
+ if (event.userIntent) properties[PostHogMCPAnalyticsProperty.Intent] = event.userIntent;
490
+ if (event.tags) for (const [key, value] of Object.entries(event.tags)) properties[key] = value;
491
+ if (event.properties) for (const [key, value] of Object.entries(event.properties)) properties[key] = value;
492
+ return {
493
+ event: "$ai_span",
494
+ distinct_id: distinctId,
495
+ properties,
496
+ timestamp,
497
+ type: "capture"
498
+ };
499
+ }
500
+ function mapEventType(eventType) {
501
+ return {
502
+ [MCPAnalyticsEventType.mcpToolsCall]: "mcp_tool_call",
503
+ [MCPAnalyticsEventType.mcpToolsList]: "mcp_tools_list",
504
+ [MCPAnalyticsEventType.mcpInitialize]: "mcp_initialize",
505
+ [MCPAnalyticsEventType.mcpResourcesRead]: "mcp_resource_read",
506
+ [MCPAnalyticsEventType.mcpResourcesList]: "mcp_resources_list",
507
+ [MCPAnalyticsEventType.mcpPromptsGet]: "mcp_prompt_get",
508
+ [MCPAnalyticsEventType.mcpPromptsList]: "mcp_prompts_list"
509
+ }[eventType] || `mcp_${eventType.replace(MCP_EVENT_PREFIX_REGEX, "").replace(SLASH_REGEX, "_")}`;
510
+ }
511
+ //#endregion
512
+ //#region src/modules/redaction.ts
513
+ /**
514
+ * Set of field names that should be protected from redaction.
515
+ * These fields contain system-level identifiers and metadata that
516
+ * need to be preserved for analytics tracking.
517
+ */
518
+ const PROTECTED_FIELDS = new Set([
519
+ "sessionId",
520
+ "id",
521
+ "apiKey",
522
+ "server",
523
+ "identifyActorGivenId",
524
+ "identifyActorName",
525
+ "identifyData",
526
+ "resourceName",
527
+ "eventType",
528
+ "actorId",
529
+ "tags",
530
+ "properties"
531
+ ]);
532
+ /**
533
+ * Recursively applies a redaction function to all string values in an object.
534
+ * This ensures that sensitive information is removed from all string fields
535
+ * before events are sent to the analytics service.
536
+ *
537
+ * @param obj - The object to redact strings from
538
+ * @param redactFn - The redaction function to apply to each string
539
+ * @param path - The current path in the object tree (used to check protected fields)
540
+ * @param isProtected - Whether the current object/value is within a protected field
541
+ * @returns A new object with all strings redacted
542
+ */
543
+ async function redactStringsInObject(obj, redactFn, path = "", isProtected = false) {
544
+ if (obj === null || obj === void 0) return obj;
545
+ if (typeof obj === "string") {
546
+ if (isProtected) return obj;
547
+ return await redactFn(obj);
548
+ }
549
+ if (Array.isArray(obj)) return Promise.all(obj.map((item, index) => redactStringsInObject(item, redactFn, `${path}[${index}]`, isProtected)));
550
+ if (obj instanceof Date) return obj;
551
+ if (typeof obj === "object") {
552
+ const redactedObj = {};
553
+ for (const [key, value] of Object.entries(obj)) {
554
+ if (typeof value === "function" || value === void 0) continue;
555
+ redactedObj[key] = await redactStringsInObject(value, redactFn, path ? `${path}.${key}` : key, isProtected || path === "" && PROTECTED_FIELDS.has(key));
556
+ }
557
+ return redactedObj;
558
+ }
559
+ return obj;
560
+ }
561
+ /**
562
+ * Applies the customer's redaction function to all string fields in an Event object.
563
+ * This is the main entry point for redacting sensitive information from events
564
+ * before they are sent to the analytics service.
565
+ *
566
+ * @param event - The event to redact
567
+ * @param redactFn - The customer's redaction function
568
+ * @returns A new event object with all strings redacted
569
+ */
570
+ function redactEvent(event, redactFn) {
571
+ return redactStringsInObject(event, redactFn, "", false);
572
+ }
573
+ //#endregion
574
+ //#region src/modules/mcp-payloads.ts
575
+ const CONTEXT_ARGUMENT_NAME = "context";
576
+ const REDACTED_VALUE = "[redacted]";
577
+ const BASE64_PATTERN = /^[A-Za-z0-9+/\n\r]+=*$/;
578
+ const SIZE_GATE = 10240;
579
+ const POSTHOG_TOKEN_PATTERN = /\bph[a-z]_[A-Za-z0-9_-]{20,}\b/g;
580
+ const SENSITIVE_KEY_PATTERN = /^(authorization|cookie|set-cookie|x-api-key|api[-_]?key|api[-_]?token|access[-_]?token|refresh[-_]?token|token|password|secret|client[-_]?secret|private[-_]?key)$/i;
581
+ function isRecord$1(value) {
582
+ return !!value && typeof value === "object" && !Array.isArray(value);
583
+ }
584
+ function shouldRedactKey(key) {
585
+ return SENSITIVE_KEY_PATTERN.test(key);
586
+ }
587
+ function sanitizeString(value) {
588
+ if (value.length >= SIZE_GATE && BASE64_PATTERN.test(value)) return "[binary data redacted - not supported by PostHog MCP analytics]";
589
+ return value.replace(POSTHOG_TOKEN_PATTERN, REDACTED_VALUE);
590
+ }
591
+ function sanitizeCapturedValue(value) {
592
+ if (value == null) return value;
593
+ if (typeof value === "string") return sanitizeString(value);
594
+ if (Array.isArray(value)) return value.map(sanitizeCapturedValue);
595
+ if (value instanceof Date) return value;
596
+ if (typeof value !== "object") return value;
597
+ const result = {};
598
+ for (const [key, nestedValue] of Object.entries(value)) result[key] = shouldRedactKey(key) ? REDACTED_VALUE : sanitizeCapturedValue(nestedValue);
599
+ return result;
600
+ }
601
+ function buildCapturedMcpArguments(argumentsValue) {
602
+ if (!isRecord$1(argumentsValue)) return sanitizeCapturedValue(argumentsValue);
603
+ const capturedArguments = {};
604
+ for (const [key, value] of Object.entries(argumentsValue)) {
605
+ if (key === CONTEXT_ARGUMENT_NAME) continue;
606
+ capturedArguments[key] = sanitizeCapturedValue(value);
607
+ }
608
+ return capturedArguments;
609
+ }
610
+ function buildCapturedMcpParams(params) {
611
+ if (!isRecord$1(params)) return sanitizeCapturedValue(params);
612
+ const capturedParams = {};
613
+ for (const [key, value] of Object.entries(params)) capturedParams[key] = key === "arguments" ? buildCapturedMcpArguments(value) : sanitizeCapturedValue(value);
614
+ return capturedParams;
615
+ }
616
+ function buildCapturedMcpParameters(request) {
617
+ if (!isRecord$1(request)) return { request: sanitizeCapturedValue(request) };
618
+ const capturedRequest = {};
619
+ for (const key of [
620
+ "id",
621
+ "jsonrpc",
622
+ "method"
623
+ ]) if (key in request) capturedRequest[key] = sanitizeCapturedValue(request[key]);
624
+ if ("params" in request) capturedRequest.params = buildCapturedMcpParams(request.params);
625
+ return { request: capturedRequest };
626
+ }
627
+ //#endregion
628
+ //#region src/modules/sanitization.ts
629
+ function isRecord(value) {
630
+ return !!value && typeof value === "object" && !Array.isArray(value);
631
+ }
632
+ /**
633
+ * Sanitizes an event by redacting non-text content blocks from responses
634
+ * and large base64-encoded strings from parameters.
635
+ *
636
+ * This is a synchronous operation that returns a new object without mutating the original.
637
+ * It should run after customer redaction in the event pipeline.
638
+ */
639
+ function sanitizeEvent(event) {
640
+ const result = { ...event };
641
+ if (result.response != null) result.response = sanitizeResponse(result.response);
642
+ if (result.parameters != null) result.parameters = sanitizeParameters(result.parameters);
643
+ return result;
644
+ }
645
+ /**
646
+ * Sanitizes response content blocks by replacing non-text content types
647
+ * with informative redaction messages.
648
+ */
649
+ function sanitizeResponse(response) {
650
+ if (response == null || typeof response !== "object") return sanitizeCapturedValue(response);
651
+ const sanitized = sanitizeCapturedValue(response);
652
+ if (!isRecord(sanitized)) return sanitized;
653
+ const result = { ...sanitized };
654
+ const content = result.content;
655
+ if (Array.isArray(content)) result.content = content.map(sanitizeContentBlock);
656
+ if (result.structuredContent != null && typeof result.structuredContent === "object") result.structuredContent = sanitizeCapturedValue(result.structuredContent);
657
+ return result;
658
+ }
659
+ /**
660
+ * Sanitizes a single content block based on its type discriminator.
661
+ */
662
+ function sanitizeContentBlock(block) {
663
+ if (block == null || typeof block !== "object") return block;
664
+ if (!isRecord(block)) return block;
665
+ switch (block.type) {
666
+ case "text": return sanitizeCapturedValue(block);
667
+ case "image": return {
668
+ type: "text",
669
+ text: "[image content redacted - not supported by PostHog MCP analytics]"
670
+ };
671
+ case "audio": return {
672
+ type: "text",
673
+ text: "[audio content redacted - not supported by PostHog MCP analytics]"
674
+ };
675
+ case "resource": return sanitizeResourceBlock(block);
676
+ case "resource_link": return sanitizeCapturedValue(block);
677
+ default: return {
678
+ type: "text",
679
+ text: `[unsupported content type "${block.type}" redacted - not supported by PostHog MCP analytics]`
680
+ };
681
+ }
682
+ }
683
+ /**
684
+ * Sanitizes an embedded resource content block.
685
+ * BlobResourceContents (has `blob` field) are redacted.
686
+ * TextResourceContents (has `text` field) pass through.
687
+ */
688
+ function sanitizeResourceBlock(block) {
689
+ if (isRecord(block.resource) && "blob" in block.resource) return {
690
+ type: "text",
691
+ text: "[binary resource content redacted - not supported by PostHog MCP analytics]"
692
+ };
693
+ return sanitizeCapturedValue(block);
694
+ }
695
+ /**
696
+ * Recursively scans parameters for large base64-encoded strings and replaces them.
697
+ * Uses a size gate (10KB) to avoid regex testing on small strings.
698
+ */
699
+ function sanitizeParameters(obj) {
700
+ return sanitizeCapturedValue(obj);
701
+ }
702
+ //#endregion
703
+ //#region package.json
704
+ var version = "0.0.0";
705
+ //#endregion
706
+ //#region src/modules/session.ts
707
+ function newSessionId() {
708
+ return newPrefixedId("ses");
709
+ }
710
+ /**
711
+ * Creates a deterministic SDK session ID from an MCP sessionId.
712
+ * The same inputs will always produce the same session ID, enabling correlation across server restarts.
713
+ *
714
+ * @param mcpSessionId - The session ID from the MCP protocol
715
+ * @returns An SDK session ID with "ses" prefix derived deterministically from the inputs
716
+ */
717
+ function deriveSessionIdFromMCPSession(mcpSessionId) {
718
+ return deterministicPrefixedId("ses", mcpSessionId);
719
+ }
720
+ /**
721
+ * Gets or generates a session ID for the server.
722
+ * Prioritizes MCP protocol sessionId over PostHog MCP analytics-generated sessionId.
723
+ *
724
+ * @param server - The MCP server instance
725
+ * @param extra - Optional extra data containing MCP sessionId
726
+ * @returns The session ID to use for events
727
+ */
728
+ function getServerSessionId(server, extra) {
729
+ const data = getServerTrackingData(server);
730
+ if (!data) throw new Error("Server tracking data not found");
731
+ const mcpSessionId = extra?.sessionId;
732
+ if (mcpSessionId) {
733
+ data.sessionId = deriveSessionIdFromMCPSession(mcpSessionId);
734
+ data.lastMcpSessionId = mcpSessionId;
735
+ data.sessionSource = "mcp";
736
+ setServerTrackingData(server, data);
737
+ setLastActivity(server);
738
+ return data.sessionId;
739
+ }
740
+ if (data.sessionSource === "mcp" && data.lastMcpSessionId) {
741
+ setLastActivity(server);
742
+ return data.sessionId;
743
+ }
744
+ if (Date.now() - data.lastActivity.getTime() > 1800 * 1e3) {
745
+ data.sessionId = newSessionId();
746
+ data.sessionSource = "generated";
747
+ setServerTrackingData(server, data);
748
+ }
749
+ setLastActivity(server);
750
+ return data.sessionId;
751
+ }
752
+ function setLastActivity(server) {
753
+ const data = getServerTrackingData(server);
754
+ if (!data) throw new Error("Server tracking data not found");
755
+ data.lastActivity = /* @__PURE__ */ new Date();
756
+ setServerTrackingData(server, data);
757
+ }
758
+ function getSessionInfo(server, data) {
759
+ let clientInfo = {
760
+ name: void 0,
761
+ version: void 0
762
+ };
763
+ if (!data?.sessionInfo.clientName) clientInfo = server.getClientVersion();
764
+ const actorInfo = data?.identifiedSessions.get(data.sessionId);
765
+ const sessionInfo = {
766
+ ipAddress: void 0,
767
+ sdkLanguage: "TypeScript",
768
+ sdkVersion: version,
769
+ serverName: server._serverInfo?.name,
770
+ serverVersion: server._serverInfo?.version,
771
+ clientName: clientInfo?.name,
772
+ clientVersion: clientInfo?.version,
773
+ identifyActorGivenId: actorInfo?.userId,
774
+ identifyActorName: actorInfo?.userName,
775
+ identifyActorData: actorInfo?.userData || {}
776
+ };
777
+ if (!data) return sessionInfo;
778
+ data.sessionInfo = sessionInfo;
779
+ setServerTrackingData(server, data);
780
+ return data.sessionInfo;
781
+ }
782
+ const MAX_STRING_LENGTH = 32768;
783
+ const MAX_EVENT_BYTES = 102400;
784
+ const MAX_USER_INTENT_LENGTH = 2048;
785
+ const MAX_ERROR_MESSAGE_LENGTH = 2048;
786
+ const MAX_RESOURCE_NAME_LENGTH = 256;
787
+ const MAX_METADATA_LENGTH = 256;
788
+ const MAX_STACK_FRAMES$1 = 50;
789
+ const MAX_CONTENT_TEXT_LENGTH = 32768;
790
+ const TRUNCATION_SUFFIX = "...";
791
+ /**
792
+ * Recursively normalizes a value, handling:
793
+ * - String truncation (> MAX_STRING_LENGTH)
794
+ * - Non-serializable values (functions, symbols, undefined, BigInt, NaN, Infinity)
795
+ * - Date objects -> ISO string
796
+ * - Circular reference detection
797
+ * - Depth limiting
798
+ * - Breadth limiting
799
+ */
800
+ function normalize(input, depth = 10, maxBreadth = 100, maxStringLength = MAX_STRING_LENGTH) {
801
+ return visit(input, depth, maxBreadth, maxStringLength, /* @__PURE__ */ new WeakSet());
802
+ }
803
+ function visit(value, remainingDepth, maxBreadth, maxStringLength, memo) {
804
+ if (value === null) return null;
805
+ if (value === void 0) return "[undefined]";
806
+ if (typeof value === "boolean") return value;
807
+ if (typeof value === "number") {
808
+ if (Number.isNaN(value)) return "[NaN]";
809
+ if (!Number.isFinite(value)) return value > 0 ? "[Infinity]" : "[-Infinity]";
810
+ return value;
811
+ }
812
+ if (typeof value === "bigint") return `[BigInt: ${value}]`;
813
+ if (typeof value === "string") {
814
+ if (value.length > maxStringLength) return value.slice(0, maxStringLength) + TRUNCATION_SUFFIX;
815
+ return value;
816
+ }
817
+ if (typeof value === "symbol") {
818
+ const desc = value.description;
819
+ return desc ? `[Symbol(${desc})]` : "[Symbol()]";
820
+ }
821
+ if (typeof value === "function") return `[Function: ${value.name || "<anonymous>"}]`;
822
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? "[Invalid Date]" : value.toISOString();
823
+ if (typeof value === "object") {
824
+ if (memo.has(value)) return "[Circular ~]";
825
+ if (remainingDepth <= 0) return Array.isArray(value) ? "[Array]" : "[Object]";
826
+ memo.add(value);
827
+ let result;
828
+ if (Array.isArray(value)) result = visitArray(value, remainingDepth - 1, maxBreadth, maxStringLength, memo);
829
+ else result = visitObject(value, remainingDepth - 1, maxBreadth, maxStringLength, memo);
830
+ memo.delete(value);
831
+ return result;
832
+ }
833
+ return String(value);
834
+ }
835
+ function visitArray(arr, remainingDepth, maxBreadth, maxStringLength, memo) {
836
+ const result = [];
837
+ for (let i = 0; i < arr.length; i++) {
838
+ if (i >= maxBreadth) {
839
+ result.push("[MaxProperties ~]");
840
+ break;
841
+ }
842
+ result.push(visit(arr[i], remainingDepth, maxBreadth, maxStringLength, memo));
843
+ }
844
+ return result;
845
+ }
846
+ function visitObject(obj, remainingDepth, maxBreadth, maxStringLength, memo) {
847
+ const result = {};
848
+ const keys = Object.keys(obj);
849
+ let count = 0;
850
+ for (const key of keys) {
851
+ if (count >= maxBreadth) {
852
+ result["..."] = "[MaxProperties ~]";
853
+ break;
854
+ }
855
+ if (obj[key] === void 0) continue;
856
+ result[key] = visit(obj[key], remainingDepth, maxBreadth, maxStringLength, memo);
857
+ count++;
858
+ }
859
+ return result;
860
+ }
861
+ function truncateString(str, maxLength) {
862
+ if (str == null) return str;
863
+ if (str.length <= maxLength) return str;
864
+ return str.slice(0, maxLength) + TRUNCATION_SUFFIX;
865
+ }
866
+ function truncateStackFrames(frames) {
867
+ if (!frames || frames.length <= MAX_STACK_FRAMES$1) return frames;
868
+ const half = Math.floor(MAX_STACK_FRAMES$1 / 2);
869
+ return [...frames.slice(0, half), ...frames.slice(-half)];
870
+ }
871
+ function truncateResponseContent(response) {
872
+ if (response == null || typeof response !== "object") return response;
873
+ const result = { ...response };
874
+ if (Array.isArray(result.content)) result.content = result.content.map((block) => {
875
+ if (block != null && typeof block === "object" && "type" in block && "text" in block && block?.type === "text" && typeof block.text === "string" && block.text.length > MAX_CONTENT_TEXT_LENGTH) return {
876
+ ...block,
877
+ text: block.text.slice(0, MAX_CONTENT_TEXT_LENGTH) + TRUNCATION_SUFFIX
878
+ };
879
+ return block;
880
+ });
881
+ return result;
882
+ }
883
+ /**
884
+ * Calculates the UTF-8 byte size of a JSON-serialized value.
885
+ */
886
+ const textEncoder = new TextEncoder();
887
+ function jsonByteSize(value) {
888
+ return textEncoder.encode(JSON.stringify(value)).length;
889
+ }
890
+ /**
891
+ * Finds and truncates the largest string values in an object to fit within a byte budget.
892
+ * Last-resort mechanism when depth reduction alone isn't enough.
893
+ * Iterates until the result fits or no further reduction is possible.
894
+ */
895
+ function truncateLargestFields(obj, maxBytes) {
896
+ const result = structuredClone(obj);
897
+ for (let attempt = 0; attempt < 10; attempt++) {
898
+ const currentSize = jsonByteSize(result);
899
+ if (currentSize <= maxBytes) return result;
900
+ const excess = currentSize - maxBytes;
901
+ const stringPaths = [];
902
+ collectStringPaths(result, [], stringPaths);
903
+ stringPaths.sort((a, b) => b.length - a.length);
904
+ if (stringPaths.length === 0) break;
905
+ let remaining = excess + 200;
906
+ let truncated = false;
907
+ for (const { path, length } of stringPaths) {
908
+ if (remaining <= 0) break;
909
+ const reduction = Math.min(remaining, Math.floor(length * .5));
910
+ if (reduction < 10) continue;
911
+ const newLength = length - reduction;
912
+ const currentValue = getNestedValue(result, path);
913
+ if (typeof currentValue !== "string") continue;
914
+ setNestedValue(result, path, currentValue.slice(0, newLength) + TRUNCATION_SUFFIX);
915
+ remaining -= reduction;
916
+ truncated = true;
917
+ }
918
+ if (!truncated) break;
919
+ }
920
+ return result;
921
+ }
922
+ function collectStringPaths(obj, currentPath, results) {
923
+ if (typeof obj === "string" && obj.length > 100) {
924
+ results.push({
925
+ path: [...currentPath],
926
+ length: obj.length
927
+ });
928
+ return;
929
+ }
930
+ if (Array.isArray(obj)) {
931
+ for (const [i, item] of obj.entries()) collectStringPaths(item, [...currentPath, String(i)], results);
932
+ return;
933
+ }
934
+ if (obj != null && typeof obj === "object") for (const [key, value] of Object.entries(obj)) collectStringPaths(value, [...currentPath, key], results);
935
+ }
936
+ function getNestedValue(obj, path) {
937
+ let current = obj;
938
+ for (const key of path) {
939
+ if (current == null || typeof current !== "object") return;
940
+ current = current[key];
941
+ }
942
+ return current;
943
+ }
944
+ function setNestedValue(obj, path, value) {
945
+ let current = obj;
946
+ for (let i = 0; i < path.length - 1; i++) {
947
+ if (current == null || typeof current !== "object") return;
948
+ current = current[path[i]];
949
+ }
950
+ const finalKey = path.at(-1);
951
+ if (finalKey !== void 0 && current != null && typeof current === "object") current[finalKey] = value;
952
+ }
953
+ /**
954
+ * Ensures an event fits within MAX_EVENT_BYTES by progressively reducing
955
+ * normalization depth, then truncating largest string fields as a last resort.
956
+ */
957
+ function truncateToSize(event) {
958
+ if (jsonByteSize(event) <= 102400) return event;
959
+ for (let depth = 9; depth >= 1; depth--) {
960
+ const reduced = { ...event };
961
+ if (reduced.parameters != null) reduced.parameters = normalize(reduced.parameters, depth);
962
+ if (reduced.response != null) reduced.response = normalize(reduced.response, depth);
963
+ if (reduced.identifyActorData != null) reduced.identifyActorData = normalize(reduced.identifyActorData, depth);
964
+ if (reduced.error != null) reduced.error = normalize(reduced.error, depth);
965
+ if (jsonByteSize(reduced) <= 102400) return reduced;
966
+ }
967
+ const minimal = { ...event };
968
+ if (minimal.parameters != null) minimal.parameters = normalize(minimal.parameters, 1);
969
+ if (minimal.response != null) minimal.response = normalize(minimal.response, 1);
970
+ if (minimal.identifyActorData != null) minimal.identifyActorData = normalize(minimal.identifyActorData, 1);
971
+ if (minimal.error != null) minimal.error = normalize(minimal.error, 1);
972
+ return truncateLargestFields(minimal, MAX_EVENT_BYTES);
973
+ }
974
+ /**
975
+ * Applies layered truncation to an event:
976
+ * 1. Field-level string limits (userIntent, resourceName, metadata fields, error.message)
977
+ * 2. Error frame limiting (first 25 + last 25 if > 50)
978
+ * 3. Response content text limits (32KB per text block)
979
+ * 4. Recursive normalization on user-controlled fields
980
+ * 5. Size-targeted truncation (progressive depth reduction + last-resort string truncation)
981
+ */
982
+ function truncateEvent(event) {
983
+ const result = { ...event };
984
+ result.userIntent = truncateString(result.userIntent, MAX_USER_INTENT_LENGTH);
985
+ result.resourceName = truncateString(result.resourceName, MAX_RESOURCE_NAME_LENGTH);
986
+ result.serverName = truncateString(result.serverName, MAX_METADATA_LENGTH);
987
+ result.serverVersion = truncateString(result.serverVersion, MAX_METADATA_LENGTH);
988
+ result.clientName = truncateString(result.clientName, MAX_METADATA_LENGTH);
989
+ result.clientVersion = truncateString(result.clientVersion, MAX_METADATA_LENGTH);
990
+ if (result.error != null && typeof result.error === "object") {
991
+ const error = { ...result.error };
992
+ error.message = truncateString(error.message, MAX_ERROR_MESSAGE_LENGTH);
993
+ if (error.frames !== void 0) error.frames = truncateStackFrames(error.frames);
994
+ result.error = error;
995
+ }
996
+ result.response = truncateResponseContent(result.response);
997
+ if (result.parameters != null) result.parameters = normalize(result.parameters);
998
+ if (result.response != null) result.response = normalize(result.response);
999
+ if (result.identifyActorData != null) result.identifyActorData = normalize(result.identifyActorData);
1000
+ if (result.error != null) result.error = normalize(result.error);
1001
+ return truncateToSize(result);
1002
+ }
1003
+ //#endregion
1004
+ //#region src/modules/event-queue.ts
1005
+ var EventQueue = class {
1006
+ constructor() {
1007
+ this.queue = [];
1008
+ this.processing = false;
1009
+ this.maxQueueSize = 1e4;
1010
+ this.concurrency = 5;
1011
+ this.activeRequests = 0;
1012
+ this.host = "https://us.i.posthog.com";
1013
+ this.posthogOptions = {};
1014
+ this.posthogClients = /* @__PURE__ */ new Map();
1015
+ }
1016
+ configure(host) {
1017
+ this.host = host;
1018
+ this.posthogClients.clear();
1019
+ }
1020
+ configurePostHogOptions(posthogOptions) {
1021
+ this.posthogOptions = {
1022
+ ...this.posthogOptions,
1023
+ ...posthogOptions
1024
+ };
1025
+ if (posthogOptions.host) this.host = posthogOptions.host;
1026
+ this.posthogClients.clear();
1027
+ }
1028
+ add(event, posthogClient, enableAITracing = false) {
1029
+ if (this.queue.length >= this.maxQueueSize) {
1030
+ writeToLog("Event queue full, dropping oldest event");
1031
+ this.queue.shift();
1032
+ }
1033
+ this.queue.push({
1034
+ enableAITracing,
1035
+ event,
1036
+ posthogClient
1037
+ });
1038
+ this.process();
1039
+ }
1040
+ async process() {
1041
+ if (this.processing) return;
1042
+ this.processing = true;
1043
+ while (this.queue.length > 0 && this.activeRequests < this.concurrency) {
1044
+ const queuedEvent = this.queue.shift();
1045
+ if (!queuedEvent) continue;
1046
+ const { enableAITracing, event, posthogClient } = queuedEvent;
1047
+ if (event.redactionFn) try {
1048
+ const redactedEvent = await redactEvent(event, event.redactionFn);
1049
+ event.redactionFn = void 0;
1050
+ Object.assign(event, redactedEvent);
1051
+ } catch (error) {
1052
+ writeToLog(`Failed to redact event: ${error}`);
1053
+ continue;
1054
+ }
1055
+ try {
1056
+ Object.assign(event, sanitizeEvent(event));
1057
+ } catch (error) {
1058
+ writeToLog(`Failed to sanitize event: ${error}`);
1059
+ continue;
1060
+ }
1061
+ try {
1062
+ Object.assign(event, truncateEvent(event));
1063
+ } catch (error) {
1064
+ writeToLog(`Failed to truncate event: ${error}`);
1065
+ continue;
1066
+ }
1067
+ event.id = event.id || newPrefixedId("evt");
1068
+ this.activeRequests++;
1069
+ try {
1070
+ this.sendEvent(event, posthogClient, enableAITracing);
1071
+ } finally {
1072
+ this.activeRequests--;
1073
+ this.process();
1074
+ }
1075
+ }
1076
+ this.processing = false;
1077
+ }
1078
+ sendEvent(event, posthogClientOverride, enableAITracing = false) {
1079
+ const posthogClient = this.getPostHogClient(event.apiKey, posthogClientOverride);
1080
+ if (posthogClient) try {
1081
+ for (const captureEvent of buildPostHogCaptureEvents(event, { enableAITracing })) posthogClient.capture({
1082
+ distinctId: captureEvent.distinct_id,
1083
+ event: captureEvent.event,
1084
+ properties: captureEvent.properties,
1085
+ timestamp: new Date(captureEvent.timestamp)
1086
+ });
1087
+ writeToLog(`Queued PostHog event ${event.id} | ${event.eventType} | ${event.duration} ms | ${event.identifyActorGivenId || "anonymous"}`);
1088
+ writeToLog(`Event details: ${JSON.stringify(event)}`);
1089
+ } catch (error) {
1090
+ writeToLog(`Failed to queue PostHog event ${event.id}: ${getMCPCompatibleErrorMessage(error)}`);
1091
+ throw error;
1092
+ }
1093
+ }
1094
+ getPostHogClient(apiKey, posthogClient) {
1095
+ if (posthogClient) return posthogClient;
1096
+ if (!apiKey) return;
1097
+ const existingClient = this.posthogClients.get(apiKey);
1098
+ if (existingClient) return existingClient;
1099
+ const client = new posthog_node.PostHog(apiKey, {
1100
+ ...this.posthogOptions,
1101
+ host: this.host
1102
+ });
1103
+ this.posthogClients.set(apiKey, client);
1104
+ return client;
1105
+ }
1106
+ delay(ms) {
1107
+ return new Promise((resolve) => setTimeout(resolve, ms));
1108
+ }
1109
+ getStats() {
1110
+ return {
1111
+ queueLength: this.queue.length,
1112
+ activeRequests: this.activeRequests,
1113
+ isProcessing: this.processing
1114
+ };
1115
+ }
1116
+ async destroy() {
1117
+ this.add = () => {
1118
+ writeToLog("Queue is shutting down, event dropped");
1119
+ };
1120
+ const timeout = 5e3;
1121
+ const start = Date.now();
1122
+ while ((this.queue.length > 0 || this.activeRequests > 0) && Date.now() - start < timeout) await this.delay(100);
1123
+ if (this.queue.length > 0) writeToLog(`Shutting down with ${this.queue.length} events still in queue`);
1124
+ const shutdowns = [];
1125
+ for (const client of this.posthogClients.values()) if (client.shutdown) shutdowns.push(client.shutdown(timeout));
1126
+ else if (client.flush) shutdowns.push(client.flush());
1127
+ await Promise.allSettled(shutdowns);
1128
+ }
1129
+ };
1130
+ const eventQueue = new EventQueue();
1131
+ try {
1132
+ if (typeof process !== "undefined" && typeof process.once === "function") {
1133
+ process.once("SIGINT", () => eventQueue.destroy());
1134
+ process.once("SIGTERM", () => eventQueue.destroy());
1135
+ process.once("beforeExit", () => eventQueue.destroy());
1136
+ }
1137
+ } catch {}
1138
+ function publishEvent(server, eventInput) {
1139
+ const data = getServerTrackingData(server);
1140
+ if (!data) {
1141
+ writeToLog("Warning: Server tracking data not found. Event will not be published.");
1142
+ return;
1143
+ }
1144
+ if (!data.options.enableTracing) return;
1145
+ const sessionInfo = getSessionInfo(server, data);
1146
+ const duration = eventInput.duration || (eventInput.timestamp ? Date.now() - eventInput.timestamp.getTime() : void 0);
1147
+ const fullEvent = {
1148
+ id: eventInput.id || "",
1149
+ sessionId: eventInput.sessionId || data.sessionId,
1150
+ apiKey: data.apiKey,
1151
+ eventType: eventInput.eventType || "",
1152
+ timestamp: eventInput.timestamp || /* @__PURE__ */ new Date(),
1153
+ duration,
1154
+ ipAddress: sessionInfo.ipAddress,
1155
+ sdkLanguage: sessionInfo.sdkLanguage,
1156
+ sdkVersion: sessionInfo.sdkVersion,
1157
+ serverName: sessionInfo.serverName,
1158
+ serverVersion: sessionInfo.serverVersion,
1159
+ clientName: sessionInfo.clientName,
1160
+ clientVersion: sessionInfo.clientVersion,
1161
+ identifyActorGivenId: sessionInfo.identifyActorGivenId,
1162
+ identifyActorName: sessionInfo.identifyActorName,
1163
+ identifyActorData: sessionInfo.identifyActorData,
1164
+ resourceName: eventInput.resourceName,
1165
+ parameters: eventInput.parameters,
1166
+ response: eventInput.response,
1167
+ userIntent: eventInput.userIntent,
1168
+ isError: eventInput.isError,
1169
+ error: eventInput.error,
1170
+ redactionFn: eventInput.redactionFn,
1171
+ tags: eventInput.tags,
1172
+ properties: eventInput.properties
1173
+ };
1174
+ if (data.options.posthogClient) eventQueue.add(fullEvent, data.options.posthogClient, data.options.enableAITracing);
1175
+ else eventQueue.add(fullEvent, void 0, data.options.enableAITracing);
1176
+ }
1177
+ //#endregion
1178
+ //#region src/modules/exceptions.ts
1179
+ let fsModule = null;
1180
+ let fsInitAttempted = false;
1181
+ function getFsSync() {
1182
+ if (!fsInitAttempted) {
1183
+ fsInitAttempted = true;
1184
+ try {
1185
+ fsModule = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href)("node:fs");
1186
+ } catch {
1187
+ fsModule = null;
1188
+ }
1189
+ }
1190
+ return fsModule;
1191
+ }
1192
+ const MAX_EXCEPTION_CHAIN_DEPTH = 10;
1193
+ const MAX_STACK_FRAMES = 50;
1194
+ const LOCATION_WITH_LINE_COLUMN_REGEX = /^(.+):(\d+):(\d+)$/;
1195
+ const WINDOWS_DRIVE_PREFIX_REGEX = /^[A-Za-z]:/;
1196
+ const WINDOWS_ABSOLUTE_PATH_REGEX = /^[A-Za-z]:\\/;
1197
+ const WINDOWS_ABSOLUTE_SLASH_PATH_REGEX = /^[A-Za-z]:[/]/;
1198
+ const UNIX_USER_HOME_REGEX = /^\/Users\/[^/]+\//;
1199
+ const LINUX_USER_HOME_REGEX = /^\/home\/[^/]+\//;
1200
+ const WINDOWS_USER_HOME_REGEX = /^[A-Za-z]:[\\/]Users[\\/][^\\/]+[\\/]/;
1201
+ const DEPLOYMENT_PREFIX_REGEXES = [
1202
+ /^\/var\/www\/[^/]+\//,
1203
+ /^\/var\/task\//,
1204
+ /^\/usr\/src\/app\//,
1205
+ /^\/app\//,
1206
+ /^\/opt\/[^/]+\//,
1207
+ /^\/srv\/[^/]+\//
1208
+ ];
1209
+ /**
1210
+ * Captures detailed exception information including stack traces and cause chains.
1211
+ *
1212
+ * This function extracts error metadata (type, message, stack trace) and recursively
1213
+ * unwraps Error.cause chains. It parses V8 stack traces into structured frames and
1214
+ * detects whether each frame is user code (in_app: true) or library code (in_app: false).
1215
+ *
1216
+ * @param error - The error to capture (can be Error, string, object, or any value)
1217
+ * @param contextStack - Optional Error object to use for stack context (for validation errors)
1218
+ * @returns ErrorData object with structured error information
1219
+ */
1220
+ function captureException(error, contextStack) {
1221
+ if (isCallToolResult(error)) return captureCallToolResultError(error, contextStack);
1222
+ if (!(error instanceof Error)) return {
1223
+ message: stringifyNonError(error),
1224
+ type: void 0,
1225
+ platform: "javascript"
1226
+ };
1227
+ const errorData = {
1228
+ message: error.message || "",
1229
+ type: error.name || error.constructor?.name || void 0,
1230
+ platform: "javascript"
1231
+ };
1232
+ if (error.stack) {
1233
+ errorData.stack = error.stack;
1234
+ errorData.frames = parseV8StackTrace(error.stack);
1235
+ }
1236
+ const chainedErrors = unwrapErrorCauses(error);
1237
+ if (chainedErrors.length > 0) errorData.chained_errors = chainedErrors;
1238
+ return errorData;
1239
+ }
1240
+ /**
1241
+ * Parses V8 stack trace string into structured StackFrame array.
1242
+ *
1243
+ * V8 stack traces have the format:
1244
+ * Error: message
1245
+ * at functionName (filename:line:col)
1246
+ * at Object.method (filename:line:col)
1247
+ * ...
1248
+ *
1249
+ * This function handles various V8 format variations including:
1250
+ * - Regular functions: "at functionName (file:10:5)"
1251
+ * - Anonymous functions: "at file:10:5"
1252
+ * - Async functions: "at async functionName (file:10:5)"
1253
+ * - Object methods: "at Object.method (file:10:5)"
1254
+ * - Native code: "at Array.map (native)"
1255
+ *
1256
+ * @param stackTrace - Raw V8 stack trace string from Error.stack
1257
+ * @returns Array of parsed StackFrame objects (limited to MAX_STACK_FRAMES)
1258
+ */
1259
+ function parseV8StackTrace(stackTrace) {
1260
+ const frames = [];
1261
+ const lines = stackTrace.split("\n");
1262
+ for (const line of lines) {
1263
+ if (!line.trim().startsWith("at ")) continue;
1264
+ const frame = parseV8StackFrame(line.trim());
1265
+ if (frame) {
1266
+ addContextToFrame(frame);
1267
+ frames.push(frame);
1268
+ }
1269
+ if (frames.length >= MAX_STACK_FRAMES) break;
1270
+ }
1271
+ return frames;
1272
+ }
1273
+ /**
1274
+ * Adds context_line to a stack frame by reading the source file.
1275
+ *
1276
+ * This function extracts the line of code where the error occurred by:
1277
+ * 1. Reading the source file using abs_path
1278
+ * 2. Extracting the line at the specified line number
1279
+ * 3. Setting the context_line field on the frame
1280
+ *
1281
+ * Only extracts context for user code (in_app: true)
1282
+ * If the file cannot be read or the line number is invalid, context_line remains undefined.
1283
+ *
1284
+ * @param frame - The StackFrame to add context to (modified in place)
1285
+ * @returns The modified StackFrame
1286
+ */
1287
+ function addContextToFrame(frame) {
1288
+ if (!(frame.in_app && frame.abs_path && frame.lineno)) return frame;
1289
+ const fs = getFsSync();
1290
+ if (!fs) return frame;
1291
+ try {
1292
+ const lines = fs.readFileSync(frame.abs_path, "utf8").split("\n");
1293
+ const lineIndex = frame.lineno - 1;
1294
+ if (lineIndex >= 0 && lineIndex < lines.length) frame.context_line = lines[lineIndex];
1295
+ } catch {}
1296
+ return frame;
1297
+ }
1298
+ /**
1299
+ * Parses a location string from a V8 stack frame.
1300
+ *
1301
+ * Handles different location formats:
1302
+ * - "fileName:lineNumber:columnNumber" - normal file location
1303
+ * - "eval at functionName (location)" - eval'd code (recursively unwraps)
1304
+ * - "native" - V8 internal code
1305
+ * - "unknown location" - location unavailable
1306
+ *
1307
+ * @param location - Location string from stack frame
1308
+ * @returns Object with filename, abs_path, and optional lineno/colno, or null if unparseable
1309
+ */
1310
+ function parseLocation(location) {
1311
+ if (location === "native") return {
1312
+ filename: "native",
1313
+ abs_path: "native"
1314
+ };
1315
+ if (location === "unknown location") return {
1316
+ filename: "<unknown>",
1317
+ abs_path: "<unknown>"
1318
+ };
1319
+ if (location.startsWith("eval at ")) return parseEvalOrigin(location);
1320
+ const match = location.match(LOCATION_WITH_LINE_COLUMN_REGEX);
1321
+ if (match) {
1322
+ const [, filename, lineStr, colStr] = match;
1323
+ return {
1324
+ filename: makeRelativePath(filename),
1325
+ abs_path: filename,
1326
+ lineno: Number.parseInt(lineStr, 10),
1327
+ colno: Number.parseInt(colStr, 10)
1328
+ };
1329
+ }
1330
+ return null;
1331
+ }
1332
+ /**
1333
+ * Recursively unwraps eval location chains to extract the underlying file location.
1334
+ *
1335
+ * Eval locations have the format: "eval at functionName (location), <anonymous>:line:col"
1336
+ * where location can be another eval or a file location.
1337
+ *
1338
+ * V8 formats:
1339
+ * - "eval at Bar.z (myscript.js:10:3)" → extract myscript.js:10:3
1340
+ * - "eval at Foo (eval at Bar (file.js:10:3)), <anonymous>:5:2" → extract file.js:10:3
1341
+ *
1342
+ * @param evalLocation - Eval location string starting with "eval at "
1343
+ * @returns Object with extracted file location, or null if unparseable
1344
+ */
1345
+ function parseEvalOrigin(evalLocation) {
1346
+ let evalChainPart = evalLocation;
1347
+ const commaIndex = findCommaAfterBalancedParens(evalLocation);
1348
+ if (commaIndex !== -1) evalChainPart = evalLocation.slice(0, commaIndex);
1349
+ const innerLocation = extractTrailingParenthesizedContent(evalChainPart);
1350
+ if (!(innerLocation && evalChainPart.startsWith("eval at "))) return null;
1351
+ if (innerLocation.startsWith("eval at ")) return parseEvalOrigin(innerLocation);
1352
+ const locationMatch = innerLocation.match(LOCATION_WITH_LINE_COLUMN_REGEX);
1353
+ if (locationMatch) {
1354
+ const [, filename, lineStr, colStr] = locationMatch;
1355
+ return {
1356
+ filename: makeRelativePath(filename),
1357
+ abs_path: filename,
1358
+ lineno: Number.parseInt(lineStr, 10),
1359
+ colno: Number.parseInt(colStr, 10)
1360
+ };
1361
+ }
1362
+ return null;
1363
+ }
1364
+ /**
1365
+ * Finds the index of the comma that appears after balanced parentheses.
1366
+ *
1367
+ * For "eval at f (eval at g (x)), <anonymous>:1:2", returns the index of the comma
1368
+ * after the closing ")" and before "<anonymous>".
1369
+ *
1370
+ * @param str - String to search
1371
+ * @returns Index of comma, or -1 if not found
1372
+ */
1373
+ function findCommaAfterBalancedParens(str) {
1374
+ let depth = 0;
1375
+ let foundOpenParen = false;
1376
+ for (let i = 0; i < str.length; i++) if (str[i] === "(") {
1377
+ depth++;
1378
+ foundOpenParen = true;
1379
+ } else if (str[i] === ")") {
1380
+ depth--;
1381
+ if (depth === 0 && foundOpenParen) {
1382
+ for (let j = i + 1; j < str.length; j++) {
1383
+ if (str[j] === ",") return j;
1384
+ if (str[j] !== " ") return -1;
1385
+ }
1386
+ return -1;
1387
+ }
1388
+ }
1389
+ return -1;
1390
+ }
1391
+ function extractTrailingParenthesizedContent(value) {
1392
+ const trimmedValue = value.trim();
1393
+ if (!trimmedValue.endsWith(")")) return null;
1394
+ const openingParenIndex = findMatchingOpeningParen(trimmedValue);
1395
+ if (openingParenIndex === -1) return null;
1396
+ return trimmedValue.slice(openingParenIndex + 1, -1);
1397
+ }
1398
+ function findMatchingOpeningParen(value) {
1399
+ let depth = 0;
1400
+ for (let index = value.length - 1; index >= 0; index--) {
1401
+ const char = value[index];
1402
+ if (char === ")") depth++;
1403
+ else if (char === "(") {
1404
+ depth--;
1405
+ if (depth === 0) return index;
1406
+ }
1407
+ }
1408
+ return -1;
1409
+ }
1410
+ /**
1411
+ * Parses a single V8 stack frame line into a StackFrame object.
1412
+ *
1413
+ * Handles multiple V8 stack frame formats:
1414
+ * - "at functionName (filename:line:col)"
1415
+ * - "at filename:line:col" (top-level code)
1416
+ * - "at async functionName (filename:line:col)"
1417
+ * - "at Object.method (filename:line:col)"
1418
+ * - "at Module._compile (node:internal/...)" (internal modules)
1419
+ * - "at functionName (eval at ...)" (eval'd code)
1420
+ * - "at functionName (native)" (native code)
1421
+ *
1422
+ * @param line - Single line from V8 stack trace (trimmed, starts with "at ")
1423
+ * @returns Parsed StackFrame or null if line cannot be parsed
1424
+ */
1425
+ function parseV8StackFrame(line) {
1426
+ const withoutAt = line.slice(3);
1427
+ const location = extractTrailingParenthesizedContent(withoutAt);
1428
+ if (location) {
1429
+ const openingParenIndex = findMatchingOpeningParen(withoutAt.trim());
1430
+ const functionName = withoutAt.slice(0, openingParenIndex).trim();
1431
+ const parsedLocation = parseLocation(location);
1432
+ if (functionName && parsedLocation) return {
1433
+ function: functionName,
1434
+ filename: parsedLocation.filename,
1435
+ abs_path: parsedLocation.abs_path,
1436
+ lineno: parsedLocation.lineno,
1437
+ colno: parsedLocation.colno,
1438
+ in_app: isInApp(parsedLocation.abs_path)
1439
+ };
1440
+ }
1441
+ const parsedLocation = parseLocation(withoutAt);
1442
+ if (parsedLocation) return {
1443
+ function: "<anonymous>",
1444
+ filename: parsedLocation.filename,
1445
+ abs_path: parsedLocation.abs_path,
1446
+ lineno: parsedLocation.lineno,
1447
+ colno: parsedLocation.colno,
1448
+ in_app: isInApp(parsedLocation.abs_path)
1449
+ };
1450
+ return {
1451
+ function: withoutAt,
1452
+ filename: "<unknown>",
1453
+ in_app: false
1454
+ };
1455
+ }
1456
+ /**
1457
+ * Determines if a file path represents user code (in_app: true) or library code (in_app: false).
1458
+ *
1459
+ * Library code is identified by:
1460
+ * - Paths containing "/node_modules/"
1461
+ * - Node.js internal modules (e.g., "node:internal/...")
1462
+ * - Native code
1463
+ *
1464
+ * @param filename - File path from stack frame
1465
+ * @returns true if user code, false if library code
1466
+ */
1467
+ function isInApp(filename) {
1468
+ if (filename.includes("/node_modules/") || filename.includes("\\node_modules\\")) return false;
1469
+ if (filename.startsWith("node:")) return false;
1470
+ if (filename === "native" || filename === "<unknown>") return false;
1471
+ return true;
1472
+ }
1473
+ /**
1474
+ * Normalizes URL schemes to regular file paths.
1475
+ *
1476
+ * Handles file:// URLs commonly seen in ESM modules and local testing:
1477
+ * - "file:///Users/john/project/src/index.ts" → "/Users/john/project/src/index.ts"
1478
+ * - "file:///C:/projects/app/src/index.ts" → "C:/projects/app/src/index.ts"
1479
+ *
1480
+ * @param filename - File path that may be a file:// URL
1481
+ * @returns Clean file path without URL scheme
1482
+ */
1483
+ function normalizeUrl(filename) {
1484
+ if (filename.startsWith("file://")) {
1485
+ let result = filename.slice(7);
1486
+ if (!(result.startsWith("/") || WINDOWS_DRIVE_PREFIX_REGEX.test(result))) result = `/${result}`;
1487
+ return result;
1488
+ }
1489
+ return filename;
1490
+ }
1491
+ /**
1492
+ * Normalizes Node.js internal module paths for consistent error grouping.
1493
+ *
1494
+ * Examples:
1495
+ * - "node:internal/modules/cjs/loader" → "node:internal"
1496
+ * - "node:fs/promises" → "node:fs"
1497
+ * - "node:fs" → "node:fs" (unchanged)
1498
+ *
1499
+ * @param filename - File path that may be a Node.js internal module
1500
+ * @returns Simplified module path or original filename
1501
+ */
1502
+ function normalizeNodeInternals(filename) {
1503
+ if (filename.startsWith("node:internal")) return "node:internal";
1504
+ if (filename.startsWith("node:")) return filename.split("/")[0];
1505
+ return filename;
1506
+ }
1507
+ /**
1508
+ * Strips user-specific and system path prefixes.
1509
+ *
1510
+ * Removes prefixes like:
1511
+ * - /Users/username/ → ~/
1512
+ * - /home/username/ → ~/
1513
+ * - C:\Users\username\ → ~\
1514
+ * - C:/Users/username/ → ~/ (mixed separators)
1515
+ *
1516
+ * @param path - File path to normalize
1517
+ * @returns Path with system prefixes removed
1518
+ */
1519
+ function stripSystemPrefixes(path) {
1520
+ let result = path;
1521
+ result = result.replace(UNIX_USER_HOME_REGEX, "~/");
1522
+ result = result.replace(LINUX_USER_HOME_REGEX, "~/");
1523
+ result = result.replace(WINDOWS_USER_HOME_REGEX, "~/");
1524
+ return result;
1525
+ }
1526
+ /**
1527
+ * Normalizes node_modules paths to be consistent across deployments.
1528
+ *
1529
+ * Extracts only the package-relative portion of the path:
1530
+ * - /Users/john/project/node_modules/express/lib/router.js → node_modules/express/lib/router.js
1531
+ * - /app/node_modules/@scope/pkg/index.js → node_modules/@scope/pkg/index.js
1532
+ *
1533
+ * @param path - File path that may contain node_modules
1534
+ * @returns Normalized node_modules path or original path
1535
+ */
1536
+ function normalizeNodeModules(path) {
1537
+ const unixIndex = path.lastIndexOf("/node_modules/");
1538
+ const winIndex = path.lastIndexOf("\\node_modules\\");
1539
+ if (unixIndex !== -1) return path.slice(unixIndex + 1);
1540
+ if (winIndex !== -1) return path.slice(winIndex + 1).replace(/\\/g, "/");
1541
+ return path;
1542
+ }
1543
+ /**
1544
+ * Strips common deployment-specific path prefixes.
1545
+ *
1546
+ * Removes prefixes like:
1547
+ * - /var/www/app/ → ""
1548
+ * - /app/ → ""
1549
+ * - /opt/project/ → ""
1550
+ * - /var/task/ → "" (AWS Lambda)
1551
+ * - /usr/src/app/ → "" (Docker)
1552
+ *
1553
+ * @param path - File path to normalize
1554
+ * @returns Path with deployment prefixes removed
1555
+ */
1556
+ function stripDeploymentPaths(path) {
1557
+ let result = path;
1558
+ for (const prefix of DEPLOYMENT_PREFIX_REGEXES) result = result.replace(prefix, "");
1559
+ return result;
1560
+ }
1561
+ /**
1562
+ * Finds project-relative path using common project boundary markers.
1563
+ *
1564
+ * Looks for markers like /src/, /lib/, /dist/, /build/ and extracts the path
1565
+ * from that marker onwards:
1566
+ * - /Users/john/project/src/components/Button.tsx → src/components/Button.tsx
1567
+ * - /app/dist/index.js → dist/index.js
1568
+ *
1569
+ * Priority order: looks for primary markers first (src, lib, dist, build),
1570
+ * then secondary markers. Uses the highest-priority marker found.
1571
+ *
1572
+ * @param path - File path to search for project boundaries
1573
+ * @returns Project-relative path or original path if no marker found
1574
+ */
1575
+ function findProjectPath(path) {
1576
+ const primaryMarkers = [
1577
+ "/src/",
1578
+ "/lib/",
1579
+ "/dist/",
1580
+ "/build/"
1581
+ ];
1582
+ const secondaryMarkers = [
1583
+ "/app/",
1584
+ "/components/",
1585
+ "/pages/",
1586
+ "/api/",
1587
+ "/utils/",
1588
+ "/services/",
1589
+ "/modules/"
1590
+ ];
1591
+ for (const marker of primaryMarkers) {
1592
+ const index = path.lastIndexOf(marker);
1593
+ if (index !== -1) return path.slice(index + 1);
1594
+ }
1595
+ for (const marker of secondaryMarkers) {
1596
+ const index = path.lastIndexOf(marker);
1597
+ if (index !== -1) return path.slice(index + 1);
1598
+ }
1599
+ return path;
1600
+ }
1601
+ /**
1602
+ * Converts absolute file paths to normalized relative paths for consistent error grouping.
1603
+ *
1604
+ * This function performs comprehensive path normalization to ensure errors from the same
1605
+ * code location group together regardless of deployment environment, user directories,
1606
+ * or system-specific paths. The original absolute path is always preserved in abs_path.
1607
+ *
1608
+ * Normalization steps:
1609
+ * 1. Normalize URL schemes (file://, etc.) - must be first to strip URL prefixes
1610
+ * 2. Preserve special paths (already relative, Node internals, etc.)
1611
+ * 3. Normalize path separators to forward slashes (for consistent processing)
1612
+ * 4. Normalize Node.js internal modules (node:internal/*, node:fs/*)
1613
+ * 5. Normalize node_modules paths to package-relative format
1614
+ * 6. Strip user home directories (/Users/*, /home/*, C:\Users\*)
1615
+ * 7. Strip deployment-specific paths (/var/www/*, /app/, AWS Lambda, Docker)
1616
+ * 8. Strip current working directory
1617
+ * 9. Find project boundaries (/src/, /lib/, /dist/, etc.)
1618
+ * 10. Remove leading slashes for clean relative paths
1619
+ *
1620
+ * @param filename - Absolute or relative file path from stack trace
1621
+ * @returns Normalized relative path for error grouping
1622
+ *
1623
+ * @example
1624
+ * makeRelativePath('/Users/john/project/src/index.ts')
1625
+ * // Returns: 'src/index.ts'
1626
+ *
1627
+ * @example
1628
+ * makeRelativePath('/home/ubuntu/app/node_modules/express/lib/router.js')
1629
+ * // Returns: 'node_modules/express/lib/router.js'
1630
+ *
1631
+ * @example
1632
+ * makeRelativePath('/var/www/myapp/dist/server.js')
1633
+ * // Returns: 'dist/server.js'
1634
+ *
1635
+ * @example
1636
+ * makeRelativePath('node:internal/modules/cjs/loader')
1637
+ * // Returns: 'node:internal'
1638
+ *
1639
+ * @example
1640
+ * makeRelativePath('C:\\Users\\John\\projects\\myapp\\src\\index.ts')
1641
+ * // Returns: 'src/index.ts'
1642
+ */
1643
+ function makeRelativePath(filename) {
1644
+ let result = filename;
1645
+ result = normalizeUrl(result);
1646
+ if (!(result.startsWith("/") || WINDOWS_ABSOLUTE_PATH_REGEX.test(result))) {
1647
+ if (result.startsWith("node:")) return normalizeNodeInternals(result);
1648
+ return result;
1649
+ }
1650
+ result = result.replace(/\\/g, "/");
1651
+ if (result.startsWith("node:")) return normalizeNodeInternals(result);
1652
+ if (result.includes("/node_modules/")) return normalizeNodeModules(result);
1653
+ result = stripSystemPrefixes(result);
1654
+ result = stripDeploymentPaths(result);
1655
+ let cwd = null;
1656
+ try {
1657
+ if (typeof process !== "undefined" && typeof process.cwd === "function") cwd = process.cwd();
1658
+ } catch {}
1659
+ if (cwd && result.startsWith(cwd)) result = result.slice(cwd.length + 1);
1660
+ if (result.startsWith("/") || WINDOWS_ABSOLUTE_SLASH_PATH_REGEX.test(result)) result = findProjectPath(result);
1661
+ else if (result.startsWith("~")) {
1662
+ const absoluteWithoutTilde = `/${result.slice(2)}`;
1663
+ const projectPath = findProjectPath(absoluteWithoutTilde);
1664
+ if (projectPath !== absoluteWithoutTilde) result = projectPath;
1665
+ }
1666
+ if (result.startsWith("/")) result = result.slice(1);
1667
+ return result;
1668
+ }
1669
+ /**
1670
+ * Recursively unwraps Error.cause chain and returns array of chained errors.
1671
+ *
1672
+ * Error.cause is a standard JavaScript feature that allows chaining errors:
1673
+ * const cause = new Error("Root cause");
1674
+ * const error = new Error("Wrapper error", { cause });
1675
+ *
1676
+ * This function extracts all errors in the cause chain up to MAX_EXCEPTION_CHAIN_DEPTH.
1677
+ *
1678
+ * @param error - Error object to unwrap
1679
+ * @returns Array of ChainedErrorData objects representing the error chain
1680
+ */
1681
+ function unwrapErrorCauses(error) {
1682
+ const chainedErrors = [];
1683
+ const seenErrors = /* @__PURE__ */ new Set();
1684
+ let currentError = error.cause;
1685
+ let depth = 0;
1686
+ while (currentError && depth < MAX_EXCEPTION_CHAIN_DEPTH) {
1687
+ if (!(currentError instanceof Error)) {
1688
+ chainedErrors.push({
1689
+ message: stringifyNonError(currentError),
1690
+ type: void 0
1691
+ });
1692
+ break;
1693
+ }
1694
+ if (seenErrors.has(currentError)) break;
1695
+ seenErrors.add(currentError);
1696
+ const chainedErrorData = {
1697
+ message: currentError.message || "",
1698
+ type: currentError.name || currentError.constructor?.name || "Error"
1699
+ };
1700
+ if (currentError.stack) {
1701
+ chainedErrorData.stack = currentError.stack;
1702
+ chainedErrorData.frames = parseV8StackTrace(currentError.stack);
1703
+ }
1704
+ chainedErrors.push(chainedErrorData);
1705
+ currentError = currentError.cause;
1706
+ depth++;
1707
+ }
1708
+ return chainedErrors;
1709
+ }
1710
+ /**
1711
+ * Detects if a value is a CallToolResult object (SDK 1.21.0+ error format).
1712
+ *
1713
+ * SDK 1.21.0+ converts errors to CallToolResult format:
1714
+ * { content: [{ type: "text", text: "error message" }], isError: true }
1715
+ *
1716
+ * @param value - Value to check
1717
+ * @returns True if value is a CallToolResult object
1718
+ */
1719
+ function isCallToolResult(value) {
1720
+ return value !== null && typeof value === "object" && "isError" in value && "content" in value && Array.isArray(value.content);
1721
+ }
1722
+ function isTextContentPart(value) {
1723
+ if (value === null || typeof value !== "object") return false;
1724
+ const contentPart = value;
1725
+ return contentPart.type === "text" && typeof contentPart.text === "string";
1726
+ }
1727
+ /**
1728
+ * Extracts error information from CallToolResult objects.
1729
+ *
1730
+ * SDK 1.21.0+ converts errors to CallToolResult, losing original stack traces.
1731
+ * This extracts the error message from the content array.
1732
+ *
1733
+ * @param result - CallToolResult object with error
1734
+ * @param _contextStack - Optional Error object for stack context (unused, kept for compatibility)
1735
+ * @returns ErrorData with extracted message (no stack trace)
1736
+ */
1737
+ function captureCallToolResultError(result, _contextStack) {
1738
+ return {
1739
+ message: result.content.filter(isTextContentPart).map((contentPart) => contentPart.text).join(" ").trim() || "Unknown error",
1740
+ type: void 0,
1741
+ platform: "javascript"
1742
+ };
1743
+ }
1744
+ /**
1745
+ * Converts non-Error objects to string representation for error messages.
1746
+ *
1747
+ * In JavaScript, anything can be thrown (not just Error objects):
1748
+ * throw "string error";
1749
+ * throw { code: 404 };
1750
+ * throw null;
1751
+ *
1752
+ * This function handles these cases by converting them to meaningful strings.
1753
+ *
1754
+ * @param value - Non-Error value that was thrown
1755
+ * @returns String representation of the value
1756
+ */
1757
+ function stringifyNonError(value) {
1758
+ if (value === null) return "null";
1759
+ if (value === void 0) return "undefined";
1760
+ if (typeof value === "string") return value;
1761
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1762
+ try {
1763
+ return JSON.stringify(value);
1764
+ } catch {
1765
+ return String(value);
1766
+ }
1767
+ }
1768
+ //#endregion
1769
+ //#region src/modules/context-parameters.ts
1770
+ function isContextEnabled(context) {
1771
+ return context !== false;
1772
+ }
1773
+ function getContextDescription(context) {
1774
+ return typeof context === "object" ? context.description : void 0;
1775
+ }
1776
+ /**
1777
+ * Adds a context parameter to a tool's JSON Schema.
1778
+ * This function is called AFTER the MCP SDK has converted Zod schemas to JSON Schema,
1779
+ * so we only need to handle JSON Schema format.
1780
+ *
1781
+ * Skips injection (with warning) for:
1782
+ * - Tools that already have a 'context' parameter
1783
+ * - Complex schemas (oneOf/allOf/anyOf) that can't safely have properties added
1784
+ * - Schemas with additionalProperties: false
1785
+ */
1786
+ function addContextParameterToTool(tool, contextDescriptionOverride) {
1787
+ const modifiedTool = { ...tool };
1788
+ const toolName = tool.name || "unknown";
1789
+ const schema = modifiedTool.inputSchema;
1790
+ if (schema?.properties?.context) {
1791
+ writeToLog(`WARN: Tool "${toolName}" already has 'context' parameter. Skipping context injection.`);
1792
+ return modifiedTool;
1793
+ }
1794
+ if (schema?.oneOf || schema?.allOf || schema?.anyOf) {
1795
+ writeToLog(`WARN: Tool "${toolName}" has complex schema (oneOf/allOf/anyOf). Skipping context injection.`);
1796
+ return modifiedTool;
1797
+ }
1798
+ if (!modifiedTool.inputSchema) modifiedTool.inputSchema = {
1799
+ type: "object",
1800
+ properties: {},
1801
+ required: []
1802
+ };
1803
+ const contextDescription = contextDescriptionOverride || DEFAULT_CONTEXT_PARAMETER_DESCRIPTION;
1804
+ modifiedTool.inputSchema = JSON.parse(JSON.stringify(modifiedTool.inputSchema));
1805
+ const inputSchema = modifiedTool.inputSchema;
1806
+ if (!inputSchema.properties) inputSchema.properties = {};
1807
+ if (inputSchema.additionalProperties === false) inputSchema.additionalProperties = void 0;
1808
+ inputSchema.properties.context = {
1809
+ type: "string",
1810
+ description: contextDescription
1811
+ };
1812
+ if (Array.isArray(inputSchema.required)) {
1813
+ if (!inputSchema.required.includes("context")) inputSchema.required.push("context");
1814
+ } else inputSchema.required = ["context"];
1815
+ return modifiedTool;
1816
+ }
1817
+ function addContextParameterToTools(tools, contextDescriptionOverride) {
1818
+ return tools.map((tool) => {
1819
+ if (tool.name === "get_more_tools") return tool;
1820
+ return addContextParameterToTool(tool, contextDescriptionOverride);
1821
+ });
1822
+ }
1823
+ //#endregion
1824
+ //#region src/modules/tools.ts
1825
+ const GET_MORE_TOOLS_NAME = "get_more_tools";
1826
+ function getReportMissingToolDescriptor() {
1827
+ return {
1828
+ name: GET_MORE_TOOLS_NAME,
1829
+ description: "Check for additional tools whenever your task might benefit from specialized capabilities - even if existing tools could work as a fallback.",
1830
+ inputSchema: {
1831
+ type: "object",
1832
+ properties: { context: {
1833
+ type: "string",
1834
+ description: "A description of your goal and what kind of tool would help accomplish it."
1835
+ } },
1836
+ required: ["context"]
1837
+ }
1838
+ };
1839
+ }
1840
+ function handleReportMissing(args) {
1841
+ writeToLog(`Missing tool reported: ${JSON.stringify(args)}`);
1842
+ return { content: [{
1843
+ type: "text",
1844
+ text: "Unfortunately, we have shown you the full tool list. We have noted your feedback and will work to improve the tool list in the future."
1845
+ }] };
1846
+ }
1847
+ function setupMCPAnalyticsTools(server) {
1848
+ const handlers = server._requestHandlers;
1849
+ const originalListToolsHandler = handlers.get("tools/list");
1850
+ const originalCallToolHandler = handlers.get("tools/call");
1851
+ if (!(originalListToolsHandler && originalCallToolHandler)) {
1852
+ writeToLog("Warning: Original tool handlers not found. Your tools may not be setup before PostHog MCP analytics .track().");
1853
+ return;
1854
+ }
1855
+ try {
1856
+ server.setRequestHandler(_modelcontextprotocol_sdk_types_js.ListToolsRequestSchema, async (request, extra) => {
1857
+ let tools = [];
1858
+ const data = getServerTrackingData(server);
1859
+ const event = {
1860
+ sessionId: getServerSessionId(server, extra),
1861
+ parameters: {
1862
+ request,
1863
+ extra
1864
+ },
1865
+ eventType: MCPAnalyticsEventType.mcpToolsList,
1866
+ timestamp: /* @__PURE__ */ new Date(),
1867
+ redactionFn: data?.options.redactSensitiveInformation
1868
+ };
1869
+ try {
1870
+ tools = (await originalListToolsHandler(request, extra)).tools || [];
1871
+ } catch (error) {
1872
+ writeToLog(`Warning: Original list tools handler failed, this suggests an error PostHog MCP analytics did not cause - ${error}`);
1873
+ event.error = { message: getMCPCompatibleErrorMessage(error) };
1874
+ event.isError = true;
1875
+ event.duration = event.timestamp && Date.now() - event.timestamp.getTime() || 0;
1876
+ publishEvent(server, event);
1877
+ throw error;
1878
+ }
1879
+ if (!data) {
1880
+ writeToLog("Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called track(server, options) before using tool calls.");
1881
+ return { tools };
1882
+ }
1883
+ if (tools.length === 0) {
1884
+ writeToLog("Warning: No tools found in the original list. This is likely due to the tools not being registered before PostHog MCP analytics.track().");
1885
+ event.error = { message: "No tools were sent to MCP client." };
1886
+ event.isError = true;
1887
+ event.duration = event.timestamp && Date.now() - event.timestamp.getTime() || 0;
1888
+ publishEvent(server, event);
1889
+ return { tools };
1890
+ }
1891
+ if (isContextEnabled(data.options.context)) tools = addContextParameterToTools(tools, getContextDescription(data.options.context));
1892
+ if (data.options.reportMissing) {
1893
+ if (!tools.some((tool) => tool?.name === "get_more_tools")) tools.push(getReportMissingToolDescriptor());
1894
+ }
1895
+ event.response = { tools };
1896
+ event.isError = false;
1897
+ event.duration = event.timestamp && Date.now() - event.timestamp.getTime() || 0;
1898
+ publishEvent(server, event);
1899
+ return { tools };
1900
+ });
1901
+ } catch (error) {
1902
+ writeToLog(`Warning: Failed to override list tools handler - ${error}`);
1903
+ }
1904
+ }
1905
+ //#endregion
1906
+ //#region src/modules/tracing.ts
1907
+ function isToolResultError$1(result) {
1908
+ return !!result && typeof result === "object" && "isError" in result && result.isError === true;
1909
+ }
1910
+ const listToolsTracingSetup = /* @__PURE__ */ new WeakMap();
1911
+ function setupListToolsTracing(highLevelServer) {
1912
+ const server = highLevelServer.server;
1913
+ if (!server._capabilities?.tools) return;
1914
+ if (listToolsTracingSetup.get(server)) return;
1915
+ const originalListToolsHandler = server._requestHandlers.get("tools/list");
1916
+ if (!originalListToolsHandler) return;
1917
+ try {
1918
+ server.setRequestHandler(_modelcontextprotocol_sdk_types_js.ListToolsRequestSchema, async (request, extra) => await handleListToolsRequest(server, originalListToolsHandler, request, extra));
1919
+ listToolsTracingSetup.set(server, true);
1920
+ } catch (error) {
1921
+ writeToLog(`Warning: Failed to override list tools handler - ${error}`);
1922
+ }
1923
+ }
1924
+ async function handleListToolsRequest(server, originalListToolsHandler, request, extra) {
1925
+ const data = getServerTrackingData(server);
1926
+ const event = {
1927
+ sessionId: getServerSessionId(server, extra),
1928
+ parameters: buildCapturedMcpParameters(request),
1929
+ eventType: MCPAnalyticsEventType.mcpToolsList,
1930
+ timestamp: /* @__PURE__ */ new Date(),
1931
+ redactionFn: data?.options.redactSensitiveInformation
1932
+ };
1933
+ if (data) await applyResolvedMetadata$1(event, data, request, extra);
1934
+ const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event);
1935
+ if (!data) {
1936
+ writeToLog("Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called track(server, options) before using tool calls.");
1937
+ return { tools };
1938
+ }
1939
+ if (tools.length === 0) {
1940
+ writeToLog("Warning: No tools found in the original list. This is likely due to the tools not being registered before PostHog MCP analytics.track().");
1941
+ event.error = { message: "No tools were sent to MCP client." };
1942
+ event.isError = true;
1943
+ event.duration = getEventDuration(event);
1944
+ publishEvent(server, event);
1945
+ return { tools };
1946
+ }
1947
+ event.response = { tools };
1948
+ event.isError = false;
1949
+ event.duration = getEventDuration(event);
1950
+ publishEvent(server, event);
1951
+ return { tools };
1952
+ }
1953
+ async function getTracedToolsList(server, originalListToolsHandler, request, extra, event) {
1954
+ try {
1955
+ const data = getServerTrackingData(server);
1956
+ let tools = (await originalListToolsHandler(request, extra)).tools || [];
1957
+ if (data && isContextEnabled(data.options.context)) tools = addContextParameterToTools(tools, getContextDescription(data.options.context));
1958
+ if (data?.options.reportMissing) {
1959
+ if (!tools.some((tool) => tool?.name === "get_more_tools")) tools.push(getReportMissingToolDescriptor());
1960
+ }
1961
+ return tools;
1962
+ } catch (error) {
1963
+ writeToLog(`Warning: Original list tools handler failed, this suggests an error PostHog MCP analytics did not cause - ${error}`);
1964
+ event.error = { message: getMCPCompatibleErrorMessage(error) };
1965
+ event.isError = true;
1966
+ event.duration = getEventDuration(event);
1967
+ publishEvent(server, event);
1968
+ throw error;
1969
+ }
1970
+ }
1971
+ function setupInitializeTracing(highLevelServer) {
1972
+ const server = highLevelServer.server;
1973
+ const originalInitializeHandler = server._requestHandlers.get("initialize");
1974
+ if (originalInitializeHandler) server.setRequestHandler(_modelcontextprotocol_sdk_types_js.InitializeRequestSchema, async (request, extra) => {
1975
+ const data = getServerTrackingData(server);
1976
+ if (!data) {
1977
+ writeToLog("Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called track(server, options) before using tool calls.");
1978
+ return await originalInitializeHandler(request, extra);
1979
+ }
1980
+ const sessionId = getServerSessionId(server, extra);
1981
+ await handleIdentify(server, data, request, extra);
1982
+ const event = {
1983
+ sessionId,
1984
+ resourceName: request.params?.name || "Unknown Tool Name",
1985
+ eventType: MCPAnalyticsEventType.mcpInitialize,
1986
+ parameters: buildCapturedMcpParameters(request),
1987
+ timestamp: /* @__PURE__ */ new Date(),
1988
+ redactionFn: data.options.redactSensitiveInformation
1989
+ };
1990
+ const resolvedTags = await resolveEventTags(data, request, extra);
1991
+ if (resolvedTags) event.tags = resolvedTags;
1992
+ const resolvedProperties = await resolveEventProperties(data, request, extra);
1993
+ if (resolvedProperties) event.properties = resolvedProperties;
1994
+ const result = await originalInitializeHandler(request, extra);
1995
+ event.response = result;
1996
+ publishEvent(server, event);
1997
+ return result;
1998
+ });
1999
+ }
2000
+ function setupToolCallTracing(server) {
2001
+ try {
2002
+ const handlers = server._requestHandlers;
2003
+ const originalCallToolHandler = handlers.get("tools/call");
2004
+ const originalInitializeHandler = handlers.get("initialize");
2005
+ if (originalInitializeHandler) server.setRequestHandler(_modelcontextprotocol_sdk_types_js.InitializeRequestSchema, async (request, extra) => {
2006
+ const data = getServerTrackingData(server);
2007
+ if (!data) {
2008
+ writeToLog("Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called track(server, options) before using tool calls.");
2009
+ return await originalInitializeHandler(request, extra);
2010
+ }
2011
+ const sessionId = getServerSessionId(server, extra);
2012
+ await handleIdentify(server, data, request, extra);
2013
+ const event = {
2014
+ sessionId,
2015
+ resourceName: request.params?.name || "Unknown Tool Name",
2016
+ eventType: MCPAnalyticsEventType.mcpInitialize,
2017
+ parameters: buildCapturedMcpParameters(request),
2018
+ timestamp: /* @__PURE__ */ new Date(),
2019
+ redactionFn: data.options.redactSensitiveInformation
2020
+ };
2021
+ const resolvedTags = await resolveEventTags(data, request, extra);
2022
+ if (resolvedTags) event.tags = resolvedTags;
2023
+ const resolvedProperties = await resolveEventProperties(data, request, extra);
2024
+ if (resolvedProperties) event.properties = resolvedProperties;
2025
+ const result = await originalInitializeHandler(request, extra);
2026
+ event.response = result;
2027
+ publishEvent(server, event);
2028
+ return result;
2029
+ });
2030
+ server.setRequestHandler(_modelcontextprotocol_sdk_types_js.CallToolRequestSchema, async (request, extra) => await handleToolCallRequest(server, originalCallToolHandler, request, extra));
2031
+ } catch (error) {
2032
+ writeToLog(`Warning: Failed to setup tool call tracing - ${error}`);
2033
+ throw error;
2034
+ }
2035
+ }
2036
+ async function handleToolCallRequest(server, originalCallToolHandler, request, extra) {
2037
+ const data = getServerTrackingData(server);
2038
+ if (!data) {
2039
+ writeToLog("Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called track(server, options) before using tool calls.");
2040
+ return await originalCallToolHandler?.(request, extra);
2041
+ }
2042
+ const event = {
2043
+ sessionId: getServerSessionId(server, extra),
2044
+ resourceName: request.params?.name || "Unknown Tool Name",
2045
+ parameters: buildCapturedMcpParameters(request),
2046
+ eventType: MCPAnalyticsEventType.mcpToolsCall,
2047
+ timestamp: /* @__PURE__ */ new Date(),
2048
+ redactionFn: data.options.redactSensitiveInformation
2049
+ };
2050
+ try {
2051
+ await handleIdentify(server, data, request, extra);
2052
+ await applyResolvedMetadata$1(event, data, request, extra);
2053
+ setToolCallContext(event, data.options.context, request);
2054
+ const result = await executeToolCall(server, originalCallToolHandler, request, extra, event);
2055
+ if (isToolResultError$1(result)) {
2056
+ event.isError = true;
2057
+ event.error = captureException(result);
2058
+ }
2059
+ event.response = result;
2060
+ publishEvent(server, event);
2061
+ return result;
2062
+ } catch (error) {
2063
+ event.isError = true;
2064
+ event.error = captureException(error);
2065
+ publishEvent(server, event);
2066
+ throw error;
2067
+ }
2068
+ }
2069
+ async function executeToolCall(server, originalCallToolHandler, request, extra, event) {
2070
+ if (request.params?.name === "get_more_tools") {
2071
+ const context = getContextArgument$1(request) || "";
2072
+ event.userIntent = context;
2073
+ return handleReportMissing({ context });
2074
+ }
2075
+ if (originalCallToolHandler) return await originalCallToolHandler(request, extra);
2076
+ event.isError = true;
2077
+ event.error = { message: `Tool call handler not found for ${request.params?.name || "unknown"}` };
2078
+ event.duration = getEventDuration(event) || void 0;
2079
+ publishEvent(server, event);
2080
+ throw new Error(`Unknown tool: ${request.params?.name || "unknown"}`);
2081
+ }
2082
+ async function applyResolvedMetadata$1(event, data, request, extra) {
2083
+ const resolvedTags = await resolveEventTags(data, request, extra);
2084
+ if (resolvedTags) event.tags = resolvedTags;
2085
+ const resolvedProperties = await resolveEventProperties(data, request, extra);
2086
+ if (resolvedProperties) event.properties = resolvedProperties;
2087
+ }
2088
+ function setToolCallContext(event, context, request) {
2089
+ if (!(isContextEnabled(context) && request.params?.name !== "get_more_tools")) return;
2090
+ const contextArgument = getContextArgument$1(request);
2091
+ if (contextArgument) event.userIntent = contextArgument;
2092
+ }
2093
+ function getContextArgument$1(request) {
2094
+ const context = request.params?.arguments?.context;
2095
+ return typeof context === "string" ? context : void 0;
2096
+ }
2097
+ function getEventDuration(event) {
2098
+ return event.timestamp ? Date.now() - event.timestamp.getTime() : 0;
2099
+ }
2100
+ //#endregion
2101
+ //#region src/modules/mcp-sdk-compat.ts
2102
+ /**
2103
+ * Returns the tool function (callback/handler) from a RegisteredTool.
2104
+ * Supports both MCP SDK 1.23- (callback) and 1.24+ (handler).
2105
+ */
2106
+ function getToolFunction(tool) {
2107
+ if ("handler" in tool && typeof tool.handler === "function") return tool.handler;
2108
+ if ("callback" in tool && typeof tool.callback === "function") return tool.callback;
2109
+ throw new Error("Tool has neither callback nor handler property");
2110
+ }
2111
+ /**
2112
+ * Returns the property key name used for the tool function ("callback" or "handler").
2113
+ * This preserves the original property name when wrapping tools.
2114
+ */
2115
+ function getToolFunctionKey(tool) {
2116
+ if ("handler" in tool && typeof tool.handler === "function") return "handler";
2117
+ return "callback";
2118
+ }
2119
+ /**
2120
+ * Returns true if the tool has a callback or handler property.
2121
+ */
2122
+ function hasToolFunction(tool) {
2123
+ if (!tool || typeof tool !== "object") return false;
2124
+ const t = tool;
2125
+ return "handler" in t && typeof t.handler === "function" || "callback" in t && typeof t.callback === "function";
2126
+ }
2127
+ /**
2128
+ * Creates a new tool object with the wrapped function, preserving the original property name.
2129
+ * This ensures MCP SDK 1.24+ gets back a tool with "handler" and 1.23- gets "callback".
2130
+ */
2131
+ function createWrappedTool(originalTool, wrappedFunction) {
2132
+ const key = getToolFunctionKey(originalTool);
2133
+ return {
2134
+ ...originalTool,
2135
+ [key]: wrappedFunction
2136
+ };
2137
+ }
2138
+ function isZ4Schema(schema) {
2139
+ if (!schema || typeof schema !== "object") return false;
2140
+ return !!schema._zod;
2141
+ }
2142
+ function getObjectShape(schema) {
2143
+ if (!schema || typeof schema !== "object") return;
2144
+ let rawShape;
2145
+ if (isZ4Schema(schema)) rawShape = schema._zod?.def?.shape;
2146
+ else {
2147
+ const v3Schema = schema;
2148
+ rawShape = v3Schema.shape ?? v3Schema._def?.shape;
2149
+ }
2150
+ if (!rawShape) return;
2151
+ if (typeof rawShape === "function") try {
2152
+ return rawShape();
2153
+ } catch {
2154
+ return;
2155
+ }
2156
+ return rawShape;
2157
+ }
2158
+ function getLiteralValue(schema) {
2159
+ if (!schema || typeof schema !== "object") return;
2160
+ if (isZ4Schema(schema)) {
2161
+ const def = schema._zod?.def;
2162
+ if (def?.value !== void 0) return def.value;
2163
+ if (Array.isArray(def?.values) && def.values.length > 0) return def.values[0];
2164
+ } else {
2165
+ const def = schema._def;
2166
+ if (def?.value !== void 0) return def.value;
2167
+ if (Array.isArray(def?.values) && def.values.length > 0) return def.values[0];
2168
+ }
2169
+ const directValue = schema.value;
2170
+ if (directValue !== void 0) return directValue;
2171
+ }
2172
+ //#endregion
2173
+ //#region src/modules/tracing-v2.ts
2174
+ const wrappedCallbacks = /* @__PURE__ */ new WeakMap();
2175
+ const MCP_ANALYTICS_PROCESSED = Symbol("__posthog_mcp_analytics_processed__");
2176
+ function isToolResultError(result) {
2177
+ return !!result && typeof result === "object" && "isError" in result && result.isError === true;
2178
+ }
2179
+ function isCallbackUpdate(value) {
2180
+ return !!value && typeof value === "object" && "callback" in value && typeof value.callback === "function";
2181
+ }
2182
+ function addTracingToToolRegistry(tools, server) {
2183
+ return Object.fromEntries(Object.entries(tools).map(([name, tool]) => [name, addTracingToToolCallbackInternal(tool, name, server)]));
2184
+ }
2185
+ function setupListenerToRegisteredTools(server) {
2186
+ try {
2187
+ if (!getServerTrackingData(server.server)) {
2188
+ writeToLog("Warning: Cannot setup listener - no tracking data found");
2189
+ return;
2190
+ }
2191
+ const handler = {
2192
+ set(target, property, value) {
2193
+ try {
2194
+ if (typeof property === "string" && value && typeof value === "object" && hasToolFunction(value)) {
2195
+ if (value[MCP_ANALYTICS_PROCESSED]) {
2196
+ writeToLog(`Tool ${String(property)} already processed, skipping proxy wrapping`);
2197
+ return Reflect.set(target, property, value);
2198
+ }
2199
+ if (wrappedCallbacks.has(getToolFunction(value))) {
2200
+ writeToLog(`Tool ${String(property)} callback already wrapped, skipping proxy wrapping`);
2201
+ return Reflect.set(target, property, value);
2202
+ }
2203
+ const nextValue = addTracingToToolCallbackInternal(value, property, server);
2204
+ setupListToolsTracing(server);
2205
+ if (typeof nextValue.update === "function") {
2206
+ const originalUpdate = nextValue.update;
2207
+ nextValue.update = function(...updateArgs) {
2208
+ if (updateArgs[0]) {
2209
+ const updateObj = updateArgs[0];
2210
+ if (isCallbackUpdate(updateObj)) updateObj.callback = getToolFunction(addTracingToToolCallbackInternal({ callback: updateObj.callback }, property, server));
2211
+ }
2212
+ return originalUpdate.apply(this, updateArgs);
2213
+ };
2214
+ }
2215
+ return Reflect.set(target, property, nextValue);
2216
+ }
2217
+ return Reflect.set(target, property, value);
2218
+ } catch (error) {
2219
+ writeToLog(`Warning: Error in proxy set handler for tool ${String(property)} - ${error}`);
2220
+ return Reflect.set(target, property, value);
2221
+ }
2222
+ },
2223
+ get(target, property) {
2224
+ return Reflect.get(target, property);
2225
+ },
2226
+ deleteProperty(target, property) {
2227
+ return Reflect.deleteProperty(target, property);
2228
+ },
2229
+ has(target, property) {
2230
+ return Reflect.has(target, property);
2231
+ }
2232
+ };
2233
+ const originalTools = server._registeredTools || {};
2234
+ server._registeredTools = new Proxy(originalTools, handler);
2235
+ writeToLog("Successfully set up listener for new tool registrations");
2236
+ } catch (error) {
2237
+ writeToLog(`Warning: Failed to setup listener for registered tools - ${error}`);
2238
+ }
2239
+ }
2240
+ function addTracingToToolCallbackInternal(tool, toolName, _server) {
2241
+ const originalCallback = getToolFunction(tool);
2242
+ if (wrappedCallbacks.has(originalCallback)) {
2243
+ writeToLog(`Tool ${toolName} callback already wrapped, skipping re-wrap`);
2244
+ return tool;
2245
+ }
2246
+ if (tool[MCP_ANALYTICS_PROCESSED]) {
2247
+ writeToLog(`Tool ${toolName} already processed, skipping re-wrap`);
2248
+ return tool;
2249
+ }
2250
+ const wrappedCallback = async (...params) => {
2251
+ let args;
2252
+ let extra;
2253
+ if (params.length === 2) {
2254
+ args = params[0];
2255
+ extra = params[1];
2256
+ } else {
2257
+ args = void 0;
2258
+ extra = params[0];
2259
+ }
2260
+ const removeContextFromArgs = (args) => {
2261
+ if (args && typeof args === "object" && "context" in args) {
2262
+ const { context: _context, ...argsWithoutContext } = args;
2263
+ return argsWithoutContext;
2264
+ }
2265
+ return args;
2266
+ };
2267
+ const cleanedArgs = toolName === "get_more_tools" ? args : removeContextFromArgs(args);
2268
+ try {
2269
+ if (cleanedArgs === void 0) return await originalCallback(extra);
2270
+ return await originalCallback(cleanedArgs, extra);
2271
+ } catch (error) {
2272
+ if (error instanceof Error) extra.__mcp_analytics_error = error;
2273
+ throw error;
2274
+ }
2275
+ };
2276
+ wrappedCallbacks.set(originalCallback, true);
2277
+ wrappedCallbacks.set(wrappedCallback, true);
2278
+ const wrappedTool = createWrappedTool(tool, wrappedCallback);
2279
+ wrappedTool[MCP_ANALYTICS_PROCESSED] = true;
2280
+ return wrappedTool;
2281
+ }
2282
+ function setupToolsCallHandlerWrapping(server) {
2283
+ const lowLevelServer = server.server;
2284
+ const existingHandler = lowLevelServer._requestHandlers.get("tools/call");
2285
+ if (existingHandler) {
2286
+ const wrappedHandler = createToolsCallWrapper(existingHandler, lowLevelServer);
2287
+ lowLevelServer._requestHandlers.set("tools/call", wrappedHandler);
2288
+ }
2289
+ const originalSetRequestHandler = lowLevelServer.setRequestHandler.bind(lowLevelServer);
2290
+ lowLevelServer.setRequestHandler = ((requestSchema, handler) => {
2291
+ const shape = getObjectShape(requestSchema);
2292
+ if ((shape?.method ? getLiteralValue(shape.method) : void 0) === "tools/call") return originalSetRequestHandler(requestSchema, createToolsCallWrapper(handler, lowLevelServer));
2293
+ return originalSetRequestHandler(requestSchema, handler);
2294
+ });
2295
+ }
2296
+ function createToolsCallWrapper(originalHandler, server) {
2297
+ return async (request, extra) => await handleWrappedToolsCall(originalHandler, server, request, extra);
2298
+ }
2299
+ async function handleWrappedToolsCall(originalHandler, server, request, extra) {
2300
+ const startTime = /* @__PURE__ */ new Date();
2301
+ const tracing = await initializeToolCallEvent(server, request, extra, startTime);
2302
+ if (request?.params?.name === "get_more_tools") return await executeReportMissingTool(server, request, tracing, startTime);
2303
+ return await executeOriginalTool(originalHandler, server, request, extra, tracing, startTime);
2304
+ }
2305
+ async function initializeToolCallEvent(server, request, extra, startTime) {
2306
+ try {
2307
+ const data = getServerTrackingData(server);
2308
+ if (!data) {
2309
+ writeToLog("Warning: PostHog MCP analytics is unable to find server tracking data. Please ensure you have called track(server, options) before using tool calls.");
2310
+ return {
2311
+ event: null,
2312
+ shouldPublishEvent: false
2313
+ };
2314
+ }
2315
+ const event = {
2316
+ sessionId: getServerSessionId(server, extra),
2317
+ resourceName: request.params?.name || "Unknown Tool",
2318
+ parameters: buildCapturedMcpParameters(request),
2319
+ eventType: MCPAnalyticsEventType.mcpToolsCall,
2320
+ timestamp: startTime,
2321
+ redactionFn: data.options.redactSensitiveInformation
2322
+ };
2323
+ await handleIdentify(server, data, request, extra);
2324
+ event.sessionId = data.sessionId;
2325
+ await applyResolvedMetadata(event, data, request, extra);
2326
+ const contextArgument = getContextArgument(request);
2327
+ if (isContextEnabled(data.options.context) && contextArgument) event.userIntent = contextArgument;
2328
+ return {
2329
+ event,
2330
+ shouldPublishEvent: true
2331
+ };
2332
+ } catch (error) {
2333
+ writeToLog(`Warning: PostHog MCP analytics tracing failed for tool ${request.params?.name}, falling back to original handler - ${error}`);
2334
+ return {
2335
+ event: null,
2336
+ shouldPublishEvent: false
2337
+ };
2338
+ }
2339
+ }
2340
+ async function applyResolvedMetadata(event, data, request, extra) {
2341
+ const resolvedTags = await resolveEventTags(data, request, extra);
2342
+ if (resolvedTags) event.tags = resolvedTags;
2343
+ const resolvedProperties = await resolveEventProperties(data, request, extra);
2344
+ if (resolvedProperties) event.properties = resolvedProperties;
2345
+ }
2346
+ async function executeReportMissingTool(server, request, tracing, startTime) {
2347
+ try {
2348
+ const context = getContextArgument(request) || "";
2349
+ const result = await handleReportMissing({ context });
2350
+ publishSuccessfulToolEvent(server, tracing, result, startTime, { userIntent: context });
2351
+ return result;
2352
+ } catch (error) {
2353
+ publishFailedToolEvent(server, tracing, error, startTime);
2354
+ throw error;
2355
+ }
2356
+ }
2357
+ async function executeOriginalTool(originalHandler, server, request, extra, tracing, startTime) {
2358
+ try {
2359
+ const result = await originalHandler(request, extra);
2360
+ publishSuccessfulToolEvent(server, tracing, result, startTime, {
2361
+ capturedError: extra?.__mcp_analytics_error,
2362
+ clearCapturedError: () => {
2363
+ if (extra) extra.__mcp_analytics_error = void 0;
2364
+ }
2365
+ });
2366
+ return result;
2367
+ } catch (error) {
2368
+ publishFailedToolEvent(server, tracing, error, startTime);
2369
+ throw error;
2370
+ }
2371
+ }
2372
+ function getContextArgument(request) {
2373
+ const context = request.params?.arguments?.context;
2374
+ return typeof context === "string" ? context : void 0;
2375
+ }
2376
+ function publishSuccessfulToolEvent(server, tracing, result, startTime, options = {}) {
2377
+ if (!(tracing.event && tracing.shouldPublishEvent)) return;
2378
+ if (options.userIntent) tracing.event.userIntent = options.userIntent;
2379
+ if (isToolResultError(result)) {
2380
+ tracing.event.isError = true;
2381
+ tracing.event.error = captureException(options.capturedError || result);
2382
+ options.clearCapturedError?.();
2383
+ }
2384
+ tracing.event.response = result;
2385
+ tracing.event.duration = Date.now() - startTime.getTime();
2386
+ publishEvent(server, tracing.event);
2387
+ }
2388
+ function publishFailedToolEvent(server, tracing, error, startTime) {
2389
+ if (!(tracing.event && tracing.shouldPublishEvent)) return;
2390
+ tracing.event.isError = true;
2391
+ tracing.event.error = captureException(error);
2392
+ tracing.event.duration = Date.now() - startTime.getTime();
2393
+ publishEvent(server, tracing.event);
2394
+ }
2395
+ function setupTracking(server) {
2396
+ try {
2397
+ getServerTrackingData(server.server);
2398
+ setupToolsCallHandlerWrapping(server);
2399
+ setupInitializeTracing(server);
2400
+ server._registeredTools = addTracingToToolRegistry(server._registeredTools, server);
2401
+ setupListToolsTracing(server);
2402
+ setupListenerToRegisteredTools(server);
2403
+ } catch (error) {
2404
+ writeToLog(`Warning: Failed to setup tool call tracing - ${error}`);
2405
+ }
2406
+ }
2407
+ //#endregion
2408
+ //#region src/index.ts
2409
+ /**
2410
+ * Integrates PostHog MCP into an MCP server to track tool usage patterns and user interactions.
2411
+ *
2412
+ * @param server - The MCP server instance to track. Must be a compatible MCP server implementation.
2413
+ * @param options - Configuration to customize tracking behavior.
2414
+ * @param options.apiKey - PostHog project API key (`phc_...`). Optional when using an injected `posthogClient`.
2415
+ * @param options.host - Custom PostHog ingestion host. Defaults to `https://us.i.posthog.com`.
2416
+ * @param options.reportMissing - Adds a "get_more_tools" tool that allows LLMs to automatically report missing functionality. Defaults to false.
2417
+ * @param options.enableAITracing - Emits `$ai_span` events for tool calls so MCP activity appears in PostHog LLM analytics. Defaults to false.
2418
+ * @param options.enableTracing - Enables tracking of tool calls and usage patterns.
2419
+ * @param options.context - Enables the required "context" parameter on tools to capture user intent. Pass false to disable, or an object with a custom description.
2420
+ * @param options.identify - Async function to identify users and attach custom data to their sessions.
2421
+ * @param options.redactSensitiveInformation - Function to redact sensitive data before sending to PostHog.
2422
+ * @param options.eventTags - Callback invoked on every auto-captured event (tool calls, tool lists, initialize) to attach string key-value tags. Tags are intended to be indexed and queryable in PostHog — use them for structured metadata you'll want to filter or group by (e.g., trace IDs, environments, regions). Tags are validated client-side: keys must be ≤32 chars matching `[a-zA-Z0-9$_.:\- ]`, values must be strings ≤200 chars with no newlines, max 50 entries per event. Invalid entries are silently dropped with a warning logged to `~/posthog-mcp-analytics.log`. If the callback throws or returns null, tags are omitted. Receives the same `(request, extra)` arguments as `identify`.
2423
+ * @param options.eventProperties - Callback invoked on every auto-captured event to attach flexible JSON metadata (device info, feature flags, nested context). No constraints beyond standard JSON types. If the callback throws or returns null, properties are omitted. Receives the same `(request, extra)` arguments as `identify`.
2424
+ * @param options.posthogClient - Optional existing posthog-node compatible client. If provided, MCP events are captured with that client instead of creating a new one.
2425
+ * @param options.posthogOptions - Optional posthog-node options used when the SDK creates its own client.
2426
+ *
2427
+ * @returns The tracked server instance.
2428
+ *
2429
+ * @remarks
2430
+ * Analytics data and debug information are logged to `~/posthog-mcp-analytics.log` since console logs interfere
2431
+ * with STDIO-based MCP servers.
2432
+ *
2433
+ * Do not call `track()` multiple times on the same server instance as this will cause unexpected behavior.
2434
+ *
2435
+ * @example
2436
+ * ```typescript
2437
+ * import { track } from "@posthog/mcp";
2438
+ *
2439
+ * const mcpServer = new Server({ name: "my-mcp-server", version: "1.0.0" });
2440
+ *
2441
+ * // Track the server with PostHog MCP
2442
+ * track(mcpServer, { apiKey: "phc_abc123xyz" });
2443
+ *
2444
+ * // Register your tools
2445
+ * mcpServer.setRequestHandler(ListToolsRequestSchema, async () => ({
2446
+ * tools: [{ name: "my_tool", description: "Does something useful" }]
2447
+ * }));
2448
+ * ```
2449
+ *
2450
+ * @example
2451
+ * ```typescript
2452
+ * // With user identification
2453
+ * track(mcpServer, {
2454
+ * apiKey: "phc_abc123xyz",
2455
+ * identify: async (request, extra) => {
2456
+ * const user = await getUserFromToken(request.params.arguments.token);
2457
+ * return {
2458
+ * userId: user.id,
2459
+ * userData: { plan: user.plan, company: user.company }
2460
+ * };
2461
+ * }
2462
+ * });
2463
+ * ```
2464
+ *
2465
+ * @example
2466
+ * ```typescript
2467
+ * // With custom context description
2468
+ * track(mcpServer, {
2469
+ * apiKey: "phc_abc123xyz",
2470
+ * context: {
2471
+ * description: "Explain why you're calling this tool and what business objective it helps achieve"
2472
+ * }
2473
+ * });
2474
+ * ```
2475
+ *
2476
+ * @example
2477
+ * ```typescript
2478
+ * // With sensitive data redaction
2479
+ * track(mcpServer, {
2480
+ * apiKey: "phc_abc123xyz",
2481
+ * redactSensitiveInformation: async (text) => {
2482
+ * return text.replace(/api_key_\w+/g, "[REDACTED]");
2483
+ * }
2484
+ * });
2485
+ * ```
2486
+ *
2487
+ * @example
2488
+ * ```typescript
2489
+ * // With event tags and properties
2490
+ * track(mcpServer, {
2491
+ * apiKey: "phc_abc123xyz",
2492
+ * eventTags: async (request, extra) => ({
2493
+ * trace_id: extra?.requestContext?.traceId,
2494
+ * env: process.env.NODE_ENV,
2495
+ * region: "us-east-1",
2496
+ * }),
2497
+ * eventProperties: async (request, extra) => ({
2498
+ * device: "desktop",
2499
+ * app_version: "2.1.0",
2500
+ * feature_flags: ["dark_mode", "beta_ui"],
2501
+ * }),
2502
+ * });
2503
+ * ```
2504
+ *
2505
+ * @example
2506
+ * ```typescript
2507
+ */
2508
+ function track(server, options = {}) {
2509
+ try {
2510
+ const validatedServer = isCompatibleServerType(server);
2511
+ const lowLevelServer = getLowLevelServer(validatedServer);
2512
+ configureIngestion(options);
2513
+ if (getServerTrackingData(lowLevelServer)) {
2514
+ writeToLog("[SESSION DEBUG] track() - Server already being tracked, skipping initialization");
2515
+ return validatedServer;
2516
+ }
2517
+ if (!(options.apiKey || options.posthogClient)) writeToLog("Warning: No PostHog API key or PostHog client configured. Events will not be sent anywhere.");
2518
+ const mcpAnalyticsData = buildTrackingData(lowLevelServer, options);
2519
+ setServerTrackingData(lowLevelServer, mcpAnalyticsData);
2520
+ setupTrackedServer(validatedServer, lowLevelServer, mcpAnalyticsData);
2521
+ return validatedServer;
2522
+ } catch (error) {
2523
+ writeToLog(`Warning: Failed to track server - ${error}`);
2524
+ return server;
2525
+ }
2526
+ }
2527
+ function getLowLevelServer(server) {
2528
+ return isHighLevelServer(server) ? server.server : server;
2529
+ }
2530
+ function configureIngestion(options) {
2531
+ const host = options.host || process.env.POSTHOG_MCP_ANALYTICS_HOST;
2532
+ if (options.posthogOptions) eventQueue.configurePostHogOptions(options.posthogOptions);
2533
+ if (host) eventQueue.configure(host);
2534
+ }
2535
+ function buildTrackingData(lowLevelServer, options) {
2536
+ return {
2537
+ apiKey: options.apiKey || "",
2538
+ sessionId: newSessionId(),
2539
+ lastActivity: /* @__PURE__ */ new Date(),
2540
+ identifiedSessions: /* @__PURE__ */ new Map(),
2541
+ sessionInfo: getSessionInfo(lowLevelServer, void 0),
2542
+ options: {
2543
+ reportMissing: options.reportMissing ?? false,
2544
+ enableAITracing: options.enableAITracing ?? false,
2545
+ enableTracing: options.enableTracing ?? true,
2546
+ context: options.context,
2547
+ identify: options.identify,
2548
+ redactSensitiveInformation: options.redactSensitiveInformation,
2549
+ eventTags: options.eventTags,
2550
+ eventProperties: options.eventProperties,
2551
+ host: options.host,
2552
+ posthogClient: options.posthogClient,
2553
+ posthogOptions: options.posthogOptions
2554
+ },
2555
+ sessionSource: "generated"
2556
+ };
2557
+ }
2558
+ function setupTrackedServer(validatedServer, lowLevelServer, mcpAnalyticsData) {
2559
+ if (isHighLevelServer(validatedServer)) setupTracking(validatedServer);
2560
+ else {
2561
+ if (mcpAnalyticsData.options.reportMissing) try {
2562
+ setupMCPAnalyticsTools(lowLevelServer);
2563
+ } catch (error) {
2564
+ writeToLog(`Warning: Failed to setup report missing tool - ${error}`);
2565
+ }
2566
+ if (mcpAnalyticsData.options.enableTracing) try {
2567
+ setupToolCallTracing(lowLevelServer);
2568
+ } catch (error) {
2569
+ writeToLog(`Warning: Failed to setup tool call tracing - ${error}`);
2570
+ }
2571
+ }
2572
+ }
2573
+ /**
2574
+ * Publishes a custom event to PostHog MCP with flexible session management.
2575
+ *
2576
+ * @param serverOrSessionId - Either a tracked MCP server instance or a MCP session ID string
2577
+ * @param eventData - Event data to include with the custom event. `apiKey` is required when publishing against a raw session ID.
2578
+ *
2579
+ * @returns Promise that resolves when the event is queued for publishing
2580
+ *
2581
+ * @example
2582
+ * ```typescript
2583
+ * // With a tracked server
2584
+ * await publishCustomEvent(
2585
+ * server,
2586
+ * {
2587
+ * resourceName: "custom-action",
2588
+ * parameters: { action: "user-feedback", rating: 5 },
2589
+ * message: "User provided feedback"
2590
+ * }
2591
+ * );
2592
+ * ```
2593
+ *
2594
+ * @example
2595
+ * ```typescript
2596
+ * // With a MCP session ID
2597
+ * await publishCustomEvent(
2598
+ * "user-session-12345",
2599
+ * {
2600
+ * apiKey: "phc_abc123xyz",
2601
+ * isError: true,
2602
+ * error: { message: "Custom error occurred", code: "ERR_001" }
2603
+ * }
2604
+ * );
2605
+ * ```
2606
+ *
2607
+ * @example
2608
+ * ```typescript
2609
+ * await publishCustomEvent(
2610
+ * server,
2611
+ * {
2612
+ * resourceName: "feature-usage",
2613
+ * }
2614
+ * );
2615
+ * ```
2616
+ */
2617
+ function publishCustomEvent(serverOrSessionId, eventData = {}) {
2618
+ try {
2619
+ publishCustomEventSync(serverOrSessionId, eventData);
2620
+ return Promise.resolve();
2621
+ } catch (error) {
2622
+ return Promise.reject(error);
2623
+ }
2624
+ }
2625
+ function publishCustomEventSync(serverOrSessionId, eventData) {
2626
+ const target = resolveCustomEventTarget(serverOrSessionId, eventData);
2627
+ const event = {
2628
+ sessionId: target.sessionId,
2629
+ apiKey: target.apiKey,
2630
+ eventType: MCPAnalyticsEventType.custom,
2631
+ timestamp: /* @__PURE__ */ new Date(),
2632
+ resourceName: eventData?.resourceName,
2633
+ parameters: eventData?.parameters,
2634
+ response: eventData?.response,
2635
+ userIntent: eventData?.message,
2636
+ duration: eventData?.duration,
2637
+ isError: eventData?.isError,
2638
+ error: resolveCustomEventError(eventData?.error)
2639
+ };
2640
+ if (eventData?.tags) event.tags = validateTags(eventData.tags);
2641
+ if (eventData?.properties && Object.keys(eventData.properties).length > 0) event.properties = eventData.properties;
2642
+ publishResolvedCustomEvent(target, event);
2643
+ writeToLog(`Published custom event for session ${target.sessionId} with type '${MCPAnalyticsEventType.custom}'`);
2644
+ }
2645
+ function resolveCustomEventError(error) {
2646
+ if (error === void 0 || error === null) return error;
2647
+ if (typeof error === "object" && "message" in error && typeof error.message === "string") return error;
2648
+ return captureException(error);
2649
+ }
2650
+ function resolveCustomEventTarget(serverOrSessionId, eventData) {
2651
+ if (typeof serverOrSessionId === "string") return resolveSessionIdTarget(serverOrSessionId, eventData);
2652
+ if (serverOrSessionId && typeof serverOrSessionId === "object") return resolveTrackedServerTarget(serverOrSessionId);
2653
+ throw new Error("First parameter must be either an MCP server object or a session ID string");
2654
+ }
2655
+ function resolveSessionIdTarget(sessionIdInput, eventData) {
2656
+ const apiKey = eventData.apiKey || "";
2657
+ if (!(apiKey || eventData.posthogClient)) throw new Error("apiKey or posthogClient is required when publishing with a session ID");
2658
+ return {
2659
+ apiKey,
2660
+ lowLevelServer: null,
2661
+ posthogClient: eventData.posthogClient,
2662
+ sessionId: deriveSessionIdFromMCPSession(sessionIdInput)
2663
+ };
2664
+ }
2665
+ function resolveTrackedServerTarget(server) {
2666
+ const lowLevelServer = getLowLevelServerFromUnknownObject(server);
2667
+ const trackingData = getServerTrackingData(lowLevelServer);
2668
+ if (!trackingData) throw new Error("Server is not tracked. Please call track() first or provide a session ID string.");
2669
+ return {
2670
+ apiKey: trackingData.apiKey,
2671
+ lowLevelServer,
2672
+ posthogClient: trackingData.options.posthogClient,
2673
+ sessionId: trackingData.sessionId
2674
+ };
2675
+ }
2676
+ function getLowLevelServerFromUnknownObject(server) {
2677
+ return "server" in server && server.server && typeof server.server === "object" ? server.server : server;
2678
+ }
2679
+ function publishResolvedCustomEvent(target, event) {
2680
+ if (target.lowLevelServer && getServerTrackingData(target.lowLevelServer)) {
2681
+ publishEvent(target.lowLevelServer, event);
2682
+ return;
2683
+ }
2684
+ if (target.posthogClient) {
2685
+ eventQueue.add(event, target.posthogClient);
2686
+ return;
2687
+ }
2688
+ eventQueue.add(event);
2689
+ }
2690
+ //#endregion
2691
+ exports.PostHogMCPAnalyticsProperty = PostHogMCPAnalyticsProperty;
2692
+ exports.publishCustomEvent = publishCustomEvent;
2693
+ exports.track = track;
2694
+
2695
+ //# sourceMappingURL=index.cjs.map