feat: add configurable MCP tool loading to reduce LLM context usage (#1181)
Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
This commit is contained in:
@@ -4,12 +4,14 @@ import dotenv from 'dotenv';
|
||||
import { fileURLToPath } from 'url';
|
||||
import fs from 'fs';
|
||||
import logger from './logger.js';
|
||||
import { registerTaskMasterTools } from './tools/index.js';
|
||||
import {
|
||||
registerTaskMasterTools,
|
||||
getToolsConfiguration
|
||||
} from './tools/index.js';
|
||||
import ProviderRegistry from '../../src/provider-registry/index.js';
|
||||
import { MCPProvider } from './providers/mcp-provider.js';
|
||||
import packageJson from '../../package.json' with { type: 'json' };
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
// Constants
|
||||
@@ -29,12 +31,10 @@ class TaskMasterMCPServer {
|
||||
this.server = new FastMCP(this.options);
|
||||
this.initialized = false;
|
||||
|
||||
// Bind methods
|
||||
this.init = this.init.bind(this);
|
||||
this.start = this.start.bind(this);
|
||||
this.stop = this.stop.bind(this);
|
||||
|
||||
// Setup logging
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@@ -44,8 +44,34 @@ class TaskMasterMCPServer {
|
||||
async init() {
|
||||
if (this.initialized) return;
|
||||
|
||||
// Pass the manager instance to the tool registration function
|
||||
registerTaskMasterTools(this.server, this.asyncManager);
|
||||
const normalizedToolMode = getToolsConfiguration();
|
||||
|
||||
this.logger.info('Task Master MCP Server starting...');
|
||||
this.logger.info(`Tool mode configuration: ${normalizedToolMode}`);
|
||||
|
||||
const registrationResult = registerTaskMasterTools(
|
||||
this.server,
|
||||
normalizedToolMode
|
||||
);
|
||||
|
||||
this.logger.info(
|
||||
`Normalized tool mode: ${registrationResult.normalizedMode}`
|
||||
);
|
||||
this.logger.info(
|
||||
`Registered ${registrationResult.registeredTools.length} tools successfully`
|
||||
);
|
||||
|
||||
if (registrationResult.registeredTools.length > 0) {
|
||||
this.logger.debug(
|
||||
`Registered tools: ${registrationResult.registeredTools.join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
if (registrationResult.failedTools.length > 0) {
|
||||
this.logger.warn(
|
||||
`Failed to register ${registrationResult.failedTools.length} tools: ${registrationResult.failedTools.join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
|
||||
@@ -3,109 +3,238 @@
|
||||
* Export all Task Master CLI tools for MCP server
|
||||
*/
|
||||
|
||||
import { registerListTasksTool } from './get-tasks.js';
|
||||
import logger from '../logger.js';
|
||||
import { registerSetTaskStatusTool } from './set-task-status.js';
|
||||
import { registerParsePRDTool } from './parse-prd.js';
|
||||
import { registerUpdateTool } from './update.js';
|
||||
import { registerUpdateTaskTool } from './update-task.js';
|
||||
import { registerUpdateSubtaskTool } from './update-subtask.js';
|
||||
import { registerGenerateTool } from './generate.js';
|
||||
import { registerShowTaskTool } from './get-task.js';
|
||||
import { registerNextTaskTool } from './next-task.js';
|
||||
import { registerExpandTaskTool } from './expand-task.js';
|
||||
import { registerAddTaskTool } from './add-task.js';
|
||||
import { registerAddSubtaskTool } from './add-subtask.js';
|
||||
import { registerRemoveSubtaskTool } from './remove-subtask.js';
|
||||
import { registerAnalyzeProjectComplexityTool } from './analyze.js';
|
||||
import { registerClearSubtasksTool } from './clear-subtasks.js';
|
||||
import { registerExpandAllTool } from './expand-all.js';
|
||||
import { registerRemoveDependencyTool } from './remove-dependency.js';
|
||||
import { registerValidateDependenciesTool } from './validate-dependencies.js';
|
||||
import { registerFixDependenciesTool } from './fix-dependencies.js';
|
||||
import { registerComplexityReportTool } from './complexity-report.js';
|
||||
import { registerAddDependencyTool } from './add-dependency.js';
|
||||
import { registerRemoveTaskTool } from './remove-task.js';
|
||||
import { registerInitializeProjectTool } from './initialize-project.js';
|
||||
import { registerModelsTool } from './models.js';
|
||||
import { registerMoveTaskTool } from './move-task.js';
|
||||
import { registerResponseLanguageTool } from './response-language.js';
|
||||
import { registerAddTagTool } from './add-tag.js';
|
||||
import { registerDeleteTagTool } from './delete-tag.js';
|
||||
import { registerListTagsTool } from './list-tags.js';
|
||||
import { registerUseTagTool } from './use-tag.js';
|
||||
import { registerRenameTagTool } from './rename-tag.js';
|
||||
import { registerCopyTagTool } from './copy-tag.js';
|
||||
import { registerResearchTool } from './research.js';
|
||||
import { registerRulesTool } from './rules.js';
|
||||
import { registerScopeUpTool } from './scope-up.js';
|
||||
import { registerScopeDownTool } from './scope-down.js';
|
||||
import {
|
||||
toolRegistry,
|
||||
coreTools,
|
||||
standardTools,
|
||||
getAvailableTools,
|
||||
getToolRegistration,
|
||||
isValidTool
|
||||
} from './tool-registry.js';
|
||||
|
||||
/**
|
||||
* Register all Task Master tools with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
* Helper function to safely read and normalize the TASK_MASTER_TOOLS environment variable
|
||||
* @returns {string} The tools configuration string, defaults to 'all'
|
||||
*/
|
||||
export function registerTaskMasterTools(server) {
|
||||
export function getToolsConfiguration() {
|
||||
const rawValue = process.env.TASK_MASTER_TOOLS;
|
||||
|
||||
if (!rawValue || rawValue.trim() === '') {
|
||||
logger.debug('No TASK_MASTER_TOOLS env var found, defaulting to "all"');
|
||||
return 'all';
|
||||
}
|
||||
|
||||
const normalizedValue = rawValue.trim();
|
||||
logger.debug(`TASK_MASTER_TOOLS env var: "${normalizedValue}"`);
|
||||
return normalizedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register Task Master tools with the MCP server
|
||||
* Supports selective tool loading via TASK_MASTER_TOOLS environment variable
|
||||
* @param {Object} server - FastMCP server instance
|
||||
* @param {string} toolMode - The tool mode configuration (defaults to 'all')
|
||||
* @returns {Object} Object containing registered tools, failed tools, and normalized mode
|
||||
*/
|
||||
export function registerTaskMasterTools(server, toolMode = 'all') {
|
||||
const registeredTools = [];
|
||||
const failedTools = [];
|
||||
|
||||
try {
|
||||
// Register each tool in a logical workflow order
|
||||
const enabledTools = toolMode.trim();
|
||||
let toolsToRegister = [];
|
||||
|
||||
// Group 1: Initialization & Setup
|
||||
registerInitializeProjectTool(server);
|
||||
registerModelsTool(server);
|
||||
registerRulesTool(server);
|
||||
registerParsePRDTool(server);
|
||||
const lowerCaseConfig = enabledTools.toLowerCase();
|
||||
|
||||
// Group 2: Task Analysis & Expansion
|
||||
registerAnalyzeProjectComplexityTool(server);
|
||||
registerExpandTaskTool(server);
|
||||
registerExpandAllTool(server);
|
||||
registerScopeUpTool(server);
|
||||
registerScopeDownTool(server);
|
||||
switch (lowerCaseConfig) {
|
||||
case 'all':
|
||||
toolsToRegister = Object.keys(toolRegistry);
|
||||
logger.info('Loading all available tools');
|
||||
break;
|
||||
case 'core':
|
||||
case 'lean':
|
||||
toolsToRegister = coreTools;
|
||||
logger.info('Loading core tools only');
|
||||
break;
|
||||
case 'standard':
|
||||
toolsToRegister = standardTools;
|
||||
logger.info('Loading standard tools');
|
||||
break;
|
||||
default:
|
||||
const requestedTools = enabledTools
|
||||
.split(',')
|
||||
.map((t) => t.trim())
|
||||
.filter((t) => t.length > 0);
|
||||
|
||||
// Group 3: Task Listing & Viewing
|
||||
registerListTasksTool(server);
|
||||
registerShowTaskTool(server);
|
||||
registerNextTaskTool(server);
|
||||
registerComplexityReportTool(server);
|
||||
const uniqueTools = new Set();
|
||||
const unknownTools = [];
|
||||
|
||||
// Group 4: Task Status & Management
|
||||
registerSetTaskStatusTool(server);
|
||||
registerGenerateTool(server);
|
||||
const aliasMap = {
|
||||
response_language: 'response-language'
|
||||
};
|
||||
|
||||
// Group 5: Task Creation & Modification
|
||||
registerAddTaskTool(server);
|
||||
registerAddSubtaskTool(server);
|
||||
registerUpdateTool(server);
|
||||
registerUpdateTaskTool(server);
|
||||
registerUpdateSubtaskTool(server);
|
||||
registerRemoveTaskTool(server);
|
||||
registerRemoveSubtaskTool(server);
|
||||
registerClearSubtasksTool(server);
|
||||
registerMoveTaskTool(server);
|
||||
for (const toolName of requestedTools) {
|
||||
let resolvedName = null;
|
||||
const lowerToolName = toolName.toLowerCase();
|
||||
|
||||
// Group 6: Dependency Management
|
||||
registerAddDependencyTool(server);
|
||||
registerRemoveDependencyTool(server);
|
||||
registerValidateDependenciesTool(server);
|
||||
registerFixDependenciesTool(server);
|
||||
registerResponseLanguageTool(server);
|
||||
if (aliasMap[lowerToolName]) {
|
||||
const aliasTarget = aliasMap[lowerToolName];
|
||||
for (const registryKey of Object.keys(toolRegistry)) {
|
||||
if (registryKey.toLowerCase() === aliasTarget.toLowerCase()) {
|
||||
resolvedName = registryKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Group 7: Tag Management
|
||||
registerListTagsTool(server);
|
||||
registerAddTagTool(server);
|
||||
registerDeleteTagTool(server);
|
||||
registerUseTagTool(server);
|
||||
registerRenameTagTool(server);
|
||||
registerCopyTagTool(server);
|
||||
if (!resolvedName) {
|
||||
for (const registryKey of Object.keys(toolRegistry)) {
|
||||
if (registryKey.toLowerCase() === lowerToolName) {
|
||||
resolvedName = registryKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Group 8: Research Features
|
||||
registerResearchTool(server);
|
||||
if (!resolvedName) {
|
||||
const withHyphens = lowerToolName.replace(/_/g, '-');
|
||||
for (const registryKey of Object.keys(toolRegistry)) {
|
||||
if (registryKey.toLowerCase() === withHyphens) {
|
||||
resolvedName = registryKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!resolvedName) {
|
||||
const withUnderscores = lowerToolName.replace(/-/g, '_');
|
||||
for (const registryKey of Object.keys(toolRegistry)) {
|
||||
if (registryKey.toLowerCase() === withUnderscores) {
|
||||
resolvedName = registryKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resolvedName) {
|
||||
uniqueTools.add(resolvedName);
|
||||
logger.debug(`Resolved tool "${toolName}" to "${resolvedName}"`);
|
||||
} else {
|
||||
unknownTools.push(toolName);
|
||||
logger.warn(`Unknown tool specified: "${toolName}"`);
|
||||
}
|
||||
}
|
||||
|
||||
toolsToRegister = Array.from(uniqueTools);
|
||||
|
||||
if (unknownTools.length > 0) {
|
||||
logger.warn(`Unknown tools: ${unknownTools.join(', ')}`);
|
||||
}
|
||||
|
||||
if (toolsToRegister.length === 0) {
|
||||
logger.warn(
|
||||
`No valid tools found in custom list. Loading all tools as fallback.`
|
||||
);
|
||||
toolsToRegister = Object.keys(toolRegistry);
|
||||
} else {
|
||||
logger.info(
|
||||
`Loading ${toolsToRegister.length} custom tools from list (${uniqueTools.size} unique after normalization)`
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Registering ${toolsToRegister.length} MCP tools (mode: ${enabledTools})`
|
||||
);
|
||||
|
||||
toolsToRegister.forEach((toolName) => {
|
||||
try {
|
||||
const registerFunction = getToolRegistration(toolName);
|
||||
if (registerFunction) {
|
||||
registerFunction(server);
|
||||
logger.debug(`Registered tool: ${toolName}`);
|
||||
registeredTools.push(toolName);
|
||||
} else {
|
||||
logger.warn(`Tool ${toolName} not found in registry`);
|
||||
failedTools.push(toolName);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.message && error.message.includes('already registered')) {
|
||||
logger.debug(`Tool ${toolName} already registered, skipping`);
|
||||
registeredTools.push(toolName);
|
||||
} else {
|
||||
logger.error(`Failed to register tool ${toolName}: ${error.message}`);
|
||||
failedTools.push(toolName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`Successfully registered ${registeredTools.length}/${toolsToRegister.length} tools`
|
||||
);
|
||||
if (failedTools.length > 0) {
|
||||
logger.warn(`Failed tools: ${failedTools.join(', ')}`);
|
||||
}
|
||||
|
||||
return {
|
||||
registeredTools,
|
||||
failedTools,
|
||||
normalizedMode: lowerCaseConfig
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`Error registering Task Master tools: ${error.message}`);
|
||||
throw error;
|
||||
logger.error(
|
||||
`Error parsing TASK_MASTER_TOOLS environment variable: ${error.message}`
|
||||
);
|
||||
logger.info('Falling back to loading all tools');
|
||||
|
||||
const fallbackTools = Object.keys(toolRegistry);
|
||||
for (const toolName of fallbackTools) {
|
||||
const registerFunction = getToolRegistration(toolName);
|
||||
if (registerFunction) {
|
||||
try {
|
||||
registerFunction(server);
|
||||
registeredTools.push(toolName);
|
||||
} catch (err) {
|
||||
if (err.message && err.message.includes('already registered')) {
|
||||
logger.debug(
|
||||
`Fallback tool ${toolName} already registered, skipping`
|
||||
);
|
||||
registeredTools.push(toolName);
|
||||
} else {
|
||||
logger.warn(
|
||||
`Failed to register fallback tool '${toolName}': ${err.message}`
|
||||
);
|
||||
failedTools.push(toolName);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn(`Tool '${toolName}' not found in registry`);
|
||||
failedTools.push(toolName);
|
||||
}
|
||||
}
|
||||
logger.info(
|
||||
`Successfully registered ${registeredTools.length} fallback tools`
|
||||
);
|
||||
|
||||
return {
|
||||
registeredTools,
|
||||
failedTools,
|
||||
normalizedMode: 'all'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
toolRegistry,
|
||||
coreTools,
|
||||
standardTools,
|
||||
getAvailableTools,
|
||||
getToolRegistration,
|
||||
isValidTool
|
||||
};
|
||||
|
||||
export default {
|
||||
registerTaskMasterTools
|
||||
};
|
||||
|
||||
168
mcp-server/src/tools/tool-registry.js
Normal file
168
mcp-server/src/tools/tool-registry.js
Normal file
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* tool-registry.js
|
||||
* Tool Registry Object Structure - Maps all 36 tool names to registration functions
|
||||
*/
|
||||
|
||||
import { registerListTasksTool } from './get-tasks.js';
|
||||
import { registerSetTaskStatusTool } from './set-task-status.js';
|
||||
import { registerParsePRDTool } from './parse-prd.js';
|
||||
import { registerUpdateTool } from './update.js';
|
||||
import { registerUpdateTaskTool } from './update-task.js';
|
||||
import { registerUpdateSubtaskTool } from './update-subtask.js';
|
||||
import { registerGenerateTool } from './generate.js';
|
||||
import { registerShowTaskTool } from './get-task.js';
|
||||
import { registerNextTaskTool } from './next-task.js';
|
||||
import { registerExpandTaskTool } from './expand-task.js';
|
||||
import { registerAddTaskTool } from './add-task.js';
|
||||
import { registerAddSubtaskTool } from './add-subtask.js';
|
||||
import { registerRemoveSubtaskTool } from './remove-subtask.js';
|
||||
import { registerAnalyzeProjectComplexityTool } from './analyze.js';
|
||||
import { registerClearSubtasksTool } from './clear-subtasks.js';
|
||||
import { registerExpandAllTool } from './expand-all.js';
|
||||
import { registerRemoveDependencyTool } from './remove-dependency.js';
|
||||
import { registerValidateDependenciesTool } from './validate-dependencies.js';
|
||||
import { registerFixDependenciesTool } from './fix-dependencies.js';
|
||||
import { registerComplexityReportTool } from './complexity-report.js';
|
||||
import { registerAddDependencyTool } from './add-dependency.js';
|
||||
import { registerRemoveTaskTool } from './remove-task.js';
|
||||
import { registerInitializeProjectTool } from './initialize-project.js';
|
||||
import { registerModelsTool } from './models.js';
|
||||
import { registerMoveTaskTool } from './move-task.js';
|
||||
import { registerResponseLanguageTool } from './response-language.js';
|
||||
import { registerAddTagTool } from './add-tag.js';
|
||||
import { registerDeleteTagTool } from './delete-tag.js';
|
||||
import { registerListTagsTool } from './list-tags.js';
|
||||
import { registerUseTagTool } from './use-tag.js';
|
||||
import { registerRenameTagTool } from './rename-tag.js';
|
||||
import { registerCopyTagTool } from './copy-tag.js';
|
||||
import { registerResearchTool } from './research.js';
|
||||
import { registerRulesTool } from './rules.js';
|
||||
import { registerScopeUpTool } from './scope-up.js';
|
||||
import { registerScopeDownTool } from './scope-down.js';
|
||||
|
||||
/**
|
||||
* Comprehensive tool registry mapping all 36 tool names to their registration functions
|
||||
* Used for dynamic tool registration and validation
|
||||
*/
|
||||
export const toolRegistry = {
|
||||
initialize_project: registerInitializeProjectTool,
|
||||
models: registerModelsTool,
|
||||
rules: registerRulesTool,
|
||||
parse_prd: registerParsePRDTool,
|
||||
'response-language': registerResponseLanguageTool,
|
||||
analyze_project_complexity: registerAnalyzeProjectComplexityTool,
|
||||
expand_task: registerExpandTaskTool,
|
||||
expand_all: registerExpandAllTool,
|
||||
scope_up_task: registerScopeUpTool,
|
||||
scope_down_task: registerScopeDownTool,
|
||||
get_tasks: registerListTasksTool,
|
||||
get_task: registerShowTaskTool,
|
||||
next_task: registerNextTaskTool,
|
||||
complexity_report: registerComplexityReportTool,
|
||||
set_task_status: registerSetTaskStatusTool,
|
||||
generate: registerGenerateTool,
|
||||
add_task: registerAddTaskTool,
|
||||
add_subtask: registerAddSubtaskTool,
|
||||
update: registerUpdateTool,
|
||||
update_task: registerUpdateTaskTool,
|
||||
update_subtask: registerUpdateSubtaskTool,
|
||||
remove_task: registerRemoveTaskTool,
|
||||
remove_subtask: registerRemoveSubtaskTool,
|
||||
clear_subtasks: registerClearSubtasksTool,
|
||||
move_task: registerMoveTaskTool,
|
||||
add_dependency: registerAddDependencyTool,
|
||||
remove_dependency: registerRemoveDependencyTool,
|
||||
validate_dependencies: registerValidateDependenciesTool,
|
||||
fix_dependencies: registerFixDependenciesTool,
|
||||
list_tags: registerListTagsTool,
|
||||
add_tag: registerAddTagTool,
|
||||
delete_tag: registerDeleteTagTool,
|
||||
use_tag: registerUseTagTool,
|
||||
rename_tag: registerRenameTagTool,
|
||||
copy_tag: registerCopyTagTool,
|
||||
research: registerResearchTool
|
||||
};
|
||||
|
||||
/**
|
||||
* Core tools array containing the 7 essential tools for daily development
|
||||
* These represent the minimal set needed for basic task management operations
|
||||
*/
|
||||
export const coreTools = [
|
||||
'get_tasks',
|
||||
'next_task',
|
||||
'get_task',
|
||||
'set_task_status',
|
||||
'update_subtask',
|
||||
'parse_prd',
|
||||
'expand_task'
|
||||
];
|
||||
|
||||
/**
|
||||
* Standard tools array containing the 15 most commonly used tools
|
||||
* Includes all core tools plus frequently used additional tools
|
||||
*/
|
||||
export const standardTools = [
|
||||
...coreTools,
|
||||
'initialize_project',
|
||||
'analyze_project_complexity',
|
||||
'expand_all',
|
||||
'add_subtask',
|
||||
'remove_task',
|
||||
'generate',
|
||||
'add_task',
|
||||
'complexity_report'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get all available tool names
|
||||
* @returns {string[]} Array of tool names
|
||||
*/
|
||||
export function getAvailableTools() {
|
||||
return Object.keys(toolRegistry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tool counts for all categories
|
||||
* @returns {Object} Object with core, standard, and total counts
|
||||
*/
|
||||
export function getToolCounts() {
|
||||
return {
|
||||
core: coreTools.length,
|
||||
standard: standardTools.length,
|
||||
total: Object.keys(toolRegistry).length
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tool arrays organized by category
|
||||
* @returns {Object} Object with arrays for each category
|
||||
*/
|
||||
export function getToolCategories() {
|
||||
const allTools = Object.keys(toolRegistry);
|
||||
return {
|
||||
core: [...coreTools],
|
||||
standard: [...standardTools],
|
||||
all: [...allTools],
|
||||
extended: allTools.filter((t) => !standardTools.includes(t))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get registration function for a specific tool
|
||||
* @param {string} toolName - Name of the tool
|
||||
* @returns {Function|null} Registration function or null if not found
|
||||
*/
|
||||
export function getToolRegistration(toolName) {
|
||||
return toolRegistry[toolName] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if a tool exists in the registry
|
||||
* @param {string} toolName - Name of the tool
|
||||
* @returns {boolean} True if tool exists
|
||||
*/
|
||||
export function isValidTool(toolName) {
|
||||
return toolName in toolRegistry;
|
||||
}
|
||||
|
||||
export default toolRegistry;
|
||||
Reference in New Issue
Block a user