mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-04-20 20:41:31 +00:00
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user