叮卡自带一个 MCP(Model Context Protocol)服务器 dingcard-mcp,让 AI 客户端(Claude Desktop、Cursor、ZCode 等任何支持 MCP 的工具)和其他程序可以不走浏览器 UI,直接完成“选模板 → 生成整套卡片 → 检查 → 无头渲染 PNG”的完整闭环,并且能看到自己做出来的样子:
list_templates → create_document_from_content / create_document_from_outline(返回 documentId)
→ check_document(排版后列出问题,可自动缩字号)→ apply_actions 修改
→ render_document → PNG / JPG / PDF / 长图 + 每页缩略图(直接给模型看)
→ open_in_editor → 在叮卡编辑器里打开,人接着改
→ share_document → 分享链接 + 二维码(手机扫码看图,见下文「分享给人」)
server_projects(list / open / save:人在编辑器里存的作品载入,接着改 / 再渲染 / 再分享 / 存回去)
服务端保存它创建和修改的文档:工具之间传 documentId 即可,不必每次把整份文档 JSON 传来传去(见下文「文档句柄」)。
渲染与编辑器导出走同一套管线:自由画布逐页 pixelRatio: 1 导出;Markdown 走工作台自己的管线(DOM 实测分页、平台头部与主题、--- 手动分页、pixelRatio: 3 导出);两者都做网页字体按字符子集嵌入与图片就绪等待。
| 工具 | 作用 |
|---|---|
get_guide |
按主题读完整使用指南:document-model 文档模型、actions 动作清单、deck-content 套图内容、outline Markdown 大纲、poster-content 海报内容、html-import 网页转文档、templates 模板清单的字段、check 检查项、render-export 渲染导出。工具说明只写做什么;动手写参数前先读对应主题。 |
list_templates |
列出内置模板,默认每个模板一张简卡,挑模板用:id、标题、描述、kind(deck 一整套卡片 / poster 单页海报)、format(尺寸 id:xhs 小红书 3:4、story 竖版海报 9:16、square 方图 1:1、landscape 横版 16:9、wechat-cover 公众号首图 2.35:1、a4 A4 印刷、a4-landscape A4 横版、moments-grid 朋友圈九宫格 3240×3240),以及模板中心用的分类 uses 用途、styles 风格。挑出候选后用 ids(最多 12 个)取详情:系列、工作台、页数、标签、colors 颜色、尺寸的名称和宽高;套图模板另有 capacity:内页最多几个要点、有没有正文和引文位、结尾页能放什么;海报模板另有 posterCapacity:副标题、正文、获得者(recipient)、按钮、角标、署名、主图位有没有,信息能放几行,有没有表格(table,最多几行几列)。不存在的 id 列在 unknown 里。可选 kind/format/q 参数只取要看的子集(如 kind: "poster"、format: "xhs"、q: "菜单");q 和模板中心的搜索一样,除了标题、标签和描述,也认用途和风格(q: "国风"、q: "菜单价目")。简卡不加筛选时整份不超过 20,000 字(docs.test.ts 卡着),模板再多,挑模板的成本也不跟着涨。Markdown 模板的 themeId 是它的版式(template-column 等),render_markdown 渲染时带着页眉页码。 |
create_document_from_template |
按模板 id 实例化完整文档:自由画布文档保存在服务端,返回 documentId 和各页 id、名称;Markdown 返回源文信封。 |
add_template_pages |
把任意自由画布模板的某几页(pages,从 1 起,可重复;不给就全部)加进已有文档:放在 afterSlideId 后面(默认最后),或换掉 replaceSlideId 那一页;页面保留模板的尺寸和示例文字,返回加进来的页并默认附缩略图(见下文「不同页用不同模板」)。 |
create_poster_from_content |
按内容生成一张海报(kind: 'poster' 的模板):{ title, subtitle?, body?, recipient?, details?: ["时间:…"…], table?: [["节次", "周一"…]…], cta?, tag?, brand?, image? },尺寸跟模板走(规则见下文「生成海报」);给 documentId 时海报加成那份文档的一页。 |
create_document_from_content |
按结构化内容生成整套卡片:{ title, subtitle?, pages: [{ title, body?, points?, quote?, templateId?, layout? }], ending? },封面 + 每个 page 一页 + 可选结尾页,风格沿用所选自由画布模板,某一页写了 templateId 就用那个套图模板的版式,layout 指定版式(不给按内容挑;规则见下文「生成整套卡片」)。 |
create_document_from_html |
把你写的 HTML/CSS 网页转成能逐个修改的自由画布文档:每页一个 <section>,色块、文字、图片、SVG 图形读成形状、文字框、图片和图形节点,返回 documentId、每页尺寸和 notes(转不了、只能近似的地方),默认附缩略图(规则见下文「用网页写法出图」)。 |
create_document_from_outline |
同上,内容写成 Markdown 大纲(写法见下文)。 |
check_document |
在与导出相同的页面里排版后,逐页列出读者会注意到的问题(见下文「检查」),每条带图层名、节点路径和改法;fix: true 时把放不下的文字改成能放下的字号并返回改好的文档。 |
validate_document |
严格校验 v44 文档(v1–v43 输入自动迁移;精确键匹配、几何范围、id 唯一性),合法时保存在服务端并返回 documentId(includeDocument: true 时附上规范化后的文档)。 |
inspect_document |
输出页面摘要与递归节点树(id、name、type、几何、文本摘要;内置图标标出 icon id,装饰素材标出 decoration id(几部分组成的装饰标在组上),其他图形给出 d 开头),以及整套卡片的 style:用到的颜色(按面积排序,附占比 share 和用在哪:background/fill/text/line/shadow)、字体(几段文字用、最大字号)和正文字号 bodySize,为编辑提供目标。 |
list_assets |
查内置素材与样式清单,kind 选一类:icons(97 个线性图标:query 搜、ids 取,带路径数据 d、统一画法 style 和可直接插入的完整节点 example,见下文「图形与图标」)、decorations(48 个装饰素材:手绘线条、贴纸、标签,query/category/ids,见下文「装饰素材」)、styles(配色与字体组合,配合 document/restyle,见下文「整套换风格」)、text_styles(16 套现成花字:patch 直接作为 node/update-style 的补丁,见下文「花字与文字效果」)、collages(6 套拼图版式:example 是完整拼图组节点,格子 fill 换图片填充;可选 pageWidth/pageHeight)、filter_presets(8 个滤镜预设:patch 给 node/update-style,传 null 清除)。不给 kind 返回各类简介。 |
add_decorations |
把装饰素材放到一页上,一次可以放多个:每项给 decoration、x、y、width,可选 color、text、rotation、below(垫在某个顶层节点下面);返回每个的 nodeId、path 和盒子。 |
apply_actions |
用与编辑器 UI 完全相同的 FreeformAction 归约器应用一串编辑,逐步报告是否生效;按 documentId 就地更新(version 加一)。 |
get_document |
取回完整文档 JSON,或用 path 写成 .json 文件(之后可拖进「我的项目」,或用 documentPath 传回来)。 |
open_in_editor |
在浏览器里的叮卡编辑器打开这份文档,存成一个新项目,人接着手改(见下文「在叮卡里打开」)。 |
render_document |
无头渲染自由画布 v44 文档,默认输出 <baseName>-01.png、-02.png… 到指定目录;format: 'jpeg' 输出白底 .jpg,format: 'pdf' 输出一个 <baseName>.pdf(每页一张,页面和卡片一样大),long: true(png / jpeg)把所有页从上到下拼成一张 <baseName>-long.png(太长时自动降低倍率,files[0].scale 是实际倍率),grid: true(png / jpeg,只用于正方形页面)把每页切成九宫格 <baseName>-01-1.png … -01-9.png(files[i].tile 是 1–9,从左到右、从上到下,按这个顺序发朋友圈拼回一整张),scale: 2 输出两倍像素,quality 是 JPEG 质量;slideIds 只渲染这些页,PDF 和长图也只放这些页。默认附上每页的 JPEG 缩略图(432 px 宽,最多 12 张)作为图片内容返回,模型可以直接看效果;previews: false 关掉。 |
render_markdown |
无头渲染 Markdown 文档信封为一套卡片 PNG:DOM 实测分页(--- 为手动分页)、平台预设(rednote/weibo/twitter)、主题与个人资料头部、pixelRatio: 3 导出;页数由分页结果决定。同样附缩略图。 |
share_document |
把文档渲染上传到部署的叮卡服务端,生成一个不用登录就能打开的分享链接,并附上二维码图片给用户扫(见下文「分享给人」);自由画布文档和 Markdown 卡片信封(与 render_markdown 的 document 相同)都可以。action 另选 list(列出账号已有的分享,含被打开次数)、revoke(按 id 撤销:链接立刻打不开)。需要环境变量 DINGCARD_SERVER_URL 加 DINGCARD_SERVER_TOKEN(API 令牌)或 DINGCARD_SERVER_USERNAME / DINGCARD_SERVER_PASSWORD。 |
server_projects |
管部署的叮卡服务端账号里存的作品(见下文「读账号里的作品」),action 选操作:list 列出作品(id、标题、类型 markdown-card / freeform-slide、更新时间,query 按标题过滤)、open 按 id 载入(自由画布作品返回 documentId,之后 inspect_document / apply_actions / render_document / share_document 都能用;Markdown 作品返回 markdownDocument 信封,交给 render_markdown)、save 把文档存回账号(不给 projectId 存成新作品,给 open 返回的 projectId 时覆盖那一份)。需要 DINGCARD_SERVER_URL 加 DINGCARD_SERVER_TOKEN(带 drafts 权限)或账号环境变量。 |
模板放不下的版式,可以写成网页交给 create_document_from_html,转出来同样接 check_document → render_document。
工具说明保持精简:每个工具的描述只写做什么、什么时候用,长内容全部收进 get_guide 工具(按主题返回完整写法)和 dingcard://schema/* 资源,工具描述里只留指路。validate_document 的描述带文档速写(顶层结构、节点类型列表、精确键匹配规则),完整的文档模型(每种节点的全部键)在 dingcard://schema/freeform 资源和 get_guide 的 document-model 主题里各留一份(同一来源),其余工具都指向它们,AI 客户端按需读取即可。validate_document 校验失败会写清第几页、哪个节点、多了或缺少哪个键、该写哪个 documentVersion;apply_actions 对没生效的动作逐条返回 reasons(与 changes 一一对应)。批量场景推荐链路:list_templates 按 capacity 选风格 → create_document_from_content(或大纲)一次生成整套 → check_document 看有没有问题 → 需要时 apply_actions 修改(整套换配色、字体用 document/restyle)→ render_document 出全套 PNG(或一个 PDF、一张长图)并看缩略图。
- 创建或修改自由画布文档的工具(
create_document_from_*、validate_document、apply_actions、check_document的fix)把结果保存在服务端,返回documentId(形如doc_1a2b3c4d5e6f)和version;默认不再附上整份文档,要的话传includeDocument: true,或用get_document取。 - 需要文档的工具都接受三种写法之一:
documentId(推荐)、document(整份 JSON)、documentPath(JSON 文件路径,~会展开)。用documentId修改时就地更新这份文档;传document/documentPath时另存为一份新文档。 - 服务端最多保留 32 份文档(最久没用的先清掉),重启后清空。要长期保存,用
get_document的path写成文件。
文档里图片的 src(图片节点、形状的图片填充、页面背景图)可以直接写本机文件路径:/Users/me/photo.jpg、~/Pictures/a.png、./cover.webp(相对 documentPath 所在目录,其他情况相对服务器的工作目录)或 file:// 地址。保存、检查、渲染之前服务端会把文件读进来,嵌成 data URL,文档因此可以单独拿到编辑器和别的机器上用。支持 PNG、JPEG、GIF、WebP、AVIF 和 SVG,单张最大 20 MB。写成 file://、~/、./、../ 的路径读不到会直接报错;以 / 开头但本机没有这个文件的,当作网址原样保留(比如 /uploads/…)。
open_in_editor 把文档交给叮卡编辑器:服务端把文档放在本机的一个地址上,在默认浏览器里打开 #/edit/canvas/import?url=… 链接,编辑器读进来存成一个新项目(title 是项目名),不登录也行,存在这台设备的浏览器里。
- 默认打开服务端自带的编辑器:
http://127.0.0.1:5390(同一份构建产物,离线可用;端口用DINGCARD_APP_PORT改,被占用时换一个空闲端口)。项目存在这个地址的浏览器存储里。 - 平时用的是别的叮卡(本地开发的
http://127.0.0.1:5173,或部署好的网站),设环境变量DINGCARD_APP_URL,或调用时传appUrl,就在那里打开;编辑器跨域读取本机地址上的文档。 open: false只返回链接(url)不打开浏览器。编辑器只接受本机(127.0.0.1、localhost)或同源地址上的文档,别的网站的链接会被拒绝。
share_document 把文档变成一个不用登录就能打开的网页链接:每一页无头渲染成图片、上传到部署的叮卡服务端,挂在一个不可猜的链接后面——手机扫码或在任何浏览器点开都能看整套卡片,长按图片保存到相册。这是「电脑上让 AI 做图、手机上发小红书」的最后一公里:不再导出文件、传文件。
- 配置一次(环境变量,重启 MCP 生效):
DINGCARD_SERVER_URL(部署的叮卡地址,如https://cards.example.com)加DINGCARD_SERVER_TOKEN(一个 API 令牌;带 images、shares 权限,见后端方案的「API 令牌」),或DINGCARD_SERVER_USERNAME/DINGCARD_SERVER_PASSWORD(一个叮卡账号)。没配置时工具会返回怎么配的提示。 expiresInHours是有效期(小时):1–720(最长一个月),默认 24。过期后链接打不开(410),页面图片仍留在账号里,和编辑器里「分享链接」的规则一致。- 返回
{ ok, share: { id, url, expiresAt, imageCount } },并附上二维码 PNG 图片(qr: false关掉)——直接给用户扫即可。 share_document的action: "list"列出已有分享(含被打开次数),action: "revoke"撤销一个(链接立刻 404)。分享出去的图片与素材库同样受图片回收保护,不会被误清。Markdown 卡片信封(source、platformId、themeId等,与render_markdown的document相同)也能一步分享:传给share_document的document即可。
server_projects 把方向反过来:人在编辑器里存好的作品,AI 也能接着做——改几个字、换个配色、再渲染、再分享,不用从零重做;save 再把改完的存回去。
- 配置与分享相同(
DINGCARD_SERVER_URL加DINGCARD_SERVER_TOKEN或账号环境变量);API 令牌需要带drafts权限(读和写都是这个作用域)。 action: "list"返回作品的id、title、mode(markdown-card/freeform-slide)和updatedAt,最新在前;作品多时用query按标题关键词过滤(不区分大小写,返回里带matched/total计数)。action: "open"按id载入:自由画布作品存进服务端并返回documentId和projectId(服务端那份作品的 id),之后inspect_document/apply_actions/check_document/render_document/share_document/open_in_editor都能用;Markdown 作品返回markdownDocument信封(source、platformId、themeId等都在里面),交给render_markdown渲染。action: "save"把文档存回服务端账号:不给projectId存成新作品(title可选,不给时按第一页名称自动起);给open返回的projectId时覆盖那一份——人在编辑器里打开看到的就是改过的版本。Markdown 作品不能从 MCP 存回(编辑器里的 Markdown 工作台没有对应的文档工具)。- 作品里的图片地址换成服务端的绝对地址(
/uploads/…指向部署的叮卡),渲染时能直接取到;不覆盖时(只载入、只渲染、另存新作品)服务端存的原作品不变。
模板排不出来的版式,可以直接写网页:create_document_from_html 在无头浏览器里按叮卡的字体把网页排好,再把排出来的样子读回来,每个元素变成能在编辑器里单独修改的节点,位置、字号、行高、字距、颜色、圆角、边框、阴影、渐变、透明度、混合模式、滤镜和旋转都照网页来,层叠顺序按 CSS 的绘制顺序(含 z-index)。网页给 html(源码)或 htmlPath(文件),里面的相对路径按 htmlPath 所在目录找,没有 htmlPath 时按服务器的工作目录。
- 页面:每页一个
<section>,放在<body>下面,用 CSS 写死宽高(px),如 1080×1440(小红书 3:4)、1080×1920、1080×1080;没有<section>时整个<body>是一页,尺寸是width×height(默认 1080×1440),100vw/100vh也按这个算。data-name给页面起名。页面的底色、渐变或铺满的背景图成为页面背景,其他背景层成为最底下的节点。 - 字体:用内置的苹方
"PingFang SC"、思源黑体"Noto Sans SC"、思源宋体"Noto Serif SC"、霞鹜文楷"LXGW WenKai TC"、站酷小薇"ZCOOL XiaoWei"、系统宋体"Songti SC";标题和海报大字还能用"Playfair Display"(西文衬线)、"Archivo Black"(西文粗黑)、"Great Vibes"(西文花体)、站酷黄油体"ZCOOL QingKe HuangYou"和马善政毛笔"Ma Shan Zheng"。别的字体按字体栈里的类别换成内置字体:衬线换思源宋体,手写换霞鹜文楷,其余换苹方。换好之后才排版,所以转出来的换行和位置跟排好的网页一致;notes里列出换掉的字体。字重只有常规和粗体,600 及以上算粗体。 - 文字:一个块里的文字是一个文字框,按原来的宽度换行,
text-align: justify照样两端对齐;行内的加粗、换色、底色(行内元素的background-color)、下划线和删除线(<s>、<del>、line-through)变成文字片段。一个块里只有这段文字、几种字号的行高倍数又一样时(价格里放大的数字),不同字号成为带字号的片段,仍是一个文字框;否则同一行里的不同字号、和inline-block小标签在同一行的文字会拆成几个文字框,多栏排版(columns)的文字每行一个文字框,每行都留在原来的位置。行内元素带圆角、内边距、边框或渐变背景(荧光笔效果)时,底色画成文字下面的色块。::before/::after照常生效,text-transform已经换好。 - 列表:
<ul>/<ol>的每一项只有文字、字号字体颜色一样、圆点或编号在外侧(disc/circle/square/decimal)、项与项间距一样时,整个列表转成一个列表文字(list,项间距成为paragraphSpacing),圆点和编号由叮卡画,在编辑器里加一项只要按回车。其他列表逐项转,圆点和编号单独成字。 - 表格:
<table>能照原样画成表格元素时变成一个可逐格编辑的表格:一种文字颜色和字号,<th>表头一种底色或没有,正文行不铺色或隔行铺一种色;border-collapse: collapse的实线格线是全框、每行下一条横线、三线表(顶线和底线是表头下那条的两倍粗)或没有线,一种颜色;行一样高,格子里的字居中或列宽正好包住字;2–12 行 1–6 列,每格 24 字以内,没有合并单元格。文字颜色和字号、表头底色、斑马纹、格线(v44 的grid、lineColor、lineWidth)和列宽照网页来。别的表格——左对齐的宽表、表头和正文两种字色、虚线或每格不同的边框——照网页原样导成文字和色块,notes里会说。 - 图片:
<img>和 CSSbackground-image都行,object-fit、object-position、background-size、background-position换成取景;带圆角或圆形裁切(自己的border-radius,或overflow: hidden的圆角容器)时成为带图片填充的形状。src写本机路径、http(s) URL 或 data URL;本机图片和本机样式表(<link rel="stylesheet">)会嵌进来,读不到的图片列在notes里。 - SVG:内联
<svg>的path、rect、circle、ellipse、line、polyline、polygon每个变成一个图形节点,一个<svg>里有几个就成一个组合;transform、viewBox、描边宽度、线帽和拐角都换算好。长短不一的虚线(比如环形进度条的stroke-dasharray)按画出来的样子描成线段,SVG 里的<text>变成单行文字框;<use>引用和<foreignObject>不转。 - 旋转和组合:
rotate和scale(transform里的,或单独的rotate、scale属性)换成节点或组合的旋转缩放,只有一个节点的就转节点本身;data-group让一个元素连同里面的东西成为一个组合,data-name给图层起名。 - 近似处理(写进
notes):透明度不一的渐变(照片上的渐隐遮罩)、平铺背景和锥形渐变画成一张图;半透明的阴影按背后的颜色换成不透明的;只有一边的边框画成细长色块;内阴影、第二层以后的阴影、阴影的扩展、backdrop-filter、clip-path、mask去掉,斜切按等比缩放。脚本和动画不运行。 - 转出来的文档和别的一样:
check_document检查,apply_actions修改,render_document出图,open_in_editor在编辑器里接着改。
create_poster_from_content 把内容填进单页模板(chart 参数填数据盘点的图表:类目与系列,kind 换成柱状 bar、环形 ring、折线 line 或雷达 radar,不给时跟模板走)(小红书封面的干货笔记、图片拼贴和作品集封面、菜单、甜品菜单、价目表、证书、朋友圈九宫格、课程表、展览、讲座、新品预告、促销、招聘、节日、节气、邀请函、婚礼请柬、生日贺卡、秋日市集、金句、商品主图、视频封面、演示封面、播客封面、公众号首图、宣传单、数据盘点、对比表、成长记录;内容位置写在 src/templates/slots.ts 的 FREEFORM_POSTER_SLOTS):
title必填,其余可选:subtitle副标题,body一段正文,details信息行("时间:10 月 18 日 14:00",冒号前放进这一行的标签,没写冒号就整句放进内容、标签去掉),cta按钮文字,tag角标(活动类型、价格、期数),brand主办或落款,image主图(本机路径、http(s) URL 或 data URL,本机路径读进来嵌成 data URL)。- 模板里每一块示例文字都会换成内容,没给的连同它的底板、按钮一起删掉。没给
image时,有照片或插画的海报(讲座、促销、商品主图、公众号首图、宣传单、照片封面、作品集封面、杂志封面)换成只用文字排的一版,summary.layout为text-only:标题放大到原来照片的位置,商品主图的价格、促销的标题放进圆里,宣传单的信息行排进拱门;展览、节气、朋友圈九宫格这类插画本身就是版面,留着。 - 金句、邀请函和只用文字排的海报,标题短时放大到填满它的位置(在词与词之间换行,不在「开发者」「周年庆」这样的词中间断开);内容少的邀请函、金句、证书整列移到中间,信息行比模板少的清单(菜单、价目表、招聘)在原来那几行的范围里居中;菜单、成长记录只写一行、书单只写一本时,这一行留在第一行的位置,空着的行收拢,整列移到页面中间。
- 菜单、价目表一项写成
details的一行,"拿铁:28"冒号前是名称、后面是价格;比模板行数少时各行在原来的范围里拉开,清单不会下半截空着。证书上的姓名放recipient,不给时留一条空白的姓名线,可以手写。课程表放table:第一行是表头(["节次", "周一", …]),之后每行第一格是节次(可以写两行,如"第 1 节\n8:00"),表格按给的行数列数重画、铺满原来的位置,同一个科目同一种颜色,最多 12 行 8 列,画不下的格子列在summary.unplaced。 - 标题比模板示例少几行时,标题下面的内容一起往上移,不在标题下面留一块空。
- 文字放不下时缩小字号(最小 72%,角标里的几个字最小 50%,好留在角标里),列在
summary.shrunk,还放不下的在summary.overflowing。两三行的标题断得一样长,只在词和词之间换行;在原来的字号下按词断不开时,标题缩小一点(同样不低于 72%)。 - 内容不会悄悄丢掉:信息行比模板多时,页面有空就照最后一行的样子往下接着排(让开按钮、署名等,不出页边),没空就放进最后一行下面空着的署名、正文或副标题位;模板没有位置的文字放进空着的同类位置——按钮、署名、角标的字放进空着的信息行,正文、获得者放进空着的副标题,副标题放进空着的正文。放到哪里写在
summary.moved(node是图层名)。还是放不下的信息行和表格格子列在summary.unplaced,没有位置的字段列在summary.unused,这时summary.hint写明哪些内容不在海报上、该怎么办(换一个posterCapacity有这些位置的模板,或生成后用apply_actions加)。 - 套图模板传给它会提示改用
create_document_from_content,反过来也一样。
create_document_from_content 和 create_document_from_outline 把内容放进模板画好的位置(src/templates/slots.ts 为每个自由画布模板的封面、内页、结尾页标明了标题、正文、要点、引文、页码和目录的位置):
- 模板里每一块示例文字都会换成你的内容,或者连同只为它画的色块、线条一起删掉,生成的卡片里不会留下模板原话;英文刊头、编号这类装饰照原样保留。
- 页码按页序更新(
02、03…;「完成进度 03 / 03」这类写法同时更新总页数)。 - 要点先放进模板的条目位:
"要点:说明"冒号后面放到这一条的第二行,太长又没写冒号时在第一个逗号处分成两行。条目占用旁边的空位、缩到 72% 还放不下时,要点改放进清单或正文。要点比条目多时,多出来的接在下方的正文里;正文在条目上方的模板,接到最后一条后面,保持阅读顺序。 - 封面的目录位(清单模板)列出各页标题;没有目录位的封面没给副标题时,导语位列出各页标题(像杂志封面的要目)。
- 八套套图模板(包括 16:9 的演示套图「聚光」和「答辩」)都按每页的内容挑版式,
summary.pages[].layout写着用了哪一种:一段话或一两条要点用观点页(页码放大、标题、正文),三条以上要点用清单页(要点少时在行间匀开,最多 1.6 倍行距),只带一个数据元素、最多一两条要点的用数据页(元素放大,摆在留给它的地方正中,颜色取这一页的正文色和最显眼的颜色,用路径画的色块也算,进度画成圆环,六条以上的时间线分成两栏并排、左栏多一条),有引文的用内页;结尾页只有一两句话时用收尾页(字放大摆在中间)。page.layout可以指定section/statement/list/data,模板没有这种版式时用内页。编辑部内页没有正文时,下面的要点和引文往上挪进正文的位置。 - 模板里个别文本框画得盖住了旁边的条目或下面的说明(示例字短,碰不到)。填内容时这种框先收到那段文字为止,长文字在自己那一栏里换行,不会压上去。
- 文字放不下时先占用下方或上方的空位(不越过它所在的卡片、不压到别的元素),再缩小字号,最小到原字号的 72%;仍放不下的列在
summary.overflowing,请删短或换一个容量大的模板。标题只在词和词之间换行,窄栏里最多排六行;在原来的字号下按词断不开时缩小一点(同样不低于 72%)。被缩小的文字列在summary.shrunk。 - 排成两三行的标题、要点和引文在词的边界上断得一样长(「先把睡眠时间 / 固定下来」,而不是一整行下面挂一个「下来」),不让标点打头,有逗号顿号时优先在它后面断;自己写了换行的照原样。信号模板封面的标题分成上下两段大字,也按两段排出来一样宽来分。
- 没有给结尾页(
ending,或大纲里的## 结尾:标题)就不出结尾页。 - 不同页可以用不同的套图模板:
pages里某一项写了templateId,这一页就用那个模板的内页版式;ending.templateId换用那个模板的结尾页。封面和没写的页用整套的templateId,summary.pages[].templateId标出每页的版式来自哪个模板。混用后可以用document/restyle统一配色和字体。
大纲写法:
# 总标题
封面副标题(# 下面、第一个 ## 之前的文字)
## 小节标题
- 要点
- 要点:这一条的说明
正文段落(不带列表标记的行)
> 引文 —— 出处
## 结尾:结尾页标题
- 结尾页的要点模板可以一页一页加进已有文档,和编辑器模板面板里点一页一样,所以一份文档里每页可以来自不同的模板:
add_template_pages:任意自由画布模板的某几页,pages从 1 起(套图是 1 封面、2 内页、3 结尾,可以重复,比如[2, 2]加两张内页),不给就整套。默认加在最后,afterSlideId加在某页后面,replaceSlideId换掉某一页(比如一张空白页)。页面保留模板自己的尺寸和示例文字:用inspect_document找到图层、apply_actions改字,check_document会把没改的示例文字报出来。返回added(每页的slideId、name、在文档里是第几页、宽高),默认附上这几页的缩略图。- 要按内容直接填好:套图的页用
create_document_from_content每页的templateId(见上文),海报用create_poster_from_content加上documentId(和afterSlideId),海报保留自己的尺寸,成为那份文档的一页。 - 底层动作是
{ type: 'slide/insert', slides, afterSlideId?, replaceSlideId? },apply_actions也能直接用来插入自己画好的整页:每页按读取文档的规则校验,页 id 不能和文档里已有的重复,第一页成为当前页。
check_document 用无头浏览器把每一页按导出的样子排出来(嵌入与导出相同的字体),再结合文档本身列出问题(AI 客户端从 get_guide 的 check 主题读到同样的清单):
| kind | 说明 |
|---|---|
text-overflow |
字被文本框裁掉了笔画,附 fitFontSize(实测不再裁字的最大字号)。只看字形本身:行高超出文本框、但笔画都还在框里的不算。 |
text-overlap |
两段文字叠在一起。 |
covered-text |
文字被上层不透明的色块或图片挡住三成以上。图形按它真正画到的地方算(渲染时在图形的框上取 32×32 个点,看哪些落在填充或描边上):圈住文字的手绘圈只有圈边压到字,不算挡住;正片叠底的荧光笔也不算。图形本身旋转了时按它的框算。 |
off-page |
文字跑出页面。 |
low-contrast |
纯色文字和身后的纯色底板、页面背景或背景图(取文字身后那块图片的平均颜色)对比度低于 4.5:1(48 号以上或 40 号以上粗体放宽到 3:1);高亮片段的字色和它的高亮色之间同样按这个标准算。圈在环形图形里的字和页面比,不和圈的颜色比。 |
sample-text |
还是模板里的示例文字。 |
empty-text |
空文本框。 |
image-failed |
这一页有图片没加载出来。 |
path-overflow |
图形画到了自己的框外:viewBox 没有包住 d 用到的坐标。改法里直接给出正好包住图形的 viewBox。 |
empty-path |
图形既没有填充也没有描边,看不见。 |
tiny-qrcode / low-contrast-qrcode |
二维码小于 120×120,或码点色和背景色对比低于 3:1,手机扫不稳。 |
progress-label-overflow |
进度条的目标名比元素宽。 |
data-clipped |
表格的格子、时间线的条目放不下,被省略号截掉了(写明第几行第几列、第几条)。 |
tiny-text |
正文小于 18px;表格、时间线、图表、进度条里的字小于 18px 也算。 |
low-contrast(数据元素) |
表格、时间线、图表、进度条的文字颜色(ink)和它们底下的颜色(压着的不透明色块或页面背景)对比低于 4.5:1,改法给出该用浅色还是深色。 |
misalignment |
几个对象的边差 4–10px:文字按它的对齐方式比(左对齐比左边、居中比中线、右对齐比右边),其他对象比左右边和中线;已经在一条线上的、套在卡片里的、模板自己的版式都不算。往模板页上加东西时,以模板的节点为准,只挪加进来的。 |
edge-margin |
对象离页边只有 2–14px,不像出血也不像留白。 |
too-many-fonts / too-many-colors / weak-heading |
一页的字体超过 3 种、文字颜色超过 6 种,或最大的字不到正文的 1.25 倍。 |
empty-band |
页面上有一整条横向的空白,高过页高的四分之一,看着像没排完(模板原样的页面最多空到两成出头)。整块排在中间、上下留白相差不到页高的 10%、每边不超过 35% 时,上下的留白不算;算整块在不在中间时,文字旁边不到页面 5% 的小图形(证书角上的奖章,和它所在的组一起算)不算进去。写明从哪到哪、占页高多少。文字按排出来的字行算,其他按画出来的框算;卡片、色块按里面放的东西算,自己不算,东西排在它正中(上下留白相差不到它高度的 20%)时整张卡算满;横线算它所在的那一行,小点缀和铺满六成以上页面的底色块不算;背景是图片的页不查。 |
empty-frame |
一块纯色的方块或圆(占页面 5% 以上、不到六成)上面几乎什么都没有,多半是该放图片的位置空着;虚化的光晕、半透明色块和渐变不算。改法给出把它换成图片填充的写法。 |
每条问题带 page、slideId、node(图层名)、path(apply_actions 用的节点路径)和中文改法。内置模板原样保留的装饰文字、模板自己采用的配色不算问题,所以报告里只有内容带来的问题。fix: true 自动处理两类:text-overflow 改成 fitFontSize(记在 fixed),misalignment 按报告里的挪法吸齐(记在 snapped);改完再检查一遍并保存(用 documentId 时就地更新,返回新的 version),附剩下的问题。颜色交给客户端用 apply_actions 改。
除工具外,服务器还以只读资源(resources/list / resources/read)暴露说明与示例,客户端可以按需取用而不必从工具描述里拼凑:
| URI | 内容 |
|---|---|
dingcard://schema/freeform |
自由画布当前版本(v44)的文档模型与校验规则说明。 |
dingcard://schema/actions |
FreeformAction 动作联合类型说明(apply_actions 的入参结构)。 |
dingcard://templates |
内置模板全集:每个模板的完整信息(与 list_templates 用 ids 取到的相同)。 |
dingcard://examples/freeform |
完整自由画布 v44 文档示例(编辑部模板实例)。 |
dingcard://icons |
内置图标全集:每个图标的 id、中英文名、关键词与 24×24 路径数据 d。 |
dingcard://decorations |
内置装饰素材全集(与 list_assets 的 decorations 不带筛选时相同)。 |
dingcard://examples/markdown |
完整 Markdown 文档信封示例(专栏模板实例)。 |
path 节点(v15)画任意矢量图:图标、徽章、对话气泡、波浪分隔线、曲线箭头、折线图、不规则色块。
d是 SVG 路径数据(M/L/H/V/C/S/Q/T/A/Z及小写相对命令,必须以M/m开头,最长 20000 字符),写在viewBox坐标系里;渲染时图形拉伸铺满节点盒。节点盒与viewBox宽高比相同就不变形,不同就跟着盒子拉伸,描边始终粗细均匀。fill是 ColorPaint(纯色、线性渐变、径向渐变)、{ type: 'transparent' },或(v19 起){ type: 'image', src, fit, framing }图片填充——任意轮廓(手绘线、图标、贴纸)变成图片框,取景与形状图片填充同一套(node/update-style改framing);v18 及更早的输入带图片填充会被拒绝。stroke是#RRGGBB;strokeWidth用viewBox单位,随图形一起缩放,0即不描边。可选dash(viewBox单位)、cap、join(round/miter/bevel)、fillRule(nonzero/evenodd,画圆环这类镂空图形用evenodd),以及通用的opacity、shadow、filter、blendMode。viewBox要正好包住d用到的坐标,画到框外check_document会报path-overflow并给出合适的viewBox。- 换图形用
node/update-content的{ d, viewBox },改样式用node/update-style。
内置图标先用 list_assets(kind: "icons")查,再把返回的 example 改好 id、位置和颜色,用 node/insert-children 插进去:
{
"id": "icon-check", "name": "对勾", "locked": false, "hidden": false,
"type": "path", "x": 120, "y": 120, "width": 96, "height": 96, "rotation": 0, "scale": 1,
"d": "M20 6 9 17l-5-5",
"viewBox": { "x": 0, "y": 0, "width": 24, "height": 24 },
"fill": { "type": "transparent" }, "stroke": "#18181b", "strokeWidth": 2
}图标是 Lucide 的线性图标(ISC 许可,来自 Feather 的部分为 MIT 许可),每个合并成一条路径,存放在 src/freeform/iconLibrary.ts,由 scripts/icons/build-icon-library.mjs 生成。
手绘圈、下划线、箭头、贴纸、标签这些不用自己写 d:list_assets(decorations)查到 id,add_decorations 一次放好。
{
"documentId": "…",
"items": [
{ "decoration": "circle-scribble", "x": 70, "y": 230, "width": 520 },
{ "decoration": "marker-band", "x": 80, "y": 600, "width": 640, "below": "标题" },
{ "decoration": "burst-badge", "x": 820, "y": 90, "width": 200, "text": "限时", "rotation": 8 },
{ "decoration": "sparkles", "x": 900, "y": 1220, "width": 120, "color": "#ffb000" }
]
}- 盒子就是画出来的范围:
x、y是左上角,width是宽,高度按aspect算;stretches为true的单笔画(下划线、波浪线、荧光笔等)可以用height拉伸。不给width就按页面短边的默认比例。 color换掉装饰本来的主色:手绘线条和贴纸整体换色,标签换底色、字自动取白或黑(大字白字要有 3:1 的对比度)。text换标签上的字,给不是标签的装饰会在notes里说没用上。- 默认放在最上层;
below填同一页一个顶层节点的 id 或图层名,就放在它下面一层(荧光笔、胶带垫在字下面)。荧光笔是正片叠底,压在字上也看得清,check_document不算它挡住文字。 - 一笔画成的放进去是一个
path节点,由几部分组成的(多色贴纸、带字的标签)是一个组,组里的字是文本图层,用inspect_document找到路径后可以用node/update-content改。放完用check_document看有没有压住文字、对比度够不够。
- 文字片段
spans除了bold、color,v16 起还有highlight(荧光笔式高亮底色,#RRGGBB)和underline(true)。几种样式可以叠在同一段上,片段之间按start排序、不能重叠:
{ "text": "三步做出好看的封面", "spans": [{ "start": 0, "end": 2, "bold": true, "highlight": "#fef08a" }] }- v20 起片段还有
strike(true,删除线)和fontSize(1–4096 px,这几个字自己的字号)。价签不用再拆成几个文字框:划掉的原价和放大的现价写在一段里,一行的行高按最大的字撑开。改整段的fontSize时片段字号按比例跟着变:
{ "text": "原价 ¥129 现价 ¥59", "fontSize": 40, "spans": [{ "start": 3, "end": 7, "strike": true, "color": "#9ca3af" }, { "start": 12, "end": 14, "fontSize": 96, "bold": true }] }v20 起 text 里的每个换行分出一段:
list: 'bullet' | 'number'每段一个列表项,圆点或编号由叮卡画,换行的第二行和第一行的字对齐,空段不算一项。圆点和编号不要写进text(写了会出现两个)。paragraphSpacing(px)是段与段之间多出的距离,代替空行。align: 'justify'两端对齐,中文正文用它,每段最后一行照常靠左。verticalAlign: 'middle' | 'bottom'让文字在比它高的框里垂直居中或靠下——按钮、色块上的字和框一样大即可,不用再算y。
{ "type": "text", "text": "准备好咖啡豆\n水温 92 度,先闷蒸 30 秒\n两分半钟内完成", "list": "number", "paragraphSpacing": 12, "lineHeight": 1.4 }-
node/update-style里verticalAlign: 'top'、paragraphSpacing: 0、list: null都是清除;check_document量的是加上圆点缩进和段间距之后的样子。 -
页面背景 v16 起可以是图片:
{ type: 'image', src, fit: 'cover' | 'contain', framing: { focusX, focusY, zoom } },画在所有节点下面。图片地址要能被浏览器加载(URL 或 data URL),加载失败时check_document报image-failed。文字直接压在背景图上时,check_document取文字所在那块图片的平均颜色来算对比度,看不清就报low-contrast(改法提示换一个和照片反差大的颜色,或在文字下面垫一块半透明色块);跨站且没有 CORS 的图片读不出像素,这时不报,请看render_document的缩略图确认。
apply_actions 的 { type: 'document/restyle', palette?, fontSet?, colors?, fonts? } 一次改所有页面,和编辑器「风格」面板是同一个动作:
{ "type": "document/restyle", "palette": "night-flight", "fontSet": "editorial", "colors": { "#d94836": "#ff5a36" } }palette/fontSet取list_assets(styles)的 id;looks里每套搭配给出一对palette和fontSet,两个一起传就是这套搭配。配色把页面底色换成新底色、正文色换成新文字色,深浅灰按原来在两者之间的位置取色,其余颜色依次换成强调色,再把因此看不清的字调深或调浅(按check_document的对比度门槛);字体组合把不小于正文字号 1.4 倍(headingScale)的文字换成标题字体,其余换成正文字体。colors({ "#原色": "#新色" })和fonts({ "原字体": "新字体" })精确替换,键是文档里现有的颜色和字体,可以从inspect_document的style里取;颜色在文字、片段标色和高亮、填充、描边、投影、渐变色标和文字效果里一起换。同时给时先套palette/fontSet,再按colors/fonts覆盖(键仍指原来的颜色和字体)。精确替换不会自动调对比度,换完用check_document看一遍。- 和其他动作一样,不认识的 id、不合法的颜色或什么都没换时,这一步被忽略(
changes里是false)。
- 文字节点 v17 起可以带一个
effect:neon发光、outline字外描边、hollow镂空、splice错位、offset硬投影、echo回声、glitch故障、extrude立体、background每行后面的底色块、marker荧光笔;amount(0–100)按字号比例算大小,所以同一个效果放在什么字号上都合适。写法见工具描述里的文档模型,node/update-style的effect: null去掉。 - 想直接用现成的样子,
list_assets(text_styles)给出 16 套花字,把其中的patch交给node/update-style:
{ "type": "node/update-style", "slideId": "cover", "updates": [{ "path": ["title"], "patch": { "textFill": { "type": "solid", "color": "#ffffff" }, "effect": { "type": "background", "color": "#18181b", "amount": 40, "radius": 30 }, "fontWeight": "bold", "stroke": null, "strokeWidth": null, "shadow": null } }] }- 深色
backdrop的花字(霓虹、故障、立体、极光、金色)放在深色页面上才好看。check_document会把带底色块的文字和底色块比对比度,带字外描边的文字只要描边和字反差够就不报看不清。
fontFamily写字体名或字体栈;内置字体(苹方、思源黑体、思源宋体、霞鹜文楷、站酷小薇、系统宋体、系统默认,以及标题用的 Playfair Display、Archivo Black、Great Vibes、站酷黄油体、马善政毛笔)在编辑器和渲染里都有。渲染在本机的无头浏览器里进行,本机装了的字体也能用名字直接写。- 编辑器里「导入字体」的字体只存在导入它的那个浏览器里,MCP 渲染时没有,会退回默认无衬线字体;要交给别人的卡片用内置字体。
所有节点的 rotation 都绕节点盒中心顺时针旋转(编辑器画布与导出渲染一致)。线段节点本身是「盒内的一条水平线段」:要画 A→B 的线段,设 L=|AB|、rotation=atan2(By−Ay, Bx−Ax)(度)、width=L+2×strokeWidth、height 取一个小正值(如 strokeWidth×2.2),再把盒子居中放到线段中点——x=(Ax+Bx)/2−width/2、y=(Ay+By)/2−height/2——圆头端点就恰好落在 A 与 B。把 x/y 当作端点、或按绕左上角旋转来推几何,都会让斜线整条错位。
最省事的是 open_in_editor(见上文)。也可以把 get_document 写出的 .json 文件导入:在工作台「我的项目」点击「导入 JSON」,或把文件拖进页面。自由画布文档(v1–v43,旧版自动迁移为 v44)和 Markdown 文档都会存为项目,并在对应的编辑器里打开;非法文件会给出可读的错误提示。由此形成完整闭环:
AI 生成文档 → 导入叮卡精修 → 编辑器导出 PNG
dingcard-mcp 发布在 npm 上,包里带着构建好的前端,装好后不依赖仓库。任何支持 stdio MCP 的客户端加上这段配置即可:
{
"mcpServers": {
"dingcard": { "command": "npx", "args": ["-y", "dingcard-mcp"] }
}
}需要 Node.js 22+ 和本机的 Google Chrome,见下文「环境与配置」。
dingcard-mcp 也登记在 MCP 官方注册表,名字是 io.github.lottshin/dingcard,以这个注册表为来源的客户端和目录都能搜到。
Claude Code 和 Codex 用一行命令接入:claude mcp add dingcard -- npx -y dingcard-mcp(Codex 把 claude 换成 codex)。叮卡工作台顶栏的「接入 AI」按客户端列出这些写法,可以一键复制。
仓库内的 mcp/ 是独立 npm 包(不进入前端依赖树):
npm --prefix mcp ci
npm --prefix mcp run build
npm run mcp # 等价于 npm --prefix mcp start,以 stdio 启动服务器npm --prefix mcp pack 打出的就是发布到 npm 的包:打包前会构建前端,放进包里的 dist/app。
客户端也可以直接接仓库里的服务器(在仓库根目录执行):
{
"mcpServers": {
"dingcard": {
"command": "npm",
"args": ["--prefix", "mcp", "start"]
}
}
}| 变量 | 默认 | 说明 |
|---|---|---|
DINGCARD_DIST_DIR |
<仓库>/dist |
渲染使用的构建产物目录。在仓库里、目录里没有 render.html 时会自动执行一次 npm run build;不在仓库里时用 npm 包自带的 dist/app;自定义目录缺失则直接报错。 |
DINGCARD_APP_URL |
(空) | open_in_editor 在这个叮卡编辑器里打开文档;不设时用服务端自带的编辑器。 |
DINGCARD_APP_PORT |
5390 |
服务端自带编辑器的端口(被占用时换一个空闲端口)。 |
DINGCARD_CHROME_PATH |
(空) | 渲染和检查用这个浏览器,例如 Linux 上的 /usr/bin/chromium;不设时找系统安装的 Google Chrome。启动失败时回退到系统 Chrome。 |
DINGCARD_FONTS_URL |
(空) | 网页字体从哪里下载:替代 https://fonts.googleapis.com,路径照旧。连不上 Google Fonts 时设成能访问的地址,例如 Google 字体的 .cn 地址 https://fonts.googleapis.cn。 |
DINGCARD_FONT_CACHE_DIR |
~/.cache/dingcard/fonts |
下载过的网页字体存在这里(设了 XDG_CACHE_HOME 时放在它下面的 dingcard/fonts),之后渲染直接用,不再联网。 |
其他要求:
- Node.js 22+(与主项目一致)。
- 浏览器:渲染优先使用系统安装的 Google Chrome(与 E2E 测试同一策略,零下载)。没有 Chrome 时会尝试默认 Chromium;两者都不可用会返回明确的错误信息。也可另行执行
npx playwright install chromium。 - 网页字体:渲染页用到的网页字体(思源宋体、思源黑体、霞鹜文楷等)从 Google Fonts 下载,下载过的存在本机(见
DINGCARD_FONT_CACHE_DIR),之后不再联网。字体请求由浏览器自己去取,系统代理照样生效。每个请求最多等 10 秒,连不上或超时就用系统字体排版和出图,这次调用剩下的字体也不再等;check_document、render_document、render_markdown、create_document_from_html和share_document的结果里会多一个fonts,写明哪些字体换成了系统字体。 - npm 包自带构建好的前端。从源码运行时,首次渲染前需要一次前端构建(自动触发,产物会被后续渲染复用)。
- 静态文件服务器只监听
127.0.0.1的随机端口,带路径穿越防护,渲染结束即关闭。open_in_editor的服务器也只监听127.0.0.1,交出去的文档地址带随机 id,只保留最近 16 份。 - MCP 服务器只读写本地文件(输出目录、
documentPath、图片路径由调用方指定),另外把下载的网页字体存进字体缓存目录;只有设了DINGCARD_SERVER_URL,share_document和server_projects才连接那台叮卡服务端的账号、作品和图片。 create_document_from_html把网页放在没有脚本权限的沙箱框架里排版:网页里的脚本、事件处理器和<noscript>都去掉,只读它排出来的样子;网页引用的网络图片和字体照常加载。本机文件只读网页里写到的图片和样式表。- stdout 只承载 JSON-RPC 协议,构建日志与诊断一律走 stderr。
render_document仅支持自由画布文档(v44 文档;v1–v43 输入自动迁移);render_markdown仅支持 Markdown 文档信封。- 自由画布文本节点的可选
spans富文本片段(局部加粗、标色、高亮、下划线、删除线、字号)和段落排版(两端对齐、垂直位置、段间距、列表)在渲染与校验中与编辑器一致支持;编辑器内改动文字时片段会按编辑位置自动保留或收缩。 - 模板只有仓库里内置的这几套(社区通过 PR 共建,见 docs/templates.md)。需要渲染自己的文档时,把文档直接传给
render_document/render_markdown。 - 文档中的图片
src(自由画布)可以是浏览器可加载的 URL、data URL 或本机文件路径(自动嵌入);Markdown 文档的图片通过信封的images映射(img:<id>→ data URL)提供,本地文件请先转为 data URL。 - 文档句柄只存在服务器进程里,重启后清空;
open_in_editor目前只交自由画布文档。 - 一次调用串行渲染全部所选页面,没有并发渲染池;
check_document同样要启动一次浏览器(一套 4–6 页的卡片约几秒)。 - 生成整套卡片只按模板画好的位置排版,不会改版式:内容明显超过模板容量时,换一个
capacity更大的模板,或把内容拆成更多页。 create_document_from_html读的是浏览器排好的样子,不是网页的布局规则:转出来的文字框按原来的宽度换行,之后改字只在框里重新换行,不会像网页那样推动下面的元素;需要整体重排的改动,改网页再转一次更省事。- Markdown 平台头部中的时间戳(微博/推特)按渲染时刻生成,与编辑器导出行为一致。