Skip to main content
Glama
architectural-context-resource.ts16.1 kB
/** * Architectural Context Resource - Current architectural state and decisions * URI Pattern: adr://architecture/context */ import { URLSearchParams } from 'url'; import { McpAdrError } from '../types/index.js'; import { resourceCache, generateETag } from './resource-cache.js'; import { ResourceGenerationResult } from './index.js'; import { resourceRouter } from './resource-router.js'; import type { KnowledgeGraphManager } from '../utils/knowledge-graph-manager.js'; export interface ArchitecturalContextData { summary: { totalAdrs: number; activeDecisions: number; deprecatedDecisions: number; proposedDecisions: number; technologiesUsed: string[]; architecturalPatterns: string[]; }; decisions: Array<{ id: string; title: string; status: string; category: string; impact: 'high' | 'medium' | 'low'; dependencies: string[]; }>; technologyStack: { frontend: string[]; backend: string[]; database: string[]; infrastructure: string[]; devops: string[]; }; patterns: Array<{ name: string; description: string; usedIn: string[]; relatedAdrs: string[]; }>; recommendations: string[]; timestamp: string; projectPath: string; } /** * Generate architectural context resource showing current state and decisions. * * Returns a comprehensive view of the project's architectural state: * - Summary of all ADRs by status * - Technology stack analysis * - Architectural patterns in use * - Recommendations for improvements * * **URI Pattern:** `adr://architecture/context` * * **Query Parameters:** * - `projectPath`: Override project root path (default: process.cwd()) * - `includeDeprecated`: Include deprecated decisions (default: true) * - `includeProposed`: Include proposed decisions (default: true) * - `maxDecisions`: Maximum decisions to return (default: 50) * * @param params - URL path parameters (none for this resource) * @param searchParams - URL query parameters for customization * @param kgManager - Optional knowledge graph manager instance * * @returns Promise resolving to resource generation result containing: * - data: Complete architectural context data * - contentType: "application/json" * - lastModified: ISO timestamp of generation * - cacheKey: Unique identifier "architectural-context" * - ttl: Cache duration (300 seconds / 5 minutes) * - etag: Entity tag for cache validation * * @throws {McpAdrError} When context generation fails * * @example * ```typescript * // Get full architectural context * const context = await generateArchitecturalContextResource( * {}, * new URLSearchParams() * ); * * console.log(`Total ADRs: ${context.data.summary.totalAdrs}`); * console.log(`Technologies: ${context.data.summary.technologiesUsed.join(', ')}`); * * // Exclude deprecated decisions * const active = await generateArchitecturalContextResource( * {}, * new URLSearchParams('includeDeprecated=false') * ); * ``` * * @since v2.2.0 * @see {@link KnowledgeGraphManager} for knowledge graph queries */ export async function generateArchitecturalContextResource( _params: Record<string, string>, searchParams: URLSearchParams, kgManager?: KnowledgeGraphManager ): Promise<ResourceGenerationResult> { const projectPath = searchParams.get('projectPath') || process.cwd(); const includeDeprecated = searchParams.get('includeDeprecated') !== 'false'; const includeProposed = searchParams.get('includeProposed') !== 'false'; const maxDecisions = parseInt(searchParams.get('maxDecisions') || '50', 10); const cacheKey = 'architectural-context'; // Check cache const cached = await resourceCache.get<ResourceGenerationResult>(cacheKey); if (cached) { return cached; } try { // Discover ADRs to build context const { discoverAdrsInDirectory } = await import('../utils/adr-discovery.js'); const pathModule = await import('path'); const adrDirectory = pathModule.resolve( projectPath, process.env['ADR_DIRECTORY'] || 'docs/adrs' ); const discoveryResult = await discoverAdrsInDirectory(adrDirectory, projectPath, { includeContent: true, includeTimeline: false, }); // Categorize ADRs by status const statusCounts = { accepted: 0, deprecated: 0, proposed: 0, superseded: 0, unknown: 0, }; const technologies = new Set<string>(); const patterns = new Set<string>(); const decisions: ArchitecturalContextData['decisions'] = []; for (const adr of discoveryResult.adrs) { const status = (adr.status || 'unknown').toLowerCase(); if (status in statusCounts) { statusCounts[status as keyof typeof statusCounts]++; } else { statusCounts.unknown++; } // Skip deprecated if not requested if (!includeDeprecated && status === 'deprecated') continue; if (!includeProposed && status === 'proposed') continue; // Extract technologies from content const content = `${adr.context || ''} ${adr.decision || ''} ${adr.consequences || ''}`; extractTechnologies(content, technologies); extractPatterns(content, patterns); // Add to decisions list if (decisions.length < maxDecisions) { decisions.push({ id: adr.filename.replace(/\.md$/, ''), title: adr.title || 'Untitled', status: adr.status || 'unknown', category: categorizeAdr(adr.title || '', content), impact: determineImpact(content), dependencies: extractDependencies(content), }); } } // Build technology stack const technologyStack = categorizeTechnologies([...technologies]); // Build patterns list const patternsList = [...patterns].map(pattern => ({ name: pattern, description: getPatternDescription(pattern), usedIn: findPatternUsage(pattern, discoveryResult.adrs), relatedAdrs: findRelatedAdrs(pattern, discoveryResult.adrs), })); // Generate recommendations const recommendations = generateRecommendations(discoveryResult.adrs, statusCounts, [ ...technologies, ]); // Get additional context from knowledge graph if available if (kgManager) { try { const kg = await kgManager.loadKnowledgeGraph(); if (kg && kg.intents) { // Extract technologies and patterns from intent goals kg.intents.forEach((intent: any) => { if (intent.parsedGoals) { intent.parsedGoals.forEach((goal: string) => { // Simple heuristic to extract tech/pattern mentions const lowerGoal = goal.toLowerCase(); if (lowerGoal.includes('technology') || lowerGoal.includes('framework')) { // Could extract specific technologies here } }); } }); } } catch { // Knowledge graph not available } } const contextData: ArchitecturalContextData = { summary: { totalAdrs: discoveryResult.totalAdrs, activeDecisions: statusCounts.accepted, deprecatedDecisions: statusCounts.deprecated, proposedDecisions: statusCounts.proposed, technologiesUsed: [...technologies].sort(), architecturalPatterns: [...patterns].sort(), }, decisions, technologyStack, patterns: patternsList, recommendations, timestamp: new Date().toISOString(), projectPath, }; const result: ResourceGenerationResult = { data: contextData, contentType: 'application/json', lastModified: new Date().toISOString(), cacheKey, ttl: 300, // 5 minutes cache etag: generateETag(contextData), }; // Cache result resourceCache.set(cacheKey, result, result.ttl); return result; } catch (error) { throw new McpAdrError( `Failed to generate architectural context: ${error instanceof Error ? error.message : String(error)}`, 'RESOURCE_GENERATION_ERROR' ); } } /** * Extract technologies from content */ function extractTechnologies(content: string, technologies: Set<string>): void { const techPatterns = [ /\b(React|Vue|Angular|Svelte|Next\.js|Nuxt|Gatsby)\b/gi, /\b(Node\.js|Express|Fastify|NestJS|Koa)\b/gi, /\b(Python|Django|Flask|FastAPI)\b/gi, /\b(PostgreSQL|MySQL|MongoDB|Redis|Elasticsearch)\b/gi, /\b(Docker|Kubernetes|AWS|Azure|GCP)\b/gi, /\b(Terraform|Ansible|Jenkins|GitHub Actions)\b/gi, /\b(GraphQL|REST|gRPC|WebSocket)\b/gi, /\b(TypeScript|JavaScript|Java|Go|Rust)\b/gi, ]; techPatterns.forEach(pattern => { const matches = content.match(pattern); if (matches) { matches.forEach(match => technologies.add(match)); } }); } /** * Extract architectural patterns from content */ function extractPatterns(content: string, patterns: Set<string>): void { const patternKeywords = [ 'microservices', 'monolith', 'event-driven', 'cqrs', 'event sourcing', 'hexagonal', 'clean architecture', 'mvc', 'mvvm', 'repository pattern', 'service layer', 'domain-driven', 'api gateway', 'saga pattern', 'circuit breaker', ]; const lowerContent = content.toLowerCase(); patternKeywords.forEach(pattern => { if (lowerContent.includes(pattern)) { patterns.add( pattern .split(' ') .map(w => w.charAt(0).toUpperCase() + w.slice(1)) .join(' ') ); } }); } /** * Categorize an ADR based on its title and content */ function categorizeAdr(title: string, content: string): string { const lowerTitle = title.toLowerCase(); const lowerContent = content.toLowerCase(); if (lowerTitle.includes('database') || lowerContent.includes('database')) return 'data'; if (lowerTitle.includes('api') || lowerContent.includes('api endpoint')) return 'api'; if (lowerTitle.includes('auth') || lowerContent.includes('authentication')) return 'security'; if (lowerTitle.includes('deploy') || lowerContent.includes('deployment')) return 'infrastructure'; if (lowerTitle.includes('test') || lowerContent.includes('testing')) return 'testing'; if (lowerTitle.includes('ui') || lowerTitle.includes('frontend')) return 'frontend'; if (lowerTitle.includes('backend') || lowerTitle.includes('service')) return 'backend'; return 'general'; } /** * Determine impact level of an ADR */ function determineImpact(content: string): 'high' | 'medium' | 'low' { const lowerContent = content.toLowerCase(); // High impact indicators if ( lowerContent.includes('critical') || lowerContent.includes('breaking change') || lowerContent.includes('major refactor') || lowerContent.includes('security vulnerability') ) { return 'high'; } // Low impact indicators if ( lowerContent.includes('minor') || lowerContent.includes('small change') || lowerContent.includes('optimization') ) { return 'low'; } return 'medium'; } /** * Extract dependencies from content */ function extractDependencies(content: string): string[] { const deps: string[] = []; const depMatch = content.match(/ADR[-\s]?(\d+)/gi); if (depMatch) { depMatch.forEach(match => { const id = match.replace(/ADR[-\s]?/i, ''); deps.push(`adr-${id.padStart(3, '0')}`); }); } return [...new Set(deps)]; } /** * Categorize technologies into stack layers */ function categorizeTechnologies(techs: string[]): ArchitecturalContextData['technologyStack'] { const stack: ArchitecturalContextData['technologyStack'] = { frontend: [], backend: [], database: [], infrastructure: [], devops: [], }; const categories: Record<string, keyof typeof stack> = { React: 'frontend', Vue: 'frontend', Angular: 'frontend', Svelte: 'frontend', 'Next.js': 'frontend', Nuxt: 'frontend', TypeScript: 'frontend', JavaScript: 'frontend', 'Node.js': 'backend', Express: 'backend', Fastify: 'backend', NestJS: 'backend', Python: 'backend', Django: 'backend', Flask: 'backend', FastAPI: 'backend', Java: 'backend', Go: 'backend', Rust: 'backend', PostgreSQL: 'database', MySQL: 'database', MongoDB: 'database', Redis: 'database', Elasticsearch: 'database', Docker: 'infrastructure', Kubernetes: 'infrastructure', AWS: 'infrastructure', Azure: 'infrastructure', GCP: 'infrastructure', Terraform: 'devops', Ansible: 'devops', Jenkins: 'devops', 'GitHub Actions': 'devops', }; techs.forEach(tech => { const category = categories[tech]; if (category && !stack[category].includes(tech)) { stack[category].push(tech); } }); return stack; } /** * Get description for a pattern */ function getPatternDescription(pattern: string): string { const descriptions: Record<string, string> = { Microservices: 'Distributed architecture with independently deployable services', Monolith: 'Single deployable unit containing all functionality', 'Event-driven': 'Asynchronous communication through events', CQRS: 'Command Query Responsibility Segregation', 'Event Sourcing': 'Storing state as sequence of events', Hexagonal: 'Ports and adapters architecture', 'Clean Architecture': 'Dependency rule with concentric layers', MVC: 'Model-View-Controller pattern', MVVM: 'Model-View-ViewModel pattern', 'Repository Pattern': 'Abstraction layer for data access', 'Service Layer': 'Business logic encapsulation', 'Domain-driven': 'Design based on domain model', 'Api Gateway': 'Single entry point for microservices', 'Saga Pattern': 'Distributed transaction management', 'Circuit Breaker': 'Fault tolerance pattern', }; return descriptions[pattern] || 'Architectural pattern'; } /** * Find ADRs that use a pattern */ function findPatternUsage(pattern: string, adrs: any[]): string[] { const lowerPattern = pattern.toLowerCase(); return adrs .filter(adr => { const content = `${adr.title || ''} ${adr.context || ''} ${adr.decision || ''}`.toLowerCase(); return content.includes(lowerPattern); }) .map(adr => adr.filename.replace(/\.md$/, '')) .slice(0, 5); } /** * Find ADRs related to a pattern */ function findRelatedAdrs(pattern: string, adrs: any[]): string[] { return findPatternUsage(pattern, adrs); } /** * Generate recommendations based on analysis */ function generateRecommendations( adrs: any[], statusCounts: Record<string, number>, technologies: string[] ): string[] { const recommendations: string[] = []; // Check for proposed ADRs that need review const proposedCount = statusCounts['proposed'] || 0; if (proposedCount > 3) { recommendations.push(`Review ${proposedCount} proposed ADRs for acceptance`); } // Check for missing documentation areas const hasSecurityAdr = adrs.some( adr => (adr.title || '').toLowerCase().includes('security') || (adr.title || '').toLowerCase().includes('auth') ); if (!hasSecurityAdr) { recommendations.push('Consider documenting security decisions in an ADR'); } // Check for deprecated decisions const deprecatedCount = statusCounts['deprecated'] || 0; if (deprecatedCount > 0) { recommendations.push(`Review ${deprecatedCount} deprecated ADRs for removal or updates`); } // Technology recommendations if (technologies.length < 5) { recommendations.push('Consider documenting technology choices in ADRs'); } // General recommendation if (adrs.length < 3) { recommendations.push('Consider creating ADRs for key architectural decisions'); } if (recommendations.length === 0) { recommendations.push('Architectural documentation appears up-to-date'); } return recommendations; } // Register route resourceRouter.register( '/architecture/context', generateArchitecturalContextResource, 'Current architectural state and decisions' );

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tosin2013/mcp-adr-analysis-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server