Add an AI agent to Medplum: approval-gated chat over the patient chart

Medplum gives you a FHIR backend and APIs to build on. Last EHR adds an AI agent layer: a chat interface that reads your patient chart and proposes writes (notes, observations) that require your approval before saving. It is open source, self-hosted, and brings your own model key.

What Last EHR adds to a Medplum project

Medplum is a headless FHIR backend. It handles authentication, multi-tenancy, access control, and audit logs. What it does not give you out of the box is a chat interface for working the chart in natural language.

Last EHR is a thin agent layer that runs on top of Medplum and adds that interface. The agent has eight tools:

  1. Search patients by name
  2. View a patient's chart (conditions, allergies, medications, observations, notes, immunizations)
  3. Read one chart section with date and code filters (tasks, documents, goals, care plans, and more)
  4. Read the text of one document from the chart, when the body is inline text
  5. Add a free-text note (approval-gated)
  6. Record an observation: a vital sign or lab value with a numeric value and unit (approval-gated)
  7. File a superseding observation that corrects an earlier value without changing it (approval-gated)
  8. Create a follow-up task with an optional due date (approval-gated)

Reads execute immediately, so you see patient data in the chat. Writes trigger a confirmation card in the UI: nothing touches your chart until you click Approve. Once approved, the write goes to Medplum under the signed-in user's identity, bounded by your AccessPolicy.

The layer stores no patient data of its own. Every API call runs against your Medplum instance, using the credentials you provide.

Not the Medplum Agent: how this differs from Medplum's own tools

Medplum has its own tools with similar-sounding names, so it is worth being precise about what Last EHR is and is not.

The Medplum Agent is Medplum's on-premise connectivity tool: it bridges devices and legacy feeds (HL7 v2, DICOM) on a local network to your Medplum project. It is integration plumbing, not a chat interface. The name collides; the products do not.

Medplum also ships an MCP server that exposes Medplum data to MCP-compatible clients such as Claude. That is lower-level infrastructure for wiring models to data; it does not include a clinician-facing UI or an approval gate. Last EHR ships a separate MCP package, read-only by default, for two bounded chart-reading tools; the difference in scope is covered below.

And Medplum now has Spaces, an in-app AI assistant in Medplum Provider, shipped as an example implementation. A chain of bots translates a question into FHIR calls, the Provider UI executes them under the signed-in user's AccessPolicy, and summary and visualizer bots narrate the results and render charts. It is a capable assistant with a real access model: every call runs as the signed-in user, the same posture Last EHR takes.

The write model is where the two part ways. Spaces executes writes as soon as its loop reaches them; its own docs advise reviewing the resulting resources and scoping the ai feature to AccessPolicies without write access to high-risk types. That control is policy scoping at the account level. Last EHR puts a human approval in front of each individual write instead. Neither is a superset of the other: policy scoping bounds what the assistant can ever touch, a per-write gate makes each change an explicit human decision. Beyond the gate, the practical differences are setup and portability: Spaces runs one vendor's models (OpenAI, via a project secret) inside Medplum Provider and requires the gated ai and bots project features plus deploying three bots and authoring their prompts; Last EHR is a standalone app you run yourself, with OpenAI, Anthropic, or Amazon Bedrock.

Medplum continues to ship its own AI features on its own roadmap; this project is independent of that and focuses on one interaction done carefully: approval-gated chat over the chart.

How approval-gated writes work with your AccessPolicy

The approval gate is a UI control, not a permission control. When you approve a write, it still goes through Medplum's AccessPolicy. If your policy says the signed-in user cannot create Communications, Observations, or Tasks, Medplum rejects the write even after approval.

  1. The agent calls add_note, record_observation, or create_task (defined with needsApproval: true in the Vercel AI SDK).
  2. The SDK pauses before the tool's execute function runs.
  3. The UI shows an approval card with exactly what will be written.
  4. You click Approve or Cancel.
  5. Only on Approve does execute run and call Medplum. On Cancel, nothing is saved.
  6. Medplum then checks the AccessPolicy for the signed-in user and accepts or rejects the write.

The gate surfaces writes for human review; your AccessPolicy stays the source of truth for who can write what. Read the full breakdown in how approval-gated writes work.

Launch it inside the Medplum app (SMART on FHIR)

You do not have to run Last EHR as a separate destination. Medplum implements SMART App Launch, so registering Last EHR takes one resource: create a ClientApplication in your project with launchUri pointing at your deployment's /launch route and redirectUri at /launch/callback, and set that application's id as SMART_CLIENT_ID in your deployment.

After that, Last EHR appears on the Apps tab of every Patient and Encounter page in app.medplum.com. Launching it opens the chat already scoped to the patient the clinician was viewing, reusing the Medplum sign-in instead of asking for a separate one. The launch is a standard OAuth2 authorization-code flow with PKCE (public client, no secret), and the resulting session is bounded by the granted SMART scopes and your AccessPolicy. Writes still stop at the approval card.

Use bounded chart reads over MCP

@lastehr/mcp is an installable MCP server for hosted or self-hosted Medplum. Use its setup command to add it to Claude Code, Cursor, or another MCP client, then search patients and read charts with the same Medplum identity your client is configured to use.

The published package is read-only by default: four tools,search_patients, show_patient_info,read_chart_section, and read_document. Writes exist only behind an explicit opt-in (LASTEHR_MCP_WRITES=proposal) that carries the approval-card semantics onto MCP: the client renders the exact proposed fields through MCP elicitation and nothing saves without a human's approval, per action. Hosts that cannot render that approval never see a write tool.

This is a different thing from Medplum's own MCP server: theirs exposes Medplum data broadly for building your own agent; this one exposes four bounded chart reads and no unapproved writes.

Run it on your Medplum: setup, env vars, and your own model key

Last EHR is a Next.js app. The one-click Vercel deploy button in the README prompts for the required env vars. To run it locally:

git clone https://github.com/cbetz/last-ehr.git
cd last-ehr
npm install
cp .env.example .env.local   # then edit .env.local
npm run seed                 # load synthetic patients into your Medplum
npm run dev                  # http://localhost:3000/demo

At minimum, set:

  • A model key: OPENAI_API_KEY (default provider), ANTHROPIC_API_KEY with AI_PROVIDER=anthropic, or AWS credentials with AI_PROVIDER=bedrock and MODEL_ID.
  • MEDPLUM_CLIENT_ID and MEDPLUM_CLIENT_SECRET: a Medplum ClientApplication, used by the seed script and by the no-sign-in quickstart mode (NEXT_PUBLIC_QUICKSTART=true).
  • MEDPLUM_BASE_URL and NEXT_PUBLIC_MEDPLUM_BASE_URL if you run your own Medplum; leave blank for Medplum's hosted API.

Every variable is documented in .env.example. The seed script loads four synthetic patients so you can test without any real data. Then open /demo and ask the agent to find a patient.

Alpha status, PHI, and BAAs: what you can and cannot do today

Last EHR is in active development. APIs are not stable and the scope will grow. Use synthetic data only at this stage.

Do not point this at real patient data without agreements in place. Last EHR is not a HIPAA-covered service, and the approval gate is a write-safety control, not a privacy control: anything the agent reads is sent to your model provider as context, under your API key. Handling real PHI would require a BAA with your model provider that covers API traffic (consumer plans do not qualify), a HIPAA-eligible FHIR backend with its own BAA (Medplum offers HIPAA-eligible hosted plans; check their current terms), and your own compliance review.

Last EHR is Apache-2.0. Self-hosting is free. A managed tier (hosted Medplum, a signed BAA, multi-tenancy, billing) may follow, built only after the open-source core has proven value.

Frequently asked questions

Is Last EHR a replacement for Medplum?

No. Last EHR is a layer that runs on top of Medplum. Medplum is the system of record: it stores the patient data, handles authentication, manages access control, and logs audit trails. Last EHR is a chat interface that reads from and writes to Medplum. It is not a backend, not a replacement, and not the source of truth.

What does the approval gate do, and what does it not do?

The approval gate is a write-safety control: it surfaces proposed writes to the user and requires an explicit click before anything is saved. It stops unilateral writes and creates a human-in-the-loop checkpoint. What it does not do is enforce access control (that is Medplum's AccessPolicy), or guarantee clinical correctness (that is the clinician's responsibility). The gate stops the agent from writing without your click; it does not stop you from approving a clinically wrong write.

Can I use Last EHR with real patient data today?

You should not. Last EHR is alpha software, and using it with real PHI requires a BAA with your model provider and a HIPAA-eligible backend with its own BAA. Use synthetic data for development, testing, and evaluation. Only consider real data once you have the agreements in place and have done your own compliance review.

Can I add more tools to the agent?

Yes. The tools are defined in lib/ai/tools.ts. You can add new tools by following the Vercel AI SDK tool pattern and writing new FHIR calls. The code is Apache-2.0, so you can fork it and extend it for your use case.

Does Last EHR work with backends other than Medplum?

Medplum (hosted or self-hosted) is the supported authenticated path. For synthetic evaluation only, four more adapters ship in the repository: local HAPI FHIR, Firely Server, Aidbox, and Oystehr, each verified with the shared contract tests and the FHIR Agent Safety Eval; none is a PHI-ready deployment path. Other FHIR R4 targets still need an adapter with a documented auth story and the same evidence before they are called supported.

What model providers does Last EHR support?

OpenAI, Anthropic, and Amazon Bedrock are supported out of the box. Set AI_PROVIDER, MODEL_ID when needed, and the matching provider credentials. The project intentionally ships BAA-capable provider paths only.

Can I use the tools from Claude Desktop or another MCP client?

Yes. @lastehr/mcp is an installable stdio server for Medplum. By default it exposes four chart-reading tools: search_patients, show_patient_info, read_chart_section, and read_document. Writes exist only behind an explicit opt-in (LASTEHR_MCP_WRITES=proposal) where the client shows the exact proposed fields and a human approves each action. Read access can still return PHI, so use a least-privilege Medplum identity and review the MCP host's data handling.

See it on synthetic patients

The live demo runs the same eight tools against seeded synthetic data. Every write goes through the approval card.