CoAuth - an agent-native prior-authorization cockpit

Inspiration

Prior authorization is where American healthcare loses time it cannot afford - and it loses it to paperwork, not to medicine. In Medicare Advantage in 2024, 80.7% of appealed prior-authorization denials were fully or partially overturned, yet only 11.5% of denials were ever appealed (KFF). Read those two numbers together: most denials that get contested turn out to have been wrong, and almost no one contests them. The clinical decision is usually fine. The filing is where it breaks.

That is an unusually clean fit for WebMCP, and not just because forms are tedious. Browser agents are weakest exactly where this work lives - long, write-heavy, multi-step forms full of coded fields (ICD-10, HCPCS J-codes, NPIs) that a pixel-clicking agent has no way to get right. And in this domain a human is not optional: a licensed clinician is legally required to attest. So the task splits along a natural seam. The agent is good at the mechanics; the human is mandatory for the judgment. That is what human-plus-agent collaboration is actually supposed to be - not an agent doing everything while a person watches a progress bar, but two participants each doing the part only they can.

What it does

CoAuth is a single live page that registers 13 typed WebMCP tools on document.modelContext. An agent loads a patient record, reads a payer's coverage rules as structured data, fills every non-judgment field, attaches clinical evidence, scores denial risk, detects contraindications and unmet step-therapy criteria, and drafts the medical-necessity narrative and an appeal letter.

What it cannot do: fill a clinician-judgment field, resolve a critical contraindication, or submit. Those belong to the human, and both halves are enforced in code rather than described in prose.

The demo case is Marcus Lee, a psoriatic-arthritis patient requesting Humira through Aetna. The agent fills the form cleanly and then catches what a rushed clinician might miss - a positive TB screen, a contraindication for this drug - flags it, tries to submit, and is stopped. It drafts the medical-necessity language from the chart but cannot adopt its own draft. From there the clinician signs in, makes the call on the TB screen and documents why, accepts the drafted wording as their own, attests, and signs. Only then does the submission go through, with a server-verified audit trail in the clinician's name.

Two details make that boundary real rather than cosmetic. The signature is minted only for an authenticated clinician who re-enters their own credential at the moment of signing - each clinician has a distinct secret, so the name and NPI in the audit trail belong to the person who actually signed, not to anyone who knew a shared password. And the filed submission is rendered as an X12 278 - the HIPAA-mandated Health Care Services Review transaction payers actually receive - so every value the agent filled and the clinician signed maps to the real prior-authorization EDI, not just our own JSON.

How I built it

Vite, React, TypeScript, and Zustand on the front end; Vercel Edge Functions and Upstash Redis on the back end. A 15-rule clinical engine drives conflict detection, denial-risk scoring, and validation.

The architectural decision that carried everything else: the WebMCP tool surface is the application's only write path. Every click in the interface calls the same invokeTool an agent calls, tagged with the actor - there is no parallel code path for humans. So the tools cannot rot behind the UI, attribution is a property of the call path rather than something each component must remember, and the built-in "with tools vs. without" comparison drives the real tools rather than a re-implementation.

Tool schemas are generated from the seed data, so the patient, payer, drug and field enums cannot drift stale - an early bug had a payer hint still reading "uhc or aetna" after a third payer was added. Every property carries a description; write tools declare destructiveHint/idempotentHint so a runtime can require confirmation before submit; and a get_submission_state tool lets an agent joining mid-task see what is filled, who filled it, what is unresolved, and whether the clinician has signed.

Correctness is proven by 127 checks that run against the deployed system - not unit tests, and nothing mocked. They attempt to submit without a signature, with a forged approval, with a form edited after signing, with a clinical override swapped after signing, and with one approval replayed twelve times concurrently, requiring exactly one acceptance. They drive the registered WebMCP tools through a real model-context surface and try, as an agent would, to set the attending attestation. They drive every clinician control the way a hostile script would and require that none of them takes effect. And a meta-check reads the clinical rules table and fails if any rule is not exercised by a test.

Challenges I faced

The entire pitch is that an AI agent cannot file a prior auth without a real clinician signing off, and the hard part was making that actually true instead of just looking true. Every time I thought I'd locked it down, I'd poke at it and find another way around my own gate. My first gate was a browser check, which is useless against an agent that lives in the browser. I moved it to the server, then found the sign endpoint would hand a signature to anyone who asked, under any name they typed. I fixed that, then found a script could still sign using the logged-in doctor's own session, no click required. Turning on the database to stop replays silently rejected every submission in production. And the safety rules were reading tea leaves, so a note saying "no adverse events" got written into a letter to the insurer as a treatment failure.

The real enemy was never a single bug. Every time I fixed the exact thing in front of me, the same kind of bug was sitting one layer over: I'd guard the button and leave the API open, guard one tool and leave its twin open, protect the signature and forget the reasoning behind it. What finally worked was to stop patching the specific hole and close the whole category at once, with one shared check instead of five copies I'd never keep in sync.

What I learned

Stop trusting the moment something starts working. Every real hole showed up after the feature looked done, and I only made progress once I started trying to break my own product on purpose. A passing test can also be a lie: I had one proving an approval couldn't be reused, and it passed while the live site happily accepted a burst of the same token, because the test wasn't checking the thing I was actually afraid of.

And the big one, about the product itself: the interesting part of a human-plus-AI tool isn't making the AI capable, that part is easy. It's drawing the line the AI cannot cross, building the wall around what belongs to the human, and proving it holds when someone is actively trying to climb it. For something that touches patient care, that wall isn't a feature. It's the whole point.

Built With

  • hmac
  • javascript
  • model-context-protocol
  • playwright
  • prior-authorization
  • react
  • redis
  • typescript
  • upstash
  • vercel
  • vercel-edge-functions
  • vite
  • web-crypto-api
  • webmcp
  • zustand
Share this project:

Updates

Submission history