healthcare
by tgdn
README.md
# healthcare-mcp
An MCP server for finding doctors in Romania: by county, speciality, language, name and weekly availability.
## Add it to Claude Code
Pick one.
### Hosted: a URL and a token
```sh
claude mcp add --transport http healthcare https://wonderful.xxxx0.top/mcp \
--header "Authorization: Bearer <token>"
```
Other MCP clients need the same two things: the URL `https://wonderful.xxxx0.top/mcp` and the header `Authorization: Bearer <token>`.
### Local: stdio, with only Docker installed
```sh
git clone https://github.com/tgdn/healthcare-mcp.git && cd healthcare-mcp
docker compose up --no-start # downloads and builds once, starts nothing
claude mcp add healthcare -- docker compose -f "$PWD/docker-compose.yml" run --rm -T stdio
```
Claude Code starts the server when it needs it, along with a Postgres container that imports the doctors on first use. `docker compose down` stops Postgres; `docker compose down -v` also deletes the data.
Then ask Claude something like *"I live in Mangalia and need a cardiologist who speaks German on Saturday"*.
## Tools
- **ListSpecialities** and **ListLanguages** return what the doctors offer, with counts, optionally for one county.
- **SearchDoctor** takes `county`, `speciality`, `first_name`, `last_name`, `language`, `availability` (`day`, `time`) and `limit`, and returns the best rated doctors first.
- A county is required unless searching by name. Without one, the tool tells the model to ask the user rather than guess.
- Speciality and language match case-insensitively; an unknown value returns the known ones.
- Names ignore case and accents. A misspelt name ("Dumitresku") returns the closest spellings, with a note.
- Availability is regular weekly working hours, not free appointment slots.
- With no match in the county it searches the neighbouring counties and says so. With no match at all, `matches_without` counts the results if each filter were dropped.
## Design decisions
### Why Postgres?
The prompt specifies a production-scale dataset with millions of records, where Postgres is standard practice:
- **Trigram search:** GIN trigram indexes make fuzzy `ILIKE` searches fast and avoid full table scans.
- **Simple ops:** Using Postgres for both data storage and search avoids managing extra sync pipelines.
- **Future scale:** B-tree indexes handle simple filters, while table partitioning and read replicas scale globally if needed.
### Alternatives
- **Elasticsearch:** Overkill for basic text matching. Only worth the operational overhead for complex relevance ranking or heavy faceting.
- **SQLite:** Fine for the small sample, but fails under concurrent writes in production.
### Why MCP over REST?
A REST API forces you to predict every endpoint in advance. MCP gives the model typed tools to compose on the fly, leveraging the existing chat surface instead of building a new UI.
### Trade-offs
REST is still easier to cache, version, and rate-limit for non-agent software. If programmatic tools needed access, I would expose the search logic via a light REST API alongside `/mcp`. Same underlying engine, two different interfaces.
## Local HTTP server
```sh
docker compose up -d
```
Serves `http://127.0.0.1:3000/mcp` with the token `local-dev-token`. Put `MCP_AUTH_TOKEN` or `MCP_PORT` in a `.env` file to change them (see `.env.example`).
## Deploy
On a hangar host, which runs Traefik and Postgres with a proxied wildcard for `*.xxxx0.top`, one command from your laptop deploys the committed code:
```sh
./deploy/deploy.sh deploy@<host> wonderful.xxxx0.top
```
The first run creates the database with hangar's `new-project` and generates the bearer token into `/opt/healthcare/.env`; later runs keep both and redeploy. The script prints how to read the token. `SSH_KEY=<path>` picks the SSH key.
Every request to `/mcp` is logged as one JSON line with the client IP, user agent, MCP client, method and tool (not the arguments):
```sh
ssh deploy@<host> docker logs -f healthcare-mcp-mcp-1
```
Hangar's Dozzle shows the same logs in the browser. `deploy/docker-compose.prod.yml` works on any host with Traefik on a shared Docker network; its header lists the variables.
## Development
Needs Node 22.9 or later.
```sh
npm install
cp .env.example .env
npm run db:up # Postgres on 127.0.0.1:5432, seeded
npm test
npm run dev # http://127.0.0.1:3000/mcp, reloads on change; stop the mcp container first if it runs
```
To mount the endpoint in an existing Hono API:
```ts
import { createApp } from "./src/app.js";
import { createDb } from "./src/db.js";
api.route("/", createApp({ db: createDb(process.env.DATABASE_URL!), authToken: process.env.MCP_AUTH_TOKEN! }));
```
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues