By MAREF Engineering
Model Context Protocol (MCP) has become the universal interface for agent-to-tool communication. Claude Code, Cursor, and Windsurf all speak it. The question isn't whether your agent will use MCP — it's whether anyone is watching the tools it calls. MAREF speaks MCP on both sides of that conversation, so governance is a layer, not a bolt-on.
This tutorial is written against the real MAREF API (maref.integration.mcp_client
and maref.integration.mcp_bridge
). Everything below runs on a stock pip install maref
.
Two roles, one protocol #
MAREF works in two directions over MCP, and understanding which one you need is the whole setup:
MAREF as MCP client— MAREF reaches out to external MCP servers (file, shell, browser, email, or a third-party tool server), lists their tools, and runs every invocation through its security gate before the call goes through.MAREF as MCP server— Claude Code / Cursor / Windsurf connect to MAREF as a tool server. Every tool the agent would call becomes a governed tool in MAREF's registry.
Most teams start with the first and graduate to the second. Both are covered below.
1. MAREF as an MCP client — governing external tools #
The entry point is MCPClient
. You register an external server with an MCPServerConfig
, and MAREF manages the connection lifecycle — initialize, capability negotiation, reconnects — for you:
from maref.integration.mcp_client import MCPClient, MCPServerConfig
client = MCPClient()
config = MCPServerConfig(
command=["npx", "-y", "@some/tool-server"],
transport_type="stdio", # or "sse" with url=
server_name="my-tool-server",
env={"TOOL_API_KEY": "..."},
)
conn = client.register_server(config) # returns an MCPConnection
tools = client.list_tools(conn) # list[MCPToolDef]
Now the interesting part. A raw MCPClient.call_tool
skips governance. The safe path is MCPBridge
, which wraps every call in the security gate:
from maref.integration.mcp_bridge import MCPBridge
bridge = MCPBridge(client) # optional: pass your own MCPSecurityGate
bridge.on("maref.mcp.invoke", lambda e: print("governed:", e.data))
bridge.discover_tools(conn) # security-check each tool once
result = bridge.invoke_tool(
conn,
tool_name="create_file",
args={"path": "/tmp/demo.txt", "content": "hello"},
)
That one line — bridge.invoke_tool
— is the difference between "an agent that can call any tool" and "an agent that can call tools its policy allows." Every invocation emits a maref.mcp.invoke
event you can route to your audit log, SIEM, or dashboards.
2. MAREF as an MCP server — governing Claude Code / Cursor #
If your agent host already speaks MCP, expose MAREF's own tool registry as an MCP server. The MCPServerAdapter
bridges MAREF's ToolRegistry
to the MCP wire protocol — list_tools
and handle_tool_call
are the two methods the protocol needs:
from maref.mcp.router import MCPServerAdapter
from maref.tools import ToolRegistry
registry = ToolRegistry() # your governed tools live here
adapter = MCPServerAdapter(registry)
adapter.handle_tool_call("send_email", {"to": "[email protected]"})
In practice you usually mount this behind the full MCPServer
implementation (maref.integration.mcp_server
), which gives you resources, prompts, and sampling callbacks on top of tools. MAREF doesn't ship a built-in maref mcp serve
CLI command — the stdio entrypoint is a ~15-line launcher wired straight to the real MCPServer
API:
import json, sys
from maref.integration.mcp_transport import JSONRPCRequest
from maref.integration.mcp_server import MCPServer
server = MCPServer(name="maref-mcp-server", security_gate=gate) # gate: your security gate
for line in sys.stdin: # newline-delimited JSON-RPC 2.0
msg = json.loads(line)
req = JSONRPCRequest(method=msg["method"], params=msg.get("params"), id=msg.get("id", 0))
resp = server.handle_request(req)
sys.stdout.write(json.dumps({"jsonrpc": resp.jsonrpc, "result": resp.result, "error": resp.error, "id": resp.id}, ensure_ascii=False) + "
")
sys.stdout.flush()
{
"mcpServers": {
"maref": {
"command": "python3",
"args": ["/path/to/mcp_stdio.py"]
}
}
}
From that point on, when Claude Code or Cursor calls any tool, the call passes through MAREF's governance state machine — policy decision tree, safety gates, and audit trail — before it touches the world.
3. What governance actually blocks #
Governance isn't a suggestion. It's a decision, and it's made four ways in the policy tree — Rule → Mode → SafetyGate → User:
- A
hard rule(never touch
/etc
) blocks instantly, no model consultation. - The current mode(read-only, triage, full) narrows what's permitted. - The safety gate catches risky operations — high blast radius, untrusted targets, anomalous patterns. Human escalation fires for the genuinely dangerous cases, with a named approver and an audit line.
And because every decision is signed per-agent (Ed25519) and written to the audit log, "which agent did this?" is never a debate.
Try it now #
The fastest way to see the loop working is the local demo — it boots a governed toy agent and a live dashboard so you can watch BLOCK/ALLOW decisions stream in:
pip install maref
maref demo --port 8080
*🛡️ Sources: MAREF source — src/maref/integration/mcp_client.py (MCPClient, MCPServerConfig, register_server), src/maref/integration/mcp_bridge.py (MCPBridge.discover_tools / invoke_tool), src/maref/integration/mcp_server.py (MCPServer), src/maref/mcp/router.py (MCPServerAdapter). *