Restaurant PoS Agent Adapter (RP2A)
A robust multi-vendor MCP server integration that empowers AI Voice Agents to seamlessly explore restaurant menus and take live orders via vendor CRMs.
System Architecture

Multi-Layered Design
The system is architected into three distinct functional layers to ensure modularity and vendor-agnostic tool execution:
Public MCP Tool Layer
Fixed schemas for menu items and orders.
Middle Adapter Layer
Translation between vendor models and internal standards.
Vendor API Client Layer
Inherits abstract clients for direct POS connectivity.
◈ Protocol Context
RP2A operates as two FastMCP servers mounted on a single Starlette application: one for Menu Exploration and one for Iterative Order Taking.
◈ Stateful Flow
It defines strict base schemas for entities like Items, Orders, and Customers, maintaining high fidelity across varied CRM responses.
Voice Agent Interface

Menu Exploration & Caching Pipeline
To ensure lightning-fast responses for the voice AI, menu structures are inherently cached via Redis using a DataRepository pattern. The agent rarely hits the direct API client unless cache state is invalid.
- ⚡Redis-backed Data Repository pattern for fast menu retrieval.
- 🔍Combines BM25 and Vector indexing stored in ChromaDB, updated periodically via a Cloud Run Cron Job (every 3 hours) fetching the latest menus.
- 📊Uses Reciprocal Rank Fusion (RRF) to merge keyword and semantic search results dynamically.
- 🧠Embeddings are generated via
all-MiniLM-L6-v2running locally inside the server rather than using an external API, entirely eliminating cloud provider latency for vectorization.
// Sample Menu Flow
Agent: "Do you have spicy chicken wings?"
→ call get_menu_search(query="spicy chicken wings")
⟳ check Redis cache...
○ BM25 Index Search
○ Vector Semantic Search
← return RRF Ranked Result
Agent: "Yes, our Buffalo Wings come in 6 or 12 pieces."
# Python Overridden Implementation
async def place_order(self, order: base_models.Order):
logger.info(f"Placing order {order.orderNum}")
# Save base details
orig_cust = order.customer
# Adapter conversion
ft_order = OrderAdapterFT.adapt_order_to_ft(order)
# API Client execution
placed = await self.api.place_order(ft_order)
# Convert back to base
final = OrderAdapterFT.adapt_order(placed)
final.customer = orig_cust
return finalStateful Iterative Order Taking
Voice orders are inherently non-linear. The user might change their mind midway. RP2A solves this by keeping a robust state engine synchronized with a Supabase PostgreSQL database during the session.
At each modification (adding items, applying customer details), the order is pushed through validation routes via the specific vendor API. Only successfully validated intermediate states are committed to Supabase, guaranteeing that by the time the AI invokes place_order, the payload is perfectly formed.
Multi-Tenant & JWT Authorization mapping
RP2A is a central hub for multiple vendors and AI agents. It uses an internal CLI tool (rp2a) to register new agents and issue specific JWTs.
- AgentID: agt_12398x
- Role: orderHelper
- Vendor: Foodtec
At runtime, standard Starlette middleware intercepts requests, validates the JWT with Supabase, extracts the vendor name, and dynamically injects the correct OrderServiceFT and MenuServiceFT into the requesting FastMCP context.
Dynamic Dependency Injection
Abstract interfaces ensure MCP schemas remain constant while underlying vendor logic hotswaps based on active authentication roles.
🚀 Infrastructure & Ultra-Low Latency
Colocated Redis
Cloud Run server and Redis cache are both in us-east-1, guaranteeing single-digit millisecond latency for menu retrieval.
Supabase Pooler
Uses the Transaction Pooler for fast, concurrent stateless connections from Cloud Run to Postgres.
GCP Cloud Run
Auto-scaling compute handles real-time voice streams with ease, integrated with SQLAlchemy for reliable state management.