Skip to main content
The adapters live in reversi_sdk.integrations. Each one routes tool calls through a run, so every call is authorized, journaled before it executes, and given an outcome afterwards. The SDK core stays standard library only: each adapter imports its framework lazily, and import reversi_sdk never needs one.
How these were tested. The adapters are duck-typed against small fakes of each framework’s interface (a tool with name and invoke, a FunctionTool-shaped dataclass, a tool() registrar, and so on), run against a fake Reversi server and, for the Claude Code hook, also the real built server. They were not run against installed copies of LangGraph, CrewAI, the OpenAI Agents SDK, the mcp package or Claude Code itself. The shapes below target the publicly documented interfaces of the current LangChain core BaseTool, CrewAI BaseTool, OpenAI Agents SDK function_tool and guardrails, MCP Python SDK FastMCP, and Claude Code hooks; no specific version range has been verified. If a release changes them, the adapter is the part to adjust. Treat a first run in your own environment as the real test.
Every tool name you pass must have a manifest on the server, and the run must hold its permission. A refusal is a normal result, not an error: the adapters either return an explanation the model can read (on_block="message", the default) or raise (on_block="raise"). When the refusal is a pending approval, the message carries the approval id so a human can approve it and the agent can retry once.

LangGraph and LangChain

reversible_tool wraps a plain function (apply it under @tool so LangChain still sees the signature and docstring) or a BaseTool-like object that has name and invoke(input). Async functions and ainvoke are supported.
rollback_node returns a function you can add to a graph as a node. It runs RunHandle.rollback (needs an operator key) and puts the report in the state under reversi_rollback:
Limits: ToolNode and create_react_agent need real BaseTool instances. Wrap the function before @tool, as above; the proxy returned for an existing tool object is not itself a BaseTool subclass.

CrewAI

ReversibleCrewTool has the shape of crewai.tools.BaseTool (name, description, _run) and wraps an existing tool. CrewAI validates its tools= list against the real BaseTool class, so call to_crewai() to get a genuine subclass instance that delegates to the adapter (it imports crewai at that moment).
Limits: to_crewai() builds the subclass dynamically and copies name, description and args_schema; custom pydantic fields on your own tool class are not carried over.

OpenAI Agents SDK

Put reversi_function_tool under @function_tool. A ready FunctionTool is also accepted: its on_invoke_tool(ctx, input_json) is wrapped and the parsed JSON arguments are journaled.
With on_block="raise" the SDK hands the Blocked error to tool_error_formatter, which turns it into text for the model (including the approval id). With the default "message" the tool simply returns that text. guardrail(client, run) returns an input guardrail function that trips while the kill switch is on (client.status()["halted"]). It also trips if Reversi cannot be reached.
The guardrail returns agents.GuardrailFunctionOutput when that package is installed. The kill switch is also enforced at every tool call, so the guardrail only stops a halted agent earlier.

FastMCP

reversible replaces @mcp.tool(): it wraps the function and then registers it. run_factory(ctx) returns the run for the call; ctx is the tool’s Context parameter (named ctx or context) or None. per_session_runs is a ready factory that opens one run per MCP session.
A refusal raises ReversiBlocked (with approval_id and reasons). FastMCP returns its message to the client as a tool error, so the model sees the approval id. Pass extra mcp.tool arguments as keyword arguments to reversible, or tool_name= when the Reversi manifest name differs from the function name. Limits: sessions are keyed by the identity of ctx.session; a run is kept in memory for the life of the process.

Claude Code hook

reversi-claude-hook implements Claude Code’s PreToolUse and PostToolUse hooks. It opens one Reversi run per Claude session (claude-<session_id>), asks Reversi before each tool call, and records the outcome afterwards. Claude Code tool names are used as Reversi tool names (Bash, Write, …), and mcp__server__tool becomes server.tool. Register a manifest for each one you want to allow; an unknown tool is blocked by the server. Install the package, export the configuration in the shell that starts Claude Code, and add the hooks:
.claude/settings.json:
Keep the agent key out of the settings file, which is usually committed. Limits to know about:
  • ask shows Claude Code’s own permission prompt. If the human says yes there, the tool runs even though the Reversi approval is still pending, and no step is recorded for that call. For a hard gate, use deny semantics by not granting the tool, or have the approver decide first and let the agent retry.
  • The Post hook never blocks. If it cannot reach Reversi it prints a note on stderr and the step stays unresolved, which the server reports instead of hiding.
  • Reversi can only undo what a manifest describes. Claude Code’s shell and file tools are usually irreversible or idempotent, so rollback will list them as not undoable rather than undo them.
  • Hook input fields (tool_use_id, PostToolUseFailure) follow Claude Code’s documented format; without a tool_use_id the hook falls back to matching on the tool name and input.

Webhook signatures

If your Reversi server signs outbound webhooks, verify them with the raw body bytes before parsing:
The header has the form sha256=<hex>, the HMAC-SHA256 of the body under the shared secret. The comparison is constant time, and a missing or malformed header returns False. The header name depends on the server configuration; the helper only checks the value you pass.

Optional pydantic models

reversi_sdk.pydantic_models provides pydantic v2 ToolManifest and Compensation models with the same rules as the standard library validator. Install with pip install 'reversi-sdk[pydantic]'. The SDK does not depend on pydantic and the standard library validator (validate_manifest) stays authoritative; a test checks both against the shared conformance cases.