Where's my sofa?
A customer care agent on MuleSoft Agent Fabric, with its own MCP server
Where's my sofa? is a care desk agent for a furniture shop that makes most pieces to order. Customers ask it where their order is, move a booked delivery or report damage, over A2A. It runs on MuleSoft Agent Fabric, and everything it touches goes through Mule: an MCP server in front of a System API.
My day job is MuleSoft integration, and there's no agent in it yet. MuleSoft is moving fast on agents (Agent Fabric, Agent Script, an MCP connector), so I built the smallest real use case I could think of, end to end, the API-led way. The shop, its products and its customers are invented.
The model classifies, the graph decides
Furniture made to order is a good test for an agent. "Where is my sofa?" means joining the order, the workshop's production stage and the delivery booking. And some calls should never be left to a model: whose order it is, which slots are free, and that a delivery under 48 hours away can't move.
So the broker is an Agent Script graph, and the model only gets the parts that need language:
understandRequestmakes one LLM call with no tools: intent, order number, email, preferred slot.- A router, with no LLM, picks the path and checks that the order number and email are there.
orderStatusgets only the two tools its path needs, with the email bound by the graph, so the model can't point a tool at someone else's order.- To move a delivery, an executor reads the free slots, one LLM call picks the slot that matches, and another executor moves it. The model picks a slot but never calls the tool, and when it has to ask which slot suits the customer, the question is a fixed reply.
- Complaints, and deliveries too close to move, go to executors that open a care case with fixed arguments. No model decides whether a case gets opened.
The rules live in the System API. A wrong email gets the same 404 as a missing order, so the API never confirms that an order number exists.
What's in it
- sofa-orders-sapi: a Mule System API over a mock ERP, with its rules in DataWeave.
- sofa-care-mcp: a Mule app on the MCP Connector 1.7, five tools over the System API, and an API key checked when an MCP session opens.
- The Care Desk broker, deployed by Agent Fabric behind Omni Gateway: client ID enforcement, 60 requests an hour per client to cap the LLM bill, and user context so each caller gets their own conversation memory.
gpt-5-minithrough OpenRouter, with low reasoning effort.
What broke
The agent sometimes answered customers with its own reasoning. Agent Fabric runs its LLM nodes with human-in-the-loop: when the model decides it needs more input, the task stops to wait for the customer, and the model's reasoning becomes the reply. The fix was to take that decision away from the model: it fills in fields, the graph decides, and every question to the customer is a fixed reply. And MCP Connector 1.6 held an SSE stream open for every response, so the broker's tool discovery timed out behind the gateway. Connector 1.7 answers with one JSON body.
The eval checks the system, not the reply
A reply can sound right and do the wrong thing. scripts/eval.py sends eight customer messages and snapshots every delivery slot and care case in the System API before and after each one. A move passes only if that one order moved to that one slot and nothing else changed. A prompt injection on someone else's order passes only if nothing moved at all.
The whole story, with the eval runs: A customer care agent on MuleSoft Agent Fabric, where the model only talks.
Run it
The repo has both Mule apps, the agent network and the deploy scripts. With an Anypoint org that has Agent Fabric, a connected app and an OpenRouter key:
scripts/deploy-apps.sh # both Mule apps to CloudHub 2.0
SETUP_GATEWAY=1 SOFA_CARE_MCP_URL=https://... scripts/deploy-network.sh # gateway, publish, broker
scripts/ask.py # talk to it over A2A
demo/server.py serves the page from the video: the chat next to the System API's own data.
Plain text: wheres-my-sofa.md · All projects