Healthcare Appointment Scheduling MCP Server
README.md
# Healthcare Appointment Scheduling MCP Server
Local **mock** Model Context Protocol (MCP) server for healthcare appointment scheduling. Designed for development and testing with **Retell AI**.
All data is stored in local JSON files. There are **no** real EHR, calendar, or database integrations.
## Features
- Official `@modelcontextprotocol/sdk` with **Streamable HTTP** transport
- Zod-validated tool inputs
- Persistent local JSON storage (`data/`)
- Health endpoint for uptime checks
- Structured JSON tool responses for voice agents
- Request/response logging for debugging Retell tool calls
## Requirements
- Node.js **20+**
- npm 9+
## Installation
```bash
cd mcp
cp .env.example .env
npm install
```
## Run locally
Development (auto-reload):
```bash
npm run dev
```
Production build:
```bash
npm run build
npm start
```
Server URLs (default):
| Endpoint | URL |
|----------|-----|
| Health | `http://localhost:3000/health` |
| MCP | `http://localhost:3000/mcp` |
Health response:
```json
{
"status": "ok",
"service": "healthcare-mcp-server"
}
```
## Environment variables
Copy `.env.example` to `.env`:
```env
PORT=3000
HOST=0.0.0.0
```
- `PORT` — HTTP port
- `HOST` — bind address (`0.0.0.0` required when exposing via ngrok / Cloudflare Tunnel)
## Expose with ngrok
Retell AI needs a public HTTPS URL.
1. Start the MCP server locally (`npm run dev`).
2. In another terminal:
```bash
ngrok http 3000
```
3. Copy the HTTPS forwarding URL, for example:
```text
https://abc123.ngrok-free.app
```
4. MCP endpoint for Retell:
```text
https://abc123.ngrok-free.app/mcp
```
### Cloudflare Tunnel (alternative)
```bash
cloudflared tunnel --url http://localhost:3000
```
Use the generated HTTPS URL + `/mcp`.
## Connect to Retell AI
1. Open your Retell AI agent / MCP settings.
2. Add a custom MCP server URL pointing to:
```text
https://<your-public-host>/mcp
```
3. Save and refresh tools so Retell can call `tools/list`.
4. Confirm the agent can see all eight tools listed below.
5. Place a test call and watch local server logs for `[MCP] TOOL CALL` / `TOOL RESPONSE`.
This server uses Streamable HTTP with session IDs (`Mcp-Session-Id`) and JSON responses (`enableJsonResponse: true`), which works with standard MCP HTTP clients.
## Available MCP tools
| Tool | Purpose |
|------|---------|
| `search_patients` | Find patients by first/last name, DOB, phone, or email |
| `create_patient` | Create a patient (with duplicate checks) |
| `get_providers` | List active providers (optional specialty/name filter) |
| `get_appointment_types` | List active appointment types / durations |
| `get_available_slots` | List open slots for provider + type + date |
| `get_appointments` | List a patient's future scheduled appointments by `patient_id` |
| `create_appointment` | Book an appointment (prevents double booking) |
| `update_appointment` | Reschedule / update an appointment |
| `cancel_appointment` | Soft-cancel (status=`cancelled`, record kept) |
| `request_prescription_refill` | Request a prescription refill for a patient |
## Example inputs and outputs
### `search_patients`
Input:
```json
{
"first_name": "John",
"last_name": "Doe"
}
```
Output:
```json
{
"success": true,
"count": 1,
"patients": [
{
"id": "patient_001",
"first_name": "John",
"last_name": "Doe",
"full_name": "John Doe",
"date_of_birth": "1990-01-15",
"phone_number": "5551234567",
"email": "john.doe@example.com"
}
]
}
```
### `create_patient`
Input:
```json
{
"first_name": "Alex",
"last_name": "Rivera",
"date_of_birth": "1993-04-02",
"phone_number": "5559998888",
"email": "alex.rivera@example.com"
}
```
### `get_available_slots`
Input:
```json
{
"provider_id": "provider_001",
"appointment_type_id": "appt_type_002",
"date": "2026-09-10"
}
```
Notes:
- Provider hours: **Monday–Friday 09:00–17:00**
- Duration comes from the appointment type
- Existing **scheduled** appointments are excluded
### `get_appointments`
Input:
```json
{
"patient_id": "patient_001"
}
```
Output:
```json
{
"success": true,
"patient_id": "patient_001",
"count": 1,
"appointments": [
{
"id": "appointment_001",
"patient_id": "patient_001",
"provider_id": "provider_001",
"appointment_type_id": "appt_type_002",
"date": "2026-09-10",
"start_time": "10:00",
"end_time": "10:15",
"status": "scheduled",
"notes": "Follow-up for blood pressure check"
}
]
}
```
Past and cancelled appointments are excluded.
### `create_appointment`
Input:
```json
{
"patient_id": "patient_001",
"provider_id": "provider_001",
"appointment_type_id": "appt_type_002",
"date": "2026-09-10",
"start_time": "09:00",
"notes": "Blood pressure follow-up"
}
```
### Error response shape
```json
{
"success": false,
"error": {
"code": "SLOT_NOT_AVAILABLE",
"message": "The requested appointment slot is not available..."
}
}
```
Common codes: `PATIENT_NOT_FOUND`, `PATIENT_ALREADY_EXISTS`, `PROVIDER_NOT_FOUND`, `APPOINTMENT_TYPE_NOT_FOUND`, `APPOINTMENT_NOT_FOUND`, `SLOT_NOT_AVAILABLE`, `INVALID_DATE`, `INVALID_INPUT`.
## JSON data files
Located under `data/`:
| File | Contents |
|------|----------|
| `patients.json` | Patient demographics |
| `providers.json` | Providers (active/inactive) |
| `appointment-types.json` | Visit types and durations |
| `appointments.json` | Scheduled / cancelled appointments |
| `prescription-refills.json` | Prescription refill requests |
Behavior:
- Tools read and write these files dynamically
- Missing files are created as `[]`
- Writes use a temp file + rename to reduce corruption risk
- Changes survive server restarts
Seed data includes 12 patients, 7 providers (6 active), 7 appointment types (6 active), and several sample appointments.
## Project structure
```text
src/
index.ts # HTTP server + Streamable HTTP transport
server.ts # MCP server + tool registration
tools/ # MCP tool definitions
services/ # Business logic
utils/ # JSON storage, IDs, dates, responses
types/ # Shared TypeScript types
data/ # Persistent mock JSON data
```
## Inspecting logs (Retell debugging)
When Retell calls a tool, the server prints:
```text
[MCP] Incoming POST /mcp ...
[MCP] TOOL CALL search_patients {...}
[MCP] TOOL RESPONSE search_patients {...}
```
Tips:
1. Keep `npm run dev` in a visible terminal while testing Retell.
2. Confirm `/health` is reachable through the tunnel before configuring Retell.
3. If tools are missing, verify the MCP URL ends with `/mcp`.
4. If sessions fail, ensure the client preserves `Mcp-Session-Id` after `initialize`.
## Manual smoke test (curl)
Initialize:
```bash
curl -s http://localhost:3000/health
```
List tools (after starting a session with an MCP client) is easiest with the SDK client. A quick end-to-end check (server must already be running):
```bash
npm run test:smoke
```
## Troubleshooting
| Issue | Fix |
|-------|-----|
| Port already in use | Change `PORT` in `.env` |
| Retell cannot reach server | Confirm ngrok is running and URL uses `/mcp` |
| Empty tool list | Restart server, re-add MCP URL in Retell, check initialize logs |
| Double booking allowed? | Should not happen — check `appointments.json` status is `scheduled` |
| Weekend slots empty | Expected — only Mon–Fri 09:00–17:00 |
| Duplicate patient created | Duplicate phone or name+DOB returns `PATIENT_ALREADY_EXISTS` |
| Data reset | Re-copy seed JSON from git or restore `data/*.json` |
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues