SmartPark Reservation MCP Server
by polloter130
README.md
# SmartPark Reservation MCP Server — Stage 3
A real **MCP (Model Context Protocol) server**, built with the official
`mcp` Python SDK, that writes confirmed parking reservations to durable
storage once a human administrator has approved them (Stage 2).
This is a **separate repository/service** from Stages 1 and 2 — Agent 2
calls this server's single tool over the network, the same way any
MCP-compliant client would.
## What it does
Exposes exactly **one MCP tool**: `write_confirmed_reservation`. Once
the administrator (Agent 2) approves a reservation, Agent 2 calls this
tool, which:
1. Validates every field (rejects the `|` delimiter, newlines, null
bytes, and overly long values — these would corrupt the file format
or let a caller inject a fake extra record).
2. Appends one line to `data/confirmed_reservations.txt` in the exact
required format:
```
Name | Car Number | Reservation Period | Approval Time
```
3. Does this atomically and safely under concurrent calls (cross-platform
file locking), and logs the attempt (success or rejection) to an audit
log.
## Architecture
```
Agent 2 (Stage 2 Admin Agent) Agent 3 (this repo)
┌──────────────────────────┐ MCP/HTTP ┌────────────────────────────┐
│ apply_decision(confirmed) │ ────────────▶ │ POST /mcp │
│ │ Bearer token │ BearerAuthMiddleware │
│ integration/ │ required │ RateLimitMiddleware │
│ admin_agent_client.py │ │ MCPServer │
│ -> record_confirmed_ │ │ └─ write_confirmed_ │
│ reservation() │ │ reservation tool │
└──────────────────────────┘ │ └─ validate ─┐ │
│ ▼ │
│ data/confirmed_ │
│ reservations.txt │
│ (file-locked append) │
│ + data/audit.log │
└────────────────────────────┘
```
## Why the official MCP SDK (not just a REST endpoint)
Stage 3 asks for a real MCP server, or a FastAPI stand-in if that's not
feasible. The official `mcp` Python SDK (`MCPServer`, formerly known as
`FastMCP`) turned out to be fully usable here: it builds a standard
streamable-HTTP ASGI app (so it composes with normal Starlette middleware
for auth/rate-limiting) and ships with production security features out
of the box — DNS-rebinding protection, host/origin allowlisting, and
request-body size limits — which is exactly what "secure and resistant to
unauthorized access" calls for. Building a hand-rolled JSON-RPC-over-FastAPI
server would have meant re-implementing a worse version of these same
protections.
## Security measures
| Concern | Mitigation |
|---|---|
| Unauthorized callers | `BearerAuthMiddleware` — every request needs `Authorization: Bearer <MCP_API_KEY>`, compared with `hmac.compare_digest` (no timing side-channel). No key configured → the server generates and prints a random one-time key rather than silently allowing unauthenticated access. |
| Abuse / DoS | `RateLimitMiddleware` — fixed-window limit per client IP (default 30 req/min, configurable). |
| DNS rebinding / host spoofing | The MCP SDK's built-in `TransportSecuritySettings` (enabled by default), with configurable `ALLOWED_HOSTS`/`ALLOWED_ORIGINS` for production. |
| Path traversal | Structurally impossible — the output file path is fixed by server config and is **never** accepted as a tool argument. |
| Record/format injection | Every field is validated to reject the `|` delimiter, newlines, and null bytes before it's ever written. |
| Concurrent-write corruption | Cross-platform atomic file locking (`filelock`) around every append — tested with 20 simultaneous threads writing with zero interleaved/corrupted lines. |
| Oversized/malformed requests | `max_request_body_size` capped at 64KB (reservation payloads are tiny; anything bigger is rejected outright). |
| Traceability without leaking secrets | Every write attempt (success or rejection) is appended to `data/audit.log`, with only a masked token prefix — the real API key is never logged. |
| Reliability | A stuck lock surfaces as a clear, catchable `TimeoutError` after 5s rather than hanging the server; invalid input raises a specific `InvalidReservationField` the MCP layer turns into a normal tool-error result rather than crashing the process. |
## Project structure
```
parking_mcp_server/
├── app/
│ ├── config.py # .env-driven configuration
│ ├── reservation_writer.py # field validation + atomic file append
│ ├── audit_log.py # append-only audit trail
│ ├── security.py # BearerAuthMiddleware, RateLimitMiddleware
│ └── server.py # MCPServer + tool + ASGI wiring
├── integration/
│ ├── admin_agent_client.py # what Stage 2 imports to call this server
│ └── db_patch_example.py # exact diff for Stage 2's decision endpoint
├── tests/ # 22 pytest tests across 4 modules
├── demo.py # end-to-end demo (live server + real MCP client)
├── view_reservations.py # quick CLI viewer for the output file/audit log
├── run_server.sh / run_server.ps1
├── requirements.txt
├── .env.example
└── .github/workflows/ci.yml # runs tests on both ubuntu-latest and windows-latest
```
## Setup
Works identically on **Windows, macOS, and Linux** — pure Python (the
official `mcp` SDK, Starlette/uvicorn, and the cross-platform `filelock`
package; no OS-specific binaries).
```bash
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env
# Set MCP_API_KEY in .env for a stable key -- otherwise a random one-time
# key is generated and printed at startup.
```
### Windows-specific notes
* Use `run_server.ps1` instead of `run_server.sh`.
* `filelock` uses `msvcrt` under the hood on Windows automatically — no
extra configuration needed for the concurrency-safety guarantees.
* CI runs the full test suite on `windows-latest` as well as
`ubuntu-latest` (see `.github/workflows/ci.yml`).
## Usage
**Run the demo (starts its own server, no setup needed):**
```bash
PYTHONPATH=. python demo.py
```
**Run the real server:**
```bash
./run_server.sh # Linux/macOS
.\run_server.ps1 # Windows
```
It prints a generated `MCP_API_KEY` on first run if you haven't set one —
copy it into your client's `Authorization: Bearer <key>` header (or into
Stage 2's `.env` as `MCP_API_KEY`, see Integration below).
**View what's been written:**
```bash
PYTHONPATH=. python view_reservations.py
```
**Run tests:**
```bash
PYTHONPATH=. python -m pytest tests/ -v
```
## Integration with Agent 2 (Stage 2)
* `integration/admin_agent_client.py` — the client Stage 2 imports
(`record_confirmed_reservation(name, car_number, reservation_period, approval_time)`),
which opens a real MCP client session, authenticates, and calls the tool.
* `integration/db_patch_example.py` — the exact before/after diff for
Stage 2's `POST /requests/{id}/decision` endpoint: once a reservation is
marked `"confirmed"`, it now also calls `record_confirmed_reservation(...)`
so the approval is durably recorded here, not just in Stage 2's own
request-tracking table.
* Stage 2's `.env` needs: `MCP_SERVER_URL=http://127.0.0.1:8002/mcp` and
`MCP_API_KEY=<the same key configured on this server>`.
## Notes / limitations (Stage 3 scope)
* Rate limiting and the audit log are in-memory/local-file, appropriate
for a single-process deployment; a multi-instance deployment would move
the rate limiter to a shared store (e.g. Redis) and the audit log to a
centralized logging system.
* `MCP_API_KEY` is a single shared secret rather than per-client
credentials — sufficient for a single trusted caller (Agent 2), but a
multi-tenant deployment would want per-client tokens or the MCP SDK's
full OAuth 2.1 support (`auth_server_provider`/`AuthSettings`, not used
here since it requires standing up a separate OAuth issuer).
* The reservations file is a flat text file, as specified; a
higher-volume production deployment would likely write to a database
instead, behind the same tool interface.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues