
Is it possible for an AI agent to use Mr D to buy me a burger in South Africa?
I recently saw a post about an MCP server that can order McDonald's in China.
Mr D gives an agent no supported way to do that. So I reverse-engineered its private API and built my own Mr D MCP server, which allowed me to order a burger from my agent over WhatsApp.


Messages highlighted in red, next to the
OpenClaw logo, are the agent. Everything else is me.
The full order happened over WhatsApp:
- Ordering a burger
- Choosing from restaurants and their menus
- Paying
- Tracking the order
This article will walk you through that order and explain the tools the agent used along the way.
Mr D's payment flow doesn't always require 3D Secure approval, so the payment tool here is locked by default. To try it yourself, point your own agent at the repo and log in to your own Mr D account.
Why this wasn't easy
Getting an agent to order a burger from Mr D took real work, and none of it was Mr D's. There is no API documentation, no MCP server, and no supported way for an agent to search a menu, build a cart, or pay.
To make any of this possible, I had to:
- Capture and reverse-engineer the private API that Mr D's own app and website already call internally.
- Build a full MCP server on top of that API myself, tool by tool, matching Mr D's own request and response shapes with no documentation to check against.
- Design and build my own safety gate around payment, since Mr D's platform doesn't provide one and a saved-card payment can settle with no bank approval step at all.
None of that is something an outside developer should have to do just to let an agent order food. A restaurant group like RocoMamas, or Mr D itself, could remove that barrier entirely by publishing an official ordering API or MCP server, documenting how payment should be confirmed safely, and giving agents a supported way in instead of leaving reverse-engineering as the only option.

Wondering what agent-ready ordering would look like for your own business? That's exactly the kind of agent experience work Ritza does, for hospitality, delivery, and any other product agents are starting to interact with directly.
Ordering a burger from my agent
I open the conversation with "I'm hungry." The agent responds that it has the Mr D tools connected, then asks for the two inputs it needs before it can do anything, what to order and where to deliver it.
The agent also warns that payments are currently blocked, a built-in safety mechanism I use while testing the agent so no accidental payments can go through.
Searching for restaurants
I ask "What are my options?" The agent calls search_restaurants and returns a list of
nearby restaurants grouped by category, with star ratings and delivery time.
search_restaurants calls two Mr D endpoints in parallel, then merges the results by restaurant ID:
dynamic: restaurant ID, menu ID, open status, distance, and ratingssemi_static: name, full address, and images
Together they make up a usable record. Both endpoints require a full street-level address block alongside latitude and longitude.
// search_restaurants merges two Mr D endpoints by restaurant id
const [dynamicRes, semiStaticRes] = await Promise.all([
mrdRequest(auth, `/landing-items/v3/restaurants/dynamic`, { query }),
mrdRequest(auth, `/landing-items/v3/restaurants/semi_static`, { query }),
]);
// dynamic: id, menu_id, open/online, distance, ratings
// semi_static: name, full address, images
Browsing the menu
I pick RocoMamas and ask what's on the menu. The agent calls get_menu and returns the full
menu with prices, burgers, wings, ribs, combos, sides, and drinks.
get_menu is a single GET request against Mr D's flattened menu endpoint, using the menu_id
returned by search_restaurants in the previous step. The flattened=true query parameter
returns items as a flat list rather than nested by section, which is what the agent needs to match
a spoken order against a specific item.
// get_menu returns the flattened menu for a given menu_id
await mrdRequest(auth, `/v2/menus/${menuId}`, { query: { flattened: "true" } });
Choosing an item
I order "1 Old Skool burger please." The agent quotes the price and asks two clarifying questions, protein choice and any extras or exclusions, before adding anything to a cart.
To add an item to a cart, Mr D needs a specific product ID and a set of option IDs for things like protein choice and extras. None of the tools ask for these directly, and none of them turn a plain-language order like "1 Old Skool burger" into the right IDs either.
The agent determined on its own that it needed to work this out itself:
- It already has the full menu, IDs included, from the earlier
get_menucall. - It matches my words against that menu to find the right item.
- Wherever the menu shows more than one option, like protein choice, it asks me instead of guessing.
A production tool would state its requirements more explicitly instead of leaving the agent to figure this out on its own.
Building the cart and checking out
Before it builds the cart, the agent checks the delivery address against the one I gave it earlier. The geocoded result lands in a different suburb and postal code, so it asks me to confirm the address is still correct before it commits anything.
Once I confirm, it adds the burger to a cart and converts that cart into an order. The order comes back unpaid.
The agent already knows a R20 service fee applies on top of the earlier quote. It tells me the real total upfront, instead of leaving me to find out at payment.
Behind the scenes, add_to_cart creates the cart and the item in a single call, then checkout
converts that cart into an order. Nothing in this step moves money, the order comes back unpaid.
// add_to_cart creates the cart with the item already in the body
const cart = await mrdRequest(auth, "/restaurant/v3/cart", {
ca: true,
method: "POST",
body: {
restaurant: { id: restaurantId },
menu_id: menuId,
items: [{ section_id, item_id, variant_id, quantity, options, extras, addons }],
customer: { address },
},
});
const cartId = cart.uuid; // not cart.id
// checkout converts the cart into an unpaid order
await mrdRequest(auth, `/restaurant/v3/cart/${cartId}/checkout`, {
ca: true,
method: "POST",
body: {},
});
Paying for the order
I say "Yes pay for me." The first payment attempt fails a 3D Secure check because the delivery address differs from the address saved against the card. The agent reports the failure and offers two ways to complete the challenge, open the order in the Mr D app, or use a direct PayU handoff URL.


Following the PayU URL opens a bank authentication screen in a browser.
Once the challenge clears, the agent reports the order as paid, with the order number, amount, and delivery address confirmed.
Payment runs through four tools:
set_payment_methodregisters which payment methods the order will accept.list_saved_cardslooks up the cards already saved on the account.prepare_paymentbuilds the payment request and returns a confirm token, without sending anything yet.submit_paymentis the only tool that can actually charge the card.
submit_payment is locked down deliberately. It refuses to run unless I have explicitly armed
payments on the server, and the confirm token it receives matches the one prepare_payment just
generated for this exact order. I built this gate myself, on top of Mr D's own systems, because I'd
already seen a saved card go through with no bank approval step at all in an earlier test. I did
not want an agent able to charge a real card without a deliberate, separate step from me first.
// prepare_payment is read-only, it builds the body and a confirm
// token, and sends nothing
const body = buildPaymentBody(intent);
const confirmToken = confirmTokenFor(intent.orderId, body);
// submit_payment refuses unless both locks pass
if (!paymentArmed()) throw new PaymentGateError("Payment is locked.");
if (confirmToken !== expected) throw new PaymentGateError("confirmToken mismatch.");
// only then does it send the real charge
await mrdRequest(auth, `/v3/users/${auth.userId}/orders/${intent.orderId}`, {
method: "PUT",
body,
});
Tracking the delivery
The agent offers to monitor the order and notify me only at meaningful points. I say yes. The agent polls the order and reports when it goes out for delivery, then I confirm delivery with a photo of the order at the address.
The agent set its own polling interval of 7 minutes and its own notification rule, pickup, nearby, delivered, or a problem. Once the order moves to out for delivery, it sends an update with the ETA.
When the order arrives, I close the loop with a photo of the order at the door, and the agent sends a short confirmation message.
Mr D has no separate tracking API. Both tools are just reading different parts of the same order:
get_order_statusreads the payment side, whether it's paid, how much, and what's still owing.track_deliveryreads the courier side, whether a driver has been assigned and the estimated delivery time.
The polling schedule and the decision about when to notify me were not platform features either. The agent chose both of those on its own.
// get_order_status and track_delivery both read the same order object
async function fetchOrder(auth, orderId) {
return mrdRequest(auth, `/v3/users/${auth.userId}/orders/${orderId}`);
}
// get_order_status extracts payment.status, paid, outstanding, total
// track_delivery extracts courier status, driver assigned, ETA
What this shows about Mr D's agent experience
Can an agent order you a burger in South Africa? Technically, yes, but only if you build the agent tooling yourself. Mr D gives an agent no supported path to place an order at all, so getting there meant reverse-engineering its private API and building an MCP server on top of it.
The use of agents to discover, use, and pay for products is only going to grow. Companies need to think about agent experience the same way they already think about user experience:
- How does an agent find your product?
- How does an agent understand what it offers?
- How does an agent act on a user's behalf, safely and reliably, without a browser or a human in the loop?
Until that changes, "can an agent buy me a burger?" will keep being answered by developers doing the work these companies haven't done themselves.

We want to work with hospitality and delivery businesses looking to win this new market. At Ritza, our Engineering Writers work at agent speed with human-expert verification (no slop) to help South African hospitality and delivery brands build the agent experience their customers will start expecting.
