Skip to main content
Glama
README.md
# ecommerce-mcp-server

An MCP (Model Context Protocol) server exposing e-commerce customer support
tools to LLM agents: product catalog search (hybrid full-text + vector
retrieval with cross-encoder reranking), order status/tracking, cart
management, a FAQ knowledge base, and human escalation via support tickets.

Built on FastAPI + the official `mcp` Python SDK, served over Streamable
HTTP, with API-key auth and per-client rate limiting applied as middleware.
Retrieval runs on Postgres + pgvector -- no separate vector database to run
or pay for. See [`SYSTEM_DESIGN.md`](./SYSTEM_DESIGN.md) for the reasoning
behind that and the retrieval pipeline design (RRF fusion + cross-encoder
rerank -- the same pattern used in the author's "Ledger" RAG project). See
[`HLD_LLD.md`](./HLD_LLD.md) for the full system design -- high-level
component/deployment views and low-level module, data-model, and
state-machine detail across the whole project.

## Architecture

```
Client (LLM agent / chat app)
        │  HTTP + X-API-Key
        ▼
 ApiKeyAuthMiddleware → RateLimitMiddleware → RequestLoggingMiddleware
        │
        ▼
   FastAPI app (src/mcp/server.py)
        │  mounted at /mcp
        ▼
   FastMCP server (src/mcp/registry.py)
        │  tool calls
        ▼
   src/tools/*  →  src/services/*  →  Postgres (products, FAQ)
                          │            + in-memory (cart, orders, tickets)
                          ▼
                  src/rag/* (hybrid search: Postgres full-text search +
                             pgvector, RRF fusion, cross-encoder rerank)
```

Each layer has one job:

| Layer | Responsibility |
|---|---|
| `src/mcp/` | MCP server setup, tool registration, session helpers |
| `src/tools/` | Thin, testable functions with the docstrings/signatures the agent sees as tool schemas |
| `src/services/` | Business logic + data access, written against a small repository-style interface |
| `src/rag/` | Hybrid (Postgres full-text + pgvector) search with cross-encoder reranking |
| `src/db/` | SQLAlchemy async engine/session, ORM models, seed script |
| `src/models/` | Pydantic schemas shared across the app |
| `src/middleware/` | Auth, rate limiting, request logging |
| `src/core/` | Config, logging setup, exceptions, seed data |

**What's Postgres-backed vs. still in-memory, and why:** `products` and
`faq_entries` are real Postgres tables (see `migrations/versions/`) because
those are exactly the two things the RAG layer searches over, and search
quality is the part of this project meant to demonstrate real retrieval
engineering. `cart`, `order`, and `ticket` services are intentionally still
in-memory -- they're written against the same small async interface
(`get_by_id`, `list_all`, ...) the product service used to use, so
Postgres-backing them later is adding a new implementation and swapping an
import, not a redesign. That seam is deliberate, not an oversight; see
`src/services/cart_service.py`'s docstring.

## Tools exposed

| Tool | Description |
|---|---|
| `search_products` | Hybrid search over the catalog (name, description, tags) with category/price/stock filters |
| `get_product_details` | Full details for a single product by ID |
| `check_order_status` | Current status of an order |
| `track_shipment` | Carrier + tracking number + ETA for a shipped order |
| `cancel_order` | Cancel an order still in a cancellable state |
| `request_order_return` | Request a return for a delivered order |
| `view_cart` / `add_to_cart` / `remove_from_cart` / `update_cart_quantity` | Session-scoped cart management |
| `answer_faq` | Hybrid search over the FAQ knowledge base |
| `escalate_to_human` | Create a support ticket for human follow-up |

## Getting started

**With Docker Compose** (Postgres + Redis provisioned for you):

```bash
cp .env.example .env   # edit at minimum: MCP_API_KEYS, GROQ_API_KEY

docker compose up -d postgres redis
docker compose run --rm app alembic upgrade head
docker compose run --rm app python -m src.db.seed
docker compose up app
```

**Without Docker** (Python 3.11+, and a Postgres instance with the
`vector` extension installable -- e.g. `CREATE EXTENSION vector;` once as
a superuser):

```bash
pip install -e ".[dev]"
cp .env.example .env
# point DATABASE_URL at your Postgres instance

alembic upgrade head        # creates products/faq_entries with FTS + HNSW indexes
python -m src.db.seed        # loads src/core/mock_data.py + computes embeddings
python server.py
# -> FastAPI on http://localhost:8000
#    health check:  GET  /health   (no auth required)
#    MCP endpoint:  POST /mcp      (requires X-API-Key header)
```

The first call that touches search (`search_products` / `answer_faq`) will
lazily download the embedding and reranker models
(`sentence-transformers/all-MiniLM-L6-v2` and
`cross-encoder/ms-marco-MiniLM-L-6-v2` by default, both configurable via
`.env`) -- expect a one-time delay on cold start in a fresh environment.
`python -m src.db.seed` also needs these models, since it computes each
row's embedding before writing it.

## Testing

```bash
pytest                          # full suite
pytest tests/unit                # fast, no DB or model downloads
pytest --cov=src --cov-report=term-missing
```

`tests/unit/test_pg_search.py` covers the Reciprocal Rank Fusion logic in
isolation (pure functions, no DB). The retrieval SQL itself (full-text
search, pgvector distance queries, `filter_products`) is covered against a
real Postgres instance rather than mocked -- `tsvector`/`vector` query
behavior isn't meaningfully testable without one. This was verified during
development against a live Postgres 16 + pgvector 0.6 instance: the
migration applied cleanly, the generated `search_vector` columns and HNSW
indexes were created as expected, and `search_products`/`answer_faq`
returned correctly ranked results end-to-end.

## Roadmap / not yet implemented

- Postgres-backed cart/order/ticket services (see the "what's Postgres-backed"
  note above -- same seam the product service used before this pass)
- Redis-backed rate limiting for multi-instance deployments (currently
  in-process token buckets)
- Groq-powered intent routing / query rewriting ahead of the MCP tool layer
- Session hardening (auth token expiry, per-session tool scoping)
- Kubernetes manifests (`deploy/k8s/`) -- Dockerfile + Compose exist,
  k8s is the next step once there's a cluster to target