A patient-directed MCP server that bridges Claude and Kaiser Permanente's patient portal.
OpenKP defines 28 tools with mostly complete schemas and descriptions. Naming is verb-first and clear (list_*, read_*, download_*, send_*, request_*). Most tools have 50-300 char descriptions and typed parameters with descriptions. However, 9 tools have minimal descriptions (<50 chars): read_lab_result, download_lab_result_pdf, list_appointments, list_medications, list_problems, list_allergies, list_care_team, list_implants, list_upcoming_orders. Error handling is weak, no recovery guidance, no actionable error messages, no confirmation pattern for destructive writes (request_refill, send_message claim preview/confirm but lack explicit error classification). Output schemas are not documented in descriptions. Two write tools (request_refill, send_message) lack proper error handling and recovery guidance.
Download a lab result PDF to disk and return its local path.
Download a message attachment to disk and return its local path. Args: dcs_id: The `dcs_id` field from a `read_message` attachment. file_extension: Kaiser's extension marker (e.g. "PDF", "JPG"). Pass through from the attachment metadata. display_name: Optional override for the saved filename. If omitted, Kaiser's display name is used. organization_id: Cross-region attachment marker. Empty string is the common same-region case. Saved under `~/.openkp/downloads/` (same directory as lab PDFs). Returns a dict shaped like `MessageAttachmentDownload`, with `status` being: - "downloaded" — saved, `path` holds the local filesystem path. - "error" — `reason` holds a short explanation. The bytes are NOT returned through MCP — too big for Claude's context. Hand the path to a separate PDF / image reading tool, or open locally.
Download an after-visit summary PDF to disk and return its local path.
Fetch detailed instructions for an upcoming order.
Return the patient's profile: demographics, contact info, insurance plans. Source: Kaiser's `/mycare/v1.0/user` endpoint, called with the pharmacy consumer identity. See ADR-006 for why. Returns a dict shaped like the `Profile` pydantic model in `openkp.scrapers.profile`, including PCP (from `CareTeam/Load`) and `emergency_contacts` (from `Demographics/Relationships/GetRelationshipList`, which also covers DPOAHC healthcare agents). Either nested fetch failing leaves its slot null / empty rather than failing the whole call. See `docs/research/endpoints/profile.md` and `emergency_contacts.md`.
9 tools have minimal descriptions (<20 chars): read_lab_result, download_lab_result_pdf, list_appointments, list_medications, list_problems, list_allergies, list_care_team, list_implants, list_upcoming_orders, list_message_recipients, list_message_topics. LLMs cannot determine when to select these tools or what they return.
Write tools (request_refill, send_message) claim preview/confirm pattern but lack explicit error classification, recovery guidance, and actionable error messages. If confirm=False fails, the LLM has no guidance on what to do next.
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | D | 55 | <=2025-11-25 | v2 |
List the patient's portal or third-party access history.
List the patient's drug, food, and environmental allergies.
List upcoming and in-progress visits.
List the patient's care team members (primary care physician, specialists, etc.).
List the patient's implanted medical devices.
List recent lab result orders (newest first, LAB type by default). Args: search: Optional search string. Kaiser matches against order names and (empirically) other fields. Empty = no filter. limit: Max orders to return. Clamped to 200 (Kaiser's ceiling). include_all_types: If False (default), only LAB-type results. If True, also include IMAGING (radiology, ECG, cardiac device checks) and OTHER (pathology, transcriptions). Returns a list of summaries shaped like the `LabResult` pydantic model in `openkp.scrapers.labs`. The `order_key` is the handle you pass to `read_lab_result` or `download_lab_result_pdf` for a specific result.
List the patient's current and recent medications.
List available message recipients (providers, departments).
List available message topics for categorizing messages.
List message threads from the Kaiser message center. Args: folder: Which folder to list. One of "inbox", "archive", "bookmarked", "automated", "appointments". Defaults to "inbox". search: Optional search string. Kaiser matches against subject, body, and sender. before_iso: Pagination cursor. Pass the ISO timestamp of the oldest thread from a previous page to fetch older results. limit: Max threads to return in single-page mode. Clamped to 50 (Kaiser's per-page max). Ignored when `deep_search=True`. deep_search: If True, walk pagination across the full inbox history. Use this when searching for older threads — Kaiser's `searchQuery` only matches within one loaded page (≈50 threads), so a default search misses anything older. Costs one round trip per page until Kaiser reports no more results or `max_pages` is hit. max_pages: Hard cap on pages walked in deep-search mode. Default 30 (≈1500 threads worth of history, sufficient for most accounts). Returns a list of thread summaries, each shaped like the `MessageThread` pydantic model in `openkp.scrapers.messages`. The `id` field is the thread handle you pass to `read_message`. See `docs/research/endpoints/messages.md`.
List completed visits, paginated newest-first.
List the patient's active and historical health problems.
List recent medication refill orders.
List upcoming medication refills and other orders.
List clinical notes and after-visit summaries from past visits.
Smoke test. Returns 'pong' if the MCP server is alive.
Read a single lab result in full, including the narrative report.
Read a single message thread in full, including every message's body. Args: thread_id: The `id` field from a `list_messages` result. Returns a dict shaped like the `MessageThreadDetail` pydantic model. The `messages` array is ordered most-recent-first per Kaiser's convention. Message bodies are HTML-stripped to plain text. Each message's `attachments[]` carries `dcs_id`, `name`, and `file_extension` — pass `dcs_id` to `download_message_attachment` to fetch the binary. Returns `None` if the thread cannot be found. See `docs/research/endpoints/messages.md`.
Read a single refill order in detail.
Request a medication refill. Returns a preview on first call; pass confirm=True to commit.
Send a message to a Kaiser provider or department. Returns a preview on first call; pass confirm=True to commit.
Verify end-to-end auth: can we reach an authenticated Kaiser endpoint? Triggers the full auth path on first call: silent session refresh if the persistent profile is still good, otherwise a visible Chromium window for interactive login (including any MFA). Subsequent calls are silent. Returns a summary of the probe, never PHI. Use `get_profile` (when it lands) for actual user data.
Return the configured Kaiser username and data directory. Does NOT return the password.
Output schemas are not documented in tool descriptions. LLMs cannot plan downstream tool calls or extract required fields (e.g., order_key from list_lab_results) without seeing the response structure.
No recovery guidance in error responses. If session_check fails with 'error: Exception', the LLM has no actionable next step. Errors should suggest retry, user action, or alternative tools.
Pagination parameters (before_iso, max_pages) lack clear documentation of cursor format and stopping conditions. LLMs may misuse pagination and exhaust API quotas.