Refactor: Improve MCP logging, update E2E & tests

Refactors MCP server logging and updates testing infrastructure.

- MCP Server:

  - Replaced manual logger wrappers with centralized `createLogWrapper` utility.

  - Updated direct function calls to use `{ session, mcpLog }` context.

  - Removed deprecated `model` parameter from analyze, expand-all, expand-task tools.

  - Adjusted MCP tool import paths and parameter descriptions.

- Documentation:

  - Modified `docs/configuration.md`.

  - Modified `docs/tutorial.md`.

- Testing:

  - E2E Script (`run_e2e.sh`):

    - Removed `set -e`.

    - Added LLM analysis function (`analyze_log_with_llm`) & integration.

    - Adjusted test run directory creation timing.

    - Added debug echo statements.

  - Deleted Unit Tests: Removed `ai-client-factory.test.js`, `ai-client-utils.test.js`, `ai-services.test.js`.

  - Modified Fixtures: Updated `scripts/task-complexity-report.json`.

- Dev Scripts:

  - Modified `scripts/dev.js`.
This commit is contained in:
Eyal Toledano
2025-04-28 14:38:01 -04:00
parent 5f504fafb8
commit 4cf7e8a74a
37 changed files with 687 additions and 1736 deletions

View File

@@ -8,6 +8,7 @@ import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/utils.js';
/**
* Direct function wrapper for adding a new task with error handling.
@@ -31,19 +32,13 @@ export async function addTaskDirect(args, log, context = {}) {
const { tasksJsonPath, prompt, dependencies, priority, research } = args;
const { session } = context; // Destructure session from context
// Define the logger wrapper to ensure compatibility with core report function
const logWrapper = {
info: (message, ...args) => log.info(message, ...args),
warn: (message, ...args) => log.warn(message, ...args),
error: (message, ...args) => log.error(message, ...args),
debug: (message, ...args) => log.debug && log.debug(message, ...args), // Handle optional debug
success: (message, ...args) => log.info(message, ...args) // Map success to info if needed
};
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Create logger wrapper using the utility
const mcpLog = createLogWrapper(log);
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('addTaskDirect called without tasksJsonPath');
@@ -112,8 +107,8 @@ export async function addTaskDirect(args, log, context = {}) {
taskDependencies,
taskPriority,
{
mcpLog: logWrapper,
session
session,
mcpLog
},
'json', // outputFormat
manualTaskData, // Pass the manual task data
@@ -132,8 +127,8 @@ export async function addTaskDirect(args, log, context = {}) {
taskDependencies,
taskPriority,
{
mcpLog: logWrapper,
session
session,
mcpLog
},
'json', // outputFormat
null, // manualTaskData is null for AI creation

View File

@@ -9,13 +9,13 @@ import {
isSilentMode
} from '../../../../scripts/modules/utils.js';
import fs from 'fs';
import { createLogWrapper } from '../../tools/utils.js'; // Import the new utility
/**
* Analyze task complexity and generate recommendations
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.outputPath - Explicit absolute path to save the report.
* @param {string} [args.model] - Deprecated: LLM model to use for analysis (ignored)
* @param {string|number} [args.threshold] - Minimum complexity score to recommend expansion (1-10)
* @param {boolean} [args.research] - Use Perplexity AI for research-backed complexity analysis
* @param {Object} log - Logger object
@@ -76,14 +76,8 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
enableSilentMode();
}
const logWrapper = {
info: (message, ...args) => log.info(message, ...args),
warn: (message, ...args) => log.warn(message, ...args),
error: (message, ...args) => log.error(message, ...args),
debug: (message, ...args) => log.debug && log.debug(message, ...args),
success: (message, ...args) => log.info(message, ...args) // Map success to info
};
// --- End Silent Mode and Logger Wrapper ---
// Create logger wrapper using the utility
const mcpLog = createLogWrapper(log);
let report; // To store the result from the core function
@@ -92,7 +86,7 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
// Call the core function, passing options and the context object { session, mcpLog }
report = await analyzeTaskComplexity(options, {
session, // Pass the session object
mcpLog: logWrapper // Pass the logger wrapper
mcpLog // Pass the logger wrapper
});
// --- End Core Function Call ---
} catch (error) {

View File

@@ -5,9 +5,9 @@
import { expandAllTasks } from '../../../../scripts/modules/task-manager.js';
import {
enableSilentMode,
disableSilentMode,
isSilentMode
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/utils.js';
/**
* Expand all pending tasks with subtasks (Direct Function Wrapper)
@@ -26,14 +26,8 @@ export async function expandAllTasksDirect(args, log, context = {}) {
// Destructure expected args
const { tasksJsonPath, num, research, prompt, force } = args;
// Create the standard logger wrapper
const logWrapper = {
info: (message, ...args) => log.info(message, ...args),
warn: (message, ...args) => log.warn(message, ...args),
error: (message, ...args) => log.error(message, ...args),
debug: (message, ...args) => log.debug && log.debug(message, ...args), // Handle optional debug
success: (message, ...args) => log.info(message, ...args) // Map success to info if needed
};
// Create logger wrapper using the utility
const mcpLog = createLogWrapper(log);
if (!tasksJsonPath) {
log.error('expandAllTasksDirect called without tasksJsonPath');
@@ -58,15 +52,14 @@ export async function expandAllTasksDirect(args, log, context = {}) {
const additionalContext = prompt || '';
const forceFlag = force === true;
// Call the core function, passing the logger wrapper and session
// Call the core function, passing options and the context object { session, mcpLog }
const result = await expandAllTasks(
tasksJsonPath, // Use the provided path
tasksJsonPath,
numSubtasks,
useResearch,
additionalContext,
forceFlag,
{ mcpLog: logWrapper, session }, // Pass the wrapper and session
'json' // Explicitly request JSON output format
{ session, mcpLog }
);
// Core function now returns a summary object

View File

@@ -3,7 +3,7 @@
* Direct function implementation for expanding a task into subtasks
*/
import expandTask from '../../../../scripts/modules/task-manager/expand-task.js'; // Correct import path
import expandTask from '../../../../scripts/modules/task-manager/expand-task.js';
import {
readJSON,
writeJSON,
@@ -13,6 +13,7 @@ import {
} from '../../../../scripts/modules/utils.js';
import path from 'path';
import fs from 'fs';
import { createLogWrapper } from '../../tools/utils.js';
/**
* Direct function wrapper for expanding a task into subtasks with error handling.
@@ -180,28 +181,23 @@ export async function expandTaskDirect(args, log, context = {}) {
// Save tasks.json with potentially empty subtasks array
writeJSON(tasksPath, data);
// Create logger wrapper using the utility
const mcpLog = createLogWrapper(log);
// Process the request
try {
// Enable silent mode to prevent console logs from interfering with JSON response
const wasSilent = isSilentMode();
if (!wasSilent) enableSilentMode();
const logWrapper = {
info: (message, ...args) => log.info(message, ...args),
warn: (message, ...args) => log.warn(message, ...args),
error: (message, ...args) => log.error(message, ...args),
debug: (message, ...args) => log.debug && log.debug(message, ...args),
success: (message, ...args) => log.info(message, ...args)
};
// Call expandTask with session context to ensure AI client is properly initialized
// Call the core expandTask function with the wrapped logger
const result = await expandTask(
tasksPath,
taskId,
numSubtasks,
useResearch,
additionalContext,
{ session: session, mcpLog: logWrapper }
{ mcpLog, session }
);
// Restore normal logging

View File

@@ -12,6 +12,7 @@ import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/utils.js';
/**
* Get or update model configuration
@@ -25,14 +26,7 @@ export async function modelsDirect(args, log, context = {}) {
const { projectRoot } = args; // Extract projectRoot from args
// Create a logger wrapper that the core functions can use
const logWrapper = {
info: (message, ...args) => log.info(message, ...args),
warn: (message, ...args) => log.warn(message, ...args),
error: (message, ...args) => log.error(message, ...args),
debug: (message, ...args) =>
log.debug ? log.debug(message, ...args) : null,
success: (message, ...args) => log.info(message, ...args)
};
const mcpLog = createLogWrapper(log);
log.info(`Executing models_direct with args: ${JSON.stringify(args)}`);
log.info(`Using project root: ${projectRoot}`);
@@ -59,7 +53,7 @@ export async function modelsDirect(args, log, context = {}) {
if (args.listAvailableModels === true) {
return await getAvailableModelsList({
session,
mcpLog: logWrapper,
mcpLog,
projectRoot // Pass projectRoot to function
});
}
@@ -68,7 +62,7 @@ export async function modelsDirect(args, log, context = {}) {
if (args.setMain) {
return await setModel('main', args.setMain, {
session,
mcpLog: logWrapper,
mcpLog,
projectRoot, // Pass projectRoot to function
providerHint: args.openrouter
? 'openrouter'
@@ -81,7 +75,7 @@ export async function modelsDirect(args, log, context = {}) {
if (args.setResearch) {
return await setModel('research', args.setResearch, {
session,
mcpLog: logWrapper,
mcpLog,
projectRoot, // Pass projectRoot to function
providerHint: args.openrouter
? 'openrouter'
@@ -94,7 +88,7 @@ export async function modelsDirect(args, log, context = {}) {
if (args.setFallback) {
return await setModel('fallback', args.setFallback, {
session,
mcpLog: logWrapper,
mcpLog,
projectRoot, // Pass projectRoot to function
providerHint: args.openrouter
? 'openrouter'
@@ -107,7 +101,7 @@ export async function modelsDirect(args, log, context = {}) {
// Default action: get current configuration
return await getModelConfiguration({
session,
mcpLog: logWrapper,
mcpLog,
projectRoot // Pass projectRoot to function
});
} finally {

View File

@@ -10,6 +10,7 @@ import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/utils.js';
/**
* Direct function wrapper for parsing PRD documents and generating tasks.
@@ -104,23 +105,20 @@ export async function parsePRDDirect(args, log, context = {}) {
`Preparing to parse PRD from ${inputPath} and output to ${outputPath} with ${numTasks} tasks`
);
// Create the logger wrapper for proper logging in the core function
const logWrapper = {
info: (message, ...args) => log.info(message, ...args),
warn: (message, ...args) => log.warn(message, ...args),
error: (message, ...args) => log.error(message, ...args),
debug: (message, ...args) => log.debug && log.debug(message, ...args),
success: (message, ...args) => log.info(message, ...args) // Map success to info
// --- Logger Wrapper ---
const mcpLog = createLogWrapper(log);
// Prepare options for the core function
const options = {
mcpLog,
session
};
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
try {
// Execute core parsePRD function - It now handles AI internally
const tasksDataResult = await parsePRD(inputPath, outputPath, numTasks, {
mcpLog: logWrapper,
session
});
const tasksDataResult = await parsePRD(inputPath, numTasks, options);
// Check the result from the core function (assuming it might return data or null/undefined)
if (!tasksDataResult || !tasksDataResult.tasks) {

View File

@@ -8,6 +8,7 @@ import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/utils.js';
/**
* Direct function wrapper for updateSubtaskById with error handling.
@@ -95,15 +96,8 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Create a logger wrapper object to handle logging without breaking the mcpLog[level] calls
// This ensures outputFormat is set to 'json' while still supporting proper logging
const logWrapper = {
info: (message) => log.info(message),
warn: (message) => log.warn(message),
error: (message) => log.error(message),
debug: (message) => log.debug && log.debug(message),
success: (message) => log.info(message) // Map success to info if needed
};
// Create the logger wrapper using the utility function
const mcpLog = createLogWrapper(log);
// Execute core updateSubtaskById function
// Pass both session and logWrapper as mcpLog to ensure outputFormat is 'json'
@@ -114,7 +108,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
useResearch,
{
session,
mcpLog: logWrapper
mcpLog
}
);

View File

@@ -8,6 +8,7 @@ import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/utils.js';
/**
* Direct function wrapper for updateTaskById with error handling.
@@ -96,14 +97,8 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Create a logger wrapper that matches what updateTaskById expects
const logWrapper = {
info: (message) => log.info(message),
warn: (message) => log.warn(message),
error: (message) => log.error(message),
debug: (message) => log.debug && log.debug(message),
success: (message) => log.info(message) // Map success to info since many loggers don't have success
};
// Create the logger wrapper using the utility function
const mcpLog = createLogWrapper(log);
// Execute core updateTaskById function with proper parameters
await updateTaskById(
@@ -112,7 +107,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
prompt,
useResearch,
{
mcpLog: logWrapper, // Use our wrapper object that has the expected method structure
mcpLog, // Pass the wrapped logger
session
},
'json'

View File

@@ -8,6 +8,7 @@ import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/utils.js';
/**
* Direct function wrapper for updating tasks based on new context/prompt.
@@ -88,6 +89,9 @@ export async function updateTasksDirect(args, log, context = {}) {
enableSilentMode(); // Enable silent mode
try {
// Create logger wrapper using the utility
const mcpLog = createLogWrapper(log);
// Execute core updateTasks function, passing session context
await updateTasks(
tasksJsonPath,
@@ -95,7 +99,7 @@ export async function updateTasksDirect(args, log, context = {}) {
prompt,
useResearch,
// Pass context with logger wrapper and session
{ mcpLog: logWrapper, session },
{ mcpLog, session },
'json' // Explicitly request JSON format for MCP
);

View File

@@ -26,12 +26,6 @@ export function registerAnalyzeTool(server) {
.describe(
'Output file path relative to project root (default: scripts/task-complexity-report.json)'
),
model: z
.string()
.optional()
.describe(
'Deprecated: LLM model override (model is determined by configured role)'
),
threshold: z.coerce
.number()
.min(1)
@@ -44,7 +38,7 @@ export function registerAnalyzeTool(server) {
.string()
.optional()
.describe(
'Path to the tasks file relative to project root (default: tasks/tasks.json)'
'Absolute path to the tasks file in the /tasks folder inside the project root (default: tasks/tasks.json)'
),
research: z
.boolean()

View File

@@ -50,7 +50,7 @@ export function registerExpandAllTool(server) {
.string()
.optional()
.describe(
'Relative path to the tasks file from project root (default: tasks/tasks.json)'
'Absolute path to the tasks file in the /tasks folder inside the project root (default: tasks/tasks.json)'
),
projectRoot: z
.string()

View File

@@ -9,7 +9,7 @@ import {
createErrorResponse,
getProjectRootFromSession
} from './utils.js';
import { expandTaskDirect } from '../core/direct-functions/expand-task.js';
import { expandTaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**

View File

@@ -443,7 +443,7 @@ function createContentResponse(content) {
* @param {string} errorMessage - Error message to include in response
* @returns {Object} - Error content response object in FastMCP format
*/
export function createErrorResponse(errorMessage) {
function createErrorResponse(errorMessage) {
return {
content: [
{
@@ -455,6 +455,25 @@ export function createErrorResponse(errorMessage) {
};
}
/**
* Creates a logger wrapper object compatible with core function expectations.
* Adapts the MCP logger to the { info, warn, error, debug, success } structure.
* @param {Object} log - The MCP logger instance.
* @returns {Object} - The logger wrapper object.
*/
function createLogWrapper(log) {
return {
info: (message, ...args) => log.info(message, ...args),
warn: (message, ...args) => log.warn(message, ...args),
error: (message, ...args) => log.error(message, ...args),
// Handle optional debug method
debug: (message, ...args) =>
log.debug ? log.debug(message, ...args) : null,
// Map success to info as a common fallback
success: (message, ...args) => log.info(message, ...args)
};
}
// Ensure all functions are exported
export {
getProjectRoot,
@@ -463,5 +482,7 @@ export {
executeTaskMasterCommand,
getCachedOrExecute,
processMCPResponseData,
createContentResponse
createContentResponse,
createErrorResponse,
createLogWrapper
};