AgentStack
MCP verified MIT Self-run

Hitl Mcp

mcp-zenlixai-hitl-mcp · by ZenlixAI

A production-ready, state-persistent MCP server for Human-in-the-Loop (HITL) interaction. Enables AI agents to suspend execution, ask structured questions to users, and resume contextually across sessions — friendly with ACP.

No reviews yet
0 installs
3 views
0.0% view→install

Install

$ agentstack add mcp-zenlixai-hitl-mcp

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Hitl Mcp? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

hitl-mcp

Question-only HITL MCP server for agent workflows.

[](https://www.typescriptlang.org/) [](https://modelcontextprotocol.io/) [](LICENSE)

[English](README.md) | [中文](README-zh.md)


Table of Contents

  • [Background](#background)
  • [What hitl-mcp is](#what-hitl-mcp-is)
  • [Goals](#goals)
  • [Non-goals](#non-goals)
  • [Core model](#core-model)
  • [Quick start](#quick-start)
  • [How interaction works](#how-interaction-works)
  • [MCP tools](#mcp-tools)
  • [HTTP API](#http-api)
  • [Runtime configuration](#runtime-configuration)
  • [How it works internally](#how-it-works-internally)
  • [Architecture](#architecture)
  • [Operations](#operations)
  • [Project structure](#project-structure)

Background

In many Agent systems, the Agent eventually reaches a point where it cannot continue autonomously:

  • a human must approve or reject a decision
  • a human must choose one option from several candidates
  • a human must provide missing business input
  • a workflow must pause until manual confirmation arrives

Without a dedicated HITL layer, these workflows are usually implemented with ad hoc prompts, side-channel UIs, or custom callback protocols. That creates three recurring problems:

  1. The Agent and the application do not share a stable model for "pending human input".
  2. Human answers are hard to correlate with the exact Agent run and session that requested them.
  3. Waiting, partial submission, cancellation, and recovery semantics become inconsistent across clients.

hitl-mcp solves this by exposing a narrow, explicit contract for human questions:

  • Agents create questions
  • clients or operator UIs read pending questions
  • humans answer, skip, or cancel them
  • Agents wait on a caller-scoped state machine until work is complete

What hitl-mcp is

hitl-mcp is a question-oriented human-in-the-loop server built on MCP and HTTP.

It provides two access surfaces over the same underlying state:

  • MCP tools for Agents and Agent platforms
  • HTTP APIs for operator consoles, backends, or custom approval UIs

The public abstraction is intentionally small:

  • the public unit is question
  • questions belong to a caller scope
  • answers may be submitted incrementally
  • waiting is modeled as a scope-level operation, not a question-level long poll

The server is designed for workflows where the Agent is the initiator, but a human completes part of the decision loop.


Goals

  • Provide a stable, minimal HITL contract for Agent workflows.
  • Make caller isolation explicit through agent_identity and agent_session_id.
  • Support multiple pending questions in the same caller scope.
  • Support partial progress instead of forcing an all-at-once final submission.
  • Allow MCP and HTTP clients to operate on the same underlying question state.
  • Keep storage pluggable so local development can use memory and production can use Redis.
  • Keep operational behavior observable through health, readiness, metrics, and structured logs.

Non-goals

  • Not a generic workflow engine.
  • Not a UI framework for approval consoles.
  • Not a task queue or event bus.
  • Not a general-purpose form builder.
  • Not a policy engine for access control and reviewer assignment.
  • Not a durable orchestration platform for arbitrary business processes.

hitl-mcp only manages question state, caller scoping, waiting semantics, and answer submission.


Core model

Public unit: question

Externally, the system only exposes question.

A question has:

  • a server-generated question_id
  • a type
  • prompt metadata such as title, description, tags, and extra
  • for single_choice, each option may also declare followup_fields
  • an optional default_answer
  • a status such as pending, answered, skipped, or cancelled

Supported question types:

  • single_choice
  • multi_choice
  • text
  • boolean
  • range

Caller scope

Every operation is scoped by:

  • agent_identity
  • agent_session_id

This scope is the isolation boundary for:

  • creating questions
  • listing pending questions
  • waiting for progress
  • submitting answers
  • cancelling questions

Two Agents can ask identical questions without colliding as long as their caller scopes differ.

Internal groups vs public API

The storage layer may still use grouping internally, but grouping is an implementation detail.

The public API is intentionally question-first:

  • create questions
  • fetch pending questions
  • submit answers by question_id
  • cancel by question_id
  • wait on the scope

Partial submission

Answers do not need to arrive in one batch.

The server accepts incremental progress:

  • answer one question now
  • answer another question later
  • skip optional questions explicitly
  • continue waiting until the scope becomes complete

Answer payloads remain backward compatible:

  • existing callers may continue sending { value }
  • when a selected single_choice option declares followup_fields, callers may send { value, fields }
  • required followup_fields are validated against the selected option

Timeout auto-response

Question groups may also define timeout behavior at creation time.

  • callers may set timeout_seconds
  • if omitted, the server uses HITL_PENDING_DEFAULT_TIMEOUT_SECONDS
  • each question may set default_answer
  • if omitted, the server derives one by question type

Timed-out pending questions are automatically answered with their resolved default answers.

  • in memory mode, this works for single-process deployments via an in-process timeout worker
  • in redis mode, this works across instances via Redis-backed timeout processing

The derived defaults are:

  • single_choice: first option value
  • multi_choice: first option value as a single-element array
  • boolean: true
  • range: range_constraints.min
  • text: "none"

Timeout-generated answers keep the normal answered status and add metadata such as:

  • auto_response_at
  • is_timeout_auto_response

Wait modes

hitl_wait and the equivalent scope-level wait behavior support two modes:

  • terminal_only: return only when the scope has no pending questions left
  • progressive: return after every state change, then let the caller wait again

terminal_only is simpler for linear workflows. progressive is better when the caller needs to react to each intermediate update.

When hitl_wait is called through MCP, it also emits a progress notification every 30 seconds while the wait is still pending.


Quick start

Installation

git clone 
cd hitl-mcp
npm install

Run in development

npm run dev

Default local bind:

  • HTTP base URL: http://0.0.0.0:3000
  • MCP base URL: http://0.0.0.0:3000/mcp
  • HTTP API prefix: /api/v1

Run with Docker

Build the image:

docker build -t hitl-mcp .

Run with in-memory storage:

docker run --rm -p 3000:3000 \
  -e MCP_URL=http://localhost:3000 \
  hitl-mcp

Run with Redis:

docker run --rm -p 3000:3000 \
  -e MCP_URL=http://localhost:3000 \
  -e HITL_STORAGE=redis \
  -e HITL_REDIS_URL=redis://host.docker.internal:6379 \
  hitl-mcp

Run from source with environment variables

export MCP_URL=http://localhost:3000
npm run dev

Minimal create request

curl -X POST "http://localhost:3000/api/v1/questions" \
  -H "Content-Type: application/json" \
  -H "x-agent-identity: agent/example" \
  -H "x-agent-session-id: session-123" \
  -d '{
    "title": "Outline confirmation",
    "questions": [
      {
        "type": "single_choice",
        "title": "Confirm the current outline",
        "options": [
          { "value": "approved", "label": "Approved" },
          {
            "value": "revise",
            "label": "Revise",
            "followup_fields": [
              {
                "id": "comment",
                "type": "text",
                "label": "Revision comment",
                "required": true,
                "description": "Explain what should change."
              }
            ]
          }
        ]
      }
    ]
  }'

How interaction works

This section describes the intended runtime flow, regardless of whether the caller uses MCP tools or HTTP APIs.

Sequence 1: standard ask -> wait -> answer -> complete

  1. The Agent creates one or more questions in its caller scope.
  2. The Agent immediately calls hitl_wait.
  3. A UI or backend fetches pending questions for the same caller scope.
  4. A human answers one or more pending questions.
  5. The Agent waits on the scope.
  6. When the scope has no pending questions left, wait returns a terminal result.

Sequence 2: partial submission

  1. The Agent creates multiple questions.
  2. The human answers only a subset.
  3. The server persists those answers and keeps the remaining questions pending.
  4. The Agent can keep waiting.
  5. Additional submissions continue until the scope is complete.

Sequence 3: progressive wait

  1. HITL_WAIT_MODE=progressive
  2. The Agent calls hitl_wait.
  3. Any answer, skip, or cancellation in the scope wakes the waiter.
  4. The wait result reports which question_ids changed.
  5. The Agent decides whether to continue waiting or act on the intermediate update.

Sequence 4: cancellation

  1. A caller cancels one question or all pending questions in the scope.
  2. The server updates scope state and notifies waiters.
  3. If no pending questions remain, the scope becomes terminal.

Sequence 5: timeout auto-response

  1. The Agent creates questions with explicit or implicit timeout behavior.
  2. In Redis mode, the server schedules the pending group for timeout processing.
  3. If no human response arrives before the deadline, a Redis-backed timeout worker auto-answers remaining pending questions.
  4. The worker publishes a scope update so waiters on any instance can wake up.
  5. hitl_wait returns the completed scope snapshot with timeout metadata on affected questions.

Scope semantics

Wait is always a scope-level operation.

This is deliberate:

  • one Agent run may have multiple pending questions
  • the Agent usually cares whether the workflow can continue
  • scope-level wait avoids fragmented per-question synchronization logic

Operational rule:

  • after every hitl_ask, the next HITL tool call must be hitl_wait
  • do not treat hitl_ask as completion
  • do not substitute hitl_get_pending_questions for the first post-ask wait

MCP tools

hitl-mcp exposes the following MCP tools:

hitl_ask

Creates one or more questions for the current caller scope.

Input shape:

{
  "title": "Release decision",
  "description": "Human approval required before deploy",
  "ttl_seconds": 3600,
  "timeout_seconds": 900,
  "questions": [
    {
      "type": "boolean",
      "title": "Approve deployment?",
      "default_answer": { "value": true }
    }
  ]
}

Notes:

  • question_id must not be provided by the caller
  • the server generates question_id
  • one request can create multiple questions
  • explicit default_answer is recommended
  • if default_answer is omitted, the server derives one
  • if timeout_seconds is omitted, the server uses the configured default timeout

hitl_wait

Waits on the current caller scope.

Typical response fields:

  • status
  • is_terminal
  • changed_question_ids
  • pending_questions
  • resolved_questions
  • answered_question_ids
  • skipped_question_ids
  • cancelled_question_ids
  • is_complete

resolved_questions contains the full resolved question objects plus their final status and any stored answer.

When a result was produced by timeout automation, the resolved question may also include:

  • auto_response_at
  • is_timeout_auto_response

When called over MCP, hitl_wait also sends notifications/progress updates every 30 seconds while still waiting.

hitl_get_pending_questions

Returns all pending questions in the current caller scope.

hitl_submit_answers

Submits new answers and optional skips.

Input shape:

{
  "answers": {
    "q_01JXYZ...": { "value": true }
  },
  "skipped_question_ids": ["q_01JABC..."],
  "idempotency_key": "idem-1"
}

Notes:

  • answers may contain any subset of pending questions
  • optional questions may be skipped explicitly
  • required questions cannot be skipped
  • submissions accumulate server-side

hitl_cancel_questions

Cancels selected pending questions or all pending questions in the scope.

Input shape:

{
  "question_ids": ["q_01JXYZ..."],
  "reason": "no longer needed"
}

Or:

{
  "cancel_all": true
}

hitl_get_question

Fetches one question by question_id.


HTTP API

The HTTP control plane is primarily for operator UIs, backend services, and troubleshooting.

Response envelope

All HTTP responses use the same envelope:

{
  "request_id": "http-request-id",
  "success": true,
  "data": {},
  "error": null
}

On failure:

{
  "request_id": "http-request-id",
  "success": false,
  "data": {},
  "error": {
    "code": "QUESTION_NOT_FOUND",
    "message": "question not found",
    "details": null
  }
}

Headers and identity rules

For question APIs, the server requires a session header on every caller-scoped request:

  • default session header: x-agent-session-id

Identity rules:

  • send x-agent-identity on every caller-scoped request

Operationally:

  • the server reads agent_identity directly from x-agent-identity

GET /api/v1/healthz

Returns liveness status.

GET /api/v1/readyz

Returns readiness status.

This is the correct probe for Redis-backed production deployments.

GET /api/v1/metrics

Returns an in-process metrics snapshot.

POST /api/v1/questions

Creates one or more questions for the current caller scope.

Request body:

{
  "title": "Release decision",
  "description": "Human approval required before deploy",
  "ttl_seconds": 3600,
  "timeout_seconds": 900,
  "questions": [
    {
      "type": "single_choice",
      "title": "Confirm the current outline",
      "options": [
        { "value": "approved", "label": "Approved" },
        {
          "value": "revise",
          "label": "Revise",
          "followup_fields": [
            {
              "id": "comment",
              "type": "text",
              "label": "Revision comment",
              "required": true,
              "description": "Explain what should change."
            }
          ]
        }
      ],
      "default_answer": { "value": "approved" }
    },
    {
      "type": "text",
      "title": "Anything to note?",
      "required": false,
      "default_answer": { "value": "none" }
    }
  ]
}

Supported question payloads:

  • single_choice with options
  • single_choice.options[].followup_fields for option-specific text follow-up inputs
  • multi_choice with options
  • text with optional text_constraints
  • boolean
  • range with range_constraints

Additional create-time fields:

  • timeout_seconds: optional positive integer timeout for the group
  • default_answer: optional per-question fallback answer used by Redis timeout auto-response

GET /api/v1/questions/pending

Returns all pending questions for the current caller scope.

POST /api/v1/questions/answers

Submits answered and skipped questions.

Request body:

{
  "answers": {
    "q_01JXYZ...": {
      "value": "revise",
      "fields": {
        "comment": "Please add recovery conditions."
      }
    }
  },
  "skipped_question_ids": ["q_01JABC..."],
  "idempotency_key": "idem-1"
}

Behavior:

  • accepts partial progress
  • persists cumulative scope state
  • wakes scope waiters after successful submission
  • continues to accept plain { value } answers when no follow-up fields are required

Typical error codes:

  • QUESTION_NOT_FOUND
  • ANSWER_VALIDATION_FAILED

POST /api/v1/questions/cancel

Cancels pending questions in the current c

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.