Hasta ahora cada tool era stateless: cada invocación independiente, sin memoria de la anterior. Funciona para lookups, pero falla en operaciones de varios pasos.
Un carrito de compras es el caso clásico:
Si cada handler arranca con el carrito vacío, no sirve. El estado tiene que vivir afuera de las funciones.
A la derecha tenés tres tools que operan sobre el mismo carrito:
cart_add. agrega un item con quantity.cart_remove. quita un item.cart_checkout. cierra el carrito y emite la orden.Tu trabajo: implementar los handlers de modo que el estado persista entre llamadas y esté aislado por usuario.
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 };
}Tres cosas que importan:
Map carts vive afuera de las funciones. Si lo declarás adentro, se reinicia cada llamada.userId. Cada usuario tiene su propio carrito. Sin esto, todos comparten el mismo y se rompe en el segundo concurrent.cart_add acumula. no sobrescribe. Si ya hay 2 de X y agregás 1 más, queda 3, no 1.5 criterios llm-judge sobre 5 escenarios:
cart_add acumula correctamente.cart_remove quita sin romper el resto.cart_checkout crea orden y deja carrito vacío.Este patrón es la base de TODO MCP con estado. Vale la pena implementarlo bien acá. vas a usarlo en sistemas reales mucho más complejos.