diff --git a/.changeset/dirty-webs-jam.md b/.changeset/dirty-webs-jam.md new file mode 100644 index 00000000..575019d0 --- /dev/null +++ b/.changeset/dirty-webs-jam.md @@ -0,0 +1,5 @@ +--- +"task-master-ai": patch +--- + +Fix MCP tool calls logging errors diff --git a/.changeset/odd-banks-fly.md b/.changeset/odd-banks-fly.md new file mode 100644 index 00000000..68e8d2b1 --- /dev/null +++ b/.changeset/odd-banks-fly.md @@ -0,0 +1,5 @@ +--- +"task-master-ai": patch +--- + +Fix bug in expand_all mcp tool diff --git a/.cursor/rules/taskmaster.mdc b/.cursor/rules/taskmaster.mdc index 3446e673..b9e7ecaf 100644 --- a/.cursor/rules/taskmaster.mdc +++ b/.cursor/rules/taskmaster.mdc @@ -45,7 +45,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov * **Description:** `Parse a Product Requirements Document, PRD, or text file with Taskmaster to automatically generate an initial set of tasks in tasks.json.` * **Key Parameters/Options:** * `input`: `Path to your PRD or requirements text file that Taskmaster should parse for tasks.` (CLI: `[file]` positional or `-i, --input `) - * `output`: `Specify where Taskmaster should save the generated 'tasks.json' file. Defaults to 'tasks/tasks.json'.` (CLI: `-o, --output `) + * `output`: `Specify where Taskmaster should save the generated 'tasks.json' file. Defaults to '.taskmaster/tasks/tasks.json'.` (CLI: `-o, --output `) * `numTasks`: `Approximate number of top-level tasks Taskmaster should aim to generate from the document.` (CLI: `-n, --num-tasks `) * `force`: `Use this to allow Taskmaster to overwrite an existing 'tasks.json' without asking for confirmation.` (CLI: `-f, --force`) * **Usage:** Useful for bootstrapping a project from an existing requirements document. diff --git a/mcp-server/src/core/direct-functions/complexity-report.js b/mcp-server/src/core/direct-functions/complexity-report.js index 5b34f678..2edfc7fa 100644 --- a/mcp-server/src/core/direct-functions/complexity-report.js +++ b/mcp-server/src/core/direct-functions/complexity-report.js @@ -28,8 +28,7 @@ export async function complexityReportDirect(args, log) { log.error('complexityReportDirect called without reportPath'); return { success: false, - error: { code: 'MISSING_ARGUMENT', message: 'reportPath is required' }, - fromCache: false + error: { code: 'MISSING_ARGUMENT', message: 'reportPath is required' } }; } @@ -111,8 +110,7 @@ export async function complexityReportDirect(args, log) { error: { code: 'UNEXPECTED_ERROR', message: error.message - }, - fromCache: false + } }; } } diff --git a/mcp-server/src/core/direct-functions/expand-all-tasks.js b/mcp-server/src/core/direct-functions/expand-all-tasks.js index 6f9dc3cb..4d1a8a74 100644 --- a/mcp-server/src/core/direct-functions/expand-all-tasks.js +++ b/mcp-server/src/core/direct-functions/expand-all-tasks.js @@ -60,7 +60,8 @@ export async function expandAllTasksDirect(args, log, context = {}) { useResearch, additionalContext, forceFlag, - { session, mcpLog, projectRoot } + { session, mcpLog, projectRoot }, + 'json' ); // Core function now returns a summary object including the *aggregated* telemetryData diff --git a/mcp-server/src/core/direct-functions/expand-task.js b/mcp-server/src/core/direct-functions/expand-task.js index 1ba53337..6c98dd91 100644 --- a/mcp-server/src/core/direct-functions/expand-task.js +++ b/mcp-server/src/core/direct-functions/expand-task.js @@ -29,7 +29,7 @@ import { createLogWrapper } from '../../tools/utils.js'; * @param {Object} log - Logger object * @param {Object} context - Context object containing session * @param {Object} [context.session] - MCP Session object - * @returns {Promise} - Task expansion result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean } + * @returns {Promise} - Task expansion result { success: boolean, data?: any, error?: { code: string, message: string } } */ export async function expandTaskDirect(args, log, context = {}) { const { session } = context; // Extract session @@ -54,8 +54,7 @@ export async function expandTaskDirect(args, log, context = {}) { error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' - }, - fromCache: false + } }; } @@ -73,8 +72,7 @@ export async function expandTaskDirect(args, log, context = {}) { error: { code: 'INPUT_VALIDATION_ERROR', message: 'Task ID is required' - }, - fromCache: false + } }; } @@ -105,8 +103,7 @@ export async function expandTaskDirect(args, log, context = {}) { error: { code: 'INVALID_TASKS_FILE', message: `No valid tasks found in ${tasksPath}. readJSON returned: ${JSON.stringify(data)}` - }, - fromCache: false + } }; } @@ -121,8 +118,7 @@ export async function expandTaskDirect(args, log, context = {}) { error: { code: 'TASK_NOT_FOUND', message: `Task with ID ${taskId} not found` - }, - fromCache: false + } }; } @@ -133,8 +129,7 @@ export async function expandTaskDirect(args, log, context = {}) { error: { code: 'TASK_COMPLETED', message: `Task ${taskId} is already marked as ${task.status} and cannot be expanded` - }, - fromCache: false + } }; } @@ -151,8 +146,7 @@ export async function expandTaskDirect(args, log, context = {}) { task, subtasksAdded: 0, hasExistingSubtasks - }, - fromCache: false + } }; } @@ -232,8 +226,7 @@ export async function expandTaskDirect(args, log, context = {}) { subtasksAdded, hasExistingSubtasks, telemetryData: coreResult.telemetryData - }, - fromCache: false + } }; } catch (error) { // Make sure to restore normal logging even if there's an error @@ -245,8 +238,7 @@ export async function expandTaskDirect(args, log, context = {}) { error: { code: 'CORE_FUNCTION_ERROR', message: error.message || 'Failed to expand task' - }, - fromCache: false + } }; } } catch (error) { @@ -256,8 +248,7 @@ export async function expandTaskDirect(args, log, context = {}) { error: { code: 'CORE_FUNCTION_ERROR', message: error.message || 'Failed to expand task' - }, - fromCache: false + } }; } } diff --git a/mcp-server/src/core/direct-functions/generate-task-files.js b/mcp-server/src/core/direct-functions/generate-task-files.js index 8a88e0da..e9b61dcc 100644 --- a/mcp-server/src/core/direct-functions/generate-task-files.js +++ b/mcp-server/src/core/direct-functions/generate-task-files.js @@ -28,8 +28,7 @@ export async function generateTaskFilesDirect(args, log) { log.error(errorMessage); return { success: false, - error: { code: 'MISSING_ARGUMENT', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_ARGUMENT', message: errorMessage } }; } if (!outputDir) { @@ -37,8 +36,7 @@ export async function generateTaskFilesDirect(args, log) { log.error(errorMessage); return { success: false, - error: { code: 'MISSING_ARGUMENT', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_ARGUMENT', message: errorMessage } }; } @@ -65,8 +63,7 @@ export async function generateTaskFilesDirect(args, log) { log.error(`Error in generateTaskFiles: ${genError.message}`); return { success: false, - error: { code: 'GENERATE_FILES_ERROR', message: genError.message }, - fromCache: false + error: { code: 'GENERATE_FILES_ERROR', message: genError.message } }; } @@ -79,8 +76,7 @@ export async function generateTaskFilesDirect(args, log) { outputDir: resolvedOutputDir, taskFiles: 'Individual task files have been generated in the output directory' - }, - fromCache: false // This operation always modifies state and should never be cached + } }; } catch (error) { // Make sure to restore normal logging if an outer error occurs @@ -92,8 +88,7 @@ export async function generateTaskFilesDirect(args, log) { error: { code: 'GENERATE_TASKS_ERROR', message: error.message || 'Unknown error generating task files' - }, - fromCache: false + } }; } } diff --git a/mcp-server/src/core/direct-functions/initialize-project.js b/mcp-server/src/core/direct-functions/initialize-project.js index a382837c..bb736d75 100644 --- a/mcp-server/src/core/direct-functions/initialize-project.js +++ b/mcp-server/src/core/direct-functions/initialize-project.js @@ -41,8 +41,7 @@ export async function initializeProjectDirect(args, log, context = {}) { code: 'INVALID_TARGET_DIRECTORY', message: `Cannot initialize project: Invalid target directory '${targetDirectory}' received. Please ensure a valid workspace/folder is open or specified.`, details: `Received args.projectRoot: ${args.projectRoot}` // Show what was received - }, - fromCache: false + } }; } @@ -97,8 +96,8 @@ export async function initializeProjectDirect(args, log, context = {}) { } if (success) { - return { success: true, data: resultData, fromCache: false }; + return { success: true, data: resultData }; } else { - return { success: false, error: errorResult, fromCache: false }; + return { success: false, error: errorResult }; } } diff --git a/mcp-server/src/core/direct-functions/list-tasks.js b/mcp-server/src/core/direct-functions/list-tasks.js index 49aa42bf..2a0133a3 100644 --- a/mcp-server/src/core/direct-functions/list-tasks.js +++ b/mcp-server/src/core/direct-functions/list-tasks.js @@ -14,7 +14,7 @@ import { * * @param {Object} args - Command arguments (now expecting tasksJsonPath explicitly). * @param {Object} log - Logger object. - * @returns {Promise} - Task list result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }. + * @returns {Promise} - Task list result { success: boolean, data?: any, error?: { code: string, message: string } }. */ export async function listTasksDirect(args, log) { // Destructure the explicit tasksJsonPath from args @@ -27,8 +27,7 @@ export async function listTasksDirect(args, log) { error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' - }, - fromCache: false + } }; } diff --git a/mcp-server/src/core/direct-functions/next-task.js b/mcp-server/src/core/direct-functions/next-task.js index 3bc80d48..bb1f0e33 100644 --- a/mcp-server/src/core/direct-functions/next-task.js +++ b/mcp-server/src/core/direct-functions/next-task.js @@ -19,7 +19,7 @@ import { * @param {Object} args - Command arguments * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file. * @param {Object} log - Logger object - * @returns {Promise} - Next task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean } + * @returns {Promise} - Next task result { success: boolean, data?: any, error?: { code: string, message: string } } */ export async function nextTaskDirect(args, log) { // Destructure expected args @@ -32,8 +32,7 @@ export async function nextTaskDirect(args, log) { error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' - }, - fromCache: false + } }; } @@ -121,7 +120,7 @@ export async function nextTaskDirect(args, log) { // Use the caching utility try { const result = await coreNextTaskAction(); - log.info(`nextTaskDirect completed.`); + log.info('nextTaskDirect completed.'); return result; } catch (error) { log.error(`Unexpected error during nextTask: ${error.message}`); @@ -130,8 +129,7 @@ export async function nextTaskDirect(args, log) { error: { code: 'UNEXPECTED_ERROR', message: error.message - }, - fromCache: false + } }; } } diff --git a/mcp-server/src/core/direct-functions/parse-prd.js b/mcp-server/src/core/direct-functions/parse-prd.js index 7ae84722..ffe74cd2 100644 --- a/mcp-server/src/core/direct-functions/parse-prd.js +++ b/mcp-server/src/core/direct-functions/parse-prd.js @@ -77,7 +77,7 @@ export async function parsePRDDirect(args, log, context = {}) { ? path.isAbsolute(outputArg) ? outputArg : path.resolve(projectRoot, outputArg) - : resolveProjectPath(TASKMASTER_TASKS_FILE, session) || + : resolveProjectPath(TASKMASTER_TASKS_FILE, args) || path.resolve(projectRoot, TASKMASTER_TASKS_FILE); // Check if input file exists diff --git a/mcp-server/src/core/direct-functions/remove-task.js b/mcp-server/src/core/direct-functions/remove-task.js index 2fb17099..26684817 100644 --- a/mcp-server/src/core/direct-functions/remove-task.js +++ b/mcp-server/src/core/direct-functions/remove-task.js @@ -21,7 +21,7 @@ import { * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file. * @param {string} args.id - The ID(s) of the task(s) or subtask(s) to remove (comma-separated for multiple). * @param {Object} log - Logger object - * @returns {Promise} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: false } + * @returns {Promise} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string } } */ export async function removeTaskDirect(args, log) { // Destructure expected args @@ -35,8 +35,7 @@ export async function removeTaskDirect(args, log) { error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' - }, - fromCache: false + } }; } @@ -48,8 +47,7 @@ export async function removeTaskDirect(args, log) { error: { code: 'INPUT_VALIDATION_ERROR', message: 'Task ID is required' - }, - fromCache: false + } }; } @@ -68,8 +66,7 @@ export async function removeTaskDirect(args, log) { error: { code: 'INVALID_TASKS_FILE', message: `No valid tasks found in ${tasksJsonPath}` - }, - fromCache: false + } }; } @@ -83,8 +80,7 @@ export async function removeTaskDirect(args, log) { error: { code: 'INVALID_TASK_ID', message: `The following tasks were not found: ${invalidTasks.join(', ')}` - }, - fromCache: false + } }; } @@ -133,8 +129,7 @@ export async function removeTaskDirect(args, log) { details: failedRemovals .map((r) => `${r.taskId}: ${r.error}`) .join('; ') - }, - fromCache: false + } }; } @@ -147,8 +142,7 @@ export async function removeTaskDirect(args, log) { failed: failedRemovals.length, results: results, tasksPath: tasksJsonPath - }, - fromCache: false + } }; } catch (error) { // Ensure silent mode is disabled even if an outer error occurs @@ -161,8 +155,7 @@ export async function removeTaskDirect(args, log) { error: { code: 'UNEXPECTED_ERROR', message: error.message - }, - fromCache: false + } }; } } diff --git a/mcp-server/src/core/direct-functions/set-task-status.js b/mcp-server/src/core/direct-functions/set-task-status.js index 9711b771..ae9dddf9 100644 --- a/mcp-server/src/core/direct-functions/set-task-status.js +++ b/mcp-server/src/core/direct-functions/set-task-status.js @@ -29,8 +29,7 @@ export async function setTaskStatusDirect(args, log) { log.error(errorMessage); return { success: false, - error: { code: 'MISSING_ARGUMENT', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_ARGUMENT', message: errorMessage } }; } @@ -41,8 +40,7 @@ export async function setTaskStatusDirect(args, log) { log.error(errorMessage); return { success: false, - error: { code: 'MISSING_TASK_ID', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_TASK_ID', message: errorMessage } }; } @@ -52,8 +50,7 @@ export async function setTaskStatusDirect(args, log) { log.error(errorMessage); return { success: false, - error: { code: 'MISSING_STATUS', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_STATUS', message: errorMessage } }; } @@ -82,8 +79,7 @@ export async function setTaskStatusDirect(args, log) { taskId, status: newStatus, tasksPath: tasksPath // Return the path used - }, - fromCache: false // This operation always modifies state and should never be cached + } }; // If the task was completed, attempt to fetch the next task @@ -126,8 +122,7 @@ export async function setTaskStatusDirect(args, log) { error: { code: 'SET_STATUS_ERROR', message: error.message || 'Unknown error setting task status' - }, - fromCache: false + } }; } finally { // ALWAYS restore normal logging in finally block @@ -145,8 +140,7 @@ export async function setTaskStatusDirect(args, log) { error: { code: 'SET_STATUS_ERROR', message: error.message || 'Unknown error setting task status' - }, - fromCache: false + } }; } } diff --git a/mcp-server/src/core/direct-functions/update-subtask-by-id.js b/mcp-server/src/core/direct-functions/update-subtask-by-id.js index f2c433cf..0df637d9 100644 --- a/mcp-server/src/core/direct-functions/update-subtask-by-id.js +++ b/mcp-server/src/core/direct-functions/update-subtask-by-id.js @@ -42,8 +42,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { logWrapper.error(errorMessage); return { success: false, - error: { code: 'MISSING_ARGUMENT', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_ARGUMENT', message: errorMessage } }; } @@ -54,8 +53,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { logWrapper.error(errorMessage); return { success: false, - error: { code: 'INVALID_SUBTASK_ID', message: errorMessage }, - fromCache: false + error: { code: 'INVALID_SUBTASK_ID', message: errorMessage } }; } @@ -65,8 +63,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { logWrapper.error(errorMessage); return { success: false, - error: { code: 'MISSING_PROMPT', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_PROMPT', message: errorMessage } }; } @@ -77,8 +74,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { log.error(errorMessage); return { success: false, - error: { code: 'INVALID_SUBTASK_ID_TYPE', message: errorMessage }, - fromCache: false + error: { code: 'INVALID_SUBTASK_ID_TYPE', message: errorMessage } }; } @@ -88,8 +84,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { log.error(errorMessage); return { success: false, - error: { code: 'INVALID_SUBTASK_ID_FORMAT', message: errorMessage }, - fromCache: false + error: { code: 'INVALID_SUBTASK_ID_FORMAT', message: errorMessage } }; } @@ -128,8 +123,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { logWrapper.error(message); return { success: false, - error: { code: 'SUBTASK_NOT_FOUND', message: message }, - fromCache: false + error: { code: 'SUBTASK_NOT_FOUND', message: message } }; } @@ -146,8 +140,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { tasksPath, useResearch, telemetryData: coreResult.telemetryData - }, - fromCache: false + } }; } catch (error) { logWrapper.error(`Error updating subtask by ID: ${error.message}`); @@ -156,8 +149,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { error: { code: 'UPDATE_SUBTASK_CORE_ERROR', message: error.message || 'Unknown error updating subtask' - }, - fromCache: false + } }; } finally { if (!wasSilent && isSilentMode()) { @@ -174,8 +166,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) { error: { code: 'DIRECT_FUNCTION_SETUP_ERROR', message: error.message || 'Unknown setup error' - }, - fromCache: false + } }; } } diff --git a/mcp-server/src/core/direct-functions/update-task-by-id.js b/mcp-server/src/core/direct-functions/update-task-by-id.js index 459ff0bc..1d3d8753 100644 --- a/mcp-server/src/core/direct-functions/update-task-by-id.js +++ b/mcp-server/src/core/direct-functions/update-task-by-id.js @@ -42,8 +42,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) { logWrapper.error(errorMessage); return { success: false, - error: { code: 'MISSING_ARGUMENT', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_ARGUMENT', message: errorMessage } }; } @@ -54,8 +53,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) { logWrapper.error(errorMessage); return { success: false, - error: { code: 'MISSING_TASK_ID', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_TASK_ID', message: errorMessage } }; } @@ -65,8 +63,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) { logWrapper.error(errorMessage); return { success: false, - error: { code: 'MISSING_PROMPT', message: errorMessage }, - fromCache: false + error: { code: 'MISSING_PROMPT', message: errorMessage } }; } @@ -84,8 +81,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) { logWrapper.error(errorMessage); return { success: false, - error: { code: 'INVALID_TASK_ID', message: errorMessage }, - fromCache: false + error: { code: 'INVALID_TASK_ID', message: errorMessage } }; } } @@ -137,8 +133,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) { taskId: taskId, updated: false, telemetryData: coreResult?.telemetryData - }, - fromCache: false + } }; } @@ -155,8 +150,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) { updated: true, updatedTask: coreResult.updatedTask, telemetryData: coreResult.telemetryData - }, - fromCache: false + } }; } catch (error) { logWrapper.error(`Error updating task by ID: ${error.message}`); @@ -165,8 +159,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) { error: { code: 'UPDATE_TASK_CORE_ERROR', message: error.message || 'Unknown error updating task' - }, - fromCache: false + } }; } finally { if (!wasSilent && isSilentMode()) { @@ -181,8 +174,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) { error: { code: 'DIRECT_FUNCTION_SETUP_ERROR', message: error.message || 'Unknown setup error' - }, - fromCache: false + } }; } } diff --git a/mcp-server/src/core/utils/path-utils.js b/mcp-server/src/core/utils/path-utils.js index 8126276f..5971a5df 100644 --- a/mcp-server/src/core/utils/path-utils.js +++ b/mcp-server/src/core/utils/path-utils.js @@ -1,5 +1,4 @@ import path from 'path'; -import fs from 'fs'; import { findTasksPath as coreFindTasksPath, findPRDPath as coreFindPrdPath, @@ -13,22 +12,22 @@ import { PROJECT_MARKERS } from '../../../../src/constants/paths.js'; * This module handles session-specific path resolution for the MCP server */ +/** + * Silent logger for MCP context to prevent console output + */ +const silentLogger = { + info: () => {}, + warn: () => {}, + error: () => {}, + debug: () => {}, + success: () => {} +}; + /** * Cache for last found project root to improve performance */ export const lastFoundProjectRoot = null; -/** - * Find tasks.json file with MCP support - * @param {string} [explicitPath] - Explicit path to tasks.json (highest priority) - * @param {Object} [args] - Arguments object for context - * @param {Object} [log] - Logger object to prevent console logging - * @returns {string|null} - Resolved path to tasks.json or null if not found - */ -export function findTasksPathCore(explicitPath, args = null, log = null) { - return coreFindTasksPath(explicitPath, args, log); -} - /** * Find PRD file with MCP support * @param {string} [explicitPath] - Explicit path to PRD file (highest priority) @@ -36,25 +35,10 @@ export function findTasksPathCore(explicitPath, args = null, log = null) { * @param {Object} [log] - Logger object to prevent console logging * @returns {string|null} - Resolved path to PRD file or null if not found */ -export function findPrdPath(explicitPath, args = null, log = null) { +export function findPrdPath(explicitPath, args = null, log = silentLogger) { return coreFindPrdPath(explicitPath, args, log); } -/** - * Find complexity report file with MCP support - * @param {string} [explicitPath] - Explicit path to complexity report (highest priority) - * @param {Object} [args] - Arguments object for context - * @param {Object} [log] - Logger object to prevent console logging - * @returns {string|null} - Resolved path to complexity report or null if not found - */ -export function findComplexityReportPathCore( - explicitPath, - args = null, - log = null -) { - return coreFindComplexityReportPath(explicitPath, args, log); -} - /** * Resolve tasks.json path from arguments * Prioritizes explicit path parameter, then uses fallback logic @@ -62,7 +46,7 @@ export function findComplexityReportPathCore( * @param {Object} [log] - Logger object to prevent console logging * @returns {string|null} - Resolved path to tasks.json or null if not found */ -export function resolveTasksPath(args, log = null) { +export function resolveTasksPath(args, log = silentLogger) { // Get explicit path from args.file if provided const explicitPath = args?.file; const projectRoot = args?.projectRoot; @@ -92,7 +76,7 @@ export function resolveTasksPath(args, log = null) { * @param {Object} [log] - Logger object to prevent console logging * @returns {string|null} - Resolved path to PRD file or null if not found */ -export function resolvePrdPath(args, log = null) { +export function resolvePrdPath(args, log = silentLogger) { // Get explicit path from args.input if provided const explicitPath = args?.input; const projectRoot = args?.projectRoot; @@ -122,7 +106,7 @@ export function resolvePrdPath(args, log = null) { * @param {Object} [log] - Logger object to prevent console logging * @returns {string|null} - Resolved path to complexity report or null if not found */ -export function resolveComplexityReportPath(args, log = null) { +export function resolveComplexityReportPath(args, log = silentLogger) { // Get explicit path from args.complexityReport if provided const explicitPath = args?.complexityReport; const projectRoot = args?.projectRoot; @@ -184,7 +168,7 @@ export function findProjectRoot(startDir) { * @param {Object} [log] - Log function to prevent console logging * @returns {string|null} - Resolved path to tasks.json or null if not found */ -export function findTasksPath(args, log = null) { +export function findTasksPath(args, log = silentLogger) { return resolveTasksPath(args, log); } @@ -194,7 +178,7 @@ export function findTasksPath(args, log = null) { * @param {Object} [log] - Log function to prevent console logging * @returns {string|null} - Resolved path to complexity report or null if not found */ -export function findComplexityReportPath(args, log = null) { +export function findComplexityReportPath(args, log = silentLogger) { return resolveComplexityReportPath(args, log); } @@ -205,7 +189,7 @@ export function findComplexityReportPath(args, log = null) { * @param {Object} [log] - Logger object to prevent console logging * @returns {string|null} - Resolved path to PRD file or null if not found */ -export function findPRDPath(explicitPath, args = null, log = null) { +export function findPRDPath(explicitPath, args = null, log = silentLogger) { return findPrdPath(explicitPath, args, log); } diff --git a/mcp-server/src/tools/complexity-report.js b/mcp-server/src/tools/complexity-report.js index 1e51e7ca..dbc03a86 100644 --- a/mcp-server/src/tools/complexity-report.js +++ b/mcp-server/src/tools/complexity-report.js @@ -51,9 +51,7 @@ export function registerComplexityReportTool(server) { ); if (result.success) { - log.info( - `Successfully retrieved complexity report${result.fromCache ? ' (from cache)' : ''}` - ); + log.info('Successfully retrieved complexity report'); } else { log.error( `Failed to retrieve complexity report: ${result.error.message}` diff --git a/mcp-server/src/tools/get-task.js b/mcp-server/src/tools/get-task.js index 727263b1..6484990b 100644 --- a/mcp-server/src/tools/get-task.js +++ b/mcp-server/src/tools/get-task.js @@ -121,9 +121,7 @@ export function registerShowTaskTool(server) { ); if (result.success) { - log.info( - `Successfully retrieved task details for ID: ${args.id}${result.fromCache ? ' (from cache)' : ''}` - ); + log.info(`Successfully retrieved task details for ID: ${args.id}`); } else { log.error(`Failed to get task: ${result.error.message}`); } diff --git a/mcp-server/src/tools/get-tasks.js b/mcp-server/src/tools/get-tasks.js index bc2fe784..b8618268 100644 --- a/mcp-server/src/tools/get-tasks.js +++ b/mcp-server/src/tools/get-tasks.js @@ -58,7 +58,7 @@ export function registerListTasksTool(server) { // Resolve the path to tasks.json using new path utilities let tasksJsonPath; try { - tasksJsonPath = resolveTasksPath(args, session); + tasksJsonPath = resolveTasksPath(args, log); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( @@ -87,7 +87,7 @@ export function registerListTasksTool(server) { ); log.info( - `Retrieved ${result.success ? result.data?.tasks?.length || 0 : 0} tasks${result.fromCache ? ' (from cache)' : ''}` + `Retrieved ${result.success ? result.data?.tasks?.length || 0 : 0} tasks` ); return handleApiResult(result, log, 'Error getting tasks'); } catch (error) { diff --git a/mcp-server/src/tools/move-task.js b/mcp-server/src/tools/move-task.js index f7b6b4d4..98c9a864 100644 --- a/mcp-server/src/tools/move-task.js +++ b/mcp-server/src/tools/move-task.js @@ -34,7 +34,6 @@ export function registerMoveTaskTool(server) { file: z.string().optional().describe('Custom path to tasks.json file'), projectRoot: z .string() - .optional() .describe( 'Root directory of the project (typically derived from session)' ) @@ -86,7 +85,41 @@ export function registerMoveTaskTool(server) { { session } ); - return handleApiResult(result, log); + if (!result.success) { + log.error( + `Failed to move ${fromId} to ${toId}: ${result.error.message}` + ); + } else { + results.push(result.data); + } + } + + return handleApiResult( + { + success: true, + data: { + moves: results, + message: `Successfully moved ${results.length} tasks` + } + }, + log + ); + } else { + // Moving a single task + return handleApiResult( + await moveTaskDirect( + { + sourceId: args.from, + destinationId: args.to, + tasksJsonPath, + projectRoot: args.projectRoot + }, + log, + { session } + ), + log + ); + } } catch (error) { return createErrorResponse( `Failed to move task: ${error.message}`, diff --git a/mcp-server/src/tools/utils.js b/mcp-server/src/tools/utils.js index b0cb9abf..b8e00914 100644 --- a/mcp-server/src/tools/utils.js +++ b/mcp-server/src/tools/utils.js @@ -7,6 +7,7 @@ import { spawnSync } from 'child_process'; import path from 'path'; import fs from 'fs'; import { contextManager } from '../core/context-manager.js'; // Import the singleton +import { fileURLToPath } from 'url'; // Import path utilities to ensure consistent path resolution import { @@ -14,6 +15,50 @@ import { PROJECT_MARKERS } from '../core/utils/path-utils.js'; +const __filename = fileURLToPath(import.meta.url); + +// Cache for version info to avoid repeated file reads +let cachedVersionInfo = null; + +/** + * Get version information from package.json + * @returns {Object} Version information + */ +function getVersionInfo() { + // Return cached version if available + if (cachedVersionInfo) { + return cachedVersionInfo; + } + + try { + // Navigate to the project root from the tools directory + const packageJsonPath = path.join( + path.dirname(__filename), + '../../../package.json' + ); + if (fs.existsSync(packageJsonPath)) { + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); + cachedVersionInfo = { + version: packageJson.version, + name: packageJson.name + }; + return cachedVersionInfo; + } + cachedVersionInfo = { + version: 'unknown', + name: 'task-master-ai' + }; + return cachedVersionInfo; + } catch (error) { + // Fallback version info if package.json can't be read + cachedVersionInfo = { + version: 'unknown', + name: 'task-master-ai' + }; + return cachedVersionInfo; + } +} + /** * Get normalized project root path * @param {string|undefined} projectRootRaw - Raw project root from arguments @@ -199,17 +244,19 @@ function getProjectRootFromSession(session, log) { * @param {Function} processFunction - Optional function to process successful result data * @returns {Object} - Standardized MCP response object */ -function handleApiResult( +async function handleApiResult( result, log, errorPrefix = 'API error', processFunction = processMCPResponseData ) { + // Get version info for every response + const versionInfo = getVersionInfo(); + if (!result.success) { const errorMsg = result.error?.message || `Unknown ${errorPrefix}`; - // Include cache status in error logs - log.error(`${errorPrefix}: ${errorMsg}. From cache: ${result.fromCache}`); // Keep logging cache status on error - return createErrorResponse(errorMsg); + log.error(`${errorPrefix}: ${errorMsg}`); + return createErrorResponse(errorMsg, versionInfo); } // Process the result data if needed @@ -217,16 +264,14 @@ function handleApiResult( ? processFunction(result.data) : result.data; - // Log success including cache status - log.info(`Successfully completed operation. From cache: ${result.fromCache}`); // Add success log with cache status + log.info('Successfully completed operation'); - // Create the response payload including the fromCache flag + // Create the response payload including version info const responsePayload = { - fromCache: result.fromCache, // Get the flag from the original 'result' - data: processedData // Nest the processed data under a 'data' key + data: processedData, + version: versionInfo }; - // Pass this combined payload to createContentResponse return createContentResponse(responsePayload); } @@ -320,8 +365,8 @@ function executeTaskMasterCommand( * @param {Function} options.actionFn - The async function to execute if the cache misses. * Should return an object like { success: boolean, data?: any, error?: { code: string, message: string } }. * @param {Object} options.log - The logger instance. - * @returns {Promise} - An object containing the result, indicating if it was from cache. - * Format: { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean } + * @returns {Promise} - An object containing the result. + * Format: { success: boolean, data?: any, error?: { code: string, message: string } } */ async function getCachedOrExecute({ cacheKey, actionFn, log }) { // Check cache first @@ -329,11 +374,7 @@ async function getCachedOrExecute({ cacheKey, actionFn, log }) { if (cachedResult !== undefined) { log.info(`Cache hit for key: ${cacheKey}`); - // Return the cached data in the same structure as a fresh result - return { - ...cachedResult, // Spread the cached result to maintain its structure - fromCache: true // Just add the fromCache flag - }; + return cachedResult; } log.info(`Cache miss for key: ${cacheKey}. Executing action function.`); @@ -341,12 +382,10 @@ async function getCachedOrExecute({ cacheKey, actionFn, log }) { // Execute the action function if cache missed const result = await actionFn(); - // If the action was successful, cache the result (but without fromCache flag) + // If the action was successful, cache the result if (result.success && result.data !== undefined) { log.info(`Action successful. Caching result for key: ${cacheKey}`); - // Cache the entire result structure (minus the fromCache flag) - const { fromCache, ...resultToCache } = result; - contextManager.setCachedData(cacheKey, resultToCache); + contextManager.setCachedData(cacheKey, result); } else if (!result.success) { log.warn( `Action failed for cache key ${cacheKey}. Result not cached. Error: ${result.error?.message}` @@ -357,11 +396,7 @@ async function getCachedOrExecute({ cacheKey, actionFn, log }) { ); } - // Return the fresh result, indicating it wasn't from cache - return { - ...result, - fromCache: false - }; + return result; } /** @@ -460,14 +495,22 @@ function createContentResponse(content) { /** * Creates error response for tools * @param {string} errorMessage - Error message to include in response + * @param {Object} [versionInfo] - Optional version information object * @returns {Object} - Error content response object in FastMCP format */ -function createErrorResponse(errorMessage) { +function createErrorResponse(errorMessage, versionInfo) { + // Provide fallback version info if not provided + if (!versionInfo) { + versionInfo = getVersionInfo(); + } + return { content: [ { type: 'text', - text: `Error: ${errorMessage}` + text: `Error: ${errorMessage} +Version: ${versionInfo.version} +Name: ${versionInfo.name}` } ], isError: true diff --git a/scripts/modules/utils.js b/scripts/modules/utils.js index 33a1c724..ab6fe422 100644 --- a/scripts/modules/utils.js +++ b/scripts/modules/utils.js @@ -155,8 +155,17 @@ function log(level, ...args) { return; } - // Get log level dynamically from config-manager - const configLevel = getLogLevel() || 'info'; // Use getter + // GUARD: Prevent circular dependency during config loading + // Use a simple fallback log level instead of calling getLogLevel() + let configLevel = 'info'; // Default fallback + try { + // Only try to get config level if we're not in the middle of config loading + configLevel = getLogLevel() || 'info'; + } catch (error) { + // If getLogLevel() fails (likely due to circular dependency), + // use default 'info' level and continue + configLevel = 'info'; + } // Use text prefixes instead of emojis const prefixes = { @@ -190,8 +199,17 @@ function log(level, ...args) { * @returns {Object|null} Parsed JSON data or null if error occurs */ function readJSON(filepath) { - // Get debug flag dynamically from config-manager - const isDebug = getDebugFlag(); + // GUARD: Prevent circular dependency during config loading + let isDebug = false; // Default fallback + try { + // Only try to get debug flag if we're not in the middle of config loading + isDebug = getDebugFlag(); + } catch (error) { + // If getDebugFlag() fails (likely due to circular dependency), + // use default false and continue + isDebug = false; + } + try { const rawData = fs.readFileSync(filepath, 'utf8'); return JSON.parse(rawData); @@ -212,8 +230,17 @@ function readJSON(filepath) { * @param {Object} data - Data to write */ function writeJSON(filepath, data) { - // Get debug flag dynamically from config-manager - const isDebug = getDebugFlag(); + // GUARD: Prevent circular dependency during config loading + let isDebug = false; // Default fallback + try { + // Only try to get debug flag if we're not in the middle of config loading + isDebug = getDebugFlag(); + } catch (error) { + // If getDebugFlag() fails (likely due to circular dependency), + // use default false and continue + isDebug = false; + } + try { const dir = path.dirname(filepath); if (!fs.existsSync(dir)) { @@ -246,8 +273,17 @@ function sanitizePrompt(prompt) { * @returns {Object|null} The parsed complexity report or null if not found */ function readComplexityReport(customPath = null) { - // Get debug flag dynamically from config-manager - const isDebug = getDebugFlag(); + // GUARD: Prevent circular dependency during config loading + let isDebug = false; // Default fallback + try { + // Only try to get debug flag if we're not in the middle of config loading + isDebug = getDebugFlag(); + } catch (error) { + // If getDebugFlag() fails (likely due to circular dependency), + // use default false and continue + isDebug = false; + } + try { let reportPath; if (customPath) { diff --git a/src/utils/path-utils.js b/src/utils/path-utils.js index b06cd7db..c4f22cb0 100644 --- a/src/utils/path-utils.js +++ b/src/utils/path-utils.js @@ -384,12 +384,6 @@ export function findConfigPath(explicitPath = null, args = null, log = null) { for (const configPath of possiblePaths) { if (fs.existsSync(configPath)) { - try { - logger.info?.(`Found config file at: ${configPath}`); - } catch (error) { - // Silently handle logging errors during testing - } - // Issue deprecation warning for legacy paths if (configPath?.endsWith(LEGACY_CONFIG_FILE)) { logger.warn?.( diff --git a/tests/integration/mcp-server/direct-functions.test.js b/tests/integration/mcp-server/direct-functions.test.js index d7c18822..640df127 100644 --- a/tests/integration/mcp-server/direct-functions.test.js +++ b/tests/integration/mcp-server/direct-functions.test.js @@ -253,8 +253,7 @@ describe('MCP Server Direct Functions', () => { error: { code: 'FILE_NOT_FOUND_ERROR', message: 'Tasks file not found' - }, - fromCache: false + } }; } @@ -288,8 +287,7 @@ describe('MCP Server Direct Functions', () => { .length, pending: tasksData.filter((t) => t.status === 'pending').length } - }, - fromCache: false + } }; } @@ -305,8 +303,7 @@ describe('MCP Server Direct Functions', () => { total: tasksData.length, filtered: filteredTasks.length } - }, - fromCache: false + } }; } @@ -320,8 +317,7 @@ describe('MCP Server Direct Functions', () => { stats: { total: tasksData.length } - }, - fromCache: false + } }; } @@ -441,8 +437,7 @@ describe('MCP Server Direct Functions', () => { error: { code: 'INPUT_VALIDATION_ERROR', message: 'Task ID is required' - }, - fromCache: false + } }; } @@ -454,8 +449,7 @@ describe('MCP Server Direct Functions', () => { error: { code: 'TASK_NOT_FOUND', message: `Task with ID ${args.id} not found` - }, - fromCache: false + } }; } @@ -469,8 +463,7 @@ describe('MCP Server Direct Functions', () => { error: { code: 'TASK_COMPLETED', message: `Task ${args.id} is already marked as done and cannot be expanded` - }, - fromCache: false + } }; } @@ -495,8 +488,7 @@ describe('MCP Server Direct Functions', () => { task: expandedTask, subtasksAdded: expandedTask.subtasks.length, hasExistingSubtasks: false - }, - fromCache: false + } }; }