Tags: mcp, agents, hateoas, authorization
If you let an AI agent loose on an API with 15 tools, it will eventually figure out the right sequence. But it will also try things that don’t make sense, hit errors, retry, and waste tokens doing it. The question is: can we make the API itself tell the agent what to do next?
Consider an order management system. An order goes through states: pending → confirmed → shipped → delivered. At each state, only certain actions are valid. You can’t ship a pending order. You can’t confirm a delivered one.
A human developer reads the docs and knows this. An AI agent sees a flat list of tools: create_order, confirm_order, ship_order, deliver_order, cancel_order, get_order, list_orders, and has to figure out the valid sequence by trial and error.
This gets worse with scale: A supply chain system might have 15+ tools, some of which are traps (insurance, claims, recalls) that sound plausible but are never the right next step. The agent has to navigate a 10-step linear chain while ignoring five distractors.
My first approach to a solution was borrowed from REST: HATEOAS (Hypermedia as the Engine of Application State). The idea is old. Roy Fielding described it in his PhD thesis 2000 and I thought this maps surprisingly well to the agent problem.
In HATEOAS, every API response includes _links that tell the client what it can do next. A pending order response includes a link to confirm it. A confirmed order includes a link to ship it. The client never needs to know the state machine. The API essentially guides the agent.
Here we could also encode permissions of the agent into a JSON policy file mapping (resource_type, state, capability) tuples to permitted actions. A FastAPI middleware could extract JWT capabilities and filter the _links in every response. JSON-LD annotations, href templates, rel semantics.
But HATEOAS felt extremely clunky, mapping REST terminology to MCP. The real insight is: if you control what tools the agent can see, you control what it can do.
HATEOAS achieves this through hypermedia links in REST responses. But AI agents don’t consume REST APIs the way browsers do. They use primarily MCP (Model Context Protocol). They discover tools via tools/list and invoke them via tools/call. The _links abstraction is a detour.
So instead of REST, JSON-LD, … We just need a policy engine that answers one question: given this resource type, its current state, and the agent’s capabilities, which tool names are allowed?
|
|
Capability could also contain more abstract roles required for a specific API call.
Fei et al. 1 introduce MCP-Zero, a framework where agents actively discover tools on-demand instead of receiving all available tools upfront. By requesting only the tools they need, agents achieve a 98% reduction in token consumption while maintaining accuracy. Zhang et al. 2 take a complementary approach with EcoAct, letting LLMs selectively register tools into their context during reasoning rather than loading everything upfront. Majumdar et al. 3 formalize this theoretically in their Sparse Agentic Control framework. They prove that when the action space is large (many tools) but only a small subset is relevant, any policy that considers all actions requires samples proportional to the total number of tools.
My approach is server-side rather than agent-side, where the API itself controls what the agent sees, rather than trusting the agent to filter its own tool set. But the underlying principle is the same: smaller action spaces lead to better decisions.
I ran a benchmark. Two scenarios, each with obfuscated tool names (random strings like xq7_proc, tn5_verify) so the model can’t cheat by inferring semantics from the name. Descriptions are deliberately vague (i.e. “Advance resource to next processing stage”).
Scenario A: 7-tool order lifecycle (create → confirm → ship → deliver) Scenario B: 15-tool supply chain with 5 trap tools (insurance, claims, recalls)
Each scenario runs 10 times with GPT-4o-mini, comparing:
next_tools hints
|
|
The roundtrip difference is modest. GPT-4o-mini is smart enough to recover from errors. But look at the error column. The guided agent makes zero wrong calls across all runs. The unguided agent averages 3.5 errors per run on the order scenario. That’s 3.5 wasted API calls, 3.5 unnecessary LLM roundtrips and 3.5 chances for the agent to go off the rails in a production system.
The supply chain scenario is more forgiving because the 10-step chain has a natural ordering that the model can infer from descriptions. But even there, the guided agent is deterministically perfect while the unguided one occasionally stumbles.
The real cost isn’t roundtrips. It’s reliability. In a production system where tool calls have side effects, like, charging a credit card, shipping a package, modifying a database, you don’t want the agent trying cancel_order on a shipped order just to see what happens.
Where my HATEOAS idea breaks is going from “Capability filtering”, i.e. “what can this type of agent do?”, to ressource-level filtering, i.e. “can this agent touch this specific resource?”.
The policy engine accepts a resource_predicate as a function that receives the resource context (type, state, and arbitrary attributes like owner, region, amount) and returns a boolean. It runs before any rules are evaluated:
|
|
Agent A accessing their own order sees all applicable tools. Agent B accessing Agent A’s order sees nothing. The predicate blocks before rules are even checked.
This keeps the declarative policy simple (capability + state → tools) while letting you encode instance-level business logic in code where it belongs.
The library is called mcp-action-guard. It’s ~300 lines of Python with a PolicyEngine, an ActionGuard, and a GuardedMCPServer that wraps the MCP SDK.
The interesting open question is cross-MCP coordination.
Fei et al., “MCP-Zero: Active Tool Discovery for Autonomous LLM Agents”, arXiv:2506.01056, 2025. https://arxiv.org/abs/2506.01056 ↩︎
Zhang et al., “EcoAct: Economic Agent Determines When to Register What Action”, arXiv:2411.01643, 2024. https://arxiv.org/abs/2411.01643 ↩︎
Majumdar et al., “Sparsity Is Necessary: Polynomial-Time Stability for Agentic LLMs in Large Action Spaces”, arXiv:2601.08271, 2026. https://arxiv.org/abs/2601.08271 ↩︎