Skip to main content
Glama

Synartesis

为 AI 智能体提供撤销层。

check MIT

一个对真实系统拥有写权限的智能体运行了二十步,误读了第七步,然后把其余步骤应用到了错误的记录上。如今你的选择是:根据转录手动撤销,恢复备份但丢失同一窗口内所有合法更改,或者接受损失。

Synartesis 位于你的 MCP 客户端与其通信的服务器之间。它记录每一次工具调用及其所替换的状态,并且能够恢复该状态。对于无法恢复的操作,它会拒绝让智能体在无人监督的情况下执行。

它不是沙箱:你的智能体运行的容器是一次性的,但它在网络上更新的 CRM 行不是。它不是追踪工具:追踪只会告诉你 update_customer 运行了四十次,而不会告诉你之前的值是什么。

它能做什么,不能做什么

每个工具都会获得四种分类之一,你需要在清单中写明:

分类

含义

示例

发生什么

readonly

不改变任何内容

get_customer

记录并转发

reversible

可以精确恢复之前的状态

update_customer

写入前捕获状态;撤销时写回

compensable

无法撤销,但可以抵消

create_charge

另一个调用使其失效

irreversible

两者皆不可

send_email

暂停,直到人类批准

清单中未提及的工具将被视为 irreversible。这是有意为之:静默转发未知的破坏性调用是最应避免的失败。

Related MCP server: mcp-compensator

要求

工具

版本

检查命令

Node

22 或更高

node --version

pnpm

9 或更高

pnpm --version

C 工具链

任意

cc --version

pnpm 随 Node 通过 corepack 提供:

corepack enable pnpm

C 工具链只需使用一次,用于编译 SQLite 的原生绑定。在 macOS 上运行 xcode-select --install;在 Debian 或 Ubuntu 上运行 apt install build-essential

安装

curl -fsSL https://raw.githubusercontent.com/ArhaanDev24/Synartesis/main/install.sh | bash

或者,如果你更想先阅读,可以从克隆开始:

git clone https://github.com/ArhaanDev24/Synartesis.git && cd Synartesis && ./install.sh

该脚本会检查你的 Node 版本,进行构建,并将 synartesissynartesis-proxy 链接到 PATH 中第一个可写的目录。它不会修改任何 shell 配置文件,也不需要 sudo。传递 --no-link 仅进行构建。

synartesis --help

如果无法链接任何内容,也不会出问题:Synartesis 打印的每条命令都会以实际在你的机器上运行的形式完整写出。

演练

这使用仓库附带的一个玩具 CRM,因此你可以在不指向真实数据的情况下看到整个循环。请从临时目录运行。

mkdir -p /tmp/synartesis-demo && cd /tmp/synartesis-demo

1. 编写策略

init 启动一个服务器,询问它有哪些工具,并写入一个清单。将 SYNARTESIS 替换为你克隆的路径。

node SYNARTESIS/dist/cli.js init crm -- node SYNARTESIS/dist/toy-crm.js --state ./crm.json

打开 synartesis.yaml。每个未声明为只读的工具都会以 irreversible 状态和 TODO 开始。处理这些 TODO 是你的工作。 该夹具的完整策略已随仓库提供,因此直接复制而不是手动输入:

cp SYNARTESIS/manifests/toy-crm.yaml ./synartesis.yaml

然后编辑指定服务器位置的那一行,使其指向你的克隆,并将数据保留在此目录中:

servers:
  crm:
    command: node
    args: ["SYNARTESIS/dist/toy-crm.js", "--state", "./crm.json"]

2. 将你的智能体指向代理

无论你的 MCP 客户端在哪里列出服务器,都将你想要覆盖的服务器条目替换为代理。对于 Claude Desktop 或 Claude Code,这是一个 mcpServers 块:

{
  "mcpServers": {
    "crm": {
      "command": "node",
      "args": ["SYNARTESIS/dist/proxy.js", "--manifest", "/tmp/synartesis-demo/synartesis.yaml"]
    }
  }
}

智能体看到的是相同的工具、相同的名称和相同的结果。这就是重点:你的智能体无需任何更改。

对于本演练,你不需要真正的智能体。以下操作效果相同:

printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"demo-agent","version":"0"}}}' '{"jsonrpc":"2.0","method":"notifications/initialized"}' '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"update_customer","arguments":{"id":"c_001","plan":"free","notes":"wrong edit"}}}' '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"delete_customer","arguments":{"id":"c_002"}}}' | node SYNARTESIS/dist/proxy.js --manifest ./synartesis.yaml --journal ./journal.db > /dev/null

查看损坏情况:

cat crm.json

Ada 被分配了错误的计划,备注也错了,Grace 消失了。

3. 查看它做了什么

node SYNARTESIS/dist/cli.js list --journal ./journal.db
node SYNARTESIS/dist/cli.js show RUN_ID --journal ./journal.db

show 会打印每次调用的分类、状态以及精确的撤销调用,并已解析为字面值。

4. 撤销

三思而后行:

node SYNARTESIS/dist/cli.js undo RUN_ID --dry-run --journal ./journal.db

然后执行:

node SYNARTESIS/dist/cli.js undo RUN_ID --journal ./journal.db
cat crm.json

Grace 回来了,Ada 回到了原来的计划,备注也恢复了。

5. 观察它拒绝

撤销不是粗暴的工具。如果智能体修改记录后,其他东西又修改了该记录,那么写回旧值会破坏那项工作,因此 Synartesis 会停止并显示两个值。

再次运行第 2 步中的损坏命令。这会创建第二次运行,因此从 list 顶部获取运行 ID(按最近优先排序)。然后手动编辑记录:

node -e 'const f="./crm.json",s=JSON.parse(require("fs").readFileSync(f));s.customers.c_001.notes="a human wrote this";require("fs").writeFileSync(f,JSON.stringify(s,null,2))'
node SYNARTESIS/dist/cli.js undo RUN_ID --journal ./journal.db

它会停止,打印预期状态和实际状态,以非零状态退出,并且不更改任何内容。

批准无法撤销的操作

send_email 被归类为 irreversible,因此智能体无法自行发送。该调用会立即被拒绝,并附带一个操作 ID 和批准该操作所需的命令。智能体告诉你,你决定,然后它重试。

它不会在等待时保持调用打开。那是最初的设计,但在真实客户端面前无法成立:一个人注意到、打开终端并做出决定所需的时间窗口,总是比客户端等待工具的时间长,因此无法通过选择更好的超时来调和两者。

批准也不会发生在智能体使用的终端上:代理通过 stdin 和 stdout 进行 MCP 通信,因此那里没有可提示的内容,桌面客户端根本没有终端。请求会进入日志,你可以从任何地方回答:

node SYNARTESIS/dist/cli.js gates --journal ./journal.db
node SYNARTESIS/dist/cli.js approve ACTION_ID --by your-name --journal ./journal.db
node SYNARTESIS/dist/cli.js deny ACTION_ID --by your-name --reason "not this one" --journal ./journal.db

批准是一次性的,并且在一小时后过期,因此它只覆盖所授予的重试,不能静默地授权明天相同的调用。它不绑定到某个会话,因为人们会重启客户端,而困在死会话中的批准根本不算批准。

任何批准都不会因沉默而生效。未回答的请求只会保持未回答状态,在 synartesis gates 中可见,直到有人决定。

智能体在连接时会被告知所有这些,因此它可以解释自己,而不是报告一个不透明的失败。

真实服务器

如果你更愿意跟随步骤而不是阅读说明,这里有一份针对你自己的文件运行此工具的指南,其中门控和漂移检查是值得特意测试的两项。

Synartesis 与电子邮件本身无关。它基于 MCP 协议,因此其主题是你所连接的服务器能做什么:你的文件、你的仓库、你的数据库、你的工单、你的智能体自身的记忆。它能撤销什么完全取决于这些服务器暴露了什么,下面的每个清单都明确说明了其边界。

清单

服务器

它管理的状态

filesystem.yaml

@modelcontextprotocol/server-filesystem

磁盘上的真实文件

memory.yaml

@modelcontextprotocol/server-memory

智能体关于你的知识图谱

git.yaml

mcp-server-git

真实仓库的索引和历史

github.yaml

github/github-mcp-server

问题、拉取请求、文件内容

toy-crm.yaml

本仓库中的夹具

每个分类的完整示例

github.yaml 外,其余所有清单都已针对实际运行的服务器进行了检查。两个演示真实运行了完整循环:

./demo/filesystem-demo.sh
./demo/memory-demo.sh

文件系统演示会覆盖一个文件并移动另一个文件,然后恢复两者,接着展示当人类在期间编辑文件时撤销如何拒绝,以及门控如何拒绝创建该服务器无法删除的目录。

内存演示更为尖锐。智能体向图谱中添加了两个人,其中一人已经存在,服务器静默忽略了重复项。因此,撤销必须恰好移除其中一个:逆操作是根据服务器声称创建的内容构建的,而不是根据智能体请求的内容,因此先存在的人能在撤销中幸存。同一会话随后尝试删除一个实体,但被阻止,因为删除实体也会删除所有关联它的关系,而一个逆调用无法同时恢复两者。

每个清单的边界

限制才是有趣的部分,它们是服务器的属性,而不是 Synartesis 的属性。

  • filesystemmove_file 仅凭其参数即可撤销,因此无需声明预读,也无法对其检查漂移。create_directoryirreversible,不是因为目录珍贵,而是因为这个服务器没有提供删除目录的方法。

  • memoryadd_observationsdelete_observations 是精确的对立操作,但对同一字段的命名不一致。路径可以读取字段,但不能重命名字段,因此该逆操作根本无法编写,该调用改为被门控。

  • git:这个服务器提供的几乎所有读取都以面向人类的散文形式返回,因此几乎无法从捕获的状态中反转任何内容,无论底层 git 操作多么可逆。提交被门控,因为这个服务器没有提供 reset、revert 或移动分支的方法。

如果你自己编写清单,有两件事值得了解,它们是通过在真实服务器上运行这些清单而不是阅读文档发现的:

$result 是结构化块,它不必与文本块匹配。内存服务器在文本块中返回 create_entities 的裸列表,并在 structuredContent 中返回 {"entities": [...]}。Synartesis 遍历结构化块,因为那是机器可读的契约。

synartesis check 证明工具存在,而不是路径可解析。它无法做到后者:因为尚未进行任何调用,所以没有结果可遍历。在依赖逆操作之前,先运行一次并阅读 synartesis show

编写清单

清单是整个产品。对于你熟悉的 API,应该只需十五分钟。

version: 1

servers:
  crm:
    command: node
    args: ["./crm-server.js"]

tools:
  - match: "crm.get_customer"
    class: readonly

  # Read the record before overwriting it, then write that record back.
  - match: "crm.update_customer"
    class: reversible
    snapshot:
      tool: "crm.get_customer"
      args:
        id: "$.id"
    inverse:
      tool: "crm.update_customer"
      args:
        id: "$.id"
        name: "$snapshot.name"
        plan: "$snapshot.plan"

  # Nothing to read beforehand; the id only exists once the call returns.
  - match: "crm.create_customer"
    class: compensable
    inverse:
      tool: "crm.delete_customer"
      args:
        id: "$result.id"

  - match: "crm.send_*"
    class: irreversible
    gate: always

值可以引用三种内容:

前缀

引用内容

可用位置

$.

智能体发送的参数

snapshotinverse

$snapshot.

预读捕获的内容

inverse

$result.

正向调用返回的内容

inverse

其他任何内容都是字面量。引用可以单独出现,此时值保持其类型,也可以放在句子中,此时以文本形式替换:

sha: "$result.content.sha"                 # the value itself
message: "Revert agent change to $.path"   # text with the path substituted

$$ 表示字面美元符号。没有表达式、条件或函数,将来也不会有:一旦这变成一种语言,它就不再是十五分钟能写出来的东西。

路径可以用 [0] 索引列表,并用 [] 从每个元素读取一个字段:

labels: "$snapshot.labels[].name"   # [{name: "bug"}, ...] becomes ["bug", ...]

这涵盖了常见情况:API 返回的字段比它接受的更丰富,GitHub 的问题标签就是如此。[] 从每个元素读取相同的键,仅此而已:它仍然是路径,不是转换。引用复制值,不能计算值,因此需要真正不同形状的 API 的逆操作应省略该字段,并明确说明。

其他需要了解的事项:

  • match 支持 *,它在一个段内匹配:crm.send_* 匹配 crm.send_email 但不匹配 crm.a.b。无论规则书写的顺序如何,最具体的模式胜出。

  • 补丁的逆操作应恢复每一个字段,而不是重新应用补丁。如果同一条记录在一次运行中被编辑两次,部分逆操作会留下第二次编辑所触及的字段。

  • gate: on_write 是用于诸如原始 SQL 运行器之类工具的一种启发式方法,这类工具的破坏性无法从工具名称中读出。任何它无法确信地读作单条读取语句的内容都会被门控。在确定性重要的地方使用 gate: always

  • 格式错误的清单会阻止代理启动,并指出需要修复的文件和行。它绝不会在无法理解的策略下运行。

命令

命令

作用

init <server> -- <cmd>

检查服务器并起草清单

list

每次记录的运行

show <runId>

单次运行的时间线,包含每一步的撤销

gates

等待决策的内容

approve <actionId>

允许被挂起的调用

deny <actionId>

拒绝一个

undo <runId>

反转一次运行,最新的操作优先

undo <runId> --replan

相同,但根据当前清单重建每次撤销

check

加载清单并对照其命名的服务器进行验证

--manifest--journal 是自动查找的,而非手动输入。两者都从当前目录向上查找,就像版本控制工具查找其根目录一样,因此在包含 synartesis.yaml 的项目内,每个命令都无需任何标志即可工作。尚不存在的日志会被放置在策略旁边,这样创建它的代理和读取它的 CLI 无需任何指示就能保持一致。

其他标志:undo 上的 --dry-run--to <seq>--replanapprovedeny 上的 --alllistshowgates 上的 --json

退出码:0 成功,1 中止或拒绝,2 用法或配置错误。

代理接受 --manifest--journal--gate-timeout <seconds>--log-level。它将结构化 JSON 记录到 stderr;stdout 保留给协议流量。

它不做什么

  • 它无法撤回已被看到的内容。 已读的电子邮件、已发布的消息、没有备份就被删除的文件。这就是门控存在的原因。

  • 可补偿操作无法检查漂移。 它们不声明预读,因此撤销会补偿它们并在其报告中将其标记为 [unverified]

  • 撤销在不确定性前停止,并跳过仅属永久性的操作。 漂移、未知结果或失败的逆向调用都会使其停止,因为继续越过这些可能会破坏某些东西。一个根本无法撤销的操作(如已发送的电子邮件)会被报告并保留在原处,同时其他所有内容都被还原:无论停止多少次都无法撤回它,而停止只会让其余部分也出错。无论哪种方式,运行都会被标记为 partial

  • 在飞行途中被中断的调用被记录为未知,而非失败。撤销拒绝越过它,因为无法确定它是否已生效。

  • 撤销的好坏取决于记录它的策略。 逆操作在调用发生时解析,而不是在撤销时解析,因此清单中的错误会被烙进在其下进行的每一次运行。undo --replan 使用已捕获的状态从修正后的清单重建它们,这就是出路。

观察它的工作

Synartesis 不是守护进程,也不可能是。MCP 客户端自行生成 stdio 服务器并拥有其生命周期,因此没有任何长期运行的东西可以夹在中间看到这些调用。人们从守护进程那里想要的通常是确认它在那里并且正在做某事,而这需要一个可以查看的地方,而不是一个后台进程:

synartesis watch

它会在代理工作时重绘:已调用了什么、每个调用属于什么类别,以及任何等待决策的内容,并附上批准它的命令。Ctrl-C 停止它。如果通过管道而非在终端中运行,它会打印一次状态然后退出。

信任

清单指定命令,Synartesis 运行它们。对于不是你编写的清单,要像对待来自同一来源的 shell 脚本一样:先阅读它。这里没有沙箱,也不打算有。

开发

pnpm test
pnpm typecheck && pnpm lint

每次推送都会在 Linux 和 macOS 上跨 Node 22 和 24 运行这些测试,外加演示和安装程序。

许可证

MIT。参见 LICENSE

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    A policy-enforcing MCP gateway that intercepts all tool calls to downstream MCP servers, applying allow/deny/ask rules with human approval and audit logging for safe access to dangerous tools.
    23
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    MCP proxy that journals mutating tool calls and enables undo via compensation. It adds checkpoint, list_changes, undo_to, and explain_blast_radius meta-tools while forwarding all original downstream tools unchanged.
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Provides a secure MCP boundary for AI agents, intercepting and validating tool calls, redacting secrets, and requiring human approval for sensitive actions with a tamper-evident audit trail.
  • A
    license
    A
    quality
    A
    maintenance
    An MCP proxy that enforces policy on every tool call, blocking or flagging actions before they reach downstream MCP servers.
    1
    249
    MIT

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ArhaanDev24/Synartesis'

If you have feedback or need assistance with the MCP directory API, please join our Discord server