diff --git a/mcp-server/src/tools/update-subtask.js b/mcp-server/src/tools/update-subtask.js index aeb55446..512a1a53 100644 --- a/mcp-server/src/tools/update-subtask.js +++ b/mcp-server/src/tools/update-subtask.js @@ -7,7 +7,8 @@ import { TaskIdSchemaForMcp } from '@tm/core'; import { createErrorResponse, handleApiResult, - withNormalizedProjectRoot + withNormalizedProjectRoot, + validateMcpMetadata } from '@tm/mcp'; import { z } from 'zod'; import { resolveTag } from '../../../scripts/modules/utils.js'; @@ -77,35 +78,14 @@ export function registerUpdateSubtaskTool(server) { } // Validate metadata if provided - let parsedMetadata = null; - if (args.metadata) { - // Check if metadata updates are allowed - const allowMetadataUpdates = - process.env.TASK_MASTER_ALLOW_METADATA_UPDATES === 'true'; - if (!allowMetadataUpdates) { - return createErrorResponse( - 'Metadata updates are disabled. Set TASK_MASTER_ALLOW_METADATA_UPDATES=true in your MCP server environment to enable metadata modifications.' - ); - } - // Parse and validate JSON - try { - parsedMetadata = JSON.parse(args.metadata); - if ( - typeof parsedMetadata !== 'object' || - parsedMetadata === null || - Array.isArray(parsedMetadata) - ) { - return createErrorResponse( - 'Invalid metadata: must be a JSON object (not null or array)' - ); - } - } catch { - return createErrorResponse( - `Invalid metadata JSON: ${args.metadata}. Provide a valid JSON object string.` - ); - } + const validationResult = validateMcpMetadata( + args.metadata, + createErrorResponse + ); + if (validationResult.error) { + return validationResult.error; } - + const parsedMetadata = validationResult.parsedMetadata; // Validate that at least prompt or metadata is provided if (!args.prompt && !parsedMetadata) { return createErrorResponse( diff --git a/mcp-server/src/tools/update-task.js b/mcp-server/src/tools/update-task.js index 386ae3d0..ccbc8d7b 100644 --- a/mcp-server/src/tools/update-task.js +++ b/mcp-server/src/tools/update-task.js @@ -6,7 +6,8 @@ import { createErrorResponse, handleApiResult, - withNormalizedProjectRoot + withNormalizedProjectRoot, + validateMcpMetadata } from '@tm/mcp'; import { z } from 'zod'; import { resolveTag } from '../../../scripts/modules/utils.js'; @@ -85,44 +86,23 @@ export function registerUpdateTaskTool(server) { ); } - // 3. Validate metadata if provided - let parsedMetadata = null; - if (args.metadata) { - // Check if metadata updates are allowed - const allowMetadataUpdates = - process.env.TASK_MASTER_ALLOW_METADATA_UPDATES === 'true'; - if (!allowMetadataUpdates) { - return createErrorResponse( - 'Metadata updates are disabled. Set TASK_MASTER_ALLOW_METADATA_UPDATES=true in your MCP server environment to enable metadata modifications.' - ); - } - // Parse and validate JSON - try { - parsedMetadata = JSON.parse(args.metadata); - if ( - typeof parsedMetadata !== 'object' || - parsedMetadata === null || - Array.isArray(parsedMetadata) - ) { - return createErrorResponse( - 'Invalid metadata: must be a JSON object (not null or array)' - ); - } - } catch { - return createErrorResponse( - `Invalid metadata JSON: ${args.metadata}. Provide a valid JSON object string.` - ); - } + // Validate metadata if provided + const validationResult = validateMcpMetadata( + args.metadata, + createErrorResponse + ); + if (validationResult.error) { + return validationResult.error; } - - // 4. Validate that at least prompt or metadata is provided + const parsedMetadata = validationResult.parsedMetadata; + // Validate that at least prompt or metadata is provided if (!args.prompt && !parsedMetadata) { return createErrorResponse( 'Either prompt or metadata must be provided for update-task' ); } - // 5. Call Direct Function - Include projectRoot and metadata + // Call Direct Function - Include projectRoot and metadata const result = await updateTaskByIdDirect( { tasksJsonPath: tasksJsonPath, diff --git a/mcp-server/src/tools/utils.js b/mcp-server/src/tools/utils.js index 0159beb3..f24aab95 100644 --- a/mcp-server/src/tools/utils.js +++ b/mcp-server/src/tools/utils.js @@ -829,6 +829,72 @@ function checkProgressCapability(reportProgress, log) { return reportProgress; } +/** + * Validates and parses metadata string for MCP tools. + * Checks environment flag, validates JSON format, and ensures metadata is a plain object. + * + * @param {string|null|undefined} metadataString - JSON string to parse and validate + * @param {Function} createErrorResponse - Function to create error response + * @returns {{parsedMetadata: Object|null, error?: Object}} Object with parsed metadata or error + * + * @example Success case: + * const result = validateMcpMetadata('{"key":"value"}', createErrorResponse); + * if (result.error) return result.error; + * const metadata = result.parsedMetadata; // { key: "value" } + * + * @example Disabled case: + * // When TASK_MASTER_ALLOW_METADATA_UPDATES !== 'true' + * const result = validateMcpMetadata('{"key":"value"}', createErrorResponse); + * // Returns: { error: } + * + * @example Invalid JSON: + * const result = validateMcpMetadata('{invalid}', createErrorResponse); + * // Returns: { error: } + */ +export function validateMcpMetadata(metadataString, createErrorResponse) { + // Return null if no metadata provided + if (!metadataString) { + return { parsedMetadata: null }; + } + + // Check if metadata updates are allowed via environment variable + const allowMetadataUpdates = + process.env.TASK_MASTER_ALLOW_METADATA_UPDATES === 'true'; + if (!allowMetadataUpdates) { + return { + error: createErrorResponse( + 'Metadata updates are disabled. Set TASK_MASTER_ALLOW_METADATA_UPDATES=true in your MCP server environment to enable metadata modifications.' + ) + }; + } + + // Parse and validate JSON + try { + const parsedMetadata = JSON.parse(metadataString); + + // Ensure it's a plain object (not null, not array) + if ( + typeof parsedMetadata !== 'object' || + parsedMetadata === null || + Array.isArray(parsedMetadata) + ) { + return { + error: createErrorResponse( + 'Invalid metadata: must be a JSON object (not null or array)' + ) + }; + } + + return { parsedMetadata }; + } catch { + return { + error: createErrorResponse( + `Invalid metadata JSON: ${metadataString}. Provide a valid JSON object string.` + ) + }; + } +} + // Ensure all functions are exported export { getProjectRoot,