export_diagram
Export .excalidraw diagrams to SVG or PNG images. Convert diagram files into portable image files without a browser or Excalidraw application.
Instructions
Export an .excalidraw file to SVG or PNG image.
Converts an existing .excalidraw diagram into a portable image file without requiring a browser or the Excalidraw application.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| input_path | Yes | Path to the source .excalidraw file. | |
| output_path | Yes | Destination file path (e.g., "./arch.svg" or "./arch.png"). | |
| format | No | Output format - "svg" (default) or "png". PNG export requires the cairosvg package (``pip install cairosvg``). | svg |
| scale | No | Resolution multiplier for PNG output (default 2.0 = 2×). Has no effect on SVG output. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- src/excalidraw_mcp/server.py:311-351 (handler)The @mcp.tool handler for export_diagram — validates format, dispatches to export_to_svg or export_to_png, and returns a result string.
@mcp.tool def export_diagram( input_path: str, output_path: str, format: str = "svg", scale: float = 2.0, ) -> str: """Export an .excalidraw file to SVG or PNG image. Converts an existing .excalidraw diagram into a portable image file without requiring a browser or the Excalidraw application. Args: input_path: Path to the source .excalidraw file. output_path: Destination file path (e.g., "./arch.svg" or "./arch.png"). format: Output format - "svg" (default) or "png". PNG export requires the cairosvg package (``pip install cairosvg``). scale: Resolution multiplier for PNG output (default 2.0 = 2×). Has no effect on SVG output. Returns: Path to the exported image file. """ fmt = format.lower().strip() if fmt not in ("svg", "png"): return f"Error: unsupported format '{format}'. Choose 'svg' or 'png'." try: if fmt == "svg": out = export_to_svg(input_path, output_path) return f"Exported SVG to: {out}" else: out = export_to_png(input_path, output_path, scale=scale) return f"Exported PNG ({scale}× scale) to: {out}" except FileNotFoundError: return f"Error: file not found: {input_path}" except ImportError as exc: return f"Error: {exc}" except Exception as exc: # noqa: BLE001 return f"Error during export: {exc}" - export_to_svg — reads an .excalidraw file, converts to SVG via excalidraw_to_svg(), and writes the output file.
def export_to_svg(input_path: str | Path, output_path: str | Path) -> Path: """Read an .excalidraw file and write a .svg file.""" data = json.loads(Path(input_path).read_text(encoding="utf-8")) svg = excalidraw_to_svg(data) out = Path(output_path) out.parent.mkdir(parents=True, exist_ok=True) out.write_text(svg, encoding="utf-8") return out - export_to_png — converts an .excalidraw file to PNG using cairosvg (optional dependency), writing the output file at the given scale.
def export_to_png(input_path: str | Path, output_path: str | Path, scale: float = 2.0) -> Path: """Read an .excalidraw file and write a .png file. Requires the ``cairosvg`` package (``pip install cairosvg``). """ try: import cairosvg # type: ignore[import] except ImportError as exc: raise ImportError( "PNG export requires cairosvg. Install it with: pip install cairosvg" ) from exc data = json.loads(Path(input_path).read_text(encoding="utf-8")) svg = excalidraw_to_svg(data) out = Path(output_path) out.parent.mkdir(parents=True, exist_ok=True) cairosvg.svg2png(bytestring=svg.encode(), write_to=str(out), scale=scale) return out - excalidraw_to_svg — core converter: computes bounding box, renders shapes (rect, ellipse, diamond), arrows, and text into an SVG string.
def excalidraw_to_svg(data: dict[str, Any]) -> str: """Convert an in-memory .excalidraw document to an SVG string.""" elements = [e for e in data.get("elements", []) if not e.get("isDeleted")] bg_color = data.get("appState", {}).get("viewBackgroundColor", "#ffffff") if not elements: return ( '<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300">' f'<rect width="400" height="300" fill="{_esc(bg_color)}"/>' "</svg>" ) min_x, min_y, max_x, max_y = _bounds(elements) ox = min_x - PADDING oy = min_y - PADDING width = max_x - min_x + PADDING * 2 height = max_y - min_y + PADDING * 2 # Two-pass rendering: shapes first, then text on top arrow_types = {"arrow", "line"} shape_svgs: list[str] = [] arrow_svgs: list[str] = [] text_svgs: list[str] = [] used_colors: set[str] = set() for el in elements: el_type = el.get("type", "") if el_type == "rectangle": shape_svgs.append(_render_rect(el, ox, oy)) elif el_type == "ellipse": shape_svgs.append(_render_ellipse(el, ox, oy)) elif el_type == "diamond": shape_svgs.append(_render_diamond(el, ox, oy)) elif el_type in arrow_types: arrow_svgs.append(_render_arrow(el, ox, oy, used_colors)) elif el_type == "text": text_svgs.append(_render_text(el, ox, oy)) markers = "\n ".join(_arrowhead_marker(c) for c in sorted(used_colors)) defs = f"<defs>\n {markers}\n </defs>" if markers else "" parts = [ f'<svg xmlns="http://www.w3.org/2000/svg" ' f'width="{width:.2f}" height="{height:.2f}" ' f'viewBox="0 0 {width:.2f} {height:.2f}">', defs, f'<rect width="{width:.2f}" height="{height:.2f}" fill="{_esc(bg_color)}"/>', ] parts.extend(shape_svgs) parts.extend(arrow_svgs) parts.extend(text_svgs) parts.append("</svg>") return "\n".join(p for p in parts if p) - src/excalidraw_mcp/server.py:36-43 (registration)FastMCP server instantiation — the @mcp.tool decorator on export_diagram registers it as an MCP tool.
mcp = FastMCP( "Excalidraw Architect", instructions=( "Generate beautiful Excalidraw architecture diagrams with perfect " "auto-layout, stateful editing, and architecture-aware component " "styling. No API keys required." ), )