Skip to main content

excalidraw-mcp-server

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

Ir a la instalación

Datos de origen

Repositorio
reason-machines/mcp-skills
Última actividad en el origen
16 de mayo de 2026 a las 16:49
Idioma detectado de SKILL.md
inglés
Estrellas
7
Forks
2

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
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),
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub