pos-mcp-server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@pos-mcp-server86 the brisket sandwich and show me today's sales"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
pos-mcp-server
A Model Context Protocol server that lets a restaurant operate its point-of-sale by talking to an LLM agent — edit the menu, 86 an item, check sales, restock, watch for voids.
Twenty tools, two transports, one codebase:
stdio — for local clients (Claude Desktop, Claude Code, Copilot in VS Code). One process, one merchant, credentials from the environment.
Streamable HTTP — for cloud agents that can only reach a public endpoint. One endpoint serves every merchant, each request scoped by the caller's own API key.
This is extracted from a multi-tenant POS running in production. It's published as a reference for three things that were not obvious when I built it: making one tool implementation serve both transports, doing per-request tenancy in ASGI without leaking between concurrent requests, and the DNS-rebinding default that breaks MCP behind any reverse proxy.
The interesting parts
1. Tenancy comes from the key, and nothing else
On the remote transport there is no tenant identifier on the wire. The caller sends their API key; the key alone determines which merchant's data they get, resolved server-side.
def _get(path, params=None):
slug = _current_slug()
merged = {**(params or {})}
if slug: # stdio mode cross-checks the slug…
merged["slug"] = slug
... # …remote mode sends none, so there is nothing to tamper withA client cannot ask for another merchant's data, because there is no field in which to ask.
2. Pure-ASGI middleware, because BaseHTTPMiddleware loses contextvars
The per-request key is bound to a contextvars.ContextVar so the tool coroutine can read it
without threading an argument through every call site.
This does not work with Starlette's BaseHTTPMiddleware. That class runs the downstream app
in a separate task, so a contextvar set in the middleware is not visible inside the tool — you get
an empty key and a confusing 401, or worse, a fallback to the wrong tenant. Writing it as pure
ASGI keeps the whole request in one task:
class ApiKeyAuth:
async def __call__(self, scope, receive, send):
...
token = server._REQ_KEY.set(key)
try:
await self.app(scope, receive, send)
finally:
server._REQ_KEY.reset(token)Found by testing cross-tenant isolation, not by reading about it.
3. MCP's DNS-rebinding protection 421s behind a proxy
The SDK enables DNS-rebinding protection with a localhost-only host allowlist by default. Put the server behind any reverse proxy and every request returns:
421 Invalid Host headerwith nothing indicating a host allowlist is the cause. The fix is to configure the allowlist explicitly rather than to disable the protection:
server.mcp.settings.transport_security = TransportSecuritySettings(
enable_dns_rebinding_protection=True,
allowed_hosts=_ALLOWED,
allowed_origins=[...],
)Set ALAMOAI_POS_MCP_ALLOWED_HOSTS to your public hostname(s). The endpoint stays bearer-gated
either way.
4. Stateless JSON, deliberately
stateless_http = True and json_response = True. Every POST is self-contained, which keeps the
auth contextvar bound to exactly one request's task — and it matches what cloud MCP clients
expect, since SSE support was dropped from that ecosystem in late 2025.
5. Errors written for a model to act on
Tool errors are phrased as instructions, not status codes, because the consumer is a language model deciding what to do next:
if r.status_code == 404:
raise PosError("Not found. Check the id or name — list the menu/inventory "
"first to see valid values.")"404" makes a model guess. "List the menu first" makes it recover.
Related MCP server: MCP Coding Agents
Tools
Menu list_menu · add_menu_category · add_menu_item · update_menu_item ·
set_item_availability · set_item_online · publish_category_online · set_store_type
Inventory inventory_status · lookup_product · receive_stock · set_stock ·
restock_ingredient · record_waste
Operations sales_today · cost_report · open_orders · void_report
Payments payment_status · create_onboarding_link
Tools resolve items and ingredients by name, not by id, so the model doesn't have to carry identifiers across turns.
Running it
stdio (Claude Desktop, Claude Code, Copilot in VS Code)
pip install -r requirements.txt{
"mcpServers": {
"pos": {
"command": "python3",
"args": ["/path/to/pos-mcp-server/server.py"],
"env": {
"ALAMOAI_POS_API_KEY": "alamopos_yourkeyhere",
"ALAMOAI_POS_SLUG": "your-restaurant"
}
}
}
}Streamable HTTP (cloud agents)
pip install -r requirements-remote.txt
ALAMOAI_POS_MCP_ALLOWED_HOSTS=your.domain \
ALAMOAI_POS_MCP_PATH=/pos-mcp \
python3 remote_server.pyCallers authenticate per request:
Authorization: Bearer alamopos_…or X-Alamoai-Key: alamopos_…. Health check at /healthz. Missing key returns 401 before
anything else runs.
Environment
Variable | Default | Notes |
| — | Required in stdio mode |
| — | Required in stdio mode; unused in remote mode |
|
| Backing REST API |
|
| Remote mode bind address |
|
| Remote mode port |
|
| Public path for the endpoint |
|
| Set to your public hostname |
Design notes
Thin by intent. Every tool is one REST call against an API that already enforces tenancy and role permissions. This process holds no secrets beyond the one key and can grant nothing the key doesn't already carry. If it were compromised it would be a worse client, not a wider door.
API keys authenticate through the same path as interactive sessions. Because a key resolves through the identical session-resolution code as a human bearer token, every tenant-scoped, role-gated endpoint in the backing API accepted key auth with zero per-endpoint changes.
Build once, expose twice. The write functions behind these tools also back a web catalog editor. The MCP server was not a side integration — it was a second front end onto the same core.
License
Apache-2.0. See LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Flicense-quality-maintenanceA secure and pluggable MCP server to run terminal commands on your local machine or cloud server — remotely, safely, and with LLMs or agentic clients.Last updated
- Alicense-qualityCmaintenanceA transport-agnostic MCP server that integrates multiple AI coding agents (Claude Code, Gemini, and Codex) with built-in tools for command execution, calculations, and streaming capabilities. Supports both STDIO and HTTP transports for flexible deployment.Last updated571MIT
- AlicenseAqualityBmaintenanceA local MCP server that lets any LLM agent manage Pine AI tasks — negotiate bills, cancel subscriptions, resolve disputes, and make phone calls on your behalf.Last updated191MIT
- Alicense-qualityBmaintenanceA production-ready MCP server that gives an LLM agent standalone-equivalent control over a Mineflayer Minecraft bot — movement, mining, crafting, inventory, combat, containers, chat, and much more — exposed as 110 strongly-typed tools across 23 groups, with full bot lifecycle management and dual (poll + push) event streaming.Last updated641MIT
Related MCP Connectors
Hosted MCP server to manage a restaurant menu from AI agents - 39 tools over the DuckHub API.
Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer
Cloud-hosted MCP server for durable AI memory
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ryanDwright/pos-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server