Files
drawio-mcp-server/src/tools/create-diagram.ts
Alejandro Lembke Barrientos 6aa7e91874
Some checks failed
Main Workflow / Security Audit (push) Successful in 4m39s
Main Workflow / Test and Build (20.x) (push) Failing after 4m56s
Main Workflow / Test and Build (18.x) (push) Failing after 5m9s
Main Workflow / Build Release Artifacts (push) Has been skipped
Main Workflow / Code Quality Check (push) Successful in 1m33s
Main Workflow / Notification (push) Failing after 21s
Adding as a Streamable HTTP MCP Server
2025-07-22 21:31:55 +00:00

344 lines
12 KiB
TypeScript

import { DiagramType, DiagramFormat, DiagramData } from '../types/diagram-types.js';
import { convertToDrawioXML, createDiagramFile, getDefaultStyle, generateId } from '../utils/drawio-converter.js';
import { analyzeDiagramDescription } from '../ai/diagram-analyzer.js';
import { generateSmartBPMNDiagram } from '../generators/smart-bpmn-generator.js';
import { generateSmartERDiagram } from '../generators/smart-er-generator.js';
import { generateSmartArchitectureDiagram } from '../generators/smart-architecture-generator.js';
import * as fs from 'fs';
import * as path from 'path';
// Functional types
type CreateDiagramInput = Readonly<{
name: string;
type: DiagramType;
format?: DiagramFormat;
description?: string;
outputPath?: string;
workspaceRoot?: string;
complexity?: 'simple' | 'detailed';
language?: string;
// Legacy parameters for backward compatibility
tasks?: readonly string[];
entities?: readonly string[];
classes?: readonly string[];
components?: readonly string[];
processes?: readonly string[];
processName?: string;
gatewayType?: 'exclusive' | 'parallel';
branches?: readonly (readonly string[])[];
beforeGateway?: readonly string[];
afterGateway?: readonly string[];
}>;
type CreateDiagramResult = Readonly<{
success: boolean;
filePath?: string;
content?: string;
message: string;
diagramType: DiagramType;
format: DiagramFormat;
}>;
// Pure function to create successful result
const createSuccessResult = (
filePath: string,
content: string,
diagramType: DiagramType,
format: DiagramFormat,
name: string
): CreateDiagramResult => ({
success: true,
filePath,
content,
message: `Successfully created ${diagramType} diagram: ${name}`,
diagramType,
format
});
// Pure function to create error result
const createErrorResult = (
error: unknown,
diagramType: DiagramType,
format: DiagramFormat
): CreateDiagramResult => ({
success: false,
message: `Failed to create diagram: ${error instanceof Error ? error.message : String(error)}`,
diagramType,
format
});
// Pure function to ensure directory exists
const ensureDirectoryExists = (dirPath: string): void => {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
};
// Pure function to write file
const writeFile = async (filePath: string, content: string): Promise<void> => {
const dir = path.dirname(filePath);
ensureDirectoryExists(dir);
await fs.promises.writeFile(filePath, content, 'utf8');
};
// Pure function to generate diagram using AI
const generateAIDiagram = async (input: CreateDiagramInput): Promise<DiagramData> => {
if (!input.description) {
throw new Error('Description is required for AI-powered diagram generation');
}
// Analyze the description
const analysis = await analyzeDiagramDescription(input.description);
// Generate diagram based on type
const preferences = {
complexity: input.complexity || 'detailed',
language: input.language || 'es'
};
switch (input.type) {
case DiagramType.BPMN_PROCESS:
case DiagramType.BPMN_COLLABORATION:
case DiagramType.BPMN_CHOREOGRAPHY:
return generateSmartBPMNDiagram(input.description, analysis, preferences);
case DiagramType.ER_DIAGRAM:
case DiagramType.DATABASE_SCHEMA:
case DiagramType.CONCEPTUAL_MODEL:
return generateSmartERDiagram(input.description, analysis, preferences);
case DiagramType.SYSTEM_ARCHITECTURE:
case DiagramType.MICROSERVICES:
case DiagramType.LAYERED_ARCHITECTURE:
case DiagramType.C4_CONTEXT:
case DiagramType.C4_CONTAINER:
case DiagramType.C4_COMPONENT:
case DiagramType.CLOUD_ARCHITECTURE:
case DiagramType.INFRASTRUCTURE:
return generateSmartArchitectureDiagram(input.description, analysis, preferences);
default:
// Fallback to basic diagram generation
return generateBasicDiagram(input);
}
};
// Pure function to generate basic diagram (fallback)
const generateBasicDiagram = (input: CreateDiagramInput): DiagramData => {
return {
elements: [{
id: 'basic-1',
type: 'rectangle',
label: input.name,
geometry: {
x: 100,
y: 100,
width: 200,
height: 100
},
style: 'rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;',
properties: {}
}],
connections: [],
metadata: {
type: input.type,
format: input.format || DiagramFormat.DRAWIO,
created: new Date().toISOString(),
modified: new Date().toISOString(),
version: '1.0'
}
};
};
// Pure function to generate legacy diagram (for backward compatibility)
const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
const elements: any[] = [];
const connections: any[] = [];
// Handle legacy parameters
if (input.tasks && input.tasks.length > 0) {
// Generate BPMN-like diagram from tasks
input.tasks.forEach((task, index) => {
elements.push({
id: `task-${index}`,
type: 'bpmn-task',
label: task,
geometry: {
x: 100,
y: 100 + (index * 120),
width: 120,
height: 80
},
style: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;',
properties: { isTask: true }
});
if (index > 0) {
connections.push({
id: `conn-${index}`,
source: `task-${index - 1}`,
target: `task-${index}`,
label: '',
style: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;',
properties: {}
});
}
});
} else if (input.entities && input.entities.length > 0) {
// Generate ER diagram from entities
input.entities.forEach((entity, index) => {
elements.push({
id: `entity-${index}`,
type: 'er-entity',
label: entity,
geometry: {
x: 100 + (index * 200),
y: 100,
width: 120,
height: 80
},
style: 'whiteSpace=wrap;html=1;align=center;fillColor=#e1d5e7;strokeColor=#9673a6;',
properties: { isEntity: true }
});
});
} else if (input.classes && input.classes.length > 0) {
// Generate UML class diagram from classes
input.classes.forEach((className, index) => {
elements.push({
id: `class-${index}`,
type: 'uml-class',
label: className,
geometry: {
x: 100 + (index * 200),
y: 100,
width: 160,
height: 120
},
style: 'swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=#dae8fc;strokeColor=#6c8ebf;',
properties: { isClass: true }
});
});
} else if (input.components && input.components.length > 0) {
// Generate architecture diagram from components
input.components.forEach((component, index) => {
elements.push({
id: `comp-${index}`,
type: 'architecture-component',
label: component,
geometry: {
x: 100 + (index % 2) * 300,
y: 100 + Math.floor(index / 2) * 150,
width: 200,
height: 100
},
style: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;',
properties: { isComponent: true }
});
});
} else {
// Fallback to basic diagram
return generateBasicDiagram(input);
}
return {
elements,
connections,
metadata: {
type: input.type,
format: input.format || DiagramFormat.DRAWIO,
created: new Date().toISOString(),
modified: new Date().toISOString(),
version: '1.0'
}
};
};
// Main function to create a new diagram
export const createDiagram = async (input: CreateDiagramInput): Promise<CreateDiagramResult> => {
try {
const format = input.format || DiagramFormat.DRAWIO;
const workspaceRoot = input.workspaceRoot || process.cwd();
// Generate diagram data
let diagramData: DiagramData;
if (input.description) {
// Use AI-powered generation
diagramData = await generateAIDiagram(input);
} else {
// Use legacy generation for backward compatibility
diagramData = generateLegacyDiagram(input);
}
// Convert to the requested format
const { content, filename } = createDiagramFile(diagramData, format, input.name);
// Determine output path
const outputDir = input.outputPath
? path.resolve(workspaceRoot, input.outputPath)
: workspaceRoot;
const filePath = path.join(outputDir, filename);
// Write file
await writeFile(filePath, content);
return createSuccessResult(filePath, content, input.type, format, input.name);
} catch (error) {
console.error('Error creating diagram:', error);
return createErrorResult(error, input.type, input.format || DiagramFormat.DRAWIO);
}
};
// Pure function to validate create diagram input
export const validateCreateDiagramInput = (input: any): input is CreateDiagramInput => {
return (
typeof input === 'object' &&
input !== null &&
typeof input.name === 'string' &&
Object.values(DiagramType).includes(input.type)
);
};
// Pure function to get supported diagram types
export const getSupportedDiagramTypes = (): readonly DiagramType[] => {
return Object.values(DiagramType);
};
// Pure function to get diagram type descriptions
const getDiagramTypeDescriptions = (): Readonly<Record<DiagramType, string>> => ({
[DiagramType.BPMN_PROCESS]: 'Business Process Model and Notation diagram for modeling business processes with AI-powered generation',
[DiagramType.BPMN_COLLABORATION]: 'BPMN collaboration diagram showing interactions between participants',
[DiagramType.BPMN_CHOREOGRAPHY]: 'BPMN choreography diagram for modeling message exchanges',
[DiagramType.UML_CLASS]: 'UML Class diagram showing classes, attributes, methods, and relationships',
[DiagramType.UML_SEQUENCE]: 'UML Sequence diagram showing object interactions over time',
[DiagramType.UML_USE_CASE]: 'UML Use Case diagram showing system functionality and user interactions',
[DiagramType.UML_ACTIVITY]: 'UML Activity diagram showing workflow and business processes',
[DiagramType.UML_STATE]: 'UML State diagram showing object states and transitions',
[DiagramType.UML_COMPONENT]: 'UML Component diagram showing software components and dependencies',
[DiagramType.UML_DEPLOYMENT]: 'UML Deployment diagram showing hardware and software deployment',
[DiagramType.ER_DIAGRAM]: 'Entity-Relationship diagram for database design with AI-powered generation',
[DiagramType.DATABASE_SCHEMA]: 'Database schema diagram showing tables and relationships',
[DiagramType.CONCEPTUAL_MODEL]: 'Conceptual data model diagram',
[DiagramType.NETWORK_TOPOLOGY]: 'Network topology diagram showing network infrastructure',
[DiagramType.INFRASTRUCTURE]: 'Infrastructure diagram showing system components',
[DiagramType.CLOUD_ARCHITECTURE]: 'Cloud architecture diagram showing cloud services and components',
[DiagramType.SYSTEM_ARCHITECTURE]: 'System architecture diagram showing high-level system design with AI-powered generation',
[DiagramType.MICROSERVICES]: 'Microservices architecture diagram',
[DiagramType.LAYERED_ARCHITECTURE]: 'Layered architecture diagram showing application layers',
[DiagramType.C4_CONTEXT]: 'C4 Context diagram showing system context',
[DiagramType.C4_CONTAINER]: 'C4 Container diagram showing application containers',
[DiagramType.C4_COMPONENT]: 'C4 Component diagram showing component details',
[DiagramType.FLOWCHART]: 'Flowchart diagram showing process flow',
[DiagramType.ORGCHART]: 'Organizational chart showing hierarchy',
[DiagramType.MINDMAP]: 'Mind map diagram for brainstorming and organizing ideas',
[DiagramType.WIREFRAME]: 'Wireframe diagram for UI/UX design',
[DiagramType.GANTT]: 'Gantt chart for project management and scheduling'
});
// Pure function to get description for a specific diagram type
export const getDiagramTypeDescription = (diagramType: DiagramType): string => {
const descriptions = getDiagramTypeDescriptions();
return descriptions[diagramType] || 'Generic diagram type';
};