← All articles

Building a ReAct Agent on Top of MCP

An agent loop on top of MCP is one of those constructs that sounds simple in a slide and is genuinely simple in production, provided the protocol underneath gives you the right primitives. This article describes the structure of the AureganeAgent in the I-Machine platform : Its ReAct loop, the brain abstraction that swaps backends transparently, the conversation store that makes sessions resumable, and the tool bridge that enforces RBAC at the call site.

The agent loop

The agent runs a classic reason-act cycle. At each iteration the brain (an LLM behind an abstraction) sees the conversation history, the available tools, and the user’s goal ; It returns either a textual response, a tool call to execute, or a proposal that requires user confirmation. Tool calls execute against the MCP server, their results are appended to the conversation, and the loop continues until the brain produces a final answer or the user stops the session.

Two contracts hold the loop together. IAgentBrain is the port that any backend implements ; It receives a BrainRequest with conversation, tools, and policy flags, and returns a BrainResponse describing what to do next. AgentCallbacks is the symmetric port the loop hands back to the brain so it can emit streaming partial output, status messages, or proposals as it works.

Brain backends

The default deployment ships with two backends. A local brain talks to a llama.cpp HTTP server (via the OpenAI-compatible API exposed by the AILLMBackend in the AIHubPlugin) ; A Claude brain calls the Anthropic API. Both implement IAgentBrain, so the loop is agnostic to the choice. A BrainFactory reads the configured backend identifier and returns the right implementation at session start ; New backends drop into the factory without touching the loop.

Tool dispatch and RBAC

The brain does not call MCP tools directly. Tool calls go through a ToolBridge that knows which tool prefixes the current session is allowed to invoke (its allowedPrefixes list). Calls that fall outside that set are rejected before they reach the MCP server. The bridge is the single enforcement point for RBAC inside the agent ; The server enforces it again on its own side, but the bridge fails fast and keeps the audit trail readable.

The list of allowed prefixes is derived from the user identity that opened the session and the mission template the agent runs. Both are server-side facts ; The brain never gets a chance to talk itself into a privilege it doesn’t have.

Conversation persistence

Every session writes through to a conversation store (ConversationStore, the port ; the default adapter persists to the platform data store). Each entry carries the role, the content, the tool calls and results, and the timestamps that let a watching UI replay the run. Resumption works because sessions are heap-allocated and keyed by a stable identifier, and because the store is the source of truth ; If the server process restarts mid-loop, the session can be reopened from disk and the brain re-prompted from the persisted history.

The proposal queue

Not every action the brain wants to take is one the loop should execute immediately. Mutations on shared systems, expensive operations, and anything flagged in the mission template as requiring confirmation are emitted as Proposal objects onto a ProposalQueue. The client UI renders the queue in real time, the user accepts or rejects each proposal, and the loop resumes once the response arrives via proposal_respond. This separation keeps the agent capable of suggesting bold actions without making them irreversible.

The MCP surface

Seven tools expose the agent to MCP clients :

  • assistant_chat : Submit a user message, drive the loop until the next pause.
  • assistant_status : Read the current session state for a watching UI.
  • assistant_cancel : Interrupt the loop cleanly between iterations.
  • assistant_history : Replay a session’s conversation entries.
  • assistant_conversations : List a user’s sessions.
  • assistant_list_backends : Discover the brain backends available to the caller.
  • assistant_proposal_respond : Accept or reject a pending proposal.

Every client (native, web, command line) goes through that same surface. The CLI uses one path, the ImGui desktop client another, the Angular browser client a third ; None of them gets to bypass the loop or the audit trail.

What this buys you

A ReAct loop wired this way is testable without a server (the brain port takes a fake that returns scripted responses, the tool bridge takes a fake that returns canned tool results), observable end to end (every iteration, tool call, and proposal is in the conversation store), and replaceable in pieces (a new brain is a new IAgentBrain implementation, a new transport is a new MCP tool on top of the same loop). The agent is a thin orchestration layer over the MCP server, not a competing runtime.

For the broader context, see Self-Hosted MCP Infrastructure for Enterprise and Mission-as-Template : Declarative AI Agents in Production.

Talk to us about MCP infrastructure for your organisation. Get in touch.