MCP use case: a manager in the loop
Some decisions belong to someone who is not in the chat. Goodwill store credit in the shop waits for a manager: the call is parked with a handle, the manager approves or rejects it from the command line, and when Claude asks again it gets the decision, never a second request. In the field test, all 8 runs of the two manager cases were correct, approvals and rejections alike.
Cargo feature: ai · Example: examples/agents/mcp-shop · Part of: Cameleer MCP
What people ask, and what happens
| request | what happened |
|---|---|
| Order 1002 is outside the refund window. Give Marco Rossi 20 in store credit as a goodwill gesture instead. | Claude asked for the credit and said it was waiting for a manager. The manager approved. Asked "Did the credit go through?", Claude called again and reported SC-1, approved by manager.jo |
| Give Hana Sato 50 store credit for the trouble with her boots. | the credit waited for a manager. The manager rejected it. Asked "Any news on Hana's credit?", Claude reported the rejection and the manager's reason, and did not try another way |
A recorded conversation
User: Order 1002 is outside the refund window. Give Marco Rossi 20 in store credit as a goodwill gesture instead.
Claude checked the order and the customer, then called issue_store_credit. The call did not
run; the result carried a handle, and Claude said:
I've asked for the £20 store credit for Marco Rossi, but it hasn't gone through yet. Every store credit needs a manager to approve it, and this one is waiting for that sign-off (approval handle
approval-169-15625474038170fb).
A manager, outside the chat:
cameleer tool approvalscameleer tool approve approval-169-15625474038170fb --by manager.joUser: The manager has just approved it. Did the credit go through?
Claude called issue_store_credit again with the same arguments and got the outcome of the
approved call:
Yes, the £20 store credit went through. It's been added to Marco Rossi's account (customer 2). Credit reference: SC-1. Approved by: manager.jo.
In the rejected case the second call returned:
The call to issue_store_credit was rejected by manager.jo: Goodwill credit is not justified for this order. It did not run, and nothing was changed.
How it is built
approval: always with an approvalEndpoint parks every call and tells the endpoint about it.
Here the endpoint is a route that writes the request to a manager's queue table; in practice it
could post to Slack or open a ticket. The approved call runs with CamelAiToolApprovedBy set,
so the route records who approved it:
- route:
id: issue-store-credit
from:
uri: ai-tool:issue_store_credit
parameters:
title: Give store credit
description: >-
Give a customer store credit (a goodwill gesture, or instead of a refund outside the
policy). Every credit needs a manager's approval: the call waits until a manager approves
or rejects it in the back office, which can take a while. If it is still waiting, call
again later with exactly the same arguments to get the outcome.
tags: acme
parameter.customerId: integer
parameter.customerId.description: The customer id from search_customers
parameter.customerId.required: true
parameter.amount: number
parameter.amount.description: Amount of credit, at most 200
parameter.amount.required: true
parameter.reason: string
parameter.reason.description: Why the credit is given
parameter.reason.required: true
approval: always
approvalEndpoint: direct:manager-queue
approvalTimeout: 1h
steps:
- choice:
when:
- simple: "${header.amount} <= 0 || ${header.amount} > 200"
steps:
- setHeader:
name: CamelAiToolIsError
constant: true
- setBody:
constant: "Store credit must be more than 0 and at most 200."
otherwise:
steps:
- to: "sql:insert into store_credits (customer_id, amount, reason, approved_by, created) values (:#customerId, :#amount, :#reason, :#CamelAiToolApprovedBy, '{{acme.today}}') returning id?dataSource=jdbc:rustsql:mem:acme&outputType=SelectOne"
- setBody:
simple: '{"storeCredit": "SC-${body}", "customerId": ${header.customerId}, "amount": ${header.amount}, "approvedBy": "${header.CamelAiToolApprovedBy}", "status": "credited"}'- route:
id: manager-queue
from:
uri: direct:manager-queue
steps:
- to: "sql:insert into manager_queue values (:#CamelAiToolApprovalHandle, :#CamelAiToolName, :#customerId, :#amount, :#reason, '{{acme.today}}')?dataSource=jdbc:rustsql:mem:acme"- Asking again is safe. A call with the same arguments in the same conversation returns "still waiting" with the same handle, or the decision once there is one. The manager is asked once.
- The description tells the model what to expect. "The call waits until a manager approves or rejects it [...] call again later with exactly the same arguments" is what made Claude check back instead of giving up or retrying with other arguments.
- The route still checks its rules after approval. A credit over 200 is refused even if a manager approves it.
- Decisions are audited with the approver's name, in the same hash-chained log as every call.
Operators can also decide from a program (AiRegistry::approve and deny), from another route
(ai-tool:x?operation=approve), and inside a container with docker exec; see
MCP server: approvals.
Try it
cd examples/agents/mcp-shop
cameleer run shop.camel.yaml --properties application.propertiesAsk a connected client for goodwill credit, then run cameleer tool approvals and
cameleer tool approve <handle> --by <name> in another terminal (or
cameleer tool deny <handle> --reason <text>). shop_use_cases in
crates/cameleer-main/tests/mcp_shop.rs approves one credit, rejects another and checks what
the next call returns.
Related
- Actions guarded by approvals: the user approves in the client instead
- The tool asks the user
Found a problem?
If something on this page doesn't match what Cameleer does, please
open an issue, or fork the repository, fix
it, add a test, run cargo test --workspace and open a pull request.