MCP-powered support assistant: LLM tool calling over PostgreSQL with role-based access, a confirm-before-write step and full tool-call tracing.
Overview
An AI customer-support assistant for a simulated e-commerce store, built on the . Customers and support staff sign in, ask questions in plain English, and an LLM (via Groq) answers by calling MCP tools that read real records from PostgreSQL: orders, shipments, tickets, returns, refunds and policies. The point of the project is : the model only sees the tools the signed-in user is allowed to use, every action is checked against the user's identity on the server, and nothing is changed until a person clicks a confirmation button. All data is synthetic or anonymized (built from the public Olist Brazilian e-commerce dataset). Refunds and payments are simulated; no real money moves. - (/): customers ask about their orders, deliveries, tickets, returns and refunds; staff ask about cases assigned to them. - : the model can only draft a customer action (create a ticket, reply to a ticket, submit a return). The customer reviews it and presses before anything is written.
README
SupportOps
An AI customer-support assistant for a simulated e-commerce store, built on the Model Context Protocol (MCP). Customers and support staff sign in, ask questions in plain English, and an LLM (via Groq) answers by calling MCP tools that read real records from PostgreSQL: orders, shipments, tickets, returns, refunds and policies.
The point of the project is safe tool use by an LLM: the model only sees the tools the signed-in user is allowed to use, every action is checked against the user’s identity on the server, and nothing is changed until a person clicks a confirmation button.
All data is synthetic or anonymized (built from the public Olist Brazilian e-commerce dataset). Refunds and payments are simulated; no real money moves.
Features
- Chat assistant (
/): customers ask about their orders, deliveries, tickets, returns and refunds; staff ask about cases assigned to them. - Confirm-before-write actions: the model can only draft a customer action (create a ticket, reply to a ticket, submit a return). The customer reviews it and presses Confirm before anything is written.
- Role-based tool access: customers and staff get different tool sets. A customer can only see their own orders and tickets; an agent only sees assigned cases; supervisors have wider scope.
- MCP activity panel: every chat answer shows which tools ran, with what arguments, how long they took, and exactly what data was sent to the model.
- Staff workflows (MCP tools): return review, item receipt, refund proposal, supervisor approval (a different person must approve), simulated refund execution, and ticket resolution checks.
- Editor integration: the same MCP server works from VS Code (Copilot agent mode) or Cursor, with its own login and read-only tools.
- Developer console (
/console): call any read tool, resource or prompt directly to compare with the chat’s answers.
How it works
Browser (chat.html)
│ HttpOnly session cookie + CSRF token
▼
Web host (Starlette, web_host.py) ──── Groq API (LLM, function calling)
│ starts an isolated MCP process per request,
│ passing only the session token
▼
MCP server (mcp_server.py, stdio)
│ resolves the session → customer / agent / supervisor
▼
Service layer (read / write / refund / staff / resolution)
▼
PostgreSQL (25 domain tables + 2 auth tables, Alembic migrations)
- The host discovers the MCP tools allowed for the current session and passes their descriptions and JSON schemas to Groq as function definitions.
- When the model requests a tool, the host checks it against the allowlist and validates the arguments against the schema, then calls it through MCP.
- The MCP server re-checks the session on every call, so a forged or out-of-scope request is refused even if the model tries it.
- Write actions go through a separate confirmation endpoint, never through the model directly.
Tech stack
| Area | Tools |
|---|---|
| Language | Python 3.11+ |
| AI | Groq Chat Completions (openai/gpt-oss-120b by default), function calling |
| Protocol | Model Context Protocol, Python SDK (mcp), stdio transport |
| Web | Starlette, Uvicorn, plain HTML/CSS/JavaScript |
| Database | PostgreSQL, SQLAlchemy 2, Alembic, psycopg 3 |
| Validation | jsonschema |
| Tooling | uv |
MCP capabilities
Tools (25)
| Who | Tools |
|---|---|
| Public | search_catalog, get_policy |
| Customer (read) | list_my_orders, list_my_tickets, lookup_order, track_shipment, lookup_ticket, get_return_status, get_refund_status, assess_return, assess_refund |
| Customer (write, confirmed) | create_ticket, add_ticket_message, submit_return |
| Staff | list_returns_for_review, get_return_review, review_return, receive_return, propose_refund, list_refund_proposals, get_refund_proposal, review_refund, execute_refund, assess_ticket_resolution, resolve_ticket |
Resources: supportops://policies/returns-and-refunds, supportops://policies/delivery, supportops://simulation/info
Prompts: summarize_support_case, draft_customer_response, review_resolution_readiness
Project structure
supportops/
├── src/supportops/
│ ├── web_host.py # Starlette app: login, chat, console, confirmations
│ ├── chat_service.py # LLM ↔ MCP tool loop, per-session history
│ ├── groq_provider.py # Groq API client
│ ├── customer_actions.py # Draft → confirm flow for customer writes
│ ├── mcp_server.py # MCP tools, resources and prompts
│ ├── mcp_bridge.py # Runs the MCP server over stdio for the host
│ ├── ide_mcp.py # Login + read-only MCP server for VS Code / Cursor
│ ├── auth_service.py # Passwords, sessions, lockout
│ ├── session_identity.py # Maps a session to a customer or staff member
│ ├── read_service.py # Orders, shipments, tickets, catalog
│ ├── write_service.py # Tickets, messages, returns
│ ├── staff_service.py # Return review and receipt
│ ├── refund_service.py # Refund proposal, approval, execution
│ ├── ticket_resolution_service.py
│ ├── context_service.py # Policies and simulation clock
│ ├── db.py # Database connection (reads .env)
│ ├── chat.html # Chat UI
│ └── web_console.html # Developer console UI
├── migrations/ # Alembic schema migrations
├── data/seed/ # 25 CSV files of seed data
├── docs/ # Table list and data dictionary
├── scripts/
│ ├── manage_login.py # Create / reset / disable logins
│ └── verify_schema.py # Check database matches migrations and CSVs
├── tests/ # Offline tests on disposable SQLite databases
├── examples/ # VS Code and Cursor MCP configs
├── alembic.ini
└── pyproject.toml
Getting started
Prerequisites
- Python 3.11+ and uv
- PostgreSQL 14+ (local)
- A free Groq API key from (needed for chat only)
1. Install
git clone https://github.com//supportops.git
cd supportops
uv sync
2. Configure
cp .env.example .env
Fill in your PostgreSQL connection details and Groq key in .env. Create an empty database first, for example createdb supportops.
3. Create the schema and load the seed data
The seed CSVs match the first migration, and later migrations fill in derived columns. So create the base schema, load the CSVs, then apply the rest:
uv run alembic upgrade b25f0929a001
set -a; source .env; set +a
export PGPASSWORD="$POSTGRES_PASSWORD"
for t in dataset_batches customers product_categories products sellers orders \
order_items payments shipments shipment_items shipment_events staff_users \
policy_versions tickets ticket_messages return_requests return_items \
return_events refunds refund_allocations action_requests audit_events \
product_variants seller_offers inventory; do
cols=$(head -1 "data/seed/$t.csv")
psql -h "$POSTGRES_HOST" -p "$POSTGRES_PORT" -U "$POSTGRES_USER" -d "$POSTGRES_DB" \
-v ON_ERROR_STOP=1 -c "\copy $t($cols) FROM 'data/seed/$t.csv' WITH (FORMAT csv, HEADER true)"
done
uv run alembic upgrade head
PYTHONPATH=src uv run python scripts/verify_schema.py
4. Create demo logins
There is no public sign-up. Logins are created locally and linked to existing seed records. You will be asked to choose a password (12–128 characters).
# Customer
PYTHONPATH=src uv run python scripts/manage_login.py create \
--username customer.demo --kind customer \
--person-id fea97ace-a2f8-52d5-bd57-fdd21a2fa218
# Support agent
PYTHONPATH=src uv run python scripts/manage_login.py create \
--username agent.demo --kind staff \
--person-id 22ec2229-836b-56d8-a61e-6e18bf91b7d1
manage_login.py reset and manage_login.py disable are also available; both sign the account out everywhere.
5. Run
PYTHONPATH=src uv run uvicorn supportops.web_host:application \
--factory --host 127.0.0.1 --port 8000
Open ****. Use 127.0.0.1 exactly, not localhost, because the origin check is strict. Run a single worker, since chat history is kept in memory.
Try it
Signed in as customer.demo:
- “What orders do I have?”
- “What is the delivery status of SO-000008?”
- Pick the summarize support case prompt with ticket
TKT-C2630CFC-EF49-4B1B-A06C-621863E75683 - “I want to open a ticket about a damaged item in my order.” The assistant drafts it and you confirm or cancel it.
Signed in as agent.demo (use a private window so both sessions can run at once):
- “Is ticket TKT-C2630CFC-EF49-4B1B-A06C-621863E75683 ready to resolve?”
Expand MCP activity under any answer to see the tool calls behind it.
Use from VS Code or Cursor (optional)
PYTHONPATH=src uv run python -m supportops.ide_mcp login --username customer.demo
Then copy examples/vscode.mcp.json to .vscode/mcp.json, or examples/cursor.mcp.json to .cursor/mcp.json, and start the supportops server from the editor. The editor session is separate from the browser session, lasts eight hours, and exposes read tools only. Sign out with python -m supportops.ide_mcp logout.
Tests
The tests build disposable SQLite databases from the seed CSVs and use a scripted LLM, so they never touch your PostgreSQL database or call Groq. Run them from the project root:
for f in tests/test_*.py; do PYTHONPATH=src:tests uv run python "$f" || break; done
Security design
- Passwords: salted PBKDF2-HMAC-SHA256 (600,000 iterations); the account locks for 5 minutes after 5 failed attempts.
- Sessions: random opaque tokens, stored only as SHA-256 hashes, sent as
HttpOnly,SameSite=Strictcookies that expire after 8 hours. - Requests: exact
Origincheck, CSRF token on authenticated POSTs, 16 KiB body limit, per-worker concurrency limits. - Identity is never taken from the browser or the model. The MCP server looks it up from the session on every call.
- The LLM gets only the tools allowed for the current role. Arguments are validated against JSON schemas, and each question is limited to 6 model rounds and 8 tool calls.
- Tool results and ticket text are treated as untrusted data, so instructions embedded in them are ignored.
- Refunds need a proposal from one staff member and approval from a different supervisor.
Limitations
- Built for local use. Not hardened for public deployment (no distributed rate limiting, single worker).
- Chat history lives in memory and is cleared when the server restarts.
- Each MCP call starts a new subprocess, which favors isolation over speed.
- LLM answers can still be wrong. The MCP activity panel is there so you can check them against the tool results.
Install
This server does not publish a one-line install command.
Open the repository installation guide