FHIR Controller Command Line Interface (CLI) Utilities
The fhir-controller command line utility for interacting with remote FHIR servers from an on-premise or cloud host. This cross-platform executable provides:
- Command line interface for FHIR server operations
- Ability to upload a directory of Synthea FHIR output directly to a FHIR server in the correct dependency sequence
- Upload terminology systems (SNOMED CT, LOINC, RxNorm) to FHIR servers as CodeSystem resources
- Polling and auto-import capabilities for FHIR Controller deployments
CLI Usage
Install or Run the CLI
# Install globally
npm install -g fhir-controller-cli
# Or use with npx (no installation required)
npx fhir-controller-cli helpAfter installation, use the fhir-controller executable:
# Show top-level help
fhir-controller helpServer Reset
Permanently reset server data using the same driver-specific reset endpoints as the browser app.
# HAPI FHIR: POST /$expunge with expungeEverything=true
fhir-controller server reset http://localhost:8080/fhir --driver hapi-fhir
# WildFHIR: POST /$purge-all
fhir-controller server reset http://wildfhir.example.com/fhir --driver wild-fhir
# Preview the request without sending it
fhir-controller server reset http://localhost:8080/fhir --driver hapi-fhir --dry-runSupported reset drivers are hapi-fhir and wild-fhir (hapi and wildfhir are accepted aliases). Generic and FHIR Candle do not currently support permanent reset behavior in FHIR Controller.
Synthea Upload
Upload Synthea-generated FHIR resources to a FHIR server. Files are loaded in dependency order: hospitals, practitioners, then patients.
# Upload Synthea directory
fhir-controller synthea-upload /path/to/synthea/output/ http://localhost:8080/fhir
# Preview the upload without writing resources
fhir-controller synthea-upload /path/to/synthea/output/ http://localhost:8080/fhir --dry-runPolling and Auto-Import
Monitor a FHIR server for AuditEvents and automatically trigger imports when the server has no matching import AuditEvent yet.
The second argument is the stack manifest (stack.json): an HTTP(S) URL, a file:// URL, or a filesystem path. Relative paths are resolved from the current working directory; a leading ~/ expands to your home directory. Data files listed in the manifest are loaded from the same base as the manifest: URL resolution for remote manifests and directory-relative paths for local manifests.
The CLI logs browser-compatible manifest warnings for questionable configuration values, such as invalid URLs, unknown drivers or loaders, duplicate priorities, undefined scenarios, and missing CQL evaluation IDs. These warnings do not stop the import; they are informational so existing stacks keep running.
# Poll and import using a remote manifest URL
fhir-controller poll-auditevent-and-trigger-import http://fhir.example.com/fhir https://stack.foundry.hl7.org/stack.json
# Local manifest and local bundle paths next to it
fhir-controller poll-auditevent-and-trigger-import http://fhir.example.com/fhir ./config/stack.json
# Only import rows tagged for a given scenario
fhir-controller poll-auditevent-and-trigger-import http://fhir.example.com/fhir https://stack.foundry.hl7.org/stack.json --scenario partial
# Match the browser app's default scenario: untagged rows, plus rows explicitly tagged `default`
fhir-controller poll-auditevent-and-trigger-import http://fhir.example.com/fhir https://stack.foundry.hl7.org/stack.json --scenario default
# Custom polling interval (seconds), verbose logging, dry run
fhir-controller poll-auditevent-and-trigger-import http://fhir.example.com/fhir https://stack.foundry.hl7.org/stack.json --interval 300 --verbose --dry-run
# One shot: poll once, import if needed, then exit
fhir-controller poll-auditevent-and-trigger-import http://fhir.example.com/fhir ./config/stack.json --exit --dry-runPolling Options
-
--exit: run a single poll cycle and then exit;--intervalis ignored after that cycle. -
--scenario <scenario_id>: restrict imports to manifestdatarows for that browser scenario. With--scenario default, rows with noscenariosarray or an empty one are included along with rows taggeddefault. With any other scenario, only rows explicitly tagged for that scenario are included. Omitting--scenariopreserves legacy CLI behavior and imports allload=truerows. -
-i, --interval <seconds>: minimum time between polls (default3600). -
-v, --verbose: extra debug output. -
-d, --dry-run: log actions without uploading to the FHIR server. -
--audit-event-system/--audit-event-code: match and create the import AuditEvent type.
CQL Library Imports
For manifest rows with loader: "cql-as-fhir-library", the CLI compiles the CQL source with @cqframework/cql and reads the library identifier from the ELM AST (library <name> version '<version>'). It uploads the primary FHIR Library resource to Library/<name> with that version.
For backwards compatibility, when the legacy manifest-derived id differs from the CQL library name, the CLI also uploads a compatibility alias at the old Library/<manifest-name> id using the same CQL-declared version. If a CQL file has no parseable library <name> version '<version>' declaration, the import fails with an error and no Library resource is uploaded. Manifest version values and defaults such as "0.0.0" are never used.
CQL Evaluation
Evaluate a CQL Library already loaded on a FHIR server:
fhir-controller cql evaluate http://localhost:8080/fhir HelloWorld Patient/cfsb1703736930464
fhir-controller cql evaluate http://localhost:8080/fhir Basic-Statin-Artifact cfsb1703736930464The command POSTs the same FHIR Parameters shape as the browser app to Library/<library_id>/$evaluate, using a single subject parameter with valueString set to the subject argument.
Terminology Imports
The CLI supports uploading major terminology systems to FHIR servers:
- SNOMED CT US Edition: clinical terminology
- LOINC: laboratory and clinical observations
- RxNorm: clinical drugs and medications
The terminology import command accepts:
-
--system <system>: terminology system to import (snomed,loinc,rxnorm). -
--dry-run: perform a dry run without uploading any resources. -
--verbose: enable verbose debugging mode. -
--keep-temporary: keep temporary files after upload for debugging. -
--replace: delete existing CodeSystem and ValueSet before importing new ones. -
--batch-size <size>: number of concepts to process in each batch (default1000). -
--skip-preprocess: skip preprocessing stage and use the most recent files in the temp directory. -
--skip-split: skip splitting stage and use the most recent files in the temp directory. -
--skip-upload: skip upload stage and only preprocess and split.
# SNOMED
fhir-controller terminology import ~/Developer/terminologies/SnomedCT_ManagedServiceUS_PRODUCTION_US1000124_20250901T120000Z http://localhost:8080/fhir tmp --system snomed --replace --keep-temporary --verbose
# LOINC
fhir-controller terminology import ~/Developer/terminologies/Loinc_2.81 http://localhost:8080/fhir tmp --system loinc --replace --keep-temporary --verbose
# RxNorm
fhir-controller terminology import ~/Developer/terminologies/RxNorm_full_09022025 http://localhost:8080/fhir tmp --system rxnorm --replace --keep-temporary --verboseDownload source files from the official terminology distributors:
- SNOMED CT US Edition: https://www.nlm.nih.gov/healthit/snomedct/us_edition.html
- LOINC: https://loinc.org/downloads/
- RxNorm: https://www.nlm.nih.gov/research/umls/rxnorm/
Docker Usage
The Docker image keeps fhir-controller as its entrypoint, so pass the same CLI subcommands after the image name.
# Get high-level subcommand help
docker run --rm p3000/fhir-controller-cli:latest help
# Poll and auto-import using a remote manifest URL
docker run --rm --pull always p3000/fhir-controller-cli:latest poll-auditevent-and-trigger-import http://fhir.example.com/fhir https://stack.foundry.hl7.org/stack.json -i 5
# Local manifest on the host: bind-mount the directory containing stack.json and referenced data files
docker run --rm --pull always -v /path/to/stack-dir:/manifest p3000/fhir-controller-cli:latest poll-auditevent-and-trigger-import http://fhir.example.com/fhir /manifest/stack.json
# Upload terminology systems
docker run --rm --pull always p3000/fhir-controller-cli:latest terminology import /data/loinc.csv http://fhir.example.com/fhir /tmp/staging --system loinc
docker run --rm --pull always p3000/fhir-controller-cli:latest terminology import /data/snomed/ http://fhir.example.com/fhir /tmp/staging --system snomed
docker run --rm --pull always p3000/fhir-controller-cli:latest terminology import /data/rxnorm.csv http://fhir.example.com/fhir /tmp/staging --system rxnorm
# Advanced terminology examples
docker run --rm --pull always p3000/fhir-controller-cli:latest terminology import /data/loinc.csv http://fhir.example.com/fhir /tmp/staging --system loinc --batch-size 2000 --verbose
docker run --rm --pull always p3000/fhir-controller-cli:latest terminology import /data/snomed/ http://fhir.example.com/fhir /tmp/staging --system snomed --keep-temporary --replace
docker run --rm --pull always p3000/fhir-controller-cli:latest terminology import /data/rxnorm.csv http://fhir.example.com/fhir /tmp/staging --system rxnorm --dry-runWhen using local data inside Docker, bind-mount every directory that the command must read or write, including manifest directories, terminology source directories, and staging directories.
MCP Server
The package includes an MCP server with two transports:
-
stdiofor local MCP clients that launch the binary directly. - Streamable HTTP for container deployments at
/mcp.
Build from Clone
cd /path/to/fhir-controller-cli
npm install
npm run buildConfigure a Local Stdio MCP Client
For JSON-style MCP clients that launch a local process, use:
{
"mcpServers": {
"fhir-controller": {
"command": "node",
"args": ["/path/to/fhir-controller-cli/build/bin/fhir-controller-mcp.js"]
}
}
}Run the HTTP MCP Server
Start the Streamable HTTP MCP server by passing the mcp subcommand:
fhir-controller mcp --port 8002In Docker, publish port 8002:
docker run --rm --pull always -p 8002:8002 p3000/fhir-controller-cli:latest mcpThe HTTP server uses Streamable HTTP, binds to 0.0.0.0 by default for container port publishing, and defaults to port 8002. Override with --host, --port, --path, or the FHIR_CONTROLLER_MCP_HOST, FHIR_CONTROLLER_MCP_PORT, and FHIR_CONTROLLER_MCP_PATH environment variables. The MCP server does not add authentication, so publish it only on trusted local interfaces or behind your own access controls.
MCP Tools and Safety
The MCP server exposes:
fhir_controller_infofhir_server_resetfhir_cql_evaluatefhir_synthea_uploadfhir_poll_auditevent_and_trigger_importfhir_terminology_import
Mutating tools default to dryRun: true; pass dryRun: false only when you intend to write to the target FHIR server.
Development and Testing
Build from Source
npm install
npm run buildRun the compiled CLI:
node build/bin/fhir-controller.js --helpDuring development, run the CLI from source:
tsx src/bin/fhir-controller.ts ...Test Suite
The Vitest suite includes unit/helper tests plus CLI integration coverage for fixture data under test/data/. By default, live CLI tests use a local FHIR server at http://127.0.0.1:8080/fhir/.
# Build the CLI used by integration tests
npm run compile
# Run all tests
npm test
# Override the FHIR test server URL
FHIR_CONTROLLER_TEST_FHIR_URL=http://127.0.0.1:8090/fhir/ npm test
# Skip live FHIR server tests, keeping dry-run and helper coverage
FHIR_CONTROLLER_SKIP_LIVE_TESTS=true npm testThe current fixture-backed live tests cover Synthea bundle upload, one-shot poll/import behavior, manifest loading, backwards-compatible scenario handling, and CQL Library import compatibility behavior. Terminology imports are not covered by bundled live fixtures because SNOMED, LOINC, and RxNorm source files are large licensed datasets.
Packaging
Create a local npm package tarball:
npm run packageThis software is released under the Apache 2.0 license. Copyright © 2017+ Preston Lee.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/preston/marketplace. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.