# Hen
Run API requests as files, from the command line or through MCP.
Hen keeps request definitions, assertions, captures, dependencies, and protocol-specific behavior in a single `.hen` file. It works well for local exploration, CI, and editor or agent integrations.
## Table of Contents
- [Quick Example](#quick-example)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [CLI](#cli)
- [MCP Server](#mcp-server)
- [Authoring Model](#authoring-model)
- [Examples](#examples)
## Quick Example
```text
name = Test Collection File
description = A collection of mock requests for testing this syntax.
$ API_KEY = $(./get_secret.sh)
$ USERNAME = $(echo $USER)
$ API_ORIGIN = https://lorem-api.com/api
$ ROLE = [admin, user, guest]
---
Some descriptive title for the prompt.
POST {{ API_ORIGIN }}/echo
* Authorization = {{ API_KEY }}
? query_param_1 = value
~~~ application/json
{
"username": "{{ USERNAME }}",
"password": "[[ password ]]",
"role": "{{ ROLE }}"
}
~~~
^ & status == 200
! sh ./callback.sh
```
This single file can define variables, prompt inputs, mapped requests, assertions, and callbacks. Add `> requires:` when later requests depend on earlier ones, and use response captures to thread values through a collection.
## Installation
```bash
cargo install hen
```
This installs both `hen` and `hen-mcp`.
### Container Image
The repository also ships a container image that includes both binaries.
Build it locally with Docker:
```bash
docker build -t hen:latest .
```
## Quick Start
Verify a collection without executing shell commands or network requests:
```bash
hen verify ./examples/lorem.hen
```
Run every request in a collection non-interactively:
```bash
hen run ./examples/lorem.hen all --non-interactive
```
Emit machine-readable output for scripts or CI:
```bash
hen run ./examples/lorem.hen all --non-interactive --output json
```
Run the same collection against a selected named environment:
```bash
hen run ./examples/environment_overrides.hen 0 --env local --non-interactive
```
Verify a collection that references local secret providers without loading the secrets:
```bash
hen verify ./examples/local_secrets.hen
```
## CLI
Hen has two primary commands:
- `hen run` executes a collection or request.
- `hen verify` parses and validates a collection without making requests.
The default `hen [PATH] [SELECTOR]` form still works for interactive terminal use, but `hen run ...` is the clearer choice for CI and automation.
### Selection and prompts
- If a directory contains one `.hen` file, Hen selects it automatically.
- If a collection contains multiple requests, provide an index or `all` to bypass the picker.
- The text CLI prompts for unresolved `[[ prompt ]]` placeholders.
- `--non-interactive` disables selection prompts and fails when required prompt values are missing.
- Use repeated `--input key=value` flags to provide prompt values up front.
- Use `--env <name>` to apply a named collection environment before request execution.
### Named environments
Collections can declare named environment overlays in the preamble to swap scalar variable values without editing requests.
```text
name = Example Collection
$ API_ORIGIN = https://api.example.com
$ CLIENT_ID = [[ client_id ]]
env local
$ API_ORIGIN = http://localhost:3000
$ CLIENT_ID = hen-local
---
Get profile
GET {{ API_ORIGIN }}/profile
* X-Client-Id = {{ CLIENT_ID }}
```
- Environment blocks are only valid in the preamble before the first `---`.
- Environment overrides can only target previously declared scalar variables in this milestone.
- If no environment is selected, Hen keeps the collection-level defaults.
- `hen verify` reports available environment names without selecting one.
- Structured run output reports the selected environment so CI artifacts show which overlay was used.
Resolution order is:
1. Collection preamble scalar assignments.
2. The selected named environment.
3. Explicit `--input key=value` or MCP `inputs` values for prompt placeholders.
4. Prompt defaults declared with `[[ name = default ]]`.
5. Runtime dependency captures and callback exports for downstream requests.
### Local secret providers
Hen supports local secret references inside scalar assignments.
```text
$ API_TOKEN = secret.env("HEN_API_TOKEN")
$ CLIENT_ID = secret.file("./secrets/client_id.txt")
```
- `secret.env("NAME")` reads one environment variable at run time.
- `secret.file("PATH")` reads one UTF-8 text file relative to the collection working directory and strips one trailing line ending.
- Repeated secret references are cached once per run after the first lookup.
- Secret references are only resolved during runs, not during `hen verify`.
- Secret reference arguments are string literals in this slice; interpolation inside `secret.env(...)` or `secret.file(...)` is intentionally out of scope.
- Supported providers in this slice are `env` and `file`. OS keychain remains a stretch goal and is not implemented.
### Safe output redaction
Hen automatically redacts values loaded through `secret.*(...)` plus built-in sensitive header names such as `Authorization`, `Proxy-Authorization`, `Cookie`, `Set-Cookie`, and API-key style headers across text, JSON, NDJSON, JUnit, transcripts, and retained artifacts.
Use preamble redaction rules to broaden that default policy without changing request execution:
```text
redact_header = X-Session-Token
redact_capture = SESSION_ID
redact_body = body.session.accessToken
```
- `redact_header = NAME` adds one header name to the built-in masked header set.
- `redact_capture = NAME` treats captured or exported values under that name as sensitive in the current request and in downstream dependent requests that reuse them.
- `redact_body = body.path` or `redact_body = json(body.payload).token` masks a specific response-body value even when you do not export it.
- Redaction rules are additive, only valid in the collection preamble before the first `---`, and are validated structurally by `hen verify`.
- See [examples/redaction_rules.hen](examples/redaction_rules.hen) for a concrete collection.
### HTTP session cookies
Ordinary HTTP requests can reuse cookies by sharing the same `session = ...` handle:
```text
Login
session = web
POST https://httpbin.org/cookies/set/session/hen-demo
# Login request succeeds
^ & status == 200
---
Load cookies
session = web
GET https://httpbin.org/cookies
# Reuses the session cookie
^ & body.cookies.session == "hen-demo"
```
- Requests that share the same HTTP session reuse one cookie jar for that run.
- Session reuse also adds the same implicit ordering Hen already uses for other session-backed protocols, so login-before-profile flows stay deterministic.
- Built-in redaction already masks `Cookie` and `Set-Cookie` headers across text, JSON, NDJSON, JUnit, transcripts, and retained artifacts.
- See [examples/http_cookie_session.hen](examples/http_cookie_session.hen) for a concrete collection.
### OAuth profiles
Hen can acquire and reuse OAuth tokens from named preamble profiles.
```text
name = OAuth Client Credentials
$ API_ORIGIN = https://api.example.com
oauth api
grant = client_credentials
issuer = https://login.example.com
client_id = secret.env("HEN_CLIENT_ID")
client_secret = secret.env("HEN_CLIENT_SECRET")
scope = read:profile
param audience = {{ API_ORIGIN }}
access_token -> $API_ACCESS_TOKEN
token_type -> $API_TOKEN_TYPE
---
Get profile
auth = api
GET {{ API_ORIGIN }}/me
* X-Token-Type = {{ API_TOKEN_TYPE }}
```
- `oauth <name>` blocks are only valid in the collection preamble before the first `---`.
- Requests attach a profile with `auth = <name>` and Hen lazily acquires or reuses the token immediately before dispatch.
- Supported grants in this slice are `client_credentials` and `refresh_token`.
- Use exactly one endpoint source per profile: `issuer = ...` for OIDC discovery or `token_url = ...` for a direct token endpoint.
- `param name = value` adds extra token form fields such as `audience` or `resource`.
- `<field> -> $VARIABLE` maps token-response fields into ordinary scalar exports that the current request and downstream dependent requests can reuse.
- Hen injects `Authorization: Bearer ...` automatically unless the request already sets `Authorization` explicitly.
- Discovery and token acquisition only happen during `hen run`, never during `hen verify`.
- Access tokens, refresh tokens, and injected authorization headers stay redacted across text output, structured reports, transcripts, and retained artifacts.
- See [examples/oauth_client_credentials.hen](examples/oauth_client_credentials.hen) and [examples/oauth_refresh_token.hen](examples/oauth_refresh_token.hen) for concrete collections.
### Conditional guards
Use a bracketed predicate to conditionally run an assertion or include a fragment.
```text
[ USERNAME == "foo" ] ^ & body.username == "foo"
[ FEATURE_ENABLED != true ] << ./fragments/legacy_assertions.hen
```
- Guards can prefix assertions or `<<` fragment imports.
- A false assertion guard skips that assertion without failing the request.
- A false fragment guard skips that import entirely.
- Guard predicates use the same comparison and regex operators as ordinary assertions, except schema validation with `===` is not supported in guards.
- See [examples/conditionals.hen](examples/conditionals.hen) for a runnable assertion-guard example.
### Useful options
- `--body none|selected|failed|all` controls how much of the request and response bodies appear in text output and retained artifacts.
- `--output text|json|ndjson|junit` selects human or machine-readable output.
- `--parallel` runs independent requests concurrently.
- `--max-concurrency N` throttles parallel execution.
- `--continue-on-error` keeps unaffected dependency branches running.
- `--benchmark N` benchmarks a request instead of running it once.
- `--export` renders the request as a curl command.
- `--verbose` includes more detail in text output and enables debug logging.
### Schema validation
Hen supports collection-local `scalar` and `schema` declarations plus built-in targets such as `UUID`, `EMAIL`, `NUMBER`, `DATE`, `DATE_TIME`, `TIME`, and `URI`.
```text
scalar HANDLE = string & len(3..24) & pattern(/^[a-z][a-z0-9_]*$/)
schema User {
id: UUID
email: EMAIL
handle: HANDLE
}
```
For the full declaration grammar and more examples, see [syntax-reference.md](syntax-reference.md) and the schema examples under [examples/](examples/).
### Assertion labels
Use a `# ...` comment directly above an assertion to annotate it with a short intent label:
```text
# The page loads
^ & status == 200
```
- Successful text output prints the label, for example `✅ [Fetch fixture] [The page loads]`.
- The comment labels only the assertion on the very next line.
- Blank lines or other request items break the association.
- Labels are literal text and are not interpolated.
## MCP Server
Hen also ships with `hen-mcp`, a stdio MCP server for editors and agents. It is intentionally non-interactive:
- use `hen` for terminal-first, prompt-driven workflows
- use `hen-mcp` when an MCP client needs structured access
- provide any `[[ prompt ]]` values explicitly through tool inputs
### Running the server
If `hen-mcp` is on your `PATH`:
```bash
hen-mcp
```
From a checkout of this repository:
```bash
cargo run --bin hen-mcp
```
For normal MCP client usage, prefer a compiled binary:
```bash
cargo build --release
./target/release/hen-mcp
```
### Example configuration
VS Code uses `.vscode/mcp.json`:
```json
{
"servers": {
"hen": {
"type": "stdio",
"command": "/absolute/path/to/hen-mcp"
}
},
"inputs": []
}
```
Claude Code uses `.mcp.json`:
```json
{
"mcpServers": {
"hen": {
"type": "stdio",
"command": "/absolute/path/to/hen-mcp"
}
}
}
```
If `hen-mcp` is installed globally, `command` can simply be `hen-mcp`.
### Exposed MCP surface
- `run_hen`: run a collection or request non-interactively
- `verify_hen_syntax`: validate a file or inline source without execution
- `get_hen_authoring_guide`: return built-in usage or syntax docs
- `hen://authoring-guide` and `hen://readme`: built-in resources for clients that read docs directly
`run_hen` accepts the same explicit environment selection used by the CLI:
```json
{
"path": "./examples/environment_overrides.hen",
"selector": "0",
"environment": "staging",
"inputs": {
"client_id": "ci-client"
}
}
```
## Authoring Model
Hen files are plain text collections made of a preamble and one or more requests separated by `---`.
### Core concepts
- Variables: `$ NAME = value`, shell substitutions with `$(...)`, local secret references such as `secret.env("NAME")` and `secret.file("./path")`, prompt placeholders with `[[ name ]]`, and simple arrays for mapped requests.
- Named environments: `env <name>` blocks in the preamble override previously declared scalar variables for a selected run.
- OAuth profiles: preamble `oauth <name>` blocks plus request-level `auth = <name>` provide lazy token acquisition and mapped token exports.
- Redaction rules: preamble-level `redact_header`, `redact_capture`, and `redact_body` extend the default safe-output masking policy without changing request behavior.
- Requests: HTTP by default, plus explicit `protocol = graphql`, `protocol = mcp`, `protocol = sse`, and `protocol = ws`.
- Captures: `& body.token -> $TOKEN` stores response data for later requests, assertions, and callbacks.
- Assertions: `^` lines validate status, headers, body fields, structural JSON matches, and schema targets.
- Dependencies: `> requires: Request Name` creates a DAG so setup requests run before dependents.
- Fragments: `<< file.hen` reuses shared request snippets or declarations.
- Callbacks: `!` lines run shell commands after request execution.
- Guards: `[predicate]` can gate assertions or fragment imports.
### Protocol support
- HTTP: ordinary request and response workflows.
- GraphQL: GraphQL-over-HTTP with `operation`, `variables`, and `~~~graphql` documents.
- MCP: MCP-over-HTTP authoring with generated JSON-RPC envelopes and reusable sessions.
- SSE: named streaming sessions with `receive` steps and timeout windows.
- WebSocket: `open`, `send`, `exchange`, and `receive` flows over a named session.
### Full syntax guide
The complete authoring grammar lives in [syntax-reference.md](syntax-reference.md). That file covers:
- variables and prompts
- headers, query parameters, form data, and body blocks
- protocol-specific directives
- declarations, fragments, captures, assertions, guards, callbacks, and dependencies
## Examples
The fastest way to learn the format is to run the included examples:
- [examples/lorem.hen](examples/lorem.hen): basic HTTP requests
- [examples/environment_overrides.hen](examples/environment_overrides.hen): named environment overlays
- [examples/oauth_client_credentials.hen](examples/oauth_client_credentials.hen): client-credentials auth with mapped token fields
- [examples/oauth_refresh_token.hen](examples/oauth_refresh_token.hen): refresh-token auth with downstream reuse
- [examples/conditionals.hen](examples/conditionals.hen): conditional assertions with guard predicates
- [examples/local_secrets.hen](examples/local_secrets.hen): local env and file secret providers
- [examples/redaction_rules.hen](examples/redaction_rules.hen): additive safe-output redaction rules
- [examples/json_response_captures.hen](examples/json_response_captures.hen): captures and JSON paths
- [examples/filtered_selector_variables.hen](examples/filtered_selector_variables.hen): selector variables in captures and assertions
- [examples/schema_scalar_checks.hen](examples/schema_scalar_checks.hen): scalar validation
- [examples/schema_object_validation.hen](examples/schema_object_validation.hen): object schema validation
- [examples/schema_root_array_validation.hen](examples/schema_root_array_validation.hen): root-array schemas
- [examples/schema_fragment_reuse.hen](examples/schema_fragment_reuse.hen): declaration reuse through fragments
- [examples/structural_json_matching.hen](examples/structural_json_matching.hen): structural JSON assertions
- [examples/graphql_protocol.hen](examples/graphql_protocol.hen): GraphQL authoring
- [examples/mcp_protocol.hen](examples/mcp_protocol.hen): MCP-over-HTTP sessions
- [examples/sse_protocol.hen](examples/sse_protocol.hen): server-sent events
- [examples/ws_protocol.hen](examples/ws_protocol.hen): WebSocket sessions