@grebyn/toolflow-mcp-server
Version:
MCP server for managing other MCP servers - discover, install, organize into bundles, and automate with workflows. Uses StreamableHTTP transport with dual OAuth/API key authentication.
103 lines • 3.96 kB
JavaScript
/**
* MCP Server Logger Utility
*
* Provides centralized logging for MCP tool executions
* Non-blocking and failure-resilient
*/
import { getServerConfig } from '../config/server-config.js';
import { sanitizeData, calculateDataSize, getAuthHeader, determineExecutionStatus, extractErrorMessage, withRetry } from './shared-logging.js';
// Sanitization is now handled by shared utilities
/**
* Main logging function - fire and forget
*/
export async function logToolExecution(context, logContext) {
try {
const config = getServerConfig();
const endpoint = `${config.apiEndpoint}/logs`;
// Sanitize data using shared utilities
const sanitizedArgs = sanitizeData(logContext.toolArgs);
const sanitizedResponse = sanitizeData(logContext.toolResponse);
// Prepare log entry with new fields
const logEntry = {
tool_name: logContext.toolName,
tool_args: sanitizedArgs,
tool_response: sanitizedResponse,
execution_status: logContext.executionStatus || 'success',
error_message: logContext.errorMessage,
execution_time_ms: logContext.executionTimeMs,
response_size: logContext.toolResponse ? calculateDataSize(logContext.toolResponse) : undefined,
transport_type: logContext.transportType,
session_id: logContext.sessionId || context.sessionId,
client_identifier: logContext.clientIdentifier,
ip_address: logContext.ipAddress,
user_agent: logContext.userAgent
};
// Get authentication header using shared utility
const authHeader = getAuthHeader(context);
// Fire and forget with retry - don't await
withRetry(async () => {
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': authHeader
},
body: JSON.stringify(logEntry)
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP ${response.status}: ${errorText}`);
}
}).catch(() => {
// Silent fail - logging errors should not break tool execution
});
}
catch (error) {
// Silently fail - logging should never break tool execution
}
}
/**
* Wrapper to time and log tool execution
*/
export async function withLogging(context, toolName, toolArgs, transportType, additionalContext, executeFunction) {
const startTime = Date.now();
let toolResponse;
let error;
try {
// Execute the actual tool
const result = await executeFunction();
toolResponse = result;
// Log successful execution with response
logToolExecution(context, {
toolName,
toolArgs,
toolResponse: result,
executionStatus: 'success',
executionTimeMs: Date.now() - startTime,
transportType,
...additionalContext
});
return result;
}
catch (err) {
error = err;
// Use shared utilities to determine error details
const executionStatus = determineExecutionStatus(error);
const errorMessage = extractErrorMessage(error);
// Log failed execution with error details as response
logToolExecution(context, {
toolName,
toolArgs,
toolResponse: { error: errorMessage, error_type: executionStatus },
executionStatus,
errorMessage,
executionTimeMs: Date.now() - startTime,
transportType,
...additionalContext
});
// Re-throw the error
throw error;
}
}
// Client identifier extraction is now handled by shared utilities
//# sourceMappingURL=logger.js.map