Skip to main content
Glama
README.md
# lft-reader-mcp

An [MCP](https://modelcontextprotocol.io) server that lets an AI assistant read
COVID-19 lateral flow tests from photographs.

It exposes a deployed computer-vision pipeline — YOLOv8-OBB for oriented
cassette detection, MobileNetV3-Small for classification, and a classical
OpenCV line counter for corroboration — as two tools any MCP client can call.

Research and demonstration use only. **Not a medical device.**

---

## Why this exists

The model already runs as a FastAPI service on Google Cloud Run, which means a
human can upload a photo and read the verdict. This server closes the last gap:
it lets an AI assistant do the same thing as part of a larger task, without a
person copying files between windows.

```
Claude / Cursor  ──MCP──►  this server  ──HTTPS──►  Cloud Run
                                                     ├── YOLOv8-OBB   (detect)
                                                     ├── MobileNetV3  (classify)
                                                     └── OpenCV       (count lines)
```

No model weights live here. The server is a thin, well-behaved client: it
validates input before spending a network call, gives the cold start enough
room to finish, and returns "no cassette found" as a *result* rather than an
error, because a well-formed request that finds nothing is not a failure.

## Install

**Windows (PowerShell)**
```powershell
py -m pip install -r requirements.txt
py test_server.py
```

**macOS / Linux**
```bash
python3 -m pip install -r requirements.txt
python3 test_server.py
```

`py` is the Windows Python launcher. Use it rather than `python3`, which on
Windows is usually intercepted by a Microsoft Store shortcut.

### SDK versions

Works with both major versions of the Python MCP SDK. Version 2.0 renamed
`FastMCP` to `MCPServer` and `Tool.inputSchema` to `Tool.input_schema`; the
server resolves the import at load time and the tests read either attribute,
so there is no need to pin an old major version. Verified against **mcp 2.2.0**
and **mcp 1.30.0**.

## Wire it into a client

**Claude Desktop, Windows** — `%APPDATA%\Claude\claude_desktop_config.json`.
Backslashes must be doubled inside JSON:

```json
{
  "mcpServers": {
    "lft-reader": {
      "command": "py",
      "args": ["C:\\Users\\YOURNAME\\lft-mcp\\server.py"]
    }
  }
}
```

**Claude Desktop, macOS** — `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "lft-reader": {
      "command": "python3",
      "args": ["/absolute/path/to/lft-mcp/server.py"]
    }
  }
}
```

**Cursor** — the same block in `.cursor/mcp.json` in your project.

Example files for both are in this repo.

Restart the client. Then ask it, in plain language:

> Read the lateral flow test in ~/Desktop/test.jpg

## Tools

### `read_lateral_flow_test(image_path)`

Reads a test from a local image and returns the verdict.

```json
{
  "detected": true,
  "result": "positive",
  "confidence": 0.9811,
  "decision_source": "cnn+lines agree (positive)",
  "detection_confidence": 0.9084,
  "processing_ms": 523.3
}
```

Send a photo of the test **in a scene** — on a table, in a hand. The detector
was trained to find a cassette within a wider frame, so a tight crop of the
cassette alone returns `detected: false` with a note saying so.

### `check_reader_health()`

Reports whether the service is up and its models are loaded. Useful before a
batch, and to tell a service problem apart from a problem with one photo.

## Configuration

| Variable | Default |
|---|---|
| `LFT_API_BASE` | `https://lft-reader-xufdqxj7ja-uc.a.run.app` |

Point it at a local instance:

```powershell
$env:LFT_API_BASE="http://localhost:8080"; py server.py    # Windows
```
```bash
LFT_API_BASE=http://localhost:8080 python3 server.py       # macOS / Linux
```

## Known limitations

- **Cold starts.** The service scales to zero to stay inside the free tier, so
  the first call after ~15 minutes idle takes a few seconds while the models
  load. Warm calls are around 500 ms. The client allows 90 seconds before
  giving up.
- **Pre-cropped images are not detected.** See above — this is a property of
  how the detector was trained, not a bug.
- **`invalid` is the weakest class.** It had only 5 held-out examples and 50
  training images.
- Accuracy figures for the underlying models: detector mAP50 0.993
  (mAP50-95 0.857); classifier 96.3% on 81 held-out images — 60 positive,
  16 negative, 5 invalid, macro-F1 0.898. The classifier number should always
  be read with that class imbalance in mind.

## Tests

```powershell
py test_server.py        # Windows
python3 test_server.py   # macOS / Linux
```

Checks that the server loads, declares both tools with usable descriptions and
schemas, and rejects missing files, unsupported formats and empty files before
making any network call.

## Licence

MIT for this server. It contains no model weights; the underlying models and
service are governed by their own licences (the detector is YOLOv8, AGPL-3.0).