SmartPark Central MCP Server
by AraksyaG
README.md
# SmartPark Central — MCP Server for Confirmed Reservations (Stage 3)
An intelligent assistant for a parking facility, built with **LangChain** and
**LangGraph** on a **Retrieval-Augmented Generation (RAG)** architecture. The bot
answers questions about the garage, looks up live prices / opening hours /
availability, interactively collects reservation details, and protects personal
data with a guardrails layer.
This repository is **Stage 3** of the EPAM final project. It builds on the Stage 1
RAG chatbot and the Stage 2 human-in-the-loop approval flow by adding an **MCP (Model
Context Protocol) server** that records every **approved** reservation to a text file
in the format `Name | Car Number | Reservation Period | Approval Time`. The server is
secured (shared-token auth, input sanitisation, path confinement, atomic locked
writes). When the administrator approves a request, the confirmed booking is written
automatically — via the MCP server or an equivalent direct function call.
---
## Features
| Requirement (brief) | Where it lives |
|---|---|
| RAG chatbot architecture | `graph.py` (LangGraph agent), `vector_store.py`, `tools.py` |
| Vector database for information | **Milvus** (`vector_store.py`; Milvus Lite locally) |
| Static/dynamic data split *(optional bonus)* | Static → Milvus (`knowledge_base.py`); dynamic (prices, hours, availability) → **SQLite/SQLAlchemy** (`dynamic_db.py`) |
| Provide information to users | retriever tool + 3 dynamic-data tools (`tools.py`) |
| Collect user inputs for reservations | `reservation.py` (validated slot-filling) + `submit_reservation` tool |
| Guardrails against sensitive-data exposure | `guardrails.py` (Microsoft Presidio + regex fallback) |
| Evaluation (Recall@K, Precision, latency) | `evaluation/evaluate.py` |
| **Second agent for the administrator** | `admin_agent.py` (compose request + interpret reply) |
| **Escalate reservation to a human** | `tools.py::submit_reservation` → `reservation_store` + `channels` |
| **Send request / receive decision** | `channels.py` (memory/email/REST) + `admin_service.py` (FastAPI) |
| **Maintain agent-to-agent communication** | reservation lifecycle in `reservation_store.py`, status tool |
| **MCP server to write data to file** | `mcp_server.py` (FastMCP) + `recorder.py` (secure core) |
| **Record on approval** (`Name \| Car \| Period \| Time`) | `admin_service.py` approval hook → `recorder`/`mcp_client` |
| **Secure & resistant to unauthorised access** | token auth, input sanitisation, path confinement, file locking |
---
## MCP server (Stage 3)
**MCP (Model Context Protocol)** is an open standard for exposing tools to AI apps.
Our server (`mcp_server.py`, built with the official SDK's `FastMCP`) exposes one tool,
`record_reservation`, that appends a confirmed booking to a text file. The write logic
(`recorder.py`) is shared by the MCP path and a direct function-call fallback.
```bash
poetry run parking-mcp-server # run the MCP server (stdio transport)
```
Flow: administrator approves a request (Stage 2 API) → the approval hook records it →
a line is appended to `data/confirmed_reservations.txt`:
```
Anna Smith | AB12CD | 2026-07-15 09:00 to 18:00 | 2026-07-11T10:00:00+00:00
```
**Security measures**
- **Token auth** — the recorder rejects writes without the correct `MCP_AUTH_TOKEN`.
- **Input sanitisation** — `|`, `\n`, `\r` are stripped so a value can't forge extra
columns/rows (format injection).
- **Path confinement** — the target file must resolve inside the project `data/` dir,
blocking path traversal (`../../etc/...`).
- **Atomic, locked appends** — a thread lock plus an OS advisory file lock prevent
corrupted/interleaved lines under concurrent approvals.
Set `USE_MCP=true` to record through the MCP server (client↔server over stdio);
`USE_MCP=false` (default) uses the equivalent direct call. The full stdio round-trip is
covered by an e2e test (`RUN_MCP_E2E=1 pytest`).
---
## Human-in-the-loop flow (Stage 2)
```
user ──▶ chatbot (agent 1) ──collect details──▶ submit_reservation
│
save PENDING record (reservation_store)
│
notify admin (channel: memory/email/REST)
▼
administrator ──▶ Admin REST API (/pending, /decision)
│
AdminAgent (agent 2) interprets the reply
▼
reservation → APPROVED / REFUSED
│
user ──ask status──▶ chatbot ──check_reservation_status──▶ reports outcome
```
### Running the admin API
```bash
poetry run parking-admin-api # serves http://localhost:8001
```
Example (the admin approves a request):
```bash
TOKEN=change-me-admin-token # set ADMIN_API_TOKEN in .env for real use
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8001/pending
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"reply":"approve, looks fine","admin":"robert"}' \
http://localhost:8001/reservations/SP-ABC123/decision
```
The `reply` field can be free text ("yes, that's fine" / "no, we're full") — the
**AdminAgent** interprets it into an `approved` / `refused` decision.
---
## Architecture
```
┌──────────────────────────── LangGraph agent ───────────────────────────┐
user ──▶ query_or_respond ──(tool needed?)──▶ tools ──▶ generate ──▶ answer ──▶ user
│ (LLM decides) │ │ (grounded + PII-redacted)
│ │
└── no tool: answer directly ├─ search_parking_info ─▶ Milvus (static KB)
(e.g. next reservation Q) ├─ get_parking_prices ─┐
├─ get_working_hours ├▶ SQLite (dynamic)
├─ check_availability ─┘
└─ submit_reservation ─▶ validated request
```
- **Static knowledge** (general info, location, rules, booking process, FAQ) is
embedded with `text-embedding-3-small` and stored in **Milvus**.
- **Dynamic data** (prices, hours, live availability) lives in **SQLite** and is
queried exactly — never embedded, never hallucinated.
- The **LLM** (Azure OpenAI `gpt-4o-mini`) chooses which tool to call, then writes a
grounded answer. Every retrieved chunk and every final answer passes through the
**guardrails** so personal data cannot leak.
---
## Project structure
```
Stage1_RAG_Chatbot/
├── src/parking_chatbot/
│ ├── config.py # settings from .env (pydantic-settings)
│ ├── llm.py # Azure chat + embedding model factories
│ ├── knowledge_base.py # load + chunk static markdown docs
│ ├── vector_store.py # Milvus build/load/retriever
│ ├── dynamic_db.py # SQLite schema, seeding, queries
│ ├── guardrails.py # PII detection + redaction (Presidio / regex)
│ ├── reservation.py # validated reservation model + slot-filling
│ ├── tools.py # LangChain tools bound to the agent
│ ├── graph.py # LangGraph agent (query_or_respond→tools→generate)
│ ├── chatbot.py # high-level Chatbot facade
│ └── cli.py # terminal chat REPL
├── data/
│ ├── static/ # knowledge base (markdown) → vector DB
│ └── dynamic/ # SQLite DB is created here
├── evaluation/ # eval dataset + Recall@K / Precision / MRR / latency
├── scripts/ingest.py # build vector store + seed SQL DB
├── tests/ # pytest suite (offline, LLM mocked)
├── docker-compose.yml # optional full Milvus server
└── .github/workflows/ci.yml
```
---
## Setup
> **Prerequisites:** Python 3.11/3.12, and — for anything that calls the model —
> an Azure OpenAI resource **reachable from the EPAM VPN**.
```bash
# 1. install dependencies (Poetry)
poetry install
# 2. configure credentials
cp .env.example .env
# then edit .env and fill in AZURE_OPENAI_API_KEY / AZURE_OPENAI_ENDPOINT
# 3. build the knowledge base + dynamic DB (needs VPN for embeddings)
poetry run parking-ingest
```
`MILVUS_URI` defaults to a local file (`./milvus_lite.db`) — **Milvus Lite**, so no
server is needed. To use a full Milvus server instead:
```bash
docker compose up -d # starts Milvus on localhost:19530
# set MILVUS_URI=http://localhost:19530 in .env, then re-run parking-ingest
```
---
## Usage
```bash
poetry run parking-chat
```
Example conversation:
```
You: what are your opening hours?
Assistant: SmartPark Central is open 24 hours a day, 7 days a week.
You: how much is daily parking for a normal car?
Assistant: Standard car parking is 24.00 USD per day.
You: I want to book a space
Assistant: Sure! What is your first name?
... (collects first name, last name, car number, period, confirms, submits)
You: what's the manager's phone number?
Assistant: I'm sorry, I can't share staff contact details. Please call our support
hotline on +1-555-0100.
```
---
## Evaluation
```bash
poetry run python evaluation/evaluate.py --k 3
```
Produces `evaluation/results/report.md` and `metrics.json` with **Recall@K**,
**Precision@K**, **MRR** and **latency** (mean / p95). See the report for the
per-question breakdown.
---
## Testing
```bash
poetry run pytest # 27 tests, fully offline (LLM + Milvus mocked)
poetry run pytest --cov=parking_chatbot
```
The suite never contacts Azure or a Milvus server, so it runs in CI without a VPN.
---
## Guardrails / data protection
Personal data (staff phones, existing customer records, licence plates) may exist in
the knowledge base. The guardrails layer redacts it **twice**: once when retrieved
context is serialised for the model, and again on the final answer. Two backends:
- **Presidio** (default): pretrained spaCy NLP model detects `PERSON` etc., plus
pattern recognisers for email / phone / credit card and a custom licence-plate
recogniser.
- **Regex fallback** (`USE_PRESIDIO=false`): no model download; covers email, phone,
credit card and plates. Used in CI.
---
## Tech choices in one line each
- **LangChain + LangGraph** — required by the brief; LangGraph gives an explicit,
inspectable state machine with built-in conversation memory.
- **Milvus** — a production vector DB explicitly recommended by the brief; Milvus
Lite makes local dev/testing zero-setup.
- **SQLite/SQLAlchemy for dynamic data** — exact, cheap-to-update structured answers
where embeddings would be the wrong tool.
- **Azure OpenAI `gpt-4o-mini` + `text-embedding-3-small`** — the models available
through EPAM, matching the course material.
- **Presidio** — the open-source standard for PII detection using pretrained NLP.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing