# Daniel Ho — Full Site Content > Software engineer, writer, potter. This file contains the complete text content of https://www.danielho.xyz/ in clean markdown for LLM consumption. --- # A Glimpse Into AI Healthcare Workflows Published: March 11, 2026 Tags: healthcare, fhir, mcp, api Repo: https://github.com/donutdaniel/fhir-gateway Website: https://fhir-mcp.com URL: https://www.danielho.xyz/writing/ai-healthcare-workflows/ FHIR, which stands for [Fast Healthcare Interoperability Resources](https://www.hl7.org/fhir/), is a standardized api layer for most things healthcare. Adoption has been serious in the past few years: [over 70% of countries actively use it](https://fire.ly/blog/the-state-of-fhir-in-2025/), and in the US it's federally mandated via the [21st Century Cures Act](https://www.healthit.gov/topic/oncs-cures-act-final-rule) and the [CMS Interoperability and Prior Authorization Rule](https://www.cms.gov/cms-interoperability-and-prior-authorization-final-rule-cms-0057-f). So, if you are in the US, it's very likely that your payer (insurance), provider (hospital), and EHR (electronic health record) systems have already each implemented the standard on their own servers, with their own auth flows and quirks. That means the practical applications are already feasible, but underdeveloped. Platform fragmentation and knowledge gaps between clinicians and engineers keep things stuck. So armed with an agentic army and a spare weekend, I spent some time diving into it! To make this concrete: if you wanted to pull a patient's medication list from Epic, their lab results from Cerner, and their coverage details from Aetna, you'd need three separate OAuth flows, three different base URLs, and you'd be handling three sets of quirks. Each platform returns FHIR resources, but the data you get back is different. EHRs give you clinical data (conditions, medications, lab results, vitals), while payers give you claims, coverage, and explanation of benefit resources. They may all be FHIR compatible, but they're answering fundamentally different questions about the same patient. This issue is also visible in the demo. You can see during the prior auth flow, Claude pointed out that it does not have access to a CRD within the smart health it sandbox. FHIR Gateway attempts to collapse all of that into one API. One rest endpoint, one mcp, one auth flow, one interface. It currently runs against sandboxes, but you can point it at any platform by adding credentials to a config. > For some additional context, I'm a software engineer by trade, and I knew next to nothing about medicine prior to this project. To my surprise, it was painlessly easy and quick to learn enough through eli5 llm questions, asking agents to crawl api references, and simply trying out the mcp during development. It truly is astonishing how efficient learning can be with ai if you know how to ask the right questions. ## How it works Every request includes a `platform_id` - like `epic`, `aetna`, or `cerner` - that tells the gateway where to route. Platform configs are just JSON files, each defining the FHIR base URL, OAuth endpoints, supported scopes, and any platform-specific behavior. ## The core ideas ### Multi-platform routing The gateway loads platform configurations from JSON files at startup. Adding a new platform is creating a new JSON file, and adding the corresponding env vars. No code changes, no adapter classes. A `GenericPayerAdapter` handles everything dynamically. ```json { "id": "aetna", "name": "Aetna", "type": "payer", "fhir_base_url": "https://vteapif1.aetna.com/fhirdemo/v2/patientaccess", "oauth": { "authorize_url": "https://vteapif1.aetna.com/fhirdemo/authorize", "token_url": "https://vteapif1.aetna.com/fhirdemo/token" } } ``` That's a complete platform definition. 64 of these exist today across payers, EHRs, and sandboxes. EHR platforms like Epic and Cerner are also multi-tenant, meaning there's no single "Epic URL," each hospital runs its own instance, so production access requires onboarding with each organization individually. While researching routing solutions and data stores I could leverage, the two most interesting products I found were [Fasten Health](https://www.fastenhealth.com/) and [Turquoise Health](https://turquoise.health/). Fasten maintains [fasten-sources](https://github.com/fastenhealth/fasten-sources), a catalog of provider metadata and OAuth endpoints, though their product is patient-facing rather than provider facing. Turquoise focuses on price transparency, making healthcare cost data accessible which is useful to make better estimates (and deals, i suppose). I later found that the claude healthex connector uses fasten for patient data! Because Fasten is more orthogonal wrt fhir gateway, and turquoise is a little out of reach for a solo dev, I did not integrate either, although they could certainly be useful. > To further elaborate on platform registration... getting production access to these healthcare platforms is *incredibly* difficult. Many require interviews, SOC2/HIPAA certs, $5k/year licenses (ahem, Oracle), and weeks to months of processing. So I have not done that. The 64 definitions are all crawled and best-effort-verified ones. Sandbox ones are tested, others have no guarantee. ### Authentication and security When a user authenticates, the gateway initiates a [SMART on FHIR](https://docs.smarthealthit.org/) OAuth flow with PKCE. SMART (Substitutable Medical Applications, Reusable Technologies) is essentially OAuth 2.0 tailored for healthcare, allowing third-party apps to launch from and authorize against FHIR servers. The user authorizes in their browser, the callback is handled automatically, and tokens are stored server-side. No tokens are ever exposed to the client. Tokens are encrypted at rest, scoped to each browser session via httponly/secure/samesite cookies, and stored in-memory for development or Redis for production. The gateway handles token refresh automatically, so authorization stays live across requests without the user needing to re-authenticate. Beyond auth, the gateway also validates platform IDs, resource types, and resource IDs, rate limits per session, and logs requests for compliance. ### Dual interface: REST + MCP The same FastAPI server exposes both a REST API and an MCP endpoint. Both share the same session and token management, so an OAuth flow initiated via MCP works with REST calls, and vice versa. I actually originally started with mcp! But it didn't make much sense to hide it behind one protocol, especially when going for interoperability. ```python # MCP is mounted in the same app mcp_app = mcp.streamable_http_app() app.mount("/mcp", mcp_app) ``` MCP uses streamable-http transport (not stdio) because OAuth callbacks need the HTTP server running to receive browser redirects. ```bash # FHIR operations GET /api/fhir/{platform_id}/metadata # CapabilityStatement GET /api/fhir/{platform_id}/{resource_type} # Search GET /api/fhir/{platform_id}/{resource_type}/{id} # Read POST /api/fhir/{platform_id}/{resource_type} # Create PUT /api/fhir/{platform_id}/{resource_type}/{id} # Update DELETE /api/fhir/{platform_id}/{resource_type}/{id} # Delete # Authentication GET /auth/{platform_id}/login # Start OAuth flow GET /auth/status # Check auth status POST /auth/{platform_id}/logout # Logout ``` Everything routes through the `platform_id`. ## Agent interface The MCP interface lets AI agents query healthcare data through tool calls: ``` User: "Get my lab results from Epic" Agent: 1. get_auth_status(platform_id="epic") → Not authenticated 2. start_auth(platform_id="epic") → Returns OAuth URL 3. Tell user to click link 4. wait_for_auth(platform_id="epic") → Blocks until complete 5. search(platform_id="epic", resource_type="Observation", params={"category": "laboratory"}) ``` Tools include FHIR operations (`search`, `read`, `create`, `update`, `delete`), auth management (`start_auth`, `wait_for_auth`), and coverage checks (`check_prior_auth`, `get_policy_rules`). So what is it useful for? Behold one of the biggest pain points: prior auth, the process where a provider must get approval from a payer before delivering a service. It is often delayed (I had to schedule a CT scan a month out bc of prior auth taking 1+ week), or mismanaged due to interop difficulties. An [AMA 2024 survey](https://www.ama-assn.org/system/files/prior-authorization-survey.pdf) found that 29% of physicians reported patients experiencing a serious adverse event due to prior auth delays. [CMS-0057-F](https://www.cms.gov/newsroom/fact-sheets/cms-interoperability-prior-authorization-final-rule-cms-0057-f) now mandates 72-hour (urgent) and 7-day (standard) prior auth decision times starting Jan 2026, with payers required to expose prior auth via FHIR APIs by Jan 2027. There's also the complexity of healthcare terminology. Diagnoses arrive as ICD-10, SNOMED CT, or free text; medications as RxNorm or NDC codes; labs as LOINC; procedures as CPT or HCPCS. The gateway passes these through transparently, but still requires interpretation and validation. Luckily, these are much more straightforward to integrate due to stable coding and existing connector availabilities, making interactions inside an llm conversation pretty smooth. ### Connecting Any MCP-compatible client can connect to the gateway. Here are a couple examples: **Claude.ai** — Settings → Connectors → Add Custom Connector: - Name: `FHIR Gateway` - Remote MCP URL: `https://fhir-mcp.com/mcp` **ChatGPT** — Settings → Connectors → Advanced → Developer Mode → Create: - Name: `FHIR Gateway` - URL: `https://fhir-mcp.com/mcp` **Any MCP client (Claude Code, Cursor, etc.)** — Add to your MCP config: ```json { "mcpServers": { "fhir-gateway": { "command": "npx", "args": ["-y", "mcp-remote", "https://fhir-mcp.com/mcp"] } } } ``` The gateway ships with sandbox configs (`epic-sandbox-patient`, `epic-sandbox-clinician`, `smarthealthit-sandbox-patient`) for development. Only SmartHealthIT and HAPI sandboxes work without any registration. ## Clinician natural language workflows This is the most exciting part to me! You can kick off medical workflows in natural language, and an agent with access to this MCP can do what a doctor or nurse does on a computer, with no additional scaffolding. The point is that it can help cut down the most mundane clerical tasks like data entry, scheduling, billing/coding, and give back time for direct patient care. The video at the top demonstrates this. I had a friend in the medical field test the MCP against a sandbox. ## On AI in Healthcare LLMs of today are not healthcare workers, nor do I have an expectation that they will be anytime soon. The intention of this project, and consequently this technology, is to demonstrate how powerful modern ai can be with the right set of tools and context. I believe this technology can act as the greatest leverage in the most difficult domains, in healthcare and policy alike, in fields that are supposed to be so unequivocally beneficial, but mired by misaligned corporate incentives and slow public policies. My hope is that adoption of such technologies will be a great lift to those who intend to do good. And yet there can be no value placed on or replacement for human interaction. Doctor patient relationships are uniquely personal, even if not the most efficient. I can, however, imagine a world where data-backed opinions and decisions can be automated, and we choose automation because it is the most utilitarian choice to make. If that comes to pass, my hope is that not only are the treatments more effective, but the relationships we make are even more meaningful. Finally, to other engineers: it's a great time to learn something you know nothing about. Read articles, ask professionals, make something! ## Links The repo is open source at [github.com/donutdaniel/fhir-gateway](https://github.com/donutdaniel/fhir-gateway) and live at [fhir-mcp.com](https://fhir-mcp.com). Sources and references: - [HL7 FHIR Specification](https://www.hl7.org/fhir/) — the standard itself - [SMART on FHIR](https://docs.smarthealthit.org/) — the OAuth-based app launch framework - [ONC's Cures Act Final Rule](https://www.healthit.gov/topic/oncs-cures-act-final-rule) — the US federal mandate requiring FHIR APIs - [State of FHIR 2025](https://fire.ly/blog/the-state-of-fhir-in-2025/) — global adoption survey by Firely/HL7 --- # Git Gud Published: May 1, 2022 Tags: git, performance, devtools URL: https://www.danielho.xyz/writing/git-gud/ Creating performant git commands. Experiencing slow tooling? This article was originally written amid the growing pains of an increasingly large monorepo, where git commands could take 10s+ to execute. For developers that do a lot of context switching, and for a repo with many contributors — it adds up. There are plenty of prefaces here. If you're just looking for the improvements, feel free to scroll down to the ["Ok so, how do I git gud?"](#ok-so-how-do-i-git-gud) section. ## Ever seen a slow repo? *Tested on a fully-installed 5.9GB repo* **1st try (git status)** ``` It took 10.88 seconds to enumerate untracked files. 'status -uno' may speed it up, but you have to be careful not to forget to add new files yourself (see 'git help status'). nothing to commit, working tree clean git status 0.45s user 2.25s system 24% cpu 11.109 total ``` **2nd try (git gc)** ``` It took 6.87 seconds to... git status 0.41s user 1.99s system 33% cpu 7.137 total ``` **3rd try (git gc --aggressive)** ``` It took 3.55 seconds to... git status 0.42s user 2.10s system 69% cpu 3.658 total ``` **4th try (git status -uno)** ``` nothing to commit (use -u to show untracked files) git status -uno 0.06s user 0.56s system 556% cpu 0.112 total ``` **5th try (fsmonitor-watchman)** ``` nothing to commit, working tree clean git status 0.31s user 1.02s system 92% cpu 1.435 total ``` ## A look at git fundamentals ### git clone O(n) where "n" includes every commit in history, which means it includes every file ever committed to the repository — even if currently deleted. It also includes every commit referenced by a branch, which could include additional files that don't exist in the standard "clean" repo checkout. These can be tuned with `--depth` and `--branch`, respectively. But these clone flags may only be useful in certain scenarios where the user does not need history and/or does not need to check out different branches. ### git status Actually runs `git diff` twice. Once to compare HEAD to staging area, another to compare staging to your work-tree. According to official docs, git diff can use any of the four following algorithms: `--diff-algorithm={patience|minimal|histogram|myers}`. The default is the Myers diff algorithm, which runs in a theoretical O(ND) time and space, where N=input length and D=edit distance. Its expected runtime is O(N+D²). Not terrible, but the runtime can be badly magnified in large repos. ### git add Adds your working tree to staging area, gotta go fast O(n). Specifying path `git add /*` instead of `git add --all` is obviously faster. ### git commit Like git add, commit is supposed to be fast, but can get bogged down when your repo has a formatter for 5 languages, 2 type-checkers, and sanitization scripts bundled together into pre-commit-hook bloatware. O(∞ⁿ) If git commit is acting up, you can also run `git commit -n` to skip commit hooks. ### git push/pull Depends on how your ISP is feeling. Pull also does diffing when attempting to rebase/merge based on your preferred strategy. ### git rebase/merge Rebase and merge have the same-ish effect, and are written similarly. Merge strategy options: `--strategy={ort|resolve|recursive|octopus|ours|subtree}`. The default merge strategy is `ort` (Ostensibly Recursive's Twin) when pulling one branch, and `octopus` when dealing with 2+ heads. Note `ort` superseded the old default `recursive` method in Q3 2021. Under the hood, `ort` and `recursive` also call `git diff` with the default Myers diffing algorithm, which is why it can be slow too. --- **tl;dr**, any operation that requires reading or writing your index will perform a FULL read or write to your index, regardless of the number of files you actually changed. *Sometimes the staging area/index is called the cache.* ## Stats, for fun *An example monorepo:* - Typical active repo size: **5.9GB** (after installation) - Fresh clone repo size: **3.0GB** - `.git` size (consistent): **2.4GB**. Unpacking it further reveals a 2.3GB `.git/objects/pack`, which acts as a database of the repo's history. ## Ok so, how do I git gud? 🧐 ### 1. Enable fsmonitor-watchman Enable with the `git/hooks/fsmonitor-watchman.sample` already in your repo: ```bash cp .git/hooks/fsmonitor-watchman.sample .git/hooks/fsmonitor-watchman git config core.fsmonitor .git/hooks/fsmonitor-watchman git update-index --fsmonitor ``` Notes: - You will need to install [watchman](https://facebook.github.io/watchman/) - If you cloned your repo before 3/22/2020 or your git is <v2.26, then you have an outdated `fsmonitor-watchman.sample` file. You can either copy the updated version from the source, or update git then `git init` an empty repo to get it. - The [rust implementation](https://github.com/jgavris/rs-git-fsmonitor) is faster ### 2. Use untracked-cache ```bash git update-index --test-untracked-cache ``` If it returns OK, run: ```bash git config core.untrackedCache true && git update-index --untracked-cache ``` ### 3. Use split-index ```bash git config core.splitIndex true && git update-index --split-index ``` ### 4. Increase vnode cache size Default kernel vnodes size is `kern.maxvnodes: 263168` (257 * 1024). To increase for your session: ```bash sudo sysctl kern.maxvnodes=$((512*1024)) ``` This setting will reset on reboot. To set permanently: ```bash echo kern.maxvnodes=$((512*1024)) | sudo tee -a /etc/sysctl.conf ``` ### 5. Use git status -uno ```bash git status -uno ``` > **Warning:** `-uno` will not show untracked files, meaning newly created files will not show up. ### 6. Incorporate git gc into your workflow In increasingly aggressive order: ```bash git prune git gc git gc --aggressive git gc --aggressive --prune=now ``` ### 7. Automatically delete old branches [git-delete-squashed](https://github.com/not-an-aardvark/git-delete-squashed) is a tool that deletes all of your git branches that have been "squash-merged" into master. Useful if you work on a project that squashes branches into master. ### 8. Manually delete old branches ```bash # Delete remote and local branch git push -d git branch -d # Usually the remote name is origin git push -d origin # Delete local branch (force) git branch -D ``` ### 9. Switch to Linux 5–10x faster according to the [Dropbox article](https://dropbox.tech/application/speeding-up-a-git-monorepo-at-dropbox-with--200-lines-of-code). ## How much faster? 🚀 Results of `git status` tested using [hyperfine](https://github.com/sharkdp/hyperfine) with 3 warmup operations and min 30 runs on a basic bash terminal with no plugins. Test setup: - Clean repo, 3.0GB - 2019 16" MacBook Pro (Intel chip, likely lowest configuration) - IntelliJ, Slack, and Chrome running in the background **0. Control `git status`:** ``` Time (mean ± σ): 1.487 s ± 0.045 s [User: 324.1 ms, System: 1470.8 ms] Range (min … max): 1.408 s … 1.587 s 30 runs ``` **1. fsmonitor-watchman (9.28% faster):** Perl (6.99%): ``` Time (mean ± σ): 1.383 s ± 0.061 s [User: 293.8 ms, System: 976.0 ms] Range (min … max): 1.326 s … 1.645 s 30 runs ``` Rust (9.28%): ``` Time (mean ± σ): 1.349 s ± 0.036 s [User: 275.2 ms, System: 968.9 ms] Range (min … max): 1.309 s … 1.457 s 30 runs ``` **2. Untracked cache (5.85% faster):** ``` Time (mean ± σ): 1.400 s ± 0.042 s [User: 311.9 ms, System: 1414.0 ms] Range (min … max): 1.340 s … 1.527 s 30 runs ``` **3. Split-index (5.98% faster):** ``` Time (mean ± σ): 1.398 s ± 0.072 s [User: 305.8 ms, System: 1404.7 ms] Range (min … max): 1.323 s … 1.666 s 30 runs ``` **4. Increase vnode cache size (2.96% faster):** ``` Time (mean ± σ): 1.443 s ± 0.069 s [User: 316.3 ms, System: 1439.3 ms] Range (min … max): 1.315 s … 1.562 s 30 runs ``` **5. git status -uno (96.1% faster):** ``` Time (mean ± σ): 57.1 ms ± 1.0 ms [User: 49.0 ms, System: 431.7 ms] Range (min … max): 55.2 ms … 59.5 ms 50 runs ``` **1–4. All combined (86.14% faster):** ``` Time (mean ± σ): 206.1 ms ± 100.0 ms [User: 126.9 ms, System: 43.4 ms] Range (min … max): 181.4 ms … 735.3 ms 30 runs ``` *I stepped away and watched an episode of Naruto between the last test and this one, so something weird might have happened here. Not sure if I can trust this data point tbh.* ## Closing Thoughts This was a private note that I wrote and tested around May 2021. Since then, I no longer work on the repo, nor do I follow the current state of monorepos. Perhaps there is better version control tooling, especially if dev environments are shifting towards cloud infra such as GitHub Codespaces where machine capability is no longer an issue. Yes, I have worked with FB's Mercurial and Salesforce's Perforce, and I didn't really have much issue with either. I choose to believe that the engineers there already yoked them to their limits. This article is more or less for startups and companies that have settled on git early on and have not changed version control or repo structure. Some other improvements that come to mind are enabling this in CI/CD if you are using version control to diff in your pipeline for whatever reason. You can also include these improvements out of the box for onboarding eng, with a script that automates these suggestions. If you have a centrally managed distro of dev tooling, you can also update all your dev machines with this.