Skip to main content
Glama

geogebra-mcp-bridge

独立程序:MCP over stdio,把浏览器里的 GeoGebra 交给任何支持 stdio 的 MCP 客户端。 不依赖 DeepSeek Harness。

GeoGebra 是浏览器里的 GWT 应用,只认页面上的 ggbApplet JS API,没有跨进程接口。 所以本程序自己托管一个查看器页面,页面加载完 GeoGebra 后经 SSE 连回来, 页面上的 API 就是全部可操作能力:

        MCP client (stdio) ──►  geogebra-mcp-bridge  ──► 127.0.0.1:3460
                                  │                        ├─ /            查看器页面
                                  │                        ├─ /client.js   页面侧的桥
                                  │                        ├─ /GeoGebra/** GeoGebra 离线包
                                  │                        ├─ /events      SSE 下行(发命令)
                                  │                        ├─ /rpc         页面回投结果
                                  └────── MCP tools ───────┴─ /api/status   自检
                                       mcp__geogebra__*        /api/pending  等用户确认「已打开」
                                                               /api/pending/confirm  用户的选择

下载

git clone https://github.com/myLiLingyun/GeoGebraMCPServer.git

本仓库不包含 GeoGebra 离线包。GeoGebra 官方离线包约 32 MB、解压后 100 MB 以上, 按 GeoGebra 自己的非商业许可分发,所以请从官方地址单独下载(见下)。

Related MCP server: lunar

要求

  • Node.js ≥ 20(node --version 确认)

  • Git(或直接下载上面的 ZIP)

  • 一个本机浏览器:Firefox / Chromium / Edge 均可,用来打开查看器页面

  • GeoGebra 离线包 5.4.932.1:解压后含 GeoGebra/deployggb.js 与 GeoGebra/HTML5/5.0/web3d/

  • 可选:DeepSeek Harness,用来获得「数学模式 / 数学模式+」和网页确认框; 不装 DSH 也能作为普通 MCP server 使用

程序不启动浏览器,也不跑 GeoGebra 内核:没有页面连上来时,工具返回一句可读的 中文,并给出查看器地址。canvas_create 会先把地址交给用户的网页界面(确认框), 本机有图形环境时也可以自己把浏览器拉起来(见下)。

MCP 客户端断开(stdin 收到 EOF/close)或进程收到 SIGTERM / SIGINT / SIGHUP 时, 桥会关掉 HTTP 服务并退出,把查看器端口放掉——HTTP 监听会把事件循环撑住,不主动退出 就会留下一个仍占着端口的孤儿进程。

安装

推荐把两个仓库和离线包放在同一个父目录里,这样桥的默认路径能直接找到离线包:

geogebra/
├── GeoGebraMCPServer/            ← 本仓库
├── GeoGebraDSHPlug/              ← DSH 插件(可选)
└── geogebra-math-apps-bundle/    ← GeoGebra 官方离线包
    ├── README.txt
    └── GeoGebra/
        ├── deployggb.js
        └── HTML5/5.0/web3d/

1. 下载两个仓库

mkdir -p ~/geogebra && cd ~/geogebra
git clone https://github.com/myLiLingyun/GeoGebraMCPServer.git
git clone https://github.com/myLiLingyun/GeoGebraDSHPlug.git   # 只用 MCP server 时可跳过

只想用 MCP server 时,也可以把 ZIP 解压到任意目录,后面的路径按实际位置替换。

2. 下载并解压 GeoGebra 离线包

官方下载地址(当前指向 geogebra-math-apps-bundle-5-4-932-1.zip):

cd ~/geogebra
curl -L -o geogebra-math-apps-bundle.zip \
  https://download.geogebra.org/package/geogebra-math-apps-bundle
unzip geogebra-math-apps-bundle.zip -d geogebra-math-apps-bundle

也可以从 https://www.geogebra.org/download 的 “GeoGebra Math Apps Bundle” 下载。 解压后确认这一层文件存在:

test -f ~/geogebra/geogebra-math-apps-bundle/GeoGebra/deployggb.js && echo OK

离线包放在别处也可以,只要启动时用 --ggb-root 或 GGB_ROOT 指过去。 仓库的 .gitignore 已排除它,不要把离线包提交进 git。

3. 安装 Node 依赖

cd ~/geogebra/GeoGebraMCPServer
npx --yes pnpm@12 install

没有 pnpm 时 npm install 也可以(会生成 package-lock.json,已被忽略)。 运行时依赖只有 @modelcontextprotocol/server 与 zod。

4. 自检

node test/list-tools.mjs

这条不需要浏览器和 GeoGebra 内核,会按 stdio 启动桥、列出 14 个工具、跑完协议断言。 要连真浏览器和真 GeoGebra 再跑:

node test/e2e-browser.mjs       # 需要 firefox;产物写到 ./verify/

接到 MCP 客户端

任何支持 stdio 的 MCP 客户端都可以直接用。配置里的路径换成你机器上的绝对路径:

{
  "mcpServers": {
    "geogebra": {
      "command": "node",
      "args": ["/home/you/geogebra/GeoGebraMCPServer/bin/geogebra-mcp-bridge.mjs"],
      "env": {
        "GGB_ROOT": "/home/you/geogebra/geogebra-math-apps-bundle"
      }
    }
  }
}
  • 按上面的推荐布局安装时,GGB_ROOT 可以省略(桥默认找仓库上一级的 geogebra-math-apps-bundle/)。

  • Windows 把 command 改成 node.exe 的绝对路径,路径分隔符用 \\ 或 /。

  • 客户端连上后会出现 mcp__geogebra__* 工具;先调 canvas_create,再打开它返回的地址。

  • 接 DeepSeek Harness 不用手写这段配置,装 GeoGebraDSHPlug 即可, 它会在「数学模式 / 数学模式+」里自动挂载本桥。

运行

cd ~/geogebra/GeoGebraMCPServer

node bin/geogebra-mcp-bridge.mjs                       # 在 stdin/stdout 上提供 MCP
node bin/geogebra-mcp-bridge.mjs --help                # 用法
node bin/geogebra-mcp-bridge.mjs --version             # 版本
node bin/geogebra-mcp-bridge.mjs --port 3461 --ggb-root /path/to/geogebra-math-apps-bundle

选项 / 环境变量

说明

--ggb-root <目录> / GGB_ROOT

GeoGebra 离线包目录,默认与本仓库同级的 geogebra-math-apps-bundle/

--port <端口> / GGB_BRIDGE_PORT

查看器 HTTP 端口,默认 3460;被占用时往后顺延(最多 20 个)

--host <地址> / GGB_BRIDGE_HOST

监听地址,默认 127.0.0.1(只监听回环)

-h, --help / -V, --version

本地回答,不启动任何服务

启动后按 stderr 上打印的地址打开查看器,页面加载完 GeoGebra 就会自动连上桥。 用 canvas_create 工具建画布时,工具返回里会带上该画布专属的地址(?canvas=cN&app=…)。

查看器页面

页面参数都在 URL 查询串里:

参数

说明

app

classic(默认,带 CAS)/ 3d / suite(带 CAS)/ geometry。选择流程见下方「界面」

toolbar

off 隐藏工具栏

input

off 隐藏代数输入栏

menu

on 显示菜单栏(默认隐藏)

lang

界面语言,默认 zh_CN

perspective

初始布局串(如 G);给了它,CAS 预热结束后会切回它

页面右上角有个连接状态小徽章,显示 GeoGebra 版本与对象数;控制台里 window.__GGB_BRIDGE__ 暴露 clientId、api() 与 events() 便于手工排查。

页面侧只做四件事:保证 applet 就绪、把方法名映射到 API、把异常变成可读文本、 把构造与用户交互记成事件流。所有业务判断与排版都在 MCP 进程里。

工具(14 个)

工具

说明

status

自检:HTTP 服务、离线资源、画布清单(id / 界面 / 地址 / 是否连上);不碰页面,随时可跑

canvas_create

建画布:返回 id + 要打开的地址;app 决定界面。默认会登记一条「等你打开画布」的请求并挂住这次调用,用户在 DSH 网页界面的弹窗里点「已打开」之后才返回(见「确认框」一节)

canvas_release

释放画布:让页面 ggbApplet.remove() 真正拆除,再删掉这个 id(顺带作废还在等确认的请求)

help

五段说明:bridge / commands / cas / views / events

objects(需 id)

对象清单(名称/类型/可见/颜色/值/定义),可按类型过滤

object(需 id)

单个对象详情(定义、非本地化写法、LaTeX、坐标、样式…)

events(需 id)

用户操作与构造变化,读取即清空

screenshot(需 id)

出图给用户看:view: "applet"(默认)截整个 applet 窗口,view: "graphics" 只截活动图形视图(干净的 2D 插图)

view(需 id)

读:工具、可见坐标范围、网格/坐标轴、视口像素、布局;写:范围/网格/轴/布局/工具/缩放

eval(需 id)

执行 GeoGebra 命令(主力);engine: "cas" 走符号计算

set(需 id)

按属性名设对象样式或数值(19 种属性)

edit(需 id)

delete / rename / clear / undo / redo / showall

state(需 id)

整份构造的 get / set / save / load(xml / base64 / json)

export(需 id)

PNG / SVG / PDF,写文件或直接返回内容;png 可用 view 选图形区(默认)或整窗

GeoGebra 没有「成功/失败」信号,eval 用的是「有没有变化」

这一点值得单独说清楚,因为它决定了 eval 的返回怎么写:

  • evalCommand(cmd) 对 SetColor / Delete / ZoomIn 这类不新建对象的命令返回 false, 哪怕它确实执行成功了;

  • evalCommandGetLabels(cmd) 对这类命令与真正的语法错误返回的都是 null。

所以页面侧的判据是「有没有产生可观察的变化」:新建了对象,或者构造/视图 XML 变了, 就算成功;什么都没变,才报「没有产生任何变化,多半是语法或参数错了」。 构造 XML 超过 1 MB(比如内嵌了图片)时放弃比对,返回「无法判断」,让模型自己用 objects 复核。代价是每次 eval 多取一次 getXML(),对正常规模的构造可以忽略。

另一个实测结论:自动命名会撞车。Segment(A,B) 会自己挑一个空闲小写字母 (常常正好是 f),此后 f(x)=x^2 会把那条线段改写成函数。eval 的返回里会列出 新建对象的真实名字,照它往下写就不会错;要更稳就给对象显式命名(s=Segment(A,B))。

CAS 需要先打开一次 CAS 视图

evalCommandCAS 在 CAS 视图从未打开过时一律返回 "?"。查看器因此在加载时做一次 预热:短暂打开 CAS 视图,探到引擎就绪(用 evalCommandCAS("1+1") 探)再切回目标布局。 只有 classic / suite 带 CAS;3d / geometry 会明确报「CAS 没有返回结果」。 另外 CAS 会把结果行(例如积分常数 c_{1})作为对象放进构造,返回里会列出这些新对象名。

canvas_create 不说假话

它默认分两步:先在用户的网页界面弹确认框(登记请求 → 等用户打开地址 → 等用户 点「已打开」),确认之后才返回 id;没人能点确认时退回老路径。老路径的判据是结果:

  • 本进程没有图形环境(linux 且 DISPLAY / WAYLAND_DISPLAY 都没设)→ 立刻告诉你 弹不出窗口、请手动打开地址,不去做注定失败的尝试;

  • 否则依次试 browser(命令行,可带参数,如 "firefox --new-window",默认 firefox、xdg-open),再用最多 8 秒等页面连上来:连上了才算成功,没连上 就报错并给出地址与退出码。

浏览器进程「启动成功」不等于「有窗口看得见」:在没有 DISPLAY 的进程里,firefox 会立刻退出,spawn 却不会报错。这条是实测改出来的:DSH 进程没有 DISPLAY,早期 实现回了「已用 firefox 打开」,模型为此白跑了 5 步、最后只能问用户要地址。

确认框(等用户把画布打开)

canvas_create 建好画布之后默认不立刻返回,而是登记一条请求并挂住这次调用, 让用户真的把画布打开、再确认已经打开,然后才把 id 交给模型:

Agent: canvas_create
   │  桥建好 c1,登记一条等待请求(默认最多等 5 分钟)
   │
   ├─ 浏览器界面(DSH 的 geogebra-bridge-ui 插件)轮询 GET /api/pending
   ├─ 弹窗里给出蓝色可点的画布地址;页面连上之前「已打开,继续」是灰的
   ├─ 用户点开地址 → 页面连回桥 → 弹窗状态变「页面已连上」
   └─ 用户点「已打开,继续」→ POST /api/pending/confirm {id, action:"opened"}
         → 挂住的调用解除,工具返回「用户已在…确认框里点过「已打开」」

工具参数:

参数

说明

confirm

是否弹确认框,默认 true;false 直接走老路径

confirmTimeoutMs

等用户点确认的上限,默认 300000(5 分钟);夹在 5 秒 – 30 分钟之间

headless

自动化路径:用无界面浏览器打开,同时等于关掉确认框

三条兜底(都不会把模型永久卡住):

情况

reason

结果

用户点「跳过,先继续」/ 按 Esc

skipped

立刻返回 id,说明用户跳过了

等了一小段(默认 4 秒)仍没有任何界面在轮询;或界面已经超过 10 秒没来问过

no-ui

走老路径:自己试 firefox / xdg-open,拉不起就把地址给用户

界面在,但用户一直没点

timeout

超时返回 id,提示用 status 看连接状态,不要重复建画布

期间画布被 canvas_release

gone

返回「请求作废」

「界面在不在」是实际观测出来的:GET /api/pending 每被问一次就刷新一次时间戳。 所以脱离 DSH 单独用这个桥(没有界面插件)时,行为就是老行为,不会白等。

挂到 DSH 时 toolCallTimeoutMs 要大于 confirmTimeoutMs,否则等确认的调用会先被 宿主掐断(bundle 里用 600000)。

画布(canvas):显式句柄

一个「画布」= 一个浏览器里跑着的 GeoGebra 页面,用短 id 指代(c1、c2…)。 这是为了消掉「同时开着好几个页面时,工具到底操作哪一个」的歧义——以前按「最近活跃」 猜,现在由调用方明确指定:

canvas_create {app: "3d"}     →  画布 c1 已建好(3d 界面)。
                                  地址:http://127.0.0.1:3460/?canvas=c1&app=3d
                                  默认先在网页界面的确认框里等用户点「已打开」;
                                  没人能点确认时退回「自己拉起浏览器 / 把地址给用户」
objects {id: "c1"}            →  读 c1 的画布
eval    {id: "c1", command: "A=(1,2)"}
canvas_release {id: "c1"}     →  页面里的 applet 被真正拆除,记录删除
  • 页面通过 URL 上的 ?canvas=c1 认领画布;/events、/hello 都把 id 带回来, 桥据此把 SSE 通道绑到画布上。一个画布同时只认最后一条连接(页面刷新会重连)。

  • 地址里没有 canvas 参数的手工页面会被自动分配 auto-1、auto-2…,也能用。

  • 画布在页面断开后记录保留(用户刷新后还能接回来),只有 canvas_release 才删。 释放时桥会先让页面自己调 ggbApplet.remove()(真正回收内核内存)再删记录; 页面也是在结果回投送达之后才拆通道。

界面:classic 与新版

同一个离线包里装着两套界面,用查看器 URL 的 ?app=(工具的 app 参数)选:

app

界面

3d

3D 计算器(新版界面里专门画 3D 的那个);需要可用的 WebGL

geometry

几何应用:网格与坐标轴默认关闭,命令面是「几何 + 无 CAS」

suite

计算器套件(新版现代界面):函数、数列、统计、表格、符号计算,带 CAS

classic(默认)

老式 Classic:顶部一整条工具栏、左侧代数区、菜单挤在顶上一行,多视图布局最全

推荐的选择流程(按顺序判断,命中就停;插件提示词里也要求模型照此执行):

  1. 需要 3D 绘图 / 立体 → 3d

  2. 不需要坐标网格的几何问题 → geometry

  3. 可以只用 suite 完成任务 → suite

  4. 其它(要经典多视图布局、构形协议,或用户点名要经典界面)→ classic

选错 app 只能 canvas_release 后重建(appName 在页面加载时定死),所以建画布前先定下来。

带 CAS 的只有 classic / suite;3d / geometry 的符号计算会明确报「CAS 没有返回结果」。

appName 只能在页面加载时决定,换 app 就是新开一个页面:那是一份空构造,而且之后的 工具会操作新页面。所以要换界面之前先 state save,切过去再 state load,或者干脆 canvas_create 建第二个画布(各个画布互不干扰),换完用 canvas_release 收掉旧的。

3d 就是 3D 计算器(新版界面里专门画 3D 的那个)。它和 3D 视图一样依赖浏览器有可用的 WebGL:没有 WebGL 时页面根本起不来(appName: "3d" 实测 180 秒也连不上桥), suite/classic 里的 3D 视图则会渲染成一片空白。status 与 view 会直接把这件事报出来 (3D 渲染:不可用(…) / 浏览器 WebGL:不可用(…)),不要靠猜。

关于 3D:先看 WebGL,再谈别的

3D 视图、3D 计算器(?app=3d)、以及 3D 内容的导出,前提都是浏览器有可用的 WebGL。 本机的无界面 Firefox 实测就是 WebGL:不可用(about:config 里 webgl.disabled 或驱动被禁), 所以在这个环境里:?app=3d 连不上、3D 视图像一片空白、导出也拿不到 3D 内容。

在真实浏览器里请先确认:

  • 打开 about:support,看 Graphics → WebGL 1 Driver Renderer 是不是正常值(不是 “WebGL is disabled / unavailable”);

  • 或者访问 https://get.webgl.org/,它直接告诉你浏览器支不支持;

  • 被禁了就在 about:config 里把 webgl.disabled 设为 false、webgl.force-enabled 设为 true 再重启浏览器(无 GPU 的虚拟机/远程桌面通常还要软件渲染兜底)。

WebGL 可用之后,?app=3d 才是真正的 3D 计算器,view(perspective: "3D") 才会真的画出 3D。

导出 3D 内容(在无 WebGL 环境下的结论,有 WebGL 时未复测)

在没有 WebGL 的环境里,导出接口拿到的是图形视图(2D)的画布:

为什么 3D 出不了图

实测结论:GeoGebra 的导出接口只能读图形视图(2D)的画布,3D 视图(WebGL 画布)的内容 拿不到。 三条路径都验证过:

调用

在无 WebGL 环境里的结果

getPNGBase64(scale, transparent, DPI, copyToClipboard, greyscale)

只有图形区,参数里没有选视图的余地

getScreenshotBase64(callback, scale)

整个 applet 的布局(代数面板 + 图形面板),3D 面板位置是空的

getThumbnailBase64()

同样是图形视图的渲染

在 perspective: "3D"(视图 XML 确认「视图 1 关、视图 16 开」,即只剩 3D)下截图,出来的 仍然是那张 2D 网格,平面与法向量都不在图里。

注意这个结论的适用范围:它是在没有 WebGL 的环境(本机无界面 Firefox)里得到的——3D 面板 本来就没渲染出东西,自然也无从抓取。用户的真实浏览器已确认支持 WebGL,那里的表现尚未 定论;切布局后不重算画布是另一个很可能的原因,现在 view 工具会补 recalculateEnvironments,待复测。 WebGL 可用时这里没有复测过,不能断言 GeoGebra 一定抓不到 3D。所以 screenshot / export 的说明只写「本环境下 3D 面板拿不到」,并让你先用 status 看 WebGL 那一行。

给 3D 内容出图的实际做法:先确认 WebGL 可用并复测导出;不行再把 3D 几何投影到 2D 重画, 或者让用户自己截窗口。

超时

情况

上限

观测类(objects / object / events / view / set)

20s

eval(含 CAS)

60s

state get/set/save/load

120s

export / screenshot

60s

canvas_create 等用户在确认框里点「已打开」

默认 300s(confirmTimeoutMs 可调)

挂到 DSH 时 toolCallTimeoutMs 要大于这里最长的那一项(bundle 里用的是 600000), 否则长调用会先被宿主掐断。

HTTP 接口(调试用)

方法

路径

说明

GET

/

查看器页面

GET

/client.js

页面侧的桥

GET

/GeoGebra/**

GeoGebra 离线包(严格限制在该目录内)

GET

/events?clientId=…

SSE 下行通道,页面在这里收命令

POST

/hello

页面自报身份

POST

/rpc

页面回投命令结果

GET

/api/status

纯 HTTP 自检(curl 也能看):name / protocol / startedAt / 画布清单

GET

/api/pending

现在有哪些画布在等用户确认打开;被问一次就刷新「有人在看弹窗」的时间戳

POST

/api/pending/confirm

用户在弹窗里的选择:{id, action:"opened"|"skip"};请求已不在时返回 409

curl -s http://127.0.0.1:3460/api/status | head -c 400
curl -s http://127.0.0.1:3460/api/pending
curl -s -X POST -H 'content-type: application/json' \
  -d '{"id":"o1","action":"opened"}' http://127.0.0.1:3460/api/pending/confirm

DSH 侧的界面插件通过同源路由 /api/dsh-geogebra-bridge/{pending,confirm,status} 访问这几条(见 GeoGebraDSHPlug); 桥自身仍然只监听回环地址。

当库用

exports 指向 src/server.mjs:

import { createServer, start, OpenRequests, SERVER_INFO, DEFAULT_GGB_ROOT } from 'geogebra-mcp-bridge';

// 只要 MCP server 实例(自己接传输)。不传 openRequests 时确认框永远走「没人看」的分支,
// 也就是老行为:自己拉起浏览器 / 把地址交给用户。
const server = createServer({ hub, viewerUrl: () => 'http://127.0.0.1:3460/' });

// 想自己接了确认界面,就把登记表也传进去(HTTP 侧的 /api/pending 也用它):
const openRequests = new OpenRequests({ timeoutMs: 300_000 });
const server2 = createServer({ hub, openRequests, viewerUrl: () => 'http://127.0.0.1:3460/' });

// 或完整启动(HTTP 查看器 + stdio MCP + 确认接口)。
// confirmTimeoutMs 是等用户点「已打开」的上限,默认 300000。
await start({ port: 3460, confirmTimeoutMs: 300_000 });

自检

node test/list-tools.mjs        # 离线:不需要浏览器与 GeoGebra 内核
node test/e2e-browser.mjs       # 真机:无界面 Firefox + 真 GeoGebra,产物写到 ./verify/
  • test/list-tools.mjs:检查命令行开关;按 stdio 启动桥并列出 14 个工具,把每个 inputSchema 送进 DSH 的 schema 子集校验器(宿主只支持 type/oneOf/properties/required/additionalProperties/items/enum/const + 注解, 多一个关键字就整体注册失败);再起一个「假页面」按同一份协议回投结果, 跑完 97 条断言,覆盖成功路径、错误路径、文件读写、页面断线,以及确认框的 全流程(登记 → 不存在/非法动作被拒 → 确认「已打开」→ 确认「跳过」→ 超时 → headless 不登记)。

  • test/e2e-browser.mjs:用无界面 Firefox 打开查看器,让真 GeoGebra 5.4.932.1 连上来, 再用 MCP 工具画图、读对象、改样式、算 CAS、导出 PNG/SVG/PDF、存取构造、撤销重做, 产物落在 ./verify/(已在 .gitignore 里)。

schema 子集校验器取自 DSH 安装目录(DSH_CHECKOUT 可覆盖),没有就自动跳过那一步。

许可证

  • 代码(src/、bin/、web/、test/、package.json):GPL-3.0-only,见 LICENSE

  • 文档(README.md):CC BY-NC-SA 4.0,见 LICENSE.CC-BY-NC-SA-4.0

GeoGebra 离线包本身为非商业用途许可,见 https://www.geogebra.org/license。

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Exposes any stdio-based MCP server to the internet via HTTP/SSE transport, enabling remote agents to access MCP tools over a network.
    12 npm
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Browser-based geometry processing server that enables AI agents to create geometry, run analysis and operators, inspect results, and take screenshots via MCP.
    48 npm
    6
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    A local stdio MCP server for a self-hosted GeoGebra 5.0 Web bundle. It enables executing GeoGebra commands with structured feedback, querying commands, evaluating CAS, managing state, and exporting images using headless Chromium.
    12
    25 npm
    MIT