Skip to main content
Glama

Read-only MCP server for a store

Python MCP server over stdio for an existing SQLite database of an online store (data/shop.db). The database file is opened read-only; modifying SQL is rejected and not executed.

Installation

Python 3.11+:

python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

Related MCP server: Shop SQLite MCP

Running

From the project root (default database path is ./data/shop.db):

python -m shop_mcp
# или
shop-mcp

Another database path:

SHOP_DB_PATH=~/my/test-mcp-db/data/shop.db python -m shop_mcp

The process communicates via MCP through stdin/stdout. Don't enter commands into this terminal until an MCP client is connected to it.

MCP config in Cursor

Add an mcpServers entry (Cursor Settings → MCP, or .cursor/mcp.json in the project). The transport is stdio.

In the examples, the path ~/my/test-mcp-db is this repository. The server expands SHOP_DB_PATH itself (~ → home directory). If Cursor doesn't expand ~ in command / cwd, specify /Users/<you>/my/test-mcp-db/... there.

Locally (venv)

{
  "mcpServers": {
    "shop": {
      "command": "~/my/test-mcp-db/.venv/bin/python",
      "args": ["-m", "shop_mcp"],
      "cwd": "~/my/test-mcp-db",
      "env": {
        "SHOP_DB_PATH": "~/my/test-mcp-db/data/shop.db"
      }
    }
  }
}

If a console script is more convenient after pip install -e .:

{
  "mcpServers": {
    "shop": {
      "command": "~/my/test-mcp-db/.venv/bin/shop-mcp",
      "cwd": "~/my/test-mcp-db",
      "env": {
        "SHOP_DB_PATH": "~/my/test-mcp-db/data/shop.db"
      }
    }
  }
}

Docker

Build the image once:

docker build -t shop-mcp .
{
  "mcpServers": {
    "shop": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-v",
        "~/my/test-mcp-db/data/shop.db:/data/shop.db:ro",
        "-e",
        "SHOP_DB_PATH=/data/shop.db",
        "shop-mcp"
      ]
    }
  }
}

The -i flag is required to keep stdio connected. Mount only shop.db; the container must not overwrite the file on the host.

Tools

Tool

Purpose

list_tables

User tables and a brief description of their role (sqlite_sequence is not shown)

describe_table

Actual columns, types, PK/FK

execute_readonly_sql

A single SELECT / WITH / EXPLAIN; limit (default 100, maximum 1000) and offset

shop_analytics

Ratings and revenue for a year without manual SQL

Amounts, quantities, and revenue are calculated as order_items.quantity * order_items.unit_price; orders with the cancelled status are excluded. The calendar year is taken from strftime('%Y', orders.order_date).

Security

  • SQLite URI file:<abs>?mode=ro

  • Comments are stripped; query chains (SELECT 1; DELETE ...) are rejected

  • INSERT / UPDATE / DELETE / DROP / ALTER / CREATE / ATTACH / PRAGMA / … → explicit "not allowed" error

  • SQLite syntax errors and unknown columns are returned in a structured way; the process doesn't terminate

Notes on evaluation

  • The customers table has no country column. That is visible through describe_table; don't invent geography. SELECT country FROM customers will return an SQLite error.

  • Order dates in the current dump are from 2026. Revenue for 2025 may be 0. That's a correct result, not a broken filter.

Tests

pytest

The tests copy data/shop.db to a temporary file and verify that the copy's hash doesn't change after rejected writes.

Example of a processed query

screenshot.png

Available Tools

4 tools
describe_tableA

Describe columns, types, primary keys, and foreign keys for one table.

Use before writing joins. Returns actual SQLite metadata only — never fabricates fields such as country. Parameters: table (name from list_tables). Returns {table, columns: [{name, type, primary_key, ...}], foreign_keys}.

Inputs: table (required). Returns: object

execute_readonly_sqlA

Run a single read-only SQL statement against shop.db.

Allowed: one SELECT, WITH (CTE), or EXPLAIN. Writes and DDL (INSERT, UPDATE, DELETE, REPLACE, DROP, ALTER, CREATE, TRUNCATE, ATTACH, VACUUM, REINDEX, PRAGMA) are rejected with an explicit not-allowed error. Stacked statements (semicolons) are rejected. Prefer shop_analytics for rankings and yearly revenue.

Pagination: default limit 100, maximum 1000 (error if exceeded), offset default 0. Returns {columns, rows, truncated, row_count, next_offset} or {error, sqlite_error?}. The database is opened read-only; refused mutations leave the file unchanged.

Inputs: sql (required), limit, offset. Returns: object

list_tablesA

List user tables in shop.db with a one-line purpose for each.

Use this first to discover the real schema. Internal SQLite tables such as sqlite_sequence are omitted. Does not invent columns (customers has no country). Returns {tables: [{name, purpose}, ...]}.

Inputs: no named parameters. Returns: object

shop_analyticsA

Shop aggregations without hand-written SQL.

Operations: top_customers_by_spend (name, email, total_amount; default limit 5), customer_most_orders (name, email, order_count), top_products (product_name, units_sold, revenue; default limit 5), top_categories (category, revenue; default limit 3), revenue_by_year (requires year). Spend/units/revenue use quantity * unit_price and exclude cancelled orders. Year is taken from order_date.

Inputs: operation (required), year, limit. Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.5/5.0

    Scored across 4 tools

    Disambiguation4/5

    The schema-discovery tools are clearly separated from the querying/analytics tools. There is some overlap between execute_readonly_sql and shop_analytics, but the descriptions explicitly steer agents toward shop_analytics for rankings and revenue, reducing confusion.

    Naming Consistency4/5

    Three tools follow a clear verb-first snake_case pattern: list_tables, describe_table, execute_readonly_sql. shop_analytics breaks that pattern as a nouny resource name, though it is still understandable and not chaotic.

    Tool Count5/5

    Four tools is a well-scoped set for a read-only shop database. Each tool fills a distinct role: schema discovery, table metadata, raw SQL execution, and prebuilt analytics, with no redundancy.

    Completeness5/5

    For a read-only database exploration server, the workflow is complete: list the tables, describe one, run arbitrary safe SQL, and get common analytics. There are no missing mutations or write operations because the server is explicitly read-only.

    Maintenance

    ActivityStale
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables natural-language sales queries against a SQLite database, generating and executing read-only SQL through a secure MCP server with table listing, schema description, and query execution.
      -
    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables safe, read-only analysis of an online store's SQLite database, providing schema introspection, restricted SELECT queries, and specialized analytics tools through MCP.
      -
    • F
      license
      Not graded
      quality
      C
      maintenance
      This 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.
      -
    • F
      license
      Not graded
      quality
      C
      maintenance
      Enables AI agents to read-only analyze a SQLite e-commerce database, exploring schema and running analytical SQL queries over stdio.
      -