mcp-server-prod
A hardened MCP server exposing read-only orders tools to AI agents, with rigorous input validation and parameterized queries to prevent injection attacks.
README
mcp-server-prod
A hardened MCP server — because MCP tool arguments are written by an LLM, from untrusted user text. Exposes read-only "orders" tools to an AI agent, and treats every argument as hostile: validated, parameterized, and served over a read-only connection.
Runs offline.
docker compose upruns the security battery with no API key and no network — the standard library only. (The MCP server itself needspip install mcp.)
The problem
An MCP tool signature like search_orders(status: str) looks safe. But status doesn't come from your code — it comes from a model, interpreting whatever a user typed. That's a network boundary wearing a function signature. The naive server everyone writes first concatenates that string into SQL and calls it a day:
sql = f"SELECT * FROM orders WHERE status = '{status}'" # ← LLM-controlled
One status = "paid' OR '1'='1" and the agent just dumped your whole table.
Quickstart
docker compose up # security battery: naive vs prod scorecard (offline)
make demo # same, without Docker
make test # the security contract tests (pytest)
make serve # run the actual MCP server over stdio (needs: pip install mcp)
The scorecard (before → after)
The same five hostile tool-calls, run against the naive server and this one:
| attack | naive | prod |
|---|---|---|
filter bypass — status = paid' OR '1'='1 |
🔴 LEAK | 🟢 SAFE |
table exfil — customer = zzz' OR '1'='1 |
🔴 LEAK | 🟢 SAFE |
numeric injection — min_total = "0 OR 1=1" |
🔴 LEAK | 🟢 SAFE |
id injection — get_order("1 OR 1=1") |
🔴 LEAK | 🟢 SAFE |
| write reaches the DB (read-only?) | 🔴 LEAK | 🟢 SAFE |
| safely handled | 0 / 5 | 5 / 5 |
Meanwhile legitimate queries are unaffected — search_orders(status="paid") returns the 12 paid orders, revenue_summary("paid") returns {count: 12, total: 25451.24}. Hardening didn't cost functionality.
The three defenses
- Validate every argument (
validation.py) —statusmust be an enum member,order_idmust be an integer,limitis clamped to 100. An agent asking for a million rows gets 100; a bad enum is a structured error, not a silent mis-query. - Parameterize every query (
tools.py) — user values are bound, never concatenated.customer = "zzz' OR '1'='1"becomes a literal name that matches nobody, not a bypass. - Open the database read-only —
file:...?mode=ro. Defense in depth: even a query that slips through cannot mutate data. The write test raises at the driver.
Wire it into a desktop MCP client
{
"mcpServers": {
"orders": { "command": "python", "args": ["-m", "mcp_server_prod.server"] }
}
}
Drop that into Claude Desktop's MCP config and the search_orders / get_order / revenue_summary tools appear to the agent. (A short screen recording of that flow makes a good demo GIF — the server code and config here are what it records.)
How it's verified
Security is a contract test suite, not a claim: the production tools must reject or safely handle each hostile input, and the naive implementation must demonstrably leak (so the threat is real, not hypothetical). CI also builds the server against the official mcp SDK. All checks run offline; the battery scorecard is computed from real behavior.
9 passed # pytest
0/5 → 5/5 # security battery
mcp server ok # builds with the official SDK
Honest limitations
· Synthetic 20-row SQLite dataset — a demonstrator of the access pattern, not a product.
· Scope is read-only retrieval. A write-capable server needs authЗ, per-tool authorization,
and audit logging (authn/authz) — explicitly out of scope here and called out, not faked.
· The "naive vs prod" contrast is illustrative; it doesn't enumerate every injection class
(no second-order, stored, or ORM-specific vectors).
· Parameterization + read-only defends the data layer; it is not a full threat model
(rate limiting, secrets handling, and prompt-injection of the agent itself are separate).
Layout
mcp_server_prod/ db.py · validation.py · tools.py (prod) · naive.py · server.py · demo.py
tests/ test_security.py # the security contract
Dockerfile · docker-compose.yml · .github/workflows/ci.yml
MIT © 2026 Jigon Yoo
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
Neon Database
MCP server for interacting with Neon Management API and databases
E2B
Using MCP to run code via e2b.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.