MathHub 是一个面向数学建模学习、竞赛备赛和工程实践的 AI 平台。项目将数学模型知识库、Markdown 学习笔记、竞赛信息、学习路径、能力测评与可执行的 Modulus 数学建模智能体 集成在同一个 Web 应用中。
相关代码已在https://github.com/AI-Dog-Creater/MathHub开源。

与只负责回答问题的聊天机器人不同,Modulus 可以在用户授权的工程目录内读取资料、编写和修改代码、运行 Python、检查结果、调用 MCP 外部服务,并把模型、图表和结论导出为 Word 或 PDF 数模论文。
当前版本主要面向本地运行、个人学习、竞赛团队协作和功能演示。账户、会话与大部分模型配置保存在浏览器本地,不是完整的云端多用户系统。
主要功能
| 模块 | 当前能力 |
|---|---|
| 社区首页 | 数模主题内容展示、竞赛与学习入口、动态视觉元素 |
| 模型库 | 按类别浏览数学模型,查看原理、场景、限制、案例及 Python/MATLAB 示例 |
| 数模学习笔记 | 新建、搜索、标签、置顶、归档、导入、导出和自动保存 Markdown 笔记,并可同步到用户选择的本地文件夹 |
| 竞赛中心 | 通过本地封面图片展示常见数学建模竞赛、时间线、难度、参赛要求与历年题入口 |
| 学习路径 | 为新手、进阶学习和竞赛训练提供阶段化路线 |
| 能力测评 | 从问题抽象、模型选择、数学基础、算法、编程和论文表达等维度生成雷达图与 AI 建议 |
| 配置中心 | 接入阿里云百炼、OpenAI、Anthropic 文本模型,以及百炼视觉、生图和视频模型 |
| Modulus 智能体 | 绑定工程、读取文件、检索内容、修改代码、执行 Python、调用 Skills/MCP、生成 Word/PDF |

左侧导航栏支持记忆折叠状态。收起后仍保留 MathHub 品牌和各模块图标,鼠标悬停可查看栏目名称,主内容区会自动占用释放出来的空间。
技术栈
前端
- React 19、TypeScript、Vite
- Tailwind CSS 4、Motion、Lucide React
- React Markdown、remark-gfm、KaTeX、remark-math、rehype-katex
- Recharts
- File System Access API
服务端
- Node.js、Express、tsx、esbuild
- Multer、Mammoth、Word Extractor、PDF.js、Tesseract.js、Sharp、XLSX
- JSZip、KaTeX、Puppeteer
- MCP TypeScript SDK、AJV、PostgreSQL Client
模型接入
- 阿里云百炼 OpenAI 兼容接口
- OpenAI Chat Completions
- Anthropic Messages API
- Google Gemini(环境变量默认能力与兼容回退)

整体架构
#mermaid-svg-CiQnIX8H7kdXpOHy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CiQnIX8H7kdXpOHy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CiQnIX8H7kdXpOHy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CiQnIX8H7kdXpOHy .error-icon{fill:#552222;}#mermaid-svg-CiQnIX8H7kdXpOHy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CiQnIX8H7kdXpOHy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CiQnIX8H7kdXpOHy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CiQnIX8H7kdXpOHy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CiQnIX8H7kdXpOHy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CiQnIX8H7kdXpOHy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CiQnIX8H7kdXpOHy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CiQnIX8H7kdXpOHy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CiQnIX8H7kdXpOHy .marker.cross{stroke:#333333;}#mermaid-svg-CiQnIX8H7kdXpOHy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CiQnIX8H7kdXpOHy p{margin:0;}#mermaid-svg-CiQnIX8H7kdXpOHy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CiQnIX8H7kdXpOHy .cluster-label text{fill:#333;}#mermaid-svg-CiQnIX8H7kdXpOHy .cluster-label span{color:#333;}#mermaid-svg-CiQnIX8H7kdXpOHy .cluster-label span p{background-color:transparent;}#mermaid-svg-CiQnIX8H7kdXpOHy .label text,#mermaid-svg-CiQnIX8H7kdXpOHy span{fill:#333;color:#333;}#mermaid-svg-CiQnIX8H7kdXpOHy .node rect,#mermaid-svg-CiQnIX8H7kdXpOHy .node circle,#mermaid-svg-CiQnIX8H7kdXpOHy .node ellipse,#mermaid-svg-CiQnIX8H7kdXpOHy .node polygon,#mermaid-svg-CiQnIX8H7kdXpOHy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CiQnIX8H7kdXpOHy .rough-node .label text,#mermaid-svg-CiQnIX8H7kdXpOHy .node .label text,#mermaid-svg-CiQnIX8H7kdXpOHy .image-shape .label,#mermaid-svg-CiQnIX8H7kdXpOHy .icon-shape .label{text-anchor:middle;}#mermaid-svg-CiQnIX8H7kdXpOHy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CiQnIX8H7kdXpOHy .rough-node .label,#mermaid-svg-CiQnIX8H7kdXpOHy .node .label,#mermaid-svg-CiQnIX8H7kdXpOHy .image-shape .label,#mermaid-svg-CiQnIX8H7kdXpOHy .icon-shape .label{text-align:center;}#mermaid-svg-CiQnIX8H7kdXpOHy .node.clickable{cursor:pointer;}#mermaid-svg-CiQnIX8H7kdXpOHy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CiQnIX8H7kdXpOHy .arrowheadPath{fill:#333333;}#mermaid-svg-CiQnIX8H7kdXpOHy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CiQnIX8H7kdXpOHy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CiQnIX8H7kdXpOHy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CiQnIX8H7kdXpOHy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CiQnIX8H7kdXpOHy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CiQnIX8H7kdXpOHy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CiQnIX8H7kdXpOHy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CiQnIX8H7kdXpOHy .cluster text{fill:#333;}#mermaid-svg-CiQnIX8H7kdXpOHy .cluster span{color:#333;}#mermaid-svg-CiQnIX8H7kdXpOHy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CiQnIX8H7kdXpOHy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CiQnIX8H7kdXpOHy rect.text{fill:none;stroke-width:0;}#mermaid-svg-CiQnIX8H7kdXpOHy .icon-shape,#mermaid-svg-CiQnIX8H7kdXpOHy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CiQnIX8H7kdXpOHy .icon-shape p,#mermaid-svg-CiQnIX8H7kdXpOHy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CiQnIX8H7kdXpOHy .icon-shape .label rect,#mermaid-svg-CiQnIX8H7kdXpOHy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CiQnIX8H7kdXpOHy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CiQnIX8H7kdXpOHy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CiQnIX8H7kdXpOHy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户
React / Vite 前端
会话与项目状态
File System Access API
Markdown 笔记与 IndexedDB 句柄
Express 服务
百炼 / OpenAI / Anthropic / Gemini
MCP Client Manager
Python 临时隔离工作区
Word / PDF 导出器
PDF / Word / Excel / OCR 解析
网络与公共数据
学术文献
GitHub
只读数据库
允许列表外部服务
用户授权的本地工程
用户选择的本地笔记文件夹
前后端职责并非简单地"前端聊天、后端转发":
- 浏览器持有用户主动选择的
FileSystemDirectoryHandle,所有工程路径都必须归一化为工程内相对路径。 - 前端负责会话状态、项目树、工具风险判断、用户审批和工程文件回写。
- 服务端负责模型网关、原生 Function Calling、MCP 连接、受限 Python 执行、文件解析及 Word/PDF 生成。
- 模型只负责决策"下一步调用什么工具",不会直接获得操作系统文件权限。
数模学习笔记
"数模学习笔记"是面向公式整理、模型总结、代码实验、任务清单和赛后复盘的浏览器 Markdown 知识工作台。
- 可在用户选择的本地文件夹内创建真实
.md文件;未选择文件夹时也可以先使用仅保存在浏览器中的笔记。 - 支持编辑、分栏和预览三种模式。实时预览可渲染 GFM 表格与任务清单、代码块、链接、标题,以及 KaTeX 行内/独立公式。
- 可搜索标题、正文和标签,并按文件夹、置顶状态或归档状态筛选。
- 支持置顶、归档、恢复、删除、导入和导出,相关操作集中在编辑器工具栏。
- 导入已有 Markdown 文件时会先创建浏览器笔记;如果需要在所选文件夹中生成同步副本,可导出该笔记,或新建一篇绑定本地文件的笔记。
- 修改内容后会自动保存到当前账号的浏览器存储;已绑定本地文件的笔记还会写入已授权文件夹。
- 修改已绑定笔记的标题会同步重命名
.md文件;系统会清理文件名中的非法字符,并在重名时添加数字后缀,避免覆盖已有文件。 - 本地笔记文件夹句柄按账号存放在浏览器 IndexedDB 中;浏览器重启、权限变化后仍可能要求重新授予读写权限。
直接同步本地文件夹依赖 File System Access API。笔记的导入、导出、浏览器内编辑和 Markdown 预览与 Modulus 工程空间相互独立;选择笔记文件夹不会自动授予智能体访问该目录的权限。

Modulus 智能体

设计目标
Modulus 的目标是完成一个可验证的数学建模工作闭环:
-
理解赛题和工程上下文。
-
选择合适的建模 Skill。
-
读取数据、论文、代码和现有结果。
-
生成可复现的 Python 脚本。
-
在隔离环境中真实执行脚本。
-
根据输出、报错和结果文件继续修正。
-
生成图表、数据文件、Word 或 PDF 成果。
-
在全部工具工作完成后给出简洁总结。

Agent 执行循环
单次任务最多进行 30 轮 模型---工具交互。核心循环位于 src/App.tsx,模型工具定义与结构化调用位于 agent-runtime.ts。
#mermaid-svg-NnVivtMCoDDRvtzL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NnVivtMCoDDRvtzL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NnVivtMCoDDRvtzL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NnVivtMCoDDRvtzL .error-icon{fill:#552222;}#mermaid-svg-NnVivtMCoDDRvtzL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NnVivtMCoDDRvtzL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NnVivtMCoDDRvtzL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NnVivtMCoDDRvtzL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NnVivtMCoDDRvtzL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NnVivtMCoDDRvtzL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NnVivtMCoDDRvtzL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NnVivtMCoDDRvtzL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NnVivtMCoDDRvtzL .marker.cross{stroke:#333333;}#mermaid-svg-NnVivtMCoDDRvtzL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NnVivtMCoDDRvtzL p{margin:0;}#mermaid-svg-NnVivtMCoDDRvtzL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NnVivtMCoDDRvtzL .cluster-label text{fill:#333;}#mermaid-svg-NnVivtMCoDDRvtzL .cluster-label span{color:#333;}#mermaid-svg-NnVivtMCoDDRvtzL .cluster-label span p{background-color:transparent;}#mermaid-svg-NnVivtMCoDDRvtzL .label text,#mermaid-svg-NnVivtMCoDDRvtzL span{fill:#333;color:#333;}#mermaid-svg-NnVivtMCoDDRvtzL .node rect,#mermaid-svg-NnVivtMCoDDRvtzL .node circle,#mermaid-svg-NnVivtMCoDDRvtzL .node ellipse,#mermaid-svg-NnVivtMCoDDRvtzL .node polygon,#mermaid-svg-NnVivtMCoDDRvtzL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NnVivtMCoDDRvtzL .rough-node .label text,#mermaid-svg-NnVivtMCoDDRvtzL .node .label text,#mermaid-svg-NnVivtMCoDDRvtzL .image-shape .label,#mermaid-svg-NnVivtMCoDDRvtzL .icon-shape .label{text-anchor:middle;}#mermaid-svg-NnVivtMCoDDRvtzL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NnVivtMCoDDRvtzL .rough-node .label,#mermaid-svg-NnVivtMCoDDRvtzL .node .label,#mermaid-svg-NnVivtMCoDDRvtzL .image-shape .label,#mermaid-svg-NnVivtMCoDDRvtzL .icon-shape .label{text-align:center;}#mermaid-svg-NnVivtMCoDDRvtzL .node.clickable{cursor:pointer;}#mermaid-svg-NnVivtMCoDDRvtzL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NnVivtMCoDDRvtzL .arrowheadPath{fill:#333333;}#mermaid-svg-NnVivtMCoDDRvtzL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NnVivtMCoDDRvtzL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NnVivtMCoDDRvtzL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NnVivtMCoDDRvtzL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NnVivtMCoDDRvtzL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NnVivtMCoDDRvtzL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NnVivtMCoDDRvtzL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NnVivtMCoDDRvtzL .cluster text{fill:#333;}#mermaid-svg-NnVivtMCoDDRvtzL .cluster span{color:#333;}#mermaid-svg-NnVivtMCoDDRvtzL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NnVivtMCoDDRvtzL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NnVivtMCoDDRvtzL rect.text{fill:none;stroke-width:0;}#mermaid-svg-NnVivtMCoDDRvtzL .icon-shape,#mermaid-svg-NnVivtMCoDDRvtzL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NnVivtMCoDDRvtzL .icon-shape p,#mermaid-svg-NnVivtMCoDDRvtzL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NnVivtMCoDDRvtzL .icon-shape .label rect,#mermaid-svg-NnVivtMCoDDRvtzL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NnVivtMCoDDRvtzL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NnVivtMCoDDRvtzL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NnVivtMCoDDRvtzL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 最终回答
Function Call
blocked
review
safe
批准
拒绝
否,未达到 30 轮
是
用户输入任务
组合会话、附件与项目上下文
发现 MCP 工具并构建系统提示
模型规划下一步
返回结果类型
结束并展示总结
AJV 校验工具名与参数
风险分级 safe / review / blocked
返回安全拒绝结果
根据权限模式请求审批
执行工具
截断并整理 Observation
任务是否完成
每轮大致执行以下逻辑:
- 将系统约束、历史消息、已选择工程、附件内容、Skills 目录和 MCP 工具目录组合为当前上下文。
- 优先调用
/api/agent/turn-stream,以流式方式获取模型正文和结构化工具调用。 - 将内置工具和已发现的 MCP 工具转换为带 JSON Schema 的原生函数定义。
- 使用 AJV 校验工具参数;无法通过校验的调用不会执行。
- 根据路径、操作类型和外部服务类型进行风险分级。
- 执行经过允许的工具,把结果作为 Observation 放回消息链。
- 模型根据新结果决定继续读取、修正、运行、导出或结束。
- 若用户明确要求 Word/PDF,而模型只口头宣布导出,系统会进行受控恢复并强制请求缺失的导出工具。
原生 Function Calling 与兼容模式
Modulus 优先使用模型原生 Function Calling:
- 百炼与 OpenAI 使用 OpenAI 兼容工具格式。
- Claude 使用 Anthropic
tool_use格式。 - Gemini 使用
functionDeclarations。 - MCP 工具在运行时转换为模型可见的函数,并保留真实的 Server/Tool 映射。
如果某个模型接口明确不支持原生工具调用,前端才会回退到旧的 <ToolCall>{...}</ToolCall> 文本协议。认证、余额、额度和限流错误不会被兼容回退掩盖。
对于默认开启思考模式、但工具调用需要非思考模式的百炼模型,运行时会自动调整参数,避免出现连通测试成功但 Function Calling 失败的问题。
工程与会话模型
- 用户可以先选择项目目录,系统会自动创建并绑定一个以项目名称命名的会话。
- 用户也可以不选项目,直接发送消息创建普通会话。
- 每个会话保存独立的消息、项目名称、项目树和更新时间。
- 会话内容存入
localStorage;目录句柄只保留在当前浏览器会话中,不会被序列化。 - 用户和智能体的每条消息均支持复制与编辑。
- 编辑历史消息并发送时,会从该消息位置建立新的对话分支,避免旧回答污染新上下文。
- 输入框支持自动换行和动态增高:
Enter发送,Shift + Enter换行。
内置工具
| 工具 | 用途 | 关键限制 |
|---|---|---|
load_skill |
加载数学建模或科研专业工作流 | 一次任务只选择最相关的 1--3 个 Skills |
load_skill_resource |
列出或读取 Nature Skill 的清单、参考资料、模板或脚本说明 | 按需加载文本资源,长文件可分页读取 |
list_files |
查看工程目录结构 | 最多返回有限数量节点 |
read_file |
读取文本、代码、PDF、Word、Excel 等 | 仅允许工程内路径;敏感文件需要审批 |
search_files |
搜索文件名与可读文本 | 跳过敏感文件并限制扫描规模 |
create_directory |
创建工程目录 | 需要工程写权限 |
write_text_file |
写入代码、文本和数据文件 | 单次内容不超过 100 万字符 |
replace_in_file |
精确替换已有文件内容 | 默认只允许唯一匹配;修改已有文件属于风险操作 |
run_python |
执行工程内 Python 脚本 | 临时工作区、一次性授权、禁止网络和子进程 |
create_word_document |
生成普通 Word 或数模论文 | 支持公式、表格、目录、分页和结果图 |
create_pdf_document |
生成排版完成的 PDF 论文 | 与 Word 共用论文内容和结果图 |
mcp_call |
调用已连接 MCP 工具 | Schema 校验、允许列表、超时和结果长度限制 |
工具输出最多保留 24,000 字符,防止日志、Traceback 或外部网页内容挤占完整对话上下文。界面默认只显示步骤概览,不直接展开大段工具输入输出。
数学建模 Skills
Skills 位于 src/skills/,通过 YAML Frontmatter 注册,由 src/skills/index.ts 在构建时加载。专业任务开始时,Modulus 先加载最相关的 Skill,再按照其中的步骤、质量标准和交付规则执行。
当前内置 12 个数学建模 Skills,并接入了 18 个可触发的 Nature 科研 Skills。Nature 的附属资源保持原目录结构并按需读取;nature-shared 仅作为内部依赖,不会作为独立 Skill 触发。
| Skill | 作用 |
|---|---|
frame-contest-problem |
赛题理解、任务拆解与目标定义 |
audit-modeling-data |
数据来源、质量、缺失值与可信度审计 |
explore-modeling-data |
探索性数据分析与特征识别 |
design-mathematical-model |
变量、假设、约束与模型结构设计 |
solve-optimization-model |
优化问题建模、求解与结果解释 |
forecast-time-series |
时间序列建模、预测与回测 |
simulate-uncertainty |
蒙特卡洛模拟和不确定性分析 |
validate-model-robustness |
敏感性、稳健性和误差分析 |
visualize-model-results |
生成适合论文的结果图表 |
write-himcm-paper |
按 HiMCM 专用格式生成英文 Word/PDF 论文 |
write-mcm-paper |
按 MCM Letter 纸张及 2.54 cm 页边距生成英文论文 |
write-chinese-modeling-paper |
沿用 MCM 页面规范生成中文论文,中文宋体、数字与字母 Times New Roman |
论文 Skills 会约束摘要、目录、正文结构、假设、表图题注、公式解释、参考文献、模型评价、结论和代码附录。Word 与 PDF 导出器会真实嵌入工程中的结果图片,而不是只输出图片路径。
Nature 套件补充了学术检索、引文处理、科研数据、实验日志、论文图表、文献流水线、论文阅读/写作/评审、参考文献核验、统计分析、审稿回复、研究计划、论文转演示和论文转专利等工作流。依赖上游浏览器自动化、终端服务、R、账号凭据或可选文档库的环节,只会在 MathHub 存在等价工具时执行;否则 Modulus 会明确说明能力边界。
MCP 扩展
MCP Server 在根目录 mcp.config.json 中配置,支持本地 stdio 和 Streamable HTTP 两种传输方式。
项目默认启用 5 个本地 MCP Server:
| Server | 主要工具 |
|---|---|
web-data |
网络搜索、网页正文读取、公共 JSON 获取 |
academic-research |
OpenAlex、Crossref、DOI 元数据 |
github |
仓库搜索、仓库信息、文件读取、Issue/PR 搜索 |
database |
数据库状态、表结构、只读 SQL 查询 |
external-services |
项目所有者显式配置的允许列表 HTTP 服务 |
MCP 安全策略包括:
allowedTools限制每个 Server 可暴露的工具。- 参数按 MCP 提供的 JSON Schema 校验。
- 返回内容按长度截断,并始终按"不可信外部数据"处理。
- 内置网络工具阻止本机、内网、链路本地及保留地址访问。
- 数据库查询在 PostgreSQL
READ ONLY事务中运行。 - 非本机 HTTP MCP、外部 stdio 命令和远程浏览器执行默认关闭。
- 疑似密钥、令牌或私钥不会被发送给 MCP。
Python 隔离执行
建模任务需要计算、拟合、优化、仿真或绘图时,Modulus 必须先把可复现脚本写入工程,再调用 run_python 执行。
执行流程:
Python Express Sandbox Browser Modulus Python Express Sandbox Browser Modulus #mermaid-svg-fnbTYWEhFNLjsg2P{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fnbTYWEhFNLjsg2P .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fnbTYWEhFNLjsg2P .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fnbTYWEhFNLjsg2P .error-icon{fill:#552222;}#mermaid-svg-fnbTYWEhFNLjsg2P .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fnbTYWEhFNLjsg2P .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fnbTYWEhFNLjsg2P .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fnbTYWEhFNLjsg2P .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fnbTYWEhFNLjsg2P .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fnbTYWEhFNLjsg2P .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fnbTYWEhFNLjsg2P .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fnbTYWEhFNLjsg2P .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fnbTYWEhFNLjsg2P .marker.cross{stroke:#333333;}#mermaid-svg-fnbTYWEhFNLjsg2P svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fnbTYWEhFNLjsg2P p{margin:0;}#mermaid-svg-fnbTYWEhFNLjsg2P .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fnbTYWEhFNLjsg2P text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-fnbTYWEhFNLjsg2P .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-fnbTYWEhFNLjsg2P .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-fnbTYWEhFNLjsg2P .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-fnbTYWEhFNLjsg2P .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-fnbTYWEhFNLjsg2P #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-fnbTYWEhFNLjsg2P .sequenceNumber{fill:white;}#mermaid-svg-fnbTYWEhFNLjsg2P #sequencenumber{fill:#333;}#mermaid-svg-fnbTYWEhFNLjsg2P #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-fnbTYWEhFNLjsg2P .messageText{fill:#333;stroke:none;}#mermaid-svg-fnbTYWEhFNLjsg2P .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fnbTYWEhFNLjsg2P .labelText,#mermaid-svg-fnbTYWEhFNLjsg2P .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-fnbTYWEhFNLjsg2P .loopText,#mermaid-svg-fnbTYWEhFNLjsg2P .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-fnbTYWEhFNLjsg2P .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-fnbTYWEhFNLjsg2P .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-fnbTYWEhFNLjsg2P .noteText,#mermaid-svg-fnbTYWEhFNLjsg2P .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-fnbTYWEhFNLjsg2P .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fnbTYWEhFNLjsg2P .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fnbTYWEhFNLjsg2P .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fnbTYWEhFNLjsg2P .actorPopupMenu{position:absolute;}#mermaid-svg-fnbTYWEhFNLjsg2P .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-fnbTYWEhFNLjsg2P .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fnbTYWEhFNLjsg2P .actor-man circle,#mermaid-svg-fnbTYWEhFNLjsg2P line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-fnbTYWEhFNLjsg2P :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} run_python(path, input_paths, args) 计算入口和输入文件 SHA-256 申请一次性授权 短时 HMAC 授权 Token 上传已批准文件和 Token 校验 Manifest 与 Token 在临时工作区以隔离模式执行 stdout / stderr / 结果文件 返回结果清单,不直接写入工程 展示新建/覆盖文件审批 回写已批准结果并返回 Observation
当前主要限制:
- 一次最多 80 个输入文件,总计不超过 60 MB。
- Python 超时可设置为 5--120 秒。
- 临时工作区最多 256 MB、2,000 个文件。
- 单个回传文件不超过 12 MB,回传结果总计不超过 24 MB。
- 标准输出与错误输出总量受限。
- 默认禁止网络访问、子进程、系统命令和工程外文件访问。
- 授权与入口脚本、输入文件哈希、参数和超时绑定,短时有效且只能使用一次。
- Python 结果需要第二次审批后才会写回用户工程;未批准结果随临时工作区清理。
该机制是应用层隔离与审计,不等同于容器、虚拟机或操作系统级安全沙箱。若开放给远程不可信用户,应在独立、可销毁的容器或虚拟机中运行服务。
操作审批
输入框下方可选择三种权限模式:
| 模式 | 行为 |
|---|---|
| 请求批准 | 执行代码、修改已有文件或访问外部服务前询问 |
| 替我审批 | 自动允许隔离执行和新建结果,只对检测到的高风险操作询问 |
| 完全访问权限 | 自动批准当前工程内操作,但系统级安全边界仍然有效 |
无论选择哪种模式,下列保护都不会被关闭:
- 工程目录边界与
..路径跳转检查。 - 敏感文件识别与密钥泄露拦截。
- Python 网络、子进程和工程外访问限制。
- MCP 工具允许列表与外部服务限制。
Word 与 PDF 论文导出
Modulus 可以把相同 Markdown 论文内容分别交给 Word 和 PDF 导出器:
- 行内公式
$...$与独立公式$$...$$。 - Word 中生成可编辑 OMML 公式。
- Markdown 表格、连续表题和单元格居中。
- 工程内 PNG、JPEG、WebP、SVG 结果图嵌入。
[TOC]目录与[PAGEBREAK]分页控制。- HiMCM、MCM 和中文数模论文格式。
- Word/PDF 共用标题、正文、公式、表格和图片来源,减少两个版本的内容偏差。
项目结构
text
MathHub/
├── src/
│ ├── App.tsx # 主界面、会话状态、Agent 循环、工具执行与审批
│ ├── components/
│ │ ├── ModelingNotes.tsx # Markdown 笔记、本地文件夹同步、搜索和预览
│ │ └── CompetitionCover.tsx # 竞赛封面展示
│ ├── data.ts # 模型库、竞赛、学习路径和测评数据
│ ├── types.ts # 前端数据类型
│ ├── main.tsx # React 入口
│ ├── index.css # 全局样式
│ ├── assets/images/competitions/ # 本地竞赛封面和图片来源说明
│ ├── assets/ # 其他前端静态资源
│ └── skills/ # 数学建模、Nature Skills 与论文格式参考
├── mcp-servers/ # 5 个内置 MCP Server
├── test/ # Agent/论文生成测试材料与结果
├── agent-runtime.ts # 工具注册、Schema 校验、各模型 Function Calling
├── mcp-client.ts # MCP 配置加载、连接、工具发现和调用
├── python-approval.ts # Python 一次性授权签发与校验
├── word-document.ts # Word OOXML/OMML 生成
├── pdf-document.ts # PDF/HTML/KaTeX 排版生成
├── server.ts # Express API、模型网关、解析、沙箱和导出接口
├── mcp.config.json # MCP Server 配置与允许工具
├── package.json
├── vite.config.ts
├── tsconfig.json
├── .env.example
└── README.md
本地运行
环境要求
- Node.js 22 或更高版本
- npm 10 或更高版本
- 可选:Python 3.10+,用于 Modulus 执行建模脚本
- 推荐 Chrome 或 Edge 新版浏览器,以使用 File System Access API
1. 安装依赖
bash
npm install
2. 创建环境变量
Windows PowerShell:
powershell
Copy-Item .env.example .env
macOS / Linux:
bash
cp .env.example .env
最小配置:
env
GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
APP_URL="http://localhost:3000"
PYTHON_EXECUTABLE="python"
文本模型的百炼、OpenAI、Anthropic API Key 可以在应用"配置中心"按当前本地账号保存。视觉、生图和视频模型也在配置中心单独设置。
3. 启动开发服务
bash
npm run dev
访问:
text
http://localhost:3000
开发模式由 server.ts 启动 Express,并以内嵌中间件方式加载 Vite。
4. 使用 Modulus
- 打开"Modulus 智能体"。
- 可先点击"项目路径"右上角加号选择工程;也可直接开始普通对话。
- 在浏览器权限弹窗中允许工程读写。
- 选择审批模式。
- 输入任务,例如"读取赛题 PDF,建立模型,生成 Python 结果图,并导出 MCM Word 和 PDF 论文"。
5. 使用数模学习笔记
- 从左侧导航栏进入"数模学习笔记"。
- 如果希望生成真实
.md文件,先选择一个本地笔记文件夹;之后也可以随时更换。 - 新建笔记或导入已有 Markdown 文件。
- 使用铅笔、分栏和眼睛按钮,在源码编辑、源码与实时预览、完整预览之间切换。
- 编辑标题、文件夹分类和以逗号分隔的标签;已绑定本地文件的笔记会自动保存,并随标题重命名。
建议使用新版 Chromium 内核浏览器,并在弹窗中授予文件夹读写权限。重要笔记应另行备份或纳入版本控制。当前删除笔记只会移除 MathHub 中的笔记记录,不会删除对应的本地 .md 文件。
环境变量
| 变量 | 用途 |
|---|---|
GEMINI_API_KEY |
默认 Gemini 对话与部分文件理解能力 |
APP_URL |
应用地址 |
PYTHON_EXECUTABLE |
Python 可执行文件路径或命令名 |
ALLOW_REMOTE_PYTHON_EXECUTION |
是否允许非本机请求触发 Python,默认 false |
ALLOW_EXTERNAL_MCP_STDIO |
是否允许启动非内置 MCP stdio 命令 |
ALLOW_REMOTE_MCP_HTTP |
是否允许连接非本机 MCP HTTP 服务 |
ALLOW_REMOTE_MCP_EXECUTION |
是否允许非本机浏览器触发 MCP 调用 |
BRAVE_SEARCH_API_KEY |
完整网络搜索;缺省时使用有限的公共搜索兜底 |
OPENALEX_API_KEY |
OpenAlex 学术检索 |
OPENALEX_EMAIL |
OpenAlex 客户端联系信息 |
CROSSREF_MAILTO |
Crossref 客户端联系信息 |
GITHUB_TOKEN |
提高 GitHub API 限额或读取令牌可见仓库 |
DATABASE_URL |
PostgreSQL 连接字符串 |
DATABASE_SSL |
PostgreSQL SSL 开关 |
MCP_EXTERNAL_SERVICES_JSON |
外部服务允许列表及凭据环境变量映射 |
不要把真实 .env、API Key、数据库密码或访问令牌提交到版本库。
常用脚本
bash
npm run dev # 启动 Express + Vite 开发服务
npm run build # 构建前端并打包 dist/server.cjs
npm start # 启动生产构建
npm run preview # 预览 Vite 前端产物
npm run lint # TypeScript 类型检查
npm run test:agent-runtime # 执行 Agent 运行时测试
npm run clean # 清理构建产物
主要 API
模型与多模态
POST /api/test-text-modelPOST /api/test-bailianPOST /api/test-visionPOST /api/test-imagegenPOST /api/test-videogenPOST /api/chatPOST /api/uploadPOST /api/image-generationPOST /api/video-generation
Modulus Agent
POST /api/agent/turnPOST /api/agent/turn-streamGET /api/agent/mcp/toolsPOST /api/agent/mcp/callPOST /api/agent/python-approvalPOST /api/agent/run-pythonPOST /api/agent/create-docxPOST /api/agent/create-pdf
数据持久化
以下数据主要存储在当前浏览器 localStorage:
- 本地登录状态与账号标识
- 用户资料
- 文本、视觉、生图和视频模型配置
- Modulus 会话历史与活动会话 ID
- 数模学习笔记的元数据与正文
- 左侧导航栏折叠偏好
- 能力测评相关状态
当前栏目保存在 sessionStorage 中,因此开发环境刷新后可以返回原栏目。
Modulus 工程目录的授权句柄属于浏览器会话状态,不会写入服务器数据库。数模学习笔记的目录句柄会按本地账号保存在 IndexedDB 中,但浏览器重启、刷新或权限变化后仍可能要求重新授权。两类目录句柄都不会上传到 MathHub 服务端数据库。
当前边界
- 本地账户系统不是服务端身份认证,不能直接用于正式多租户部署。
- API Key 保存在浏览器本地配置中;共享设备上应谨慎使用。
- File System Access API 的浏览器兼容性有限,建议使用 Chromium 内核浏览器。
- 本地 Markdown 同步依赖用户持续保留文件夹权限;重要笔记不应只依赖浏览器存储,建议额外备份。
- Python 隔离是应用层保护;开放远程执行前必须增加容器、资源配额和主机级隔离。
- Word 与 PDF 排版器面向当前内置论文规范,复杂的任意 Word 模板仍可能需要专门适配。
- 外部 MCP、数据库和网络数据都应被视为不可信输入。
适用场景
- 数学建模竞赛训练与论文产出
- 校队或课程内部学习平台
- 个人数模知识库、公式笔记、代码实验与竞赛复盘
- 赛题 PDF、数据集和代码工程联合分析
- 模型计算、绘图、稳健性验证与结果复现
- AI Agent、Skills、MCP 和受控代码执行的教学演示


致谢与第三方 Skills 说明
MathHub 集成并适配了 Yuan1z0825 开源项目 nature-skills 中的科研 Skill 资源,用于支持学术检索、论文阅读与写作、引用核验、数据分析、科研绘图、同行评审等工作流。该上游项目采用 Apache License 2.0 发布;本项目所使用相关资源的原始著作权、贡献者署名和许可声明仍归各上游作者与贡献者所有。MathHub 对这些资源的集成与适配属于独立项目行为,不代表 nature-skills 官方发布或对 MathHub 的认可与背书。有关最新文档、许可证要求、运行依赖和版本更新,请以上游仓库说明为准。
License / 许可证
Except for separately identified third-party components, this project is
licensed under the PolyForm Noncommercial License 1.0.0.
除另行标注的第三方组件外,本项目采用 PolyForm Noncommercial
License 1.0.0:
- 允许个人出于学习、研究、实验和其他非商业目的使用、下载和修改;
- 允许在遵守许可证及保留版权声明的前提下进行非商业分享;
- 未经作者书面许可,不得销售、提供收费服务、用于商业产品、
商业化部署或其他以商业利益为目的的活动; - 商业授权请通过本仓库 Issues 联系作者。
完整条款以根目录 LICENSE 文件为准。