生成 Excalidraw 技术配图(.excalidraw 源文件 + PNG 渲染图)。当用户要求"生成配图"、"画技术图"、"Excalidraw 图解"、"技术图解"、"流程图"、"架构图"时触发。支持7种风格(纵向流程/横向卡片/架构拓扑/时间线/对比矩阵/分层架构/状态机),双面板组合,自动渲染 PN...
Coding
Excalidraw Handdraw
Try it根据提示词生成手绘风格图表的 skill。用于:(1) 创建架构图、流程图、ER 图等 Excalidraw 手绘风格图表 (2) 通过 Docker 本地运行 canvas 服务器 (3) 生成 PNG/SVG 图片 (4) 保存图片到指定目录 (5) 将图表插入或替换到文件指定位置 (6) 支持中文手写字体。...
What it does
根据提示词生成手绘风格图表的 skill。用于:(1) 创建架构图、流程图、ER 图等 Excalidraw 手绘风格图表 (2) 通过 Docker 本地运行 canvas 服务器 (3) 生成 PNG/SVG 图片 (4) 保存图片到指定目录 (5) 将图表插入或替换到文件指定位置 (6) 支持中文手写字体。触发词:画图、创建图表、生成图表、手绘风格、架构图、流程图、Excalidraw、保存图表到文件、插入图表。
The skill document
Excalidraw Handdraw Skill
Step 0: 检查 Canvas 服务器状态
首先检查 Docker canvas 是否已运行:
docker ps | grep mcp_excalidraw-canvas
如果未运行,使用脚本启动:
./scripts/start-canvas.sh
确认服务可用:
curl -s http://localhost:3000/health
Step 1: 理解用户需求
用户描述想要创建的图表类型,例如:
- "画一个微服务架构图"
- "创建一个用户登录流程图"
- "生成一个数据库 ER 图"
- "画一个网络拓扑图"
分析需求:
- 确定图表类型(架构图、流程图、ER 图等)
- 确定需要的元素(矩形、箭头、文字等)
- 确定是否需要中文标签
Step 2: 创建图表元素
⚠️ 重要:REST API 格式说明
- 文字标签:必须使用
{"label": {"text": "文字"}}格式(不是text) - 箭头连接:必须使用
{"start": {"id": "id1"}, "end": {"id": "id2"}}格式(不是startElementId) - 箭头坐标:箭头元素必须有
x和y坐标(可以为 0)
API 调用方式:
- 使用
POST /api/elements逐个创建元素(不支持批量创建) - 每个元素必须包含完整的属性:id, type, x, y, width, height, label
Playwright 操作示例:
async () => {
const elements = [
{ id: "lb", type: "rectangle", x: 300, y: 50, width: 180, height: 60, label: { text: "负载均衡器" } },
{ id: "svc-a", type: "rectangle", x: 100, y: 200, width: 160, height: 60, label: { text: "服务 A" } },
{ id: "arrow1", type: "arrow", x: 0, y: 0, start: { id: "lb" }, end: { id: "svc-a" } }
];
for (const el of elements) {
await fetch('/api/elements', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(el)
});
}
return { success: true };
}
参考坐标系统:
- 原点 (0,0) 在左上角
- x 向右增加,y 向下增加
- 元素宽度:
max(160, labelCharCount * 9) - 元素高度:单行 60px,双行 80px
⚠️ 布局最佳实践(避免重叠和裁剪):
- 垂直间距:140-180px(不要小于 140px)
- 水平间距:80-100px(不要小于 80px)
- 顶部边距:startY 至少 100px(避免顶部元素被裁剪)
- 底部边距:最后一个元素 Y + height 应小于 600px
- 留白原则:宁可间距大,也不要太小
- 多行文字时:height = 行数 × 30px + 15px
Step 3: 验证图表质量
创建后获取截图验证:
- 使用
get_canvas_screenshot获取图片 - 检查文字是否截断
- 检查元素是否重叠
- 检查箭头是否穿过无关元素
发现问题时修复:
- 文字截断 → 增加元素宽度/高度
- 元素重叠 → 调整坐标位置
- 箭头穿过元素 → 使用曲线箭头或调整布局
Step 4: 导出纯净图片(关键步骤)
⚠️ 重要:必须使用 Canvas API 导出纯净图片,不要使用页面截图!
使用 Playwright 执行 JavaScript,直接从 canvas 元素导出:
async () => {
const canvas = document.querySelector('canvas');
if (!canvas) return { success: false };
// 获取画布数据并下载
const dataUrl = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.download = 'diagram.png';
link.href = dataUrl;
link.click();
return { success: true, downloaded: true };
}
Playwright 操作步骤:
- 使用
browser_evaluate执行上述 JavaScript - 图片会下载到
.playwright-mcp/目录 - 使用
mv命令移动到目标位置
示例:
browser_evaluate → 执行导出 JS
→ 下载到 .playwright-mcp/diagram.png
→ mv .playwright-mcp/diagram.png ./output/diagram.png
❌ 错误方式:
- 使用
browser_take_screenshot- 会包含网页工具栏和侧边栏 - 使用
browser_snapshot+ 截图 - 同样包含 UI 元素
✅ 正确方式:
- 使用
browser_evaluate执行 canvas.toDataURL() 导出 - 这样得到的图片是纯净的,只有画布内容,白色背景
Step 5: 保存到指定位置
图片下载后保存在 .playwright-mcp/ 目录,使用 mv 命令移动到目标位置:
mv .playwright-mcp/diagram.png ./docs/diagrams/architecture.png
Step 6: 插入到 Markdown 文件
将图片插入到 Markdown 文件:

常用命令参考
| 操作 | 方法 |
|---|---|
| 启动 Canvas | docker ps | grep mcp_excalidraw-canvas 确认运行 |
| 停止 Canvas | docker stop mcp_excalidraw-canvas |
| 创建元素 | browser_evaluate 执行 fetch('/api/elements', {...}) |
| 清除画布 | 点击页面 "Clear Canvas" 按钮 |
| 同步画布 | 点击页面 "Sync to Backend" 按钮 |
| 导出纯净图片 | browser_evaluate 执行 canvas.toDataURL() + 下载 |
| 移动图片 | mv .playwright-mcp/diagram.png ./output/diagram.png |
常用图表模板
组织架构图模板
async () => {
const elements = [];
// 布局参数
const config = {
startY: 50, // 起始 Y 坐标
levelGap: 180, // 层级垂直间距
nodeWidth: 140, // 节点宽度
nodeHeight: 60, // 节点高度
hGap: 100 // 水平间距
};
// 第一层:CEO
elements.push({
id: "ceo", type: "rectangle",
x: 300, y: config.startY,
width: 160, height: config.nodeHeight,
label: { text: "CEO\n总经理" }
});
// 第二层:高管层(水平排列)
const level2 = ["CTO\n技术总监", "CFO\n财务总监", "COO\n运营总监", "CMO\n市场总监"];
const level2Y = config.startY + config.levelGap;
level2.forEach((label, i) => {
elements.push({
id: `l2-${i}`, type: "rectangle",
x: 50 + i * (config.nodeWidth + config.hGap),
y: level2Y,
width: config.nodeWidth, height: config.nodeHeight,
label: { text: label }
});
// 连接到 CEO
elements.push({
id: `arr-ceo-${i}`, type: "arrow",
x: 0, y: 0,
start: { id: "ceo" }, end: { id: `l2-${i}` }
});
});
// 创建所有元素
for (const el of elements) {
await fetch('/api/elements', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(el)
});
}
return { success: true };
}
流程图模板(水平布局)
async () => {
const steps = ["开始", "步骤1", "步骤2", "结束"];
const config = { startX: 50, y: 100, boxWidth: 120, boxHeight: 50, gap: 80 };
const elements = [];
steps.forEach((label, i) => {
elements.push({
id: `step-${i}`, type: "rectangle",
x: config.startX + i * (config.boxWidth + config.gap),
y: config.y,
width: config.boxWidth, height: config.boxHeight,
label: { text: label }
});
// 连接箭头(从上一个到当前)
if (i > 0) {
elements.push({
id: `arr-${i}`, type: "arrow",
x: 0, y: 0,
start: { id: `step-${i-1}` }, end: { id: `step-${i}` }
});
}
});
for (const el of elements) {
await fetch('/api/elements', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(el)
});
}
return { success: true };
}
垂直流程图模板
async () => {
const steps = ["输入", "处理", "输出"];
const config = { x: 300, startY: 50, boxWidth: 160, boxHeight: 60, vGap: 150 };
const elements = [];
steps.forEach((label, i) => {
elements.push({
id: `node-${i}`, type: "rectangle",
x: config.x,
y: config.startY + i * (config.boxHeight + config.vGap),
width: config.boxWidth, height: config.boxHeight,
label: { text: label }
});
if (i > 0) {
elements.push({
id: `arr-${i}`, type: "arrow",
x: 0, y: 0,
start: { id: `node-${i-1}` }, end: { id: `node-${i}` }
});
}
});
for (const el of elements) {
await fetch('/api/elements', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(el)
});
}
return { success: true };
}
布局计算辅助函数
在编写 JavaScript 时,可使用以下辅助计算:
// 水平居中计算
const centerX = (canvasWidth, elementWidth) => (canvasWidth - elementWidth) / 2;
// 多元素水平均匀分布
const distributeHorizontal = (count, elementWidth, totalWidth, startX) => {
const totalGap = totalWidth - count * elementWidth;
const gap = totalGap / (count + 1);
return Array.from({length: count}, (_, i) => startX + gap + i * (elementWidth + gap));
};
// 计算元素所需宽度(根据文字)
const calcWidth = (text) => Math.max(120, text.length * 12);
// 计算元素所需高度(根据行数)
const calcHeight = (text) => {
const lines = text.split('\n').length;
return lines * 30 + 20;
};
注意事项
- Docker 必须运行:Canvas 服务器通过 Docker 运行,确保
mcp_excalidraw-canvas容器在运行 - 中文支持:Excalidraw 原生支持中文,无需额外配置
- 导出纯净图片:必须使用
canvas.toDataURL()方式导出,不要用页面截图 - 图片下载位置:Playwright 下载的图片在
.playwright-mcp/目录 - 间距宁大勿小:使用较大间距(150px+)避免重叠,宁可图表松散也不要拥挤
- 箭头用元素引用:使用
start: {id}, end: {id}格式,让 Excalidraw 自动计算路径
故障排除
- Canvas 无法连接:检查 Docker 容器是否运行
docker ps | grep mcp_excalidraw-canvas - 导出图片有 UI 元素:确保使用
canvas.toDataURL()而不是browser_take_screenshot - 元素创建失败:确保每个元素包含完整属性(id, type, x, y, width, height)
- 元素重叠:增大间距,垂直间距至少 150px,水平间距至少 80px
- 箭头位置错误:使用
start: {id}, end: {id}元素引用格式,而非手动 points - 文字截断:增加元素宽度,使用
width = text.length * 12估算
常见错误及修复
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 矩形重叠 | 间距太小 | 垂直间距改为 150px+,水平 80px+ |
| 箭头乱连 | points 坐标错误 | 改用 start: {id}, end: {id} 格式 |
| 文字不全 | width/height 不足 | 根据字数行数计算足够尺寸 |
| 图表太密 | 整体布局紧凑 | 放大间距,宁可松散不要拥挤 |
| 连线穿过元素 | 布局未考虑路径 | 调整元素位置或使用曲线箭头 |
Related skills
Excalidraw MCP (excalidraw.com). Use this skill for ANY Excalidraw MCP request — reading, creating, and updating data. Whenever a task involves Excalidraw MCP, use this skill instead of calling the API directly.
Create and edit draw.io diagrams through the configured drawio MCP server, including flowcharts, architecture diagrams, ML model diagrams, Chinese labels, an...
Generate editable ProcessOn diagrams from natural language, covering flowcharts, architecture diagrams, and ER diagrams.
Convert natural language descriptions into editable ProcessOn diagrams across many chart types.
Turn a natural-language description into a real Excalidraw whiteboard file (.excalidraw) — flowcharts, mind maps, architecture/system diagrams, org charts, wireframes, sticky-note brainstorms, sequence diagrams. Always use this skill whenever the user asks to draw, sketch, diagram, whiteboard, mind-map, or map something out, or mentions "excalidraw", "boardraw", "白板", "画一个", "流程图", "思维导图", "架构图", "组织架构图", "线框图", "泳道图", "看板", or wants a file they can open in Excalidraw or Boardraw. Do NOT hand-write raw .excalidraw JSON directly — the schema has fragile id/seed/binding requirements — always build it with the bundled scripts/excalidraw_builder.py helper instead.