Skip to content

Commit ac96b3c

Browse files
authored
feat: add publishCustomEvent function for custom analytics events (#16)
Add publishCustomEvent() function that allows publishing arbitrary events to MCPCat outside of standard tool call tracking. This enables tracking custom business logic, user actions, and errors. Features: - Works with tracked MCP servers or custom session IDs - Supports flexible event metadata (resourceName, parameters, response, etc.) - Includes error tracking with isError and error fields - Derives session IDs deterministically for custom sessions
1 parent aa62bf2 commit ac96b3c

File tree

4 files changed

+460
-2
lines changed

4 files changed

+460
-2
lines changed

src/index.ts

Lines changed: 139 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
UserIdentity,
66
MCPServerLike,
77
HighLevelMCPServerLike,
8+
CustomEventData,
9+
UnredactedEvent,
810
} from "./types.js";
911

1012
// Import from modules
@@ -15,14 +17,23 @@ import {
1517
import { writeToLog } from "./modules/logging.js";
1618
import { setupMCPCatTools } from "./modules/tools.js";
1719
import { setupToolCallTracing } from "./modules/tracing.js";
18-
import { getSessionInfo, newSessionId } from "./modules/session.js";
20+
import {
21+
getSessionInfo,
22+
newSessionId,
23+
deriveSessionIdFromMCPSession,
24+
} from "./modules/session.js";
1925
import {
2026
setServerTrackingData,
2127
getServerTrackingData,
2228
} from "./modules/internal.js";
2329
import { setupTracking } from "./modules/tracingV2.js";
2430
import { TelemetryManager } from "./modules/telemetry.js";
25-
import { setTelemetryManager } from "./modules/eventQueue.js";
31+
import {
32+
setTelemetryManager,
33+
publishEvent as publishEventToQueue,
34+
} from "./modules/eventQueue.js";
35+
import { MCPCAT_CUSTOM_EVENT_TYPE } from "./modules/constants.js";
36+
import { eventQueue } from "./modules/eventQueue.js";
2637

2738
/**
2839
* Integrates MCPCat analytics into an MCP server to track tool usage patterns and user interactions.
@@ -212,12 +223,138 @@ function track(
212223
}
213224
}
214225

226+
/**
227+
* Publishes a custom event to MCPCat with flexible session management.
228+
*
229+
* @param serverOrSessionId - Either a tracked MCP server instance or a MCP session ID string
230+
* @param projectId - Your MCPCat project ID (required)
231+
* @param eventData - Optional event data to include with the custom event
232+
*
233+
* @returns Promise that resolves when the event is queued for publishing
234+
*
235+
* @example
236+
* ```typescript
237+
* // With a tracked server
238+
* await mcpcat.publishCustomEvent(
239+
* server,
240+
* "proj_abc123xyz",
241+
* {
242+
* resourceName: "custom-action",
243+
* parameters: { action: "user-feedback", rating: 5 },
244+
* message: "User provided feedback"
245+
* }
246+
* );
247+
* ```
248+
*
249+
* @example
250+
* ```typescript
251+
* // With a MCP session ID
252+
* await mcpcat.publishCustomEvent(
253+
* "user-session-12345",
254+
* "proj_abc123xyz",
255+
* {
256+
* isError: true,
257+
* error: { message: "Custom error occurred", code: "ERR_001" }
258+
* }
259+
* );
260+
* ```
261+
*
262+
* @example
263+
* ```typescript
264+
* await mcpcat.publishCustomEvent(
265+
* server,
266+
* "proj_abc123xyz",
267+
* {
268+
* resourceName: "feature-usage",
269+
* }
270+
* );
271+
* ```
272+
*/
273+
export async function publishCustomEvent(
274+
serverOrSessionId: any | string,
275+
projectId: string,
276+
eventData?: CustomEventData,
277+
): Promise<void> {
278+
// Validate required parameters
279+
if (!projectId) {
280+
throw new Error("projectId is required for publishCustomEvent");
281+
}
282+
283+
let sessionId: string;
284+
285+
// Determine if the first parameter is a tracked server or a session ID string
286+
const isServer =
287+
typeof serverOrSessionId === "object" && serverOrSessionId !== null;
288+
let lowLevelServer: MCPServerLike | null = null;
289+
290+
if (isServer) {
291+
// Try to get tracking data for the server
292+
lowLevelServer = serverOrSessionId.server
293+
? serverOrSessionId.server
294+
: serverOrSessionId;
295+
const trackingData = getServerTrackingData(lowLevelServer as MCPServerLike);
296+
297+
if (trackingData) {
298+
// Use the tracked server's session ID and configuration
299+
sessionId = trackingData.sessionId;
300+
} else {
301+
// Server is not tracked - treat it as an error
302+
throw new Error(
303+
"Server is not tracked. Please call mcpcat.track() first or provide a session ID string.",
304+
);
305+
}
306+
} else if (typeof serverOrSessionId === "string") {
307+
// Custom session ID provided - derive a deterministic session ID
308+
sessionId = deriveSessionIdFromMCPSession(serverOrSessionId, projectId);
309+
} else {
310+
throw new Error(
311+
"First parameter must be either an MCP server object or a session ID string",
312+
);
313+
}
314+
315+
// Build the event object
316+
const event: UnredactedEvent = {
317+
// Core fields
318+
sessionId,
319+
projectId,
320+
321+
// Fixed event type for custom events
322+
eventType: MCPCAT_CUSTOM_EVENT_TYPE,
323+
324+
// Timestamp
325+
timestamp: new Date(),
326+
327+
// Event data from parameters
328+
resourceName: eventData?.resourceName,
329+
parameters: eventData?.parameters,
330+
response: eventData?.response,
331+
userIntent: eventData?.message,
332+
duration: eventData?.duration,
333+
isError: eventData?.isError,
334+
error: eventData?.error,
335+
};
336+
337+
// If we have a tracked server, use the publishEvent function
338+
// Otherwise, add directly to the event queue
339+
if (lowLevelServer && getServerTrackingData(lowLevelServer)) {
340+
publishEventToQueue(lowLevelServer, event);
341+
} else {
342+
// For custom sessions, we need to import and use the event queue directly
343+
eventQueue.add(event);
344+
}
345+
346+
writeToLog(
347+
`Published custom event for session ${sessionId} with type 'mcpcat:custom'`,
348+
);
349+
}
350+
215351
export type {
216352
MCPCatOptions,
217353
UserIdentity,
218354
RedactFunction,
219355
ExporterConfig,
220356
Exporter,
357+
CustomEventData,
221358
} from "./types.js";
222359

223360
export type IdentifyFunction = MCPCatOptions["identify"];

src/modules/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
// MCPCat Settings
22
export const INACTIVITY_TIMEOUT_IN_MINUTES = 30;
33
export 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."`;
4+
export const MCPCAT_CUSTOM_EVENT_TYPE = "mcpcat:custom";

0 commit comments

Comments
 (0)