UNPKG

mcp-product-manager

Version:

MCP Orchestrator for task and project management with web interface

361 lines 13.2 kB
/** * Legacy Tool Adapter Middleware * * Maps old tool names to new ones and logs deprecation warnings * Ensures backward compatibility while encouraging migration */ import { sendSuccess } from '../utils/response.js'; // Mapping of legacy tool names to new tool names const LEGACY_TOOL_MAPPINGS = { // Task management - old bash function names to new MCP tools 'task_create': 'create_task', 'task_list': 'list_tasks', 'task_get': 'get_task', 'task_claim': 'claim_task', 'task_complete': 'complete_task', 'task_update_status': 'update_task_status', 'task_get_blocked': 'get_blocked_tasks', 'task_get_all_summaries': 'get_project_summaries', // Project management 'project_create': 'create_project', 'project_list': 'list_projects', 'get_project_status': 'get_project_status', 'project_set_purpose': 'update_project_purpose', 'project_recalibrate': 'recalibrate_project', // Bundle operations 'bundle_analyze': 'analyze_bundles', 'bundle_create': 'create_bundle', 'bundle_claim': 'claim_bundle', 'bundle_get': 'get_bundle_details', // Agent operations 'agent_spawn': 'spawn_agent', 'agent_list': 'list_agents', 'agent_status': 'get_agent_status', 'agent_get_scorecard': 'get_agent_scorecard', // Orchestrator operations 'orch_analyze_work': 'orchestrate', 'orch_get_next_action': 'get_next_action', 'orch_review_task': 'review_task', // Database operations 'db_query': 'execute_sqlite', 'db_get_error_patterns': 'get_error_patterns', // Legacy bash API functions 'spawn_agent_basic': 'spawn_agent', 'spawn_agent_bundle': 'spawn_agent', 'spawn_agent_critical': 'spawn_agent', 'get_ready_tasks': 'list_tasks', 'get_active_agents': 'list_agents', // Deprecated composite operations 'claim_and_start': 'claim_task', 'complete_and_review': 'complete_task', 'create_and_assign': 'create_task' }; // Track deprecation warnings to avoid spamming const deprecationWarnings = new Map(); const WARNING_COOLDOWN = 300000; // 5 minutes class LegacyToolAdapter { constructor() { this.usageStats = new Map(); this.migrationSuggestions = new Map(); this.initializeMigrationSuggestions(); } initializeMigrationSuggestions() { // Provide migration guidance for common patterns this.migrationSuggestions.set('task_create', { old: 'task_create "description" "priority" "project" "hours"', new: 'create_task({ description, priority, project, estimated_hours })', notes: 'New format uses object parameters for better clarity' }); this.migrationSuggestions.set('task_claim', { old: 'task_claim "TASK-001" "agent-name"', new: 'claim_task({ task_id: "TASK-001", agent: "agent-name" })', notes: 'Explicit parameter names improve readability' }); this.migrationSuggestions.set('bundle_analyze', { old: 'bundle_analyze "project-name"', new: 'analyze_bundles({ project: "project-name", include_stats: true })', notes: 'New tool provides more options and better response format' }); this.migrationSuggestions.set('spawn_agent_basic', { old: 'spawn_agent_basic "project" "agent-name"', new: 'spawn_agent({ project, agent_type: "basic", model: "sonnet" })', notes: 'Unified spawn_agent tool with explicit type parameter' }); } /** * Middleware to intercept and adapt legacy tool calls */ middleware() { return async (req, res, next) => { const toolName = this.extractToolName(req); if (!toolName) { return next(); } // Check if this is a legacy tool const newToolName = LEGACY_TOOL_MAPPINGS[toolName]; if (newToolName) { // Log deprecation warning this.logDeprecation(toolName, newToolName, req); // Track usage statistics this.trackUsage(toolName, req); // Adapt the request this.adaptRequest(req, toolName, newToolName); // Add deprecation notice to response const originalJson = res.json.bind(res); res.json = (data) => { if (data && typeof data === 'object') { data._deprecation = { warning: `Tool '${toolName}' is deprecated. Use '${newToolName}' instead.`, migration: this.migrationSuggestions.get(toolName), will_be_removed: '2025-03-01' }; } return originalJson(data); }; } next(); }; } /** * Extract tool name from request */ extractToolName(req) { // Check multiple possible locations for tool name // 1. MCP tool execution endpoint if (req.path === '/api/mcp/execute' && req.body?.tool) { return req.body.tool; } // 2. Direct tool endpoints (legacy style) const pathParts = req.path.split('/'); if (pathParts[2] === 'tools') { return pathParts[3]; } // 3. Header-based tool specification if (req.headers['x-tool-name']) { return req.headers['x-tool-name']; } return null; } /** * Adapt legacy request format to new format */ adaptRequest(req, oldTool, newTool) { // Update tool name in request if (req.body?.tool) { req.body.tool = newTool; } // Adapt parameters based on specific tool mappings switch (oldTool) { case 'task_create': this.adaptTaskCreate(req); break; case 'task_claim': this.adaptTaskClaim(req); break; case 'spawn_agent_basic': case 'spawn_agent_bundle': case 'spawn_agent_critical': this.adaptSpawnAgent(req, oldTool); break; case 'bundle_analyze': this.adaptBundleAnalyze(req); break; case 'claim_and_start': this.adaptClaimAndStart(req); break; case 'complete_and_review': this.adaptCompleteAndReview(req); break; } // Add legacy flag for tracking req.isLegacyRequest = true; req.originalToolName = oldTool; } /** * Adapt task_create parameters */ adaptTaskCreate(req) { if (req.body?.arguments && Array.isArray(req.body.arguments)) { // Old format: ["description", "priority", "project", "hours"] const [description, priority, project, estimated_hours] = req.body.arguments; req.body.arguments = { description, priority: priority || 'medium', project: project || 'default', estimated_hours: parseFloat(estimated_hours) || 2 }; } } /** * Adapt task_claim parameters */ adaptTaskClaim(req) { if (req.body?.arguments && Array.isArray(req.body.arguments)) { // Old format: ["task_id", "agent_name"] const [task_id, agent] = req.body.arguments; req.body.arguments = { task_id, agent }; } } /** * Adapt spawn_agent variants */ adaptSpawnAgent(req, oldTool) { const agentType = oldTool.replace('spawn_agent_', ''); if (req.body?.arguments) { if (Array.isArray(req.body.arguments)) { // Old format: ["project", "agent_name"] const [project, agent_name] = req.body.arguments; req.body.arguments = { project, agent_type: agentType, agent_name, model: agentType === 'critical' ? 'opus' : 'sonnet' }; } else if (typeof req.body.arguments === 'object') { // Add agent_type if not present req.body.arguments.agent_type = req.body.arguments.agent_type || agentType; } } } /** * Adapt bundle_analyze parameters */ adaptBundleAnalyze(req) { if (req.body?.arguments) { if (typeof req.body.arguments === 'string') { // Old format: just project name as string req.body.arguments = { project: req.body.arguments, include_stats: true }; } } } /** * Adapt composite operations */ adaptClaimAndStart(req) { // Split into separate claim operation req.body.tool = 'claim_task'; req.legacyComposite = { followUp: 'update_task_status', followUpArgs: { status: 'in_progress' } }; } adaptCompleteAndReview(req) { // Split into separate complete operation req.body.tool = 'complete_task'; req.legacyComposite = { followUp: 'review_task', followUpArgs: { auto_review: true } }; } /** * Log deprecation warning */ logDeprecation(oldTool, newTool, req) { const now = Date.now(); const lastWarning = deprecationWarnings.get(oldTool); // Only log if enough time has passed since last warning if (!lastWarning || now - lastWarning > WARNING_COOLDOWN) { console.warn(`[DEPRECATION] Tool '${oldTool}' is deprecated. Use '${newTool}' instead.`, { agent: req.headers['x-agent'] || req.body?.agent || 'unknown', timestamp: new Date().toISOString(), migration: this.migrationSuggestions.get(oldTool), endpoint: req.path }); deprecationWarnings.set(oldTool, now); } // Track usage for reporting const count = this.usageStats.get(oldTool) || 0; this.usageStats.set(oldTool, count + 1); } /** * Track usage statistics */ trackUsage(toolName, req) { const agent = req.headers['x-agent'] || req.body?.agent || 'unknown'; const key = `${toolName}:${agent}`; if (!this.usageStats.has(key)) { this.usageStats.set(key, { tool: toolName, agent, count: 0, firstSeen: new Date(), lastSeen: null }); } const stats = this.usageStats.get(key); stats.count++; stats.lastSeen = new Date(); } /** * Get deprecation report */ getDeprecationReport() { const report = { summary: { total_legacy_calls: 0, unique_legacy_tools: new Set(), unique_agents: new Set(), top_offenders: [] }, details: [], migration_guide: {} }; // Aggregate statistics for (const [key, stats] of this.usageStats) { if (key.includes(':')) { report.details.push(stats); report.summary.total_legacy_calls += stats.count; report.summary.unique_legacy_tools.add(stats.tool); report.summary.unique_agents.add(stats.agent); } } // Convert sets to arrays report.summary.unique_legacy_tools = Array.from(report.summary.unique_legacy_tools); report.summary.unique_agents = Array.from(report.summary.unique_agents); // Find top offenders report.summary.top_offenders = report.details .sort((a, b) => b.count - a.count) .slice(0, 10) .map(s => ({ agent: s.agent, tool: s.tool, count: s.count })); // Add migration guide for (const [oldTool, suggestion] of this.migrationSuggestions) { if (report.summary.unique_legacy_tools.includes(oldTool)) { report.migration_guide[oldTool] = suggestion; } } return report; } /** * Express route for deprecation report */ reportRoute() { return async (req, res) => { const report = this.getDeprecationReport(); sendSuccess(res, 'Deprecation report generated', report); }; } } // Singleton instance let adapterInstance = null; export function getLegacyToolAdapter() { if (!adapterInstance) { adapterInstance = new LegacyToolAdapter(); } return adapterInstance; } // Export middleware for easy use export const legacyToolMiddleware = () => { const adapter = getLegacyToolAdapter(); return adapter.middleware(); }; // Export report route export const deprecationReportRoute = () => { const adapter = getLegacyToolAdapter(); return adapter.reportRoute(); }; export default LegacyToolAdapter; //# sourceMappingURL=legacyToolAdapter.js.map