Skip to main content
Glama
polloter130

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.