refactor(mcp-server): Prioritize session roots for project path discovery

This commit refactors how the MCP server determines the project root directory, prioritizing the path provided by the client session (e.g., Cursor) for increased reliability and simplification.

Previously, project root discovery relied on a complex chain of fallbacks (environment variables, CWD searching, package path checks) within `findTasksJsonPath`. This could be brittle and less accurate when running within an integrated environment like Cursor.

Key changes:

- **Prioritize Session Roots:** MCP tools (`add-task`, `add-dependency`, etc.) now first attempt to extract the project root URI directly from `session.roots[0].uri`.

- **New Utility `getProjectRootFromSession`:** Added a utility function in `mcp-server/src/tools/utils.js` to encapsulate the logic for extracting and decoding the root URI from the session object.

- **Refactor MCP Tools:** Updated tools (`add-task.js`, `add-dependency.js`) to use `getProjectRootFromSession`.

- **Simplify `findTasksJsonPath`:** Prioritized `args.projectRoot`, removed checks for `TASK_MASTER_PROJECT_ROOT` env var and package directory fallback. Retained CWD search and cache check for CLI compatibility.

- **Fix `reportProgress` Usage:** Corrected parameters in `add-dependency.js`.

This change makes project root determination more robust for the MCP server while preserving discovery mechanisms for the standalone CLI.
This commit is contained in:
Eyal Toledano
2025-04-02 12:33:46 -04:00
parent 3af469b35f
commit e04c16cec6
7 changed files with 1621 additions and 61 deletions

View File

@@ -6,7 +6,8 @@
import { z } from "zod";
import {
handleApiResult,
createErrorResponse
createErrorResponse,
getProjectRootFromSession
} from "./utils.js";
import { addDependencyDirect } from "../core/task-master-core.js";
@@ -24,12 +25,27 @@ export function registerAddDependencyTool(server) {
file: z.string().optional().describe("Path to the tasks file (default: tasks/tasks.json)"),
projectRoot: z.string().optional().describe("Root directory of the project (default: current working directory)")
}),
execute: async (args, { log }) => {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Adding dependency for task ${args.id} to depend on ${args.dependsOn} with args: ${JSON.stringify(args)}`);
log.info(`Adding dependency for task ${args.id} to depend on ${args.dependsOn}`);
reportProgress({ progress: 0 });
// Call the direct function wrapper
const result = await addDependencyDirect(args, log);
// Get project root using the utility function
let rootFolder = getProjectRootFromSession(session, log);
// Fallback to args.projectRoot if session didn't provide one
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
// Call the direct function with the resolved rootFolder
const result = await addDependencyDirect({
projectRoot: rootFolder,
...args
}, log);
reportProgress({ progress: 100 });
// Log result
if (result.success) {

View File

@@ -6,7 +6,8 @@
import { z } from "zod";
import {
handleApiResult,
createErrorResponse
createErrorResponse,
getProjectRootFromSession
} from "./utils.js";
import { addTaskDirect } from "../core/task-master-core.js";
@@ -25,19 +26,26 @@ export function registerAddTaskTool(server) {
file: z.string().optional().describe("Path to the tasks file"),
projectRoot: z.string().optional().describe("Root directory of the project (default: current working directory)")
}),
execute: async ({ prompt, dependencies, priority, file, projectRoot }, log) => {
execute: async (args, { log, reportProgress, session }) => {
try {
log.info(`MCP add_task called with prompt: "${prompt}"`);
log.info(`MCP add_task called with prompt: "${args.prompt}"`);
// Get project root using the utility function
let rootFolder = getProjectRootFromSession(session, log);
// Fallback to args.projectRoot if session didn't provide one
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
// Call the direct function with the resolved rootFolder
const result = await addTaskDirect({
prompt,
dependencies,
priority,
file,
projectRoot
projectRoot: rootFolder, // Pass the resolved root
...args
}, log);
return handleApiResult(result);
return handleApiResult(result, log);
} catch (error) {
log.error(`Error in add_task MCP tool: ${error.message}`);
return createErrorResponse(error.message, "ADD_TASK_ERROR");

View File

@@ -7,6 +7,7 @@ import { spawnSync } from "child_process";
import path from "path";
import { contextManager } from '../core/context-manager.js'; // Import the singleton
import fs from 'fs';
import { decodeURIComponent } from 'querystring'; // Added for URI decoding
// Import path utilities to ensure consistent path resolution
import { lastFoundProjectRoot, getPackagePath, PROJECT_MARKERS } from '../core/utils/path-utils.js';
@@ -67,6 +68,36 @@ export function getProjectRoot(projectRootRaw, log) {
return currentDir;
}
/**
* Extracts the project root path from the FastMCP session object.
* @param {Object} session - The FastMCP session object.
* @param {Object} log - Logger object.
* @returns {string|null} - The absolute path to the project root, or null if not found.
*/
export function getProjectRootFromSession(session, log) {
if (session && session.roots && session.roots.length > 0) {
const firstRoot = session.roots[0];
if (firstRoot && firstRoot.uri) {
try {
const rootUri = firstRoot.uri;
const rootPath = rootUri.startsWith('file://')
? decodeURIComponent(rootUri.slice(7)) // Remove 'file://' and decode
: rootUri; // Assume it's a path if no scheme
log.info(`Extracted project root from session: ${rootPath}`);
return rootPath;
} catch (e) {
log.error(`Error decoding project root URI from session: ${firstRoot.uri}`, e);
return null;
}
} else {
log.info('Session exists, but first root or its URI is missing.');
}
} else {
log.info('No session or session roots found to extract project root.');
}
return null;
}
/**
* Handle API result with standardized error handling and response formatting
* @param {Object} result - Result object from API call with success, data, and error properties
@@ -317,3 +348,13 @@ export function createErrorResponse(errorMessage) {
isError: true
};
}
// Ensure all functions are exported
export {
handleApiResult,
executeTaskMasterCommand,
getCachedOrExecute,
processMCPResponseData,
createContentResponse,
createErrorResponse
};