fix(mcp): prevents the mcp from failing due to the newly introduced ConfigurationError object thrown if .taskmasterconfig is not present. I'll need to implement MCP tools for model to manage models from MCP and be able to create it.
This commit is contained in:
@@ -1,251 +0,0 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
class AsyncOperationManager {
|
||||
constructor() {
|
||||
this.operations = new Map(); // Stores active operation state
|
||||
this.completedOperations = new Map(); // Stores completed operations
|
||||
this.maxCompletedOperations = 100; // Maximum number of completed operations to store
|
||||
this.listeners = new Map(); // For potential future notifications
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an operation to be executed asynchronously.
|
||||
* @param {Function} operationFn - The async function to execute (e.g., a Direct function).
|
||||
* @param {Object} args - Arguments to pass to the operationFn.
|
||||
* @param {Object} context - The MCP tool context { log, reportProgress, session }.
|
||||
* @returns {string} The unique ID assigned to this operation.
|
||||
*/
|
||||
addOperation(operationFn, args, context) {
|
||||
const operationId = `op-${uuidv4()}`;
|
||||
const operation = {
|
||||
id: operationId,
|
||||
status: 'pending',
|
||||
startTime: Date.now(),
|
||||
endTime: null,
|
||||
result: null,
|
||||
error: null,
|
||||
// Store necessary parts of context, especially log for background execution
|
||||
log: context.log,
|
||||
reportProgress: context.reportProgress, // Pass reportProgress through
|
||||
session: context.session // Pass session through if needed by the operationFn
|
||||
};
|
||||
this.operations.set(operationId, operation);
|
||||
this.log(operationId, 'info', `Operation added.`);
|
||||
|
||||
// Start execution in the background (don't await here)
|
||||
this._runOperation(operationId, operationFn, args, context).catch((err) => {
|
||||
// Catch unexpected errors during the async execution setup itself
|
||||
this.log(
|
||||
operationId,
|
||||
'error',
|
||||
`Critical error starting operation: ${err.message}`,
|
||||
{ stack: err.stack }
|
||||
);
|
||||
operation.status = 'failed';
|
||||
operation.error = {
|
||||
code: 'MANAGER_EXECUTION_ERROR',
|
||||
message: err.message
|
||||
};
|
||||
operation.endTime = Date.now();
|
||||
|
||||
// Move to completed operations
|
||||
this._moveToCompleted(operationId);
|
||||
});
|
||||
|
||||
return operationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function to execute the operation.
|
||||
* @param {string} operationId - The ID of the operation.
|
||||
* @param {Function} operationFn - The async function to execute.
|
||||
* @param {Object} args - Arguments for the function.
|
||||
* @param {Object} context - The original MCP tool context.
|
||||
*/
|
||||
async _runOperation(operationId, operationFn, args, context) {
|
||||
const operation = this.operations.get(operationId);
|
||||
if (!operation) return; // Should not happen
|
||||
|
||||
operation.status = 'running';
|
||||
this.log(operationId, 'info', `Operation running.`);
|
||||
this.emit('statusChanged', { operationId, status: 'running' });
|
||||
|
||||
try {
|
||||
// Pass the necessary context parts to the direct function
|
||||
// The direct function needs to be adapted if it needs reportProgress
|
||||
// We pass the original context's log, plus our wrapped reportProgress
|
||||
const result = await operationFn(args, operation.log, {
|
||||
reportProgress: (progress) =>
|
||||
this._handleProgress(operationId, progress),
|
||||
mcpLog: operation.log, // Pass log as mcpLog if direct fn expects it
|
||||
session: operation.session
|
||||
});
|
||||
|
||||
operation.status = result.success ? 'completed' : 'failed';
|
||||
operation.result = result.success ? result.data : null;
|
||||
operation.error = result.success ? null : result.error;
|
||||
this.log(
|
||||
operationId,
|
||||
'info',
|
||||
`Operation finished with status: ${operation.status}`
|
||||
);
|
||||
} catch (error) {
|
||||
this.log(
|
||||
operationId,
|
||||
'error',
|
||||
`Operation failed with error: ${error.message}`,
|
||||
{ stack: error.stack }
|
||||
);
|
||||
operation.status = 'failed';
|
||||
operation.error = {
|
||||
code: 'OPERATION_EXECUTION_ERROR',
|
||||
message: error.message
|
||||
};
|
||||
} finally {
|
||||
operation.endTime = Date.now();
|
||||
this.emit('statusChanged', {
|
||||
operationId,
|
||||
status: operation.status,
|
||||
result: operation.result,
|
||||
error: operation.error
|
||||
});
|
||||
|
||||
// Move to completed operations if done or failed
|
||||
if (operation.status === 'completed' || operation.status === 'failed') {
|
||||
this._moveToCompleted(operationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an operation from active operations to completed operations history.
|
||||
* @param {string} operationId - The ID of the operation to move.
|
||||
* @private
|
||||
*/
|
||||
_moveToCompleted(operationId) {
|
||||
const operation = this.operations.get(operationId);
|
||||
if (!operation) return;
|
||||
|
||||
// Store only the necessary data in completed operations
|
||||
const completedData = {
|
||||
id: operation.id,
|
||||
status: operation.status,
|
||||
startTime: operation.startTime,
|
||||
endTime: operation.endTime,
|
||||
result: operation.result,
|
||||
error: operation.error
|
||||
};
|
||||
|
||||
this.completedOperations.set(operationId, completedData);
|
||||
this.operations.delete(operationId);
|
||||
|
||||
// Trim completed operations if exceeding maximum
|
||||
if (this.completedOperations.size > this.maxCompletedOperations) {
|
||||
// Get the oldest operation (sorted by endTime)
|
||||
const oldest = [...this.completedOperations.entries()].sort(
|
||||
(a, b) => a[1].endTime - b[1].endTime
|
||||
)[0];
|
||||
|
||||
if (oldest) {
|
||||
this.completedOperations.delete(oldest[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles progress updates from the running operation and forwards them.
|
||||
* @param {string} operationId - The ID of the operation reporting progress.
|
||||
* @param {Object} progress - The progress object { progress, total? }.
|
||||
*/
|
||||
_handleProgress(operationId, progress) {
|
||||
const operation = this.operations.get(operationId);
|
||||
if (operation && operation.reportProgress) {
|
||||
try {
|
||||
// Use the reportProgress function captured from the original context
|
||||
operation.reportProgress(progress);
|
||||
this.log(
|
||||
operationId,
|
||||
'debug',
|
||||
`Reported progress: ${JSON.stringify(progress)}`
|
||||
);
|
||||
} catch (err) {
|
||||
this.log(
|
||||
operationId,
|
||||
'warn',
|
||||
`Failed to report progress: ${err.message}`
|
||||
);
|
||||
// Don't stop the operation, just log the reporting failure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the status and result/error of an operation.
|
||||
* @param {string} operationId - The ID of the operation.
|
||||
* @returns {Object | null} The operation details or null if not found.
|
||||
*/
|
||||
getStatus(operationId) {
|
||||
// First check active operations
|
||||
const operation = this.operations.get(operationId);
|
||||
if (operation) {
|
||||
return {
|
||||
id: operation.id,
|
||||
status: operation.status,
|
||||
startTime: operation.startTime,
|
||||
endTime: operation.endTime,
|
||||
result: operation.result,
|
||||
error: operation.error
|
||||
};
|
||||
}
|
||||
|
||||
// Then check completed operations
|
||||
const completedOperation = this.completedOperations.get(operationId);
|
||||
if (completedOperation) {
|
||||
return completedOperation;
|
||||
}
|
||||
|
||||
// Operation not found in either active or completed
|
||||
return {
|
||||
error: {
|
||||
code: 'OPERATION_NOT_FOUND',
|
||||
message: `Operation ID ${operationId} not found. It may have been completed and removed from history, or the ID may be invalid.`
|
||||
},
|
||||
status: 'not_found'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal logging helper to prefix logs with the operation ID.
|
||||
* @param {string} operationId - The ID of the operation.
|
||||
* @param {'info'|'warn'|'error'|'debug'} level - Log level.
|
||||
* @param {string} message - Log message.
|
||||
* @param {Object} [meta] - Additional metadata.
|
||||
*/
|
||||
log(operationId, level, message, meta = {}) {
|
||||
const operation = this.operations.get(operationId);
|
||||
// Use the logger instance associated with the operation if available, otherwise console
|
||||
const logger = operation?.log || console;
|
||||
const logFn = logger[level] || logger.log || console.log; // Fallback
|
||||
logFn(`[AsyncOp ${operationId}] ${message}`, meta);
|
||||
}
|
||||
|
||||
// --- Basic Event Emitter ---
|
||||
on(eventName, listener) {
|
||||
if (!this.listeners.has(eventName)) {
|
||||
this.listeners.set(eventName, []);
|
||||
}
|
||||
this.listeners.get(eventName).push(listener);
|
||||
}
|
||||
|
||||
emit(eventName, data) {
|
||||
if (this.listeners.has(eventName)) {
|
||||
this.listeners.get(eventName).forEach((listener) => listener(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export a singleton instance
|
||||
const asyncOperationManager = new AsyncOperationManager();
|
||||
|
||||
// Export the manager and potentially the class if needed elsewhere
|
||||
export { asyncOperationManager, AsyncOperationManager };
|
||||
@@ -5,7 +5,6 @@ import { fileURLToPath } from 'url';
|
||||
import fs from 'fs';
|
||||
import logger from './logger.js';
|
||||
import { registerTaskMasterTools } from './tools/index.js';
|
||||
import { asyncOperationManager } from './core/utils/async-manager.js';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
@@ -35,9 +34,6 @@ class TaskMasterMCPServer {
|
||||
|
||||
this.server.addResourceTemplate({});
|
||||
|
||||
// Make the manager accessible (e.g., pass it to tool registration)
|
||||
this.asyncManager = asyncOperationManager;
|
||||
|
||||
// Bind methods
|
||||
this.init = this.init.bind(this);
|
||||
this.start = this.start.bind(this);
|
||||
@@ -88,7 +84,4 @@ class TaskMasterMCPServer {
|
||||
}
|
||||
}
|
||||
|
||||
// Export the manager from here as well, if needed elsewhere
|
||||
export { asyncOperationManager };
|
||||
|
||||
export default TaskMasterMCPServer;
|
||||
|
||||
@@ -27,14 +27,12 @@ import { registerComplexityReportTool } from './complexity-report.js';
|
||||
import { registerAddDependencyTool } from './add-dependency.js';
|
||||
import { registerRemoveTaskTool } from './remove-task.js';
|
||||
import { registerInitializeProjectTool } from './initialize-project.js';
|
||||
import { asyncOperationManager } from '../core/utils/async-manager.js';
|
||||
|
||||
/**
|
||||
* Register all Task Master tools with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
* @param {asyncOperationManager} asyncManager - The async operation manager instance
|
||||
*/
|
||||
export function registerTaskMasterTools(server, asyncManager) {
|
||||
export function registerTaskMasterTools(server) {
|
||||
try {
|
||||
// Register each tool
|
||||
registerListTasksTool(server);
|
||||
@@ -47,7 +45,7 @@ export function registerTaskMasterTools(server, asyncManager) {
|
||||
registerShowTaskTool(server);
|
||||
registerNextTaskTool(server);
|
||||
registerExpandTaskTool(server);
|
||||
registerAddTaskTool(server, asyncManager);
|
||||
registerAddTaskTool(server);
|
||||
registerAddSubtaskTool(server);
|
||||
registerRemoveSubtaskTool(server);
|
||||
registerAnalyzeTool(server);
|
||||
|
||||
@@ -170,10 +170,14 @@ function _loadAndValidateConfig(explicitRoot = null) {
|
||||
}
|
||||
} else {
|
||||
// Config file doesn't exist
|
||||
// **Strict Check**: Throw error if config file is missing
|
||||
throw new ConfigurationError(
|
||||
`${CONFIG_FILE_NAME} not found at project root (${rootToUse || 'unknown'}).`
|
||||
// **Changed: Log warning instead of throwing error**
|
||||
console.warn(
|
||||
chalk.yellow(
|
||||
`Warning: ${CONFIG_FILE_NAME} not found at project root (${rootToUse || 'unknown'}). Using default configuration. Run 'task-master models --setup' to configure.`
|
||||
)
|
||||
);
|
||||
// Return defaults instead of throwing error
|
||||
return defaults;
|
||||
}
|
||||
|
||||
return config;
|
||||
@@ -541,11 +545,26 @@ function writeConfig(config, explicitRoot = null) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the .taskmasterconfig file exists at the project root
|
||||
* @param {string|null} explicitRoot - Optional explicit path to the project root
|
||||
* @returns {boolean} True if the file exists, false otherwise
|
||||
*/
|
||||
function isConfigFilePresent(explicitRoot = null) {
|
||||
const rootPath = explicitRoot || findProjectRoot();
|
||||
if (!rootPath) {
|
||||
return false;
|
||||
}
|
||||
const configPath = path.join(rootPath, CONFIG_FILE_NAME);
|
||||
return fs.existsSync(configPath);
|
||||
}
|
||||
|
||||
export {
|
||||
// Core config access
|
||||
getConfig,
|
||||
writeConfig,
|
||||
ConfigurationError, // Export custom error type
|
||||
isConfigFilePresent, // Add the new function export
|
||||
|
||||
// Validation
|
||||
validateProvider,
|
||||
|
||||
@@ -3,6 +3,7 @@ import boxen from 'boxen';
|
||||
import Table from 'cli-table3';
|
||||
|
||||
import { log, readJSON, truncate } from '../utils.js';
|
||||
import findNextTask from './find-next-task.js';
|
||||
|
||||
import {
|
||||
displayBanner,
|
||||
|
||||
@@ -1678,6 +1678,33 @@ The new AI architecture introduces a clear separation between sensitive credenti
|
||||
### Details:
|
||||
|
||||
|
||||
<info added on 2025-04-22T06:51:02.444Z>
|
||||
I'll provide additional technical information to enhance the "Cleanup Old AI Service Files" subtask:
|
||||
|
||||
## Implementation Details
|
||||
|
||||
**Pre-Cleanup Verification Steps:**
|
||||
- Run a comprehensive codebase search for any remaining imports or references to `ai-services.js` and `ai-client-factory.js` using grep or your IDE's search functionality[1][4]
|
||||
- Check for any dynamic imports that might not be caught by static analysis tools
|
||||
- Verify that all dependent modules have been properly migrated to the new AI service architecture
|
||||
|
||||
**Cleanup Process:**
|
||||
- Create a backup of the files before deletion in case rollback is needed
|
||||
- Document the file removal in the migration changelog with timestamps and specific file paths[5]
|
||||
- Update any build configuration files that might reference these files (webpack configs, etc.)
|
||||
- Run a full test suite after removal to ensure no runtime errors occur[2]
|
||||
|
||||
**Post-Cleanup Validation:**
|
||||
- Implement automated tests to verify the application functions correctly without the removed files
|
||||
- Monitor application logs and error reporting systems for 48-72 hours after deployment to catch any missed dependencies[3]
|
||||
- Perform a final code review to ensure clean architecture principles are maintained in the new implementation
|
||||
|
||||
**Technical Considerations:**
|
||||
- Check for any circular dependencies that might have been created during the migration process
|
||||
- Ensure proper garbage collection by removing any cached instances of the old services
|
||||
- Verify that performance metrics remain stable after the removal of legacy code
|
||||
</info added on 2025-04-22T06:51:02.444Z>
|
||||
|
||||
## 34. Audit and Standardize Env Variable Access [done]
|
||||
### Dependencies: None
|
||||
### Description: Audit the entire codebase (core modules, provider modules, utilities) to ensure all accesses to environment variables (API keys, configuration flags) consistently use a standardized resolution function (like `resolveEnvVariable` or a new utility) that checks `process.env` first and then `session.env` if available. Refactor any direct `process.env` access where `session.env` should also be considered.
|
||||
|
||||
@@ -3095,9 +3095,10 @@
|
||||
"id": 33,
|
||||
"title": "Cleanup Old AI Service Files",
|
||||
"description": "After all other migration subtasks (refactoring, provider implementation, testing, documentation) are complete and verified, remove the old `ai-services.js` and `ai-client-factory.js` files from the `scripts/modules/` directory. Ensure no code still references them.",
|
||||
"details": "",
|
||||
"details": "\n\n<info added on 2025-04-22T06:51:02.444Z>\nI'll provide additional technical information to enhance the \"Cleanup Old AI Service Files\" subtask:\n\n## Implementation Details\n\n**Pre-Cleanup Verification Steps:**\n- Run a comprehensive codebase search for any remaining imports or references to `ai-services.js` and `ai-client-factory.js` using grep or your IDE's search functionality[1][4]\n- Check for any dynamic imports that might not be caught by static analysis tools\n- Verify that all dependent modules have been properly migrated to the new AI service architecture\n\n**Cleanup Process:**\n- Create a backup of the files before deletion in case rollback is needed\n- Document the file removal in the migration changelog with timestamps and specific file paths[5]\n- Update any build configuration files that might reference these files (webpack configs, etc.)\n- Run a full test suite after removal to ensure no runtime errors occur[2]\n\n**Post-Cleanup Validation:**\n- Implement automated tests to verify the application functions correctly without the removed files\n- Monitor application logs and error reporting systems for 48-72 hours after deployment to catch any missed dependencies[3]\n- Perform a final code review to ensure clean architecture principles are maintained in the new implementation\n\n**Technical Considerations:**\n- Check for any circular dependencies that might have been created during the migration process\n- Ensure proper garbage collection by removing any cached instances of the old services\n- Verify that performance metrics remain stable after the removal of legacy code\n</info added on 2025-04-22T06:51:02.444Z>",
|
||||
"status": "pending",
|
||||
"dependencies": [
|
||||
"61.31",
|
||||
"61.32"
|
||||
],
|
||||
"parentTaskId": 61
|
||||
|
||||
48
test-config-manager.js
Normal file
48
test-config-manager.js
Normal file
@@ -0,0 +1,48 @@
|
||||
// test-config-manager.js
|
||||
console.log('=== ENVIRONMENT TEST ===');
|
||||
console.log('Working directory:', process.cwd());
|
||||
console.log('NODE_PATH:', process.env.NODE_PATH);
|
||||
|
||||
// Test basic imports
|
||||
try {
|
||||
console.log('Importing config-manager');
|
||||
// Use dynamic import for ESM
|
||||
const configManagerModule = await import(
|
||||
'./scripts/modules/config-manager.js'
|
||||
);
|
||||
const configManager = configManagerModule.default || configManagerModule;
|
||||
console.log('Config manager loaded successfully');
|
||||
|
||||
console.log('Loading supported models');
|
||||
// Add after line 14 (after "Config manager loaded successfully")
|
||||
console.log('Config manager exports:', Object.keys(configManager));
|
||||
} catch (error) {
|
||||
console.error('Import error:', error.message);
|
||||
console.error(error.stack);
|
||||
}
|
||||
|
||||
// Test file access
|
||||
try {
|
||||
console.log('Checking for .taskmasterconfig');
|
||||
// Use dynamic import for ESM
|
||||
const { readFileSync, existsSync } = await import('fs');
|
||||
const { resolve } = await import('path');
|
||||
|
||||
const configExists = existsSync('./.taskmasterconfig');
|
||||
console.log('.taskmasterconfig exists:', configExists);
|
||||
|
||||
if (configExists) {
|
||||
const config = JSON.parse(readFileSync('./.taskmasterconfig', 'utf-8'));
|
||||
console.log('Config keys:', Object.keys(config));
|
||||
}
|
||||
|
||||
console.log('Checking for supported-models.json');
|
||||
const modelsPath = resolve('./scripts/modules/supported-models.json');
|
||||
console.log('Models path:', modelsPath);
|
||||
const modelsExists = existsSync(modelsPath);
|
||||
console.log('supported-models.json exists:', modelsExists);
|
||||
} catch (error) {
|
||||
console.error('File access error:', error.message);
|
||||
}
|
||||
|
||||
console.log('=== TEST COMPLETE ===');
|
||||
Reference in New Issue
Block a user