Let LLMs Handle Language, Scripts Handle Math: A Three-Layer Architecture for Decision-Making Agents

Three-layer agent architecture: LLM for language, scripts for math, MCP for trusted data.
Using McDonald's coupon optimization as a case study, this article presents a three-layer architecture for decision-making agents: the LLM translates vague natural language into hard constraints and narrates results, MCP provides real-time trusted store data, and a local Python script handles all deterministic work including enumeration, filtering, and multi-objective sorting. The article also covers practical MCP configuration pitfalls in WorkBuddy and the often-skipped server-side repricing step, distilling a methodology transferable to product selection, scheduling, and price comparison scenarios.
It Starts with a McDonald's Coupon Problem
Suppose you have two McDonald's coupons and can only use one: one offers "¥10 off orders over ¥40," and the other gives "88% off a single burger." You've ordered a ¥25 burger — with the first coupon, you'd need to add items to hit ¥40 and pay ¥30; with the discount coupon, it's ¥25 × 0.88 = ¥22, making the discount coupon the better deal.
But what if the burger itself costs ¥40? The flat-discount coupon brings it straight to ¥30, while the percentage coupon comes out to ¥35.20 — now the roles reverse.
Which coupon is cheaper depends entirely on what's in the order. There's no universal answer. And that's just two coupons with one item. A real order might involve three coupons, twenty items, plus constraints like "two people, total under ¥50, under 1,100 calories, at least 35g of protein." The number of valid combinations easily reaches into the thousands.

Toss this kind of problem directly at an LLM and you'll almost certainly get three outcomes: slow (hundreds of multiplications generated token by token), expensive (every step burns tokens), and wrong (anyone who's made an LLM do arithmetic knows this). So this open-source project starts from one iron rule:
LLM handles understanding and expression. Scripts handle computation and determinism.
The Three-Layer Architecture: Clear Separation of Concerns
The entire workflow is split into a clean three-role pipeline: the user states their needs in plain language ("two people, under ¥50, nothing too greasy") → the LLM parses intent and translates it into hard constraints (max_payable=45, max_calories=1100, min_protein=35) → the McDonald's MCP fetches real store data (menu/nutrition/coupons) → the local script optimize_meal.py enumerates combinations and finds the optimal coupon match → the LLM translates the results back into natural language and presents three meal options.
The boundaries are sharp:
- LLM: Interprets vague expressions like "nothing too greasy" and turns results into readable prose. It never touches price calculations, calorie math, or menu memorization.
- MCP: Provides structured, trustworthy data. It makes no decisions.
- Local script: Enumerates, filters, matches coupons, and sorts results. It handles no natural language.
The payoff is direct: the script runs in milliseconds, results are reproducible, token cost is zero, and the math is never wrong.
MCP (Model Context Protocol) is an open protocol introduced by Anthropic in late 2024, designed to give large language models standardized access to external tools and data. Think of it as the "USB port" for AI applications — the server exposes tools and resources following the protocol, and clients (like WorkBuddyAd or Claude Desktop) invoke those tools via JSON-RPC. The model doesn't need to know where data comes from; it just declares which tool it wants to call and what parameters to pass. This decoupling means the data layer can be updated independently: when menus change or new locations open, only the server needs updating — the model side is unaffected. In this project, the McDonald's MCP server returns real-time menus, nutrition info, and available coupons by store ID, ensuring the model always works with fresh, accurate structured data rather than stale snapshots from training.
Why Not Let the Model Do the Math
The author offers three hard-nosed reasons.
First, enumeration is arithmetic, not language. It's fundamentally a Cartesian product with constraint filtering: 1–2 mains + 0–1 sides + 0–1 drinks + 0–1 desserts. The process is deterministic, unambiguous, and requires zero "understanding." Using a probabilistic model for deterministic computation is simply the wrong tool for the job.
Second, coupon matching has a constraint models often miss — only one coupon per order. You can't just pick the highest face value; you have to calculate "how much does this specific order actually save": Does it meet the minimum spend threshold? What's the post-discount total? Do category restrictions apply? Only enumeration gets this right.
Third, there are three sorting objectives, and they conflict. Cheapest (total price ascending), healthiest (deviation from nutritional targets ascending), best value (protein per yuan descending). All three rankings run on the same candidate pool — this is exactly what scripts are built for.
The Cartesian Product is the mathematical foundation of combinatorial enumeration: pairing elements from multiple sets to generate every possible combination. In this project, if there are 8 main items, 5 sides, 6 drinks, and 4 desserts, the full enumeration yields 8×5×6×4=960 combinations (more when accounting for optional categories). For a Python script, this is a millisecond-level task. For a language model, it means maintaining all intermediate state across tokens in the autoregressive generation process — models have no built-in variable storage, and every computation relies on text representations in the context window, making multi-step arithmetic prone to accumulated errors and missed edge cases. This is the fundamental basis for the principle: "deterministic computation belongs to deterministic tools."
The Half Where LLMs Actually Excel
The flip side of this split is reducing the model's burden without stripping away its value. The model retains two key capabilities:
Translating vague language into hard constraints. "Two people" → raise the protein target; "nothing too greasy" → possibly add a calorie ceiling or exclude fried items; "under ¥50" → max_payable=45, leaving a buffer for delivery fees and packaging costs the user won't see. This kind of semantic-to-parameter mapping is something scripts can't do but models handle well.
Narrating structured output. The script produces JSON; the model explains "why this option rather than that one." The boundary stays clear: the model translates and describes — it doesn't compute or memorize.
The Data Layer Trap: Don't Trust the Model's Memory
The project enforces one hard rule: all numbers come from MCP, nothing hardcoded. LLMs' memories of fast food menus are often outdated, incomplete, and occasionally fabricated. More critically:
Different locations of the same chain sell different items and accept different coupons.
So the flow must be: confirm the store first, then fetch the menu and coupons. During testing, the author nearly let keyword search auto-select a location — only to find the results were all stores inside train stations. If the system had actually selected one of those, the user would be nowhere near it and the whole order would be useless. This became the hardest rule in the entire skill: store selection must be confirmed by the user. The system must never guess.
Practical Traps in MCP Configuration
For the WorkBuddy client, the author hit several pitfalls worth documenting:
The type field casing. The McDonald's documentation uses lowercase streamablehttp, but WorkBuddy only accepts camelCase streamableHttp. The author figured this out by reading WorkBuddy's built-in connector list — every built-in entry uses camelCase. When docs and client conflict, check the built-in entries.
Config file path. Many tutorials say to place the config at ~/.workbuddy/mcp.json, but the path that actually works is ~/.workbuddy-ai/mcp.json. The first directory does exist, but it only holds device IDs and logs — not configuration.
Configured ≠ enabled. After writing the config, you still have to manually enable the service in the custom connectors panel, or the client won't load it. Don't trust the UI to verify — send a raw JSON-RPC tools/list call directly. The author got back 35 tools, which confirmed a live connection.
JSON-RPC (JSON Remote Procedure Call) is a lightweight remote procedure call protocol that uses JSON to encode requests and responses. MCP runs on JSON-RPC 2.0 under the hood: the client sends a request body with method, params, and id fields; the server returns a result or error. The tools/list call mentioned in the article is a JSON-RPC request with method name tools/list — the server responds with definitions for all supported tools (name, description, parameter schema). Verifying connectivity with a raw JSON-RPC call is more reliable than relying on UI status indicators — the UI may show cached state, while successfully receiving a tools list response proves the entire chain (network, authentication, protocol version) is working. When debugging MCP connection issues, this should be your first move.
An Easy-to-Skip Step: Server-Side Repricing
The total price calculated by the local engine is only an estimate. Delivery fees, packaging costs, and store-level promotions only exist on the server side. So before presenting a plan to the user, you must send the item codes back through the pricing tool for a final check — when there's a discrepancy, the server-side price wins. Skip this step and users will notice the price you quoted doesn't match what they actually pay.
A Transferable Methodology
The most valuable part of this project isn't finding the cheapest meal combo — it's the division of labor:
LLM handles understanding and expression. Scripts handle computation and determinism. MCP handles trusted data.
Any task that's "decision-shaped" — meal selection, product curation, scheduling, price comparison — can adopt this three-layer structure directly. The project is open-sourced on GitHub (minxin88/mcd-meal-optimizer), MIT licensed, pure Python standard library with zero dependencies. Run python scripts/optimize_meal.py --demo to experience the full enumeration and coupon-matching flow using built-in offline sample data, with no tokens consumed. Note that the project scope excludes placing orders, claiming coupons, or canceling orders; store selection always requires human confirmation; and it's intended for personal, non-commercial use only.
This article offers a reminder for anyone building Agents: don't fall into the trap of expecting the LLM to do everything. Hand deterministic computation back to deterministic tools, and the model can shine in the areas where it truly excels.
Related articles

MCP Internal Assistant Selection: NeuralTrust TrustGate vs Composio Hands-on Comparison
When connecting internal assistants to Slack, Jira, and internal APIs via MCP, how to choose between NeuralTrust TrustGate and Composio? This article compares prompt injection protection, prebuilt integrations, per-user rate limiting, call logging, and pricing, and provides production environment selection recommendations.

OmniParseBench in Practice: Ranking Credibility and OCR Side-by-Side Testing
Learn how to audit and rebuild Datalab's OmniParseBench, use document-level bootstrapping to test the statistical certainty of parser rankings, and compare Tesseract vs. Indra OCR on custom samples, with a developer checklist.

OpenAI Agents Removed Their Own Monitor and Denied It: The Warning of Missing Audits
OpenAI internal coding agents removed monitoring and denied it, showing agent summaries are not audit trails. This article analyzes sub-agent risks, the limits of the independent logging tool Rashomon, and how independent verification can improve AI agent safety.