Merge branch 'next' of https://github.com/eyaltoledano/claude-task-master into joedanz/flexible-brand-rules
# Conflicts: # .cursor/rules/dev_workflow.mdc # mcp-server/src/tools/index.js # scripts/init.js
This commit is contained in:
198
mcp-server/src/core/direct-functions/add-tag.js
Normal file
198
mcp-server/src/core/direct-functions/add-tag.js
Normal file
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* add-tag.js
|
||||
* Direct function implementation for creating a new tag
|
||||
*/
|
||||
|
||||
import {
|
||||
createTag,
|
||||
createTagFromBranch
|
||||
} from '../../../../scripts/modules/task-manager/tag-management.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for creating a new tag with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {string} args.name - Name of the new tag to create
|
||||
* @param {boolean} [args.copyFromCurrent=false] - Whether to copy tasks from current tag
|
||||
* @param {string} [args.copyFromTag] - Specific tag to copy tasks from
|
||||
* @param {boolean} [args.fromBranch=false] - Create tag name from current git branch
|
||||
* @param {string} [args.description] - Optional description for the tag
|
||||
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
|
||||
* @param {string} [args.projectRoot] - Project root path
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function addTagDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const {
|
||||
tasksJsonPath,
|
||||
name,
|
||||
copyFromCurrent = false,
|
||||
copyFromTag,
|
||||
fromBranch = false,
|
||||
description,
|
||||
projectRoot
|
||||
} = args;
|
||||
const { session } = context;
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('addTagDirect called without tasksJsonPath');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Handle --from-branch option
|
||||
if (fromBranch) {
|
||||
log.info('Creating tag from current git branch');
|
||||
|
||||
// Import git utilities
|
||||
const gitUtils = await import(
|
||||
'../../../../scripts/modules/utils/git-utils.js'
|
||||
);
|
||||
|
||||
// Check if we're in a git repository
|
||||
if (!(await gitUtils.isGitRepository(projectRoot))) {
|
||||
log.error('Not in a git repository');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'NOT_GIT_REPO',
|
||||
message: 'Not in a git repository. Cannot use fromBranch option.'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Get current git branch
|
||||
const currentBranch = await gitUtils.getCurrentBranch(projectRoot);
|
||||
if (!currentBranch) {
|
||||
log.error('Could not determine current git branch');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'NO_CURRENT_BRANCH',
|
||||
message: 'Could not determine current git branch.'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Prepare options for branch-based tag creation
|
||||
const branchOptions = {
|
||||
copyFromCurrent,
|
||||
copyFromTag,
|
||||
description:
|
||||
description || `Tag created from git branch "${currentBranch}"`
|
||||
};
|
||||
|
||||
// Call the createTagFromBranch function
|
||||
const result = await createTagFromBranch(
|
||||
tasksJsonPath,
|
||||
currentBranch,
|
||||
branchOptions,
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot
|
||||
},
|
||||
'json' // outputFormat - use 'json' to suppress CLI UI
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
branchName: result.branchName,
|
||||
tagName: result.tagName,
|
||||
created: result.created,
|
||||
mappingUpdated: result.mappingUpdated,
|
||||
message: `Successfully created tag "${result.tagName}" from git branch "${result.branchName}"`
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// Check required parameters for regular tag creation
|
||||
if (!name || typeof name !== 'string') {
|
||||
log.error('Missing required parameter: name');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_PARAMETER',
|
||||
message: 'Tag name is required and must be a string'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`Creating new tag: ${name}`);
|
||||
|
||||
// Prepare options
|
||||
const options = {
|
||||
copyFromCurrent,
|
||||
copyFromTag,
|
||||
description
|
||||
};
|
||||
|
||||
// Call the createTag function
|
||||
const result = await createTag(
|
||||
tasksJsonPath,
|
||||
name,
|
||||
options,
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot
|
||||
},
|
||||
'json' // outputFormat - use 'json' to suppress CLI UI
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
tagName: result.tagName,
|
||||
created: result.created,
|
||||
tasksCopied: result.tasksCopied,
|
||||
sourceTag: result.sourceTag,
|
||||
description: result.description,
|
||||
message: `Successfully created tag "${result.tagName}"`
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in addTagDirect: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'ADD_TAG_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,7 @@ export async function addTaskDirect(args, log, context = {}) {
|
||||
let manualTaskData = null;
|
||||
let newTaskId;
|
||||
let telemetryData;
|
||||
let tagInfo;
|
||||
|
||||
if (isManualCreation) {
|
||||
// Create manual task data object
|
||||
@@ -129,6 +130,7 @@ export async function addTaskDirect(args, log, context = {}) {
|
||||
);
|
||||
newTaskId = result.newTaskId;
|
||||
telemetryData = result.telemetryData;
|
||||
tagInfo = result.tagInfo;
|
||||
} else {
|
||||
// AI-driven task creation
|
||||
log.info(
|
||||
@@ -154,6 +156,7 @@ export async function addTaskDirect(args, log, context = {}) {
|
||||
);
|
||||
newTaskId = result.newTaskId;
|
||||
telemetryData = result.telemetryData;
|
||||
tagInfo = result.tagInfo;
|
||||
}
|
||||
|
||||
// Restore normal logging
|
||||
@@ -164,7 +167,8 @@ export async function addTaskDirect(args, log, context = {}) {
|
||||
data: {
|
||||
taskId: newTaskId,
|
||||
message: `Successfully added new task #${newTaskId}`,
|
||||
telemetryData: telemetryData
|
||||
telemetryData: telemetryData,
|
||||
tagInfo: tagInfo
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
@@ -196,7 +196,8 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
|
||||
lowComplexityTasks
|
||||
},
|
||||
fullReport: coreResult.report,
|
||||
telemetryData: coreResult.telemetryData
|
||||
telemetryData: coreResult.telemetryData,
|
||||
tagInfo: coreResult.tagInfo
|
||||
}
|
||||
};
|
||||
} catch (parseError) {
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
import { clearSubtasks } from '../../../../scripts/modules/task-manager.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
disableSilentMode,
|
||||
readJSON
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Clear subtasks from specified tasks
|
||||
@@ -15,12 +17,13 @@ import fs from 'fs';
|
||||
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
|
||||
* @param {string} [args.id] - Task IDs (comma-separated) to clear subtasks from
|
||||
* @param {boolean} [args.all] - Clear subtasks from all tasks
|
||||
* @param {string} [args.tag] - Tag context to operate on (defaults to current active tag)
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
|
||||
*/
|
||||
export async function clearSubtasksDirect(args, log) {
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, id, all } = args;
|
||||
const { tasksJsonPath, id, all, tag, projectRoot } = args;
|
||||
try {
|
||||
log.info(`Clearing subtasks with args: ${JSON.stringify(args)}`);
|
||||
|
||||
@@ -64,52 +67,70 @@ export async function clearSubtasksDirect(args, log) {
|
||||
|
||||
let taskIds;
|
||||
|
||||
// Use readJSON which handles silent migration and tag resolution
|
||||
const data = readJSON(tasksPath, projectRoot, tag);
|
||||
|
||||
if (!data || !data.tasks) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INPUT_VALIDATION_ERROR',
|
||||
message: `No tasks found in tasks file: ${tasksPath}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const currentTag = data.tag || 'master';
|
||||
const tasks = data.tasks;
|
||||
|
||||
// If all is specified, get all task IDs
|
||||
if (all) {
|
||||
log.info('Clearing subtasks from all tasks');
|
||||
const data = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
|
||||
if (!data || !data.tasks || data.tasks.length === 0) {
|
||||
log.info(`Clearing subtasks from all tasks in tag '${currentTag}'`);
|
||||
if (tasks.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INPUT_VALIDATION_ERROR',
|
||||
message: 'No valid tasks found in the tasks file'
|
||||
message: `No tasks found in tag context '${currentTag}'`
|
||||
}
|
||||
};
|
||||
}
|
||||
taskIds = data.tasks.map((t) => t.id).join(',');
|
||||
taskIds = tasks.map((t) => t.id).join(',');
|
||||
} else {
|
||||
// Use the provided task IDs
|
||||
taskIds = id;
|
||||
}
|
||||
|
||||
log.info(`Clearing subtasks from tasks: ${taskIds}`);
|
||||
log.info(`Clearing subtasks from tasks: ${taskIds} in tag '${currentTag}'`);
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Call the core function
|
||||
clearSubtasks(tasksPath, taskIds);
|
||||
clearSubtasks(tasksPath, taskIds, { projectRoot, tag: currentTag });
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
// Read the updated data to provide a summary
|
||||
const updatedData = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
|
||||
const updatedData = readJSON(tasksPath, projectRoot, currentTag);
|
||||
const taskIdArray = taskIds.split(',').map((id) => parseInt(id.trim(), 10));
|
||||
|
||||
// Build a summary of what was done
|
||||
const clearedTasksCount = taskIdArray.length;
|
||||
const updatedTasks = updatedData.tasks || [];
|
||||
|
||||
const taskSummary = taskIdArray.map((id) => {
|
||||
const task = updatedData.tasks.find((t) => t.id === id);
|
||||
const task = updatedTasks.find((t) => t.id === id);
|
||||
return task ? { id, title: task.title } : { id, title: 'Task not found' };
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
message: `Successfully cleared subtasks from ${clearedTasksCount} task(s)`,
|
||||
tasksCleared: taskSummary
|
||||
message: `Successfully cleared subtasks from ${clearedTasksCount} task(s) in tag '${currentTag}'`,
|
||||
tasksCleared: taskSummary,
|
||||
tag: currentTag
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
125
mcp-server/src/core/direct-functions/copy-tag.js
Normal file
125
mcp-server/src/core/direct-functions/copy-tag.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* copy-tag.js
|
||||
* Direct function implementation for copying a tag
|
||||
*/
|
||||
|
||||
import { copyTag } from '../../../../scripts/modules/task-manager/tag-management.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for copying a tag with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {string} args.sourceName - Name of the source tag to copy from
|
||||
* @param {string} args.targetName - Name of the new tag to create
|
||||
* @param {string} [args.description] - Optional description for the new tag
|
||||
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
|
||||
* @param {string} [args.projectRoot] - Project root path
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function copyTagDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, sourceName, targetName, description, projectRoot } =
|
||||
args;
|
||||
const { session } = context;
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('copyTagDirect called without tasksJsonPath');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check required parameters
|
||||
if (!sourceName || typeof sourceName !== 'string') {
|
||||
log.error('Missing required parameter: sourceName');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_PARAMETER',
|
||||
message: 'Source tag name is required and must be a string'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (!targetName || typeof targetName !== 'string') {
|
||||
log.error('Missing required parameter: targetName');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_PARAMETER',
|
||||
message: 'Target tag name is required and must be a string'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`Copying tag from "${sourceName}" to "${targetName}"`);
|
||||
|
||||
// Prepare options
|
||||
const options = {
|
||||
description
|
||||
};
|
||||
|
||||
// Call the copyTag function
|
||||
const result = await copyTag(
|
||||
tasksJsonPath,
|
||||
sourceName,
|
||||
targetName,
|
||||
options,
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot
|
||||
},
|
||||
'json' // outputFormat - use 'json' to suppress CLI UI
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
sourceName: result.sourceName,
|
||||
targetName: result.targetName,
|
||||
copied: result.copied,
|
||||
tasksCopied: result.tasksCopied,
|
||||
description: result.description,
|
||||
message: `Successfully copied tag from "${result.sourceName}" to "${result.targetName}"`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in copyTagDirect: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'COPY_TAG_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
159
mcp-server/src/core/direct-functions/create-tag-from-branch.js
Normal file
159
mcp-server/src/core/direct-functions/create-tag-from-branch.js
Normal file
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* create-tag-from-branch.js
|
||||
* Direct function implementation for creating tags from git branches
|
||||
*/
|
||||
|
||||
import { createTagFromBranch } from '../../../../scripts/modules/task-manager/tag-management.js';
|
||||
import {
|
||||
getCurrentBranch,
|
||||
isGitRepository
|
||||
} from '../../../../scripts/modules/utils/git-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for creating tags from git branches with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {string} args.tasksJsonPath - Path to the tasks.json file (resolved by tool)
|
||||
* @param {string} [args.branchName] - Git branch name (optional, uses current branch if not provided)
|
||||
* @param {boolean} [args.copyFromCurrent] - Copy tasks from current tag
|
||||
* @param {string} [args.copyFromTag] - Copy tasks from specific tag
|
||||
* @param {string} [args.description] - Custom description for the tag
|
||||
* @param {boolean} [args.autoSwitch] - Automatically switch to the new tag
|
||||
* @param {string} [args.projectRoot] - Project root path
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function createTagFromBranchDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const {
|
||||
tasksJsonPath,
|
||||
branchName,
|
||||
copyFromCurrent,
|
||||
copyFromTag,
|
||||
description,
|
||||
autoSwitch,
|
||||
projectRoot
|
||||
} = args;
|
||||
const { session } = context;
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('createTagFromBranchDirect called without tasksJsonPath');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check if projectRoot was provided
|
||||
if (!projectRoot) {
|
||||
log.error('createTagFromBranchDirect called without projectRoot');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'projectRoot is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check if we're in a git repository
|
||||
if (!(await isGitRepository(projectRoot))) {
|
||||
log.error('Not in a git repository');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'NOT_GIT_REPOSITORY',
|
||||
message: 'Not in a git repository. Cannot create tag from branch.'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Determine branch name
|
||||
let targetBranch = branchName;
|
||||
if (!targetBranch) {
|
||||
targetBranch = await getCurrentBranch(projectRoot);
|
||||
if (!targetBranch) {
|
||||
log.error('Could not determine current git branch');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'NO_CURRENT_BRANCH',
|
||||
message: 'Could not determine current git branch'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
log.info(`Creating tag from git branch: ${targetBranch}`);
|
||||
|
||||
// Prepare options
|
||||
const options = {
|
||||
copyFromCurrent: copyFromCurrent || false,
|
||||
copyFromTag,
|
||||
description:
|
||||
description || `Tag created from git branch "${targetBranch}"`,
|
||||
autoSwitch: autoSwitch || false
|
||||
};
|
||||
|
||||
// Call the createTagFromBranch function
|
||||
const result = await createTagFromBranch(
|
||||
tasksJsonPath,
|
||||
targetBranch,
|
||||
options,
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot
|
||||
},
|
||||
'json' // outputFormat - use 'json' to suppress CLI UI
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
branchName: result.branchName,
|
||||
tagName: result.tagName,
|
||||
created: result.created,
|
||||
mappingUpdated: result.mappingUpdated,
|
||||
autoSwitched: result.autoSwitched,
|
||||
message: `Successfully created tag "${result.tagName}" from branch "${result.branchName}"`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in createTagFromBranchDirect: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'CREATE_TAG_FROM_BRANCH_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
110
mcp-server/src/core/direct-functions/delete-tag.js
Normal file
110
mcp-server/src/core/direct-functions/delete-tag.js
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* delete-tag.js
|
||||
* Direct function implementation for deleting a tag
|
||||
*/
|
||||
|
||||
import { deleteTag } from '../../../../scripts/modules/task-manager/tag-management.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for deleting a tag with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {string} args.name - Name of the tag to delete
|
||||
* @param {boolean} [args.yes=false] - Skip confirmation prompts
|
||||
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
|
||||
* @param {string} [args.projectRoot] - Project root path
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function deleteTagDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, name, yes = false, projectRoot } = args;
|
||||
const { session } = context;
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('deleteTagDirect called without tasksJsonPath');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check required parameters
|
||||
if (!name || typeof name !== 'string') {
|
||||
log.error('Missing required parameter: name');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_PARAMETER',
|
||||
message: 'Tag name is required and must be a string'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`Deleting tag: ${name}`);
|
||||
|
||||
// Prepare options
|
||||
const options = {
|
||||
yes // For MCP, we always skip confirmation prompts
|
||||
};
|
||||
|
||||
// Call the deleteTag function
|
||||
const result = await deleteTag(
|
||||
tasksJsonPath,
|
||||
name,
|
||||
options,
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot
|
||||
},
|
||||
'json' // outputFormat - use 'json' to suppress CLI UI
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
tagName: result.tagName,
|
||||
deleted: result.deleted,
|
||||
tasksDeleted: result.tasksDeleted,
|
||||
wasCurrentTag: result.wasCurrentTag,
|
||||
switchedToMaster: result.switchedToMaster,
|
||||
message: `Successfully deleted tag "${result.tagName}"`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in deleteTagDirect: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'DELETE_TAG_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
|
||||
// Read tasks data
|
||||
log.info(`[expandTaskDirect] Attempting to read JSON from: ${tasksPath}`);
|
||||
const data = readJSON(tasksPath);
|
||||
const data = readJSON(tasksPath, projectRoot);
|
||||
log.info(
|
||||
`[expandTaskDirect] Result of readJSON: ${data ? 'Data read successfully' : 'readJSON returned null or undefined'}`
|
||||
);
|
||||
@@ -164,10 +164,6 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
// Tracking subtasks count before expansion
|
||||
const subtasksCountBefore = task.subtasks ? task.subtasks.length : 0;
|
||||
|
||||
// Create a backup of the tasks.json file
|
||||
const backupPath = path.join(path.dirname(tasksPath), 'tasks.json.bak');
|
||||
fs.copyFileSync(tasksPath, backupPath);
|
||||
|
||||
// Directly modify the data instead of calling the CLI function
|
||||
if (!task.subtasks) {
|
||||
task.subtasks = [];
|
||||
@@ -207,7 +203,7 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
if (!wasSilent && isSilentMode()) disableSilentMode();
|
||||
|
||||
// Read the updated data
|
||||
const updatedData = readJSON(tasksPath);
|
||||
const updatedData = readJSON(tasksPath, projectRoot);
|
||||
const updatedTask = updatedData.tasks.find((t) => t.id === taskId);
|
||||
|
||||
// Calculate how many subtasks were added
|
||||
@@ -225,7 +221,8 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
task: coreResult.task,
|
||||
subtasksAdded,
|
||||
hasExistingSubtasks,
|
||||
telemetryData: coreResult.telemetryData
|
||||
telemetryData: coreResult.telemetryData,
|
||||
tagInfo: coreResult.tagInfo
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
132
mcp-server/src/core/direct-functions/list-tags.js
Normal file
132
mcp-server/src/core/direct-functions/list-tags.js
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* list-tags.js
|
||||
* Direct function implementation for listing all tags
|
||||
*/
|
||||
|
||||
import { tags } from '../../../../scripts/modules/task-manager/tag-management.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for listing all tags with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {boolean} [args.showMetadata=false] - Whether to include metadata in the output
|
||||
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
|
||||
* @param {string} [args.projectRoot] - Project root path
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function listTagsDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, showMetadata = false, projectRoot } = args;
|
||||
const { session } = context;
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('listTagsDirect called without tasksJsonPath');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info('Listing all tags');
|
||||
|
||||
// Prepare options
|
||||
const options = {
|
||||
showMetadata
|
||||
};
|
||||
|
||||
// Call the tags function
|
||||
const result = await tags(
|
||||
tasksJsonPath,
|
||||
options,
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot
|
||||
},
|
||||
'json' // outputFormat - use 'json' to suppress CLI UI
|
||||
);
|
||||
|
||||
// Transform the result to remove full task data and provide summary info
|
||||
const tagsSummary = result.tags.map((tag) => {
|
||||
const tasks = tag.tasks || [];
|
||||
|
||||
// Calculate status breakdown
|
||||
const statusBreakdown = tasks.reduce((acc, task) => {
|
||||
const status = task.status || 'pending';
|
||||
acc[status] = (acc[status] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Calculate subtask counts
|
||||
const subtaskCounts = tasks.reduce(
|
||||
(acc, task) => {
|
||||
if (task.subtasks && task.subtasks.length > 0) {
|
||||
acc.totalSubtasks += task.subtasks.length;
|
||||
task.subtasks.forEach((subtask) => {
|
||||
const subStatus = subtask.status || 'pending';
|
||||
acc.subtasksByStatus[subStatus] =
|
||||
(acc.subtasksByStatus[subStatus] || 0) + 1;
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ totalSubtasks: 0, subtasksByStatus: {} }
|
||||
);
|
||||
|
||||
return {
|
||||
name: tag.name,
|
||||
isCurrent: tag.isCurrent,
|
||||
taskCount: tasks.length,
|
||||
completedTasks: tag.completedTasks,
|
||||
statusBreakdown,
|
||||
subtaskCounts,
|
||||
created: tag.created,
|
||||
description: tag.description
|
||||
};
|
||||
});
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
tags: tagsSummary,
|
||||
currentTag: result.currentTag,
|
||||
totalTags: result.totalTags,
|
||||
message: `Found ${result.totalTags} tag(s)`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in listTagsDirect: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'LIST_TAGS_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,10 @@ import {
|
||||
* @param {Object} log - Logger object.
|
||||
* @returns {Promise<Object>} - Task list result { success: boolean, data?: any, error?: { code: string, message: string } }.
|
||||
*/
|
||||
export async function listTasksDirect(args, log) {
|
||||
export async function listTasksDirect(args, log, context = {}) {
|
||||
// Destructure the explicit tasksJsonPath from args
|
||||
const { tasksJsonPath, reportPath, status, withSubtasks } = args;
|
||||
const { tasksJsonPath, reportPath, status, withSubtasks, projectRoot } = args;
|
||||
const { session } = context;
|
||||
|
||||
if (!tasksJsonPath) {
|
||||
log.error('listTasksDirect called without tasksJsonPath');
|
||||
@@ -50,7 +51,9 @@ export async function listTasksDirect(args, log) {
|
||||
statusFilter,
|
||||
reportPath,
|
||||
withSubtasksFilter,
|
||||
'json'
|
||||
'json',
|
||||
null, // tag
|
||||
{ projectRoot, session } // context
|
||||
);
|
||||
|
||||
if (!resultData || !resultData.tasks) {
|
||||
|
||||
@@ -13,10 +13,11 @@ import {
|
||||
* Move a task or subtask to a new position
|
||||
* @param {Object} args - Function arguments
|
||||
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file
|
||||
* @param {string} args.sourceId - ID of the task/subtask to move (e.g., '5' or '5.2')
|
||||
* @param {string} args.destinationId - ID of the destination (e.g., '7' or '7.3')
|
||||
* @param {string} args.sourceId - ID of the task/subtask to move (e.g., '5' or '5.2' or '5,6,7')
|
||||
* @param {string} args.destinationId - ID of the destination (e.g., '7' or '7.3' or '7,8,9')
|
||||
* @param {string} args.file - Alternative path to the tasks.json file
|
||||
* @param {string} args.projectRoot - Project root directory
|
||||
* @param {boolean} args.generateFiles - Whether to regenerate task files after moving (default: true)
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Promise<{success: boolean, data?: Object, error?: Object}>}
|
||||
*/
|
||||
@@ -64,12 +65,17 @@ export async function moveTaskDirect(args, log, context = {}) {
|
||||
// Enable silent mode to prevent console output during MCP operation
|
||||
enableSilentMode();
|
||||
|
||||
// Call the core moveTask function, always generate files
|
||||
// Call the core moveTask function with file generation control
|
||||
const generateFiles = args.generateFiles !== false; // Default to true
|
||||
const result = await moveTask(
|
||||
tasksPath,
|
||||
args.sourceId,
|
||||
args.destinationId,
|
||||
true
|
||||
generateFiles,
|
||||
{
|
||||
projectRoot: args.projectRoot,
|
||||
tag: args.tag
|
||||
}
|
||||
);
|
||||
|
||||
// Restore console output
|
||||
@@ -78,7 +84,7 @@ export async function moveTaskDirect(args, log, context = {}) {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
movedTask: result.movedTask,
|
||||
...result,
|
||||
message: `Successfully moved task/subtask ${args.sourceId} to ${args.destinationId}`
|
||||
}
|
||||
};
|
||||
|
||||
@@ -21,9 +21,10 @@ import {
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Promise<Object>} - Next task result { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function nextTaskDirect(args, log) {
|
||||
export async function nextTaskDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, reportPath } = args;
|
||||
const { tasksJsonPath, reportPath, projectRoot } = args;
|
||||
const { session } = context;
|
||||
|
||||
if (!tasksJsonPath) {
|
||||
log.error('nextTaskDirect called without tasksJsonPath');
|
||||
@@ -45,7 +46,7 @@ export async function nextTaskDirect(args, log) {
|
||||
log.info(`Finding next task from ${tasksJsonPath}`);
|
||||
|
||||
// Read tasks data using the provided path
|
||||
const data = readJSON(tasksJsonPath);
|
||||
const data = readJSON(tasksJsonPath, projectRoot);
|
||||
if (!data || !data.tasks) {
|
||||
disableSilentMode(); // Disable before return
|
||||
return {
|
||||
|
||||
@@ -170,7 +170,8 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
data: {
|
||||
message: successMsg,
|
||||
outputPath: result.tasksPath,
|
||||
telemetryData: result.telemetryData
|
||||
telemetryData: result.telemetryData,
|
||||
tagInfo: result.tagInfo
|
||||
}
|
||||
};
|
||||
} else {
|
||||
|
||||
@@ -23,9 +23,10 @@ import {
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Promise<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function removeTaskDirect(args, log) {
|
||||
export async function removeTaskDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, id } = args;
|
||||
const { tasksJsonPath, id, projectRoot } = args;
|
||||
const { session } = context;
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
@@ -59,7 +60,7 @@ export async function removeTaskDirect(args, log) {
|
||||
);
|
||||
|
||||
// Validate all task IDs exist before proceeding
|
||||
const data = readJSON(tasksJsonPath);
|
||||
const data = readJSON(tasksJsonPath, projectRoot);
|
||||
if (!data || !data.tasks) {
|
||||
return {
|
||||
success: false,
|
||||
|
||||
118
mcp-server/src/core/direct-functions/rename-tag.js
Normal file
118
mcp-server/src/core/direct-functions/rename-tag.js
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* rename-tag.js
|
||||
* Direct function implementation for renaming a tag
|
||||
*/
|
||||
|
||||
import { renameTag } from '../../../../scripts/modules/task-manager/tag-management.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for renaming a tag with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {string} args.oldName - Current name of the tag to rename
|
||||
* @param {string} args.newName - New name for the tag
|
||||
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
|
||||
* @param {string} [args.projectRoot] - Project root path
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function renameTagDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, oldName, newName, projectRoot } = args;
|
||||
const { session } = context;
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('renameTagDirect called without tasksJsonPath');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check required parameters
|
||||
if (!oldName || typeof oldName !== 'string') {
|
||||
log.error('Missing required parameter: oldName');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_PARAMETER',
|
||||
message: 'Old tag name is required and must be a string'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (!newName || typeof newName !== 'string') {
|
||||
log.error('Missing required parameter: newName');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_PARAMETER',
|
||||
message: 'New tag name is required and must be a string'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`Renaming tag from "${oldName}" to "${newName}"`);
|
||||
|
||||
// Call the renameTag function
|
||||
const result = await renameTag(
|
||||
tasksJsonPath,
|
||||
oldName,
|
||||
newName,
|
||||
{}, // options (empty for now)
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot
|
||||
},
|
||||
'json' // outputFormat - use 'json' to suppress CLI UI
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
oldName: result.oldName,
|
||||
newName: result.newName,
|
||||
renamed: result.renamed,
|
||||
taskCount: result.taskCount,
|
||||
wasCurrentTag: result.wasCurrentTag,
|
||||
message: `Successfully renamed tag from "${result.oldName}" to "${result.newName}"`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in renameTagDirect: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'RENAME_TAG_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
249
mcp-server/src/core/direct-functions/research.js
Normal file
249
mcp-server/src/core/direct-functions/research.js
Normal file
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* research.js
|
||||
* Direct function implementation for AI-powered research queries
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { performResearch } from '../../../../scripts/modules/task-manager.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for performing AI-powered research with project context.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {string} args.query - Research query/prompt (required)
|
||||
* @param {string} [args.taskIds] - Comma-separated list of task/subtask IDs for context
|
||||
* @param {string} [args.filePaths] - Comma-separated list of file paths for context
|
||||
* @param {string} [args.customContext] - Additional custom context text
|
||||
* @param {boolean} [args.includeProjectTree=false] - Include project file tree in context
|
||||
* @param {string} [args.detailLevel='medium'] - Detail level: 'low', 'medium', 'high'
|
||||
* @param {string} [args.saveTo] - Automatically save to task/subtask ID (e.g., "15" or "15.2")
|
||||
* @param {boolean} [args.saveToFile=false] - Save research results to .taskmaster/docs/research/ directory
|
||||
* @param {string} [args.projectRoot] - Project root path
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function researchDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const {
|
||||
query,
|
||||
taskIds,
|
||||
filePaths,
|
||||
customContext,
|
||||
includeProjectTree = false,
|
||||
detailLevel = 'medium',
|
||||
saveTo,
|
||||
saveToFile = false,
|
||||
projectRoot
|
||||
} = args;
|
||||
const { session } = context; // Destructure session from context
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check required parameters
|
||||
if (!query || typeof query !== 'string' || query.trim().length === 0) {
|
||||
log.error('Missing or invalid required parameter: query');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_PARAMETER',
|
||||
message:
|
||||
'The query parameter is required and must be a non-empty string'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Parse comma-separated task IDs if provided
|
||||
const parsedTaskIds = taskIds
|
||||
? taskIds
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id.length > 0)
|
||||
: [];
|
||||
|
||||
// Parse comma-separated file paths if provided
|
||||
const parsedFilePaths = filePaths
|
||||
? filePaths
|
||||
.split(',')
|
||||
.map((path) => path.trim())
|
||||
.filter((path) => path.length > 0)
|
||||
: [];
|
||||
|
||||
// Validate detail level
|
||||
const validDetailLevels = ['low', 'medium', 'high'];
|
||||
if (!validDetailLevels.includes(detailLevel)) {
|
||||
log.error(`Invalid detail level: ${detailLevel}`);
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INVALID_PARAMETER',
|
||||
message: `Detail level must be one of: ${validDetailLevels.join(', ')}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info(
|
||||
`Performing research query: "${query.substring(0, 100)}${query.length > 100 ? '...' : ''}", ` +
|
||||
`taskIds: [${parsedTaskIds.join(', ')}], ` +
|
||||
`filePaths: [${parsedFilePaths.join(', ')}], ` +
|
||||
`detailLevel: ${detailLevel}, ` +
|
||||
`includeProjectTree: ${includeProjectTree}, ` +
|
||||
`projectRoot: ${projectRoot}`
|
||||
);
|
||||
|
||||
// Prepare options for the research function
|
||||
const researchOptions = {
|
||||
taskIds: parsedTaskIds,
|
||||
filePaths: parsedFilePaths,
|
||||
customContext: customContext || '',
|
||||
includeProjectTree,
|
||||
detailLevel,
|
||||
projectRoot,
|
||||
saveToFile
|
||||
};
|
||||
|
||||
// Prepare context for the research function
|
||||
const researchContext = {
|
||||
session,
|
||||
mcpLog,
|
||||
commandName: 'research',
|
||||
outputType: 'mcp'
|
||||
};
|
||||
|
||||
// Call the performResearch function
|
||||
const result = await performResearch(
|
||||
query.trim(),
|
||||
researchOptions,
|
||||
researchContext,
|
||||
'json', // outputFormat - use 'json' to suppress CLI UI
|
||||
false // allowFollowUp - disable for MCP calls
|
||||
);
|
||||
|
||||
// Auto-save to task/subtask if requested
|
||||
if (saveTo) {
|
||||
try {
|
||||
const isSubtask = saveTo.includes('.');
|
||||
|
||||
// Format research content for saving
|
||||
const researchContent = `## Research Query: ${query.trim()}
|
||||
|
||||
**Detail Level:** ${result.detailLevel}
|
||||
**Context Size:** ${result.contextSize} characters
|
||||
**Timestamp:** ${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}
|
||||
|
||||
### Results
|
||||
|
||||
${result.result}`;
|
||||
|
||||
if (isSubtask) {
|
||||
// Save to subtask
|
||||
const { updateSubtaskById } = await import(
|
||||
'../../../../scripts/modules/task-manager/update-subtask-by-id.js'
|
||||
);
|
||||
|
||||
const tasksPath = path.join(
|
||||
projectRoot,
|
||||
'.taskmaster',
|
||||
'tasks',
|
||||
'tasks.json'
|
||||
);
|
||||
await updateSubtaskById(
|
||||
tasksPath,
|
||||
saveTo,
|
||||
researchContent,
|
||||
false, // useResearch = false for simple append
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
commandName: 'research-save',
|
||||
outputType: 'mcp',
|
||||
projectRoot
|
||||
},
|
||||
'json'
|
||||
);
|
||||
|
||||
log.info(`Research saved to subtask ${saveTo}`);
|
||||
} else {
|
||||
// Save to task
|
||||
const updateTaskById = (
|
||||
await import(
|
||||
'../../../../scripts/modules/task-manager/update-task-by-id.js'
|
||||
)
|
||||
).default;
|
||||
|
||||
const taskIdNum = parseInt(saveTo, 10);
|
||||
const tasksPath = path.join(
|
||||
projectRoot,
|
||||
'.taskmaster',
|
||||
'tasks',
|
||||
'tasks.json'
|
||||
);
|
||||
await updateTaskById(
|
||||
tasksPath,
|
||||
taskIdNum,
|
||||
researchContent,
|
||||
false, // useResearch = false for simple append
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
commandName: 'research-save',
|
||||
outputType: 'mcp',
|
||||
projectRoot
|
||||
},
|
||||
'json',
|
||||
true // appendMode = true
|
||||
);
|
||||
|
||||
log.info(`Research saved to task ${saveTo}`);
|
||||
}
|
||||
} catch (saveError) {
|
||||
log.warn(`Error saving research to task/subtask: ${saveError.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
query: result.query,
|
||||
result: result.result,
|
||||
contextSize: result.contextSize,
|
||||
contextTokens: result.contextTokens,
|
||||
tokenBreakdown: result.tokenBreakdown,
|
||||
systemPromptTokens: result.systemPromptTokens,
|
||||
userPromptTokens: result.userPromptTokens,
|
||||
totalInputTokens: result.totalInputTokens,
|
||||
detailLevel: result.detailLevel,
|
||||
telemetryData: result.telemetryData,
|
||||
tagInfo: result.tagInfo,
|
||||
savedFilePath: result.savedFilePath
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in researchDirect: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'RESEARCH_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -13,13 +13,15 @@ import { nextTaskDirect } from './next-task.js';
|
||||
/**
|
||||
* Direct function wrapper for setTaskStatus with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments containing id, status and tasksJsonPath.
|
||||
* @param {Object} args - Command arguments containing id, status, tasksJsonPath, and projectRoot.
|
||||
* @param {Object} log - Logger object.
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object with success status and data/error information.
|
||||
*/
|
||||
export async function setTaskStatusDirect(args, log) {
|
||||
// Destructure expected args, including the resolved tasksJsonPath
|
||||
const { tasksJsonPath, id, status, complexityReportPath } = args;
|
||||
export async function setTaskStatusDirect(args, log, context = {}) {
|
||||
// Destructure expected args, including the resolved tasksJsonPath and projectRoot
|
||||
const { tasksJsonPath, id, status, complexityReportPath, projectRoot } = args;
|
||||
const { session } = context;
|
||||
try {
|
||||
log.info(`Setting task status with args: ${JSON.stringify(args)}`);
|
||||
|
||||
@@ -67,7 +69,11 @@ export async function setTaskStatusDirect(args, log) {
|
||||
enableSilentMode(); // Enable silent mode before calling core function
|
||||
try {
|
||||
// Call the core function
|
||||
await setTaskStatus(tasksPath, taskId, newStatus, { mcpLog: log });
|
||||
await setTaskStatus(tasksPath, taskId, newStatus, {
|
||||
mcpLog: log,
|
||||
projectRoot,
|
||||
session
|
||||
});
|
||||
|
||||
log.info(`Successfully set task ${taskId} status to ${newStatus}`);
|
||||
|
||||
@@ -89,9 +95,11 @@ export async function setTaskStatusDirect(args, log) {
|
||||
const nextResult = await nextTaskDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
reportPath: complexityReportPath
|
||||
reportPath: complexityReportPath,
|
||||
projectRoot: projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (nextResult.success) {
|
||||
|
||||
@@ -24,8 +24,7 @@ import { findTasksPath } from '../utils/path-utils.js';
|
||||
* @returns {Promise<Object>} - Result object with success status and data/error information.
|
||||
*/
|
||||
export async function showTaskDirect(args, log) {
|
||||
// Destructure session from context if needed later, otherwise ignore
|
||||
// const { session } = context;
|
||||
// This function doesn't need session context since it only reads data
|
||||
// Destructure projectRoot and other args. projectRoot is assumed normalized.
|
||||
const { id, file, reportPath, status, projectRoot } = args;
|
||||
|
||||
@@ -56,7 +55,7 @@ export async function showTaskDirect(args, log) {
|
||||
|
||||
// --- Rest of the function remains the same, using tasksJsonPath ---
|
||||
try {
|
||||
const tasksData = readJSON(tasksJsonPath);
|
||||
const tasksData = readJSON(tasksJsonPath, projectRoot);
|
||||
if (!tasksData || !tasksData.tasks) {
|
||||
return {
|
||||
success: false,
|
||||
@@ -66,32 +65,91 @@ export async function showTaskDirect(args, log) {
|
||||
|
||||
const complexityReport = readComplexityReport(reportPath);
|
||||
|
||||
const { task, originalSubtaskCount } = findTaskById(
|
||||
tasksData.tasks,
|
||||
id,
|
||||
complexityReport,
|
||||
status
|
||||
);
|
||||
// Parse comma-separated IDs
|
||||
const taskIds = id
|
||||
.split(',')
|
||||
.map((taskId) => taskId.trim())
|
||||
.filter((taskId) => taskId.length > 0);
|
||||
|
||||
if (!task) {
|
||||
if (taskIds.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'TASK_NOT_FOUND',
|
||||
message: `Task or subtask with ID ${id} not found`
|
||||
code: 'INVALID_TASK_ID',
|
||||
message: 'No valid task IDs provided'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`Successfully retrieved task ${id}.`);
|
||||
// Handle single task ID (existing behavior)
|
||||
if (taskIds.length === 1) {
|
||||
const { task, originalSubtaskCount } = findTaskById(
|
||||
tasksData.tasks,
|
||||
taskIds[0],
|
||||
complexityReport,
|
||||
status
|
||||
);
|
||||
|
||||
const returnData = { ...task };
|
||||
if (originalSubtaskCount !== null) {
|
||||
returnData._originalSubtaskCount = originalSubtaskCount;
|
||||
returnData._subtaskFilter = status;
|
||||
if (!task) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'TASK_NOT_FOUND',
|
||||
message: `Task or subtask with ID ${taskIds[0]} not found`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`Successfully retrieved task ${taskIds[0]}.`);
|
||||
|
||||
const returnData = { ...task };
|
||||
if (originalSubtaskCount !== null) {
|
||||
returnData._originalSubtaskCount = originalSubtaskCount;
|
||||
returnData._subtaskFilter = status;
|
||||
}
|
||||
|
||||
return { success: true, data: returnData };
|
||||
}
|
||||
|
||||
return { success: true, data: returnData };
|
||||
// Handle multiple task IDs
|
||||
const foundTasks = [];
|
||||
const notFoundIds = [];
|
||||
|
||||
taskIds.forEach((taskId) => {
|
||||
const { task, originalSubtaskCount } = findTaskById(
|
||||
tasksData.tasks,
|
||||
taskId,
|
||||
complexityReport,
|
||||
status
|
||||
);
|
||||
|
||||
if (task) {
|
||||
const taskData = { ...task };
|
||||
if (originalSubtaskCount !== null) {
|
||||
taskData._originalSubtaskCount = originalSubtaskCount;
|
||||
taskData._subtaskFilter = status;
|
||||
}
|
||||
foundTasks.push(taskData);
|
||||
} else {
|
||||
notFoundIds.push(taskId);
|
||||
}
|
||||
});
|
||||
|
||||
log.info(
|
||||
`Successfully retrieved ${foundTasks.length} of ${taskIds.length} requested tasks.`
|
||||
);
|
||||
|
||||
// Return multiple tasks with metadata
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
tasks: foundTasks,
|
||||
requestedIds: taskIds,
|
||||
foundCount: foundTasks.length,
|
||||
notFoundIds: notFoundIds,
|
||||
isMultiple: true
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
log.error(`Error showing task ${id}: ${error.message}`);
|
||||
return {
|
||||
|
||||
@@ -139,7 +139,8 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
subtask: coreResult.updatedSubtask,
|
||||
tasksPath,
|
||||
useResearch,
|
||||
telemetryData: coreResult.telemetryData
|
||||
telemetryData: coreResult.telemetryData,
|
||||
tagInfo: coreResult.tagInfo
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import { createLogWrapper } from '../../tools/utils.js';
|
||||
* @param {string} args.id - Task ID (or subtask ID like "1.2").
|
||||
* @param {string} args.prompt - New information/context prompt.
|
||||
* @param {boolean} [args.research] - Whether to use research role.
|
||||
* @param {boolean} [args.append] - Whether to append timestamped information instead of full update.
|
||||
* @param {string} [args.projectRoot] - Project root path.
|
||||
* @param {Object} log - Logger object.
|
||||
* @param {Object} context - Context object containing session data.
|
||||
@@ -27,7 +28,7 @@ import { createLogWrapper } from '../../tools/utils.js';
|
||||
export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
const { session } = context;
|
||||
// Destructure expected args, including projectRoot
|
||||
const { tasksJsonPath, id, prompt, research, projectRoot } = args;
|
||||
const { tasksJsonPath, id, prompt, research, append, projectRoot } = args;
|
||||
|
||||
const logWrapper = createLogWrapper(log);
|
||||
|
||||
@@ -76,7 +77,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
} else {
|
||||
// Parse as integer for main task IDs
|
||||
taskId = parseInt(id, 10);
|
||||
if (isNaN(taskId)) {
|
||||
if (Number.isNaN(taskId)) {
|
||||
const errorMessage = `Invalid task ID: ${id}. Task ID must be a positive integer or subtask ID (e.g., "5.2").`;
|
||||
logWrapper.error(errorMessage);
|
||||
return {
|
||||
@@ -118,7 +119,8 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
commandName: 'update-task',
|
||||
outputType: 'mcp'
|
||||
},
|
||||
'json'
|
||||
'json',
|
||||
append || false
|
||||
);
|
||||
|
||||
// Check if the core function returned null or an object without success
|
||||
@@ -132,7 +134,8 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
message: message,
|
||||
taskId: taskId,
|
||||
updated: false,
|
||||
telemetryData: coreResult?.telemetryData
|
||||
telemetryData: coreResult?.telemetryData,
|
||||
tagInfo: coreResult?.tagInfo
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -149,7 +152,8 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
useResearch: useResearch,
|
||||
updated: true,
|
||||
updatedTask: coreResult.updatedTask,
|
||||
telemetryData: coreResult.telemetryData
|
||||
telemetryData: coreResult.telemetryData,
|
||||
tagInfo: coreResult.tagInfo
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
@@ -90,7 +90,8 @@ export async function updateTasksDirect(args, log, context = {}) {
|
||||
message: `Successfully updated ${result.updatedTasks.length} tasks.`,
|
||||
tasksPath: tasksJsonPath,
|
||||
updatedCount: result.updatedTasks.length,
|
||||
telemetryData: result.telemetryData
|
||||
telemetryData: result.telemetryData,
|
||||
tagInfo: result.tagInfo
|
||||
}
|
||||
};
|
||||
} else {
|
||||
|
||||
103
mcp-server/src/core/direct-functions/use-tag.js
Normal file
103
mcp-server/src/core/direct-functions/use-tag.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* use-tag.js
|
||||
* Direct function implementation for switching to a tag
|
||||
*/
|
||||
|
||||
import { useTag } from '../../../../scripts/modules/task-manager/tag-management.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for switching to a tag with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {string} args.name - Name of the tag to switch to
|
||||
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
|
||||
* @param {string} [args.projectRoot] - Project root path
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function useTagDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, name, projectRoot } = args;
|
||||
const { session } = context;
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('useTagDirect called without tasksJsonPath');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check required parameters
|
||||
if (!name || typeof name !== 'string') {
|
||||
log.error('Missing required parameter: name');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_PARAMETER',
|
||||
message: 'Tag name is required and must be a string'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`Switching to tag: ${name}`);
|
||||
|
||||
// Call the useTag function
|
||||
const result = await useTag(
|
||||
tasksJsonPath,
|
||||
name,
|
||||
{}, // options (empty for now)
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot
|
||||
},
|
||||
'json' // outputFormat - use 'json' to suppress CLI UI
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
tagName: result.currentTag,
|
||||
switched: result.switched,
|
||||
previousTag: result.previousTag,
|
||||
taskCount: result.taskCount,
|
||||
message: `Successfully switched to tag "${result.currentTag}"`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in useTagDirect: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'USE_TAG_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,13 @@ import { removeTaskDirect } from './direct-functions/remove-task.js';
|
||||
import { initializeProjectDirect } from './direct-functions/initialize-project.js';
|
||||
import { modelsDirect } from './direct-functions/models.js';
|
||||
import { moveTaskDirect } from './direct-functions/move-task.js';
|
||||
import { researchDirect } from './direct-functions/research.js';
|
||||
import { addTagDirect } from './direct-functions/add-tag.js';
|
||||
import { deleteTagDirect } from './direct-functions/delete-tag.js';
|
||||
import { listTagsDirect } from './direct-functions/list-tags.js';
|
||||
import { useTagDirect } from './direct-functions/use-tag.js';
|
||||
import { renameTagDirect } from './direct-functions/rename-tag.js';
|
||||
import { copyTagDirect } from './direct-functions/copy-tag.js';
|
||||
|
||||
// Re-export utility functions
|
||||
export { findTasksPath } from './utils/path-utils.js';
|
||||
@@ -62,7 +69,14 @@ export const directFunctions = new Map([
|
||||
['removeTaskDirect', removeTaskDirect],
|
||||
['initializeProjectDirect', initializeProjectDirect],
|
||||
['modelsDirect', modelsDirect],
|
||||
['moveTaskDirect', moveTaskDirect]
|
||||
['moveTaskDirect', moveTaskDirect],
|
||||
['researchDirect', researchDirect],
|
||||
['addTagDirect', addTagDirect],
|
||||
['deleteTagDirect', deleteTagDirect],
|
||||
['listTagsDirect', listTagsDirect],
|
||||
['useTagDirect', useTagDirect],
|
||||
['renameTagDirect', renameTagDirect],
|
||||
['copyTagDirect', copyTagDirect]
|
||||
]);
|
||||
|
||||
// Re-export all direct function implementations
|
||||
@@ -92,5 +106,12 @@ export {
|
||||
removeTaskDirect,
|
||||
initializeProjectDirect,
|
||||
modelsDirect,
|
||||
moveTaskDirect
|
||||
moveTaskDirect,
|
||||
researchDirect,
|
||||
addTagDirect,
|
||||
deleteTagDirect,
|
||||
listTagsDirect,
|
||||
useTagDirect,
|
||||
renameTagDirect,
|
||||
copyTagDirect
|
||||
};
|
||||
|
||||
@@ -75,7 +75,13 @@ export function registerAddDependencyTool(server) {
|
||||
}
|
||||
|
||||
// Use handleApiResult to format the response
|
||||
return handleApiResult(result, log, 'Error adding dependency');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error adding dependency',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in addDependency tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -88,9 +88,11 @@ export function registerAddSubtaskTool(server) {
|
||||
details: args.details,
|
||||
status: args.status,
|
||||
dependencies: args.dependencies,
|
||||
skipGenerate: args.skipGenerate
|
||||
skipGenerate: args.skipGenerate,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
@@ -99,7 +101,13 @@ export function registerAddSubtaskTool(server) {
|
||||
log.error(`Failed to add subtask: ${result.error.message}`);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error adding subtask');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error adding subtask',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in addSubtask tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
99
mcp-server/src/tools/add-tag.js
Normal file
99
mcp-server/src/tools/add-tag.js
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* tools/add-tag.js
|
||||
* Tool to create a new tag
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
createErrorResponse,
|
||||
handleApiResult,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { addTagDirect } from '../core/task-master-core.js';
|
||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||
|
||||
/**
|
||||
* Register the addTag tool with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
*/
|
||||
export function registerAddTagTool(server) {
|
||||
server.addTool({
|
||||
name: 'add_tag',
|
||||
description: 'Create a new tag for organizing tasks in different contexts',
|
||||
parameters: z.object({
|
||||
name: z.string().describe('Name of the new tag to create'),
|
||||
copyFromCurrent: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Whether to copy tasks from the current tag (default: false)'
|
||||
),
|
||||
copyFromTag: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Specific tag to copy tasks from'),
|
||||
fromBranch: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Create tag name from current git branch (ignores name parameter)'
|
||||
),
|
||||
description: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional description for the tag'),
|
||||
file: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Path to the tasks file (default: tasks/tasks.json)'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Starting add-tag with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
||||
let tasksJsonPath;
|
||||
try {
|
||||
tasksJsonPath = findTasksPath(
|
||||
{ projectRoot: args.projectRoot, file: args.file },
|
||||
log
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks.json: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
`Failed to find tasks.json: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// Call the direct function
|
||||
const result = await addTagDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
name: args.name,
|
||||
copyFromCurrent: args.copyFromCurrent,
|
||||
copyFromTag: args.copyFromTag,
|
||||
fromBranch: args.fromBranch,
|
||||
description: args.description,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error creating tag',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in add-tag tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -99,7 +99,13 @@ export function registerAddTaskTool(server) {
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(result, log);
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error adding task',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in add-task tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -135,7 +135,13 @@ export function registerAnalyzeProjectComplexityTool(server) {
|
||||
log.info(
|
||||
`${toolName}: Direct function result: success=${result.success}`
|
||||
);
|
||||
return handleApiResult(result, log, 'Error analyzing task complexity');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error analyzing task complexity',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Critical error in ${toolName} tool execute: ${error.message}`
|
||||
|
||||
@@ -35,7 +35,8 @@ export function registerClearSubtasksTool(server) {
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
.describe('The directory of the project. Must be an absolute path.'),
|
||||
tag: z.string().optional().describe('Tag context to operate on')
|
||||
})
|
||||
.refine((data) => data.id || data.all, {
|
||||
message: "Either 'id' or 'all' parameter must be provided",
|
||||
@@ -63,9 +64,12 @@ export function registerClearSubtasksTool(server) {
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
id: args.id,
|
||||
all: args.all
|
||||
all: args.all,
|
||||
projectRoot: args.projectRoot,
|
||||
tag: args.tag || 'master'
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
@@ -74,7 +78,13 @@ export function registerClearSubtasksTool(server) {
|
||||
log.error(`Failed to clear subtasks: ${result.error.message}`);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error clearing subtasks');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error clearing subtasks',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in clearSubtasks tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -69,7 +69,9 @@ export function registerComplexityReportTool(server) {
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error retrieving complexity report'
|
||||
'Error retrieving complexity report',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in complexity-report tool: ${error.message}`);
|
||||
|
||||
83
mcp-server/src/tools/copy-tag.js
Normal file
83
mcp-server/src/tools/copy-tag.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* tools/copy-tag.js
|
||||
* Tool to copy an existing tag to a new tag
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
createErrorResponse,
|
||||
handleApiResult,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { copyTagDirect } from '../core/task-master-core.js';
|
||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||
|
||||
/**
|
||||
* Register the copyTag tool with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
*/
|
||||
export function registerCopyTagTool(server) {
|
||||
server.addTool({
|
||||
name: 'copy_tag',
|
||||
description:
|
||||
'Copy an existing tag to create a new tag with all tasks and metadata',
|
||||
parameters: z.object({
|
||||
sourceName: z.string().describe('Name of the source tag to copy from'),
|
||||
targetName: z.string().describe('Name of the new tag to create'),
|
||||
description: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional description for the new tag'),
|
||||
file: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Path to the tasks file (default: tasks/tasks.json)'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Starting copy-tag with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
||||
let tasksJsonPath;
|
||||
try {
|
||||
tasksJsonPath = findTasksPath(
|
||||
{ projectRoot: args.projectRoot, file: args.file },
|
||||
log
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks.json: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
`Failed to find tasks.json: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// Call the direct function
|
||||
const result = await copyTagDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
sourceName: args.sourceName,
|
||||
targetName: args.targetName,
|
||||
description: args.description,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error copying tag',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in copy-tag tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
80
mcp-server/src/tools/delete-tag.js
Normal file
80
mcp-server/src/tools/delete-tag.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* tools/delete-tag.js
|
||||
* Tool to delete an existing tag
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
createErrorResponse,
|
||||
handleApiResult,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { deleteTagDirect } from '../core/task-master-core.js';
|
||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||
|
||||
/**
|
||||
* Register the deleteTag tool with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
*/
|
||||
export function registerDeleteTagTool(server) {
|
||||
server.addTool({
|
||||
name: 'delete_tag',
|
||||
description: 'Delete an existing tag and all its tasks',
|
||||
parameters: z.object({
|
||||
name: z.string().describe('Name of the tag to delete'),
|
||||
yes: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Skip confirmation prompts (default: true for MCP)'),
|
||||
file: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Path to the tasks file (default: tasks/tasks.json)'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Starting delete-tag with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
||||
let tasksJsonPath;
|
||||
try {
|
||||
tasksJsonPath = findTasksPath(
|
||||
{ projectRoot: args.projectRoot, file: args.file },
|
||||
log
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks.json: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
`Failed to find tasks.json: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// Call the direct function (always skip confirmation for MCP)
|
||||
const result = await deleteTagDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
name: args.name,
|
||||
yes: args.yes !== undefined ? args.yes : true, // Default to true for MCP
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error deleting tag',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in delete-tag tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -92,7 +92,13 @@ export function registerExpandAllTool(server) {
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(result, log, 'Error expanding all tasks');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error expanding all tasks',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Unexpected error in expand_all tool execute: ${error.message}`
|
||||
|
||||
@@ -79,7 +79,13 @@ export function registerExpandTaskTool(server) {
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(result, log, 'Error expanding task');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error expanding task',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in expand-task tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -57,7 +57,13 @@ export function registerFixDependenciesTool(server) {
|
||||
log.error(`Failed to fix dependencies: ${result.error.message}`);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error fixing dependencies');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error fixing dependencies',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in fixDependencies tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -57,9 +57,11 @@ export function registerGenerateTool(server) {
|
||||
const result = await generateTaskFilesDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
outputDir: outputDir
|
||||
outputDir: outputDir,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
@@ -70,7 +72,13 @@ export function registerGenerateTool(server) {
|
||||
);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error generating task files');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error generating task files',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in generate tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -44,7 +44,11 @@ export function registerShowTaskTool(server) {
|
||||
name: 'get_task',
|
||||
description: 'Get detailed information about a specific task',
|
||||
parameters: z.object({
|
||||
id: z.string().describe('Task ID to get'),
|
||||
id: z
|
||||
.string()
|
||||
.describe(
|
||||
'Task ID(s) to get (can be comma-separated for multiple tasks)'
|
||||
),
|
||||
status: z
|
||||
.string()
|
||||
.optional()
|
||||
@@ -61,12 +65,11 @@ export function registerShowTaskTool(server) {
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Absolute path to the project root directory (Optional, usually from session)'
|
||||
)
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log }) => {
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
const { id, file, status, projectRoot } = args;
|
||||
|
||||
try {
|
||||
@@ -112,7 +115,8 @@ export function registerShowTaskTool(server) {
|
||||
status: status,
|
||||
projectRoot: projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
@@ -126,7 +130,8 @@ export function registerShowTaskTool(server) {
|
||||
result,
|
||||
log,
|
||||
'Error retrieving task details',
|
||||
processTaskResponse
|
||||
processTaskResponse,
|
||||
projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in get-task tool: ${error.message}\n${error.stack}`);
|
||||
|
||||
@@ -28,7 +28,9 @@ export function registerListTasksTool(server) {
|
||||
status: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Filter tasks by status (e.g., 'pending', 'done')"),
|
||||
.describe(
|
||||
"Filter tasks by status (e.g., 'pending', 'done') or multiple statuses separated by commas (e.g., 'blocked,deferred')"
|
||||
),
|
||||
withSubtasks: z
|
||||
.boolean()
|
||||
.optional()
|
||||
@@ -81,15 +83,23 @@ export function registerListTasksTool(server) {
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
status: args.status,
|
||||
withSubtasks: args.withSubtasks,
|
||||
reportPath: complexityReportPath
|
||||
reportPath: complexityReportPath,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
log.info(
|
||||
`Retrieved ${result.success ? result.data?.tasks?.length || 0 : 0} tasks`
|
||||
);
|
||||
return handleApiResult(result, log, 'Error getting tasks');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error getting tasks',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error getting tasks: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -29,6 +29,13 @@ import { registerRemoveTaskTool } from './remove-task.js';
|
||||
import { registerInitializeProjectTool } from './initialize-project.js';
|
||||
import { registerModelsTool } from './models.js';
|
||||
import { registerMoveTaskTool } from './move-task.js';
|
||||
import { registerAddTagTool } from './add-tag.js';
|
||||
import { registerDeleteTagTool } from './delete-tag.js';
|
||||
import { registerListTagsTool } from './list-tags.js';
|
||||
import { registerUseTagTool } from './use-tag.js';
|
||||
import { registerRenameTagTool } from './rename-tag.js';
|
||||
import { registerCopyTagTool } from './copy-tag.js';
|
||||
import { registerResearchTool } from './research.js';
|
||||
import { registerRulesTool } from './rules.js';
|
||||
|
||||
/**
|
||||
@@ -45,17 +52,22 @@ export function registerTaskMasterTools(server) {
|
||||
registerRulesTool(server);
|
||||
registerParsePRDTool(server);
|
||||
|
||||
// Group 2: Task Listing & Viewing
|
||||
// Group 2: Task Analysis & Expansion
|
||||
registerAnalyzeProjectComplexityTool(server);
|
||||
registerExpandTaskTool(server);
|
||||
registerExpandAllTool(server);
|
||||
|
||||
// Group 3: Task Listing & Viewing
|
||||
registerListTasksTool(server);
|
||||
registerShowTaskTool(server);
|
||||
registerNextTaskTool(server);
|
||||
registerComplexityReportTool(server);
|
||||
|
||||
// Group 3: Task Status & Management
|
||||
// Group 4: Task Status & Management
|
||||
registerSetTaskStatusTool(server);
|
||||
registerGenerateTool(server);
|
||||
|
||||
// Group 4: Task Creation & Modification
|
||||
// Group 5: Task Creation & Modification
|
||||
registerAddTaskTool(server);
|
||||
registerAddSubtaskTool(server);
|
||||
registerUpdateTool(server);
|
||||
@@ -66,16 +78,22 @@ export function registerTaskMasterTools(server) {
|
||||
registerClearSubtasksTool(server);
|
||||
registerMoveTaskTool(server);
|
||||
|
||||
// Group 5: Task Analysis & Expansion
|
||||
registerAnalyzeProjectComplexityTool(server);
|
||||
registerExpandTaskTool(server);
|
||||
registerExpandAllTool(server);
|
||||
|
||||
// Group 6: Dependency Management
|
||||
registerAddDependencyTool(server);
|
||||
registerRemoveDependencyTool(server);
|
||||
registerValidateDependenciesTool(server);
|
||||
registerFixDependenciesTool(server);
|
||||
|
||||
// Group 7: Tag Management
|
||||
registerListTagsTool(server);
|
||||
registerAddTagTool(server);
|
||||
registerDeleteTagTool(server);
|
||||
registerUseTagTool(server);
|
||||
registerRenameTagTool(server);
|
||||
registerCopyTagTool(server);
|
||||
|
||||
// Group 8: Research Features
|
||||
registerResearchTool(server);
|
||||
} catch (error) {
|
||||
logger.error(`Error registering Task Master tools: ${error.message}`);
|
||||
throw error;
|
||||
|
||||
@@ -55,7 +55,13 @@ export function registerInitializeProjectTool(server) {
|
||||
|
||||
const result = await initializeProjectDirect(args, log, { session });
|
||||
|
||||
return handleApiResult(result, log, 'Initialization failed');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Initialization failed',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
const errorMessage = `Project initialization tool failed: ${error.message || 'Unknown error'}`;
|
||||
log.error(errorMessage, error);
|
||||
|
||||
78
mcp-server/src/tools/list-tags.js
Normal file
78
mcp-server/src/tools/list-tags.js
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* tools/list-tags.js
|
||||
* Tool to list all available tags
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
createErrorResponse,
|
||||
handleApiResult,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { listTagsDirect } from '../core/task-master-core.js';
|
||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||
|
||||
/**
|
||||
* Register the listTags tool with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
*/
|
||||
export function registerListTagsTool(server) {
|
||||
server.addTool({
|
||||
name: 'list_tags',
|
||||
description: 'List all available tags with task counts and metadata',
|
||||
parameters: z.object({
|
||||
showMetadata: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether to include metadata in the output (default: false)'),
|
||||
file: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Path to the tasks file (default: tasks/tasks.json)'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Starting list-tags with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
||||
let tasksJsonPath;
|
||||
try {
|
||||
tasksJsonPath = findTasksPath(
|
||||
{ projectRoot: args.projectRoot, file: args.file },
|
||||
log
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks.json: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
`Failed to find tasks.json: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// Call the direct function
|
||||
const result = await listTagsDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
showMetadata: args.showMetadata,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error listing tags',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in list-tags tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -68,7 +68,13 @@ export function registerModelsTool(server) {
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(result, log);
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error managing models',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in models tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -102,7 +102,10 @@ export function registerMoveTaskTool(server) {
|
||||
message: `Successfully moved ${results.length} tasks`
|
||||
}
|
||||
},
|
||||
log
|
||||
log,
|
||||
'Error moving multiple tasks',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} else {
|
||||
// Moving a single task
|
||||
@@ -117,7 +120,10 @@ export function registerMoveTaskTool(server) {
|
||||
log,
|
||||
{ session }
|
||||
),
|
||||
log
|
||||
log,
|
||||
'Error moving task',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -64,13 +64,21 @@ export function registerNextTaskTool(server) {
|
||||
const result = await nextTaskDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
reportPath: complexityReportPath
|
||||
reportPath: complexityReportPath,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
log.info(`Next task result: ${result.success ? 'found' : 'none'}`);
|
||||
return handleApiResult(result, log, 'Error finding next task');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error finding next task',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error finding next task: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -64,7 +64,13 @@ export function registerParsePRDTool(server) {
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
const result = await parsePRDDirect(args, log, { session });
|
||||
return handleApiResult(result, log);
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error parsing PRD',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in parse_prd: ${error.message}`);
|
||||
return createErrorResponse(`Failed to parse PRD: ${error.message}`);
|
||||
|
||||
@@ -68,7 +68,13 @@ export function registerRemoveDependencyTool(server) {
|
||||
log.error(`Failed to remove dependency: ${result.error.message}`);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error removing dependency');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error removing dependency',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in removeDependency tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -46,7 +46,7 @@ export function registerRemoveSubtaskTool(server) {
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log }) => {
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
|
||||
|
||||
@@ -69,9 +69,11 @@ export function registerRemoveSubtaskTool(server) {
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
id: args.id,
|
||||
convert: args.convert,
|
||||
skipGenerate: args.skipGenerate
|
||||
skipGenerate: args.skipGenerate,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
@@ -80,7 +82,13 @@ export function registerRemoveSubtaskTool(server) {
|
||||
log.error(`Failed to remove subtask: ${result.error.message}`);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error removing subtask');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error removing subtask',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in removeSubtask tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -35,7 +35,7 @@ export function registerRemoveTaskTool(server) {
|
||||
.optional()
|
||||
.describe('Whether to skip confirmation prompt (default: false)')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log }) => {
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Removing task(s) with ID(s): ${args.id}`);
|
||||
|
||||
@@ -58,9 +58,11 @@ export function registerRemoveTaskTool(server) {
|
||||
const result = await removeTaskDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
id: args.id
|
||||
id: args.id,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
@@ -69,7 +71,13 @@ export function registerRemoveTaskTool(server) {
|
||||
log.error(`Failed to remove task: ${result.error.message}`);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error removing task');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error removing task',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in remove-task tool: ${error.message}`);
|
||||
return createErrorResponse(`Failed to remove task: ${error.message}`);
|
||||
|
||||
77
mcp-server/src/tools/rename-tag.js
Normal file
77
mcp-server/src/tools/rename-tag.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* tools/rename-tag.js
|
||||
* Tool to rename an existing tag
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
createErrorResponse,
|
||||
handleApiResult,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { renameTagDirect } from '../core/task-master-core.js';
|
||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||
|
||||
/**
|
||||
* Register the renameTag tool with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
*/
|
||||
export function registerRenameTagTool(server) {
|
||||
server.addTool({
|
||||
name: 'rename_tag',
|
||||
description: 'Rename an existing tag',
|
||||
parameters: z.object({
|
||||
oldName: z.string().describe('Current name of the tag to rename'),
|
||||
newName: z.string().describe('New name for the tag'),
|
||||
file: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Path to the tasks file (default: tasks/tasks.json)'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Starting rename-tag with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
||||
let tasksJsonPath;
|
||||
try {
|
||||
tasksJsonPath = findTasksPath(
|
||||
{ projectRoot: args.projectRoot, file: args.file },
|
||||
log
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks.json: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
`Failed to find tasks.json: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// Call the direct function
|
||||
const result = await renameTagDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
oldName: args.oldName,
|
||||
newName: args.newName,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error renaming tag',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in rename-tag tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
102
mcp-server/src/tools/research.js
Normal file
102
mcp-server/src/tools/research.js
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* tools/research.js
|
||||
* Tool to perform AI-powered research queries with project context
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
createErrorResponse,
|
||||
handleApiResult,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { researchDirect } from '../core/task-master-core.js';
|
||||
|
||||
/**
|
||||
* Register the research tool with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
*/
|
||||
export function registerResearchTool(server) {
|
||||
server.addTool({
|
||||
name: 'research',
|
||||
description: 'Perform AI-powered research queries with project context',
|
||||
parameters: z.object({
|
||||
query: z.string().describe('Research query/prompt (required)'),
|
||||
taskIds: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Comma-separated list of task/subtask IDs for context (e.g., "15,16.2,17")'
|
||||
),
|
||||
filePaths: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Comma-separated list of file paths for context (e.g., "src/api.js,docs/readme.md")'
|
||||
),
|
||||
customContext: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Additional custom context text to include in the research'),
|
||||
includeProjectTree: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Include project file tree structure in context (default: false)'
|
||||
),
|
||||
detailLevel: z
|
||||
.enum(['low', 'medium', 'high'])
|
||||
.optional()
|
||||
.describe('Detail level for the research response (default: medium)'),
|
||||
saveTo: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Automatically save research results to specified task/subtask ID (e.g., "15" or "15.2")'
|
||||
),
|
||||
saveToFile: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Save research results to .taskmaster/docs/research/ directory (default: false)'
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(
|
||||
`Starting research with query: "${args.query.substring(0, 100)}${args.query.length > 100 ? '...' : ''}"`
|
||||
);
|
||||
|
||||
// Call the direct function
|
||||
const result = await researchDirect(
|
||||
{
|
||||
query: args.query,
|
||||
taskIds: args.taskIds,
|
||||
filePaths: args.filePaths,
|
||||
customContext: args.customContext,
|
||||
includeProjectTree: args.includeProjectTree || false,
|
||||
detailLevel: args.detailLevel || 'medium',
|
||||
saveTo: args.saveTo,
|
||||
saveToFile: args.saveToFile || false,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error performing research',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in research tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export function registerSetTaskStatusTool(server) {
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log }) => {
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Setting status of task(s) ${args.id} to: ${args.status}`);
|
||||
|
||||
@@ -85,9 +85,11 @@ export function registerSetTaskStatusTool(server) {
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
id: args.id,
|
||||
status: args.status,
|
||||
complexityReportPath
|
||||
complexityReportPath,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
@@ -100,7 +102,13 @@ export function registerSetTaskStatusTool(server) {
|
||||
);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error setting task status');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error setting task status',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in setTaskStatus tool: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
|
||||
@@ -75,7 +75,13 @@ export function registerUpdateSubtaskTool(server) {
|
||||
);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error updating subtask');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error updating subtask',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Critical error in ${toolName} tool execute: ${error.message}`
|
||||
|
||||
@@ -34,6 +34,12 @@ export function registerUpdateTaskTool(server) {
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Use Perplexity AI for research-backed updates'),
|
||||
append: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Append timestamped information to task details instead of full update'
|
||||
),
|
||||
file: z.string().optional().describe('Absolute path to the tasks file'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
@@ -67,6 +73,7 @@ export function registerUpdateTaskTool(server) {
|
||||
id: args.id,
|
||||
prompt: args.prompt,
|
||||
research: args.research,
|
||||
append: args.append,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
@@ -77,7 +84,13 @@ export function registerUpdateTaskTool(server) {
|
||||
log.info(
|
||||
`${toolName}: Direct function result: success=${result.success}`
|
||||
);
|
||||
return handleApiResult(result, log, 'Error updating task');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error updating task',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Critical error in ${toolName} tool execute: ${error.message}`
|
||||
|
||||
@@ -80,7 +80,13 @@ export function registerUpdateTool(server) {
|
||||
log.info(
|
||||
`${toolName}: Direct function result: success=${result.success}`
|
||||
);
|
||||
return handleApiResult(result, log, 'Error updating tasks');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error updating tasks',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Critical error in ${toolName} tool execute: ${error.message}`
|
||||
|
||||
75
mcp-server/src/tools/use-tag.js
Normal file
75
mcp-server/src/tools/use-tag.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* tools/use-tag.js
|
||||
* Tool to switch to a different tag context
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
createErrorResponse,
|
||||
handleApiResult,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { useTagDirect } from '../core/task-master-core.js';
|
||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||
|
||||
/**
|
||||
* Register the useTag tool with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
*/
|
||||
export function registerUseTagTool(server) {
|
||||
server.addTool({
|
||||
name: 'use_tag',
|
||||
description: 'Switch to a different tag context for task operations',
|
||||
parameters: z.object({
|
||||
name: z.string().describe('Name of the tag to switch to'),
|
||||
file: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Path to the tasks file (default: tasks/tasks.json)'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Starting use-tag with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
||||
let tasksJsonPath;
|
||||
try {
|
||||
tasksJsonPath = findTasksPath(
|
||||
{ projectRoot: args.projectRoot, file: args.file },
|
||||
log
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks.json: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
`Failed to find tasks.json: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// Call the direct function
|
||||
const result = await useTagDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
name: args.name,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error switching tag',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in use-tag tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { contextManager } from '../core/context-manager.js'; // Import the singleton
|
||||
import { fileURLToPath } from 'url';
|
||||
import { getCurrentTag } from '../../../scripts/modules/utils.js';
|
||||
|
||||
// Import path utilities to ensure consistent path resolution
|
||||
import {
|
||||
@@ -59,6 +60,64 @@ function getVersionInfo() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current tag information for MCP responses
|
||||
* @param {string} projectRoot - The project root directory
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Object} Tag information object
|
||||
*/
|
||||
function getTagInfo(projectRoot, log) {
|
||||
try {
|
||||
if (!projectRoot) {
|
||||
log.warn('No project root provided for tag information');
|
||||
return { currentTag: 'master', availableTags: ['master'] };
|
||||
}
|
||||
|
||||
const currentTag = getCurrentTag(projectRoot);
|
||||
|
||||
// Read available tags from tasks.json
|
||||
let availableTags = ['master']; // Default fallback
|
||||
try {
|
||||
const tasksJsonPath = path.join(
|
||||
projectRoot,
|
||||
'.taskmaster',
|
||||
'tasks',
|
||||
'tasks.json'
|
||||
);
|
||||
if (fs.existsSync(tasksJsonPath)) {
|
||||
const tasksData = JSON.parse(fs.readFileSync(tasksJsonPath, 'utf-8'));
|
||||
|
||||
// If it's the new tagged format, extract tag keys
|
||||
if (
|
||||
tasksData &&
|
||||
typeof tasksData === 'object' &&
|
||||
!Array.isArray(tasksData.tasks)
|
||||
) {
|
||||
const tagKeys = Object.keys(tasksData).filter(
|
||||
(key) =>
|
||||
tasksData[key] &&
|
||||
typeof tasksData[key] === 'object' &&
|
||||
Array.isArray(tasksData[key].tasks)
|
||||
);
|
||||
if (tagKeys.length > 0) {
|
||||
availableTags = tagKeys;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (tagError) {
|
||||
log.debug(`Could not read available tags: ${tagError.message}`);
|
||||
}
|
||||
|
||||
return {
|
||||
currentTag: currentTag || 'master',
|
||||
availableTags: availableTags
|
||||
};
|
||||
} catch (error) {
|
||||
log.warn(`Error getting tag information: ${error.message}`);
|
||||
return { currentTag: 'master', availableTags: ['master'] };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get normalized project root path
|
||||
* @param {string|undefined} projectRootRaw - Raw project root from arguments
|
||||
@@ -242,21 +301,26 @@ function getProjectRootFromSession(session, log) {
|
||||
* @param {Object} log - Logger object
|
||||
* @param {string} errorPrefix - Prefix for error messages
|
||||
* @param {Function} processFunction - Optional function to process successful result data
|
||||
* @param {string} [projectRoot] - Optional project root for tag information
|
||||
* @returns {Object} - Standardized MCP response object
|
||||
*/
|
||||
async function handleApiResult(
|
||||
result,
|
||||
log,
|
||||
errorPrefix = 'API error',
|
||||
processFunction = processMCPResponseData
|
||||
processFunction = processMCPResponseData,
|
||||
projectRoot = null
|
||||
) {
|
||||
// Get version info for every response
|
||||
const versionInfo = getVersionInfo();
|
||||
|
||||
// Get tag info if project root is provided
|
||||
const tagInfo = projectRoot ? getTagInfo(projectRoot, log) : null;
|
||||
|
||||
if (!result.success) {
|
||||
const errorMsg = result.error?.message || `Unknown ${errorPrefix}`;
|
||||
log.error(`${errorPrefix}: ${errorMsg}`);
|
||||
return createErrorResponse(errorMsg, versionInfo);
|
||||
return createErrorResponse(errorMsg, versionInfo, tagInfo);
|
||||
}
|
||||
|
||||
// Process the result data if needed
|
||||
@@ -266,12 +330,17 @@ async function handleApiResult(
|
||||
|
||||
log.info('Successfully completed operation');
|
||||
|
||||
// Create the response payload including version info
|
||||
// Create the response payload including version info and tag info
|
||||
const responsePayload = {
|
||||
data: processedData,
|
||||
version: versionInfo
|
||||
};
|
||||
|
||||
// Add tag information if available
|
||||
if (tagInfo) {
|
||||
responsePayload.tag = tagInfo;
|
||||
}
|
||||
|
||||
return createContentResponse(responsePayload);
|
||||
}
|
||||
|
||||
@@ -496,21 +565,30 @@ function createContentResponse(content) {
|
||||
* Creates error response for tools
|
||||
* @param {string} errorMessage - Error message to include in response
|
||||
* @param {Object} [versionInfo] - Optional version information object
|
||||
* @param {Object} [tagInfo] - Optional tag information object
|
||||
* @returns {Object} - Error content response object in FastMCP format
|
||||
*/
|
||||
function createErrorResponse(errorMessage, versionInfo) {
|
||||
function createErrorResponse(errorMessage, versionInfo, tagInfo) {
|
||||
// Provide fallback version info if not provided
|
||||
if (!versionInfo) {
|
||||
versionInfo = getVersionInfo();
|
||||
}
|
||||
|
||||
let responseText = `Error: ${errorMessage}
|
||||
Version: ${versionInfo.version}
|
||||
Name: ${versionInfo.name}`;
|
||||
|
||||
// Add tag information if available
|
||||
if (tagInfo) {
|
||||
responseText += `
|
||||
Current Tag: ${tagInfo.currentTag}`;
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error: ${errorMessage}
|
||||
Version: ${versionInfo.version}
|
||||
Name: ${versionInfo.name}`
|
||||
text: responseText
|
||||
}
|
||||
],
|
||||
isError: true
|
||||
@@ -704,6 +782,7 @@ function withNormalizedProjectRoot(executeFn) {
|
||||
export {
|
||||
getProjectRoot,
|
||||
getProjectRootFromSession,
|
||||
getTagInfo,
|
||||
handleApiResult,
|
||||
executeTaskMasterCommand,
|
||||
getCachedOrExecute,
|
||||
|
||||
@@ -60,7 +60,13 @@ export function registerValidateDependenciesTool(server) {
|
||||
log.error(`Failed to validate dependencies: ${result.error.message}`);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error validating dependencies');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error validating dependencies',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in validateDependencies tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
Reference in New Issue
Block a user