MCP server exposing CUF health portal as tools for Claude and other MCP clients. Enables downloading medical records, invoices, viewing appointments, exam results, prescriptions, and notifications from the CUF (Serviços de Saúde CUF) healthcare portal.
This MCP server defines 12 tools with consistent naming (verb_noun pattern) and reasonably detailed descriptions. However, several critical gaps prevent a higher score: (1) Input schemas are present but lack formal enum constraints for categorical parameters (e.g., list_appointments 'date_from' should specify ISO 8601 format constraint; list_prescriptions 'years' lacks min/max bounds); (2) Output schemas are documented in descriptions but not formally specified in JSON Schema format, LLMs cannot reliably parse unstructured text descriptions of return types; (3) Error handling is minimal, most tools lack recovery guidance ('If this fails, try X'); (4) No tool annotations (readOnlyHint, idempotentHint) despite all being read-only operations; (5) Several parameter descriptions are thin (e.g., get_patient_info has no input params and minimal doc). The server demonstrates solid fundamentals (clear naming, reasonable descriptions, async pattern) but lacks the polish expected of production-grade tools. Average per-tool score: 62.
Download a specific clinical document PDF by its ID. Args: doc_id: The document ID from list_clinical_documents (e.g. "12345"). Returns the file path where the PDF was saved.
Download a specific invoice PDF by payment number. Args: payment_number: The paymentNumber from list_invoices (e.g. "CCF2026/45291" or "FT FSDR2025/170390"). Returns the file path where the PDF was saved.
Get patient portal notifications. Args: include_read: Whether to include already-read notifications (default: True). Returns a list of dicts with keys: id, name, description, date, message, read, entityType, entityCode.
Get basic patient profile information (name, contacts, identity numbers). Returns a dict with keys: fullName, title, contacts, identity.
Download a prescription PDF by its download_url from list_prescriptions(). Args: download_url: The download_url field from list_prescriptions() output. Returns the file path where the PDF was saved.
Output schemas not formally documented in JSON Schema format. Descriptions state return structure in prose (e.g., 'Returns a list of dicts with keys: id, patientId, documentType...') but LLMs cannot reliably parse unstructured text. Formal JSON Schema would enable reliable type inference and chaining.
Input parameter constraints not formalized. 'date_from' in list_appointments should declare format='date-time' or pattern='^\d{4}-\d{2}-\d{2}$'; 'years' in list_prescriptions lacks minimum/maximum bounds; 'model' in parse_prescription should use enum (llama3.2, llava, etc.). Current descriptions include format hints but no machine-parseable constraints.
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | D | 54 | <=2025-11-25 | v2 |
| 2026-03-09 | F | 48 | - | v1 |
List upcoming and recent appointments. Args: date_from: ISO date string (YYYY-MM-DD) to start from. Defaults to today. Returns a list of dicts with keys: id, scheduledDatetime, medicalActName, staffName, site Name, status, type, canCancel, canReSchedule, and more.
List all clinical documents (imaging reports, cardiology, pathology, etc.). Returns a list of dicts with keys: id, patientId, documentType, documentDate, fileName.
List active lab/exam results available in the portal. Returns a list of dicts with keys: code, report, client. Note: This section may be empty if no results are currently available.
List all invoices from the CUF portal. Returns a list of dicts with keys: id, code, paymentNumber, date, status, value, externalSiteCode.
List all past appointments (from 2000-01-01 to today). Returns a list of dicts with keys: id, scheduledDatetime, medicalActName, staffName, site Name, status, type, and more.
List exam prescriptions from the CUF portal (www.cuf.pt). Scrapes the Drupal-rendered HTML page — prescriptions use a separate auth system from the GraphQL API. Returns list of dicts with: date, patient, site, download_url, document_id. Args: years: List of years to fetch (default: current year and 2 prior years).
Parse a downloaded prescription PDF with a local Ollama model. Extracts text from the PDF and sends it to a local Ollama model for structured extraction. Falls back to vision-mode (image rendering) for image-based PDFs. Args: file_path: Path to the prescription PDF (from get_prescription). model: Ollama model ID to use (default: "llama3.2"; use a vision model like "llava" if the PDF is image-based). Returns structured dict with: patient, date, doctor, specialty, medications (list with name/dci/strength/form/quantity/posology/duration), prescription_number, notes.
Minimal error handling and recovery guidance. list_exam_results catches exceptions but returns generic [{'error': str(e)}]. Most tools provide no guidance: if get_clinical_document fails with 'Document not found', the LLM receives no hint to call list_clinical_documents first. Errors should state: 'What went wrong' + 'What to try next'.
No tool annotations despite all being read-only or idempotent. FastMCP supports readOnlyHint and idempotentHint to guide agent planning. All 12 tools should declare readOnlyHint=true to signal safety for agent retry logic.
get_patient_info description is vague (60 chars). States only 'Get basic patient profile information (name, contacts, identity numbers).' Should explain: When to call it (for profile display), what structure is returned (dict with fullName, title, contacts dict, identity dict), and whether it requires prior authentication context.
Pagination not implemented for list tools. list_clinical_documents, list_invoices, list_appointments, etc. lack limit/offset or cursor parameters. Medical portals may return hundreds of records, returning all at once risks blowing context windows. Tools should accept limit (default 50) and offset/cursor for agent-controlled pagination.
Credentials exposed via environment variables without documented secret injection pattern. Code loads CUF_USERNAME, CUF_PASSWORD from .env, but no documentation of how to safely rotate credentials or revoke agent access. Should document: secrets are server-injected, agent cannot access or log them.
No explicit permission gates or audit trail. Tools access patient medical records without demonstrated per-agent permission checks or logging. Should implement: who (agent identity), what (tool + params), when (timestamp), outcome (success/failure), for HIPAA/compliance traceability.