import axios from "axios";
import { getMetaMcpApiBaseUrl, getMetaMcpApiKey } from "./utils.js";
import {
ProfileCapability,
getProfileCapabilities,
} from "./fetch-capabilities.js";
// Define status enum for tool execution
export enum ToolExecutionStatus {
SUCCESS = "SUCCESS",
ERROR = "ERROR",
PENDING = "PENDING",
}
// Define interface for tool execution log data
export interface ToolExecutionLog {
id?: string;
tool_name: string;
payload: any;
status: ToolExecutionStatus;
result?: any;
mcp_server_uuid: string;
error_message?: string | null;
execution_time_ms: number;
created_at?: string;
updated_at?: string;
}
// Response interfaces
export interface ToolLogResponse {
id?: string;
success: boolean;
data?: any;
error?: string;
status?: number;
details?: any;
}
// Class to manage tool execution logs
export class ToolLogManager {
private static instance: ToolLogManager;
private logStore: Map<string, ToolExecutionLog> = new Map();
private constructor() {}
public static getInstance(): ToolLogManager {
if (!ToolLogManager.instance) {
ToolLogManager.instance = new ToolLogManager();
}
return ToolLogManager.instance;
}
/**
* Creates a new tool execution log
* @param toolName Name of the tool
* @param serverUuid UUID of the MCP server
* @param payload The input parameters for the tool
* @returns Log object with tracking ID
*/
public async createLog(
toolName: string,
serverUuid: string,
payload: any
): Promise<ToolExecutionLog> {
// Check for TOOL_LOGS capability first
const profileCapabilities = await getProfileCapabilities();
const hasToolsLogCapability = profileCapabilities.includes(
ProfileCapability.TOOL_LOGS
);
// Generate a temporary ID for tracking
const tempId = `${Date.now()}-${Math.random()
.toString(36)
.substring(2, 9)}`;
const log: ToolExecutionLog = {
id: tempId, // Will be replaced with the real ID from the API
tool_name: toolName,
mcp_server_uuid: serverUuid,
payload,
status: ToolExecutionStatus.PENDING,
execution_time_ms: 0,
created_at: new Date().toISOString(),
};
// Store in memory
this.logStore.set(tempId, log);
// Submit to API only if TOOL_LOGS capability is present
if (hasToolsLogCapability) {
const response = await reportToolExecutionLog(log);
// Update with real ID if available
if (response.success && response.data?.id) {
const newId = response.data.id;
log.id = newId;
this.logStore.delete(tempId);
this.logStore.set(newId, log);
}
}
return log;
}
/**
* Updates the status of a tool execution log
* @param logId ID of the log to update
* @param status New status
* @param result Optional result data
* @param errorMessage Optional error message
* @param executionTimeMs Optional execution time in milliseconds
* @returns Updated log
*/
public async updateLogStatus(
logId: string,
status: ToolExecutionStatus,
result?: any,
errorMessage?: string | null,
executionTimeMs?: number
): Promise<ToolExecutionLog | null> {
const log = this.logStore.get(logId);
if (!log) {
console.error(`Cannot update log: Log with ID ${logId} not found`);
return null;
}
// Update log properties
log.status = status;
if (result !== undefined) log.result = result;
if (errorMessage !== undefined) log.error_message = errorMessage;
if (executionTimeMs !== undefined) log.execution_time_ms = executionTimeMs;
log.updated_at = new Date().toISOString();
// Update in memory
this.logStore.set(logId, log);
// Check for TOOL_LOGS capability before sending update to API
const profileCapabilities = await getProfileCapabilities();
const hasToolsLogCapability = profileCapabilities.includes(
ProfileCapability.TOOL_LOGS
);
// Send update to API only if TOOL_LOGS capability is present
if (hasToolsLogCapability) {
await updateToolExecutionLog(logId, {
status,
result,
error_message: errorMessage,
execution_time_ms: executionTimeMs,
});
}
return log;
}
/**
* Get a log by ID
* @param logId ID of the log
* @returns Log or null if not found
*/
public getLog(logId: string): ToolExecutionLog | null {
return this.logStore.get(logId) || null;
}
/**
* Complete the tool execution log with success status
* @param logId ID of the log to complete
* @param result Result data
* @param executionTimeMs Execution time in milliseconds
* @returns Updated log
*/
public async completeLog(
logId: string,
result: any,
executionTimeMs: number
): Promise<ToolExecutionLog | null> {
return this.updateLogStatus(
logId,
ToolExecutionStatus.SUCCESS,
result,
null,
executionTimeMs
);
}
/**
* Mark the tool execution log as failed
* @param logId ID of the log to fail
* @param errorMessage Error message
* @param executionTimeMs Execution time in milliseconds
* @returns Updated log
*/
public async failLog(
logId: string,
errorMessage: string,
executionTimeMs: number
): Promise<ToolExecutionLog | null> {
return this.updateLogStatus(
logId,
ToolExecutionStatus.ERROR,
null,
errorMessage,
executionTimeMs
);
}
}
/**
* Reports a tool execution log to the MetaMCP API
* @param logData The tool execution log data
* @returns Result of the API call
*/
export async function reportToolExecutionLog(
logData: ToolExecutionLog
): Promise<ToolLogResponse> {
try {
// Check for TOOL_LOGS capability first
const profileCapabilities = await getProfileCapabilities();
const hasToolsLogCapability = profileCapabilities.includes(
ProfileCapability.TOOL_LOGS
);
if (!hasToolsLogCapability) {
return { success: false, error: "TOOL_LOGS capability not enabled" };
}
const apiKey = getMetaMcpApiKey();
const apiBaseUrl = getMetaMcpApiBaseUrl();
if (!apiKey) {
return { success: false, error: "API key not set" };
}
// Validate required fields
if (!logData.tool_name || !logData.mcp_server_uuid) {
return {
success: false,
error: "Missing required fields: tool_name or mcp_server_uuid",
status: 400,
};
}
// Submit log to MetaMCP API
try {
const response = await axios.post(
`${apiBaseUrl}/api/tool-execution-logs`,
logData,
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
}
);
return {
success: true,
data: response.data,
};
} catch (error: any) {
if (error.response) {
// The request was made and the server responded with a status code outside of 2xx
return {
success: false,
error:
error.response.data.error || "Failed to submit tool execution log",
status: error.response.status,
details: error.response.data,
};
} else if (error.request) {
// The request was made but no response was received
return {
success: false,
error: "No response received from server",
details: error.request,
};
} else {
// Something happened in setting up the request
return {
success: false,
error: "Error setting up request",
details: error.message,
};
}
}
} catch (error: any) {
return {
success: false,
error: "Failed to process tool execution log request",
status: 500,
details: error.message,
};
}
}
/**
* Updates an existing tool execution log
* @param logId The ID of the log to update
* @param updateData The updated log data
* @returns Result of the API call
*/
export async function updateToolExecutionLog(
logId: string,
updateData: Partial<ToolExecutionLog>
): Promise<ToolLogResponse> {
try {
// Check for TOOL_LOGS capability first
const profileCapabilities = await getProfileCapabilities();
const hasToolsLogCapability = profileCapabilities.includes(
ProfileCapability.TOOL_LOGS
);
if (!hasToolsLogCapability) {
return { success: false, error: "TOOL_LOGS capability not enabled" };
}
const apiKey = getMetaMcpApiKey();
const apiBaseUrl = getMetaMcpApiBaseUrl();
if (!apiKey) {
return { success: false, error: "API key not set" };
}
if (!logId) {
return {
success: false,
error: "Log ID is required for updates",
};
}
// Submit update to MetaMCP API
try {
const response = await axios.put(
`${apiBaseUrl}/api/tool-execution-logs/${logId}`,
updateData,
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
}
);
return {
success: true,
data: response.data,
};
} catch (error: any) {
if (error.response) {
return {
success: false,
error:
error.response.data.error || "Failed to update tool execution log",
status: error.response.status,
details: error.response.data,
};
} else if (error.request) {
return {
success: false,
error: "No response received from server",
details: error.request,
};
} else {
return {
success: false,
error: "Error setting up request",
details: error.message,
};
}
}
} catch (error: any) {
return {
success: false,
error: "Failed to process update request",
status: 500,
details: error.message,
};
}
}
/**
* Simple function to log a tool execution
* @param toolName Name of the tool
* @param serverUuid UUID of the MCP server
* @param payload The input parameters for the tool
* @param result The result of the tool execution
* @param status Status of the execution
* @param errorMessage Optional error message if execution failed
* @param executionTimeMs Time taken to execute the tool in milliseconds
* @returns Result of the API call
*/
export async function logToolExecution(
toolName: string,
serverUuid: string,
payload: any,
result: any = null,
status: ToolExecutionStatus = ToolExecutionStatus.SUCCESS,
errorMessage: string | null = null,
executionTimeMs: number = 0
): Promise<ToolLogResponse> {
// Check for TOOL_LOGS capability first
const profileCapabilities = await getProfileCapabilities();
const hasToolsLogCapability = profileCapabilities.includes(
ProfileCapability.TOOL_LOGS
);
if (!hasToolsLogCapability) {
return { success: false, error: "TOOL_LOGS capability not enabled" };
}
const logData: ToolExecutionLog = {
tool_name: toolName,
mcp_server_uuid: serverUuid,
payload,
status,
result,
error_message: errorMessage,
execution_time_ms: executionTimeMs,
};
return await reportToolExecutionLog(logData);
}