Skip to main content
Glama
README.md
# medicover-mcp

An [MCP](https://modelcontextprotocol.io) server that lets an AI agent manage appointments on
Medicover's Polish patient portal (`https://online24.medicover.pl/`). It talks directly to the
portal's private JSON API (reverse-engineered from real traffic — see
[`NOTES.md`](./NOTES.md)); it does **not** drive a browser at runtime.

It is built for an **unattended daily routine**: hunt for scarce slots and optionally rebook
an existing appointment to an earlier one — including for linked dependents (e.g. children).

## Tools

| Tool | Purpose |
|------|---------|
| `list_patients` | The account holder + linked dependents, each with a stable `patient_id` (MRN). |
| `list_appointments` | A patient's planned/past appointments (read-only). |
| `search_slots` | Available slots by region + specialty (optionally doctor/clinic/language, date range, and slot-search type e.g. `DiagnosticProcedure`). Each slot reports its `visit_type` (Center/Phone/PhoneAdvice). |
| `book_appointment` | Book a slot for a patient — `patient_id` **required**. |
| `reschedule_appointment` | Move an appointment to another slot; "earlier-only" by default. |
| `list_appointment_filters` | Resolve specialty/doctor/clinic/region **names → IDs** from history. |
| `list_search_filters` | Clinics/doctors/languages bookable for a region + specialty. |

Patient-scoped read tools default to the account holder; the **write** tools require an
explicit `patient_id` so a booking can never land on the wrong person. Nothing else (messaging,
records, prescriptions, payments, cancel-without-rebook) is exposed.

## Authentication (two-phase)

Login uses SMS 2FA, which can't be in an unattended hot path, so:

1. **One-time bootstrap (interactive CLI, not a tool):**
   ```bash
   export MEDICOVER_USERNAME=<your-MRN-or-login>
   export MEDICOVER_PASSWORD=<your-password>   # or omit to be prompted
   uv run medicover-auth login                 # prompts for the SMS code
   ```
   This completes OAuth + 2FA and saves the **refresh token** to a `0600` file (default
   `~/.config/medicover-mcp/credentials.json`; override with `MEDICOVER_STORE_PATH`).
   Username/password are never stored.

2. **Unattended runtime:** the MCP server loads the refresh token and silently refreshes the
   short-lived (~3 min) access token on every run. No password or SMS is ever needed during
   tool calls.

3. **On expiry/revocation:** tools fail with `session expired — run medicover-auth login`;
   they never prompt for an SMS through a tool call.

> **Token-lifetime decision:** the in-MCP `request_sms_code`/`submit_sms_code` fallback is
> **not** shipped — see the reasoning in [`NOTES.md`](./NOTES.md#token-lifetime--the-sms-fallback-decision).

Helpers: `uv run medicover-auth status` (shows whether creds exist, no secrets) and
`uv run medicover-auth logout` (deletes them).

## Setup

```bash
uv sync                 # create the venv and install
uv run medicover-auth login
uv run pytest           # run the tests (no network — HTTP is mocked)
```

## MCP client config

Add to your MCP client (e.g. Claude Desktop `claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "medicover": {
      "command": "uv",
      "args": ["--directory", "/abs/path/to/medicover", "run", "medicover-mcp"],
      "env": {
        "MEDICOVER_STORE_PATH": "/home/you/.config/medicover-mcp/credentials.json"
      }
    }
  }
}
```

(Run `medicover-auth login` once on the same machine first so the credential store exists.)

## Environment variables

| Var | Used by | Meaning |
|-----|---------|---------|
| `MEDICOVER_USERNAME` / `MEDICOVER_PASSWORD` | bootstrap only | Login credentials (else prompted). |
| `MEDICOVER_STORE_PATH` | bootstrap + server | Credential file path (default `~/.config/medicover-mcp/credentials.json`). |
| `MEDICOVER_TIMEOUT` | server | HTTP timeout seconds (default 30). |

## Example agent flow (earlier-slot hunt for a child)

1. `list_patients` → find the child's `patient_id`.
2. `list_appointments(patient_id=child, state="Planned")` → note the appointment to improve and its `appointment_id`/date.
3. `list_appointment_filters(patient_id=child)` → get the region id and specialty id.
4. `search_slots(patient_id=child, region_ids=[...], specialty_ids=[...], start_date="today")`.
5. If an earlier slot exists: `reschedule_appointment(patient_id=child, appointment_id=..., booking_string=<slot>, new_slot_date=<slot date>)` (refuses unless strictly earlier).

## Safety

Read-before-write on booking/reschedule (the portal validates the slot at action time);
reschedule never cancels without rebooking and defaults to earlier-only; polite request pacing
with backoff; tokens/credentials are never logged or returned; request/response bodies (which
carry medical data) are not logged.

## Disclaimer

Unofficial, built against an undocumented private API that may change without notice. Use with
your own account and within Medicover's terms. Not affiliated with Medicover.

TDQS

A4.4/5.0

Scored across 7 tools

Disambiguation5/5

Each tool targets a distinct action (booking, listing, searching, rescheduling) on different entities (appointments, patients, filters). There is no ambiguity between them.

Naming Consistency5/5

All tool names follow the consistent verb_noun pattern in snake_case (e.g., book_appointment, list_patients, search_slots). No mixing of styles or unconventional names.

Tool Count5/5

With 7 tools, the set is well-scoped for an appointment booking domain. Each tool has a clear role, and the count is neither too thin nor too heavy.

Completeness4/5

The set covers the main lifecycle: search, filter, book, list, and reschedule. However, a cancellation tool is missing, which is a minor gap for a complete appointment management workflow.

Maintenance

ActivityStale
ResponsivenessNo issues