Six Patterns for Connecting LLM Agents to Stateful Tools
⚡ The Problem Statement
LLMs are stateless. They process input, generate output, and forget everything. But real-world applications—games, document indexing, multi-agent workflows—are fundamentally stateful. After building 6+ MCP servers totaling 200+ tools, I've identified six architectural patterns that bridge this gap without fighting the LLM's nature.
Pattern 1: Externalize All State to Persistent Storage
The agent isn't smart. The database is smart. The agent is just hands.
Every MCP server I've built follows this rule: state lives in JSON files, SQLite, or external databases—never in memory that dies with the process. When a new conversation starts, the agent queries the state store and reconstructs context from scratch.
- Active Context: Current focus + summary
- Filing Cabinet: Indexed file summaries
- Spatial Map: Project folder graph
- Bug Tracker: Issues with severity
The key insight: design your storage schema so a brand-new agent instance
can call get_active_context and immediately understand what's
happening. If context reconstruction requires reading 50 files, you've failed.
Pattern 2: Rich Query Tools for Context Reconstruction
Instead of passing massive context windows, expose tools that let the agent selectively query what it needs. The LLM asks questions; the tools answer.
🎮 ChatRPG Game Engine
get_session_context returns location, party, notes, combat state
in one call. render_battlefield generates ASCII maps. The agent
reconstructs a D&D session by asking "what's happening?" not reading history.
📚 Index Foundry
project_query with semantic, keyword, or hybrid modes.
debug_query shows similarity scores and retrieval traces.
classify_query determines if RAG is even needed.
// OODA MCP: 62 tools organized by query type
OBSERVE
├── read_file, list_directory, search_files
├── screenshot, get_active_window, list_windows
└── get_system_info, list_processes
ORIENT
├── search_in_file, batch_search_in_files
└── file_info, batch_file_info
DECIDE
└── (LLM reasoning over observed data)
ACT
├── write_file, str_replace, apply_diff
├── keyboard_*, mouse_*, clipboard_*
└── launch_application, exec_cli
Pattern 3: Composite Operations Reduce Coordination
Every tool call is a round-trip. Network latency, token generation, parsing—it adds up. When an operation logically requires multiple steps, batch them into a single tool.
BEFORE: Many Tools
- ✗ create_encounter
- ✗ get_encounter
- ✗ end_encounter
- ✗ commit_encounter
4 tools × overhead = slow
AFTER: One Composite
- ✓ manage_encounter
- operation: "create" | "get" | "end" | "commit"
1 tool, enum-driven = fast
The OODA MCP takes this further with batch variants: batch_read_files,
batch_str_replace, batch_exec_cli. Index Foundry's
project_build runs an entire pipeline—fetch, extract, chunk, embed,
upsert—in a single call.
// ChatRPG: manage_condition composite
{
"operation": "add" | "remove" | "query" | "tick",
"batch": [
{ targetId, condition, duration },
{ targetId, condition, duration }
]
}
Pattern 4: Fuzzy Validation for LLM Tolerance
LLMs make typos. They use synonyms. They forget exact enum values. Instead of failing hard, match fuzzy and correct automatically.
function fuzzyMatch(input: string, validValues: string[]) {
// "FRIGHTNED" → "frightened"
// "half cover" → "half"
// "fire dmg" → "fire"
return findClosestMatch(normalize(input), validValues);
}
Every D&D enum in ChatRPG—conditions, damage types, abilities, sizes—uses fuzzy matching. The LLM can say "poisened" and the tool auto-corrects to "poisoned". This eliminates entire classes of retry loops.
"FRIGHTNED" "frightened" Pattern 5: Fork/Snapshot Synchronization
Some operations need isolation. Combat in a D&D game shouldn't permanently modify character HP until the encounter is resolved. This requires a fork/snapshot architecture.
commit This pattern appears in multiple servers:
- ChatRPG: Encounter participants are snapshots; commit syncs back to persistent characters
- Synch MCP: File locks with reader/writer semantics for multi-agent coordination
- Index Foundry: Run-based pipeline with isolated artifacts per run_id
// Synch MCP: Lock manager for multi-agent safety
await acquire_lock({
resource_id: "src/app.ts",
agent_id: "agent-alpha",
operation: "write",
timeout_ms: 30000
});
// Other agents can read but not write
// Write lock auto-expires after timeout Pattern 6: Design Output for Chat Interfaces
The output of MCP tools isn't consumed by APIs—it's displayed in chat windows. That means: ASCII art, clear formatting, and visual hierarchy matter.
ASCII Art Rendering
ChatRPG Battlefield Maps
╔═══════════════════════════════════════════╗ ║ COMBAT ENCOUNTER ║ ║ Round 3 ║ ╠═══════════════════════════════════════════╣ ║ ║ ║ · · · · · · · · · · ║ ║ · · F · · · · G · · ║ ║ · · · · █ █ · · · · ║ ║ · W · · █ █ · · G · ║ ║ · · · · · · · · · · ║ ║ ║ ║ F = Fighter W = Wizard G = Goblin ║ ║ █ = Obstacle ║ ╚═══════════════════════════════════════════╝
Structured Output
Character Sheets & Status
╔═══════════════════ CHARACTER SHEET ════════════════════╗ ║ Finn ║ ║ PC - Halfling Rogue (Level 5) ║ ║ ║ ║ HP: [████████████████] 35/35 ║ ║ ║ ║ ─────────────────────────────────────────────────────── ║ ║ ║ ║ AC │ Speed │ Initiative │ Prof Bonus ║ ║ 15 │ 25 ft │ +4 │ +3 ║ ╚═════════════════════════════════════════════════════════╝
Good Patterns
- ✓ Box drawing characters for structure
- ✓ Visual health bars with Unicode
- ✓ Legends explaining symbols
- ✓ Consistent spacing and alignment
Anti-Patterns
- ✗ Raw JSON dumps
- ✗ Tables wider than chat windows
- ✗ No visual hierarchy
- ✗ Missing context/explanations
Putting It All Together
These six patterns form a cohesive architecture for LLM-powered stateful applications:
The tools described here are all open source and part of the Mnehmos MCP ecosystem. Each server solves a different problem—memory, computer control, gaming, RAG indexing—but they all follow these same architectural principles.