Skip to main content

excalidraw-mcp-server

Build and deploy MCP servers that stream interactive Excalidraw diagrams with smooth viewport control

Zur Installation springen

Quellinformationen

Repository
reason-machines/mcp-skills
Letzte Quellaktivität
16. Mai 2026 um 16:49
Erkannte Sprache von SKILL.md
Englisch
Sterne
7
Forks
2

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
excalidraw-mcp-server
description
Build and deploy MCP servers that stream interactive Excalidraw diagrams with smooth viewport control
triggers
["create an excalidraw diagram in my MCP server","add excalidraw visualization to this tool","stream a hand-drawn diagram using MCP","integrate excalidraw with model context protocol","build an interactive diagram server","render excalidraw in my AI app","create MCP app with diagrams","add visual whiteboard to my MCP tool"]
# Excalidraw MCP Server Skill > Skill by [ara.so](https://ara.so) — MCP Skills collection. This skill teaches AI agents how to build MCP servers that return interactive Excalidraw diagrams using the [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps). Excalidraw MCP enables streaming hand-drawn style diagrams directly into AI chat interfaces like Claude Desktop, ChatGPT, VS Code, and Goose. ## What It Does The Excalidraw MCP server provides: - **Interactive diagram rendering**: Returns HTML apps that render Excalidraw canvases - **Streaming updates**: Supports progressive diagram building with viewport control - **Fullscreen editing**: Users can click to edit diagrams in a modal - **Remote & local deployment**: Run as Vercel serverless function or local Node.js process ## Installation ### Remote Server (Recommended) Use the hosted version at `https://mcp.excalidraw.com`: ```json { "mcpServers": { "excalidraw": { "url": "https://mcp.excalidraw.com" } } } ``` ### Local Installation **From source:** ```bash git clone https://github.com/excalidraw/excalidraw-mcp.git cd excalidraw-mcp-app pnpm install && pnpm run build ``` **Configure in Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`): ```json { "mcpServers": { "excalidraw": { "command": "node", "args": ["/path/to/excalidraw-mcp-app/dist/index.js", "--stdio"] } } } ``` **From release:** 1. Download `excalidraw-mcp-app.mcpb` from [releases](https://github.com/excalidraw/excalidraw-mcp/releases) 2. Double-click to install in Claude Desktop ## Core Concepts ### MCP Apps Extension MCP Apps is an official extension that lets servers return interactive HTML instead of just text. The response format: ```typescript { content: [ { type: "resource", resource: { uri: "excalidraw://diagram", mimeType: "text/html", text: "<html>...</html>" } } ] } ``` ### Excalidraw Scene Format Excalidraw diagrams are JSON with `elements` and `appState`: ```typescript interface ExcalidrawScene { type: "excalidraw"; version: 2; source: string; elements: ExcalidrawElement[]; appState: { viewBackgroundColor: string; currentItemFontFamily?: number; // ... viewport state }; files?: Record<string, BinaryFileData>; } ``` ## Building an Excalidraw MCP Server ### Basic Server Structure ```typescript import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; const server = new Server( { name: "excalidraw-server", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: "draw_diagram", description: "Create an interactive Excalidraw diagram", inputSchema: { type: "object", properties: { elements: { type: "array", description: "Excalidraw elements (rectangles, arrows, text, etc.)", }, description: { type: "string", description: "What the diagram shows", }, }, required: ["elements"], }, }, ], })); server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name === "draw_diagram") { const { elements, description } = request.params.arguments as any; const html = generateExcalidrawHTML(elements, description); return { content: [ { type: "resource", resource: { uri: `excalidraw://diagram-${Date.now()}`, mimeType: "text/html", text: html, }, }, ], }; } throw new Error("Unknown tool"); }); const transport = new StdioServerTransport(); await server.connect(transport); ``` ### Generating the HTML Response ```typescript function generateExcalidrawHTML( elements: any[], description?: string ): string { const scene = { type: "excalidraw", version: 2, source: "mcp-server", elements: elements, appState: { viewBackgroundColor: "#ffffff", currentItemFontFamily: 1, }, files: {}, }; const sceneJSON = JSON.stringify(scene); return `<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>${description || "Excalidraw Diagram"}</title> <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.production.min.js"></script> <style> body { margin: 0; padding: 0; overflow: hidden; } #app { width: 100vw; height: 100vh; } </style> </head> <body> <div id="app"></div> <script> const { Excalidraw } = ExcalidrawLib; const scene = ${sceneJSON}; const App = () => { return React.createElement(Excalidraw, { initialData: scene, viewModeEnabled: true, zenModeEnabled: false, gridModeEnabled: false, }); }; ReactDOM.render( React.createElement(App), document.getElementById('app') ); </script> </body> </html>`; } ``` ### Creating Excalidraw Elements **Rectangle:** ```typescript { id: "rect-1", type: "rectangle", x: 100, y: 100, width: 200, height: 150, strokeColor: "#1e1e1e", backgroundColor: "#ffc9c9", fillStyle: "hachure", strokeWidth: 2, roughness: 1, opacity: 100, angle: 0, roundness: { type: 3 }, seed: 12345, version: 1, versionNonce: 1, isDeleted: false, groupIds: [], boundElements: null, } ``` **Text:** ```typescript { id: "text-1", type: "text", x: 150, y: 150, width: 100, height: 25, text: "Hello World", fontSize: 20, fontFamily: 1, // 1=Virgil, 2=Helvetica, 3=Cascadia textAlign: "center", verticalAlign: "middle", strokeColor: "#1e1e1e", backgroundColor: "transparent", fillStyle: "hachure", strokeWidth: 2, roughness: 1, opacity: 100, angle: 0, seed: 12346, version: 1, versionNonce: 1, isDeleted: false, groupIds: [], containerId: null, originalText: "Hello World", } ``` **Arrow:** ```typescript { id: "arrow-1", type: "arrow", x: 300, y: 175, width: 150, height: 0, points: [[0, 0], [150, 0]], strokeColor: "#1e1e1e", backgroundColor: "transparent", fillStyle: "hachure", strokeWidth: 2, roughness: 1, opacity: 100, angle: 0, startBinding: { elementId: "rect-1", focus: 0, gap: 1 }, endBinding: { elementId: "rect-2", focus: 0, gap: 1 }, startArrowhead: null, endArrowhead: "arrow", seed: 12347, version: 1, versionNonce: 1, isDeleted: false, groupIds: [], } ``` **Ellipse:** ```typescript { id: "ellipse-1", type: "ellipse", x: 500, y: 100, width: 120, height: 120, strokeColor: "#2f9e44", backgroundColor: "#d3f9d8", fillStyle: "solid", strokeWidth: 2, roughness: 1, opacity: 100, angle: 0, seed: 12348, version: 1, versionNonce: 1, isDeleted: false, groupIds: [], boundElements: null, } ``` ## Advanced Patterns ### Architecture Diagram Generator ```typescript function createArchitectureDiagram( components: { name: string; type: "user" | "server" | "database" }[] ): any[] { const elements: any[] = []; let xOffset = 100; components.forEach((comp, i) => { const id = `comp-${i}`; const y = comp.type === "user" ? 100 : comp.type === "server" ? 300 : 500; // Component box elements.push({ id, type: "rectangle", x: xOffset, y, width: 150, height: 80, strokeColor: "#1e1e1e", backgroundColor: comp.type === "database" ? "#ffd43b" : "#a5d8ff", fillStyle: "hachure", strokeWidth: 2, roughness: 1, opacity: 100, angle: 0, roundness: { type: 3 }, seed: Math.random() * 10000, version: 1, versionNonce: 1, isDeleted: false, groupIds: [], boundElements: null, }); // Label elements.push({ id: `text-${i}`, type: "text", x: xOffset + 10, y: y + 30, width: 130, height: 20, text: comp.name, fontSize: 16, fontFamily: 1, textAlign: "center", verticalAlign: "middle", strokeColor: "#1e1e1e", backgroundColor: "transparent", fillStyle: "hachure", strokeWidth: 2, roughness: 1, opacity: 100, angle: 0, seed: Math.random() * 10000, version: 1, versionNonce: 1, isDeleted: false, groupIds: [], containerId: id, originalText: comp.name, }); // Arrow to next component if (i < components.length - 1) { elements.push({ id: `arrow-${i}`, type: "arrow", x: xOffset + 75, y: y + 80, width: 0, height: 120, points: [[0, 0], [0, 120]], strokeColor: "#1e1e1e", backgroundColor: "transparent", fillStyle: "hachure", strokeWidth: 2, roughness: 1, opacity: 100, angle: 0, startBinding: { elementId: id, focus: 0, gap: 1 }, endBinding: null, startArrowhead: null, endArrowhead: "arrow", seed: Math.random() * 10000, version: 1, versionNonce: 1, isDeleted: false, groupIds: [], }); } xOffset += 250; }); return elements; } ``` ### Streaming Diagrams with Updates For progressive rendering, send multiple tool responses: ```typescript async function streamDiagram( initialElements: any[], updates: any[][] ): Promise<void> { // Send initial state await sendToolResponse({ content: [{ type: "resource", resource: { uri: "excalidraw://stream-1", mimeType: "text/html", text: generateExcalidrawHTML(initialElements),
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen