Helios Field Service
README.md
# Helios Field Service — Production MCP Server & Client
Module 4 Lab — Model Context Protocol
Turns the Helios Robotics parts, inventory and RMA systems into an MCP capability
that any MCP-aware client can plug into. Built with [FastMCP](https://gofastmcp.com)
3.x against the [MCP specification](https://modelcontextprotocol.io).
| Requirement | Implementation |
|---|---|
| ≥3 tools | **4** — `search_parts`, `get_inventory`, `analyse_failure`, `create_rma` |
| ≥2 resources | **3** — `helios://catalog/summary` + two URI templates |
| ≥1 prompt | `diagnose_fault(fault_code, sku, site)` |
| Client discovers + invokes each | `client.py` — lists all three, invokes all three |
| Transport + justification | stdio (default), HTTP supported — [rationale](docs/architecture.md#transport-choice) |
| Client-side safety | **Both** — elicitation on the write, roots on the client |
| Security design summary | [docs/security.md](docs/security.md) |
| Error handling | Invalid input, unknown record, **and** unreachable backing store |
Docs: [architecture + transport](docs/architecture.md) ·
[tools/resources/prompts](docs/capabilities.md) ·
[security](docs/security.md) · logs: [`logs/`](logs/)
## Quick start
```bash
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python seed_data.py
python client.py # spawns the server over stdio and runs the full demo
```
No API key, no model, no cost — MCP is a protocol, and the client invokes the
server directly.
### Other runs
```bash
AUTO_APPROVE=1 python client.py # non-interactive (CI, log capture)
SIMULATE_DB_OUTAGE=1 python client.py # backing data source unreachable
MCP_TRANSPORT=http python server.py # serve on 127.0.0.1:8000
MCP_TRANSPORT=http python client.py # ...and connect to it
```
### Use it from any MCP host
```json
{
"mcpServers": {
"helios-field-service": {
"command": "python",
"args": ["/absolute/path/to/helios-mcp/server.py"]
}
}
}
```
That is the point of the exercise — built once, usable by every MCP-aware client.
## What the demo shows
`logs/demo.log` — full discovery-and-invocation flow:
```
1. DISCOVERY — tools
• search_parts [read-only] Search the Helios spare parts catalogue...
• get_inventory [read-only] Stock level and lead time for a part...
• analyse_failure [read-only] Correlate a fault code with known issues...
• create_rma [WRITE] Raise a Return Material Authorisation.
1. DISCOVERY — resources
• helios://catalog/summary Catalogue summary
• helios://parts/{part_number} Catalogue entry (template)
• helios://kb/{doc_id} Knowledge base article (template)
1. DISCOVERY — prompts
• diagnose_fault(fault_code, sku, site)
```
The write, gated by elicitation:
```
┌─ SERVER REQUESTS CONFIRMATION ──────────────────────────────────
│ Raise an RMA for 1 x HX2-BMS-03 (HX-200 Battery Management Board rev C)?
│ Serial: HX200-PHX-0442
│ Total value: $1,240.00
└─────────────────────────────────────────────────────────────────
{"created": true, "rma_id": "RMA-00001", "value_usd": 1240.0,
"requested_by": "mahesh.s"}
```
`logs/demo-db-outage.log` — the backing store unreachable. What the client sees:
```
TOOL UNAVAILABLE — Failure analysis is temporarily unavailable.
Quote reference dddc45d0fc07 to support if this persists.
```
What the server logged, on stderr (`logs/server-errors.log`):
```
ERROR [helios-mcp] [dddc45d0fc07] Failure analysis failed:
OperationalError: could not connect to helios-db-prod-01.internal:5432: timeout
```
The hostname and port never cross the protocol boundary. The correlation id is the
bridge between the two.
## Design notes
**Resources and tools are not interchangeable.** `search_parts` finds a part when
you don't know its id; `helios://parts/{pn}` fetches one you already have. Same
data, different access pattern.
**The prompt lives on the server on purpose.** The diagnostic procedure is Helios
domain knowledge, not host logic. Every connecting client gets the same rules —
rule out firmware before condemning hardware, never quote a superseded part —
rather than each reimplementing them and drifting.
**All logging goes to stderr.** On stdio, stdout carries the JSON-RPC frames. A
stray `print()` corrupts the protocol stream; there are none in the server.
**The client controls the server's environment.** A spawned stdio server does not
inherit the parent environment automatically, so `PythonStdioTransport(env=...)`
passes an explicit allow-list rather than handing over the caller's whole shell.
## Layout
```
server.py MCP server: 4 tools, 3 resources, 1 prompt
client.py MCP client: discovery, invocation, elicitation, roots
seed_data.py Creates data/helios.db
docs/
architecture.md Diagrams + transport justification
capabilities.md Every tool, resource and prompt documented
security.md Auth, least privilege, error redaction
logs/
demo.log Successful discovery-and-invocation flow
demo-db-outage.log Backing store unreachable, client view
server-errors.log Server-side detail with correlation ids
```
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues