Skip to content

Commit dbfbda7

Browse files
committed
feat: add publishCustomEvent function for custom analytics events
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 98729f8 commit dbfbda7

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)