MCP server exposing dummy Indonesian hospital doctor-scheduling tools
Project description
mcp-jadwal-dokter
An MCP (Model Context Protocol) server that exposes dummy hospital doctor-scheduling tools: search doctors, read a schedule, check open slots, and make a booking. It is built to be driven by an LLM agent, and it ships as a pip package that runs over stdio with one command.
The server is deterministic: data plus tools, no model and no API key. That is the
point. An MCP server is the tool layer; the model lives in the client. A separate
Streamlit demo (in demo/) lets an LLM drive these tools in an agentic chat, for
example to find a pediatrician free on Thursday afternoon and book the slot.
This is a portfolio project (project 11 of a larger set). PLAN.md is the full build
plan and design record.
Architecture
flowchart LR
User -->|chat| Streamlit
Streamlit -->|asyncio.run per turn| Agent[Agent loop]
Agent <-->|prompt plus tools / tool calls| LLM[LLM provider<br/>Anthropic or OpenRouter]
Agent <-->|stdio MCP| Server[jadwal-dokter MCP server]
Server --> Tools[search_doctors / get_schedule<br/>check_slot / create_booking]
Tools --> Seed[(doctors.json, read-only)]
Tools --> DB[(SQLite bookings)]
Everything from the MCP server rightward is the published package and needs no key. The LLM and the Streamlit client are the demo, and only they read an API key. Each user turn opens one stdio session to the server, runs the tool-calling loop, and closes it.
Tools
Eight tools, plus resources and a prompt:
| Tool | What it does |
|---|---|
get_today() |
The server's date and weekday, so an agent resolves relative phrases ("next Thursday") against the server clock. |
search_doctors(specialization?, name?, day?) |
Find doctors by specialization, name substring, and/or practice day. Filters are optional and combine with AND. |
get_schedule(doctor_id) |
A doctor's weekly practice blocks and any leave periods. |
check_slot(doctor_id, date) |
Available 30-minute slots for a doctor on an ISO date. A day off, a leave date, or a full day return an empty list with an explanatory note, not an error. |
create_booking(doctor_id, date, time, patient_name) |
Create a booking. Validates the doctor, practice day, leave, slot, availability, and rejects past dates, with a specific, actionable error when it cannot book. |
cancel_booking(booking_id) |
Cancel a booking; the slot frees again, history is kept as cancelled. |
reschedule_booking(booking_id, new_date, new_time) |
Move a confirmed booking to another slot, keeping its id; the original is untouched on failure. |
list_bookings(doctor_id?, date?, patient_name?, status?) |
Filtered list of bookings, including cancelled ones. |
Resources: jadwal://roster, jadwal://specializations, jadwal://info. Prompt:
book_appointment. Tool descriptions and error messages are available in Indonesian
(default) or English via JADWAL_LANG.
All data is fictional. The bundled example is a made-up hospital ("Rapha Hospital"): 32 doctors across 12 specializations, with varied weekly schedules and deliberate edge cases (a doctor on leave, a fully booked day, a specialization with a single doctor). Bookings persist in SQLite, so a booked slot shows as taken on the next check. There is no connection to any real hospital, patient, or record.
Bring your own roster
Point JADWAL_DATA at a JSON or YAML file (same schema as the bundled example) and the
server schedules against your doctors instead:
JADWAL_DATA=/path/to/my-roster.yaml mcp-jadwal-dokter
Copy jadwal_dokter/seed/doctors.example.yaml as a starting point. Each doctor has an
id, name, specialization, weekly_blocks (day in lowercase Indonesian
senin..minggu, start/end as HH:MM; slots are 30 minutes), and optional
leave_periods (inclusive ISO date ranges). A bare list or a {doctors: [...]} wrapper
both work. YAML needs the [yaml] extra (pip install "mcp-jadwal-dokter[yaml]").
Requirements
Python 3.10 or newer.
Install and run the server
During development, from this directory:
python -m venv .venv
.venv/Scripts/python -m pip install -e . # on macOS/Linux: .venv/bin/python
Run the server over stdio:
mcp-jadwal-dokter
The server speaks JSON-RPC on stdio and logs to stderr, so it is meant to be launched by an MCP client rather than used directly in a terminal.
Once published (see PLAN.md, milestone M3), it will run without a manual install:
uvx mcp-jadwal-dokter
Use from Claude Desktop
Add the server to claude_desktop_config.json:
{
"mcpServers": {
"jadwal-dokter": {
"command": "uvx",
"args": ["mcp-jadwal-dokter"]
}
}
}
For a local editable checkout instead of a published package, point the command at the
venv's mcp-jadwal-dokter (or python -m jadwal_dokter.server) and set JADWAL_DB if
you want the bookings file in a specific place.
Run the demo (agent chat)
The demo is an optional extra with its own dependencies and an API key:
.venv/Scripts/python -m pip install -e ".[demo]"
copy .env.example .env # then set your key, see below
.venv/Scripts/python -m streamlit run demo/streamlit_app.py
Set one key in .env: OPENROUTER_API_KEY (with JADWAL_PROVIDER=openai) or
ANTHROPIC_API_KEY (with JADWAL_PROVIDER=anthropic). Then click "Jalankan skenario
contoh" and watch the agent chain search_doctors -> check_slot -> create_booking, with
every call shown in the tool-call log.
A captured end-to-end run of that scenario is in SAMPLE_RUN.md.
Full stack (Vue console + chat)
Beyond the Streamlit demo there is a richer web app: a FastAPI backend (backend/) that
holds the key and drives the MCP server, and a Vue frontend (frontend/) with an agent
Chat and an MCP Console (roster, schedules, bookings, a tool-invoke panel, metrics, and a
tool-call history). See docs/design-system.md and frontend/README.md.
Run the three processes (the MCP server over HTTP, the backend, and the Vite dev server):
# 1. MCP server in HTTP mode
JADWAL_TRANSPORT=http JADWAL_API_KEY=dev-key mcp-jadwal-dokter
# 2. backend (points at the MCP server; holds the provider key from .env)
JADWAL_MCP_URL=http://127.0.0.1:8000/mcp JADWAL_API_KEY=dev-key \
.venv/Scripts/python -m backend.app
# 3. frontend dev server (proxies /api to the backend)
cd frontend && npm install && npm run dev # http://localhost:5173
In production, npm run build emits frontend/dist, which the backend serves from the
same origin, so it is one server. When hosting, set JADWAL_BACKEND_KEY and
JADWAL_BACKEND_RATE_LIMIT to gate /api, and JADWAL_CORS_ORIGINS for your domain.
Note: the backend has no per-user auth; it is a single-tenant demo (see
docs/decisions/).
Note on free models: OpenRouter :free models are rate limited per upstream and can
return 429. The app handles this with retries and a clean error message. For a smooth
run, add a small OpenRouter credit, or use an Anthropic key.
Tool design decisions
The tools are the product here, so the design choices are deliberate:
- Verb-noun names, schema from types.
search_doctors,get_schedule,check_slot,create_booking. Typed parameters become the JSON input schema and docstrings become the descriptions, so the model can pick and fill a tool from the schema alone. - Error messages written for the model. A failed booking says what is wrong and what to do next ("slot 14:00 sudah dibooking; slot tersedia: 15:00, 15:30"), not "400". The agent recovers instead of giving up.
- Compact structured returns. Each tool returns a small typed object, not a prose paragraph. Fewer tokens, and the model reads fields reliably.
- Unavailable is an answer, not an error. A day off, a leave date, or a full day
come back from
check_slotas an empty slot list with a note, so the agent can reason and try another day. Only genuinely bad input (unknown id, malformed date) raises. - Dummy but persistent bookings. SQLite so a booking then shows the slot taken, with some slots pre-seeded full. State without a real backend.
- The LLM is in the client, not the server. The server is data plus tools, so it installs, runs, and tests with no key. That is what an MCP server is, and it is what makes the package broadly useful.
Configuration
| Variable | Purpose | Default |
|---|---|---|
JADWAL_DATA |
Path to a custom roster (JSON/YAML). | bundled example |
JADWAL_DB |
Path to the SQLite bookings file. | bookings.db |
JADWAL_LANG |
Tool/error language: id or en. |
id |
JADWAL_TZ |
IANA timezone for dates and get_today. |
Asia/Jakarta |
JADWAL_TODAY |
Pin "today" (ISO date) for reproducible runs. | real clock |
JADWAL_TRANSPORT |
stdio or http (Streamable HTTP at /mcp). |
stdio |
JADWAL_HTTP_HOST / JADWAL_HTTP_PORT |
Bind address for HTTP mode. | 127.0.0.1 / 8000 |
JADWAL_API_KEY |
HTTP mode: require this key (Bearer/X-API-Key). |
none (no auth) |
JADWAL_RATE_LIMIT |
HTTP mode: max requests/min per client IP. | 0 (off) |
JADWAL_PROVIDER |
Demo only: openai or anthropic. |
openai |
JADWAL_MODEL |
Demo only: override the model id. | a free OpenRouter model |
The server core needs no API key. The JADWAL_* provider variables and the API keys in
.env.example are read only by the demo.
Development
.venv/Scripts/python -m pip install -e ".[dev]"
.venv/Scripts/python -m pytest
The suite is deterministic and needs no model or key: the server tools are pure, and the agent-loop tests drive the real MCP server with a stub provider. Each test runs against an isolated temporary bookings database.
There is also a rerunnable, model-driven eval (python -m eval.run) that runs scenarios
through the real agent and checks final state. It needs a working model and reports
free-tier 429s as skips.
Status
- Server core and tools: done, tested.
- Streamlit agent demo: done, tested; full scenario verified end to end.
- PyPI publish, CI, and a recorded demo: planned (see
PLAN.md).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_jadwal_dokter-0.2.0.tar.gz.
File metadata
- Download URL: mcp_jadwal_dokter-0.2.0.tar.gz
- Upload date:
- Size: 47.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c48ef9155b2d3c0a290844add364b6ea4f38899538a5c84d76428ab7821fb05f
|
|
| MD5 |
a8172fde86cbe031615d71051a08b008
|
|
| BLAKE2b-256 |
e08d066001daa9a5849e0188323e40cf64b10a52aaa5bb88c9d70f27853dd752
|
File details
Details for the file mcp_jadwal_dokter-0.2.0-py3-none-any.whl.
File metadata
- Download URL: mcp_jadwal_dokter-0.2.0-py3-none-any.whl
- Upload date:
- Size: 33.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cc0b934bb2280b3d0d9aec12e5100587904a28b57b48851fd7ed5c09798b22e6
|
|
| MD5 |
19ce0f6cf3724af617b6068c66853710
|
|
| BLAKE2b-256 |
06dd6e62a9c870630c0ea9499a13281bfe7de87bc6be4792740d88907cdd8662
|