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, собирает проект и создаёт ссылки synartesis и synartesis-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. Каждый инструмент, который не объявил себя read-only, по умолчанию становится 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

Ада оказалась на неверном тарифе и с неправильными заметками, а Грейс исчезла.

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

Грейс вернулась, а Ада снова на своём исходном тарифе и со своими исходными заметками.

5. Посмотрите, как он отказывается

Отмена — не тупой инструмент. Если что-то другое изменило запись после того, как к ней прикоснулся агент, запись старого значения обратно уничтожит эту работу, поэтому Synartesis останавливается и показывает вам оба значения.

Запустите повреждающую команду из шага 2 ещё раз. Это создаст второй запуск, поэтому возьмите идентификатор запуска из верхней части вывода list, которая упорядочена от самых новых к самым старым. Затем измените запись вручную:

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, поэтому агент не может отправить его сам. Вызов сразу же отклоняется вместе с идентификатором действия и командой, которая бы его одобрила. Агент рассказывает вам, вы решаете, и он пробует снова.

Он не удерживает вызов открытым, пока ждёт ответ. Это был первый вариант дизайна, и он не выживает при контакте с настоящим клиентом: любое полезное окно, за которое человек может заметить, открыть терминал и решить, длиннее, чем клиент готов ждать результат инструмента, поэтому их нельзя свести, выбрав лучший таймаут.

Одобрение также не происходит не в терминале, которым пользуется агент: прокси общается по протоколу MCP через stdin и stdout, поэтому там нет ничего, где можно было бы запросить подтверждение, а у настольного клиента терминала нет вообще. Запрос уходит в журнал, и вы отвечаете на него откуда угодно:

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

Одобрение — одноразовое и истекает через час, поэтому оно покрывает только ту повторную попытку, для которой было дано, и не может покапать разрешение на тот же вызов завтра. Оно не привязано к одной сессии, потому что люди перезапускают своим клиентом, а одобрение, застрявшее в мёртвой сессии, не было бы одобрением вовсе.

Ничто никогда не одобряется молчанием. Отвеченный запрос просто остаётся без ответа и остаётся видимым в ssynlesis gates, пока кто-нибудь не решит этот вопрос.

Всё это сообщается агенту при подключении, чтобы он мог объяснить ситуацию собеседнику, а не выдавать непонятную ошибку.

Реальные серверы

Если вам удобнее повторять шаги, а не читать описание, есть инструкция по запуску на собствен стране файлах; из двух мест там специально стоит проверить гейт и проверку расхождения.

Synartesis не имеет специфических отношений именно с почтой. Она работает поверх протокола MCP, поэтому её предмет — то, что умеют подключённые вами серверы: ваши файлы, ваши репозитории, ваша база данных, ваши заявки, собственная память вашего агента. То, что она может отменить, целиком зависит от того, что эти серверы открывают наружу, и каждый манифест ниже явно говорит, где эти возможности заканчиваются.

Манифест

Сервер

Состояние, которым он управляет

filesystem.yaml

@modelcontextprotocol/server-filesystem

реальные файлы на диске

memory.yaml

@modelcontextprotocol/server-memory

граф знаний, который агент хранит в вас

git.yaml

mcp-server-git

индекс и история настоящего репозитория

github.yaml

github/github-mcp-server

issues, pull requests, содержимое файлов

toy-crm.yaml

фикстура в этом репозитории

проработанный пример каждого класса

Каждый из этих манифестов, кроме github.yaml, был проверен против реально запущенного сервера. Две демонстрации проходят весь цикл по-настоящему:

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

Демонстрация с файловой системой перезаписывает файл и перемещает другой, восстанавливает оба обратно, затем показывает, как отмена отказывается, когда между делом файл отредактировал человек, и как гейт отказывается создавать директорию, которую этот сервер вообще не умеет удалять.

Демонстрация с памятью — более острая. Агент добавляет в граф двух людей, один из которых уже там был, и сервер тихо игнорирует дубликат. Поэтому отмене приходится удалить ровно одного из них: обратный вызов строится из того, что сервер сообщил о создании, а не из того, что запрашивал агент, — и человек, который оказался там первым, переживает отмену. В этой же сессии пробуют удалить сущность, и вызов блокируется, потому что удаление сущности одновременно удаляет и все связанные с ней отношения, а один обратный вызов не может вернуть обратно сразу оба.

Где заканчиваются возможности каждого из них

Ограничения — самая интересная часть, и они определяются возможностями серверов, а не функциями самого Synartesis.

  • filesystem: ий move_file обратим уже из одних своих аргументов, поэтому предварительное чтение не объявлено и проверку на расхождение для него выполнить нельзя. create_directory объявлена необратимой не потому, что каталог так дорог, а потому, что этот сервер не предоставляет способа его удалить.

  • memory: add_observations и delete_observations — точные противоположности, которые расходятся в том, как назвать одно и то же поле. Путь умеет прочитать поле, но не переименовать его, поэтому такой обратный вызов не может быть написан вообще, и вместо этого вызов проходит через гейт.

  • git: чуть ли не каждое чтение, которое предоставляет этот сервер, — это человеко-ориентированная проза, поэтому из зафиксированного состояния почти ничего нельзя восстановить, как бы ни была обратима сама операция git. Коммиты проходят через гейт, потому что этот сервер не предоставляет ни reset, ни revert, ни перемещения ветки.

Две вещи, которые стоит знать, если вы пишете свои пути. Обе пришли из тестов против живых серверов, а не из документации.

$result — это структурированный блок, и он не обязан совпадать с текстовым. Сервер памяти отвечает на create_entities простым списком в текстовом блоке и {"entities": [...]} в structuredContent. 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

Значение может ссылаться ровно на три вещи:

Префикс

Что это

Доступность

`.

аргументы, отправил которые место агент

snapshot и inverse

$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", ...]

Это закрывает

  • match поддерживает *, который соответствует одному сегменту: crm.send_* соответствует crm.send_email, но не crm.a.b. Выигрывает наиболее специфичный шаблон независимо от порядка, в котором записаны правила.

  • Обратная операция патча должна восстанавливать каждое поле, а не применять патч заново. Если одна и та же запись изменена дважды за один запуск, частичная обратная операция оставит поля, которых коснулась вторая правка.

  • gate: on_write — эвристика для инструментов вроде сырого SQL-раннера, где деструктивность нельзя определить по имени инструмента. Всё, что нельзя с уверенностью прочитать как одиночный оператор чтения, блокируется. Используйте gate: always везде, где важна определённость.

  • Некорректный манифест не даст прокси запуститься, указав файл и строку для исправления. Он никогда не будет работать с политикой, которую не смог понять.

Команды

Команда

Действие

init <server> -- <cmd>

Проанализировать сервер и составить черновик манифеста

list

Все записанные запуски

show <runId>

Хронология одного запуска с undo для каждого шага

gates

Что ожидает решения

approve <actionId>

Разрешить приостановленный вызов

deny <actionId>

Отказать в нём

undo <runId>

Обратить запуск, сначала самое новое действие

undo <runId> --replan

То же, но пересобрать каждый undo из текущего манифеста

check

Загрузить манифест и проверить его по указанным серверам

--manifest и --journal находятся, а не вводятся вручную. Оба ищутся от текущей директории вверх, как инструмент контроля версий ищет свой корень, поэтому внутри проекта с synartesis.yaml каждая команда работает вообще без флагов. Журнал, которого ещё нет, размещается рядом с политикой, так что прокси, который его создаёт, и CLI, который его читает, согласуются без дополнительных указаний.

Прочие флаги: --dry-run, --to <seq> и --replan для undo, --all для approve и deny, --json для list, show и gates.

Коды выхода: 0 — успех, 1 — остановлено или отклонено, 2 — неверное использование или конфигурация.

Прокси принимает --manifest, --journal, --gate-timeout <seconds> и --log-level. Он пишет структурированный JSON в stderr; stdout зарезервирован для протокольного трафика.

Чего он не делает

  • Нельзя отправить обратно то, что уже увидено. Прочитанное письмо, опубликованное сообщение, удалённый без резервной копии файл. Именно для этого существует gate.

  • Компенсируемые действия нельзя проверить на расхождение. Они не объявляют предварительного чтения, поэтому undo компенсирует их и помечает как [unverified] в своём отчёте.

  • Undo останавливается при неопределённости и перешагивает лишь через безвозвратное. Расхождение, неизвестный исход или неудачный обратный вызов останавливают его, потому что продолжение могло бы что-то уничтожить. Действие, которое просто нельзя отменить, например отправленное письмо, фиксируется в отчёте и остаётся на месте, пока всё остальное откатывается: никакая остановка не отправит его обратно, а остановка лишь оставила бы остальное тоже неверным. В любом случае запуск помечается как partial.

  • Вызов, прерванный на полпути, записывается как неизвестный, а не как неудавшийся. Undo отказывается проходить мимо него, потому что нельзя определить, применился ли он.

  • Undo настолько хорош, насколько хороша политика, которая его записала. Обратные операции вычисляются в момент вызова, а не при откате, поэтому ошибка в манифесте встраивается в каждый запуск, выполненный под ним. undo --replan пересобирает их из исправленного манифеста, используя уже захваченное состояние, — это и есть выход.

Наблюдение за работой

Synartesis — не демон и не может им быть. MCP-клиент сам порождает stdio-сервер и владеет его жизненным циклом, поэтому ничто долгоживущее не могло бы расположиться между ними и видеть эти вызовы. То, что человек обычно хочет от демона, — это уверенность, что он на месте и что-то делает, а для этого нужно место, куда можно посмотреть, а не фоновый процесс:

synartesis watch

Он перерисовывается по мере работы агента: что было вызвано, к какому классу относился каждый вызов и что ожидает решения, с командой для одобрения. Ctrl-C останавливает его. При запуске через конвейер, а не в терминале, он печатает состояние один раз и завершается.

Доверие

Манифест называет команды, и Synartesis их выполняет. Относитесь к манифесту, который вы не писали, так же, как к shell-скрипту из того же источника: сначала прочитайте. Здесь нет песочницы, и её и не предполагается.

Разработка

pnpm test
pnpm typecheck && pnpm lint

Каждый push прогоняет их на 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