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 rename-tag <old> <new>` - Rename tags with automatic current tag reference updates
|
||||||
- `task-master copy-tag <source> <target> [options]` - Duplicate tag contexts for experimentation
|
- `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:**
|
**Tag Creation Options:**
|
||||||
- `--copy-from-current` - Copy tasks from currently active tag
|
- `--copy-from-current` - Copy tasks from currently active tag
|
||||||
- `--copy-from=<tag>` - Copy tasks from specific tag
|
- `--copy-from=<tag>` - Copy tasks from specific tag
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"currentTag": "master",
|
"currentTag": "master",
|
||||||
"lastSwitched": "2025-06-13T07:55:31.313Z",
|
"lastSwitched": "2025-06-13T15:17:22.946Z",
|
||||||
"branchTagMapping": {},
|
"branchTagMapping": {},
|
||||||
"migrationNoticeShown": true
|
"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.
|
* 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} log - Logger object.
|
||||||
|
* @param {Object} context - Additional context (session)
|
||||||
* @returns {Promise<Object>} - Result object with success status and data/error information.
|
* @returns {Promise<Object>} - Result object with success status and data/error information.
|
||||||
*/
|
*/
|
||||||
export async function setTaskStatusDirect(args, log) {
|
export async function setTaskStatusDirect(args, log, context = {}) {
|
||||||
// Destructure expected args, including the resolved tasksJsonPath
|
// Destructure expected args, including the resolved tasksJsonPath and projectRoot
|
||||||
const { tasksJsonPath, id, status, complexityReportPath } = args;
|
const { tasksJsonPath, id, status, complexityReportPath, projectRoot } = args;
|
||||||
|
const { session } = context;
|
||||||
try {
|
try {
|
||||||
log.info(`Setting task status with args: ${JSON.stringify(args)}`);
|
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
|
enableSilentMode(); // Enable silent mode before calling core function
|
||||||
try {
|
try {
|
||||||
// Call the core function
|
// 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}`);
|
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.id - Task ID (or subtask ID like "1.2").
|
||||||
* @param {string} args.prompt - New information/context prompt.
|
* @param {string} args.prompt - New information/context prompt.
|
||||||
* @param {boolean} [args.research] - Whether to use research role.
|
* @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 {string} [args.projectRoot] - Project root path.
|
||||||
* @param {Object} log - Logger object.
|
* @param {Object} log - Logger object.
|
||||||
* @param {Object} context - Context object containing session data.
|
* @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 = {}) {
|
export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||||
const { session } = context;
|
const { session } = context;
|
||||||
// Destructure expected args, including projectRoot
|
// Destructure expected args, including projectRoot
|
||||||
const { tasksJsonPath, id, prompt, research, projectRoot } = args;
|
const { tasksJsonPath, id, prompt, research, append, projectRoot } = args;
|
||||||
|
|
||||||
const logWrapper = createLogWrapper(log);
|
const logWrapper = createLogWrapper(log);
|
||||||
|
|
||||||
@@ -118,7 +119,8 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
|||||||
commandName: 'update-task',
|
commandName: 'update-task',
|
||||||
outputType: 'mcp'
|
outputType: 'mcp'
|
||||||
},
|
},
|
||||||
'json'
|
'json',
|
||||||
|
append || false
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check if the core function returned null or an object without success
|
// 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 { modelsDirect } from './direct-functions/models.js';
|
||||||
import { moveTaskDirect } from './direct-functions/move-task.js';
|
import { moveTaskDirect } from './direct-functions/move-task.js';
|
||||||
import { researchDirect } from './direct-functions/research.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
|
// Re-export utility functions
|
||||||
export { findTasksPath } from './utils/path-utils.js';
|
export { findTasksPath } from './utils/path-utils.js';
|
||||||
@@ -64,7 +70,13 @@ export const directFunctions = new Map([
|
|||||||
['initializeProjectDirect', initializeProjectDirect],
|
['initializeProjectDirect', initializeProjectDirect],
|
||||||
['modelsDirect', modelsDirect],
|
['modelsDirect', modelsDirect],
|
||||||
['moveTaskDirect', moveTaskDirect],
|
['moveTaskDirect', moveTaskDirect],
|
||||||
['researchDirect', researchDirect]
|
['researchDirect', researchDirect],
|
||||||
|
['addTagDirect', addTagDirect],
|
||||||
|
['deleteTagDirect', deleteTagDirect],
|
||||||
|
['listTagsDirect', listTagsDirect],
|
||||||
|
['useTagDirect', useTagDirect],
|
||||||
|
['renameTagDirect', renameTagDirect],
|
||||||
|
['copyTagDirect', copyTagDirect]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Re-export all direct function implementations
|
// Re-export all direct function implementations
|
||||||
@@ -95,5 +107,11 @@ export {
|
|||||||
initializeProjectDirect,
|
initializeProjectDirect,
|
||||||
modelsDirect,
|
modelsDirect,
|
||||||
moveTaskDirect,
|
moveTaskDirect,
|
||||||
researchDirect
|
researchDirect,
|
||||||
|
addTagDirect,
|
||||||
|
deleteTagDirect,
|
||||||
|
listTagsDirect,
|
||||||
|
useTagDirect,
|
||||||
|
renameTagDirect,
|
||||||
|
copyTagDirect
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -88,9 +88,11 @@ export function registerAddSubtaskTool(server) {
|
|||||||
details: args.details,
|
details: args.details,
|
||||||
status: args.status,
|
status: args.status,
|
||||||
dependencies: args.dependencies,
|
dependencies: args.dependencies,
|
||||||
skipGenerate: args.skipGenerate
|
skipGenerate: args.skipGenerate,
|
||||||
|
projectRoot: args.projectRoot
|
||||||
},
|
},
|
||||||
log
|
log,
|
||||||
|
{ session }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
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,
|
tasksJsonPath: tasksJsonPath,
|
||||||
id: args.id,
|
id: args.id,
|
||||||
all: args.all
|
all: args.all,
|
||||||
|
projectRoot: args.projectRoot
|
||||||
},
|
},
|
||||||
log
|
log,
|
||||||
|
{ session }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
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(
|
const result = await generateTaskFilesDirect(
|
||||||
{
|
{
|
||||||
tasksJsonPath: tasksJsonPath,
|
tasksJsonPath: tasksJsonPath,
|
||||||
outputDir: outputDir
|
outputDir: outputDir,
|
||||||
|
projectRoot: args.projectRoot
|
||||||
},
|
},
|
||||||
log
|
log,
|
||||||
|
{ session }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ import { registerRemoveTaskTool } from './remove-task.js';
|
|||||||
import { registerInitializeProjectTool } from './initialize-project.js';
|
import { registerInitializeProjectTool } from './initialize-project.js';
|
||||||
import { registerModelsTool } from './models.js';
|
import { registerModelsTool } from './models.js';
|
||||||
import { registerMoveTaskTool } from './move-task.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 { registerResearchTool } from './research.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,17 +50,22 @@ export function registerTaskMasterTools(server) {
|
|||||||
registerModelsTool(server);
|
registerModelsTool(server);
|
||||||
registerParsePRDTool(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);
|
registerListTasksTool(server);
|
||||||
registerShowTaskTool(server);
|
registerShowTaskTool(server);
|
||||||
registerNextTaskTool(server);
|
registerNextTaskTool(server);
|
||||||
registerComplexityReportTool(server);
|
registerComplexityReportTool(server);
|
||||||
|
|
||||||
// Group 3: Task Status & Management
|
// Group 4: Task Status & Management
|
||||||
registerSetTaskStatusTool(server);
|
registerSetTaskStatusTool(server);
|
||||||
registerGenerateTool(server);
|
registerGenerateTool(server);
|
||||||
|
|
||||||
// Group 4: Task Creation & Modification
|
// Group 5: Task Creation & Modification
|
||||||
registerAddTaskTool(server);
|
registerAddTaskTool(server);
|
||||||
registerAddSubtaskTool(server);
|
registerAddSubtaskTool(server);
|
||||||
registerUpdateTool(server);
|
registerUpdateTool(server);
|
||||||
@@ -65,18 +76,21 @@ export function registerTaskMasterTools(server) {
|
|||||||
registerClearSubtasksTool(server);
|
registerClearSubtasksTool(server);
|
||||||
registerMoveTaskTool(server);
|
registerMoveTaskTool(server);
|
||||||
|
|
||||||
// Group 5: Task Analysis & Expansion
|
|
||||||
registerAnalyzeProjectComplexityTool(server);
|
|
||||||
registerExpandTaskTool(server);
|
|
||||||
registerExpandAllTool(server);
|
|
||||||
|
|
||||||
// Group 6: Dependency Management
|
// Group 6: Dependency Management
|
||||||
registerAddDependencyTool(server);
|
registerAddDependencyTool(server);
|
||||||
registerRemoveDependencyTool(server);
|
registerRemoveDependencyTool(server);
|
||||||
registerValidateDependenciesTool(server);
|
registerValidateDependenciesTool(server);
|
||||||
registerFixDependenciesTool(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);
|
registerResearchTool(server);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Error registering Task Master tools: ${error.message}`);
|
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';
|
} from './utils.js';
|
||||||
import { nextTaskDirect } from '../core/task-master-core.js';
|
import { nextTaskDirect } from '../core/task-master-core.js';
|
||||||
import {
|
import {
|
||||||
resolveTasksPath,
|
findTasksPath,
|
||||||
resolveComplexityReportPath
|
findComplexityReportPath
|
||||||
} from '../core/utils/path-utils.js';
|
} from '../core/utils/path-utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,10 +40,13 @@ export function registerNextTaskTool(server) {
|
|||||||
try {
|
try {
|
||||||
log.info(`Finding next task with args: ${JSON.stringify(args)}`);
|
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;
|
let tasksJsonPath;
|
||||||
try {
|
try {
|
||||||
tasksJsonPath = resolveTasksPath(args, session);
|
tasksJsonPath = findTasksPath(
|
||||||
|
{ projectRoot: args.projectRoot, file: args.file },
|
||||||
|
log
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error finding tasks.json: ${error.message}`);
|
log.error(`Error finding tasks.json: ${error.message}`);
|
||||||
return createErrorResponse(
|
return createErrorResponse(
|
||||||
@@ -54,7 +57,13 @@ export function registerNextTaskTool(server) {
|
|||||||
// Resolve the path to complexity report (optional)
|
// Resolve the path to complexity report (optional)
|
||||||
let complexityReportPath;
|
let complexityReportPath;
|
||||||
try {
|
try {
|
||||||
complexityReportPath = resolveComplexityReportPath(args, session);
|
complexityReportPath = findComplexityReportPath(
|
||||||
|
{
|
||||||
|
projectRoot: args.projectRoot,
|
||||||
|
complexityReport: args.complexityReport
|
||||||
|
},
|
||||||
|
log
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error finding complexity report: ${error.message}`);
|
log.error(`Error finding complexity report: ${error.message}`);
|
||||||
// This is optional, so we don't fail the operation
|
// This is optional, so we don't fail the operation
|
||||||
@@ -64,9 +73,11 @@ export function registerNextTaskTool(server) {
|
|||||||
const result = await nextTaskDirect(
|
const result = await nextTaskDirect(
|
||||||
{
|
{
|
||||||
tasksJsonPath: tasksJsonPath,
|
tasksJsonPath: tasksJsonPath,
|
||||||
reportPath: complexityReportPath
|
reportPath: complexityReportPath,
|
||||||
|
projectRoot: args.projectRoot
|
||||||
},
|
},
|
||||||
log
|
log,
|
||||||
|
{ session }
|
||||||
);
|
);
|
||||||
|
|
||||||
log.info(`Next task result: ${result.success ? 'found' : 'none'}`);
|
log.info(`Next task result: ${result.success ? 'found' : 'none'}`);
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export function registerRemoveSubtaskTool(server) {
|
|||||||
.string()
|
.string()
|
||||||
.describe('The directory of the project. Must be an absolute path.')
|
.describe('The directory of the project. Must be an absolute path.')
|
||||||
}),
|
}),
|
||||||
execute: withNormalizedProjectRoot(async (args, { log }) => {
|
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||||
try {
|
try {
|
||||||
log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
|
log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
|
||||||
|
|
||||||
@@ -69,9 +69,11 @@ export function registerRemoveSubtaskTool(server) {
|
|||||||
tasksJsonPath: tasksJsonPath,
|
tasksJsonPath: tasksJsonPath,
|
||||||
id: args.id,
|
id: args.id,
|
||||||
convert: args.convert,
|
convert: args.convert,
|
||||||
skipGenerate: args.skipGenerate
|
skipGenerate: args.skipGenerate,
|
||||||
|
projectRoot: args.projectRoot
|
||||||
},
|
},
|
||||||
log
|
log,
|
||||||
|
{ session }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export function registerRemoveTaskTool(server) {
|
|||||||
.optional()
|
.optional()
|
||||||
.describe('Whether to skip confirmation prompt (default: false)')
|
.describe('Whether to skip confirmation prompt (default: false)')
|
||||||
}),
|
}),
|
||||||
execute: withNormalizedProjectRoot(async (args, { log }) => {
|
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||||
try {
|
try {
|
||||||
log.info(`Removing task(s) with ID(s): ${args.id}`);
|
log.info(`Removing task(s) with ID(s): ${args.id}`);
|
||||||
|
|
||||||
@@ -58,9 +58,11 @@ export function registerRemoveTaskTool(server) {
|
|||||||
const result = await removeTaskDirect(
|
const result = await removeTaskDirect(
|
||||||
{
|
{
|
||||||
tasksJsonPath: tasksJsonPath,
|
tasksJsonPath: tasksJsonPath,
|
||||||
id: args.id
|
id: args.id,
|
||||||
|
projectRoot: args.projectRoot
|
||||||
},
|
},
|
||||||
log
|
log,
|
||||||
|
{ session }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
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()
|
.string()
|
||||||
.describe('The directory of the project. Must be an absolute path.')
|
.describe('The directory of the project. Must be an absolute path.')
|
||||||
}),
|
}),
|
||||||
execute: withNormalizedProjectRoot(async (args, { log }) => {
|
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||||
try {
|
try {
|
||||||
log.info(`Setting status of task(s) ${args.id} to: ${args.status}`);
|
log.info(`Setting status of task(s) ${args.id} to: ${args.status}`);
|
||||||
|
|
||||||
@@ -85,9 +85,11 @@ export function registerSetTaskStatusTool(server) {
|
|||||||
tasksJsonPath: tasksJsonPath,
|
tasksJsonPath: tasksJsonPath,
|
||||||
id: args.id,
|
id: args.id,
|
||||||
status: args.status,
|
status: args.status,
|
||||||
complexityReportPath
|
complexityReportPath,
|
||||||
|
projectRoot: args.projectRoot
|
||||||
},
|
},
|
||||||
log
|
log,
|
||||||
|
{ session }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ export function registerUpdateTaskTool(server) {
|
|||||||
.boolean()
|
.boolean()
|
||||||
.optional()
|
.optional()
|
||||||
.describe('Use Perplexity AI for research-backed updates'),
|
.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'),
|
file: z.string().optional().describe('Absolute path to the tasks file'),
|
||||||
projectRoot: z
|
projectRoot: z
|
||||||
.string()
|
.string()
|
||||||
@@ -67,6 +73,7 @@ export function registerUpdateTaskTool(server) {
|
|||||||
id: args.id,
|
id: args.id,
|
||||||
prompt: args.prompt,
|
prompt: args.prompt,
|
||||||
research: args.research,
|
research: args.research,
|
||||||
|
append: args.append,
|
||||||
projectRoot: args.projectRoot
|
projectRoot: args.projectRoot
|
||||||
},
|
},
|
||||||
log,
|
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',
|
'-r, --research',
|
||||||
'Use Perplexity AI for research-backed task updates'
|
'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')
|
.option('--tag <tag>', 'Specify tag context for task operations')
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
try {
|
try {
|
||||||
@@ -1058,7 +1062,9 @@ function registerCommands(programInstance) {
|
|||||||
taskId,
|
taskId,
|
||||||
prompt,
|
prompt,
|
||||||
useResearch,
|
useResearch,
|
||||||
{ projectRoot, tag }
|
{ projectRoot, tag },
|
||||||
|
'text',
|
||||||
|
options.append || false
|
||||||
);
|
);
|
||||||
|
|
||||||
// If the task wasn't updated (e.g., if it was already marked as done)
|
// If the task wasn't updated (e.g., if it was already marked as done)
|
||||||
|
|||||||
Reference in New Issue
Block a user