Zhen-WushuiLingchun
dsh-wolfram-bundle
No description
- Stars
- 0
- Language
- JavaScript
- Created
- Aug 15, 2026
- Updated
- Aug 16, 2026
Introduction
dsh-wolfram
把 Wolfram 官方 Wolfram/AgentTools MCP Server 接进 DeepSeek Harness (DSH) 的专用集成 bundle。
它是一层薄集成,不重写任何一方:MCP 协议交给 DSH 通用的 @deepseek-ai/dsh-mcp-client,Wolfram 工具由官方 Paclet 提供。本包只补三件中间缺口:
- 内核发现 —— 自动找到本机的 Wolfram 可执行文件(注册表 / 标准安装根目录 /
wolframscript),也支持显式覆盖。 - Windows 透明 stdio supervisor —— 保证连接关闭、HMR 重载、DSH 退出后,只有本插件自己启动的那棵 Wolfram 进程树被终止,绝不碰你已经开着的 Mathematica 前端、笔记本内核,或别的客户端启动的 AgentTools server。
- DSH 原生权限策略 —— 求值、跑测试、读写 notebook 这类有副作用的工具执行前先经 DSH 的审批链;未知的新工具默认也要问。
DSH 工具注册表
├── dsh-wolfram/policy ← tools/pre-execute:分级 → ask / 交给后续策略
└── dsh-wolfram ← 发现内核,挂载子插件 ↓
└── @deepseek-ai/dsh-mcp-client (DSH 通用,未修改)
└── node src/supervisor-main.js ← 本包的 supervisor
└── wolfram.exe -run PacletSymbol["Wolfram/AgentTools", …][] -noinit -noprompt
前置条件
-
DeepSeek Harness(提供
dsh命令)。 -
Node.js
^22.19 || >=24、pnpm(dsh plugin转发给 pnpm)。 -
已安装 Wolfram 桌面产品或 Wolfram Engine,且已安装
Wolfram/AgentToolsPaclet:PacletInstall["Wolfram/AgentTools"]验证:
PacletObject["Wolfram/AgentTools"]["Version"]应返回2.2.0或更高。
安装
dsh plugin --profile <你的 profile> add D:\dsh-dev\dsh-wolfram
add 后面可以是本地目录、tarball、git URL 或已发布的包名。dsh plugin 会在
$DSH_HOME/profiles/<profile>/ 里初始化 profile(如果还没有)、调用 pnpm 安装,然后检测到本包声明了
dsh.bundle 并把它追加进 dsh.profile.bundles 层列表。
确认它进了组合树:
dsh --profile <你的 profile> --dump-config
输出里应该出现 id: wolfram 与 id: wolfram-permission 两行。
启动后日志里会打印实际选中的内核,例如:
wolfram: kernel D:\mathematica\Mathematica 15.0\wolfram.exe (via windows-registry), server WolframLanguage, tools under mcp__wolfram__*
想先在隔离环境里试
profile 完全由 $DSH_HOME 决定,所以换一个 DSH_HOME 就是一个干净的沙盒,不会碰你现有的 profile:
$env:DSH_HOME = 'D:\tmp\dsh-home-试用'
dsh plugin --profile wolfram-试用 add D:\dsh-dev\dsh-wolfram
dsh --profile wolfram-试用 --dump-config
装上之后有什么
默认连接内置的 WolframLanguage server,模型会看到 7 个工具,名字统一带 mcp__wolfram__ 前缀:
| 工具 | 作用 | 默认权限 |
|---|---|---|
WolframLanguageEvaluator | 求值任意 Wolfram 代码 | ask —— 无沙箱,可写文件、起进程、访问网络 |
TestReport | 运行 .wlt 测试文件 | ask —— 在新内核里执行任意代码 |
WriteNotebook | Markdown 转 .nb 并写盘 | ask —— 写任意路径 |
ReadNotebook | 读 .nb 为 markdown | ask —— 读任意路径 |
CodeInspector | 静态检查代码/文件/目录 | ask —— 可递归读整个目录 |
WolframLanguageContext | 文档语义检索 | 交给后续策略(只读元数据) |
SymbolDefinition | 取符号定义 | 交给后续策略(只读元数据) |
ask 走 DSH 自己的审批链(ctx.approval)。会话审批策略是 never 时,这些调用会被直接拒绝——这是 DSH 的既有语义,本插件不另开后门。
未列出的 Wolfram 工具默认也是 ask。 官方 Paclet 升级后可能新增工具,没有分级信息之前先问人,而不是先放行。
官方 server 的
tools/list只在annotations里给title,没有readOnlyHint/destructiveHint,所以分级只能按工具名做,未知即 ask。
配置项
两行都可以在 profile 的 cordis.patch.yml 里按 id 覆盖。一次 patch 会替换该行的整个 config,所以要把想保留的字段一起写上。
wolfram 行
| 字段 | 默认值 | 说明 |
|---|---|---|
serverName | wolfram | 工具名命名空间,模型看到 mcp__<serverName>__<工具名> |
mcpServerName | WolframLanguage | 启动哪个内置 AgentTools server(MCP_SERVER_NAME)。可选 Wolfram、WolframAlpha、WolframPacletDevelopment |
command | 自动发现 | 显式指定 Wolfram 可执行文件,原样使用,跳过全部探测 |
installationDirectory | 自动发现 | 显式指定安装目录,按平台拼可执行文件 |
args | 官方启动参数 | 覆盖传给 Wolfram 的参数(高级用法) |
env | {} | 额外环境变量,合并在 MCP_SERVER_NAME 之上 |
cwd | 继承 | Wolfram 进程的工作目录 |
graceMs | 1000 | 关闭内核 stdin 后等多久再强杀进程树。必须 < 2000(见下) |
recordDirectory | $DSH_HOME/.dsh-wolfram | supervisor 记录自己子进程的位置 |
reclaimOrphans | true | 装载/卸载时回收被强杀的 supervisor 遗留的 Wolfram 进程树 |
probeWolframScript | true | 允许用 wolframscript 探测安装目录(最后一步,要启内核,慢) |
probeTimeoutMs | 30000 | 上述探测的超时 |
toolCallTimeoutMs | 120000 | 单次工具调用的传输超时 |
failOnStartupError | false | 首次连接失败时是否让插件装载失败 |
reconnect | mcp-client 默认 | 断线重连策略,原样透传给 mcp-client |
wolfram-permission 行
| 字段 | 默认值 | 说明 |
|---|---|---|
serverName | wolfram | 必须和 wolfram 行一致,策略靠它认出自己要守的工具 |
unknown | ask | 未分级 Wolfram 工具的决策,可改成 deny |
ask | 见上表 | 整表替换:工具名 → 审批理由 |
additionalAsk | {} | 在生效表之上追加条目,不丢默认值 |
delegate | [WolframLanguageContext, WolframAlphaContext, SymbolDefinition] | 整表替换:交给后续策略的只读工具 |
覆盖示例
写在 $DSH_HOME/profiles/<profile>/cordis.patch.yml:
# 指定内核、加长求值超时、把只读元数据工具也纳入审批
- id: wolfram
config:
serverName: wolfram
mcpServerName: WolframLanguage
command: 'D:\mathematica\Mathematica 15.0\wolfram.exe'
graceMs: 1000
toolCallTimeoutMs: 300000
reclaimOrphans: true
- id: wolfram-permission
config:
serverName: wolfram
unknown: deny
delegate: []
只想临时关掉权限策略(不推荐):
- id: wolfram-permission
disabled: true
卸载
dsh plugin --profile <你的 profile> remove dsh-wolfram
dsh plugin 会在 pnpm 成功后把它从 dsh.profile.bundles 里摘掉。确认:
dsh --profile <你的 profile> --dump-config
不再出现 id: wolfram 即可。
残留清理(可选):删除 $DSH_HOME/.dsh-wolfram/。里面只有 supervisor 的子进程记录文件,正常退出时会自己删掉。
排障
找不到内核
启动时抛出 dsh-wolfram: no Wolfram kernel found. Tried: …,错误里会逐条列出试过的路径。按顺序确认:
-
Wolfram 是否真的装了:
wolframscript -code '$InstallationDirectory'; -
用返回的目录直接指定:
- id: wolfram config: serverName: wolfram installationDirectory: 'D:\mathematica\Mathematica 15.0' -
或者用环境变量临时覆盖,不改配置:
DSH_WOLFRAM_COMMAND=…\wolfram.exe。
发现顺序是:config.command → DSH_WOLFRAM_COMMAND → config.installationDirectory → WOLFRAM_INSTALLATION_DIRECTORY → Windows 注册表 → 平台标准安装根目录 → PATH → wolframscript 探测。
连上了但工具是空的
多半是 Paclet 没装或版本太老。先手工跑一遍 server,看它有没有正常应答:
node D:\dsh-dev\dsh-wolfram\src\supervisor-main.js `
--command 'D:\mathematica\Mathematica 15.0\wolfram.exe' `
--arg -run --arg 'PacletSymbol["Wolfram/AgentTools","Wolfram`AgentTools`StartMCPServer"][]' `
--arg -noinit --arg -noprompt
(需要设 MCP_SERVER_NAME=WolframLanguage。)然后往 stdin 粘一行 initialize 请求。stdout 只会有协议字节,supervisor 自己的日志都在 stderr,带 [dsh-wolfram-supervisor] 前缀。
更省事的做法是跑本仓库的实测用例,它会完成真实握手、列工具、做一次无副作用求值,并检查退出后没有进程残留:
npm run test:live
工具每次都要点确认
这是设计如此。要放宽某个工具,把它从 ask 表移到 delegate:
- id: wolfram-permission
config:
serverName: wolfram
unknown: ask
delegate: [WolframLanguageContext, SymbolDefinition, ReadNotebook]
注意 delegate 是整表替换,写全你要放行的名字。
怀疑有 Wolfram 进程残留
先看基线,再对比。永远不要按进程名批量杀——你自己的 Mathematica 前端和别的 MCP 客户端的 server 长得一模一样:
Get-CimInstance Win32_Process |
Where-Object { $_.Name -match 'wolfram' } |
Select-Object ProcessId, ParentProcessId, Name, CreationDate, CommandLine | Format-List
本插件启动的 Wolfram 进程,其父进程是一个跑 supervisor-main.js 的 node.exe。正常关闭后两者都会消失。
即使 supervisor 被强杀(来不及运行任何清理代码),内核多半也会跟着退出:supervisor 一死,内核 stdin 的写端就关了,AgentTools 把 EOF 当关机信号,实测约 1 秒内自行 Exit[0]。万一没退,下一次插件装载或卸载时会自动回收——前提是那个进程通过身份三重校验(映像名、命令行、创建时间窗口)确实是记录里的那一个;有任何一项对不上,就只删记录、不动进程。
graceMs 为什么不能调大
MCP SDK 的 StdioClientTransport.close() 在关闭 stdin 后 2000ms 就会对 supervisor 发 SIGTERM;Windows 上那等于 TerminateProcess,没有任何 JavaScript 能执行。graceMs 一旦 ≥ 2000,进程树清理这一步永远轮不到运行。所以配置校验会直接拒绝,并把原因写在错误里。
安全边界
- 只终止自己的进程树。 代码里没有任何按进程名或命令行模糊匹配的终止路径。终止只发生在 supervisor 仍持有未回收的子进程句柄时(此时 PID 不可能被系统复用),或孤儿回收通过三重身份校验之后。
WolframLanguageEvaluator没有沙箱。 它能读写文件、启动进程、访问网络。本插件的边界是「执行前必须有人点头」,不是「执行被限制」。真要限制,用 DSH 自己的 sandbox / approval 组合。- stdout 只走协议。 supervisor 的所有诊断都在 stderr,任何写进 stdout 的东西都会破坏 JSON-RPC 流。
- 不注入 system prompt。 模型可见文本会进每一次请求前缀,属于产品决策,交给使用者自己的 patch 层。
- 凭据清洗沿用 DSH。 子进程环境由
dsh-mcp-client的scrubbedParentEnv()过滤(丢弃*KEY*/*SECRET*/*TOKEN*/*PASSWORD*与DSH_*),本包只在其上叠加MCP_SERVER_NAME和你显式配置的env。
设计取舍与证据来源见 docs/design.md。
开发
npm install # 只装 typescript / yaml / @types/node,零运行时依赖
npm run typecheck # tsc --checkJs,严格模式
npm test # 单元 + 真实子进程集成用例(不需要 Wolfram)
npm run test:live # 真实 Wolfram MCP 握手(需要本机装了 Wolfram + AgentTools)
npm test 里的进程用例会真的拉起进程树、强杀、并断言一个无关的对照进程还活着——那条断言才是这套清理逻辑的意义所在。
已知限制
- Windows 强杀窗口无法完全消除。 supervisor 被
SIGKILL/TerminateProcess时不会运行任何 JavaScript,这一瞬间无法保证子进程立即死亡;孤儿回收是事后补救,不是实时保证。彻底消除需要 Windows Job Object(KILL_ON_JOB_CLOSE),那要原生模块。 - 孤儿回收的身份校验只在 Windows 上完整。 POSIX 分支退化为
ps命令行比对,没有创建时间,尚未在真实 macOS / Linux 上验证;这些平台上 supervisor 自己的进程组终止仍然生效。 - 只桥接 MCP Tools。 DSH 通用 mcp-client 目前不桥接 Resources 与 Prompts,本包不在其上另开旁路,所以 AgentTools 的
WolframLanguageSearch/Notebook等 prompt 用不到。 - 审批没有「记住选择」。 DSH 审批语义只有一次性授权,会话级策略只有
ask/never;本插件不自建第二套授权存储。 toolCallTimeoutMs与内核的TimeConstraint是两个预算。 前者是传输超时,后者是 Wolfram 自己的求值上限(默认 60s),改一个不会改另一个。- 工具自身的失败属于上游。 实测本机
WolframLanguageContext会返回AgentTools::Internal::Path@@Kernel/Tools/Context.wl内部错误;调用链本身是通的(请求送达、错误原样回传并映射成isError),但那条工具的实现问题要报给 WolframResearch/AgentTools,本插件不做补丁。