Files
Momento/mcp-server/tool-handlers.js
Antigravity 0784c94242
Some checks failed
CI / Lint, Test & Build (push) Failing after 57s
CI / Deploy production (on server) (push) Has been skipped
feat(notes): vues structurées tableau/kanban, flashcards et MCP robuste
Ajoute la base organisable par carnet (schéma, champs partagés, valeurs par note)
avec activation guidée, tableau éditable, kanban et suppression de colonnes.
Corrige le multiselect en vue tableau et enrichit sidebar, grille et i18n FR/EN.
Inclut aussi les améliorations flashcards SM-2, l'audit consentement IA et la
robustesse du serveur MCP (config, validation, rate-limit, métriques).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 23:03:16 +00:00

156 lines
4.5 KiB
JavaScript

/**
* Memento MCP Server - Enhanced Tool Handler Wrapper
*
* Wraps tool handlers with error handling, metrics recording, and validation.
* Import this in tools.js to wrap the CallToolRequestSchema handler.
*/
import { McpErrors, getErrorCategory, mcpErrorContent, logError } from './errors.js';
import { recordRequest, recordError, recordToolExecution } from './metrics.js';
/**
* Wrap a tool handler with error handling and metrics
*
* @param {string} toolName - Name of the tool
* @param {Function} handler - The actual tool handler function
* @param {object} options - Options
* @returns {Function} Wrapped handler
*/
export function wrapToolHandler(toolName, handler, options = {}) {
const { timeoutMs = 60000 } = options;
return async (args, context) => {
const start = Date.now();
// Create timeout promise
const timeout = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Tool ${toolName} timed out after ${timeoutMs}ms`));
}, timeoutMs);
});
try {
// Race between handler and timeout
const result = await Promise.race([
handler(args, context),
timeout,
]);
// Record success
const duration = Date.now() - start;
recordToolExecution(toolName, true, duration);
recordRequest(toolName, 'success', 'mcp', duration);
return result;
} catch (error) {
const duration = Date.now() - start;
// Determine error type
let errorCode = McpErrors.INTERNAL_ERROR.code;
let errorMessage = error.message || 'An unexpected error occurred';
if (error.code === 'P2025') {
errorCode = McpErrors.NOT_FOUND.code;
errorMessage = 'Record not found';
} else if (error.code?.startsWith('P')) {
errorCode = McpErrors.DATABASE_ERROR.code;
errorMessage = 'Database operation failed';
} else if (error.message?.includes('timeout')) {
errorCode = McpErrors.TIMEOUT.code;
}
// Record error
recordError(getErrorCategory(errorCode), errorCode, { tool: toolName });
recordToolExecution(toolName, false, duration);
recordRequest(toolName, 'error', 'mcp', duration);
// Log error
logError(console, error, { tool: toolName });
// Return error response
return mcpErrorContent(errorCode, {
detail: errorMessage,
context: { tool: toolName, duration },
cause: error,
});
}
};
}
/**
* Create a tool handler map with wrapped handlers
*
* @param {object} handlers - Map of tool name to handler function
* @param {object} options - Options for wrapping
* @returns {object} Map of wrapped handlers
*/
export function createToolHandlerMap(handlers, options = {}) {
const wrapped = {};
for (const [name, handler] of Object.entries(handlers)) {
wrapped[name] = wrapToolHandler(name, handler, options);
}
return wrapped;
}
/**
* Execute a tool by name with proper error handling
*
* @param {string} toolName - Name of the tool to execute
* @param {object} handlers - Map of tool handlers
* @param {object} args - Tool arguments
* @param {object} context - Execution context (prisma, userId, etc.)
* @param {object} options - Options
* @returns {Promise<object>} Tool result
*/
export async function executeTool(toolName, handlers, args, context, options = {}) {
const handler = handlers[toolName];
if (!handler) {
return mcpErrorContent(McpErrors.NOT_FOUND.code, {
detail: `Tool not found: ${toolName}`,
context: { availableTools: Object.keys(handlers) },
});
}
return wrapToolHandler(toolName, handler, options)(args, context);
}
/**
* Batch execute multiple tools
*
* @param {Array} operations - Array of { tool, args } objects
* @param {object} handlers - Map of tool handlers
* @param {object} context - Execution context
* @param {object} options - Options
* @returns {Promise<Array>} Array of results
*/
export async function executeBatch(operations, handlers, context, options = {}) {
const results = [];
const { continueOnError = true } = options;
for (const op of operations) {
try {
const result = await executeTool(op.tool, handlers, op.args || {}, context, options);
results.push({ tool: op.tool, success: true, result });
} catch (error) {
results.push({
tool: op.tool,
success: false,
error: error.message || 'Execution failed',
});
if (!continueOnError) {
break;
}
}
}
return results;
}
export default {
wrapToolHandler,
createToolHandlerMap,
executeTool,
executeBatch,
};