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 "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., "@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.mdDatabase 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 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 Connectors
Run AI customer support from your terminal: conversations, knowledge base, and chat widget.
Read-only bank access for your AI agent. Connects Claude, ChatGPT, Cursor, Gemini, Codex.
Query PostgreSQL databases in plain English — LLM-generated, safety-validated SQL.
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/AndrewKonst/shop-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server