UNPKG

@jerfowler/agent-comm-mcp-server

Version:

MCP server for AI agent task communication and delegation with diagnostic lifecycle visibility

209 lines 6.89 kB
/** * MCP Metadata Handler Compliance * * Implements MCP 2025-06-18 specification metadata handling for full protocol compliance. * Manages _meta field validation, reserved key handling, and metadata creation utilities * for MCP-compliant responses. * * @see https://modelcontextprotocol.io/specification/2025-06-18#metadata */ /** * MCP Metadata Validation Result */ import debug from 'debug'; const log = debug('agent-comm:metadatahandler'); // Initialize metadata handler log('MCP metadata handler initialized'); /** * MCP Reserved Key Patterns * All keys starting with modelcontextprotocol.io/ are reserved */ const MCP_RESERVED_PREFIX = 'modelcontextprotocol.io/'; /** * Valid MCP Reserved Keys * Complete list of officially supported MCP metadata keys */ const VALID_MCP_RESERVED_KEYS = new Set([ 'modelcontextprotocol.io/serverVersion', 'modelcontextprotocol.io/serverName', 'modelcontextprotocol.io/requestId', 'modelcontextprotocol.io/timestamp', 'modelcontextprotocol.io/responseStatus', 'modelcontextprotocol.io/errorType', 'modelcontextprotocol.io/errorMessage', 'modelcontextprotocol.io/taskId', 'modelcontextprotocol.io/progress', 'modelcontextprotocol.io/status' ]); /** * MCP Metadata Handler * Core class for managing MCP-compliant metadata operations */ export class MCPMetaHandler { serverVersion; serverName; constructor(serverVersion, serverName) { this.serverVersion = serverVersion; this.serverName = serverName; } /** * Create standard response metadata * * @param requestId - Request identifier * @param status - Response status * @returns MCP-compliant metadata object */ createResponseMeta(requestId, status) { return { 'modelcontextprotocol.io/serverVersion': this.serverVersion, 'modelcontextprotocol.io/serverName': this.serverName, 'modelcontextprotocol.io/requestId': requestId, 'modelcontextprotocol.io/responseStatus': status, 'modelcontextprotocol.io/timestamp': new Date().toISOString() }; } /** * Create error-specific metadata * * @param requestId - Request identifier * @param errorType - Type of error * @param errorMessage - Error message * @returns MCP-compliant error metadata */ createErrorMeta(requestId, errorType, errorMessage) { return { 'modelcontextprotocol.io/serverVersion': this.serverVersion, 'modelcontextprotocol.io/requestId': requestId, 'modelcontextprotocol.io/errorType': errorType, 'modelcontextprotocol.io/errorMessage': errorMessage, 'modelcontextprotocol.io/timestamp': new Date().toISOString() }; } /** * Create progress tracking metadata * * @param taskId - Task identifier * @param progress - Progress percentage (0-100) * @param status - Current status * @returns MCP-compliant progress metadata */ createProgressMeta(taskId, progress, status) { return { 'modelcontextprotocol.io/taskId': taskId, 'modelcontextprotocol.io/progress': progress, 'modelcontextprotocol.io/status': status, 'modelcontextprotocol.io/timestamp': new Date().toISOString() }; } /** * Validate metadata structure and reserved keys * * @param meta - Metadata object to validate * @returns Validation result with errors */ validateMeta(meta) { const errors = []; // Check all keys for reserved key violations for (const key of Object.keys(meta)) { if (isMCPReservedKey(key) && !VALID_MCP_RESERVED_KEYS.has(key)) { errors.push(`Invalid reserved key: ${key}`); } } return { isValid: errors.length === 0, errors }; } /** * Merge metadata objects with conflict resolution * Override strategy: later values override earlier ones * * @param base - Base metadata object * @param override - Override metadata object * @returns Merged metadata object */ mergeMeta(base, override) { return { ...base, ...override }; } } /** * Create MCP-compliant metadata from standard fields * * @param meta - Standard metadata fields * @returns MCP-compliant metadata object with proper key prefixes */ export function createMCPMeta(meta) { const result = {}; if (meta.serverVersion !== undefined) { result['modelcontextprotocol.io/serverVersion'] = meta.serverVersion; } if (meta.serverName !== undefined) { result['modelcontextprotocol.io/serverName'] = meta.serverName; } if (meta.requestId !== undefined) { result['modelcontextprotocol.io/requestId'] = meta.requestId; } if (meta.timestamp !== undefined) { result['modelcontextprotocol.io/timestamp'] = meta.timestamp; } if (meta.responseStatus !== undefined) { result['modelcontextprotocol.io/responseStatus'] = meta.responseStatus; } if (meta.errorType !== undefined) { result['modelcontextprotocol.io/errorType'] = meta.errorType; } if (meta.errorMessage !== undefined) { result['modelcontextprotocol.io/errorMessage'] = meta.errorMessage; } if (meta.taskId !== undefined) { result['modelcontextprotocol.io/taskId'] = meta.taskId; } if (meta.progress !== undefined) { result['modelcontextprotocol.io/progress'] = meta.progress; } if (meta.status !== undefined) { result['modelcontextprotocol.io/status'] = meta.status; } return result; } /** * Validate MCP metadata structure * * @param meta - Metadata object to validate * @returns Validation result with errors */ export function validateMCPMeta(meta) { const errors = []; // Check all keys for reserved key violations for (const key of Object.keys(meta)) { if (isMCPReservedKey(key) && !VALID_MCP_RESERVED_KEYS.has(key)) { errors.push(`Invalid reserved key: ${key}`); } } return { isValid: errors.length === 0, errors }; } /** * Merge MCP metadata objects * Simple merge strategy where later values override earlier ones * * @param meta1 - First metadata object * @param meta2 - Second metadata object (takes precedence) * @returns Merged metadata object */ export function mergeMCPMeta(meta1, meta2) { return { ...meta1, ...meta2 }; } /** * Check if a key is MCP reserved * All keys starting with modelcontextprotocol.io/ are considered reserved * * @param key - Key to check * @returns True if key is MCP reserved */ export function isMCPReservedKey(key) { return key.startsWith(MCP_RESERVED_PREFIX); } //# sourceMappingURL=metadata-handler.js.map