mcp-product-manager
Version:
MCP Orchestrator for task and project management with web interface
361 lines • 13.2 kB
JavaScript
/**
* 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