Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions packages/app/src/server/transport/base-transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,14 @@ export abstract class BaseTransport {
/**
* Track a method call with timing and error status
*/
protected trackMethodCall(methodName: string | null, startTime: number, isError: boolean = false): void {
protected trackMethodCall(
methodName: string | null,
startTime: number,
isError: boolean = false,
clientInfo?: { name: string; version: string }
): void {
const duration = Date.now() - startTime;
this.metrics.trackMethod(methodName, duration, isError);
this.metrics.trackMethod(methodName, duration, isError, clientInfo);
}

/**
Expand Down Expand Up @@ -763,7 +768,7 @@ export abstract class StatefulTransport<TSession extends BaseSession = BaseSessi
// Check if server is shutting down
if (this.isShuttingDown) {
this.trackError(503);
this.metrics.trackMethod(trackingName, undefined, true);
this.metrics.trackMethod(trackingName, undefined, true, undefined);
return {
isValid: false,
errorResponse: JsonRpcErrors.serverShuttingDown(extractJsonRpcId(requestBody)),
Expand All @@ -775,7 +780,7 @@ export abstract class StatefulTransport<TSession extends BaseSession = BaseSessi
// Check session ID requirements
if (!sessionId && !allowMissingSession) {
this.trackError(400);
this.metrics.trackMethod(trackingName, undefined, true);
this.metrics.trackMethod(trackingName, undefined, true, undefined);
return {
isValid: false,
errorResponse: JsonRpcErrors.invalidParams('sessionId is required', extractJsonRpcId(requestBody)),
Expand All @@ -787,7 +792,7 @@ export abstract class StatefulTransport<TSession extends BaseSession = BaseSessi
// Check session existence
if (sessionId && !this.sessions.has(sessionId)) {
this.trackError(404);
this.metrics.trackMethod(trackingName, undefined, true);
this.metrics.trackMethod(trackingName, undefined, true, undefined);
return {
isValid: false,
errorResponse: JsonRpcErrors.sessionNotFound(sessionId, extractJsonRpcId(requestBody)),
Expand Down
15 changes: 10 additions & 5 deletions packages/app/src/server/transport/stateless-http-transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,10 @@ export class StatelessHttpTransport extends BaseTransport {
this.trackNewConnection();

if (isJSONRPCNotification(req.body)) {
this.trackMethodCall(trackingName, startTime, false);
// For notifications, try to get client info from analytics session
const analyticsSession = sessionId ? this.analyticsSessions.get(sessionId) : undefined;
const clientInfo = analyticsSession?.metadata.clientInfo;
this.trackMethodCall(trackingName, startTime, false, clientInfo);
res.status(202).json({ jsonrpc: '2.0', result: null });
return;
}
Expand Down Expand Up @@ -348,8 +351,8 @@ export class StatelessHttpTransport extends BaseTransport {

await transport.handleRequest(req, res, req.body);

// Track successful method call
this.trackMethodCall(trackingName, startTime, false);
// Track successful method call with client info
this.trackMethodCall(trackingName, startTime, false, clientInfo);

logger.debug(
{
Expand Down Expand Up @@ -378,8 +381,10 @@ export class StatelessHttpTransport extends BaseTransport {
'Error handling request'
);

// Track failed method call
this.trackMethodCall(trackingName, startTime, true);
// Track failed method call - try to get client info from analytics session
const analyticsSession = sessionId ? this.analyticsSessions.get(sessionId) : undefined;
const clientInfo = analyticsSession?.metadata.clientInfo;
this.trackMethodCall(trackingName, startTime, true, clientInfo);

this.trackError(500, error instanceof Error ? error : new Error(String(error)));

Expand Down
41 changes: 40 additions & 1 deletion packages/app/src/shared/transport-metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ export interface ClientMetrics {
totalConnections: number;
}

/**
* Metrics per client for a specific method
*/
export interface ClientMethodMetrics {
clientName: string;
count: number;
}

/**
* Metrics per MCP method
*/
Expand All @@ -87,6 +95,7 @@ export interface MethodMetrics {
lastCalled: Date;
averageResponseTime?: number;
errors: number;
byClient: Map<string, ClientMethodMetrics>;
}

/**
Expand Down Expand Up @@ -205,6 +214,10 @@ export interface TransportMetricsResponse {
averageResponseTime?: number; // milliseconds
errors: number;
errorRate: number; // percentage
byClient: Array<{
clientName: string;
count: number;
}>;
}>;

// API call metrics (only shown in external API mode)
Expand Down Expand Up @@ -263,6 +276,10 @@ export function formatMetricsForAPI(
firstCalled: method.firstCalled.toISOString(),
lastCalled: method.lastCalled.toISOString(),
errorRate: method.count > 0 ? (method.errors / method.count) * 100 : 0,
byClient: Array.from(method.byClient.values()).map((client) => ({
clientName: client.clientName,
count: client.count,
})),
})),
};
}
Expand Down Expand Up @@ -528,7 +545,12 @@ export class MetricsCounter {
/**
* Track a method call
*/
trackMethod(method: string | null, responseTime?: number, isError: boolean = false): void {
trackMethod(
method: string | null,
responseTime?: number,
isError: boolean = false,
clientInfo?: { name: string; version: string }
): void {
if (!method) return;
let methodMetrics = this.metrics.methods.get(method);

Expand All @@ -539,13 +561,30 @@ export class MetricsCounter {
firstCalled: new Date(),
lastCalled: new Date(),
errors: 0,
byClient: new Map(),
};
this.metrics.methods.set(method, methodMetrics);
}

methodMetrics.count++;
methodMetrics.lastCalled = new Date();

// Track per-client breakdown
if (clientInfo) {
const clientName = clientInfo.name;
let clientMethodMetrics = methodMetrics.byClient.get(clientName);

if (!clientMethodMetrics) {
clientMethodMetrics = {
clientName,
count: 0,
};
methodMetrics.byClient.set(clientName, clientMethodMetrics);
}

clientMethodMetrics.count++;
}

if (isError) {
methodMetrics.errors++;
}
Expand Down