Skip to main content
Glama
server.ts25.6 kB
/** * Archy MCP Server * * This file implements the core functionality of the Archy MCP server. * It handles the setup of the MCP server, defines the available tools, * and implements the logic for generating architectural diagrams. */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import { AxiosInstance } from 'axios'; import { createGitHubApiClient, fetchRepositoryData, extractRepoInfoFromUrl } from './utils/github-api.js'; import { generateDiagramFromText } from './generators/text-generator.js'; import { generateDiagramFromGithub } from './generators/github-generator.js'; import { generateDiagramFromTextWithAI, generateDiagramFromCode, generateDiffDiagram } from './generators/langchain-generator.js'; import { isApiConfigured } from './langchain/config.js'; import { exportDiagramInMemory, exportDiagramToDataUrl, ImageFormat } from './utils/image-exporter.js'; import { InMemoryGit } from './utils/git-memory.js'; import { validateMermaidSyntax, validateAndFixMermaidSyntax } from './utils/mermaid-validator.js'; import { addColorContrastDirective, applyAllStylingDirectives } from './utils/mermaid-styler.js'; // Define a generic type for all tool arguments type ToolArguments = Record<string, any>; /** * Archy MCP Server class */ export class ArchyServer { server: Server; axiosInstance: AxiosInstance; constructor(githubToken?: string) { this.server = new Server( { name: 'archy', version: '0.1.0', }, { capabilities: { tools: {}, }, } ); this.axiosInstance = createGitHubApiClient(githubToken); this.setupToolHandlers(); this.server.onerror = (error) => console.error('[MCP Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } setupToolHandlers() { const diagramTypes = [ 'flowchart', 'sequenceDiagram', 'classDiagram', 'stateDiagram', 'entityRelationshipDiagram', 'userJourney', 'gantt', 'pieChart', 'quadrantChart', 'requirementDiagram', 'gitGraph', 'c4Diagram' ]; const tools = [ { name: 'generate_diagram_from_text', description: 'Generate a Mermaid diagram from a text description', inputSchema: { type: 'object', properties: { description: { type: 'string', description: 'Text description of the diagram to generate', }, diagramType: { type: 'string', description: 'Type of diagram to generate (flowchart, class, sequence, etc.)', enum: diagramTypes } }, required: ['description', 'diagramType'], }, }, { name: 'generate_diagram_from_github', description: 'Generate a Mermaid diagram from a GitHub repository', inputSchema: { type: 'object', properties: { repoUrl: { type: 'string', description: 'URL of the GitHub repository', }, diagramType: { type: 'string', description: 'Type of diagram to generate (flowchart, class, sequence, etc.)', enum: diagramTypes } }, required: ['repoUrl', 'diagramType'], }, }, { name: 'list_supported_diagram_types', description: 'List all supported diagram types with descriptions', inputSchema: { type: 'object', properties: {}, required: [], }, } ]; if (isApiConfigured()) { tools.push( { name: 'generate_diagram_from_text_with_ai', description: 'Generate a Mermaid diagram from a text description using AI (LangChain with OpenRouter)', inputSchema: { type: 'object', properties: { description: { type: 'string', description: 'Text description of the diagram to generate', }, diagramType: { type: 'string', description: 'Type of diagram to generate (flowchart, class, sequence, etc.)', enum: diagramTypes } }, required: ['description', 'diagramType'], }, }, { name: 'generate_diagram_from_code', description: 'Generate a Mermaid diagram from code using AI', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'The code to analyze and generate a diagram from', }, diagramType: { type: 'string', description: 'Type of diagram to generate (flowchart, class, sequence, etc.)', enum: diagramTypes } }, required: ['code', 'diagramType'] } as any, }, { name: 'generate_diff_diagram', description: 'Generate a Mermaid diagram showing differences between two versions of code', inputSchema: { type: 'object', properties: { beforeCode: { type: 'string', description: 'The code before changes', }, afterCode: { type: 'string', description: 'The code after changes', }, diagramType: { type: 'string', description: 'Type of diagram to generate (flowchart, class, sequence, etc.)', enum: diagramTypes } }, required: ['beforeCode', 'afterCode', 'diagramType'], } as any, }, { name: 'export_diagram_to_image', description: 'Export a Mermaid diagram to an image format (PNG, SVG, or PDF)', inputSchema: { type: 'object', properties: { mermaidCode: { type: 'string', description: 'The Mermaid diagram code to export', }, format: { type: 'string', description: 'The image format to export to', enum: ['png', 'svg', 'pdf'], default: 'png' }, width: { type: 'number', description: 'The width of the image in pixels', default: 800 }, height: { type: 'number', description: 'The height of the image in pixels', default: 600 }, backgroundColor: { type: 'string', description: 'The background color of the image (CSS color or "transparent")', default: '#ffffff' } }, required: ['mermaidCode'], } as any, }, { name: 'generate_repository_evolution_diagram', description: 'Generate a diagram showing the evolution of a repository over time', inputSchema: { type: 'object', properties: { repoUrl: { type: 'string', description: 'URL of the GitHub repository', }, diagramType: { type: 'string', description: 'Type of diagram to generate (flowchart, class, sequence, etc.)', enum: diagramTypes }, filepath: { type: 'string', description: 'Path to a specific file to track (optional)', }, commitLimit: { type: 'number', description: 'Maximum number of commits to analyze', default: 10 } }, required: ['repoUrl', 'diagramType'], } as any, } ); } this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { switch (request.params.name) { case 'generate_diagram_from_text': return this.handleGenerateDiagramFromText(request.params.arguments); case 'generate_diagram_from_github': return this.handleGenerateDiagramFromGithub(request.params.arguments); case 'list_supported_diagram_types': return this.handleListSupportedDiagramTypes(); case 'generate_diagram_from_text_with_ai': return this.handleGenerateDiagramFromTextWithAI(request.params.arguments); case 'generate_diagram_from_code': return this.handleGenerateDiagramFromCode(request.params.arguments); case 'generate_diff_diagram': return this.handleGenerateDiffDiagram(request.params.arguments); case 'export_diagram_to_image': return this.handleExportDiagramToImage(request.params.arguments); case 'generate_repository_evolution_diagram': return this.handleGenerateRepositoryEvolutionDiagram(request.params.arguments); default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } }); } async handleGenerateDiagramFromText(args: ToolArguments): Promise<object> { if (!args.description || !args.diagramType) { throw new McpError( ErrorCode.InvalidParams, 'Missing required parameters: description and diagramType' ); } try { let mermaidCode = generateDiagramFromText(args.diagramType, args.description); // Validate and fix the Mermaid syntax const validationResult = await validateMermaidSyntax(mermaidCode); if (!validationResult.isValid) { console.warn(`Mermaid syntax validation failed: ${validationResult.error?.message}`); // Try to fix the syntax, passing the original description for context mermaidCode = await validateAndFixMermaidSyntax(mermaidCode, args.diagramType, args.description); } // Apply all styling directives for maximum visibility and clean layout mermaidCode = applyAllStylingDirectives(mermaidCode); return { content: [ { type: 'text', text: `Generated ${args.diagramType} diagram from text description with clean layout and optimal readability:\n\n\`\`\`mermaid\n${mermaidCode}\n\`\`\``, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error generating diagram: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } async handleGenerateDiagramFromGithub(args: ToolArguments): Promise<object> { if (!args.repoUrl || !args.diagramType) { throw new McpError( ErrorCode.InvalidParams, 'Missing required parameters: repoUrl and diagramType' ); } try { const [owner, repo] = extractRepoInfoFromUrl(args.repoUrl); const repoData = await fetchRepositoryData(this.axiosInstance, owner, repo); let mermaidCode = generateDiagramFromGithub(args.diagramType, owner, repo, repoData); // Validate and fix the Mermaid syntax const validationResult = await validateMermaidSyntax(mermaidCode); if (!validationResult.isValid) { console.warn(`Mermaid syntax validation failed: ${validationResult.error?.message}`); // Try to fix the syntax, passing repository info for context const repoContext = `GitHub repository: ${owner}/${repo}`; mermaidCode = await validateAndFixMermaidSyntax(mermaidCode, args.diagramType, repoContext); } // Apply all styling directives for maximum visibility and clean layout mermaidCode = applyAllStylingDirectives(mermaidCode); return { content: [ { type: 'text', text: `Generated ${args.diagramType} diagram for ${owner}/${repo} with clean layout and optimal readability:\n\n\`\`\`mermaid\n${mermaidCode}\n\`\`\``, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error generating diagram from GitHub: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } handleListSupportedDiagramTypes(): object { return { content: [ { type: 'text', text: ` # Supported Diagram Types 1. **flowchart** - Flowcharts for visualizing processes and workflows 2. **sequenceDiagram** - Sequence diagrams for showing interactions between components 3. **classDiagram** - Class diagrams for showing object-oriented structures 4. **stateDiagram** - State diagrams for showing state transitions 5. **entityRelationshipDiagram** - ER diagrams for database schema visualization 6. **userJourney** - User journey diagrams for mapping user experiences 7. **gantt** - Gantt charts for project planning and scheduling 8. **pieChart** - Pie charts for showing proportions 9. **quadrantChart** - Quadrant charts for categorizing items 10. **requirementDiagram** - Requirement diagrams for software requirements 11. **gitGraph** - Git graphs for visualizing Git workflows 12. **c4Diagram** - C4 diagrams for software architecture visualization `, }, ], }; } async handleGenerateDiagramFromTextWithAI(args: ToolArguments): Promise<object> { if (!args.description || !args.diagramType) { throw new McpError( ErrorCode.InvalidParams, 'Missing required parameters: description and diagramType' ); } try { if (!isApiConfigured()) { throw new Error('OpenRouter API key not configured. Cannot use AI-powered diagram generation.'); } let mermaidCode = await generateDiagramFromTextWithAI( args.diagramType, args.description, args.useAdvancedModel || false ); // Validate and fix the Mermaid syntax const validationResult = await validateMermaidSyntax(mermaidCode); if (!validationResult.isValid) { console.warn(`Mermaid syntax validation failed: ${validationResult.error?.message}`); // Try to fix the syntax, passing the original description for context mermaidCode = await validateAndFixMermaidSyntax(mermaidCode, args.diagramType, args.description); } // Apply all styling directives for maximum visibility and clean layout mermaidCode = applyAllStylingDirectives(mermaidCode); return { content: [ { type: 'text', text: `Generated ${args.diagramType} diagram from text description using AI with clean layout and optimal readability:\n\n\`\`\`mermaid\n${mermaidCode}\n\`\`\``, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error generating diagram with AI: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } async handleGenerateDiagramFromCode(args: ToolArguments): Promise<object> { if (!args.code || !args.diagramType) { throw new McpError( ErrorCode.InvalidParams, 'Missing required parameters: code and diagramType' ); } try { if (!isApiConfigured()) { throw new Error('OpenRouter API key not configured. Cannot use AI-powered code analysis.'); } let mermaidCode = await generateDiagramFromCode(args.diagramType, args.code); // Validate and fix the Mermaid syntax const validationResult = await validateMermaidSyntax(mermaidCode); if (!validationResult.isValid) { console.warn(`Mermaid syntax validation failed: ${validationResult.error?.message}`); // Try to fix the syntax, passing the original code for context mermaidCode = await validateAndFixMermaidSyntax(mermaidCode, args.diagramType, `Code context: ${args.code.substring(0, 200)}...`); } // Apply all styling directives for maximum visibility and clean layout mermaidCode = applyAllStylingDirectives(mermaidCode); return { content: [ { type: 'text', text: `Generated ${args.diagramType} diagram from code with clean layout and optimal readability:\n\n\`\`\`mermaid\n${mermaidCode}\n\`\`\``, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error generating diagram from code: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } async handleGenerateDiffDiagram(args: ToolArguments): Promise<object> { if (!args.beforeCode || !args.afterCode || !args.diagramType) { throw new McpError( ErrorCode.InvalidParams, 'Missing required parameters: beforeCode, afterCode, and diagramType' ); } try { if (!isApiConfigured()) { throw new Error('OpenRouter API key not configured. Cannot use AI-powered diff visualization.'); } let mermaidCode = await generateDiffDiagram( args.diagramType, args.beforeCode, args.afterCode ); // Validate and fix the Mermaid syntax const validationResult = await validateMermaidSyntax(mermaidCode); if (!validationResult.isValid) { console.warn(`Mermaid syntax validation failed: ${validationResult.error?.message}`); // Try to fix the syntax, passing diff context const diffContext = `Diff diagram showing changes between code versions`; mermaidCode = await validateAndFixMermaidSyntax(mermaidCode, args.diagramType, diffContext); } // Apply all styling directives for maximum visibility and clean layout mermaidCode = applyAllStylingDirectives(mermaidCode); return { content: [ { type: 'text', text: `Generated ${args.diagramType} diagram showing code differences with clean layout and optimal readability:\n\n\`\`\`mermaid\n${mermaidCode}\n\`\`\``, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error generating diff diagram: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } async handleExportDiagramToImage(args: ToolArguments): Promise<object> { if (!args.mermaidCode) { throw new McpError( ErrorCode.InvalidParams, 'Missing required parameter: mermaidCode' ); } try { const format = (args.format || 'png') as ImageFormat; const width = args.width || 800; const height = args.height || 600; const backgroundColor = args.backgroundColor || '#ffffff'; // Apply all styling directives for maximum visibility and clean layout const mermaidCodeWithStyling = applyAllStylingDirectives(args.mermaidCode); const dataUrl = await exportDiagramToDataUrl(mermaidCodeWithStyling, { format, width, height, backgroundColor }); return { content: [ { type: 'text', text: `Exported diagram as ${format.toUpperCase()}:`, }, { type: 'image', url: dataUrl, title: `Diagram exported as ${format.toUpperCase()}` } ], }; } catch (error) { return { content: [ { type: 'text', text: `Error exporting diagram to image: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } async handleGenerateRepositoryEvolutionDiagram(args: ToolArguments): Promise<object> { if (!args.repoUrl || !args.diagramType) { throw new McpError( ErrorCode.InvalidParams, 'Missing required parameters: repoUrl and diagramType' ); } try { const [owner, repo] = extractRepoInfoFromUrl(args.repoUrl); const commitLimit = args.commitLimit || 10; const git = new InMemoryGit(); await git.clone(args.repoUrl); const commits = await git.getCommits(commitLimit); let mermaidCode: string; let needsValidation = true; if (args.filepath) { const fileEvolution = await git.getFileEvolution(args.filepath, commitLimit); if (args.diagramType === 'gitGraph') { mermaidCode = `gitGraph commit id: "${fileEvolution[0]?.sha?.substring(0, 7) || 'initial'}" tag: "initial"`; for (let i = 1; i < fileEvolution.length; i++) { const commit = fileEvolution[i]; mermaidCode += ` commit id: "${commit.sha.substring(0, 7)}" tag: "${i}"`; } } else { if (fileEvolution.length >= 2) { const firstVersion = fileEvolution[fileEvolution.length - 1].content; const lastVersion = fileEvolution[0].content; mermaidCode = await generateDiffDiagram(args.diagramType, firstVersion, lastVersion); } else if (fileEvolution.length === 1) { mermaidCode = await generateDiagramFromCode(args.diagramType, fileEvolution[0].content); } else { throw new Error(`File ${args.filepath} not found in repository history`); } } } else { if (args.diagramType === 'gitGraph') { mermaidCode = `gitGraph commit id: "${commits[0]?.sha?.substring(0, 7) || 'initial'}" tag: "initial"`; for (let i = 1; i < commits.length; i++) { const commit = commits[i]; mermaidCode += ` commit id: "${commit.sha.substring(0, 7)}" tag: "${i}"`; } } else if (args.diagramType === 'flowchart') { mermaidCode = `flowchart TD subgraph "Repository Evolution" Start([Start]) --> Commit1[Commit: ${commits[commits.length - 1]?.sha?.substring(0, 7) || 'initial'}]`; for (let i = commits.length - 2; i >= 0; i--) { const commit = commits[i]; const nextCommit = commits[i + 1]; const filesAdded = commit.files.filter(f => f.type === 'add').length; const filesModified = commit.files.filter(f => f.type === 'modify').length; const filesDeleted = commit.files.filter(f => f.type === 'delete').length; mermaidCode += ` Commit${commits.length - i} --> Commit${commits.length - i + 1}[Commit: ${commit.sha.substring(0, 7)}] Commit${commits.length - i + 1} -- "+${filesAdded} -${filesDeleted} ~${filesModified}" --> Changes${commits.length - i + 1}[Changes]`; } mermaidCode += ` Commit${commits.length} --> End([End]) end`; } else { mermaidCode = `${args.diagramType} note "Repository evolution diagram for ${owner}/${repo} (${args.diagramType})" note "This diagram type is not yet fully implemented for repository evolution"`; } } // Validate and fix the Mermaid syntax if needed if (needsValidation) { const validationResult = await validateMermaidSyntax(mermaidCode); if (!validationResult.isValid) { console.warn(`Mermaid syntax validation failed: ${validationResult.error?.message}`); // Try to fix the syntax, passing repository info for context const repoContext = `Repository evolution diagram for ${owner}/${repo}${args.filepath ? ` (file: ${args.filepath})` : ''}`; mermaidCode = await validateAndFixMermaidSyntax(mermaidCode, args.diagramType, repoContext); } } // Apply all styling directives for maximum visibility and clean layout mermaidCode = applyAllStylingDirectives(mermaidCode); return { content: [ { type: 'text', text: `Generated ${args.diagramType} diagram showing evolution of ${owner}/${repo}${args.filepath ? ` (file: ${args.filepath})` : ''} with clean layout and optimal readability:\n\n\`\`\`mermaid\n${mermaidCode}\n\`\`\``, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error generating repository evolution diagram: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } async run(): Promise<void> { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Archy MCP server running on stdio'); } }

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/phxdev1/archy-mcp'

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