diagram
Generate ASCII diagrams for flowcharts, boxes, trees, tables, sequence, timeline, bar, class, ER, mindmap, and gantt charts. Choose from unicode, rounded, or ascii border styles.
Instructions
Generate ASCII diagrams: flowcharts, boxes, trees, tables, sequence, timeline, bar, class, ER, mindmap, and gantt.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| type | Yes | Diagram type | |
| nodes | No | Flowchart: list of step labels | |
| title | No | Box: title text | |
| lines | No | Box: body lines | |
| root | No | Tree/Mindmap: root node with label and optional children | |
| headers | No | Table: column headers | |
| rows | No | Table: data rows | |
| style | No | Border style: unicode (default), rounded, or ascii | unicode |
| actors | No | Sequence: list of actor names | |
| messages | No | Sequence: messages between actors | |
| events | No | Timeline: list of events with label and description | |
| items | No | Bar: list of items with label and numeric value | |
| maxWidth | No | Bar: maximum bar width in characters (default 20) | |
| classes | No | Class: list of classes with name, properties, methods | |
| entities | No | ER: list of entities with name and attributes | |
| relationships | No | ER: relationships between entities (from, to, label like "1:N") | |
| tasks | No | Gantt: tasks with label, start position, and duration | |
| unitLabel | No | Gantt: label for time units (e.g. "weeks", "sprints") |
Implementation Reference
- src/mcp.ts:372-490 (registration)The 'diagram' tool is registered via server.tool() with Zod schema for all diagram types (flowchart, box, tree, table, sequence, timeline, bar, class, er, mindmap, gantt). The handler validates inputs then calls renderDiagram().
server.tool( 'diagram', 'Generate ASCII diagrams: flowcharts, boxes, trees, tables, sequence, timeline, bar, class, ER, mindmap, and gantt.', { type: z.enum(['flowchart', 'box', 'tree', 'table', 'sequence', 'timeline', 'bar', 'class', 'er', 'mindmap', 'gantt']).describe('Diagram type'), nodes: z.array(z.string()).max(MAX_DIAGRAM_NODES).optional().describe('Flowchart: list of step labels'), title: z.string().optional().describe('Box: title text'), lines: z.array(z.string()).optional().describe('Box: body lines'), root: z.lazy(() => treeNodeSchema).optional().describe('Tree/Mindmap: root node with label and optional children'), headers: z.array(z.string()).optional().describe('Table: column headers'), rows: z.array(z.array(z.string())).max(MAX_DIAGRAM_ROWS).optional().describe('Table: data rows'), style: z.enum(['unicode', 'rounded', 'ascii']).default('unicode').describe('Border style: unicode (default), rounded, or ascii'), actors: z.array(z.string()).max(MAX_SEQUENCE_ACTORS).optional().describe('Sequence: list of actor names'), messages: z.array(z.object({ from: z.string(), to: z.string(), label: z.string() })).max(MAX_SEQUENCE_MESSAGES).optional().describe('Sequence: messages between actors'), events: z.array(z.object({ label: z.string(), description: z.string() })).max(MAX_TIMELINE_EVENTS).optional().describe('Timeline: list of events with label and description'), items: z.array(z.object({ label: z.string(), value: z.number() })).max(MAX_BAR_ITEMS).optional().describe('Bar: list of items with label and numeric value'), maxWidth: z.number().min(1).max(MAX_BAR_WIDTH).optional().describe('Bar: maximum bar width in characters (default 20)'), classes: z.array(classDefSchema).max(MAX_DIAGRAM_NODES).optional().describe('Class: list of classes with name, properties, methods'), entities: z.array(entitySchema).max(MAX_DIAGRAM_NODES).optional().describe('ER: list of entities with name and attributes'), relationships: z.array(relationshipSchema).max(MAX_SEQUENCE_MESSAGES).optional().describe('ER: relationships between entities (from, to, label like "1:N")'), tasks: z.array(ganttTaskSchema).max(MAX_DIAGRAM_ROWS).optional().describe('Gantt: tasks with label, start position, and duration'), unitLabel: z.string().optional().describe('Gantt: label for time units (e.g. "weeks", "sprints")'), }, async ({ type, nodes, title, lines, root, headers, rows, style, actors, messages, events, items, maxWidth, classes, entities, relationships, tasks, unitLabel }) => { try { let input; switch (type) { case 'flowchart': if (!nodes || nodes.length === 0) { return { content: [{ type: 'text', text: 'Error: "nodes" is required for flowchart' }], isError: true }; } input = { type: 'flowchart' as const, nodes, style }; break; case 'box': if (!title) { return { content: [{ type: 'text', text: 'Error: "title" is required for box' }], isError: true }; } input = { type: 'box' as const, title, lines: lines ?? [], style }; break; case 'tree': if (!root) { return { content: [{ type: 'text', text: 'Error: "root" is required for tree' }], isError: true }; } if (!validateTreeDepth(root, 1)) { return { content: [{ type: 'text', text: `Error: tree depth exceeds maximum of ${MAX_TREE_DEPTH}` }], isError: true }; } input = { type: 'tree' as const, root }; break; case 'table': if (!headers || headers.length === 0) { return { content: [{ type: 'text', text: 'Error: "headers" is required for table' }], isError: true }; } input = { type: 'table' as const, headers, rows: rows ?? [], style }; break; case 'sequence': if (!actors || actors.length === 0) { return { content: [{ type: 'text', text: 'Error: "actors" is required for sequence' }], isError: true }; } if (!messages || messages.length === 0) { return { content: [{ type: 'text', text: 'Error: "messages" is required for sequence' }], isError: true }; } for (const msg of messages) { if (!actors.includes(msg.from)) { return { content: [{ type: 'text', text: `Error: actor "${msg.from}" not in actors list` }], isError: true }; } if (!actors.includes(msg.to)) { return { content: [{ type: 'text', text: `Error: actor "${msg.to}" not in actors list` }], isError: true }; } } input = { type: 'sequence' as const, actors, messages }; break; case 'timeline': if (!events || events.length === 0) { return { content: [{ type: 'text', text: 'Error: "events" is required for timeline' }], isError: true }; } input = { type: 'timeline' as const, events }; break; case 'bar': if (!items || items.length === 0) { return { content: [{ type: 'text', text: 'Error: "items" is required for bar' }], isError: true }; } input = { type: 'bar' as const, items, maxWidth }; break; case 'class': if (!classes || classes.length === 0) { return { content: [{ type: 'text', text: 'Error: "classes" is required for class diagram' }], isError: true }; } input = { type: 'class' as const, classes, style }; break; case 'er': if (!entities || entities.length === 0) { return { content: [{ type: 'text', text: 'Error: "entities" is required for ER diagram' }], isError: true }; } input = { type: 'er' as const, entities, relationships: relationships ?? [] }; break; case 'mindmap': if (!root) { return { content: [{ type: 'text', text: 'Error: "root" is required for mindmap' }], isError: true }; } if (!validateTreeDepth(root, 1)) { return { content: [{ type: 'text', text: `Error: tree depth exceeds maximum of ${MAX_TREE_DEPTH}` }], isError: true }; } input = { type: 'mindmap' as const, root }; break; case 'gantt': if (!tasks || tasks.length === 0) { return { content: [{ type: 'text', text: 'Error: "tasks" is required for gantt chart' }], isError: true }; } input = { type: 'gantt' as const, tasks, unitLabel }; break; } const diagram = renderDiagram(input); return { content: [{ type: 'text', text: diagram }] }; } catch (err: unknown) { const e = err as { message?: string }; return { content: [{ type: 'text', text: `Error: ${e.message ?? 'Diagram generation failed'}` }], isError: true }; } } ); - src/diagram.ts:426-451 (handler)The renderDiagram() function is the main dispatcher that routes to the specific render function based on the diagram type.
export function renderDiagram(input: DiagramInput): string { switch (input.type) { case 'flowchart': return renderFlowchart(input.nodes, input.style); case 'box': return renderBox(input.title, input.lines, input.style); case 'tree': return renderTree(input.root); case 'table': return renderTable(input.headers, input.rows, input.style); case 'sequence': return renderSequence(input.actors, input.messages); case 'timeline': return renderTimeline(input.events); case 'bar': return renderBar(input.items, input.maxWidth); case 'class': return renderClass(input.classes, input.style); case 'er': return renderER(input.entities, input.relationships); case 'mindmap': return renderMindmap(input.root); case 'gantt': return renderGantt(input.tasks, input.unitLabel); } } - src/diagram.ts:37-48 (schema)The DiagramInput type defines the discriminated union of all supported diagram input types with their required and optional fields.
export type DiagramInput = | { type: 'flowchart'; nodes: string[]; style?: BoxStyle } | { type: 'box'; title: string; lines: string[]; style?: BoxStyle } | { type: 'tree'; root: TreeNode } | { type: 'table'; headers: string[]; rows: string[][]; style?: BoxStyle } | { type: 'sequence'; actors: string[]; messages: SequenceMessage[] } | { type: 'timeline'; events: { label: string; description: string }[] } | { type: 'bar'; items: { label: string; value: number }[]; maxWidth?: number } | { type: 'class'; classes: ClassDef[]; style?: BoxStyle } | { type: 'er'; entities: Entity[]; relationships: Relationship[] } | { type: 'mindmap'; root: TreeNode } | { type: 'gantt'; tasks: GanttTask[]; unitLabel?: string }; - src/diagram.ts:89-111 (helper)renderFlowchart() - renders vertical flowcharts with connected boxes (one of the 11 diagram rendering helpers).
export function renderFlowchart(nodes: string[], style: BoxStyle = 'unicode'): string { if (nodes.length === 0) return ''; const c = BOX_CHARS[style]; const maxLen = Math.max(...nodes.map((n) => n.length)); const boxWidth = maxLen + 4; // 2 padding + 2 border const inner = boxWidth - 2; const centerCol = Math.floor(boxWidth / 2); const out: string[] = []; for (let i = 0; i < nodes.length; i++) { // box out.push(c.tl + c.t.repeat(inner) + c.tr); out.push(c.l + ' ' + padCenter(nodes[i], inner - 2) + ' ' + c.r); out.push(c.bl + c.b.repeat(Math.floor((inner - 1) / 2)) + (i < nodes.length - 1 ? c.mt : c.b) + c.b.repeat(inner - Math.floor((inner - 1) / 2) - 1) + c.br); // connector if (i < nodes.length - 1) { out.push(' '.repeat(centerCol) + c.l); out.push(' '.repeat(centerCol) + '\u25BC'); } } return out.join('\n'); } - src/mcp.ts:336-369 (helper)Helper Zod schemas: treeNodeSchema, classDefSchema, entitySchema, relationshipSchema, ganttTaskSchema, and validateTreeDepth() used by the diagram tool.
const treeNodeSchema: z.ZodType<TreeNode> = z.object({ label: z.string(), children: z.lazy(() => treeNodeSchema.array()).optional(), }); function validateTreeDepth(node: TreeNode, depth: number): boolean { if (depth > MAX_TREE_DEPTH) return false; for (const child of node.children ?? []) { if (!validateTreeDepth(child, depth + 1)) return false; } return true; } const classDefSchema = z.object({ name: z.string(), properties: z.array(z.string()).optional(), methods: z.array(z.string()).optional(), }); const entitySchema = z.object({ name: z.string(), attributes: z.array(z.string()).optional(), }); const relationshipSchema = z.object({ from: z.string(), to: z.string(), label: z.string(), }); const ganttTaskSchema = z.object({ label: z.string(), start: z.number().min(0), duration: z.number().min(1),