feat(tags): Implement full MCP support for Tagged Task Lists and update-task append mode
This commit is contained in:
18
.changeset/free-pants-rescue.md
Normal file
18
.changeset/free-pants-rescue.md
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Enhance update-task with --append flag for timestamped task updates
|
||||
|
||||
Adds the `--append` flag to `update-task` command, enabling it to behave like `update-subtask` with timestamped information appending. This provides more flexible task updating options:
|
||||
|
||||
**CLI Enhancement:**
|
||||
- `task-master update-task --id=5 --prompt="New info"` - Full task update (existing behavior)
|
||||
- `task-master update-task --id=5 --append --prompt="Progress update"` - Append timestamped info to task details
|
||||
|
||||
**Full MCP Integration:**
|
||||
- MCP tool `update_task` now supports `append` parameter
|
||||
- Seamless integration with Cursor and other MCP clients
|
||||
- Consistent behavior between CLI and MCP interfaces
|
||||
|
||||
Instead of requiring separate subtask creation for progress tracking, you can now append timestamped information directly to parent tasks while preserving the option for comprehensive task updates.
|
||||
@@ -25,6 +25,16 @@ The new tagged system fundamentally changes how tasks are organized:
|
||||
- `task-master rename-tag <old> <new>` - Rename tags with automatic current tag reference updates
|
||||
- `task-master copy-tag <source> <target> [options]` - Duplicate tag contexts for experimentation
|
||||
|
||||
**🤖 Full MCP Integration for Tag Management:**
|
||||
|
||||
Task Master's multi-context capabilities are now fully exposed through the MCP server, enabling powerful agentic workflows:
|
||||
- **`list_tags`**: List all available tag contexts.
|
||||
- **`add_tag`**: Programmatically create new tags.
|
||||
- **`delete_tag`**: Remove tag contexts.
|
||||
- **`use_tag`**: Switch the agent's active task context.
|
||||
- **`rename_tag`**: Rename existing tags.
|
||||
- **`copy_tag`**: Duplicate entire task contexts for experimentation.
|
||||
|
||||
**Tag Creation Options:**
|
||||
- `--copy-from-current` - Copy tasks from currently active tag
|
||||
- `--copy-from=<tag>` - Copy tasks from specific tag
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"currentTag": "master",
|
||||
"lastSwitched": "2025-06-13T07:55:31.313Z",
|
||||
"lastSwitched": "2025-06-13T15:17:22.946Z",
|
||||
"branchTagMapping": {},
|
||||
"migrationNoticeShown": true
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
121
mcp-server/src/core/direct-functions/add-tag.js
Normal file
121
mcp-server/src/core/direct-functions/add-tag.js
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* add-tag.js
|
||||
* Direct function implementation for creating a new tag
|
||||
*/
|
||||
|
||||
import { createTag } 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 {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,
|
||||
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'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 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(`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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,12 @@ import { initializeProjectDirect } from './direct-functions/initialize-project.j
|
||||
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';
|
||||
@@ -64,7 +70,13 @@ export const directFunctions = new Map([
|
||||
['initializeProjectDirect', initializeProjectDirect],
|
||||
['modelsDirect', modelsDirect],
|
||||
['moveTaskDirect', moveTaskDirect],
|
||||
['researchDirect', researchDirect]
|
||||
['researchDirect', researchDirect],
|
||||
['addTagDirect', addTagDirect],
|
||||
['deleteTagDirect', deleteTagDirect],
|
||||
['listTagsDirect', listTagsDirect],
|
||||
['useTagDirect', useTagDirect],
|
||||
['renameTagDirect', renameTagDirect],
|
||||
['copyTagDirect', copyTagDirect]
|
||||
]);
|
||||
|
||||
// Re-export all direct function implementations
|
||||
@@ -95,5 +107,11 @@ export {
|
||||
initializeProjectDirect,
|
||||
modelsDirect,
|
||||
moveTaskDirect,
|
||||
researchDirect
|
||||
researchDirect,
|
||||
addTagDirect,
|
||||
deleteTagDirect,
|
||||
listTagsDirect,
|
||||
useTagDirect,
|
||||
renameTagDirect,
|
||||
copyTagDirect
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
92
mcp-server/src/tools/add-tag.js
Normal file
92
mcp-server/src/tools/add-tag.js
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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'),
|
||||
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,
|
||||
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);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -63,9 +63,11 @@ export function registerClearSubtasksTool(server) {
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
id: args.id,
|
||||
all: args.all
|
||||
all: args.all,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
|
||||
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);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -29,6 +29,12 @@ 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';
|
||||
|
||||
/**
|
||||
@@ -44,17 +50,22 @@ export function registerTaskMasterTools(server) {
|
||||
registerModelsTool(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);
|
||||
@@ -65,18 +76,21 @@ 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: AI-Powered Features
|
||||
// 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}`);
|
||||
|
||||
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);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
} from './utils.js';
|
||||
import { nextTaskDirect } from '../core/task-master-core.js';
|
||||
import {
|
||||
resolveTasksPath,
|
||||
resolveComplexityReportPath
|
||||
findTasksPath,
|
||||
findComplexityReportPath
|
||||
} from '../core/utils/path-utils.js';
|
||||
|
||||
/**
|
||||
@@ -40,10 +40,13 @@ export function registerNextTaskTool(server) {
|
||||
try {
|
||||
log.info(`Finding next task with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Resolve the path to tasks.json using new path utilities
|
||||
// Resolve the path to tasks.json
|
||||
let tasksJsonPath;
|
||||
try {
|
||||
tasksJsonPath = resolveTasksPath(args, session);
|
||||
tasksJsonPath = findTasksPath(
|
||||
{ projectRoot: args.projectRoot, file: args.file },
|
||||
log
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks.json: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
@@ -54,7 +57,13 @@ export function registerNextTaskTool(server) {
|
||||
// Resolve the path to complexity report (optional)
|
||||
let complexityReportPath;
|
||||
try {
|
||||
complexityReportPath = resolveComplexityReportPath(args, session);
|
||||
complexityReportPath = findComplexityReportPath(
|
||||
{
|
||||
projectRoot: args.projectRoot,
|
||||
complexityReport: args.complexityReport
|
||||
},
|
||||
log
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error finding complexity report: ${error.message}`);
|
||||
// This is optional, so we don't fail the operation
|
||||
@@ -64,9 +73,11 @@ 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'}`);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
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);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
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);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -948,6 +948,10 @@ function registerCommands(programInstance) {
|
||||
'-r, --research',
|
||||
'Use Perplexity AI for research-backed task updates'
|
||||
)
|
||||
.option(
|
||||
'--append',
|
||||
'Append timestamped information to task details instead of full update'
|
||||
)
|
||||
.option('--tag <tag>', 'Specify tag context for task operations')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
@@ -1058,7 +1062,9 @@ function registerCommands(programInstance) {
|
||||
taskId,
|
||||
prompt,
|
||||
useResearch,
|
||||
{ projectRoot, tag }
|
||||
{ projectRoot, tag },
|
||||
'text',
|
||||
options.append || false
|
||||
);
|
||||
|
||||
// If the task wasn't updated (e.g., if it was already marked as done)
|
||||
|
||||
Reference in New Issue
Block a user