@healthcloudai/hc-cli
v3.1.2
Published
HealthCloud MCP command-line interface — one `hc mcp <audience> <command>` per MCP gateway tool, generated from the shared tool manifest.
Maintainers
Readme
@healthcloudai/hc-cli
hc is the HealthCloud MCP command-line interface. It exposes every MCP
gateway tool as exactly one CLI command — hc mcp <audience> <command> —
generated dynamically at startup from a bundled, checksummed tool
manifest (contracts/mcp-tools.manifest.json, synced from
packages/cli/contracts at build time). There are no per-tool generated
source files: the entire command tree is built by iterating that manifest
each time hc runs — run hc mcp tools list to see the exact, current set
for your installed version.
Install
npm install -g @healthcloudai/hc-cli(Inside this monorepo, packages/cli/npm is an npm workspace member —
npm install at the repo root links it against the local
@healthcloudai/hc-sdk build.)
Authentication & configuration
Every option below can be set as a --flag, an environment variable, or a
named entry in an optional profiles file. Precedence (highest wins):
explicit CLI flag > environment variable > profile file value > built-in default| Flag | Env var | Profile key | Default |
|-------------------|--------------------|---------------|-----------|
| --access-token | HC_ACCESS_TOKEN | accessToken | (unset) |
| --base-url | HC_MCP_BASE_URL | baseUrl | (unset — SDK resolves https://dev-api-mcp.health.cloud-style URLs from environment) |
| --profile | HC_PROFILE | — | (unset) |
| — (profile only) | — | environment | dev |
--base-url/HC_MCP_BASE_URL only overrides the MCP gateway's base URL —
this CLI is MCP-only and never talks to the other HealthCloud services
directly.
Profiles file
~/.healthcloud/cli/profiles.json is entirely optional — a missing file is
not an error:
{
"dev-local": { "baseUrl": "http://localhost:8080", "environment": "dev" },
"staging": { "baseUrl": "https://uat-api-mcp.health.cloud", "accessToken": "..." }
}# get-patient is self-service — the patient is resolved from the access
# token, so no --patient-id flag exists.
hc mcp patient get-patient --profile stagingA --profile/HC_PROFILE name that doesn't match any entry (or the file
doesn't exist at all) is a non-fatal no-op — it just contributes no
overrides, rather than failing every command that happens to inherit a
stale HC_PROFILE.
The resolved access token is never written to any log, generated file, or package artifact.
Discovering tools
# Every tool in the bundled manifest, grouped by audience — no network call.
hc mcp tools list
# One audience only.
hc mcp tools list --audience patient
# Machine-readable summary (name, cliCommand, description, method, path).
hc mcp tools list --audience patient --output json
# Ask the *live* gateway what it actually serves for one audience
# (--audience is required with --remote — hitting all 12 audiences on every
# invocation would be wasteful).
hc mcp tools list --remote --audience patient --access-token "$HC_ACCESS_TOKEN"
# Full manifest entry for one tool (description, inputSchema, method/path).
# <tool-name> is the snake_case MCP tool name, e.g. add_insurance — not the
# kebab-case CLI command.
hc mcp tools show patient add_insurance --output json--help on any generated command shows the manifest's own description, so
it's always current:
hc mcp patient get-patient --helpUsage: hc mcp patient get-patient [options]
Get the authenticated patient's full FHIR R4 record including name, date of
birth, sex, addresses, and contact details.
Options:
--input <file> Path to a JSON file of base arguments (explicit
flags override matching keys)
--stdin Read base arguments as JSON from stdin (explicit
flags override matching keys)
-h, --help display help for commandCalling a tool
# get-patient is self-service — no id argument; the patient is resolved
# server-side from the access token.
hc mcp patient get-patient --access-token "$HC_ACCESS_TOKEN"
hc mcp appointments book-appointment \
--patient-id patient-123 --slot-id slot-456 \
--access-token "$HC_ACCESS_TOKEN"
# Every Diagnostics tool takes an explicit --patient-id — providers and
# field agents legitimately look up or record diagnostics for a patient
# other than themselves. When the caller holds a patient token, the backend
# enforces server-side that --patient-id must be their own (403 otherwise).
hc mcp diagnostics create-rapid-test \
--patient-id patient-123 --test-type covid-19 --result negative \
--access-token "$HC_ACCESS_TOKEN"Argument mapping
Every manifest tool's inputSchema.properties becomes CLI flags, derived
straight from the JSON Schema — nothing hand-written per tool:
primitive (
string/number/integer/boolean) → a normal kebab-case flag, e.g.patient_id→--patient-id <value>.number/integerflags are parsed and range-checked against the schema'sminimum/maximum/exclusiveMinimum/exclusiveMaximumbefore anything is sent.enumstring properties are validated against the allowed values by commander itself (.choices()); an invalid value is rejected with the full allowed list, before any network call.booleanproperties take an optional value:--include-content(bare flag =true) or--include-content false/--include-content true.- a schema
defaultis applied only when neither--input/--stdinnor an explicit flag supplies that key.
everything else (
object,array, a uniontypelike["string","integer"], oroneOf/anyOfschemas with no singletype) → a--<prop>-json <json>flag carrying a raw JSON string, parsed withJSON.parse— nested properties are never flattened into their own flags:# create-medication is self-service — no --patient-id flag; the patient is # resolved server-side from the access token. hc mcp patient create-medication \ --name Amoxicillin \ --route oral \ --dosage-json '{"value":10,"unit":"mg","frequency":"once_daily"}' \ --access-token "$HC_ACCESS_TOKEN"a property listed in the manifest's
parameters.path(i.e. it's a{placeholder}in the tool's HTTP path) is always required, even if the tool'sinputSchema.requireddoesn't happen to repeat it.
--input / --stdin (per-tool commands)
Every generated hc mcp <audience> <command> also accepts:
--input <file>— a file path to a JSON object of base arguments.--stdin— read a JSON object of base arguments from stdin.
Exactly one of these may be used (using both is a validation error, exit
2, no network call). Merge precedence:
manifest schema defaults < --input/--stdin object < explicit flagsi.e. an explicit --patient-id on the command line always wins over a
patient_id key in the --input file, which in turn wins over that
property's schema default.
hc mcp appointments book-appointment --input request.json
echo '{"patient_id":"patient-123","slot_id":"slot-456"}' | hc mcp appointments book-appointment --stdinhc mcp call — the generic escape hatch
hc mcp call <audience> <exact-mcp-tool-name> calls any tool by its exact
MCP name (the snake_case name field, e.g. get_patient) without going
through a generated command — including a tool that isn't in the bundled
manifest yet (nothing about <exact-mcp-tool-name> is validated locally;
it's forwarded to the gateway as-is). <audience> still must be one of the
12 known gateway routes.
# get_patient is self-service and takes no arguments — {} is a complete,
# valid call.
hc mcp call patient get_patient --input '{}'Important — --input means something different here than on the
generated per-tool commands. This is an intentional, spec'd
inconsistency:
| Command | --input meaning |
|-----------------------------------|---------------------------------|
| hc mcp <audience> <command> | a file path |
| hc mcp call <audience> <tool> | an inline JSON value |
hc mcp call also has --input-file <path> (the file-path equivalent) and
--stdin. At most one of --input / --input-file / --stdin may be
given; omitting all three sends {}.
hc mcp call patient get_patient --input-file request.json
hc mcp call patient get_patient --stdin < request.jsonOutput modes
--output pretty|json|raw (default pretty), or --raw as a shortcut for
--output raw (wins if both are given).
Every tools/call response is the JSON-RPC envelope
{ jsonrpc, id, result: { content: [{type:"text", text}], isError } } or
{ jsonrpc, id, error: { code, message } }:
pretty(default) — ifcontent[0].textparses as JSON, pretty-print the parsed value; otherwise print the raw text. A JSON-RPCerrorprintserror.messageto stderr (pluserror.codewith--verbose) — nothing is written to stdout.json— the tool result's parsed JSON (or raw text if it doesn't parse) printed to stdout, with no envelope.raw— the complete JSON-RPC envelope exactly as the gateway sent it.
Diagnostics and verbose logs always go to stderr — stdout stays
pipeable (hc mcp patient get-patient --output json | jq .).
Exit codes
| Code | Meaning |
|------|--------------------------------------------------------------------------|
| 0 | Success |
| 1 | SDK/network failure (SDKError — includes "access token required") |
| 2 | Local input validation failure — bad JSON, missing required argument, invalid enum/number, --input+--stdin together, unknown option. Never reaches the network. |
| 3 | JSON-RPC error response from the gateway |
| 4 | The tool ran but returned result.is_error === true |
Development
npm install # from the repo root (links the workspace)
npm run typecheck --workspace packages/cli/npm
npm run build --workspace packages/cli/npm # -> dist/bin/hc.js
npm run test --workspace packages/cli/npm
node packages/cli/npm/dist/bin/hc.js --helpThe manifest bundled into this package (contracts/mcp-tools.manifest.json)
is copied from packages/cli/contracts/mcp-tools.manifest.json by
scripts/copy-manifest.cjs, which runs automatically as an npm
pre-script before build, dev, and test. See
packages/cli/contracts/README.md for how that source-of-truth file itself
gets synced from healthcloud-services.
