MCP Server

FHIR PostgreSQL MCP Server

Direct access to FHIR resources stored in a Medplum PostgreSQL database via the Model Context Protocol.

https://mcp.pegasusone.com/mcp

🔌 Quick Connect

Add this to your MCP client configuration (Claude Desktop, Cursor, etc.):

📄 claude_desktop_config.json / mcp.json
{
  "mcpServers": {
    "fhir-pg": {
      "type": "http",
      "url":  "https://mcp.pegasusone.com/mcp"
    }
  }
}

🗺️ Recommended Workflow

1
Discover available data Call list_resource_types to see all FHIR resource types in the database and their record counts.
2
Check capabilities before searching Call get_capabilities with the resource type you want to query — it returns valid search parameters and database column info.
3
Search or read resources Use search to find multiple resources with filters, or read to fetch one by ID.
4
Create, update, or delete Modify resources using create, update, or delete (soft-delete — data is preserved).
5
Complex analytics Use sql_query for cross-resource queries or anything standard FHIR search can't express. SELECT only, 100-row limit.

🛠️ Tools Reference

list_resource_types Read

Lists all FHIR resource types available in the database with record counts. Use this to discover what data exists.

No parameters required.
get_capabilities Read

Returns valid search parameters, database columns, and supported interactions for a resource type. Call this before searching.

type required FHIR resource type name (e.g. Patient)
search Read

Executes a FHIR search and returns a Bundle (searchset). Supports column-indexed queries and JSON-path fallback.

type required FHIR resource type name
searchParam optional Key-value search parameters (see below)
Search parameters:
_id   _count (default 20)   _sort   _lastUpdated
Date prefixes: eq ge gt le lt ne
read Read

Retrieves a single FHIR resource by its type and logical UUID. Returns the full FHIR JSON.

type required FHIR resource type name
id required The logical UUID of the resource
create Write

Creates a new FHIR resource. The server assigns an ID if not provided. Returns the created resource with server-assigned meta fields.

type required FHIR resource type name
payload required Complete FHIR resource JSON (must include resourceType)
update Write

Replaces an existing FHIR resource entirely. The resource must already exist. Returns the updated resource.

type required FHIR resource type name
id required UUID of the resource to update
payload required Complete updated FHIR resource JSON
delete Write

Soft-deletes a resource (sets deleted=true). Data is preserved for audit. Returns an OperationOutcome.

type required FHIR resource type name
id required UUID of the resource to delete
sql_query Power

Executes a read-only SQL SELECT against the Medplum PostgreSQL database. For complex analytics or cross-resource queries.

query required A SQL SELECT statement
params optional List of bind parameters for $1, $2, ...
Rules: SELECT only · Max 100 rows · Use content::jsonb for JSON ops · Always add WHERE deleted=false

💡 Examples

Search for patients born after 1980

search — Patient
type: "Patient"
searchParam: {
  "birthdate": "ge1980-01-01",
  "_count": 10,
  "_sort": "family"
}

Create a new Patient

create — Patient
type: "Patient"
payload: {
  "resourceType": "Patient",
  "name": [{ "family": "Smith", "given": ["Jane"] }],
  "birthDate": "1985-06-15",
  "gender": "female"
}

Cross-resource SQL query

sql_query
query: "SELECT
  p.content::jsonb->>'id'        AS patient_id,
  p.content::jsonb->>'birthDate' AS dob,
  COUNT(e.id)                    AS encounters
FROM \"Patient\" p
LEFT JOIN \"Encounter\" e
  ON e.content::jsonb->>'subject' LIKE '%' || (p.content::jsonb->>'id') || '%'
WHERE p.deleted = false
GROUP BY patient_id, dob
ORDER BY encounters DESC
LIMIT 20"

📌 Good to Know

Stateless HTTP

Every request is independent. No session state is maintained between calls.

Soft Deletes

Deleted resources are never purged. They have deleted=true in the database and are excluded from all queries by default.

JSON Storage

Full FHIR JSON is in the content column. Use content::jsonb in SQL queries for JSON path operations.

Date Filtering

Date search params support prefixes: ge, gt, le, lt, eq, ne. Example: ge2024-01-01.

SQL Safety

Only SELECT statements are permitted via sql_query. Results are capped at 100 rows.

Always Check Capabilities

Call get_capabilities before searching — it reveals which parameters are backed by indexed columns (fast) vs JSON fallback (slower).