mcp architecture state-management llm-tooling

Six Patterns for Connecting LLM Agents to Stateful Tools

A deep dive into MCP server architecture: externalizing state, composite operations, fuzzy validation, fork/snapshot synchronization, and designing for chat interfaces.

V
Vario aka Mnehmos

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.

SYNCH MCP: MEMORY ARCHITECTURE
Active Context • Filing Cabinet • Bug Tracker • Spatial Maps
Storage Layers
  • Active Context: Current focus + summary
  • Filing Cabinet: Indexed file summaries
  • Spatial Map: Project folder graph
  • Bug Tracker: Issues with severity
Agent is stateless. Storage is the intelligence.

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.

// ChatRPG: fuzzy-enum.ts
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.

Input
"FRIGHTNED"
Matched
"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.

ENCOUNTER STATE ISOLATION
Persistent Storage
28 HP
characters/fenric.json
FORK
Encounter Snapshot
23 HP
5 damage taken in combat
Changes stay isolated until explicit commit
manage_encounter(operation: "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:

01 Externalize State → Database is intelligence, agent is hands
02 Rich Query Tools → Agent reconstructs context by asking
03 Composite Operations → Reduce round-trips, batch intelligently
04 Fuzzy Validation → Auto-correct typos, don't fail hard
05 Fork/Snapshot Sync → Isolate changes until explicit commit
06 Chat-First Output → ASCII art, visual hierarchy, legends

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.

Building stateful LLM applications one MCP tool at a time.