关于 maidr
maidr-mcp 在 ChatGPT 和 Claude 对话中展示无障碍图表,盲人和低视力读者可通过键盘、屏幕阅读器、声音和盲文进行探索。模型可导航图表数据、运行支持的图表命令,并原位更新图表。
主要功能
- 01在对话中显示和更新图表
- 02支持柱形、折线、散点等图表
- 03根据模型请求导航数据点
- 04运行支持的图表命令
- 05报告读者当前图表位置
- 06通过语音、声音和盲文播报图表内容
适用场景
- 01无需依赖视觉探索图表
- 02请模型跳转到指定数据点
- 03按需切换盲文或播放图表
- 04在保留视图的情况下替换图表
MCP 相关
MCP 服务向模型提供图表显示、更新、导航、数据和命令工具。图表视图将模型的工具调用转交给 maidr,并返回结果。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
10收录的工具列表 · 更新于 1 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| chart | any | 是 |
详细介绍
maidr-mcp
一个 MCP 服务,可在 ChatGPT 和 Claude 对话中显示易于访问的 maidr 图表,并让对话中的模型带领读者浏览图表,替他们运行图表命令。
模型使用数据调用 show_chart,图表便会作为 MCP App 出现在对话中。盲人或低视力读者按 Tab 键进入图表,并通过他们探索任何 maidr 图表的方式进行探索:方向键、屏幕阅读器、声音化、盲文。随后,模型会完成以下三件事:
- 模型可以带读者前往某处。 当读者询问“最高的柱子”时,模型会调用
maidr_get_layer_data找到它,再调用maidr_navigate跳转至该处,maidr 会通过语音、盲文和声音播报该数据点。当读者是在向模型输入,而不是在图表中输入时,模型会传入focus: true,因为读者希望模型带他们前往该处;maidr 会将键盘焦点移至图表中以完成跳转。 - 模型可以替读者按下图表按键。 当读者要求关闭盲文、播放图表或跳转至最低值时,模型会调用
maidr_list_commands查找相应命令和读者当前的模式,再调用maidr_run_command执行命令。maidr 会像读者亲自按下按键一样播报结果,模型则会告诉读者按的是哪个键。 - 模型知道读者所在的位置。 读者移动时,图表会将其位置告知模型,因此询问“这个数据点是什么?”无需调用工具。在一个轮次内,
maidr_list_charts会实时读取该位置。
实验性。 已在 MCP Apps SDK 的参考宿主中完成端到端检查(下文)。尚未在 claude.ai 或 ChatGPT 中检查。
工作原理
-
show_chart使用 matplotlib 和 py-maidr 在服务端绘制图表,因此模型发送的是数字,而非 SVG。SVG 会传到工具结果的_meta中的图表视图,不会添加到模型的上下文中。 -
视图从
cdn.jsdelivr.net加载 maidr.js 和 MCP Apps SDK。该域名是其 CSP 声明的唯一域名,且使用固定版本。 -
maidr.js 会在
document.modelContext上注册自己的 WebMCP 工具。目前还没有宿主会将视图的工具交给模型(见 ext-apps#797),因此视图会自行提供document.modelContext。每个同名的服务端工具都会将调用转发给当前打开的视图:- 模型调用服务端工具;
- 视图轮询仅供应用使用的工具,接收调用、运行 maidr 的工具,并将 maidr 的回答发回;
- 该回答会成为模型的工具结果。
轮询采用长轮询:服务端会保持请求最长 20 秒,直到收到调用,因此调用能立即传到图表。如果宿主中断或拒绝保持这么久的请求,视图就会改用短轮询;服务端会立即响应,每 2 秒一次,因此模型的调用仍会在服务端等待图表的 10 秒内得到响应。视图会在一分钟后再次尝试长轮询;每次被宿主中断后,等待时间都会加倍。
如果宿主像 ext-apps#798 提议的那样,为 MCP Apps 采用 WebMCP,maidr 的工具就能直接传到模型,转发机制也可以移除。
-
每当读者在图表中按下按键,视图都会通过
ui/update-model-context发送其位置。 -
update_chart会为一个viewId绘制新图表,并将其转发给该视图,由视图原位替换:maidr 会释放旧图表(包括其中仍在等待读者执行的移动或命令),并绑定新图表。如果有操作正在等待,图表的状态行会说明该操作已被取消,update_chart也会告知模型,让模型可以收回对读者的承诺。服务端会保留最新图表,因此视图如果错过替换,或因移出视野后被宿主重新挂载,就能在下一次轮询时同步更新。 -
除了将读者送回被
update_chart替换的图表中,视图绝不会自行移动读者的焦点。只有当模型传入focus: true时,maidr 才会将焦点移入图表;服务端的指令和工具描述会告知模型,只有在读者要求立即将其带到某处或为其执行某项操作时才这样做,绝不能因为读者离开了图表就这样做。读者在图表外时,模型执行的任何其他移动或命令都会等待读者;如果传入focus: true却未能将读者带入图表(focused: false),该操作也会等待。读者进入图表前,视图的状态行会显示:“助手有一项待执行的移动:按 Tab 进入图表以听取指示。”update_chart执行后,同一行会显示“图表已更新:”,直到读者通过按 Tab 或传入 focus: true 进入图表;如果同时有移动或命令等待执行,状态行会同时显示两者。
工具
| 工具 | 由谁调用 | 功能 |
|---|---|---|
show_chart | 模型 | 绘制图表并显示。返回其他工具所需的 viewId。 |
update_chart | 模型 | 绘制新图表,并在该 viewId 对应的视图中替换原图表。不添加视图。如果读者正在图表中,新图表仍会保留其所在位置;其他人则保持焦点不变。系统会通知所有人图表已更改。 |
maidr_list_charts | 模型 | 返回图表的图层、数据点数量;如果读者正在图表中,还会实时返回其所在位置。静默执行。 |
maidr_get_layer_data | 模型 | 返回图层中一页数据点,每个数据点都带有 maidr_navigate 所需的 target。静默执行。 |
maidr_navigate | 模型 | 将读者移至某个数据点并播报。对于请求跳转到该位置的读者,如果使用 focus: true,maidr 会先将其键盘焦点移入图表,并返回 focused: true;如果无法做到,则返回 focused: false,并等待读者自行移动。 |
maidr_list_commands | 模型 | 返回读者可用的命令,包括每个命令的 id、标题、按键,以及模型能否运行该命令;还会返回读者当前的模式(文本、声音、盲文、自动播放等)。静默执行。 |
maidr_run_command | 模型 | 运行读者的某个命令,例如 toggle_braille、autoplay_forward 或 go_to_max_value,效果如同读者按下对应按键,并播报结果。打开对话框或文本字段的命令由读者自行操作。对于此时请求执行命令的读者,如果使用 focus: true,maidr 会先将其键盘焦点移入图表,并在读者听到当前位置半秒后依次运行命令(applied: "queued",focused: true);如果无法做到,则返回 focused: false,并等待读者自行操作。 |
maidr_view_poll、maidr_view_reply、maidr_view_svg | 仅图表视图 | 传递中继消息,以及用于 update_chart 和会移除 _meta 的主机的 SVG。 |
show_chart 和 update_chart 接受以下图表类型,这些类型均被 py-maidr 标记为稳定。每种类型都对应下表所示的 maidr 图层类型:
type | 字段 | maidr 图层 | 模型能否将读者移至该图层 |
|---|---|---|---|
bar | categories、series(一个系列、多个并排系列,或 stacked) | bar、dodged_bar、stacked_bar | 可以 |
line | x(数字或标签)、series | line | 可以 |
step | x、series、where(post、pre 或 mid) | step | 可以 |
scatter | x、y,以及表示最小二乘直线的 trend | point,以及表示趋势线的 smooth | 可以移至数据点,不能移至直线 |
histogram | values、bins | hist | 可以 |
box | groups | box | 不可以 |
violin | groups | violin_box、violin_kde | 不可以 |
heatmap | x_labels、y_labels、values、z_label | heat | 可以 |
pie | categories、values(从 12 点钟方向开始顺时针读取) | pie | 不可以 |
candlestick | dates、open、high、low、close | candlestick | 不可以 |
每种类型也都接受 title、x_label 和 y_label。饼图没有坐标轴,因此这些字段用于标注扇区内容和数值含义。
模型会使用 maidr_get_layer_data 读取每个图层的所有数据点。如果最后一列为“不可以”,maidr 就不会为这些数据点提供 target:读者可通过按键在数据点间移动,而 maidr_navigate 会返回 layer not navigable。
示例提示词
连接服务后,读者可以用日常语言提出请求;无需提及工具名称。以下提示词按读者正在执行的操作分组,并注明模型预期调用的工具。可将数据粘贴到对话中,也可以让模型使用之前聊天中已有的数字。
显示图表
| 场景 | 提示词 | 图表 |
|---|---|---|
| 销售报告 | “用柱状图显示我的季度收入:Q1 为 120,Q2 为 180,Q3 为 150,Q4 为 210,单位为千美元。” | bar |
| 比较组别 | “按月份并排比较这两家门店:North 为 40、52、61、58,South 为 35、49、66、70,时间为 1 月至 4 月。” | bar,两个系列 |
| 总量构成 | “按季度堆叠显示今年的能源构成:煤炭、天然气、太阳能和风能。数字如下。” | bar、stacked |
| 随时间变化的趋势 | “用折线图绘制上周每日气温:周一至周日分别为 18、21、19、24、26、23、20 度。” | line |
| 多条趋势 | “用下表绘制 2015 至 2025 年美国、韩国和德国的失业率折线图,每个国家一条线。” | line,三个系列 |
| 逐级变化的值 | “用阶梯图显示我们全年定价档位:3 月前为 9 美元,8 月前为 12 美元,之后为 15 美元。” | step |
| 相关性 | “根据这 30 名学生的数据,绘制学习时长与考试成绩的散点图,并添加趋势线。” | scatter,带 trend |
| 分布 | “这里有 200 个以毫秒为单位的响应时间。请用 20 个区间绘制直方图。” | histogram |
| 离散程度和异常值 | “绘制对照组、低剂量组和高剂量组的反应时间箱线图。” | box |
| 分布形状 | “用同一组数据绘制小提琴图,这样我就能听出每组数值集中在哪里。” | violin |
| 数值网格 | “绘制网站访问量热力图,按星期和小时排列;将颜色刻度标注为 ‘访问量’。” | heatmap |
| 整体占比 | “绘制我的月度预算饼图:房租占 45%,食物占 20%,交通占 10%,储蓄占 15%,其他占 10%。” | pie |
| 股价 | “绘制这只股票最近十个交易日的开盘价、最高价、最低价和收盘价蜡烛图。” | candlestick |
模型调用一次 show_chart,图表就会以其他工具使用的 viewId 显示。
通过模型探索图表
| 提示词 | 模型的操作 |
|---|---|
| “带我到最高的柱子。” | 调用 maidr_get_layer_data 查找它,然后调用 maidr_navigate 并设置 focus: true;maidr 会通过语音、盲文和声音进行播报。 |
| “哪个月份的气温最低?不要移动我的位置,只告诉我。” | 仅调用 maidr_get_layer_data,并在聊天中回答。 |
| “前往第一个分数高于 90 的点。” | 在图层数据中找到该点,然后移动到那里。 |
| “这个点是什么?”(位于图表中时) | 根据图表报告的位置回答,或通过 maidr_list_charts 实时读取。 |
| “这张图有多少个点?我现在在哪里?” | 调用 maidr_list_charts。 |
| “跳到 South 系列的 Q3。” | 调用 maidr_navigate 前往该系列和类别。 |
| “带我到蜡烛图中收盘价最低的点。” | 说明 maidr 不允许模型将读者移到那里,并改为提供相应按键(见上表)。 |
让模型按下图表的按键
| 提示词 | 模型运行的命令 |
|---|---|
| “开启盲文。” / “关闭声音。” | toggle_braille、toggle_sound |
| “停止朗读文本,我只想听音调。” | toggle_text |
| “从左到右为我播放整张图表。” | autoplay_forward |
| “快一点。” / “停止。” | speed_up_autoplay、stop_autoplay |
| “前往最大值。” / “现在前往最小值。” | go_to_max_value、go_to_min_value |
| “跳到最后一个点。” | move_to_right_extreme |
| “切换到趋势线。” | next_layer |
| “开启高对比度。” | toggle_high_contrast |
模型会先调用 maidr_list_commands,查看读者可以运行哪些命令以及当前启用了哪些模式,然后调用 maidr_run_command,并告知读者下次可以按哪个按键执行相同操作。
在原图表中更改
| 提示词 | 模型会执行的操作 |
|---|---|
| "添加 12 月:230。" | 使用新数据调用 update_chart,并在同一视图中更新。 |
| "现在改为折线图,而不是柱状图。" | 调用 update_chart,并设置 type: "line"。 |
| "将分箱数改为 10,而不是 20。" | 对直方图调用 update_chart。 |
| "按地区拆分。" | 调用 update_chart,为每个地区创建一个系列。 |
原本位于图表中的读者仍会留在图表中,看到新图表,并听到图表已更改的提示。
完整对话
- 家庭作业辅导。 "我是一名盲人,正在准备统计学考试。请为这 50 个考试分数绘制直方图,从左到右播放,让我听出数据分布的形状,然后告诉我它是否偏斜。"模型会显示直方图、运行
autoplay_forward,并根据图层数据作答。 - 快速查看数据集。 "这是一个包含房价和建筑面积的 CSV。请绘制带趋势线的散点图,带我查看最贵的房子,并告诉我它离趋势线有多远。"
- 跟踪股票。 "这里有某只股票过去 10 个交易日的开盘价、最高价、最低价和收盘价。请用蜡烛图显示。哪一天的最高价与最低价之间的区间最大?"接着:"改用折线图显示收盘价,并带我查看那一天。"
- 检查实验。 "请为三个组的反应时间绘制箱线图。哪个组的离散程度最大?开启盲文,并带我查看那个箱体。"模型会根据数据作答并运行
toggle_braille;箱线图无法由模型导航,因此它会告知读者前往该箱体的按键。 - 查看预算。 "先用饼图显示我的支出,再改用按金额从大到小排序的柱状图,并带我查看最大的类别。"
使用方法
maidr-mcp 以同一版本通过三种方式发布:
- PyPI:
maidr-mcp,使用 uv 运行。uvx maidr-mcp会在127.0.0.1:8000/mcp提供 HTTP 服务;uvx maidr-mcp --stdio会提供一个由宿主启动服务的主机。uvx maidr-mcp@latest会获取最新版本,而不是 uv 缓存的版本。 - 容器镜像:
ghcr.io/xability/maidr-mcp,每个版本和latest均有对应标签。它会在 8000 端口的/mcp路径提供 HTTP 服务。 - MCP Registry:
io.github.xability/maidr-mcp,供可从 registry 添加服务的客户端使用。
服务器所需的配置取决于宿主如何连接:
| 宿主 | 连接方式 | 服务器所需配置 |
|---|---|---|
| Claude(网页、Desktop、移动端) | 自定义连接器,需使用远程 MCP 服务 | 公共 HTTPS 地址 |
| ChatGPT | 开发者模式应用 | 公共 HTTPS 地址,或连接到你自己的机器的 Secure MCP Tunnel |
| ChatGPT 桌面应用 | 自带的 MCP 服务,通过 STDIO 启动 | 无需托管;哪些部分尚未测试,请参阅下文 |
使用公共地址
# Make a token once, and keep it: it is the <token> in each host's URL below.
export MAIDR_MCP_TOKEN="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
uvx maidr-mcp --host 0.0.0.0 --port 8000
# or
docker run -p 8000:8000 -e MAIDR_MCP_TOKEN ghcr.io/xability/maidr-mcp
端点为 /mcp,使用 Streamable HTTP;设置令牌后,也可使用 /mcp/<token>。
- **运行一个实例。**中继会在内存中保存每个图表的队列。
- **允许重启。**打开的图表会在重启后的下一次轮询中重新注册。
**Claude。**添加自定义连接器:依次选择 Customize > Connectors > Add custom connector,并填写 https://<your-host>/mcp/<token>。
- **套餐:**所有套餐均支持自定义连接器;Free 套餐允许添加一个。
- **Team 和 Enterprise:**由 Owner 添加。
- **图表的显示位置:**添加连接器后,可在网页、Desktop 和 iOS/Android 上显示。
**ChatGPT。**开启开发者模式,然后为 https://<your-host>/mcp/<token> 创建应用。Plus、Pro、Business、Enterprise 和 Education 账户可在网页上使用开发者模式。
访问令牌
没有令牌,任何能够访问服务的人都可以绘制图表。设置令牌后,除 OPTIONS(CORS 预检、探测)外,每个 HTTP 请求都必须携带令牌,否则会收到 401。可通过 MAIDR_MCP_TOKEN 设置令牌,也可使用 --token-file 或 MAIDR_MCP_TOKEN_FILE 指定一个仅包含令牌的文件,或者使用 --token。通过 STDIO 连接时无需令牌:主机负责启动服务。
请求可通过以下两种方式携带令牌:
- 在 URL 中, 使用
/mcp/<token>。Claude 的自定义连接器和 ChatGPT 的开发者模式应用只接受 URL,不接受 OAuth 以外的其他信息,因此应使用这种形式。 - 在请求头中, 对于能够发送请求头的客户端,在
/mcp上设置Authorization: Bearer <token>。
python3 -c 'import secrets; print(secrets.token_urlsafe(32))' 可以生成合适的令牌。服务会拒绝长度少于 16 个字符,或包含字母、数字和 -._~ 以外字符的令牌。
- URL 就是密钥。 它保存在主机的连接器或应用设置中,任何能在那里看到它的人都可以使用该服务。
- 重启服务即可轮换令牌, 使用新令牌重启服务,然后将新 URL 提供给每个主机。旧令牌会立即失效。
- 这是一个共享令牌,不是 OAuth。 所有获得 URL 的人都共享此令牌,而且无法单独撤销其中某个人的访问权限。目前尚未实现完整的 OAuth。
- 日志。 服务的访问日志会将 URL 显示为
/mcp/<redacted>。服务前面的代理、负载均衡器或平台可能会记录完整 URL;如果客户端支持发送请求头,请使用请求头传递令牌。 - 其他机器用户可以在进程列表中看到
--token。 建议优先使用环境变量或文件:使用 Docker secrets 时,可设置-e MAIDR_MCP_TOKEN_FILE=/run/secrets/<name>。 - 请通过 HTTPS 提供服务。 使用纯 HTTP 时,令牌会以明文形式通过网络传输。
没有公网地址时使用 ChatGPT
通过 Secure MCP Tunnel 连接自己的机器。 ChatGPT 会从 OpenAI 一侧调用应用的 MCP 服务,因此无法直接访问 localhost。OpenAI 的 Secure MCP Tunnel 可将其连接到一台不接入互联网的服务:
- **服务:**在你的机器上运行,可以使用
maidr-mcp(通过 HTTP 在127.0.0.1:8000/mcp提供服务),也可以使用maidr-mcp --stdio。 - **隧道客户端:**OpenAI 的开源客户端与服务一起运行,仅向 OpenAI 发起出站 HTTPS 连接。它需要从 Platform 的隧道设置中获取
tunnel_id,以及一个运行时 API 密钥。 - **应用:**按照该指南的说明,通过隧道在开发者模式中添加应用。
- **运行时间:**使用图表期间,机器必须保持开机状态。
ChatGPT 桌面应用自带的 MCP 服务。桌面应用可以自行启动本地服务。打开“设置”>“MCP 服务”>“添加服务”,选择 STDIO,并输入以下命令。此命令需要 uv:
uvx maidr-mcp --stdio
桌面应用会与 Codex CLI 和 IDE 扩展共用此配置。需要注意两点:
- 尚无文档说明,通过这种方式添加的服务是否会显示 MCP App 的 UI。
- 尚未使用 maidr-mcp 测试这种方式,因此不确定图表是否会显示。
与 maidr skill 搭配使用
maidr-skill 教 AI Agent 用 Python、R 或 JavaScript 编写无障碍图表。两者可以同时安装。服务的说明和 skill 以相同方式划分图表,并且都会告知模型,每种图表只能用一种方式制作:
| 请求 | 交由 |
|---|---|
| 在对话中查看或探索的图表,且属于上述类型之一 | show_chart,以及用于修改图表的 update_chart |
| 绘图代码、文件、笔记本、网页或报告;其他图表类型、多个面板或经过样式设计的图表 | skill |
| 在终端中使用的图表,或在不显示 MCP Apps 的其他宿主中使用的图表 | skill |
这里的 maidr_* 工具只能访问由 show_chart 打开的图表。skill 生成的页面会运行 maidr.js;在支持它的浏览器中,maidr.js 会向其中的 Agent 提供同名工具,且不需要 viewId;说明会告知模型如何区分它们。在 ChatGPT 桌面应用中添加的 MCP 服务也会供 Codex CLI 和 IDE 扩展使用,而这些环境通常也会安装 skill,因此这里的划分尤为重要。
在参考宿主中试用
e2e/run.sh 无需 ChatGPT 或 Claude 账号,即可检查整个流程。它会执行以下三项操作:
- 从 ext-apps(
examples/basic-host,标签为v2.0.3)构建参考宿主。 - 启动此服务。
- 使用 Playwright 驱动两者,模拟模型和读者的操作。
cd e2e && npm install && npx playwright-core install chromium && cd ..
bash e2e/run.sh
设置 MAIDR_MCP_TOKEN 后,脚本会在该令牌后启动服务,宿主通过 /mcp/<token> 访问服务。
它会检查:
- 图表是否显示;
- 模型的调用是否由图表内的 maidr 响应,包括
maidr_list_commands;并且maidr_run_command是否只提供 maidr 列出的可运行命令; - 读者在聊天中时发出的移动和命令是否会等待,图表状态栏是否会显示这一点;读者按 Tab 键进入图表后,移动是否会被播报、命令是否会生效、提示是否会消失;
- 设置
focus: true时,maidr_navigate是否会将正在宿主页面中输入内容的读者带入图表:读者的焦点是否会落在图表中,是否会播报数据点,状态栏是否像按 Tab 键进入时那样清空,以及读者是否可以从那里开始用方向键移动。在 10 秒内,maidr 不会再次改变读者的焦点:带focus: true的移动和命令会返回focused: false并等待,状态栏会显示它们。之后,带focus: true的命令会将读者带入图表,返回queued,并在等待的操作之后运行; - 方向键操作是否会被播报,以及读者的位置是否会作为模型上下文传递给宿主;
- 读者在图表中时发出的移动和命令是否会立即生效,以及
maidr_list_charts是否会向模型提供命令将读者带到的位置; update_chart是否会在当前视图中修改图表,而不新增视图:在图表外的读者是否保持焦点,在图表内的读者是否仍留在图表中,是否会告知两者,以及 maidr 是否只读取新图表;旧图表中等待读者操作的操作是否会随之取消,模型和状态栏是否会说明这一点,以及新图表中等待执行的移动是否会加入状态栏提示;- 错过更新的视图是否会在下一次轮询时追上,并继续使用长轮询;
- 是否没有控制台错误或 CSP 违规;
- 步进图、提琴图、饼图和 K 线图,以及带趋势线的散点图是否都会显示;maidr 是否会按上表中的层级读取每种图表;按下 ArrowRight 是否会播报第一个数据点;模型请求的移动是否会被播报,或在上表规定的情况下被 maidr 拒绝;
- 宿主停止长轮询时,视图是否会回退到短轮询,仍能响应模型(包括
update_chart),并在图表关闭时停止轮询。
MAIDR_JS_FILE=/path/to/maidr/dist/maidr.js bash e2e/run.sh 会使用本地构建的 maidr.js 代替固定版本,运行相同的检查。
限制
- 图表报告的位置会随读者的下一条消息到达模型。 宿主会在下一轮应用
ui/update-model-context,因此在一轮之内,模型上下文会漏掉模型自身的移动和命令,也会漏掉模型回答期间读者的移动。maidr_list_charts会实时读取位置,服务端的指令会告诉模型:当读者询问“这个点”且上下文可能已过时时,应调用它。仍然存在的问题:只有读者停留在图表中时,maidr 才知道位置,因此读者向模型输入内容时,最后一次报告的位置仍然有效;而跳过调用的模型只会根据上下文回答。 - 除非读者要求跳转到那里,否则移动或命令会等待。 如果没有
focus: true,读者向模型输入内容时发出的移动或命令会被保留,等读者按 Tab 键返回图表后再执行:先移动,然后每隔半秒执行一条命令。工具会返回applied: "on-next-focus",模型也会收到提示,告知读者这一点;图表的状态行也会显示这一信息,使用屏幕阅读器浏览对话的读者可以找到它。如果使用focus: true,模型会被告知仅在读者提出要求时才传入此参数,maidr 会将读者的焦点移入图表,并在那里执行操作。仍然存在的问题:- Safari 不允许。 WebKit 会阻止另一个页面框架中的图表获取焦点,除非读者亲自按键或点击。因此在 Safari 中,maidr 会返回
focused: false,读者需自行按 Tab 键进入图表,并会发现待执行的移动或命令。Chromium 和 Firefox 则允许图表获取焦点。 - 每 10 秒最多一次。 maidr 在同一页面上每 10 秒最多为模型移动一次焦点,避免读者离开图表后立即被拉回。过早发出的调用会返回
focused: false,并像未使用focus的调用一样等待。每个图表视图都是独立页面,因此此限制不会跨图表生效:模型可以立即将读者带入第二个图表,只有模型收到的指令能阻止这种情况。 - 宿主自身的对话框。 图表无法看到宿主显示在对话上方的对话框,因此 maidr 无法避免从对话框中移走焦点。模型会收到提示,在对话框打开时不要传入
focus: true;除此之外没有其他限制。 - 最多等待 8 条命令。
- Safari 不允许。 WebKit 会阻止另一个页面框架中的图表获取焦点,除非读者亲自按键或点击。因此在 Safari 中,maidr 会返回
- 每次调用
show_chart仍会新增一个图表。 Claude 每次调用带有 UI 的工具时,都会挂载一个新视图,并保留之前的视图。只有模型按服务端指令调用update_chart时,发生变化的图表才会留在原视图中;第二次调用show_chart会创建第二个视图。 - 中继会轮询。 每个打开的图表每 20 秒发出一次请求;如果宿主会切断保持打开状态的请求,则每 2 秒发出一次。在后一种情况下,模型的调用也可能需要额外 2 秒才能到达图表。如果宿主还将每个视图的调用次数限制在每分钟少于 30 次,就会有部分模型调用得不到响应。目前尚不清楚哪些宿主会限制来自视图的调用。同一个图表同时打开两次(例如在两个标签页中)时,每个副本都会把另一个副本的轮询视为请求被切断,因此两者都会改用较短的轮询间隔。
- 访问权限使用一个共享令牌,且只有在你设置令牌时才启用。 未设置令牌时,任何知道服务端 URL 的人都能绘制图表。设置
MAIDR_MCP_TOKEN后,每个请求都必须携带该令牌;Claude 和 ChatGPT 会将其保存在 URL 中。该 URL 保存在宿主的连接器设置中,因此它本身就是秘密;轮换令牌意味着使用新令牌重启服务端,并更新每个宿主。尚未实现完整的 OAuth。请参阅访问令牌。只有持有图表的viewId才能读取或控制图表;它是一个随机的 24 字符令牌。 - 支持十种图表类型,不接受绘图代码。 模型会为上述某种类型发送数据,服务端负责绘制。服务端刻意不接受绘图代码,因为任何知道其 URL 的人都可能在其中运行代码。py-maidr 的实验性绘图类型暂不提供,等它们经过读者测试后再加入。其余内容由 maidr 技能提供。
show_chart无法判断图表是否显示出来。 由于无状态请求不一定会说明客户端能显示什么,因此在所有宿主中,它的响应都相同。在不显示 MCP Apps 的宿主(例如终端)中,响应仍会说图表正在显示。指令和工具说明都会告诉模型不要在此类宿主中调用它;除此之外没有其他限制。
网络和数据
- **服务端:**接收模型发送的用于绘制图表的数据。它会将图表最新的 SVG 保存在内存中,直到图表 15 分钟未收到轮询请求,并且除了 HTTP 访问日志(其中 URL 中的令牌显示为
/mcp/<redacted>)之外,不会记录任何数据相关信息。 - **图表视图:**从
cdn.jsdelivr.net加载 maidr.js 和 MCP Apps SDK。图表中的 maidr 自带 AI 聊天功能与其他场景下的工作方式相同,使用读者自行添加的密钥。
开发
uv sync
uv run ruff check . && uv run ruff format --check .
uv run pytest
bash e2e/run.sh
maidr 的发布如何进入 maidr-mcp
- **maidr.js,**在图表视图中使用,其版本由
server.py中的MAIDR_JS_VERSION指定。update-maidr.yml会在 npm 发布后约一小时内更新该版本;通过下方检查后,基于main创建的安装包或镜像就会加载此版本,下一次每周发布会将其推送到 PyPI 和 GHCR。 - **py-maidr,**用于绘制图表,每次
uvx或 Docker 安装时都会重新解析版本,范围为maidr>=1.26,<2,因此 PyPI 发布新版本后,新安装即可使用该版本。同一工作流更新的uv.lock会固定 CI 和检出测试所使用的 py-maidr 版本。
.github/workflows/update-maidr.yml 每小时检查一次 npm 和 PyPI。新的 maidr.js 必须带有来自 maidr 发布工作流的 npm 来源证明,并提供可由 npm audit signatures 验证的 Sigstore 签名。随后会针对新版本运行 ruff、Python 3.10 和 3.13 上的 pytest,以及 e2e/run.sh;只有全部通过后,工作流才会将更新提交到 main:当 maidr.js 固定版本发生变化时,提交信息为 fix(deps):,因为这会发布给用户;如果只更新了 uv.lock,则提交信息为 chore(deps):。如果 maidr.js 更改了可运行命令,e2e/run.sh 会因此失败,不会让过时的 RunnableCommand 随更新发布。检查失败时,不会推送任何内容,并会创建一个 issue,列出版本、失败步骤和运行记录。对于可能下次就能通过的失败,例如下载失败或浏览器安装失败,会每小时重试一次,持续一天;对于重试也只会重复失败的情况,例如新版本检查未通过,则会暂缓更新,直到 issue 关闭或手动运行工作流。达到或超过该更新版本的更新提交到 main 后,issue 会自动关闭。每次更新都会配对 npm 最新的 maidr.js 和 PyPI 最新的 py-maidr,因此 maidr.js 发布失败期间,只会尝试与其配对的新 py-maidr,uv.lock 也会等待;issue 会说明如何单独更新 py-maidr:手动运行工作流,并确保 maidr.js 使用 main 当前已加载的版本。
按设计,工作流会直接提交到 main,而不是创建拉取请求。maidr.js 和 py-maidr 都会在各自的仓库中经过审查和测试后再发布。此仓库需要检查的是:服务端、视图以及读者浏览图表的流程是否仍能正常工作。也就是说,工作流会在提交前通过 e2e/run.sh 检查播报、焦点和盲文,以及其他所有项目。使用工作流自身的令牌创建的拉取请求不会触发任何工作流,包括 ci.yml。它只会等待某人合并已经通过相同检查的内容,而其中的 maidr.js 修复也会因此一并等待。如需自行检查每次更新,请禁用该工作流,然后使用 scripts/update_maidr.py 在拉取请求中更新固定版本。
如需选择其他版本,可在 Actions 选项卡中手动运行工作流并指定 maidr.js 版本;或者在检出目录中运行 uv run --no-project python scripts/update_maidr.py update --maidr-js <version> --allow-lower,然后创建拉取请求。下一次每小时运行时会再次提高较低的固定版本;因此,如需将 maidr.js 固定在较旧版本,请在 Actions 选项卡中禁用该工作流,直到 maidr 发布修复版本。
许可证
与 maidr 的其余部分一样,采用 GPL-3.0-or-later 许可证。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。