Skip to main content
Glama

Synartesis

Una capa de deshacer para agentes de IA.

check MIT

Un agente con acceso de escritura a un sistema real ejecuta veinte pasos, lee mal el paso siete y aplica el resto a los registros equivocados. Hoy tus opciones son revertirlo a mano desde la transcripción, restaurar una copia de seguridad y perder todos los cambios legítimos hechos en el mismo intervalo, o aceptar el daño.

Synartesis se sitúa entre tu cliente MCP y los servidores con los que habla. Registra cada llamada a una herramienta junto con el estado que esa llamada reemplazó, y puede devolver ese estado a su lugar. Lo que no se puede devolver, no permite que un agente lo haga sin supervisión.

No es una sandbox: el contenedor donde corre tu agente es desechable, pero la fila del CRM que actualizó por red no lo es. No es una herramienta de trazabilidad: un rastro te dice que update_customer se ejecutó cuarenta veces, no cuáles eran los valores antes.

Qué puede y qué no puede hacer

Cada herramienta recibe una de cuatro clasificaciones, que escribes en un manifiesto:

Clase

Significado

Ejemplo

Qué ocurre

readonly

No cambia nada

get_customer

Se registra, se reenvía

reversible

El estado previo puede restaurarse

update_customer

Se captura el estado antes de escribir; se restaura al deshacer

compensable

No se puede revertir, pero sí compensar

create_charge

Una llamada distinta lo neutraliza

irreversible

Ni lo uno ni lo otro

send_email

Se suspende hasta que un humano lo aprueba

Una herramienta que tu manifiesto no menciona se trata como irreversible. Es deliberado: reenviar en silencio una llamada destructiva desconocida es el único fallo que de verdad hay que evitar.

Related MCP server: mcp-compensator

Requisitos

Herramienta

Versión

Compruébalo con

Node

22 o superior

node --version

pnpm

9 o superior

pnpm --version

Un toolchain de C

cualquiera

cc --version

pnpm viene con Node a través de corepack:

corepack enable pnpm

El toolchain de C se necesita una sola vez, para compilar los bindings nativos de SQLite. En macOS ejecuta xcode-select --install; en Debian o Ubuntu, apt install build-essential.

Instalación

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

O desde un clon, si prefieres leerlo primero:

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

El script comprueba tu versión de Node, compila y enlaza synartesis y synartesis-proxy en el primer directorio escribible que ya esté en tu PATH. No edita ningún perfil de shell y no necesita sudo. Pasa --no-link para solo compilar.

synartesis --help

Si no se pudiera enlazar nada, nada se rompe: cada comando de Synartesis se explica por sí mismo en la forma que realmente se ejecuta en tu máquina.

Recorrido

Esto usa un CRM de juguete que viene con el repositorio, para que veas el bucle completo sin apuntar a datos reales. Ejecútalo desde un directorio vacío.

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

1. Escribe una política

init arranca un servidor, le pregunta qué herramientas tiene y escribe un manifiesto. Sustituye SYNARTESIS por la ruta donde clonaste.

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

Abre synartesis.yaml. Toda herramienta que no se declara a sí misma como lectura empieza como irreversible con un TODO. Completar esos TODOs es tu trabajo. Una política terminada para este fixture viene en el repositorio, así que cópiala en lugar de escribirla a mano:

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

Luego edita la línea que dice dónde vive el servidor, para que apunte a tu clon y guarde sus datos en este directorio:

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

2. Apunta tu agente al proxy

Donde tu cliente MCP liste servidores, sustituye la entrada del servidor que quieres cubrir por el proxy. Para Claude Desktop o Claude Code, eso es un bloque mcpServers:

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

El agente ve las mismas herramientas, con los mismos nombres y los mismos resultados. Ese es el punto: nada cambia para tu agente.

Para este recorrido no necesitas un agente real. Esto hace lo mismo:

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

Mira el daño:

cat crm.json

Ada está en el plan equivocado con las notas equivocadas, y Grace ha desaparecido.

3. Mira qué hizo

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

show imprime cada llamada con su clase, su estado y la llamada exacta que la desharía, ya resuelta a valores literales.

4. Deshazlo

Mira antes de saltar:

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

Luego hazlo:

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

Grace ha vuelto y Ada está en su plan original, con sus notas originales.

5. Mira cómo se niega

Deshacer no es un instrumento contundente. Si algo más cambió un registro después de que el agente lo tocara, escribir el valor antiguo encima destruiría ese trabajo, así que Synartesis se detiene y te muestra ambos valores.

Ejecuta el comando dañino del paso 2 otra vez. Eso crea una segunda ejecución, así que toma el id de ejecución de arriba en list, que está ordenado de más reciente a más antiguo. Luego edita el registro a mano:

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

Se detiene, imprime el estado esperado y el real, sale con código distinto de cero y no cambia nada.

Aprobar lo que no se puede deshacer

send_email está clasificada como irreversible, así que el agente no puede enviar un correo por su cuenta. La llamada se rechaza de inmediato con un id de acción y el comando que la aprobaría. El agente te lo cuenta, tú decides, y lo intenta otra vez.

No mantiene la llamada abierta mientras espera. Ese fue el primer diseño y no sobrevive al contacto con un cliente real: cualquier ventana útil para que una persona se dé cuenta, abra una terminal y decida es más larga de lo que un cliente esperará a que una herramienta responda, así que no se pueden reconciliar eligiendo un mejor tiempo de espera.

La aprobación tampoco ocurre en la terminal que usa el agente: el proxy habla MCP por stdin y stdout, así que no hay nada donde mostrar un aviso, y un cliente de escritorio no tiene terminal alguna. La petición va al diario, y tú la respondes desde cualquier sitio:

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

Una aprobación es de un solo uso y caduca a la hora, así que cubre el reintento para el que se concedió y no puede autorizar en silencio la misma llamada mañana. No está atada a una sesión concreta, porque la gente reinicia su cliente y una aprobación atrapada en una sesión muerta no sería una aprobación en absoluto.

Nada se aprueba por silencio. Una petición sin respuesta simplemente sigue sin respuesta, visible en synartesis gates hasta que alguien decide.

Al agente se le cuenta todo esto cuando se conecta, para que pueda explicarse en lugar de informar de un fallo opaco.

Servidores reales

Si prefieres seguir los pasos a leer sobre ello, hay una guía para ejecutar esto contra tus propios archivos, con el gate y la comprobación de deriva como las dos cosas que merece la pena probar a propósito.

Synartesis no tiene nada que ver con el correo electrónico en particular. Se apoya en el protocolo MCP, así que su tema es lo que los servidores que has conectado puedan hacer: tus archivos, tus repositorios, tu base de datos, tus tickets, la memoria de tu agente. Lo que puede deshacer depende por completo de lo que esos servidores expongan, y cada manifiesto siguiente dice claramente hasta dónde llega.

Manifiesto

Servidor

Estado que gobierna

filesystem.yaml

@modelcontextprotocol/server-filesystem

archivos reales en disco

memory.yaml

@modelcontextprotocol/server-memory

el grafo de conocimiento que un agente guarda sobre ti

git.yaml

mcp-server-git

el índice y el historial de un repositorio real

github.yaml

github/github-mcp-server

issues, pull requests, contenidos de archivos

toy-crm.yaml

el fixture de este repositorio

el ejemplo trabajado de cada clase

Todos excepto github.yaml se comprobaron contra el servidor realmente en ejecución. Dos demos recorren el bucle completo de verdad:

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

La demo de filesystem sobrescribe un archivo y mueve otro, restaura ambos, luego muestra a deshacer negándose cuando un humano editó el archivo en medio, y al gate negándose a crear un directorio que este servidor no tiene forma de eliminar.

La demo de memory es la más afilada. El agente añade dos personas al grafo, una de las cuales ya estaba, y el servidor ignora silenciosamente el duplicado. Deshacer tiene por tanto que eliminar exactamente una: la inversa se construye a partir de lo que el servidor dijo que creó, no de lo que el agente pidió, así que la persona que ya estaba primero sobrevive al deshacer. La misma sesión intenta luego borrar una entidad y se detiene, porque borrar una entidad también borra todas las relaciones que la tocan y una sola llamada inversa no puede devolver ambas.

Dónde se agota cada uno

Los límites son la parte interesante, y son propiedades de los servidores más que de Synartesis.

  • filesystem: move_file es reversible solo con sus argumentos, así que no se declara ninguna pre-lectura y no se puede comprobar la deriva para ella. create_directory es irreversible no porque los directorios sean valiosos, sino porque este servidor no expone forma alguna de eliminar uno.

  • memory: add_observations y delete_observations son opuestos exactos que no se ponen de acuerdo sobre cómo llamar al mismo campo. Una ruta puede leer un campo y no puede renombrarlo, así que esa inversa no se puede escribir en absoluto y la llamada se somete al gate.

  • git: casi toda lectura que este servidor ofrece responde en prosa pensada para una persona, así que casi nada puede invertirse a partir de un estado capturado, por reversible que sea la operación git subyacente. Los commits pasan por el gate porque este servidor no expone reset, ni revert, ni forma de mover una rama.

Dos cosas que conviene saber si escribes las tuyas, ambas descubiertas ejecutando estas contra servidores vivos en lugar de leyendo documentación:

$result es el bloque estructurado, y no tiene por qué coincidir con el bloque de texto. El servidor de memory responde a create_entities con una lista desnuda en su bloque de texto y con {"entities": [...]} en structuredContent. Synartesis recorre el estructurado, porque ese es el contrato legible por máquina.

Y synartesis check demuestra que una herramienta existe, no que una ruta se resuelve. No puede: no se ha hecho ninguna llamada, así que no hay resultado que recorrer. Ejecuta la cosa una vez y lee synartesis show antes de confiar en una inversa.

Escribir un manifiesto

El manifiesto es el producto entero. Debería llevarte quince minutos para una API que conozcas.

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

Hay exactamente tres cosas a las que un valor puede referirse:

Prefijo

Se refiere a

Disponible en

$.

los argumentos que envió el agente

snapshot e inverse

$snapshot.

lo que capturó la pre-lectura

inverse

$result.

lo que devolvió la llamada directa

inverse

Cualquier otra cosa es un literal. Una referencia puede estar sola, en cuyo caso el valor conserva su tipo, o dentro de una frase, en cuyo caso se sustituye como texto:

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

Escribe $$ para un signo de dólar literal. No hay expresiones, condicionales ni funciones, y no las habrá: en el momento en que esto se convierte en un lenguaje deja de ser algo que puedas escribir en quince minutos.

Las rutas pueden indexar una lista con [0] y leer un campo de cada elemento con []:

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

Eso cubre el caso común en que una API devuelve un campo más rico de lo que lo acepta, que es lo que GitHub hace con las etiquetas de issues. [] lee la misma clave de cada elemento y nada más: sigue siendo una ruta, no una transformación. Una referencia copia valores, no puede calcularlos, así que una API que necesite una forma genuinamente distinta es una de la que la inversa debería omitir ese campo, y decirlo.

Otras cosas que saber:

  • match admite *, que coincide dentro de un segmento: crm.send_* coincide con crm.send_email pero no con crm.a.b. El patrón más específico gana independientemente del orden en que estén escritas las reglas.

  • La inversa de un parche debería restaurar todos los campos, no reaplicar un parche. Si el mismo registro se edita dos veces en una ejecución, una inversa parcial deja atrás los campos que tocó la segunda edición.

  • gate: on_write es una heurística para herramientas como un ejecutor de SQL sin procesar, donde la destructividad no se puede leer del nombre de la herramienta. Todo lo que no pueda leer con confianza como una única sentencia de lectura se somete a la compuerta. Usa gate: always dondequiera que la certeza importe.

  • Un manifiesto malformado impide que el proxy se inicie, indicando el archivo y la línea a corregir. Nunca se ejecutará con una política que no haya podido entender.

Comandos

Comando

Función

init <server> -- <cmd>

Inspecciona un servidor y redacta un manifiesto

list

Cada ejecución registrada

show <runId>

La línea de tiempo de una ejecución, con el deshacer para cada paso

gates

Lo que está esperando una decisión

approve <actionId>

Permitir una llamada suspendida

deny <actionId>

Rechazar una

undo <runId>

Invertir una ejecución, acción más reciente primero

undo <runId> --replan

Igual, pero reconstruye cada deshacer desde el manifiesto actual

check

Carga un manifiesto y verifícalo contra los servidores que nombra

--manifest y --journal se encuentran en lugar de escribirse. Ambos se buscan desde el directorio actual hacia arriba, de la misma manera que una herramienta de control de versiones encuentra su raíz, de modo que dentro de un proyecto que tenga un synartesis.yaml, todos los comandos funcionan sin ninguna bandera. Un diario que aún no existe se coloca junto a la política, de modo que el proxy que lo crea y la CLI que lo lee coinciden sin que se les diga.

Otras banderas: --dry-run, --to <seq> y --replan en undo, --all en approve y deny, --json en list, show y gates.

Códigos de salida: 0 éxito, 1 detenido o rechazado, 2 uso o configuración incorrectos.

El proxy acepta --manifest, --journal, --gate-timeout <seconds> y --log-level. Registra JSON estructurado en stderr; stdout está reservado para el tráfico de protocolo.

Lo que no hace

  • No puede desenviar lo que ya se ha visto. Un correo que ha sido leído, un mensaje publicado, un archivo eliminado sin copia de seguridad. Por eso existe la compuerta.

  • Las acciones compensables no pueden verificarse en busca de desviaciones. No declaran ninguna pre-lectura, por lo que el deshacer las compensa y las marca como [unverified] en su informe.

  • El deshacer se detiene ante la incertidumbre y pasa por alto lo meramente permanente. La desviación, un resultado desconocido o una llamada de reversión fallida lo detienen, porque continuar más allá de eso podría destruir algo. Una acción que simplemente no se puede deshacer, como un correo enviado, se informa y se deja en su lugar mientras todo lo demás se revierte: ninguna cantidad de detención lo desenvía, y detenerse solo dejaría el resto también mal. De cualquier manera, la ejecución se marca como partial.

  • Una llamada interrumpida a mitad de vuelo se registra como desconocida, no como fallida. El deshacer se niega a pasar por encima, porque no se puede determinar si se aplicó.

  • Un deshacer es tan bueno como la política que lo registró. Las inversas se resuelven cuando ocurre la llamada, no cuando deshaces, por lo que un error en un manifiesto se incorpora a cada ejecución realizada bajo él. undo --replan las reconstruye a partir de un manifiesto corregido usando el estado ya capturado, que es la salida.

Viéndolo funcionar

Synartesis no es un demonio y no puede serlo. Un cliente MCP genera un servidor stdio por sí mismo y es dueño de su ciclo de vida, por lo que nada de larga duración podría sentarse en medio y ver esas llamadas. Lo que una persona quiere de un demonio suele ser la tranquilidad de que está ahí y haciendo algo, y eso necesita un lugar donde mirar en lugar de un proceso en segundo plano:

synartesis watch

Se redibuja mientras el agente trabaja: qué se ha llamado, qué clase era cada llamada, y cualquier cosa que espera una decisión, con el comando para aprobarla. Ctrl-C lo detiene. Si se canaliza en lugar de ejecutarse en una terminal, imprime el estado una vez y sale.

Confianza

Un manifiesto nombra comandos y Synartesis los ejecuta. Trata uno que no hayas escrito de la misma manera que tratarías un script de shell de la misma fuente: léelo primero. Aquí no hay sandbox, y no se pretende que lo haya.

Desarrollo

pnpm test
pnpm typecheck && pnpm lint

Cada push ejecuta esos en Linux y macOS en Node 22 y 24, además de la demo y el instalador.

Licencia

MIT. Ver 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