Fix: MCP log errors (#648)

This commit is contained in:
Ralph Khreish
2025-06-03 01:09:29 +02:00
committed by GitHub
parent ad612763ff
commit 9b4168bb4e
24 changed files with 226 additions and 218 deletions

View File

@@ -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
}
};
}
}

View File

@@ -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

View File

@@ -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<Object>} - Task expansion result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }
* @returns {Promise<Object>} - 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
}
};
}
}

View File

@@ -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
}
};
}
}

View File

@@ -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 };
}
}

View File

@@ -14,7 +14,7 @@ import {
*
* @param {Object} args - Command arguments (now expecting tasksJsonPath explicitly).
* @param {Object} log - Logger object.
* @returns {Promise<Object>} - Task list result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }.
* @returns {Promise<Object>} - 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
}
};
}

View File

@@ -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<Object>} - Next task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }
* @returns {Promise<Object>} - 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
}
};
}
}

View File

@@ -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

View File

@@ -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<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: false }
* @returns {Promise<Object>} - 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
}
};
}
}

View File

@@ -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
}
};
}
}

View File

@@ -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
}
};
}
}

View File

@@ -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
}
};
}
}

View File

@@ -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);
}

View File

@@ -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}`

View File

@@ -116,9 +116,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}`);
}

View File

@@ -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) {

View File

@@ -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)'
)
@@ -95,13 +94,16 @@ export function registerMoveTaskTool(server) {
}
}
return {
success: true,
data: {
moves: results,
message: `Successfully moved ${results.length} tasks`
}
};
return handleApiResult(
{
success: true,
data: {
moves: results,
message: `Successfully moved ${results.length} tasks`
}
},
log
);
} else {
// Moving a single task
return handleApiResult(

View File

@@ -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<Object>} - 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<Object>} - 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