Integrations¶
interlock plugs in wherever your agent calls tools. This page shows each way to connect it, from a single decorated function to a whole MCP session or a Node.js agent. All of them run the same rules, so you can mix them freely.
| You have | Use | Checks calls | Checks results |
|---|---|---|---|
| Your own Python functions | @guard |
yes | yes |
| An MCP client session | guard_mcp_session |
yes | yes |
| LangChain tools | guard_langchain_tools |
yes | yes |
| A model SDK's tool loop (OpenAI, Anthropic, others) or any other framework | enforce_tool_call + enforce_tool_result |
yes | yes, when you call it |
| A Node.js agent | the interlock-guard npm package |
yes | yes for MCP and model-SDK tools |
| Subprocesses your agent launches | BRACE sandbox | the launch | OS-level confinement instead |
Your own functions¶
Put @guard() on any function your agent calls. It works on sync and async
functions:
from interlock import guard, monitor
@guard()
def transfer(amount: int, to: str) -> str: ...
@guard()
async def fetch(url: str) -> str: ...
@monitor() # observe only: record decisions, never block
def experimental_tool(x): ...
The event's action is the function's name, and args maps parameter names to
values (defaults included). Pass principal= to tag every call from this
function with a fixed identity, for example @guard(principal="billing-bot").
Without it, the principal comes from the surrounding span().
@guard always uses the process engine set by interlock.install().
MCP sessions¶
Wrap a client session once, and every call_tool is checked before it reaches
the server. The result is checked before your agent sees it.
from interlock import guard_mcp_session
session = guard_mcp_session(await make_client_session(), origin="github")
await session.call_tool("write_file", {"path": "/etc/passwd"}) # Blocked, never sent
It works with any object that has a call_tool(name, arguments) method, sync or
async, so it doesn't import the MCP SDK and is easy to test with a fake. It also
accepts engine=, enforcement=, and principal=.
The official SDK's CallToolResult is understood: its text content is what your
result rules read. origin= tells the MCP trust detectors which server the
session talks to; a server label the model slips into the arguments is dropped.
Checking which servers you connect to and whether their tools changed takes a
few more lines. The MCP guide covers that.
LangChain tools¶
from interlock import guard_langchain_tools
safe_tools = guard_langchain_tools(agent_tools, principal="agent-7")
agent = create_agent(llm, safe_tools) # whatever your agent constructor is
Requires pip install "interlock-guard[langchain] @ git+https://github.com/SourceShift/InterLock.git".
Each returned tool keeps the original's name, description, and argument schema,
so the model sees no difference. Each call is checked before the tool runs, and
what the tool returns is checked before the agent reads it:
import interlock
from interlock import guard_langchain_tools
from interlock.detectors.tool_result_injection_guard import tool_result_injection_guard
interlock.install(rules=[tool_result_injection_guard()])
safe_fetch, = guard_langchain_tools([fetch]) # fetch: a LangChain @tool
safe_fetch.invoke({"url": "https://docs.python.org"})
# -> '<html>ok</html>'
safe_fetch.invoke({"url": "https://blog.example.com"}) # the page carries an injected instruction
# -> Blocked: tool_result_injection: ignore previous instructions
A model SDK, or any other framework¶
Most agents end up with a loop that takes the model's tool request, runs the tool, and sends the result back. Put interlock in that loop:
from interlock import Blocked, enforce_tool_call, enforce_tool_result
TOOLS = {"fetch": fetch, "send_email": send_email}
def run_tool(name: str, arguments: dict):
try:
safe_args = enforce_tool_call(name, arguments)
result = TOOLS[name](**safe_args)
return enforce_tool_result(name, result)
except Blocked as e:
return f"[blocked by policy: {e.decision.reason}]"
run_tool("fetch", {"url": "https://evil.example"})
-> [blocked by policy: egress to 'evil.example' is not on the destination allowlist]
run_tool("fetch", {"url": "https://blog.example.com"})
-> [blocked by policy: tool_result_injection: ignore previous instructions]
run_tool("fetch", {"url": "https://docs.python.org"})
-> <html>ok</html>
Returning the refusal as the tool's result, rather than crashing the loop, lets
the model see what happened and try something else. Built-in detectors keep
their reasons short, though some name the host or IP address they refused. Make
sure your own rules don't put secrets in reason, since the model will read it.
enforce_tool_call returns the arguments to actually use, which may have been
rewritten. Always call the tool with its return value, not with the original
arguments.
Both functions accept engine=, enforcement=, and principal=.
Node.js agents¶
A Node.js agent uses the same Python engine through a small daemon that runs next
to it. You write the rules once in Python, and the interlock-guard npm package
asks the daemon for a verdict before each tool call.
import { homedir } from "node:os";
import { join } from "node:path";
import { RemoteEngine, guardMcpSession } from "interlock-guard";
const engine = new RemoteEngine(join(homedir(), ".interlock", "guard.sock"), { timeout: 5 });
guardMcpSession(client, { engine });
Warning If the daemon isn't running, every guarded call is denied, even in monitor mode.
RemoteEnginegives up after 5 seconds by default (and denies the call), so a hung daemon can't hang your agent. Don't passtimeout: nullin production.
The package can also guard fs, child_process, and fetch for the whole
process, and the tool map or runner of any model SDK. See
js/README.md for setup and the
sidecar reference for every daemon flag.
Subprocesses¶
When your agent runs a program (subprocess.run, a shell tool, generated code),
interlock can't see what that program does. Run it in the
BRACE sandbox instead. The launch is checked against your rules,
then the process runs with OS-level limits on network, files, and environment.
Next¶
- MCP guide: trusting servers, not just checking calls.
- Writing rules
- Python API reference