generate_diagram
Generate Excalidraw diagrams from natural language descriptions. Choose from flowchart, mindmap, sequence, architecture, ERD, or freeform diagrams.
Instructions
Generate an Excalidraw diagram from a natural-language description.
Args: description: What the diagram should show, e.g. "user login flow with OAuth and MFA" diagram_type: One of: flowchart, mindmap, sequence, architecture, erd, freeform filename: Output filename without extension (saved to ~/excalidraw_diagrams/)
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| description | Yes | ||
| diagram_type | No | flowchart | |
| filename | No | diagram |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- src/excalidraw_mcp/server.py:19-70 (handler)The main handler for the 'generate_diagram' tool. It is registered via @mcp.tool(), takes a description, diagram_type, and filename, calls the LLM to generate Excalidraw JSON, validates it, and saves it to disk.
@mcp.tool() async def generate_diagram( description: str, diagram_type: str = "flowchart", filename: str = "diagram", ) -> str: """Generate an Excalidraw diagram from a natural-language description. Args: description: What the diagram should show, e.g. "user login flow with OAuth and MFA" diagram_type: One of: flowchart, mindmap, sequence, architecture, erd, freeform filename: Output filename without extension (saved to ~/excalidraw_diagrams/) """ if not await check_llm_health(): return ( "ERROR: llama.cpp server is not running at localhost:8080.\n" "Start it with:\n" " ./build/bin/llama-server -m models/your-model.gguf --port 8080 -c 8192" ) try: raw = await generate_with_llm( system_prompt=SYSTEM_PROMPT, user_message=build_user_prompt(description, diagram_type), temperature=0.2, ) except Exception as e: return f"ERROR contacting llama.cpp: {type(e).__name__}: {e}" try: json_str = extract_json(raw) data = json.loads(json_str) data = validate_and_patch(data) except (ValueError, json.JSONDecodeError) as e: preview = raw[:600].replace("\n", " ") return ( f"ERROR: Could not parse LLM output as valid Excalidraw JSON.\n" f"Reason: {e}\n" f"LLM response preview: {preview}" ) try: filepath = save_excalidraw(data, filename) except Exception as e: return f"ERROR saving file: {e}" elem_count = len(data["elements"]) return ( f"Diagram saved to: {filepath}\n" f"Elements: {elem_count}\n" f"Open it in Excalidraw: File → Open → select the .excalidraw file" ) - src/excalidraw_mcp/server.py:16-16 (registration)The FastMCP server instance used to register the tool via the @mcp.tool() decorator.
mcp = FastMCP("excalidraw-mcp") - build_user_prompt constructs the user message sent to the LLM. It is called by the generate_diagram handler.
""" def build_user_prompt(description: str, diagram_type: str) -> str: - validate_and_patch validates and fills defaults for the Excalidraw JSON returned by the LLM. Called by generate_diagram.
def validate_and_patch(data: dict) -> dict: """Validate top-level structure and fill in any missing element defaults.""" if data.get("type") != "excalidraw": raise ValueError(f"Invalid type field: {data.get('type')!r}") if not isinstance(data.get("elements"), list): raise ValueError("Missing or invalid 'elements' array") data.setdefault("version", 2) data.setdefault("source", "https://excalidraw.com") data.setdefault("appState", {"viewBackgroundColor": "#ffffff"}) data.setdefault("files", {}) for i, elem in enumerate(data["elements"]): for f in _REQUIRED_ELEMENT_FIELDS: if f not in elem: raise ValueError(f"Element #{i} (id={elem.get('id')!r}) missing field '{f}'") for k, v in _ELEMENT_DEFAULTS.items(): elem.setdefault(k, v) return data - save_excalidraw saves the diagram JSON to ~/excalidraw_diagrams/. Called by generate_diagram.
def save_excalidraw(data: dict, filename: str) -> str: """Save an Excalidraw document to ~/excalidraw_diagrams/ and return the path.""" output_dir = Path.home() / "excalidraw_diagrams" output_dir.mkdir(parents=True, exist_ok=True) if not filename.endswith(".excalidraw"): filename += ".excalidraw" filepath = output_dir / filename with open(filepath, "w", encoding="utf-8") as fh: json.dump(data, fh, indent=2) return str(filepath)