TL;DR: The Model Context Protocol (MCP) is solving one of the hardest developer problems in AI: giving models a standard way to find tools and context. But once an agent selects a tool, execution is usually direct: the agent calls the tool with standing API credentials. If an LLM hallucinates, misreads a number, or gets tricked by a prompt injection, nothing stands in between. We're experimenting with an inline zero-trust proxy written in Rust that treats agent tool calls as untrusted proposals rather than automatic commands. Here is what we've learned, what works locally, and where we're still looking for answers.
The Great Thing About MCP
If you have spent any time connecting LLMs to databases, internal APIs, or file systems, you know the pain: every framework used to invent its own bespoke tool format.
Anthropic's Model Context Protocol (MCP) changes that for the better. It gives the industry an open, standard way for an assistant (like Claude Desktop or any custom agent loop) to ask an environment: "What tools do you have, and what schemas do they take?"
flowchart LR A["🤖 LLM / Agent Loop"] -->|"1. What tools exist?"| M["🔌 MCP Server"] M -->|"2. read_file, charge_card"| A A -->|"3. Call: charge_card(50000)"| M M -->|"4. Execute"| S["💳 Production Backend"] MCP acts like a clean, universal adapter catalog. It does this job really well, and we think it's winning that role for good reason.
Where the Problem Shifts: Ambient Authority
MCP solves discovery and interface standardization. But it intentionally doesn't solve runtime safety policies.
In most default setups today:
- The developer runs a local or remote MCP server.
- That server holds a long-lived API key or database credential.
- When the agent asks the server to execute an action, the server just executes it.
In computer security, this is called ambient authority: whoever holds the connection holds the full permission of the master key.
This works fine for personal desktop productivity or harmless read operations (like reading local markdown files or querying a documentation site). But consider what happens when an autonomous agent is connected to real systems:
- What if the agent misinterprets currency units and submits
50000dollars instead of50000cents? - What if an indirect prompt injection in a customer support ticket tricks the agent into triggering a
delete_customerorrefund_alltool? - Who generates a cryptographic, tamper-evident receipt that proves why and under what exact parameters that tool was invoked?
If an agent has direct execution rights, a single prompt error or edge-case reasoning failure immediately mutates your business state.
Our Experiment: Agents Propose, Firewalls Decide
Over the last few months, we've been experimenting with a simple rule:
An agent should be allowed to propose an action, but it should never have the authority to execute it directly.
Rather than building another agent framework or replacing MCP, we built a small, standalone security proxy in Rust called the Trust Gateway, accompanied by an inspection CLI (trustctl).
Instead of the agent loop talking directly to the system with master credentials, the architecture puts an inline checkpoint between the agent and downstream executors:
sequenceDiagram autonumber actor User as 👤 User participant Agent as 🤖 Agent (MCP Client) participant LLM as 🧠 LLM Inference participant GW as 🛡️ Trust Gateway (Rust Proxy) participant Tool as 🔧 Sandboxed API / Backend User->>Agent: Send user task Agent->>LLM: Request plan LLM-->>Agent: Propose tool intent Agent->>GW: Submit action proposal envelope Note over GW: • Verify caller identity token<br/>• Check signed contract bounds (e.g. max $500)<br/>• Quarantine for human quorum if destructive GW->>Tool: Approved: Issue single-use grant Tool-->>GW: Return signed execution receipt GW-->>Agent: Return verified result 3 Core Principles We Are Testing
1. Zero Standing Credentials for Models
In our test setup, the agent never touches raw API secrets or long-lived bearer tokens.
When the agent decides to invoke an API, it signs an action proposal envelope (canonicalized via RFC 8785). The gateway inspects the proposal. If valid, the gateway mints a short-lived, single-use grant (typically with a 30-second TTL) that is bound to the SHA-256 hash of the exact input arguments.
If the agent tries to reuse the grant, or tamper with the payload to change the destination account, the signature fails closed.
2. Offline-Verifiable Contracts (Ed25519)
We wanted to ensure that evaluating safety doesn't add a slow, brittle database dependency in the hot path.
Using trustctl, an engineering team can pre-sign API capability bounds offline using standard Ed25519 keys:
# Define strict bounds on a payment tool (e.g. max charge $500 USD) trustctl contract sign \ --contract stripe_payment_bounds.json \ --key ops_private_key.pem # Gateway verifies the signed policy locally in microseconds without network I/O trustctl contract verify \ --contract stripe_payment_bounds.json \ --key ops_public_key.pem Our core verification engine in Rust contains zero async code and zero network access. It is a deterministic state machine: it takes a proposal, verifies the cryptographic signature against the active policy bounds, and outputs an allow or reject decision in sub-millisecond time.
3. Fail-Closed Triage for Unknowns & Drift
What happens when a tool's schema changes, or an agent calls an endpoint marked as destructive?
Rather than crashing or guessing, the gateway puts unverified or high-risk actions into an isolated ProbationStore (using SQLite with Write-Ahead Logging). In our demo environment, destructive actions require a two-human quorum signature before a grant is issued. The agent pauses its task loop and waits until the approval event appears in its audit stream.
What a Real Run Looks Like
To test this mechanically, we wired a sample scenario: an agent tasked with fetching data and triggering an action on a billing API.
When the agent attempts an operation, we can run our end-to-end conformance check:
Step 1: Checking Port Assignments & Topology Invariants... ✓ All service boundaries verified. Step 2: Checking Zero-Trust Route Inventory across Services... ✓ Internal execution routes strictly enforce token verification. Step 3: Running Governed Agent Onboarding (OpenAPI classification)... 🛡️ Analyzed stripe_billing spec: 14 queries, 3 mutations, 1 destructive. ✓ Destructive endpoints automatically flagged for triage quarantine. Step 4: Cryptographic Contract Signing & Offline Verification... ✓ Contract 'contract_payment_guard_001' signature is VALID. Step 5: Verifying Gateway Health Probes... * Cognitive Ingress Proxy : 8088 -> 🟢 ONLINE * Trust Gateway Execution Proxy : 8081 -> 🟢 ONLINE * Human Triage Policy Node : 4015 -> 🟢 ONLINE Every executed step leaves an append-only audit trace. When we inspect the gateway's replay log, we see:
- The exact user prompt that prompted the plan.
- The model's proposed tool invocation.
- The specific contract ID and policy rule that checked it.
- The signed execution receipt emitted after the backend executed.
What We're Still Grappling With
This is an ongoing experiment, and several hard questions remain open:
- Latency Overhead: Intercepting every tool call to canonicalize JSON, verify signatures, and log to a WAL sink introduces a small cost (around 1–3 ms locally). In automated multi-step agent loops that make dozens of calls, does this overhead become noticeable compared to the LLM generation time?
- Calibration of Human Friction: If an agent escalates too many calls to human approval, users suffer from "approval fatigue" and start blindly clicking "Allow". How do you draw the line between a routine mutation that should auto-pass under contract bounds and one that genuinely warrants stopping a human?
- Developer Experience: Developers love MCP because you can spin up a tool server in 10 lines of Python or TypeScript. Asking teams to write contracts, sign them with Ed25519 keys, and route requests through an inline proxy introduces operational friction. We're experimenting with automated OpenAPI onboarding (
trustctl onboard) to generate these drafts automatically, but keeping friction low remains the main hurdle.
Try It & Poke Holes in the Experiment
This is very much an ongoing experiment, not a finished standard or commercial product announcement.
The code is public at github.com/fcn06/trust_gateway if you want to inspect how the Ed25519 grant issuance works or run the offline test suite locally (cargo test).
We're actively looking for peer critique:
- How are you currently handling tool execution security in your MCP setups?
- Where does this inline proxy pattern introduce too much friction for developers?
- How would you design the quarantine boundary for novel tools?
Pull requests, GitHub issues, and blunt critiques are all welcome. Drop your thoughts in the comments below!


