fix(mcp, expand): pass projectRoot through expand/expand-all flows

Problem: expand_task & expand_all MCP tools failed with .env keys due to missing projectRoot propagation for API key resolution. Also fixed a ReferenceError: wasSilent is not defined in expandTaskDirect.

Solution: Modified core logic, direct functions, and MCP tools for expand-task and expand-all to correctly destructure projectRoot from arguments and pass it down through the context object to the AI service call (generateTextService). Fixed wasSilent scope in expandTaskDirect.

Verification: Tested expand_task successfully in MCP using .env keys. Reviewed expand_all flow for correct projectRoot propagation.
This commit is contained in:
Eyal Toledano
2025-05-01 22:37:33 -04:00
parent 303b13e3d4
commit f5585e6c31
5 changed files with 42 additions and 39 deletions

View File

@@ -1,8 +1,8 @@
{ {
"models": { "models": {
"main": { "main": {
"provider": "openai", "provider": "anthropic",
"modelId": "gpt-4o", "modelId": "claude-3-7-sonnet-20250219",
"maxTokens": 100000, "maxTokens": 100000,
"temperature": 0.2 "temperature": 0.2
}, },

View File

@@ -17,14 +17,15 @@ import { createLogWrapper } from '../../tools/utils.js';
* @param {boolean} [args.research] - Enable research-backed subtask generation * @param {boolean} [args.research] - Enable research-backed subtask generation
* @param {string} [args.prompt] - Additional context to guide subtask generation * @param {string} [args.prompt] - Additional context to guide subtask generation
* @param {boolean} [args.force] - Force regeneration of subtasks for tasks that already have them * @param {boolean} [args.force] - Force regeneration of subtasks for tasks that already have them
* @param {string} [args.projectRoot] - Project root path.
* @param {Object} log - Logger object from FastMCP * @param {Object} log - Logger object from FastMCP
* @param {Object} context - Context object containing session * @param {Object} context - Context object containing session
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>} * @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/ */
export async function expandAllTasksDirect(args, log, context = {}) { export async function expandAllTasksDirect(args, log, context = {}) {
const { session } = context; // Extract session const { session } = context; // Extract session
// Destructure expected args // Destructure expected args, including projectRoot
const { tasksJsonPath, num, research, prompt, force } = args; const { tasksJsonPath, num, research, prompt, force, projectRoot } = args;
// Create logger wrapper using the utility // Create logger wrapper using the utility
const mcpLog = createLogWrapper(log); const mcpLog = createLogWrapper(log);
@@ -43,7 +44,7 @@ export async function expandAllTasksDirect(args, log, context = {}) {
enableSilentMode(); // Enable silent mode for the core function call enableSilentMode(); // Enable silent mode for the core function call
try { try {
log.info( log.info(
`Calling core expandAllTasks with args: ${JSON.stringify({ num, research, prompt, force })}` `Calling core expandAllTasks with args: ${JSON.stringify({ num, research, prompt, force, projectRoot })}`
); );
// Parse parameters (ensure correct types) // Parse parameters (ensure correct types)
@@ -52,14 +53,14 @@ export async function expandAllTasksDirect(args, log, context = {}) {
const additionalContext = prompt || ''; const additionalContext = prompt || '';
const forceFlag = force === true; const forceFlag = force === true;
// Call the core function, passing options and the context object { session, mcpLog } // Call the core function, passing options and the context object { session, mcpLog, projectRoot }
const result = await expandAllTasks( const result = await expandAllTasks(
tasksJsonPath, tasksJsonPath,
numSubtasks, numSubtasks,
useResearch, useResearch,
additionalContext, additionalContext,
forceFlag, forceFlag,
{ session, mcpLog } { session, mcpLog, projectRoot }
); );
// Core function now returns a summary object // Core function now returns a summary object

View File

@@ -25,6 +25,7 @@ import { createLogWrapper } from '../../tools/utils.js';
* @param {boolean} [args.research] - Enable research role for subtask generation. * @param {boolean} [args.research] - Enable research role for subtask generation.
* @param {string} [args.prompt] - Additional context to guide subtask generation. * @param {string} [args.prompt] - Additional context to guide subtask generation.
* @param {boolean} [args.force] - Force expansion even if subtasks exist. * @param {boolean} [args.force] - Force expansion even if subtasks exist.
* @param {string} [args.projectRoot] - Project root directory.
* @param {Object} log - Logger object * @param {Object} log - Logger object
* @param {Object} context - Context object containing session * @param {Object} context - Context object containing session
* @param {Object} [context.session] - MCP Session object * @param {Object} [context.session] - MCP Session object
@@ -32,8 +33,8 @@ import { createLogWrapper } from '../../tools/utils.js';
*/ */
export async function expandTaskDirect(args, log, context = {}) { export async function expandTaskDirect(args, log, context = {}) {
const { session } = context; // Extract session const { session } = context; // Extract session
// Destructure expected args // Destructure expected args, including projectRoot
const { tasksJsonPath, id, num, research, prompt, force } = args; const { tasksJsonPath, id, num, research, prompt, force, projectRoot } = args;
// Log session root data for debugging // Log session root data for debugging
log.info( log.info(
@@ -184,20 +185,22 @@ export async function expandTaskDirect(args, log, context = {}) {
// Create logger wrapper using the utility // Create logger wrapper using the utility
const mcpLog = createLogWrapper(log); const mcpLog = createLogWrapper(log);
let wasSilent; // Declare wasSilent outside the try block
// Process the request // Process the request
try { try {
// Enable silent mode to prevent console logs from interfering with JSON response // Enable silent mode to prevent console logs from interfering with JSON response
const wasSilent = isSilentMode(); wasSilent = isSilentMode(); // Assign inside the try block
if (!wasSilent) enableSilentMode(); if (!wasSilent) enableSilentMode();
// Call the core expandTask function with the wrapped logger // Call the core expandTask function with the wrapped logger and projectRoot
const result = await expandTask( const updatedTaskResult = await expandTask(
tasksPath, tasksPath,
taskId, taskId,
numSubtasks, numSubtasks,
useResearch, useResearch,
additionalContext, additionalContext,
{ mcpLog, session } { mcpLog, session, projectRoot },
forceFlag
); );
// Restore normal logging // Restore normal logging

View File

@@ -4,13 +4,10 @@
*/ */
import { z } from 'zod'; import { z } from 'zod';
import { import { handleApiResult, createErrorResponse } from './utils.js';
handleApiResult,
createErrorResponse,
getProjectRootFromSession
} from './utils.js';
import { expandTaskDirect } from '../core/task-master-core.js'; import { expandTaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js'; import { findTasksJsonPath } from '../core/utils/path-utils.js';
import path from 'path';
/** /**
* Register the expand-task tool with the MCP server * Register the expand-task tool with the MCP server
@@ -51,19 +48,16 @@ export function registerExpandTaskTool(server) {
try { try {
log.info(`Starting expand-task with args: ${JSON.stringify(args)}`); log.info(`Starting expand-task with args: ${JSON.stringify(args)}`);
// Get project root from args or session const rootFolder = args.projectRoot;
const rootFolder = if (!rootFolder || !path.isAbsolute(rootFolder)) {
args.projectRoot || getProjectRootFromSession(session, log); log.error(
`expand-task: projectRoot is required and must be absolute.`
// Ensure project root was determined );
if (!rootFolder) {
return createErrorResponse( return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.' 'projectRoot is required and must be absolute.'
); );
} }
log.info(`Project root resolved to: ${rootFolder}`);
// Resolve the path to tasks.json using the utility // Resolve the path to tasks.json using the utility
let tasksJsonPath; let tasksJsonPath;
try { try {
@@ -71,35 +65,39 @@ export function registerExpandTaskTool(server) {
{ projectRoot: rootFolder, file: args.file }, { projectRoot: rootFolder, file: args.file },
log log
); );
log.info(`expand-task: Resolved tasks path: ${tasksJsonPath}`);
} catch (error) { } catch (error) {
log.error(`Error finding tasks.json: ${error.message}`); log.error(`expand-task: Error finding tasks.json: ${error.message}`);
return createErrorResponse( return createErrorResponse(
`Failed to find tasks.json: ${error.message}` `Failed to find tasks.json within project root '${rootFolder}': ${error.message}`
); );
} }
// Call direct function with only session in the context, not reportProgress
// Use the pattern recommended in the MCP guidelines
const result = await expandTaskDirect( const result = await expandTaskDirect(
{ {
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath, tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id, id: args.id,
num: args.num, num: args.num,
research: args.research, research: args.research,
prompt: args.prompt, prompt: args.prompt,
force: args.force // Need to add force to parameters force: args.force,
projectRoot: rootFolder
}, },
log, log,
{ session } { session }
); // Only pass session, NOT reportProgress );
// Return the result log.info(
`expand-task: Direct function result: success=${result.success}`
);
return handleApiResult(result, log, 'Error expanding task'); return handleApiResult(result, log, 'Error expanding task');
} catch (error) { } catch (error) {
log.error(`Error in expand task tool: ${error.message}`); log.error(
return createErrorResponse(error.message); `Critical error in ${toolName} tool execute: ${error.message}`
);
return createErrorResponse(
`Internal tool error (${toolName}): ${error.message}`
);
} }
} }
}); });

View File

@@ -503,7 +503,8 @@ async function expandTask(
prompt: promptContent, prompt: promptContent,
systemPrompt: systemPrompt, // Use the determined system prompt systemPrompt: systemPrompt, // Use the determined system prompt
role, role,
session session,
projectRoot
}); });
logger.info( logger.info(
'Successfully received text response from AI service', 'Successfully received text response from AI service',