UNPKG

@swoft/platform-contracts

Version:

DDD-compliant dependency injection contracts for Swoft platform - Defines clean architecture boundaries

390 lines (345 loc) 13 kB
/** * MCP Semantic Startup Enforcer * * MANDATORY startup protocol for all MCP servers to ensure they load and understand * their domain's semantic navigation before accepting any requests. * * This enforces the AGENT-STARTUP-PROTOCOL.md requirements with specific focus on * semantic navigation to prevent agents from "taking shortcuts" and missing context. * * @author Technical Coordinator & Derick * @since 2025-08-02 */ import { IDomainSemantics, IEntityRelationship, IWorkflowDefinition } from './SemanticNavigation'; import { readFile } from 'fs/promises'; import { join } from 'path'; /** * Startup validation result */ export interface SemanticStartupResult { success: boolean; domainId: string; entityRelationships: number; workflows: number; customSemantics: number; errors: string[]; warnings: string[]; } /** * Domain boundary information from DOMAIN-BOUNDARY-MAP.md */ interface DomainBoundary { domainId: string; domainRef: string; // e.g., DOM-001 packageName: string; services: string[]; boundedContexts: string[]; publishedLanguage: string[]; database?: string; } /** * Enforces semantic navigation loading at MCP server startup */ export class MCPSemanticStartupEnforcer { private domainId: string; private packageName: string; private semantics?: IDomainSemantics; private domainBoundary?: DomainBoundary; private startupResult: SemanticStartupResult; constructor(domainId: string, packageName: string) { this.domainId = domainId; this.packageName = packageName; this.startupResult = { success: false, domainId, entityRelationships: 0, workflows: 0, customSemantics: 0, errors: [], warnings: [], }; } /** * Execute mandatory startup sequence * Throws on critical errors, preventing server startup */ async enforceStartup(): Promise<SemanticStartupResult> { console.log(''); console.log('🚀 ========================================'); console.log('🚀 MCP Semantic Startup Protocol v1.0'); console.log(`🚀 Domain: ${this.domainId}`); console.log(`🚀 Package: ${this.packageName}`); console.log('🚀 ========================================'); console.log(''); try { // Step 1: Load Domain Boundary Map (from AGENT-STARTUP-PROTOCOL.md) console.log('📍 Step 1: Loading Domain Boundary Map...'); await this.loadDomainBoundaryMap(); // Step 2: Load Domain Semantic Navigation console.log('📍 Step 2: Loading Semantic Navigation...'); await this.loadSemanticNavigation(); // Step 3: Validate Semantic Understanding console.log('📍 Step 3: Validating Semantic Understanding...'); await this.validateSemanticUnderstanding(); // Step 4: Display Semantic Summary console.log('📍 Step 4: Semantic Navigation Summary...'); this.displaySemanticSummary(); this.startupResult.success = true; console.log(''); console.log('✅ ========================================'); console.log('✅ Semantic startup complete!'); console.log('✅ Ready to provide context-aware navigation'); console.log('✅ ========================================'); console.log(''); } catch (error: any) { this.startupResult.errors.push(error.message); console.error(''); console.error('❌ ========================================'); console.error('❌ FATAL: Semantic startup failed!'); console.error(`❌ Error: ${error.message}`); console.error('❌ Server CANNOT start without semantic context'); console.error('❌ ========================================'); console.error(''); throw error; } return this.startupResult; } /** * Load and parse Domain Boundary Map */ private async loadDomainBoundaryMap(): Promise<void> { const workspace = process.env.SWOFT_TEAM_WORKSPACE || process.cwd(); const mapPath = join(workspace, 'docs/architecture/DOMAIN-BOUNDARY-MAP.md'); try { const content = await readFile(mapPath, 'utf-8'); // Parse the domain boundary map to find our domain const domainInfo = this.parseDomainBoundaryMap(content); if (!domainInfo) { throw new Error( `Domain '${this.domainId}' not found in Domain Boundary Map. ` + `Expected format: DOM-XXX (e.g., DOM-001) or domain name (e.g., 'party-manager'). ` + `Check docs/architecture/DOMAIN-BOUNDARY-MAP.md for valid domains.` ); } this.domainBoundary = domainInfo; console.log(` ✅ Found domain ${domainInfo.domainRef}: ${domainInfo.domainId}`); console.log(` ✅ Bounded contexts: ${domainInfo.boundedContexts.length}`); console.log(` ✅ Published language terms: ${domainInfo.publishedLanguage.length}`); } catch (error: any) { throw new Error(`Failed to load Domain Boundary Map: ${error.message}`); } } /** * Parse domain boundary map markdown to extract domain info */ private parseDomainBoundaryMap(content: string): DomainBoundary | null { // Simple parser for the markdown structure // In production, this would be more robust console.log(` 🔍 Searching for domain: ${this.domainId}`); const lines = content.split('\n'); let currentDomain: Partial<DomainBoundary> | null = null; let foundDomain = false; for (const line of lines) { // Look for domain headers like "#### DOM-004: Party Manager Domain" const domainMatch = line.match(/^####\s+(DOM-\d+):\s+(.+)\s+Domain/); if (domainMatch) { const [, domainRef, domainName] = domainMatch; // Check if the domainId matches either the DOM-XXX format or the domain name if ( domainRef === this.domainId || (domainName && domainName.toLowerCase().includes(this.domainId.toLowerCase().replace('-', ' '))) ) { foundDomain = true; currentDomain = { domainRef, domainId: this.domainId, packageName: '', services: [], boundedContexts: [], publishedLanguage: [], }; } else { foundDomain = false; } } if (foundDomain && currentDomain) { // Extract package if (line.includes('**Package**:')) { const packageMatch = line.match(/`([^`]+)`/); if (packageMatch) { currentDomain.packageName = packageMatch[1]; } } // Extract bounded contexts if (line.includes('**Bounded Contexts**:')) { const contextsMatch = line.match(/contexts?\s*\(([^)]+)\)/); if (contextsMatch && contextsMatch[1]) { currentDomain.boundedContexts = contextsMatch[1].split(',').map((c) => c.trim()); } } // Extract published language if (line.includes('**Published Language**:')) { const termsMatch = line.match(/:\s*(.+)$/); if (termsMatch && termsMatch[1]) { currentDomain.publishedLanguage = termsMatch[1].split(',').map((t) => t.trim()); } } // Extract database if (line.includes('**Database**:')) { const dbMatch = line.match(/:\s*(.+)$/); if (dbMatch && dbMatch[1]) { currentDomain.database = dbMatch[1].trim(); } } } } return currentDomain as DomainBoundary | null; } /** * Load semantic navigation from domain package */ private async loadSemanticNavigation(): Promise<void> { try { // Try to dynamically import the semantic navigation // In real implementation, this would be more robust with proper error handling const semanticExports = await this.tryLoadSemantics(); if (!semanticExports || !semanticExports.domainSemantics) { // If dynamic import fails, create a minimal semantic definition console.log(' ⚠️ No semantic navigation found, creating minimal definition'); this.createMinimalSemantics(); return; } this.semantics = semanticExports.domainSemantics; // Count what we loaded if (this.semantics) { this.startupResult.entityRelationships = this.semantics.entityRelationships?.length || 0; this.startupResult.workflows = this.semantics.workflows?.length || 0; this.startupResult.customSemantics = this.semantics.customSemantics?.length || 0; } console.log(` ✅ Loaded ${this.startupResult.entityRelationships} entity relationships`); console.log(` ✅ Loaded ${this.startupResult.workflows} workflows`); console.log(` ✅ Loaded ${this.startupResult.customSemantics} custom semantic patterns`); } catch (error: any) { this.startupResult.warnings.push(`Could not load semantic navigation: ${error.message}`); console.log(' ⚠️ Using minimal semantic navigation'); this.createMinimalSemantics(); } } /** * Try to load semantics from various possible locations */ private async tryLoadSemantics(): Promise<any> { // In a real implementation, this would try multiple paths // For now, we'll return null to trigger minimal semantics return null; } /** * Create minimal semantic navigation when none exists */ private createMinimalSemantics(): void { this.semantics = { domainId: this.domainId as any, mcpServerId: `mcp-${this.domainId}` as any, entityRelationships: [], workflows: [], confidence: 0.5, lastUpdated: new Date().toISOString(), }; this.startupResult.warnings.push( 'Using minimal semantic navigation - domain should implement full semantics' ); } /** * Validate the MCP server understands its semantic context */ private async validateSemanticUnderstanding(): Promise<void> { const validations = [ { name: 'Domain Identity', check: () => this.domainBoundary !== null, error: 'Domain not found in Domain Boundary Map', }, { name: 'Semantic Navigation', check: () => this.semantics !== null, error: 'No semantic navigation loaded', }, { name: 'Published Language', check: () => (this.domainBoundary?.publishedLanguage?.length || 0) > 0, error: 'No published language terms defined', }, ]; for (const validation of validations) { if (!validation.check()) { throw new Error(`Validation failed for ${validation.name}: ${validation.error}`); } console.log(` ✅ Validated: ${validation.name}`); } } /** * Display semantic navigation summary */ private displaySemanticSummary(): void { console.log(''); console.log('📊 Semantic Navigation Summary:'); console.log('================================'); if (this.domainBoundary) { console.log(`Domain: ${this.domainBoundary.domainRef} - ${this.domainBoundary.domainId}`); console.log(`Package: ${this.domainBoundary.packageName}`); console.log(`Database: ${this.domainBoundary.database || 'Not specified'}`); console.log(''); console.log('Published Language Terms:'); this.domainBoundary.publishedLanguage.forEach((term) => { console.log(` - ${term}`); }); } if ( this.semantics && this.semantics.entityRelationships && this.semantics.entityRelationships.length > 0 ) { console.log(''); console.log('Entity Relationships:'); this.semantics.entityRelationships.slice(0, 3).forEach((rel) => { console.log(` - ${rel.sourceEntityType} ${rel.relationshipType} ${rel.targetEntityType}`); }); if (this.semantics.entityRelationships.length > 3) { console.log(` ... and ${this.semantics.entityRelationships.length - 3} more`); } } if (this.semantics && this.semantics.workflows && this.semantics.workflows.length > 0) { console.log(''); console.log('Available Workflows:'); this.semantics.workflows.slice(0, 3).forEach((wf) => { console.log(` - ${wf.name} (${wf.steps.length} steps)`); }); if (this.semantics.workflows.length > 3) { console.log(` ... and ${this.semantics.workflows.length - 3} more`); } } console.log('================================'); } /** * Get loaded semantic navigation for use by MCP server */ getSemantics(): IDomainSemantics | undefined { return this.semantics; } /** * Get domain boundary information */ getDomainBoundary(): DomainBoundary | undefined { return this.domainBoundary; } } /** * Factory function for easy creation */ export function createSemanticEnforcer( domainId: string, packageName: string ): MCPSemanticStartupEnforcer { return new MCPSemanticStartupEnforcer(domainId, packageName); }