shop-mcp
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-mcp What are our top 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 read-only Model Context Protocol server
that exposes analytics tools over the shop.db SQLite database of an internet
shop (customers, products, orders, order items). It is designed to be connected
to an AI agent so the agent can answer analytical questions about the data
without ever being able to modify it.
The server speaks MCP over stdio, opens the database in read-only mode, and exposes a small set of specialised, parameterised tools whose descriptions encode the domain rules (which order statuses count as revenue, how a customer's country is derived, where money comes from). There is no generic SQL tool and no write tool — a destructive prompt such as "Delete all cancelled orders" cannot be executed.
The MCP server code in this repository was produced by an AI coding agent (Cursor), per the homework constraint that the server must not be written by hand.
Requirements
Python 3.11 or newer
The
shop.dbSQLite database (committed atdatabase/shop.db)uv(recommended) — runs the server in an isolated project environment with no global install. Install it withbrew install uv(macOS) orcurl -LsSf https://astral.sh/uv/install.sh | sh.
Related MCP server: mcp-data-analyst
Install
With uv (recommended) — no manual venv or pip needed, uv resolves the
project and its dependencies from pyproject.toml on first run:
uv sync # create / refresh the project's .venv from pyproject.tomlWithout uv — create a virtualenv and install the package yourself:
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .This installs the mcp SDK and the shop-mcp package (which provides the
python -m shop_mcp entry point and the shop-mcp console script).
Configure
The server opens the database at database/shop.db relative to the
process working directory (ProjectRoot). No environment variables are required.
When launched via uv run --directory <project> (see the client configs
below), uv sets the working directory to the project root, so the committed
database is found automatically.
If database/shop.db is missing, the server exits at startup with a clear
configuration error that includes the current working directory (no stack
trace, no silent fallback). Ensure your MCP client config sets cwd to the
repository root.
Run
uv run python -m shop_mcpor, with the package installed in an active venv:
python -m shop_mcpor, equivalently:
shop-mcpThe server reads JSON-RPC over stdin and writes to stdout. You normally do not run it directly — your AI agent launches it for you (see below).
Connect to an agent
Ready-to-use MCP client configs are committed under examples/mcp/ and run
with no setup beyond installing uv:
Client | Config file |
Cursor |
|
Claude Desktop |
|
Generic stdio |
|
Canonical/default |
|
Docker |
|
Each config looks like this (replace the --directory path with the absolute
path of this repo on your machine):
{
"mcpServers": {
"shop": {
"command": "uv",
"args": ["run", "--directory", "/path/to/internet-shop-mcp", "python", "-m", "shop_mcp"]
}
}
}uv run --directory <project> sets the working directory to the project root
and uses the project's .venv, so the server finds database/shop.db
automatically. The same config is portable across machines (only the
--directory path changes).
If you prefer not to use uv, install the package into a venv yourself (see
Install), use command: "python", and set cwd to the repository
root in your MCP client config.
Cursor: open Settings → MCP → Add MCP Server and paste the contents of
examples/mcp/cursor.json(or use the Project MCP scope and commit it).Claude Desktop: copy the contents of
examples/mcp/claude_desktop.jsonintoclaude_desktop_config.json(macOS:~/Library/Application Support/Claude/claude_desktop_config.json).Generic stdio client: use
examples/mcp/generic_stdio.jsonwith any client that speaks MCP over stdio.
After connecting, the agent sees eight tools: list_tables,
describe_table, count_customers_by_country, rank_countries_by_customers,
top_customers, top_products, revenue_by_category, revenue_by_year.
Tools
Tool | Answers |
| Task 1 — list tables and what each contains |
| schema of one table |
| Task 2 — customers from a country |
| Task 3 — country with the most customers |
| Tasks 4 & 8 — top spender / most orders |
| Task 5 — top best-selling products |
| Task 6 — top categories by revenue |
| Task 7 — revenue for a year |
Domain rules baked into the tool descriptions (see CONTEXT.md and
docs/adr/ for the full rationale):
Country is derived from the customer's phone-number prefix (E.164). There is no
countrycolumn.+49→ Germany,+7→ Russia. An unrecognised prefix maps tounknown. The tool accepts a full name ("Germany") or an ISO alpha-2 code ("DE") and returns both.Revenue / spend count only
completedandshippedorders.Most orders counts every order status except
cancelled.Best-selling ranks products by units sold; revenue is a secondary field.
Money comes from
orders.total_amountfor order/customer/year rollups and fromSUM(order_items.quantity * order_items.unit_price)for product/category rollups (the actual sale price, not the currentproducts.price).Limits default to 100 and are clamped to a maximum of 1000;
offsetpaginates.Errors are returned to the agent as short plain messages (e.g.
Invalid year: must be a 4-digit integer); stack traces go to stderr only.
Safety
The database is read-only by construction:
SQLite is opened with
file:<path>?mode=ro(uri=True), so any write attempt raisessqlite3.OperationalError: attempt to write a readonly database.PRAGMA query_only = 1is set as defense in depth.No write or generic-SQL tool is exposed. The only tools are the eight read-only analytics tools above.
A test (tests/test_safety.py) asserts that a write attempt raises, that no
write tool is advertised, and that the database file is byte-for-byte unchanged
after every tool runs.
End-to-end verification
The eight homework tasks were verified against a connected AI agent. Expected
results on the committed data (150 customers, all with +7 numbers; 750
orders, all dated 2026):
List all tables —
list_tablesreturnscustomers,products,orders,order_itemswith a description each.How many customers are from Germany? —
count_customers_by_country("Germany")→0(honest zero; no customer has a+49number).Which country has the most customers? —
rank_countries_by_customers→ Russia (RU), 150 customers.Who spent the most money? —
top_customers(by="spend", limit=1)→ Полина Козлов,polina.kozlov340@icloud.com, total spend 531810.0.Top 5 best-selling products —
top_products(limit=5)→ ranked by units sold (Эспандер плечевой, Планшет Tab 10, …) with revenue alongside.Top 3 categories by revenue —
revenue_by_category(limit=3)→ Электроника, Бытовая техника, Одежда и обувь.Revenue in 2025 —
revenue_by_year(2025)→0with the noteno orders in 2025(no year substitution; all orders are 2026).Most orders —
top_customers(by="order_count", limit=1)→ София Яковлев,sofiya.yakovlev284@yandex.ru, 15 orders.
The destructive prompt "Delete all cancelled orders" is refused: there is no tool that accepts it, and the read-only connection rejects any write at the SQLite level.
Tests
uv run --extra dev pytest
# or, with the package installed in an active venv:
pip install -e ".[dev]"
python -m pytestThe suite covers: the smoke test (server starts over stdio and answers a handshake/list_tools), every tool's happy path, the domain rules (revenue excludes non-earned statuses, order count excludes cancelled, products rank by units), edge cases (Germany → 0, 2025 → 0 with note, unknown country, invalid year/metric/by, limit clamping, pagination), and the safety guarantees (write attempt raises, no write tools, database file unchanged).
Docker (bonus)
See the "Docker" section below for a containerised run.
Project layout
internet-shop-mcp/
├── database/
│ └── shop.db # the read-only database
├── pyproject.toml # package + dependency declaration
├── README.md
├── CONTEXT.md # domain glossary
├── docs/adr/ # ADR-0001..0005
├── src/shop_mcp/
│ ├── __main__.py # `python -m shop_mcp`
│ ├── main.py # server wiring + tool registration
│ ├── config.py # database/shop.db resolution
│ ├── db.py # read-only SQLite connection
│ ├── country.py # phone-prefix → country mapping
│ └── tools.py # tool implementations
├── tests/ # pytest suite mirroring src
├── examples/mcp/ # agent connection configs
├── Dockerfile
└── .dockerignoreDocker
Build and run the server in a container. The database is copied into the image
at /app/database/shop.db (same convention as local dev).
docker build -t shop-mcp .
docker run --rm -i shop-mcpA matching MCP client config using Docker:
{
"mcpServers": {
"shop": {
"command": "docker",
"args": ["run", "--rm", "-i", "shop-mcp"]
}
}
}To mount your own database instead of the bundled one:
docker run --rm -i -v "$PWD/database:/app/database:ro" shop-mcpThe read-only guarantees are preserved inside the container: the connection
uses mode=ro and query_only=1, and a destructive prompt is still refused.
Available Tools
8 toolscount_customers_by_countryA
Count customers by country. The country is NOT a column — it is derived from the customer's phone-number prefix (E.164), so do not look for a country column. With no argument, returns counts per country as a list of {country_code, country_name, customer_count}. With a country argument (either a full name like 'Germany' or an ISO alpha-2 code like 'DE'), returns the count for that country only — an honest 0 if there are no customers there. An unknown country name returns an error.
Inputs: country.
describe_tableA
Describe the schema of one table: columns (name, type, not_null, default, primary_key) and foreign keys. Use this before building any mental model of how tables relate. Returns {table, columns, foreign_keys}. Pass the table name (one of: customers, products, orders, order_items).
Inputs: table (required).
list_tablesA
List every table in the shop database with a short description of what each table holds. Use this first to understand what entities are available. Returns a list of {name, description}.
Inputs: no named parameters.
rank_countries_by_customersA
Rank countries by number of customers, highest first. The country is derived from each customer's phone-number prefix (there is no country column). Returns a list of {country_code, country_name, customer_count}. limit defaults to 100 and is clamped to a maximum of 1000.
Inputs: limit.
revenue_by_categoryA
Rank product categories by revenue, joining orders -> order_items -> products. Only 'completed' and 'shipped' orders count. Revenue is SUM(order_items.quantity * order_items.unit_price) using the actual sale price. Returns a list of {category, revenue}. limit defaults to 100 (max 1000); offset paginates.
Inputs: limit, offset.
revenue_by_yearA
Return total revenue for a single year (a 4-digit integer, e.g. 2025). Revenue is SUM(orders.total_amount) over orders whose order_date falls in that year and whose status is 'completed' or 'shipped'. A year with no qualifying orders returns revenue 0 with a 'no orders in ' note — it does not substitute a different year. An invalid year returns an error.
Inputs: year (required).
top_customersA
Rank customers either by total spend or by number of placed orders.
by='spend': sums orders.total_amount over orders with status 'completed' or 'shipped' only (new, processing, cancelled do not count as earned). Returns first_name, last_name, email, total_spend.
by='order_count': counts orders whose status is NOT 'cancelled'. Returns first_name, last_name, email, order_count. Customer names are returned as separate first_name and last_name fields.
limitdefaults to 100 (max 1000);offsetpaginates.
Inputs: by, limit, offset.
top_productsA
Rank products by units sold (metric='units', default) or by revenue (metric='revenue'). Only 'completed' and 'shipped' orders count. Money is computed from order_items.unit_price (the actual sale price), not the current products.price. Returns a list of {name, units_sold, revenue}. When metric='units', ranking is by units_sold and revenue is a secondary field. limit defaults to 100 (max 1000); offset paginates.
Inputs: limit, metric, offset.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 8 tools
Most tools are clearly distinct, but count_customers_by_country and rank_countries_by_customers return the same country-level customer counts with only ordering/filtering differences. The other tools, such as top_customers and top_products, are well separated by their selection criteria.
Namess are mostly predictable and use snake_case with clear intent, like list_tables and describe_table. The main inconsistency is that some tools follow imperative verb names while others use noun phrases like top_customers or revenue_by_category, but the pattern remains readable.
Eight tools is well-suited for a read-only analytics server covering schema inspection and common shop metrics. Each tool has a clear role, and the count does not feel excessive or thin.
The toolset covers the main analytics surface: customer geography, top customers, top products, category revenue, and annual revenue. It lacks order-level or product-level detail queries and finer time-based filters, but the visible analytical workflows are complete enough for most shop insight requests.
Maintenance
Related MCP Connectors
Text-to-SQL MCP server: read-only queries on PostgreSQL, MySQL and SQL Server with your real schema
MerchantFlow is a hosted, read-only ecommerce analytics MCP server for Shopify and WooCommerce. It gives AI assistants tenant-scoped access to revenue, profit and loss, product and SKU profitability, COGS coverage, fulfillment costs, advertising spend, ROAS, marketing performance, cohorts, LTV, and business valuation across connected commerce and marketing platforms. Connect with OAuth over Streamable HTTP. No local server installation is required.
Read-only Google Analytics 4 MCP server for AI assistants.
PostgreSQL, MySQL, and SQL Server in one session. 26 read-only MCP tools for AI agents.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceA secure MCP server that exposes a SQLite database to AI agents with Role-Based Access Control, supporting authentication, customer/order/user management, and audit logging.-
- AlicenseNot gradedqualityDmaintenanceA natural-language data analyst MCP server that lets users query SQLite sales datasets via MCP tools (list_tables, aggregate, time_series, run_sql) with read-only SQL safety guards, returning results through a FastAPI dashboard.MIT
- FlicenseNot gradedqualityDmaintenanceEnables safe, read-only analysis of an online store's SQLite database, providing schema introspection, restricted SELECT queries, and specialized analytics tools through MCP.-
- FlicenseNot gradedqualityCmaintenanceThis MCP server lets an AI agent securely connect to a read-only SQLite store database, inspect its tables and schema, and run analytical SQL queries without modifying any data.-