Skip to content

MCP Server Guide

@humanauth/mcp is a Model Context Protocol (MCP) server that exposes three HumanAuth tools to any MCP-compatible agent — Claude Code, Cursor, or any client that speaks stdio. No code changes: add the server to your client config and your agent can put a human in the loop before it acts.

  • human_authorize — request a binary approve/deny from a human or group. Blocks until the request is decided, expires, or is cancelled, then returns the decision plus a receipt id.
  • human_collect — request data from a human. Blocks until they respond. Schema-driven forms are not shipped yet — see Not yet supported.
  • human_inform — send a fire-and-forget notification. Returns immediately without waiting for a response.

The server talks to the HumanAuth platform over HTTPS with your tenant API key, runs over stdio, and keeps no local state.

  1. A HumanAuth tenant and API key (ha_…) — see HumanAuth in 5 Minutes.
  2. An MCP-compatible agent (Claude Code, Cursor, or any stdio MCP client).

The server reads your API key from the HUMANAUTH_API_KEY environment variable and exits at startup if it is unset — the process prints a setup hint and returns a non-zero status. Provide the key through your client’s env block:

{
"mcpServers": {
"humanauth": {
"command": "npx",
"args": ["@humanauth/mcp"],
"env": { "HUMANAUTH_API_KEY": "ha_live_…" }
}
}
}

The three keys — command: "npx", args: ["@humanauth/mcp"], and the HUMANAUTH_API_KEY env — are the same for every client. Only the config file location differs:

  • Claude Code — ~/.claude/settings.json
  • Cursor — .cursor/mcp.json in your project root
  • Any other stdio MCP client — wherever it stores server definitions

To smoke-test it directly:

Terminal window
HUMANAUTH_API_KEY=ha_live_… npx @humanauth/mcp

On a healthy start it produces no output — it waits for an MCP client over stdin/stdout (JSON-RPC). With the key missing it prints the required-variable message and exits.

Each tool returns its result as a single text content block whose text is a JSON string; the agent reads the fields described below out of that JSON. Errors return the same way with the block flagged isError.

Request binary authorization from a human or group. Blocks until the target approves or denies, the request expires, or it is cancelled.

Parameters

ParameterTypeRequiredDefaultDescription
targetstringYes—HumanID (hu_…) or GroupID (grp_…) to request authorization from
actionstringYes—Short action identifier (e.g. deploy-to-prod, send-email)
descriptionstringYes—Human-readable explanation of what you want to do and why
severity"low" | "medium" | "high" | "critical"No"medium"How sensitive/risky the action is; higher severities show more prominently to the human
ttlnumberNo300Timeout in seconds (min 10, max 3600)

Returns — a JSON object in the text block:

  • Decided — decision ("approved" or "denied"), request_id, receipt_id, decisions (per-approver detail), and decided_at.
  • Expired — decision: "expired", request_id, and a message.
  • Cancelled — decision: "cancelled" and request_id.
  • Error — error (code), status (HTTP status, when applicable), and message; timeouts return error: "timeout".

Example decided result:

{
"decision": "approved",
"request_id": "req_a1b2c3d4e5f6a7b8",
"receipt_id": "rct_a1b2c3d4e5f6a7b8",
"decisions": [
{
"human_id": "hu_karthick_7x2f",
"decision": "approved",
"device_id": "dev_a1b2c3d4e5f6a7b8",
"decided_at": "2026-08-10T17:04:22Z",
"reason": null
}
],
"decided_at": "2026-08-10T17:04:22Z"
}

receipt_id is the handle to the signed receipt. To actually gate the action, forward it (or the receipt JWS) to your resource server and verify it there with @humanauth/verifier.


Request data from a human. Blocks until they respond, the request expires, or it is cancelled.

Parameters

ParameterTypeRequiredDefaultDescription
human_idstringYes—HumanID (hu_…) to collect data from
actionstringYes—Short action identifier
descriptionstringYes—What data you need and why
severity"low" | "medium" | "high" | "critical"No"medium"How sensitive the request is
ttlnumberNo600Timeout in seconds (min 10, max 3600)

Returns — request_id, status ("decided" | "expired" | "cancelled"), result ("approved" | "denied" | "expired"), and receipt_id.


Send a notification to a human. Fire-and-forget — returns as soon as the notification is dispatched, without waiting for acknowledgement.

Parameters

ParameterTypeRequiredDefaultDescription
human_idstringYes—HumanID (hu_…) to notify
actionstringYes—Context identifier (e.g. job-complete, payment-processed)
descriptionstringYes—The notification message
severity"low" | "medium" | "high" | "critical"No"low"Notification urgency

There is no ttl parameter — inform does not wait for a response.

Returns — ok: true, request_id, and a message:

{ "ok": true, "request_id": "req_a1b2c3d4e5f6a7b8", "message": "Notification sent" }

A human takes seconds to respond — sometimes hours — and no LLM tool call can sit blocked that long. So in @humanauth/mcp@0.3.0, human_authorize and human_collect are task tools under the MCP Tasks extension (SEP-2663, io.modelcontextprotocol/tasks), declared with taskSupport: "optional":

  • A Tasks-capable client gets back a task handle (resultType: "task" plus a taskId) and polls tasks/get until the request reaches a terminal state — no single call is held open across the whole approval window.
  • A client without Tasks is served synchronously by the server: the call blocks until the request is decided, expires, or is cancelled, exactly as described above.

Either way the outcome is the same. When the task completes, its result carries the signed receipt (receipt_jws) — forward it to your resource server and verify it with @humanauth/verifier before anything irreversible runs.

All three tools take a severity of low, medium, high, or critical; higher severities surface more prominently to the human. Reach for the higher end on production changes and irreversible or security-sensitive actions, and the lower end on routine or informational ones. The default is medium for human_authorize and human_collect, and low for human_inform.

VariableRequiredDefaultDescription
HUMANAUTH_API_KEYYes—Tenant API key (ha_…). The server exits at startup if it is unset.
HUMANAUTH_BASE_URLNohttps://api.humanauth.aiOverride the platform base URL (self-hosting or testing).
  • Schema-driven collect. human_collect is a stub over authorize (intent=collect); it accepts no field schema and returns an approve/deny-shaped result. There are no select / number / checkbox form fields.
  • Groups on collect and inform. Only human_authorize accepts a group target (grp_…). human_collect and human_inform take a single human_id.
  • Notification categories. human_inform has no category or type parameter; it sends the message with the given severity.
  • Enforcement. As noted above, these tools request and report — they do not gate. Pair them with @humanauth/verifier at the resource server.

Server exits immediately (“HUMANAUTH_API_KEY environment variable is required”)

Section titled “Server exits immediately (“HUMANAUTH_API_KEY environment variable is required”)”

The key is not reaching the process. Confirm the env block in your MCP config, or export HUMANAUTH_API_KEY before launching the server manually.

Check the config path for your client and that npx @humanauth/mcp resolves — the first run downloads the package. Launch it manually with the key set to confirm it starts cleanly; if it does, the problem is in the client configuration.

human_authorize and human_collect block until the human responds or the TTL elapses. Make sure the target hu_… / grp_… has an enrolled approver device with notifications enabled, and raise ttl (up to 3600 seconds) for slower approvers.