Skip to main content
Glama
voidwhereprohibited

secure-inventory-mcp

secure-inventory-mcp

A small, read-only MCP server that lets an AI assistant (Claude Desktop, etc.) answer inventory and order-status questions for a small shop — built to show least-privilege MCP design, not just "it works."

Demo data is fictional. This is a reference implementation, not a certified or guaranteed-secure product. Review it against your own threat model.

Tools

Tool

What it does

Guardrails

Adapters

get_product

One product by SKU

SKU must match AA-0000

sqlite, csv

search_products

Name search

2–40 chars, letters/digits/space/-// only, ≤50 rows

sqlite, csv

low_stock

Items at/below a threshold

0–1000

sqlite, csv

order_status

Status + item count of one order

Never returns customer name/email

sqlite

There is deliberately no raw-query tool and no write tool.

Related MCP server: OpsBridge MCP

Data sources (adapters)

The server is split so a new data source is about one file:

server.py              picks the adapter (INVENTORY_ADAPTER), runs STDIO only
core.py                shared controls: input types, row cap, sanitizing,
                       untrusted_data wrapper, audit log, masked errors
adapters/__init__.py   allowlist registry (env var can't import arbitrary code)
adapters/sqlite_inventory.py   SQLite, read-only (default)
adapters/csv_inventory.py      CSV file, read-only
adapters/_template.py          copy this to add Google Sheets, REST API, Postgres...

Adapter

INVENTORY_ADAPTER

Config env var

Extra protections

SQLite

sqlite (default)

INVENTORY_DB

mode=ro + PRAGMA query_only; parameterized SQL

CSV

csv

INVENTORY_CSV

Column allowlist (other columns like costs or supplier emails are dropped on load); 5 MB / 50,000-row cap; spreadsheet formulas (= + - @) defused; malformed files fail closed

Adding a source: copy adapters/_template.py, fill in _fetch_rows() using a read-only credential from an env var, add it to ADAPTERS in adapters/__init__.py, and copy the CSV tests in tests/test_adapters.py. The template's docstring lists which controls core.py gives you for free (C1, C6–C9) and which the adapter must handle (C2, C3, C5, C10).

Security controls

#

Control

Where

C1

STDIO only — no network port

server.py

C2

Data source opened read-only

each adapter (_connect(), _load())

C3

Fixed, parameterized queries / filters only

each adapter

C4

Strict input schemas checked before code runs

shared types in core.py

C5

Data minimization (allowlisted fields only, no customer PII)

each adapter

C6

Hard row cap (50)

core.wrap()

C7

Output sanitized + wrapped as untrusted_data (stored prompt-injection defense)

core.clean(), core.wrap()

C8

Every call audited to JSONL, including rejected calls

core.AuditMiddleware

C9

Internal error details masked

core.build_server()

C10

Config by env vars, no secrets in code

INVENTORY_ADAPTER, INVENTORY_DB, INVENTORY_CSV, AUDIT_LOG

Each control has tests that try to break it. tests/test_security.py (SQLite): SQL injection, LIKE wildcards, oversize input, row-cap bypass, stored prompt injection with a bidi-override character, PII leakage, writes, path leakage in errors, audit gaps. tests/test_adapters.py (CSV + registry): the same attacks plus hidden-column leakage, spreadsheet-formula injection, file tampering, malformed/oversized/binary files leaking paths or row content, unknown-adapter names, and the template failing closed.

For a new client job, start with CLIENT-INTAKE.md.

Run it

python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python seed_db.py
.venv/bin/python -m pytest -q

Connect to Claude Desktop

Add to claude_desktop_config.json (use absolute paths):

{
  "mcpServers": {
    "secure-inventory": {
      "command": "/ABSOLUTE/PATH/secure-inventory-mcp/.venv/bin/python",
      "args": ["/ABSOLUTE/PATH/secure-inventory-mcp/server.py"],
      "env": {
        "INVENTORY_DB": "/ABSOLUTE/PATH/shop.db",
        "AUDIT_LOG": "/ABSOLUTE/PATH/audit.jsonl"
      }
    }
  }
}

For a CSV file instead, use this env block:

"env": {
  "INVENTORY_ADAPTER": "csv",
  "INVENTORY_CSV": "/ABSOLUTE/PATH/inventory.csv",
  "AUDIT_LOG": "/ABSOLUTE/PATH/audit.jsonl"
}

The CSV needs the columns sku,name,description,price,qty (extra columns are allowed and ignored). See sample_inventory.csv.

Adapting it for a real business

The pattern carries over to any data source (Shopify, Square, a Postgres DB, a Google Sheet): write one adapter from adapters/_template.py, give it its own read-only credential scoped to the smallest set of data, expose a few narrow tools instead of a generic query tool, and keep the tests. See SECURITY-CHECKLIST.md.

References

License

MIT (see LICENSE).

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI to query a business database for customers, orders, and revenue using natural language through safe, well-defined tools.
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI clients to search and retrieve customer and support-ticket data from a SQLite database, and to create support tickets only when an explicit approval flag is supplied, with all actions validated and audit-logged.
    -
  • F
    license
    A
    quality
    C
    maintenance
    Enables AI agents to answer analytical questions about an online store's SQLite database through specialized read-only tools, without any risk of modifying the underlying data.
    8
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to query and modify inventory data through natural language, bridging AI assistants to the inventory management system without requiring SQL knowledge or direct database access.
    -