Back to home

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 提供。本包只补三件中间缺口:

  1. 内核发现 —— 自动找到本机的 Wolfram 可执行文件(注册表 / 标准安装根目录 / wolframscript),也支持显式覆盖。
  2. Windows 透明 stdio supervisor —— 保证连接关闭、HMR 重载、DSH 退出后,只有本插件自己启动的那棵 Wolfram 进程树被终止,绝不碰你已经开着的 Mathematica 前端、笔记本内核,或别的客户端启动的 AgentTools server。
  3. 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 || >=24pnpmdsh plugin 转发给 pnpm)。

  • 已安装 Wolfram 桌面产品或 Wolfram Engine,且已安装 Wolfram/AgentTools Paclet:

    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: wolframid: 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 —— 在新内核里执行任意代码
WriteNotebookMarkdown 转 .nb 并写盘ask —— 写任意路径
ReadNotebook.nb 为 markdownask —— 读任意路径
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

字段默认值说明
serverNamewolfram工具名命名空间,模型看到 mcp__<serverName>__<工具名>
mcpServerNameWolframLanguage启动哪个内置 AgentTools server(MCP_SERVER_NAME)。可选 WolframWolframAlphaWolframPacletDevelopment
command自动发现显式指定 Wolfram 可执行文件,原样使用,跳过全部探测
installationDirectory自动发现显式指定安装目录,按平台拼可执行文件
args官方启动参数覆盖传给 Wolfram 的参数(高级用法)
env{}额外环境变量,合并在 MCP_SERVER_NAME 之上
cwd继承Wolfram 进程的工作目录
graceMs1000关闭内核 stdin 后等多久再强杀进程树。必须 < 2000(见下)
recordDirectory$DSH_HOME/.dsh-wolframsupervisor 记录自己子进程的位置
reclaimOrphanstrue装载/卸载时回收被强杀的 supervisor 遗留的 Wolfram 进程树
probeWolframScripttrue允许用 wolframscript 探测安装目录(最后一步,要启内核,慢)
probeTimeoutMs30000上述探测的超时
toolCallTimeoutMs120000单次工具调用的传输超时
failOnStartupErrorfalse首次连接失败时是否让插件装载失败
reconnectmcp-client 默认断线重连策略,原样透传给 mcp-client

wolfram-permission

字段默认值说明
serverNamewolfram必须和 wolfram 行一致,策略靠它认出自己要守的工具
unknownask未分级 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: …,错误里会逐条列出试过的路径。按顺序确认:

  1. Wolfram 是否真的装了:wolframscript -code '$InstallationDirectory'

  2. 用返回的目录直接指定:

    - id: wolfram
      config:
        serverName: wolfram
        installationDirectory: 'D:\mathematica\Mathematica 15.0'
    
  3. 或者用环境变量临时覆盖,不改配置:DSH_WOLFRAM_COMMAND=…\wolfram.exe

发现顺序是:config.commandDSH_WOLFRAM_COMMANDconfig.installationDirectoryWOLFRAM_INSTALLATION_DIRECTORY → Windows 注册表 → 平台标准安装根目录 → PATHwolframscript 探测。

连上了但工具是空的

多半是 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.jsnode.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-clientscrubbedParentEnv() 过滤(丢弃 *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,本插件不做补丁。