shop-mcp
Provides tools to explore the schema of a SQLite database and run read-only SQL queries (SELECT, WITH ... SELECT) with pagination, enabling analysis of tables, rows, and relationships without modifying the database.
Click on "Deploy 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., "@shop-mcpWhat are the top 5 products by revenue?"
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.
shop-mcp
A local, read-only MCP (Model Context Protocol) server that lets an AI agent
analyze a SQLite e-commerce database (shop.db) — customers, products,
orders and order items — over the stdio transport. No HTTP server, no
separate database process: the server opens shop.db directly and exposes
two small, general-purpose tools an agent can use to explore the schema and
run its own analytical SQL.
Built with the official Python MCP SDK (mcp on PyPI).
Project structure
mcp-sql/
├── server.py # the MCP server (stdio transport)
├── shop.db # SQLite database (not modified by this project)
├── requirements.txt
├── .env.example
├── mcp-config.example.json
├── tests/
│ ├── conftest.py
│ ├── test_server.py # unit tests (call tool functions directly)
│ └── test_stdio_integration.py# protocol-level test (spawns server.py over stdio)
└── README.mdRelated MCP server: shop-db MCP Server
Database schema (as actually found in shop.db)
customers(id PK, first_name, last_name, email UNIQUE, phone, created_at)
products(id PK, name, category, price, stock_quantity, created_at)
orders(id PK, customer_id -> customers.id, order_date, status, total_amount)
order_items(id PK, order_id -> orders.id, product_id -> products.id, quantity, unit_price)orders.status is constrained to: new, processing, shipped,
completed, cancelled. products.category currently has 5 distinct
values. Foreign keys: orders.customer_id → customers.id,
order_items.order_id → orders.id, order_items.product_id → products.id.
The server derives all of this from the live database at query time (via
sqlite_master / PRAGMA table_info / PRAGMA foreign_key_list) — nothing
here is hard-coded, so if shop.db is swapped for another file with a
different schema, get_database_schema will reflect that automatically.
Known data characteristics of the provided shop.db: customers has no
country column, so "customers from Germany" style questions cannot be
answered — the schema tool makes this discoverable, and query_database
returns a clear no such column: country error instead of guessing. All
750 orders currently in the database are dated in 2026 (none in 2025), so a
"revenue in 2025" query correctly returns 0/null, not an error.
Installation
cd mcp-sql
python3 -m venv .venv
source .venv/bin/activate # on Windows: .venv\Scripts\activate
pip install -r requirements.txtConfiguration
The database path is never hard-coded in the source. It is resolved as:
the
SHOP_DB_PATHenvironment variable, if set;otherwise
shop.dbnext toserver.py.
Copy .env.example to .env and edit it if you want to point the server at
a different database file (you'll need to load it into your shell/agent
launcher yourself, e.g. export $(cat .env | xargs), or just set
SHOP_DB_PATH directly):
cp .env.example .env
# edit .env, or simply:
export SHOP_DB_PATH=/absolute/path/to/shop.dbRun
source .venv/bin/activate
python server.pyThe process speaks MCP over stdio and waits for a client — it will look
"stuck" with no output, which is expected: connect an MCP client (an AI
agent, or mcp-inspector, see below) rather than running it standalone in a
terminal.
Quick manual check with the official MCP Inspector (no install needed):
npx @modelcontextprotocol/inspector --cli .venv/bin/python server.py --method tools/listConnect to an AI agent
Most MCP-compatible clients (Claude Desktop, Claude Code, etc.) read a JSON
config block like mcp-config.example.json:
{
"mcpServers": {
"shop-mcp": {
"command": "/absolute/path/to/mcp-sql/.venv/bin/python",
"args": ["/absolute/path/to/mcp-sql/server.py"],
"env": {
"SHOP_DB_PATH": "/absolute/path/to/mcp-sql/shop.db"
}
}
}
}Notes:
Use the absolute path to the venv's Python interpreter (as above) so the
mcppackage is found without activating the venv manually; using a barepython3also works ifmcpis installed in whatever environment that resolves to.SHOP_DB_PATHis optional — omit it to use the bundledshop.db.Absolute paths belong in this configuration file, supplied by whoever connects the server — never inside
server.pyitself.Client-specific placement of this block varies (e.g. Claude Desktop uses
claude_desktop_config.jsonwith the samemcpServersshape; other clients may want just the inner{"command": ..., "args": ..., "env": ...}object). Check your client's docs for where the file lives.
Testing
source .venv/bin/activate
python -m pytest tests/ -vThis runs 48 tests, including:
schema discovery (tables, columns, PK/FK, relationships, row counts);
SELECT,JOIN,WHERE,GROUP BY,ORDER BY, aggregates (COUNT/SUM/AVG/MIN/MAX), subqueries, a safeWITH ... SELECTCTE, and date filtering (strftime);row-limit clamping and offset-based pagination;
friendly error handling for invalid SQL, unknown tables/columns, an empty query, and a missing database file;
read-only safety: every statement type listed in the assignment (
DELETE,UPDATE,DROP,CREATE,INSERT, plusALTER,REPLACE,TRUNCATE,ATTACH,DETACH,VACUUM,REINDEX, a destructivePRAGMA, a stackedSELECT 1; DROP TABLE ..., and aWITH x AS (...) DELETE ...CTE-disguised delete) is rejected, and the database file's row counts and SHA-256 hash are asserted unchanged afterwards;tests/test_stdio_integration.pylaunchesserver.pyas a real subprocess and drives it through the actual MCP client SDK over stdio (initialize→list_tools→call_tool), rather than calling Python functions directly — this is the same path a real agent uses.
MCP tools
get_database_schema()
No parameters. Call this first whenever you don't already know the exact
table/column names — don't guess them. Returns, per table: row_count,
columns (name, SQLite type, not_null, default_value,
is_primary_key), primary_key, foreign_keys (column, referenced
table/column, ON DELETE/ON UPDATE), and a few sample_rows so the agent
can see real date formats, status values, price magnitudes, etc. A
top-level relationships list gives table.column -> other_table.column
strings derived from the live foreign keys.
query_database(sql, limit=100, offset=0)
Runs one read-only SQL statement (SELECT, or WITH ... SELECT) and
returns {columns, rows, row_count, limit, offset, truncated, total_matching_rows}. Supports JOIN, WHERE, GROUP BY, ORDER BY,
aggregate functions, subqueries, and CTEs. limit is clamped to 1..500
(default 100); use offset to page through larger results.
total_matching_rows and truncated tell the caller whether the current
page is the whole result or there is more to fetch. Errors (bad syntax,
unknown table/column, or a rejected write attempt) are raised as a short,
specific message — never a raw Python traceback.
Security: how read-only is enforced
The assignment explicitly asks not to rely on a single regex/keyword check,
so this server layers four independent defenses — verified in
tests/test_server.py:
OS-level read-only file handle. The SQLite file is opened with the URI
file:<path>?mode=ro. SQLite itself then refuses any write (OperationalError: attempt to write a readonly database) no matter what SQL is executed — this holds even if every check below has a bug.PRAGMA query_only = ONis set on every connection as a second, independent SQLite-level guard against writes.A
sqlite3authorizer callback (Connection.set_authorizer) allow-lists only theSELECT/READ/FUNCTION/RECURSIVEactions at the SQLite engine level and denies everything else —INSERT,UPDATE,DELETE,DROP,ALTER,CREATE,REPLACE,TRUNCATE,ATTACH,DETACH,VACUUM,REINDEX,PRAGMA, transactions, etc. This runs on the parsed statement, so it also catches the classic CTE bypassWITH x AS (SELECT 1) DELETE FROM ...that a naive "must start with SELECT" text check would miss.Statement-shape checks in
server.py: the submitted text must start withSELECT/WITH(fast, friendly rejection before touching SQLite), and every query is executed wrapped asSELECT * FROM (<query>) LIMIT :limit OFFSET :offset— a single statement is required for this to parse at all, so a stackedSELECT 1; DROP TABLE customersbecomes a plain SQL syntax error rather than two executed statements.
Because layer 1 (mode=ro) is enforced by SQLite/the OS independently of
this server's own logic, shop.db cannot be modified through this server
even if a bug existed in layers 2-4.
Known limitations
customershas nocountry/location column in the providedshop.db, so questions like "customers from Germany" cannot be answered from this data — the schema tool surfaces this rather than the server inventing a column.All orders in the provided data are dated in 2026; a 2025 revenue query correctly returns 0 rather than an error.
total_matching_rowsinquery_databaseis computed with a secondCOUNT(*)wrapping the same query; for very expensive queries this roughly doubles the work. Given the size of this database (hundreds to a few thousand rows per table) this is not a practical concern.
This server cannot be deployed
Maintenance
Related MCP Connectors
Query 40 databases from Claude, ChatGPT, or Cursor — on any device. Read-only, encrypted, audited.
Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.
Ask questions across Shopify, Klaviyo, GA4 and 20+ e-commerce sources in plain English.
Run AI customer support from your terminal: conversations, knowledge base, and chat widget.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceProvides AI agents read-only analytical access to a SQLite database over stdio, with tools for listing tables, describing schemas, and running paginated SQL queries.-
- FlicenseAqualityBmaintenanceGives AI agents read-only analytical access to an e-commerce SQLite database (customers, orders, order_items, products) via SQL queries, table listing, and schema inspection.3-
- FlicenseAqualityCmaintenanceEnables AI agents to analyze an SQLite e-commerce database via secure read-only SQL queries, providing tools for table inspection and analytical requests.2-
- FlicenseNot gradedqualityCmaintenanceEnables AI agents to connect to a read-only SQLite e-commerce database via stdio, safely executing SELECT queries with schema exploration, sample data, pagination, and self-correcting error messages.-