The companion to the IEEE talk “Connecting Enterprise AI Using MCP.”
One request — “Book me a cheap flight somewhere sunny this weekend and use my miles” — run four ways: the happy path, two ways it silently goes wrong, and the fix. Everything is synthetic and open-source. Tool-neutral: any MCP-capable client works.
3 off-the-shelf MCP servers no restarts between beats synthetic data MIT
View the repo on GitHub →An assistant composes three separate back-end systems over MCP to book a trip:
| Server | Why it's needed | What it contains |
|---|---|---|
reservations | Finds the flights and the weather, and records the booking | 3 tables — flights, airports (weather), bookings |
loyalty | Confirms the traveler's miles so they can be applied to the fare | 1 table — members (miles balance, tier) |
policies | The refund & baggage rules the assistant must quote | 2 docs — refund-policy.md, baggage-policy.md |
Each server reads its data fresh on every call. That is the whole trick: change a file between two questions and the next answer changes — no restart, no redeploy.
All synthetic. Two SQLite databases and a folder of markdown — this is everything the assistant can see. Follow these rows through the simulator below.
reservations · SQLite → flights| flight_id | airline | route | date | price_usd | seats_left |
|---|---|---|---|---|---|
| AA88 | American | SJC → SAN (San Diego) | 2026-08-22 | 149 | 9 |
| UA210 | United | SJC → SEA (Seattle) | 2026-08-22 | 118 | 4 |
| DL455 | Delta | SJC → PHX (Phoenix) | 2026-08-23 | 129 | 6 |
| WN360 | Southwest | SJC → LAS (Las Vegas) | 2026-08-22 | 176 | 12 |
| AS512 | Alaska | SJC → PSP (Palm Springs) | 2026-08-23 | 169 | 3 |
Cheapest overall is Seattle ($118) — but it's raining. AA88 → San Diego ($149) is the cheapest that's actually sunny. DL455 → Phoenix ($129) is the trap in Beat B.
reservations · SQLite → airports (weather)| code | city | weather | weather_as_of |
|---|---|---|---|
| SJC | San Jose | Cloudy | 2026-08-22 |
| SAN | San Diego | Sunny | 2026-08-22 |
| SEA | Seattle | Rain | 2026-08-22 |
| PHX | Phoenix | Storm | 2026-08-22 |
| LAS | Las Vegas | Sunny | 2026-08-22 |
| PSP | Palm Springs | Sunny | 2026-08-22 |
This is the current snapshot: Phoenix is a Storm today. The
stale snapshot (Beat B) is the same table frozen 5 days ago — every weather_as_of reads
2026-08-17 and Phoenix still says Sunny. Same data, older date → wrong answer.
reservations · SQLite → bookings| booking_id | flight_id | passenger | miles_applied | status |
|---|---|---|---|---|
| BKG-5001 | WN360 | Sam Delgado | 0 | confirmed |
| BKG-5002 | AS512 | Priya Anand | 8000 | confirmed |
Two seed bookings. When you click Book it in the simulator, the assistant
write_querys a new BKG-60xx row here — a real write over MCP.
loyalty · SQLite → members| name | miles_balance | tier |
|---|---|---|
| Jordan Rivera (the traveler) | 45,000 | Gold |
| Sam Delgado | 2,200 | Member |
| Priya Anand | 132,000 | Platinum |
| Marcus Webb | 6,100 | Silver |
Jordan has plenty of miles — so the happy path applies 12,000. In Beat C this whole table is unreachable (the DB file is swapped for junk), and the query errors.
policies · filesystem → markdown| file | current | stale (optional variant) |
|---|---|---|
refund-policy.md | v2026.3 — non-refundable basic fares | v2025.4 — “fully refundable any time” (false) |
baggage-policy.md | v2026.3 | v2025.4 |
The filesystem server serves whatever is in policy/active/. A toggle swaps
current ↔ stale — the RAG version of the stale-source failure.
Each server re-reads its backend on every call, so the toggle scripts (use-stale.sh, break-miles.sh) change behavior with no restart — the point of the demo, and the source of both failures.
Want to run this for real on your machine? → open the Run it yourself tab above.
curl -LsSf https://astral.sh/uv/install.sh | shnpxgit clone https://github.com/agnitrip/mcp-airline-demo.git
cd mcp-airline-demo
./scripts/setup.sh # seed synthetic DBs + clean baseline
./scripts/status.sh # -> reservations CURRENT, loyalty healthy, policies CURRENT
Add the three servers to your client's config. Replace
/ABSOLUTE/PATH/TO/mcp-airline-demo with the real path — SQLite needs absolute paths.
{
"mcpServers": {
"reservations": {
"command": "uvx",
"args": ["--with", "mcp[cli]<2", "mcp-server-sqlite",
"--db-path", "/ABSOLUTE/PATH/TO/mcp-airline-demo/data/reservations.db"]
},
"loyalty": {
"command": "uvx",
"args": ["--with", "mcp[cli]<2", "mcp-server-sqlite",
"--db-path", "/ABSOLUTE/PATH/TO/mcp-airline-demo/data/loyalty.db"]
},
"policies": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem",
"/ABSOLUTE/PATH/TO/mcp-airline-demo/policy/active"]
}
}
}
--with "mcp[cli]<2" pin is required. Plain
uvx mcp-server-sqlite pulls the mcp SDK 2.x, and the archived reference
server crashes on it ('Server' object has no attribute 'list_resources'). The pin holds it
at 1.x. See FIX-sqlite-server-pin.md.
Claude Desktop (macOS): put this in
~/Library/Application Support/Claude/claude_desktop_config.json.
Claude Code: save as .mcp.json in the project root.
Then fully quit and reopen the host and confirm all three servers connect.
If the host can't find uvx, use its absolute path (which uvx).
Set the assistant's instructions to one of these (paste as Project instructions in Claude Desktop, or as the first message in Claude Code):
assistant-base.md
— naive build (Beats A, B, C). No guardrail, so a stale source or a broken tool fools it.assistant-guardrailed.md
— same assistant + a guardrail: quote your source and fail closed on missing,
errored, or stale data (Beat D).Start a new chat for each beat and type the same request every time:
Book me a cheap flight somewhere sunny this weekend and use my miles. I'm Jordan Rivera.
| Beat | Toggle (terminal) | Prompt | Outcome |
|---|---|---|---|
| A · happy path | ./scripts/setup.sh |
base | Books San Diego $149, sunny, 12k miles — correct |
| B · stale source | ./scripts/use-stale.sh |
base | Books Phoenix $129 on 5-day-old weather — it's really a storm |
| C · tool error | ./scripts/use-current.sh./scripts/break-miles.sh |
base | Loyalty server errors → books anyway (fail open) |
| D · guardrail | swap to the guardrailed prompt | guardrailed | Catches both: refuses on the error, flags the stale weather |
Reset any time with ./scripts/setup.sh.