excalidraw-mcp-server
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseExcalidraw MCP Server Skill
Excalidraw MCP Server 技能
Skill by 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. Excalidraw MCP enables streaming hand-drawn style diagrams directly into AI chat interfaces like Claude Desktop, ChatGPT, VS Code, and Goose.
由ara.so提供的技能 — MCP技能合集。
本技能教AI Agent如何使用MCP Apps扩展构建可返回交互式Excalidraw图表的MCP服务器。Excalidraw MCP支持将手绘风格的图表直接流式传输到Claude Desktop、ChatGPT、VS Code和Goose等AI聊天界面中。
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
Excalidraw MCP服务器提供:
- 交互式图表渲染:返回可渲染Excalidraw画布的HTML应用
- 流式更新:支持带视口控制的渐进式图表构建
- 全屏编辑:用户可点击在模态框中编辑图表
- 远程与本地部署:可作为Vercel无服务器函数运行,也可作为本地Node.js进程运行
Installation
安装
Remote Server (Recommended)
远程服务器(推荐)
Use the hosted version at :
https://mcp.excalidraw.comjson
{
"mcpServers": {
"excalidraw": {
"url": "https://mcp.excalidraw.com"
}
}
}使用托管版本::
https://mcp.excalidraw.comjson
{
"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 buildConfigure in Claude Desktop ():
~/Library/Application Support/Claude/claude_desktop_config.jsonjson
{
"mcpServers": {
"excalidraw": {
"command": "node",
"args": ["/path/to/excalidraw-mcp-app/dist/index.js", "--stdio"]
}
}
}From release:
- Download from releases
excalidraw-mcp-app.mcpb - Double-click to install in Claude Desktop
从源码安装:
bash
git clone https://github.com/excalidraw/excalidraw-mcp.git
cd excalidraw-mcp-app
pnpm install && pnpm run build在Claude Desktop中配置():
~/Library/Application Support/Claude/claude_desktop_config.jsonjson
{
"mcpServers": {
"excalidraw": {
"command": "node",
"args": ["/path/to/excalidraw-mcp-app/dist/index.js", "--stdio"]
}
}
}从发布版本安装:
- 从发布页面下载
excalidraw-mcp-app.mcpb - 双击在Claude Desktop中安装
Core Concepts
核心概念
MCP Apps Extension
MCP Apps扩展
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>"
}
}
]
}MCP Apps是官方扩展,允许服务器返回交互式HTML而非纯文本。响应格式如下:
typescript
{
content: [
{
type: "resource",
resource: {
uri: "excalidraw://diagram",
mimeType: "text/html",
text: "<html>...</html>"
}
}
]
}Excalidraw Scene Format
Excalidraw场景格式
Excalidraw diagrams are JSON with and :
elementsappStatetypescript
interface ExcalidrawScene {
type: "excalidraw";
version: 2;
source: string;
elements: ExcalidrawElement[];
appState: {
viewBackgroundColor: string;
currentItemFontFamily?: number;
// ... viewport state
};
files?: Record<string, BinaryFileData>;
}Excalidraw图表是包含和的JSON:
elementsappStatetypescript
interface ExcalidrawScene {
type: "excalidraw";
version: 2;
source: string;
elements: ExcalidrawElement[];
appState: {
viewBackgroundColor: string;
currentItemFontFamily?: number;
// ... 视口状态
};
files?: Record<string, BinaryFileData>;
}Building an Excalidraw MCP Server
构建Excalidraw MCP服务器
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);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
生成HTML响应
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>`;
}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
创建Excalidraw元素
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,
}矩形:
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,
}文本:
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",
}箭头:
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: [],
}椭圆:
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;
}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;
// 组件框
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,
});
// 标签
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,
});
// 连接到下一个组件的箭头
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),
},
}],
});
// Stream updates
for (const update of updates) {
await new Promise(resolve => setTimeout(resolve, 500));
const allElements = [...initialElements, ...update];
await sendToolResponse({
content: [{
type: "resource",
resource: {
uri: "excalidraw://stream-1", // Same URI to update
mimeType: "text/html",
text: generateExcalidrawHTML(allElements),
},
}],
});
}
}对于渐进式渲染,发送多个工具响应:
typescript
async function streamDiagram(
initialElements: any[],
updates: any[][]
): Promise<void> {
// 发送初始状态
await sendToolResponse({
content: [{
type: "resource",
resource: {
uri: "excalidraw://stream-1",
mimeType: "text/html",
text: generateExcalidrawHTML(initialElements),
},
}],
});
// 流式发送更新
for (const update of updates) {
await new Promise(resolve => setTimeout(resolve, 500));
const allElements = [...initialElements, ...update];
await sendToolResponse({
content: [{
type: "resource",
resource: {
uri: "excalidraw://stream-1", // 使用相同URI进行更新
mimeType: "text/html",
text: generateExcalidrawHTML(allElements),
},
}],
});
}
}Viewport Control
视口控制
typescript
function generateExcalidrawHTMLWithViewport(
elements: any[],
viewport: { x: number; y: number; zoom: number }
): string {
const scene = {
type: "excalidraw",
version: 2,
source: "mcp-server",
elements,
appState: {
viewBackgroundColor: "#ffffff",
scrollX: viewport.x,
scrollY: viewport.y,
zoom: { value: viewport.zoom },
},
files: {},
};
// ... rest of HTML generation
}typescript
function generateExcalidrawHTMLWithViewport(
elements: any[],
viewport: { x: number; y: number; zoom: number }
): string {
const scene = {
type: "excalidraw",
version: 2,
source: "mcp-server",
elements,
appState: {
viewBackgroundColor: "#ffffff",
scrollX: viewport.x,
scrollY: viewport.y,
zoom: { value: viewport.zoom },
},
files: {},
};
// ... 剩余HTML生成代码
}Deployment
部署
Vercel Serverless Function
Vercel无服务器函数
Create :
api/mcp.tstypescript
import type { VercelRequest, VercelResponse } from "@vercel/node";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
const server = new Server(
{
name: "excalidraw-server",
version: "1.0.0",
},
{
capabilities: { tools: {} },
}
);
// ... set up handlers (same as above)
export default async function handler(
req: VercelRequest,
res: VercelResponse
) {
const transport = new SSEServerTransport("/message", res);
await server.connect(transport);
await transport.handleRequest(req);
}Deploy:
bash
vercel --prodYour server will be at .
https://your-project.vercel.app/api/mcp创建:
api/mcp.tstypescript
import type { VercelRequest, VercelResponse } from "@vercel/node";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
const server = new Server(
{
name: "excalidraw-server",
version: "1.0.0",
},
{
capabilities: { tools: {} },
}
);
// ... 设置处理器(与上述相同)
export default async function handler(
req: VercelRequest,
res: VercelResponse
) {
const transport = new SSEServerTransport("/message", res);
await server.connect(transport);
await transport.handleRequest(req);
}部署:
bash
vercel --prod你的服务器地址将是。
https://your-project.vercel.app/api/mcpDocker Container
Docker容器
dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build
CMD ["node", "dist/index.js", "--stdio"]bash
docker build -t excalidraw-mcp .
docker run -i excalidraw-mcpdockerfile
FROM node:20-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build
CMD ["node", "dist/index.js", "--stdio"]bash
docker build -t excalidraw-mcp .
docker run -i excalidraw-mcpTroubleshooting
故障排除
Elements Not Rendering
元素未渲染
Problem: Diagram appears blank.
Solution: Ensure all required element properties are present:
typescript
{
id: string, // Must be unique
type: string, // "rectangle", "ellipse", "arrow", "text", etc.
x: number, // Position
y: number,
width: number, // Dimensions
height: number,
seed: number, // For roughness randomization
version: number, // Version tracking
versionNonce: number, // Change detection
isDeleted: boolean, // Soft delete flag
groupIds: string[], // Group membership
}问题: 图表显示空白。
解决方案: 确保所有必需的元素属性都存在:
typescript
{
id: string, // 必须唯一
type: string, // "rectangle", "ellipse", "arrow", "text"等
x: number, // 位置
y: number,
width: number, // 尺寸
height: number,
seed: number, // 用于粗糙度随机化
version: number, // 版本跟踪
versionNonce: number, // 变更检测
isDeleted: boolean, // 软删除标记
groupIds: string[], // 组归属
}Arrow Bindings Not Working
箭头绑定不生效
Problem: Arrows don't connect to shapes.
Solution: Ensure and reference valid element IDs:
startBindingendBindingtypescript
{
type: "arrow",
startBinding: {
elementId: "rect-1", // Must match existing element
focus: 0, // -1 to 1, position on edge
gap: 1, // Distance from edge
},
endBinding: {
elementId: "rect-2",
focus: 0,
gap: 1,
},
}问题: 箭头未连接到形状。
解决方案: 确保和引用有效的元素ID:
startBindingendBindingtypescript
{
type: "arrow",
startBinding: {
elementId: "rect-1", // 必须匹配现有元素
focus: 0, // -1到1,边缘位置
gap: 1, // 与边缘的距离
},
endBinding: {
elementId: "rect-2",
focus: 0,
gap: 1,
},
}Text Inside Shapes Not Centered
形状内的文本未居中
Problem: Text doesn't appear inside rectangles.
Solution: Set to bind text to container:
containerIdtypescript
{
type: "text",
containerId: "rect-1", // ID of containing rectangle
textAlign: "center",
verticalAlign: "middle",
}问题: 文本未显示在矩形内部。
解决方案: 设置将文本绑定到容器:
containerIdtypescript
{
type: "text",
containerId: "rect-1", // 容器矩形的ID
textAlign: "center",
verticalAlign: "middle",
}Streaming Updates Not Appearing
流式更新未显示
Problem: Updated diagrams don't replace previous ones.
Solution: Use the same for all updates:
uritypescript
const diagramURI = `excalidraw://diagram-${sessionId}`;
// All updates must use this same URI
resource: {
uri: diagramURI,
mimeType: "text/html",
text: generateExcalidrawHTML(updatedElements),
}问题: 更新后的图表未替换之前的图表。
解决方案: 所有更新使用相同的:
uritypescript
const diagramURI = `excalidraw://diagram-${sessionId}`;
// 所有更新必须使用相同的URI
resource: {
uri: diagramURI,
mimeType: "text/html",
text: generateExcalidrawHTML(updatedElements),
}CORS Errors in Vercel Deployment
Vercel部署中的CORS错误
Problem: Client can't connect to Vercel MCP endpoint.
Solution: Add CORS headers in :
vercel.jsonjson
{
"headers": [
{
"source": "/api/mcp",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "*" },
{ "key": "Access-Control-Allow-Methods", "value": "GET,POST,OPTIONS" },
{ "key": "Access-Control-Allow-Headers", "value": "Content-Type" }
]
}
]
}问题: 客户端无法连接到Vercel MCP端点。
解决方案: 在中添加CORS头:
vercel.jsonjson
{
"headers": [
{
"source": "/api/mcp",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "*" },
{ "key": "Access-Control-Allow-Methods", "value": "GET,POST,OPTIONS" },
{ "key": "Access-Control-Allow-Headers", "value": "Content-Type" }
]
}
]
}Best Practices
最佳实践
- Generate unique IDs: Use or timestamps
crypto.randomUUID() - Set reasonable viewport: Calculate bounds from element positions
- Use consistent colors: Define a palette for visual coherence
- Add descriptions: Include metadata for accessibility
- Validate elements: Check required properties before rendering
- Handle errors gracefully: Return text fallbacks if diagram fails
- Test locally first: Use mode before deploying remotely
--stdio
- 生成唯一ID:使用或时间戳
crypto.randomUUID() - 设置合理的视口:根据元素位置计算边界
- 使用一致的颜色:定义调色板以保证视觉一致性
- 添加描述:包含元数据以提升可访问性
- 验证元素:渲染前检查必需属性
- 优雅处理错误:如果图表生成失败,返回文本替代方案
- 先在本地测试:部署到远程前先使用模式测试
--stdio