Skip to main content
Glama

Synartesis

AIエージェントのためのアンドゥレイヤー。

check MIT

実システムへの書き込みアクセスを持つエージェントが20ステップ実行し、7ステップ目を読み違え、残りを誤ったレコードに適用してしまう。今日のあなたの選択肢は、トランスクリプトから手作業で逆操作するか、バックアップを復元して同じ期間に行われた正当な変更をすべて失うか、あるいは損害を受け入れるかだ。

SynartesisはMCPクライアントと、それが通信するサーバーの間に位置する。すべてのツール呼び出しを、その呼び出しが置き換えた状態とともに記録し、その状態を戻すことができる。戻せないものについては、エージェントが監視なしで実行することを拒否する。

これはサンドボックスではない。エージェントが実行されるコンテナは使い捨てだが、ネットワーク越しに更新したCRMの行は使い捨てではない。これはトレーシングツールでもない。トレースはupdate_customerが40回実行されたことを教えてくれるが、その前の値が何だったかは教えてくれない。

できることとできないこと

すべてのツールは4つの分類のいずれかになり、それをマニフェストに記述する。

クラス

意味

例

何が起こるか

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はcorepack経由でNodeに付属する。

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のバージョンを確認し、ビルドし、synartesisとsynartesis-proxyをPATH上にある最初の書き込み可能なディレクトリにリンクする。シェルプロファイルは編集せず、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を開く。自己宣言された読み取りではないすべてのツールは、TODO付きのirreversibleとして始まる。それらのTODOを処理することが仕事だ。 このフィクスチャ用の完成済みポリシーがリポジトリに同梱されているので、タイプ入力するよりコピーしよう。

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

次に、サーバーの場所を示す1行を編集して、あなたのクローンを指し、データをこのディレクトリに保持するようにする。

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の損害コマンドを再度実行する。これで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

承認は一度きりで、1時間で期限切れになる。承認が付与された再試行をカバーし、明日同じ呼び出しを静かに許可することはない。特定のセッションには結び付けられていない。なぜなら、人々はクライアントを再起動するし、死んだセッションに取り残された承認は承認とは言えないからだ。

沈黙によって承認されることは決してない。未回答のリクエストは単に未回答のまま、誰かが決定するまでsynartesis gatesに表示され続ける。

エージェントは接続時にこのすべてを伝えられるため、不透明な失敗を報告するのではなく、自分自身を説明できる。

実際のサーバー

読むより手順に従いたいなら、自分のファイルに対してこれを実行するガイドがある。ゲートとドリフトチェックが、意図的にテストする価値のある2つのものだ。

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を除くすべてが、実際に実行されているサーバーに対して検証済みだ。2つのデモがループ全体を実際に実行する。

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

ファイルシステムデモは、ファイルを上書きし、別のファイルを移動し、両方を復元し、その後、人間が間にファイルを編集した場合にアンドゥが拒否されること、そしてこのサーバーに削除手段がないディレクトリの作成をゲートが拒否することを示す。

メモリデモはより鋭い。エージェントは2人をグラフに追加するが、そのうち1人はすでに存在しており、サーバーは重複を静かに無視する。したがって、アンドゥは正確に1人だけを削除しなければならない。逆操作はエージェントが要求したものではなく、サーバーが作成したと報告したものから構築されるため、先にいた人物はアンドゥされても生き残る。同じセッションがその後エンティティの削除を試みると保留される。エンティティの削除はそれに触れるすべてのリレーションも削除し、1つの逆呼び出しでは両方を戻せないからだ。

それぞれが尽きる場所

限界が興味深い部分であり、それらはSynartesisではなくサーバーの性質だ。

  • filesystem: move_fileは引数だけで逆操作可能なため、事前読み取りは宣言されず、ドリフトチェックもできない。create_directoryがirreversibleなのはディレクトリが貴重だからではなく、このサーバーには削除手段が公開されていないからだ。

  • memory: add_observationsとdelete_observationsは、同じフィールドを何と呼ぶかについて意見が食い違う正確な対義語だ。パスはフィールドを読めても名前を変更できないため、その逆操作はまったく書けず、呼び出しは代わりにゲートされる。

  • git: このサーバーが提供する読み取りのほぼすべてが人間向けの散文で答えるため、基盤となるgit操作がどれほど可逆的であっても、キャプチャされた状態から逆操作できるものはほとんどない。コミットはゲートされる。このサーバーにはリセットもリバートもブランチを移動する手段も公開されていないからだ。

自分で書く場合に知っておく価値のある2つのこと。どちらもドキュメントを読むのではなく、ライブサーバーに対して実行して見つかったものだ。

$resultは構造化ブロックであり、テキストブロックと一致する必要はない。メモリサーバーはcreate_entitiesにテキストブロックの裸のリストと、structuredContentの{"entities": [...]}で答える。Synartesisは構造化された方を辿る。それが機械可読な契約だからだ。

そしてsynartesis checkはツールが存在することを証明するだけで、パスが解決することを証明しない。それは不可能だ。呼び出しが行われていないため、辿る結果がない。逆操作に依存する前に、一度実行してsynartesis showを読もう。

マニフェストの書き方

マニフェストこそが製品全体だ。知っているAPIなら15分で書けるはずだ。

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

値が参照できるものは正確に3つある。

プレフィックス

参照先

利用可能な場所

$.

エージェントが送信した引数

snapshotとinverse

$snapshot.

事前読み取りがキャプチャしたもの

inverse

$result.

フォワード呼び出しが返したもの

inverse

これ以外はすべてリテラルだ。参照は単独で置くことができ、その場合値は型を保持する。または文の中に置くことができ、その場合テキストとして置換される。

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

リテラルのドル記号には$$と書く。式、条件、関数はなく、今後も追加されない。これが言語になった瞬間、15分で書けるものではなくなる。

パスは[0]でリストにインデックスでき、[]で各要素から1つのフィールドを読める。

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

これは、APIが受け取るよりも豊かな形でフィールドを返す一般的なケースをカバーする。GitHubがイシューラベルで行うことだ。[]は各要素から同じキーを読み取るだけで、それ以外は何もしない。依然としてパスであり、変換ではない。参照は値をコピーできても計算はできないため、本当に異なる形を必要とするAPIの場合、逆操作はそのフィールドを除外し、その旨を明記すべきだ。

他に知っておくべきこと:

  • match は * をサポートしており、1 セグメント内で一致します。crm.send_* は crm.send_email に一致しますが、crm.a.b には一致しません。ルールが記述された順序に関係なく、最も具体的なパターンが優先されます。

  • パッチの逆操作はすべてのフィールドを復元すべきであり、パッチを再適用すべきではありません。同じレコードが 1 回の実行で 2 回編集された場合、部分的な逆操作では、2 回目の編集が触れたフィールドが残ってしまいます。

  • gate: on_write は、生の SQL ランナーなどのツール向けのヒューリスティックであり、破壊性をツール名から読み取ることができません。単一の読み取りステートメントとして確実に読み取れないものはすべてゲートされます。確実性が重要な場所ではどこでも gate: always を使用してください。

  • 不正なマニフェストは、修正すべきファイルと行を示してプロキシの起動を停止します。理解できないポリシーで実行されることは決してありません。

コマンド

コマンド

動作

init <server> -- <cmd>

サーバーをイントロスペクトし、マニフェストのドラフトを作成する

list

記録されたすべての実行

show <runId>

1 回の実行のタイムライン。各ステップの undo 付き

gates

決定を待っているもの

approve <actionId>

一時停止された呼び出しを許可する

deny <actionId>

1 つを拒否する

undo <runId>

実行を逆順に、新しいアクションから先に元に戻す

undo <runId> --replan

同じだが、各 undo を現在のマニフェストから再構築する

check

マニフェストを読み込み、それが指定するサーバーに対して検証する

--manifest と --journal は入力されるのではなく検索されます。どちらも現在のディレクトリから上方向に探され、バージョン管理ツールがルートを見つけるのと同じ方法です。そのため、synartesis.yaml があるプロジェクト内では、すべてのコマンドがフラグなしで動作します。まだ存在しないジャーナルはポリシーの隣に配置されるため、作成するプロキシと読み取る CLI は、どちらにも指示されなくても一致します。

その他のフラグ: --dry-run、undo の --to <seq> と --replan、approve と deny の --all、list、show、gates の --json。

終了コード: 0 は成功、1 は停止または拒否、2 は不正な使用法または設定。

プロキシは --manifest、--journal、--gate-timeout <seconds>、--log-level を受け取ります。構造化された JSON を stderr にログ出力し、stdout はプロトコルトラフィック用に予約されています。

行わないこと

  • 送信済みのものを送信前に戻すことはできません。 読まれたメール、投稿されたメッセージ、バックアップなしで削除されたファイル。これがゲートが存在する理由です。

  • 補償可能なアクションはドリフトをチェックできません。 事前読み取りを宣言しないため、undo はそれらを補償し、レポートで [unverified] とマークします。

  • undo は不確実性で停止し、単に恒久的なものはスキップします。 ドリフト、未知の結果、または失敗した逆転呼び出しは停止させます。それらを越えて進むと何かを破壊する可能性があるからです。送信済みメールのように単に元に戻せないアクションは報告され、他のすべてが元に戻されている間もそのまま残されます: 停止しても送信を元に戻すことはできず、停止すると他の部分も間違ったままになるだけです。どちらの場合も実行は partial とマークされます。

  • 途中で中断された呼び出しは失敗として記録されず、未知として記録されます。 undo はそれを越えて進むことを拒否します。適用されたかどうかを判断できないためです。

  • undo はそれを記録したポリシーと同じだけの信頼性しかありません。 逆操作は undo したときではなく呼び出しが発生したときに解決されるため、マニフェストの誤りは、その下で行われたすべての実行に組み込まれます。undo --replan は、すでにキャプチャされた状態を使用して、修正されたマニフェストから各 undo を再構築します。これが脱出手段です。

動作の確認

Synartesis はデーモンではなく、デーモンになることはできません。MCP クライアントはサーバーを生成し、そのライフタイムを所有するため、長期間実行されるものはそれらの呼び出しを認識できません。人が望むのは通常、バックグラウンドプロセスではなく、それが存在して何かをしているという保証です:

synartesis watch

エージェントが作業するにつれて、何が呼び出されたか、各呼び出しがどのクラスだったか、決定を待っているものがあればそれを再描画します。Ctrl-C で停止します。パイプで渡された場合は、状態を 1 回出力して終了します。

信頼

マニフェストはコマンドを指定し、Synartesis はそれを実行します。自分で書いていないマニフェストは、同じソースからのシェルスクリプトを扱うのと同じように扱ってください: まず読むこと。ここにはサンドボックスはなく、その意図もありません。

開発

pnpm test
pnpm typecheck && pnpm lint

すべてのプッシュで、Linux と macOS 上の Node 22 と 24 でこれらを実行します。さらにデモとインストーラーも実行します。

ライセンス

MIT。 LICENSE を参照してください。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    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.
    11 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    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
  • 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
    26 npm
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides AI agents with a transparent proxy and journal for every tool action, including pre-state snapshots and approval gates for risky operations. Enables per-action undo, rewind to a point in time, and a kill switch that agents cannot override.
    10 npm
    Business Source 1.1