/**
* Higher-order function to wrap a tool and log usage after successful invocation.
* Usage: export default withToolUsageLogging(toolFn, metadata.name)
*/
export function withToolUsageLogging<T extends (...args: any[]) => Promise<any>>(
toolFn: T,
toolName: string
): T {
return (async (params: any, ...rest: any[]) => {
const result = await toolFn(params, ...rest);
// Only log if no error thrown
logToolUsage(toolName, params);
return result;
}) as T;
}
import { supabase } from "./supabase-client";
import { getUserIdFromApiKey, decrementUserToolAllotment } from "./token-auth";
import { headers } from "xmcp/headers";
export interface ToolUsageLog {
user_id: string | null;
tool_name: string;
params: Record<string, any>;
timestamp: string;
api_key_id: string | null;
}
/**
* Logs a tool usage event to the 'tool_usage_log' table in Supabase.
* If Supabase is not available, logs to stderr as a fallback.
*/
export async function logToolUsage(toolName: string, params: Record<string, any>) {
try {
const reqHeaders = headers();
let apiKeyRaw = reqHeaders["x-api-key"] || null;
let apiKey: string | null = null;
if (Array.isArray(apiKeyRaw)) {
apiKey = apiKeyRaw[0] || null;
} else {
apiKey = apiKeyRaw;
}
let userId: string | null = null;
let apiKeyId: string | null = null;
if (apiKey) {
// Look up user_id and api_key_id (id from api_tokens)
const crypto = await import('crypto');
const { supabase } = await import('./supabase-client');
const tokenHash = crypto.createHash('sha256').update(apiKey).digest('hex');
const { data, error } = await supabase
.from('api_tokens')
.select('id, user_id')
.eq('token_hash', tokenHash)
.maybeSingle();
if (error) {
console.error('[logToolUsage] Supabase error during api_key_id lookup:', error);
throw new Error('Internal error validating API key.');
}
userId = data?.user_id || null;
apiKeyId = data?.id || null;
}
const logEntry: ToolUsageLog = {
user_id: userId,
tool_name: toolName,
params,
timestamp: new Date().toISOString(),
api_key_id: apiKeyId,
};
// Attempt to insert into Supabase
const { error } = await supabase.from("tool_usage_log").insert([logEntry]);
if (error) {
console.error("[logToolUsage] Failed to log tool usage to Supabase:", error, logEntry);
}
// Decrement user's allotment if userId is present
if (userId) {
try {
await decrementUserToolAllotment(userId);
} catch (decrementErr) {
// If quota exhausted or decrement fails, log and throw error
console.error("[logToolUsage] Failed to decrement user allotment:", decrementErr);
throw new Error("Tool call allotment exhausted or error decrementing quota. Please purchase more or contact support.");
}
}
} catch (err) {
console.error("[logToolUsage] Unexpected error logging tool usage:", err, { toolName, params });
}
}