chore(api): remove unused backend endpoints after MCP audit

Remove legacy backend routes that no longer have frontend or internal
callers, including the old Claude/Codex MCP APIs, unused Cursor and Codex
helper endpoints, stale TaskMaster detection/next/initialize routes,
and unused command/project helpers.

This reduces duplicated MCP behavior now handled by the provider-based
MCP API, shrinks the exposed backend surface, and removes probe/service
code that only existed for deleted endpoints.

Add an MCP settings API audit document to capture the route-usage
analysis and explain why the legacy MCP endpoints were considered safe
to remove.
This commit is contained in:
Haileyesus
2026-04-17 15:57:07 +03:00
parent 4e962272cd
commit eb6268748b
14 changed files with 17 additions and 2125 deletions

View File

@@ -13,16 +13,10 @@ import fs from 'fs';
import path from 'path';
import { promises as fsPromises } from 'fs';
import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import os from 'os';
import { extractProjectDirectory } from '../projects.js';
import { detectTaskMasterMCPServer } from '../utils/mcp-detector.js';
import { broadcastTaskMasterProjectUpdate, broadcastTaskMasterTasksUpdate } from '../utils/taskmaster-websocket.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const router = express.Router();
/**
@@ -100,140 +94,6 @@ async function checkTaskMasterInstallation() {
});
}
/**
* Detect .taskmaster folder presence in a given project directory
* @param {string} projectPath - Absolute path to project directory
* @returns {Promise<Object>} Detection result with status and metadata
*/
async function detectTaskMasterFolder(projectPath) {
try {
const taskMasterPath = path.join(projectPath, '.taskmaster');
// Check if .taskmaster directory exists
try {
const stats = await fsPromises.stat(taskMasterPath);
if (!stats.isDirectory()) {
return {
hasTaskmaster: false,
reason: '.taskmaster exists but is not a directory'
};
}
} catch (error) {
if (error.code === 'ENOENT') {
return {
hasTaskmaster: false,
reason: '.taskmaster directory not found'
};
}
throw error;
}
// Check for key TaskMaster files
const keyFiles = [
'tasks/tasks.json',
'config.json'
];
const fileStatus = {};
let hasEssentialFiles = true;
for (const file of keyFiles) {
const filePath = path.join(taskMasterPath, file);
try {
await fsPromises.access(filePath, fs.constants.R_OK);
fileStatus[file] = true;
} catch (error) {
fileStatus[file] = false;
if (file === 'tasks/tasks.json') {
hasEssentialFiles = false;
}
}
}
// Parse tasks.json if it exists for metadata
let taskMetadata = null;
if (fileStatus['tasks/tasks.json']) {
try {
const tasksPath = path.join(taskMasterPath, 'tasks/tasks.json');
const tasksContent = await fsPromises.readFile(tasksPath, 'utf8');
const tasksData = JSON.parse(tasksContent);
// Handle both tagged and legacy formats
let tasks = [];
if (tasksData.tasks) {
// Legacy format
tasks = tasksData.tasks;
} else {
// Tagged format - get tasks from all tags
Object.values(tasksData).forEach(tagData => {
if (tagData.tasks) {
tasks = tasks.concat(tagData.tasks);
}
});
}
// Calculate task statistics
const stats = tasks.reduce((acc, task) => {
acc.total++;
acc[task.status] = (acc[task.status] || 0) + 1;
// Count subtasks
if (task.subtasks) {
task.subtasks.forEach(subtask => {
acc.subtotalTasks++;
acc.subtasks = acc.subtasks || {};
acc.subtasks[subtask.status] = (acc.subtasks[subtask.status] || 0) + 1;
});
}
return acc;
}, {
total: 0,
subtotalTasks: 0,
pending: 0,
'in-progress': 0,
done: 0,
review: 0,
deferred: 0,
cancelled: 0,
subtasks: {}
});
taskMetadata = {
taskCount: stats.total,
subtaskCount: stats.subtotalTasks,
completed: stats.done || 0,
pending: stats.pending || 0,
inProgress: stats['in-progress'] || 0,
review: stats.review || 0,
completionPercentage: stats.total > 0 ? Math.round((stats.done / stats.total) * 100) : 0,
lastModified: (await fsPromises.stat(tasksPath)).mtime.toISOString()
};
} catch (parseError) {
console.warn('Failed to parse tasks.json:', parseError.message);
taskMetadata = { error: 'Failed to parse tasks.json' };
}
}
return {
hasTaskmaster: true,
hasEssentialFiles,
files: fileStatus,
metadata: taskMetadata,
path: taskMasterPath
};
} catch (error) {
console.error('Error detecting TaskMaster folder:', error);
return {
hasTaskmaster: false,
reason: `Error checking directory: ${error.message}`
};
}
}
// MCP detection is now handled by the centralized utility
// API Routes
/**
@@ -271,298 +131,6 @@ router.get('/installation-status', async (req, res) => {
}
});
/**
* GET /api/taskmaster/detect/:projectName
* Detect TaskMaster configuration for a specific project
*/
router.get('/detect/:projectName', async (req, res) => {
try {
const { projectName } = req.params;
// Use the existing extractProjectDirectory function to get actual project path
let projectPath;
try {
projectPath = await extractProjectDirectory(projectName);
} catch (error) {
console.error('Error extracting project directory:', error);
return res.status(404).json({
error: 'Project path not found',
projectName,
message: error.message
});
}
// Verify the project path exists
try {
await fsPromises.access(projectPath, fs.constants.R_OK);
} catch (error) {
return res.status(404).json({
error: 'Project path not accessible',
projectPath,
projectName,
message: error.message
});
}
// Run detection in parallel
const [taskMasterResult, mcpResult] = await Promise.all([
detectTaskMasterFolder(projectPath),
detectTaskMasterMCPServer()
]);
// Determine overall status
let status = 'not-configured';
if (taskMasterResult.hasTaskmaster && taskMasterResult.hasEssentialFiles) {
if (mcpResult.hasMCPServer && mcpResult.isConfigured) {
status = 'fully-configured';
} else {
status = 'taskmaster-only';
}
} else if (mcpResult.hasMCPServer && mcpResult.isConfigured) {
status = 'mcp-only';
}
const responseData = {
projectName,
projectPath,
status,
taskmaster: taskMasterResult,
mcp: mcpResult,
timestamp: new Date().toISOString()
};
res.json(responseData);
} catch (error) {
console.error('TaskMaster detection error:', error);
res.status(500).json({
error: 'Failed to detect TaskMaster configuration',
message: error.message
});
}
});
/**
* GET /api/taskmaster/detect-all
* Detect TaskMaster configuration for all known projects
* This endpoint works with the existing projects system
*/
router.get('/detect-all', async (req, res) => {
try {
// Import getProjects from the projects module
const { getProjects } = await import('../projects.js');
const projects = await getProjects();
// Run detection for all projects in parallel
const detectionPromises = projects.map(async (project) => {
try {
// Use the project's fullPath if available, otherwise extract the directory
let projectPath;
if (project.fullPath) {
projectPath = project.fullPath;
} else {
try {
projectPath = await extractProjectDirectory(project.name);
} catch (error) {
throw new Error(`Failed to extract project directory: ${error.message}`);
}
}
const [taskMasterResult, mcpResult] = await Promise.all([
detectTaskMasterFolder(projectPath),
detectTaskMasterMCPServer()
]);
// Determine status
let status = 'not-configured';
if (taskMasterResult.hasTaskmaster && taskMasterResult.hasEssentialFiles) {
if (mcpResult.hasMCPServer && mcpResult.isConfigured) {
status = 'fully-configured';
} else {
status = 'taskmaster-only';
}
} else if (mcpResult.hasMCPServer && mcpResult.isConfigured) {
status = 'mcp-only';
}
return {
projectName: project.name,
displayName: project.displayName,
projectPath,
status,
taskmaster: taskMasterResult,
mcp: mcpResult
};
} catch (error) {
return {
projectName: project.name,
displayName: project.displayName,
status: 'error',
error: error.message
};
}
});
const results = await Promise.all(detectionPromises);
res.json({
projects: results,
summary: {
total: results.length,
fullyConfigured: results.filter(p => p.status === 'fully-configured').length,
taskmasterOnly: results.filter(p => p.status === 'taskmaster-only').length,
mcpOnly: results.filter(p => p.status === 'mcp-only').length,
notConfigured: results.filter(p => p.status === 'not-configured').length,
errors: results.filter(p => p.status === 'error').length
},
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('Bulk TaskMaster detection error:', error);
res.status(500).json({
error: 'Failed to detect TaskMaster configuration for projects',
message: error.message
});
}
});
/**
* POST /api/taskmaster/initialize/:projectName
* Initialize TaskMaster in a project (placeholder for future CLI integration)
*/
router.post('/initialize/:projectName', async (req, res) => {
try {
const { projectName } = req.params;
const { rules } = req.body; // Optional rule profiles
// This will be implemented in a later subtask with CLI integration
res.status(501).json({
error: 'TaskMaster initialization not yet implemented',
message: 'This endpoint will execute task-master init via CLI in a future update',
projectName,
rules
});
} catch (error) {
console.error('TaskMaster initialization error:', error);
res.status(500).json({
error: 'Failed to initialize TaskMaster',
message: error.message
});
}
});
/**
* GET /api/taskmaster/next/:projectName
* Get the next recommended task using task-master CLI
*/
router.get('/next/:projectName', async (req, res) => {
try {
const { projectName } = req.params;
// Get project path
let projectPath;
try {
projectPath = await extractProjectDirectory(projectName);
} catch (error) {
return res.status(404).json({
error: 'Project not found',
message: `Project "${projectName}" does not exist`
});
}
// Try to execute task-master next command
try {
const { spawn } = await import('child_process');
const nextTaskCommand = spawn('task-master', ['next'], {
cwd: projectPath,
stdio: ['pipe', 'pipe', 'pipe']
});
let stdout = '';
let stderr = '';
nextTaskCommand.stdout.on('data', (data) => {
stdout += data.toString();
});
nextTaskCommand.stderr.on('data', (data) => {
stderr += data.toString();
});
await new Promise((resolve, reject) => {
nextTaskCommand.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`task-master next failed with code ${code}: ${stderr}`));
}
});
nextTaskCommand.on('error', (error) => {
reject(error);
});
});
// Parse the output - task-master next usually returns JSON
let nextTaskData = null;
if (stdout.trim()) {
try {
nextTaskData = JSON.parse(stdout);
} catch (parseError) {
// If not JSON, treat as plain text
nextTaskData = { message: stdout.trim() };
}
}
res.json({
projectName,
projectPath,
nextTask: nextTaskData,
timestamp: new Date().toISOString()
});
} catch (cliError) {
console.warn('Failed to execute task-master CLI:', cliError.message);
// Fallback to loading tasks and finding next one locally
// Use localhost to bypass proxy for internal server-to-server calls
const tasksResponse = await fetch(`http://localhost:${process.env.SERVER_PORT || process.env.PORT || '3001'}/api/taskmaster/tasks/${encodeURIComponent(projectName)}`, {
headers: {
'Authorization': req.headers.authorization
}
});
if (tasksResponse.ok) {
const tasksData = await tasksResponse.json();
const nextTask = tasksData.tasks?.find(task =>
task.status === 'pending' || task.status === 'in-progress'
) || null;
res.json({
projectName,
projectPath,
nextTask,
fallback: true,
message: 'Used fallback method (CLI not available)',
timestamp: new Date().toISOString()
});
} else {
throw new Error('Failed to load tasks via fallback method');
}
}
} catch (error) {
console.error('TaskMaster next task error:', error);
res.status(500).json({
error: 'Failed to get next task',
message: error.message
});
}
});
/**
* GET /api/taskmaster/tasks/:projectName
* Load actual tasks from .taskmaster/tasks/tasks.json
@@ -904,66 +472,6 @@ router.get('/prd/:projectName/:fileName', async (req, res) => {
}
});
/**
* DELETE /api/taskmaster/prd/:projectName/:fileName
* Delete a specific PRD file
*/
router.delete('/prd/:projectName/:fileName', async (req, res) => {
try {
const { projectName, fileName } = req.params;
// Get project path
let projectPath;
try {
projectPath = await extractProjectDirectory(projectName);
} catch (error) {
return res.status(404).json({
error: 'Project not found',
message: `Project "${projectName}" does not exist`
});
}
const filePath = path.join(projectPath, '.taskmaster', 'docs', fileName);
// Check if file exists
try {
await fsPromises.access(filePath, fs.constants.F_OK);
} catch (error) {
return res.status(404).json({
error: 'PRD file not found',
message: `File "${fileName}" does not exist`
});
}
// Delete the file
try {
await fsPromises.unlink(filePath);
res.json({
projectName,
projectPath,
fileName,
message: 'PRD file deleted successfully',
timestamp: new Date().toISOString()
});
} catch (deleteError) {
console.error('Failed to delete PRD file:', deleteError);
return res.status(500).json({
error: 'Failed to delete PRD file',
message: deleteError.message
});
}
} catch (error) {
console.error('PRD delete error:', error);
res.status(500).json({
error: 'Failed to delete PRD file',
message: error.message
});
}
});
/**
* POST /api/taskmaster/init/:projectName
* Initialize TaskMaster in a project