UNPKG

jay-code

Version:

Streamlined AI CLI orchestration engine with mathematical rigor and enterprise-grade reliability

475 lines (409 loc) 15.3 kB
/** * MCP Protocol Version Management and Compatibility Checking */ import type { MCPProtocolVersion, MCPCapabilities, MCPInitializeParams } from '../utils/types.js'; import type { ILogger } from '../core/logger.js'; import { MCPError } from '../utils/errors.js'; export interface ProtocolVersionInfo { version: MCPProtocolVersion; name: string; releaseDate: Date; deprecated?: boolean; deprecationDate?: Date; supportedFeatures: string[]; breakingChanges?: string[]; migrationGuide?: string; } export interface CompatibilityResult { compatible: boolean; warnings: string[]; errors: string[]; recommendedVersion?: MCPProtocolVersion; missingFeatures?: string[]; deprecatedFeatures?: string[]; } export interface NegotiationResult { agreedVersion: MCPProtocolVersion; agreedCapabilities: MCPCapabilities; clientCapabilities: MCPCapabilities; serverCapabilities: MCPCapabilities; warnings: string[]; limitations: string[]; } /** * MCP Protocol Manager * Handles protocol version negotiation, compatibility checking, and feature management */ export class MCPProtocolManager { private supportedVersions: Map<string, ProtocolVersionInfo> = new Map(); private currentVersion: MCPProtocolVersion; private serverCapabilities: MCPCapabilities; private readonly knownVersions: ProtocolVersionInfo[] = [ { version: { major: 2024, minor: 11, patch: 5 }, name: 'MCP 2024.11.5', releaseDate: new Date('2024-11-01'), supportedFeatures: [ 'tools', 'prompts', 'resources', 'logging', 'sampling', 'notifications', 'tool_list_changed', 'resource_list_changed', 'prompt_list_changed', ], }, { version: { major: 2024, minor: 11, patch: 4 }, name: 'MCP 2024.11.4', releaseDate: new Date('2024-10-15'), supportedFeatures: [ 'tools', 'prompts', 'resources', 'logging', 'notifications', 'tool_list_changed', 'resource_list_changed', ], }, { version: { major: 2024, minor: 11, patch: 3 }, name: 'MCP 2024.11.3', releaseDate: new Date('2024-10-01'), supportedFeatures: ['tools', 'prompts', 'resources', 'logging', 'notifications'], }, { version: { major: 2024, minor: 10, patch: 0 }, name: 'MCP 2024.10.0', releaseDate: new Date('2024-09-01'), deprecated: true, deprecationDate: new Date('2024-11-01'), supportedFeatures: ['tools', 'prompts', 'resources', 'logging'], breakingChanges: ['Changed tool response format', 'Modified error codes'], migrationGuide: 'https://docs.mcp.io/migration/2024.10-to-2024.11', }, ]; constructor( private logger: ILogger, preferredVersion?: MCPProtocolVersion, serverCapabilities?: MCPCapabilities, ) { // Initialize supported versions for (const versionInfo of this.knownVersions) { const key = this.versionToString(versionInfo.version); this.supportedVersions.set(key, versionInfo); } // Set current version (latest supported or preferred) this.currentVersion = preferredVersion || this.getLatestSupportedVersion(); // Set server capabilities this.serverCapabilities = serverCapabilities || this.getDefaultCapabilities(); this.logger.info('Protocol manager initialized', { currentVersion: this.versionToString(this.currentVersion), supportedVersions: this.getSupportedVersionStrings(), }); } /** * Negotiate protocol version and capabilities with client */ async negotiateProtocol(clientParams: MCPInitializeParams): Promise<NegotiationResult> { this.logger.debug('Starting protocol negotiation', { clientVersion: this.versionToString(clientParams.protocolVersion), clientCapabilities: clientParams.capabilities, clientInfo: clientParams.clientInfo, }); const result: NegotiationResult = { agreedVersion: this.currentVersion, agreedCapabilities: { ...this.serverCapabilities }, clientCapabilities: clientParams.capabilities, serverCapabilities: this.serverCapabilities, warnings: [], limitations: [], }; try { // Check version compatibility const compatibility = this.checkCompatibility(clientParams.protocolVersion); if (!compatibility.compatible) { throw new MCPError( `Protocol version ${this.versionToString(clientParams.protocolVersion)} is not compatible. ${compatibility.errors.join(', ')}`, ); } // Use client's version if it's supported and newer if (this.isVersionSupported(clientParams.protocolVersion)) { const clientVersionInfo = this.getVersionInfo(clientParams.protocolVersion); const currentVersionInfo = this.getVersionInfo(this.currentVersion); if (clientVersionInfo && currentVersionInfo) { if (this.compareVersions(clientParams.protocolVersion, this.currentVersion) <= 0) { result.agreedVersion = clientParams.protocolVersion; } } } // Negotiate capabilities result.agreedCapabilities = this.negotiateCapabilities( clientParams.capabilities, this.serverCapabilities, result.agreedVersion, ); // Add warnings from compatibility check result.warnings.push(...compatibility.warnings); // Check for deprecated features const versionInfo = this.getVersionInfo(result.agreedVersion); if (versionInfo?.deprecated) { result.warnings.push( `Protocol version ${this.versionToString(result.agreedVersion)} is deprecated. ` + `Please upgrade to a newer version.`, ); } // Check for missing features const missingFeatures = this.getMissingFeatures( result.agreedVersion, result.agreedCapabilities, ); if (missingFeatures.length > 0) { result.limitations.push( `Some features may not be available: ${missingFeatures.join(', ')}`, ); } this.logger.info('Protocol negotiation completed', { agreedVersion: this.versionToString(result.agreedVersion), warnings: result.warnings.length, limitations: result.limitations.length, }); return result; } catch (error) { this.logger.error('Protocol negotiation failed', { clientVersion: this.versionToString(clientParams.protocolVersion), error, }); throw error; } } /** * Check compatibility between client and server versions */ checkCompatibility(clientVersion: MCPProtocolVersion): CompatibilityResult { const result: CompatibilityResult = { compatible: false, warnings: [], errors: [], }; const clientVersionInfo = this.getVersionInfo(clientVersion); const serverVersionInfo = this.getVersionInfo(this.currentVersion); // Check if version is known if (!clientVersionInfo) { result.errors.push(`Unknown protocol version: ${this.versionToString(clientVersion)}`); result.recommendedVersion = this.getLatestSupportedVersion(); return result; } // Check major version compatibility if (clientVersion.major !== this.currentVersion.major) { result.errors.push( `Major version mismatch: client ${clientVersion.major}, server ${this.currentVersion.major}`, ); return result; } // Check if client version is too new if (this.compareVersions(clientVersion, this.currentVersion) > 0) { result.errors.push( `Client version ${this.versionToString(clientVersion)} is newer than supported server version ${this.versionToString(this.currentVersion)}`, ); result.recommendedVersion = this.currentVersion; return result; } // Check for deprecated versions if (clientVersionInfo.deprecated) { result.warnings.push( `Client is using deprecated version ${this.versionToString(clientVersion)}. ` + `Support will be removed after ${clientVersionInfo.deprecationDate?.toISOString().split('T')[0]}`, ); result.recommendedVersion = this.getLatestSupportedVersion(); } // Check for missing features const serverFeatures = serverVersionInfo?.supportedFeatures || []; const clientFeatures = clientVersionInfo.supportedFeatures; const missingFeatures = serverFeatures.filter((feature) => !clientFeatures.includes(feature)); if (missingFeatures.length > 0) { result.missingFeatures = missingFeatures; result.warnings.push( `Client version lacks some server features: ${missingFeatures.join(', ')}`, ); } // Check for deprecated features being used const deprecatedFeatures = this.getDeprecatedFeatures(clientVersion); if (deprecatedFeatures.length > 0) { result.deprecatedFeatures = deprecatedFeatures; result.warnings.push( `Client version uses deprecated features: ${deprecatedFeatures.join(', ')}`, ); } result.compatible = true; return result; } /** * Get information about a specific protocol version */ getVersionInfo(version: MCPProtocolVersion): ProtocolVersionInfo | undefined { return this.supportedVersions.get(this.versionToString(version)); } /** * Check if a version is supported */ isVersionSupported(version: MCPProtocolVersion): boolean { return this.supportedVersions.has(this.versionToString(version)); } /** * Get the latest supported version */ getLatestSupportedVersion(): MCPProtocolVersion { const versions = Array.from(this.supportedVersions.values()) .filter((v) => !v.deprecated) .sort((a, b) => this.compareVersions(b.version, a.version)); return versions[0]?.version || { major: 2024, minor: 11, patch: 5 }; } /** * Get all supported version strings */ getSupportedVersionStrings(): string[] { return Array.from(this.supportedVersions.keys()); } /** * Get current server capabilities */ getServerCapabilities(): MCPCapabilities { return { ...this.serverCapabilities }; } /** * Update server capabilities */ updateServerCapabilities(capabilities: Partial<MCPCapabilities>): void { this.serverCapabilities = { ...this.serverCapabilities, ...capabilities }; this.logger.info('Server capabilities updated', { capabilities: this.serverCapabilities }); } /** * Check if a feature is supported in a specific version */ isFeatureSupported(version: MCPProtocolVersion, feature: string): boolean { const versionInfo = this.getVersionInfo(version); return versionInfo?.supportedFeatures.includes(feature) || false; } private versionToString(version: MCPProtocolVersion): string { return `${version.major}.${version.minor}.${version.patch}`; } private compareVersions(a: MCPProtocolVersion, b: MCPProtocolVersion): number { if (a.major !== b.major) return a.major - b.major; if (a.minor !== b.minor) return a.minor - b.minor; return a.patch - b.patch; } private getDefaultCapabilities(): MCPCapabilities { return { logging: { level: 'info', }, tools: { listChanged: true, }, resources: { listChanged: true, subscribe: false, }, prompts: { listChanged: true, }, }; } private negotiateCapabilities( clientCapabilities: MCPCapabilities, serverCapabilities: MCPCapabilities, agreedVersion: MCPProtocolVersion, ): MCPCapabilities { const result: MCPCapabilities = {}; // Negotiate logging capabilities if (clientCapabilities.logging && serverCapabilities.logging) { result.logging = { level: this.negotiateLogLevel( clientCapabilities.logging.level, serverCapabilities.logging.level, ), }; } // Negotiate tools capabilities if (clientCapabilities.tools && serverCapabilities.tools) { result.tools = { listChanged: clientCapabilities.tools.listChanged && serverCapabilities.tools.listChanged, }; } // Negotiate resources capabilities if (clientCapabilities.resources && serverCapabilities.resources) { result.resources = { listChanged: clientCapabilities.resources.listChanged && serverCapabilities.resources.listChanged, subscribe: clientCapabilities.resources.subscribe && serverCapabilities.resources.subscribe, }; } // Negotiate prompts capabilities if (clientCapabilities.prompts && serverCapabilities.prompts) { result.prompts = { listChanged: clientCapabilities.prompts.listChanged && serverCapabilities.prompts.listChanged, }; } // Only include capabilities supported by the agreed version return this.filterCapabilitiesByVersion(result, agreedVersion); } private negotiateLogLevel( clientLevel?: 'debug' | 'info' | 'warn' | 'error', serverLevel?: 'debug' | 'info' | 'warn' | 'error', ): 'debug' | 'info' | 'warn' | 'error' { const levels = ['debug', 'info', 'warn', 'error']; const clientIndex = clientLevel ? levels.indexOf(clientLevel) : 1; const serverIndex = serverLevel ? levels.indexOf(serverLevel) : 1; // Use the more restrictive (higher) level const chosenIndex = Math.max(clientIndex, serverIndex); return levels[chosenIndex] as 'debug' | 'info' | 'warn' | 'error'; } private filterCapabilitiesByVersion( capabilities: MCPCapabilities, version: MCPProtocolVersion, ): MCPCapabilities { const versionInfo = this.getVersionInfo(version); if (!versionInfo) return capabilities; const result: MCPCapabilities = {}; // Only include capabilities supported by this version if (versionInfo.supportedFeatures.includes('logging') && capabilities.logging) { result.logging = capabilities.logging; } if (versionInfo.supportedFeatures.includes('tools') && capabilities.tools) { result.tools = capabilities.tools; } if (versionInfo.supportedFeatures.includes('resources') && capabilities.resources) { result.resources = capabilities.resources; } if (versionInfo.supportedFeatures.includes('prompts') && capabilities.prompts) { result.prompts = capabilities.prompts; } return result; } private getMissingFeatures(version: MCPProtocolVersion, capabilities: MCPCapabilities): string[] { const versionInfo = this.getVersionInfo(version); if (!versionInfo) return []; const missing: string[] = []; const availableFeatures = versionInfo.supportedFeatures; // Check what's missing compared to latest version const latestVersion = this.getLatestSupportedVersion(); const latestVersionInfo = this.getVersionInfo(latestVersion); if (latestVersionInfo) { for (const feature of latestVersionInfo.supportedFeatures) { if (!availableFeatures.includes(feature)) { missing.push(feature); } } } return missing; } private getDeprecatedFeatures(version: MCPProtocolVersion): string[] { const versionInfo = this.getVersionInfo(version); return versionInfo?.breakingChanges || []; } }