So far every tool has been stateless: each invocation independent, no memory of the previous one. That works for lookups, but breaks for multi-step operations.
A shopping cart is the classic case:
If every handler starts with an empty cart, it doesn't work. State has to live outside the functions.
On the right you have three tools that operate on the same cart:
cart_add. adds an item with quantity.cart_remove. removes an item.cart_checkout. closes the cart and creates the order.Your job: implement the handlers so that state persists between calls and is isolated per user.
const carts = new Map(); // userId -> { itemId -> quantity }
async function cart_add({ item_id, quantity }) {
const userId = getUserId();
if (!carts.has(userId)) carts.set(userId, {});
const cart = carts.get(userId);
cart[item_id] = (cart[item_id] || 0) + quantity;
return { ok: true };
}Three things that matter:
Map carts lives outside the functions. If you declare it inside, it resets every call.userId. Each user has their own cart. Without this, everyone shares the same cart and it breaks on the second concurrent user.cart_add accumulates. doesn't overwrite. If there's already 2 of X and you add 1 more, you get 3, not 1.5 llm-judge criteria over 5 scenarios:
cart_add accumulates correctly.cart_remove removes without breaking the rest.cart_checkout creates the order and leaves the cart empty.This pattern is the foundation of ANY stateful MCP. It's worth implementing it well here. you'll use it in much more complex real systems.