人工介入服务

hitl-mcp是一个基于MCP和HTTP的问题导向人工介入服务器,用于代理工作流中的人机交互。

  • by ZenlixAI
  • TypeScript
  • 本地部署
  • 79 星标
  • 更新于
在 GitHub 查看 ZenlixAI/hitl-mcp

使用说明

hitl mcp

仅询问代理工作流的HITL MCP服务器。

英语 | 中文


目录


背景

在许多Agent系统中,Agent最终会达到无法自主继续的地步:

  • 一个人必须批准或拒绝一个决定
  • 一个人必须从几个候选人中选择一个
  • 人类必须提供缺失的业务输入
  • 工作流必须暂停,直到手动确认到达

在没有专用HITL层的情况下,这些工作流通常使用特别提示、侧通道UI或自定义回调协议来实现。这造成了三个反复出现的问题:

  1. Agent和应用程序不共享“待定人工输入”的稳定模型。
  2. 人工答案很难与请求它们的确切代理运行和会话相关联。
  3. 等待、部分提交、取消和恢复语义在客户端之间变得不一致。

hitl-mcp 通过为人类问题暴露一个狭窄、明确的契约来解决这个问题:

  • 代理创建问题
  • 客户或运营商用户界面阅读未决问题
  • 人类回答、跳过或取消它们
  • 代理在调用者作用域的状态机上等待,直到工作完成

什么是hitl mcp

hitl-mcp 是一个 面向问题的人在环服务器 基于MCP和HTTP构建。

它在相同的底层状态上提供两个访问表面:

  • MCP工具 用于代理和代理平台
  • HTTP API 用于操作员控制台、后端或自定义审批UI

公共抽象是有意的小:

  • 公共单位是 question
  • 问题属于a 呼叫者范围
  • 答案可以逐步提交
  • 等待被建模为范围级操作,而不是问题级长轮询

该服务器专为代理作为发起者,但由人类完成部分决策循环的工作流而设计。


目标

  • 为Agent工作流提供稳定、最小的HITL合同。
  • 通过以下方式明确呼叫者隔离 agent_identity 和 agent_session_id.
  • 支持同一调用者范围内的多个未决问题。
  • 支持部分进度,而不是强制一次性提交最终结果。
  • 允许MCP和HTTP客户端在相同的基础问题状态上操作。
  • 保持存储可插拔,以便本地开发可以使用内存,生产可以使用Redis。
  • 通过健康状况、准备状态、指标和结构化日志来观察操作行为。

非目标

  • 不是通用的工作流引擎。
  • 不是用于审批控制台的UI框架。
  • 不是任务队列或事件总线。
  • 不是通用的表单生成器。
  • 不是用于访问控制和审阅者分配的策略引擎。
  • 不是用于任意业务流程的持久编排平台。

hitl-mcp 仅管理问题状态、调用者范围、等待语义和答案提交。


核心模型

公共单位:问题

从外部来看,系统只暴露 question.

一个问题是:

  • 服务器生成 question_id
  • 一 type
  • 提示元数据,例如 title, description, tags,以及 extra
  • 为了 single_choice,每个选项还可以声明 followup_fields
  • 可选 default_answer
  • 一种状态,例如 pending, answered, skipped,或 cancelled

支持的问题类型:

  • single_choice
  • multi_choice
  • text
  • boolean
  • range

呼叫者范围

每个操作的范围如下:

  • agent_identity
  • agent_session_id

该范围是以下内容的隔离边界:

  • 提出问题
  • 列出未决问题
  • 等待进展
  • 提交答案
  • 取消提问

只要两个代理的调用者范围不同,它们就可以提出相同的问题而不会发生冲突。

内部团体与公共API

存储层可能仍然在内部使用分组,但分组是一个实现细节。

公众API有意质疑第一:

  • 创建问题
  • 获取未决问题
  • 提交答案 question_id
  • 取消由 question_id
  • 在示波器上等待

部分提交

答案不需要一批到达。

服务器接受增量进度:

  • 现在回答一个问题
  • 稍后回答另一个问题
  • 明确跳过可选问题
  • 继续等待,直到范围完成

应答有效载荷保持向后兼容:

  • 现有呼叫者可以继续发送 { value }
  • 当选择 single_choice 选项声明 followup_fields,呼叫者可以发送 { value, fields }
  • 必需的 followup_fields 根据所选选项进行验证

超时自动响应

问题组还可以在创建时定义超时行为。

  • 呼叫者可以设置 timeout_seconds
  • 如果省略,服务器将使用 HITL_PENDING_DEFAULT_TIMEOUT_SECONDS
  • 每个问题都可以设置 default_answer
  • 如果省略,服务器将按问题类型导出一个

超时的未决问题将自动以已解决的默认答案回答。

  • 在 memory 模式下,这适用于通过进程内超时工作器进行单进程部署
  • 在 redis 模式,这通过Redis支持的超时处理跨实例工作

衍生默认值为:

  • single_choice:第一个选项值
  • multi_choice:第一个选项值作为单个元素数组
  • boolean: true
  • range: range_constraints.min
  • text: "none"

超时生成的答案保持正常 answered 状态并添加元数据,例如:

  • auto_response_at
  • is_timeout_auto_response

等待模式

hitl_wait 等效作用域级等待行为支持两种模式:

  • terminal_only:仅当作用域没有未决问题时返回
  • progressive:每次状态更改后返回,然后让调用者再次等待

terminal_only 对于线性工作流来说更简单。 progressive 当调用者需要对每个中间更新做出反应时,效果更好。

什么时候 hitl_wait 通过MCP调用,它还会在等待过程中每30秒发出一次进度通知。


快速开始

安装

git clone <your-repo-url>
cd hitl-mcp
npm install

在开发中运行

npm run dev

默认本地绑定:

  • HTTP基本URL: http://0.0.0.0:3000
  • MCP基本URL: http://0.0.0.0:3000/mcp
  • HTTP API前缀: /api/v1

使用Docker运行

塑造形象:

docker build -t hitl-mcp .

使用内存存储运行:

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

使用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

使用环境变量从源代码运行

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

最小创建请求

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."
              }
            ]
          }
        ]
      }
    ]
  }'

互动是如何运作的

本节描述了预期的运行时流,无论调用者是使用MCP工具还是HTTP API。

顺序1:标准提问->等待->回答->完成

  1. Agent在其调用者范围内创建一个或多个问题。
  2. 代理人立即致电 hitl_wait.
  3. UI或后端获取同一调用者范围的未决问题。
  4. 人类回答一个或多个悬而未决的问题。
  5. 代理在作用域上等待。
  6. 当作用域中没有未决问题时,wait将返回最终结果。

序列2:部分提交

  1. 代理创建多个问题。
  2. 人类只回答了一个子集。
  3. 服务器会保留这些答案,并保留剩余的问题。
  4. 代理人可以继续等待。
  5. 其他提交将继续,直到范围完成。

序列3:渐进式等待

  1. HITL_WAIT_MODE=progressive
  2. 代理人打电话来 hitl_wait.
  3. 范围内的任何回答、跳过或取消都会唤醒服务员。
  4. 等待结果报告了 question_ids变了。
  5. 代理决定是继续等待还是对中间更新采取行动。

序列4:取消

  1. 呼叫者取消范围内的一个问题或所有未决问题。
  2. 服务器更新作用域状态并通知服务员。
  3. 如果没有悬而未决的问题,范围就变成了终点。

序列5:超时自动响应

  1. Agent创建具有显式或隐式超时行为的问题。
  2. 在Redis模式下,服务器会安排挂起的组进行超时处理。
  3. 如果在截止日期之前没有收到人工响应,Redis支持的超时工作器会自动回答剩余的未决问题。
  4. worker发布一个作用域更新,以便任何实例上的服务员都可以醒来。
  5. hitl_wait 返回已完成的范围快照,其中包含受影响问题的超时元数据。

范围语义

等待总是 范围级别 操作。

这是故意的:

  • 一次代理运行可能有多个未决问题
  • 代理通常关心工作流是否可以继续
  • 作用域级等待避免了按问题分段的同步逻辑

操作规则:

  • 之后每 hitl_ask,下一个HITL工具调用必须是 hitl_wait
  • 不要治疗 hitl_ask 作为完成
  • 不要替代 hitl_get_pending_questions 对于第一篇帖子,请稍候

MCP工具

hitl-mcp 公开以下MCP工具:

hitl_ask

为当前调用者范围创建一个或多个问题。

输入形状:

{
  "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 }
    }
  ]
}

笔记:

  • question_id 不得由呼叫者提供
  • 服务器生成 question_id
  • 一个请求可以创建多个问题
  • 明确的 default_answer 推荐
  • 如果 default_answer 如果省略,服务器将导出一个
  • 如果 timeout_seconds 如果省略,服务器将使用配置的默认超时

hitl_wait

在当前调用者作用域上等待。

典型响应字段:

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

resolved_questions 包含完整的已解决问题对象及其最终状态和任何存储的答案。

当超时自动化产生结果时,解决的问题还可能包括:

  • auto_response_at
  • is_timeout_auto_response

当通过MCP呼叫时, hitl_wait 还发送 notifications/progress 每30秒更新一次,同时仍在等待。

hitl_get_pending_questions

返回当前调用者作用域中的所有未决问题。

hitl_submit_answers

提交新答案和可选跳过。

输入形状:

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

笔记:

  • answers 可能包含未决问题的任何子集
  • 可选问题可以明确跳过
  • 必填问题不能跳过
  • 提交在服务器端累积

hitl_cancel_questions

取消范围内选定的未决问题或所有未决问题。

输入形状:

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

或者:

{
  "cancel_all": true
}

hitl_get_question

通过以下方式获取一个问题 question_id.


HTTP API

HTTP控制平面主要用于操作员UI、后端服务和故障排除。

响应信封

所有HTTP响应都使用相同的信封:

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

失败时:

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

标题和标识规则

对于问题API,服务器要求在每个调用者范围的请求上都有一个会话头:

  • 默认会话标头: x-agent-session-id

身份规则:

  • 发送 x-agent-identity 在每个调用者范围的请求上

操作上:

  • 服务器读取 agent_identity 直接来自 x-agent-identity

GET /api/v1/healthz

返回活动状态。

GET /api/v1/readyz

返回准备状态。

这是对Redis支持的生产部署的正确探测。

GET /api/v1/metrics

返回进程内指标快照。

POST /api/v1/questions

为当前调用者范围创建一个或多个问题。

请求正文:

{
  "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" }
    }
  ]
}

支持的问题有效载荷:

  • single_choice 随着 options
  • single_choice.options[].followup_fields 用于特定选项的文本后续输入
  • multi_choice 随着 options
  • text 可选的 text_constraints
  • boolean
  • range 随着 range_constraints

其他创建时间字段:

  • timeout_seconds:组的可选正整数超时
  • default_answer:Redis超时自动响应使用的每个问题的可选回退答案

GET /api/v1/questions/pending

返回当前调用者作用域的所有未决问题。

POST /api/v1/questions/answers

提交已回答和跳过的问题。

请求正文:

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

行为:

  • 接受部分进展
  • 保持累积作用域状态
  • 成功提交后唤醒范围服务员
  • 继续接受普通 { value } 不需要后续字段时的答案

典型错误代码:

  • QUESTION_NOT_FOUND
  • ANSWER_VALIDATION_FAILED

POST /api/v1/questions/cancel

取消当前呼叫者范围内的未决问题。

请求正文:

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

或者:

{
  "cancel_all": true
}

GET /api/v1/questions/:question_id

通过以下方式获取一个问题 question_id.

如果问题不存在,服务器将返回 404 随着 QUESTION_NOT_FOUND.


运行时配置

配置源和优先级

配置按以下顺序加载:

  1. 内置默认值
  2. config/hitl-mcp.yaml
  3. .env
  4. 过程环境变量

后来的来源会取代以前的来源。

环境变量

代码库当前支持以下环境变量。

变量 默认值 目的 何时更改
PORT 3000 回退HTTP端口。相当于 HITL_HTTP_PORT 当设置时。 仅在运行时注入时更改 PORT 或者当平台需要固定端口环境时。
MCP_URL http://0.0.0.0:3000 MCP服务器元数据使用的公共基URL。 更改任何非本地部署,以便MCP客户端接收可访问的外部URL
HITL_SERVER_NAME hitl-mcp MCP服务器名称元数据。 将此服务器嵌入到其他产品标识下时进行更改。
HITL_SERVER_VERSION 0.1.0 MCP服务器版本元数据。 发布具有显式运行时版本的打包版本时进行更改。
HITL_HTTP_HOST 0.0.0.0 HTTP绑定主机。 仅当您有意只进行环回绑定或使用其他接口时才进行更改。
HITL_HTTP_PORT 3000 显式HTTP端口。覆盖默认端口。 使用自定义端口映射更改本地多服务设置或生产平台。
HITL_HTTP_API_PREFIX /api/v1 HTTP控制平面路由的前缀。 仅当您需要将API装载到不同的路径段之后时才更改。
HITL_STORAGE memory 存储后端选择: memory 或 redis。 设置为 redis 用于多进程或持久部署。
HITL_REDIS_URL redis://127.0.0.1:6379 Redis连接URL。 需要时 HITL_STORAGE=redis 外部本地违约。
HITL_REDIS_PREFIX hitl Redis密钥前缀。 当多个环境共享一个Redis实例时更改。
HITL_TTL_SECONDS 604800 新创建的问题集的默认TTL。 更改以使未决问题保留与您的业务SLA保持一致。
HITL_ANSWERED_RETENTION_SECONDS 2592000 应答状态的保留窗口。 当可审计性或存储压力需要不同的保留期时,请进行更改。
HITL_PENDING_MAX_WAIT_SECONDS 0 一次等待呼叫的最长持续时间。 0 意味着没有超时限制。 更改何时需要有界等待以进行工作进程调度或请求生命周期控制。
HITL_WAIT_MODE terminal_only 作用域等待行为: terminal_only 或 progressive。 设置为 progressive 当调用者必须对每个中间更新做出反应时。
HITL_PENDING_DEFAULT_TIMEOUT_SECONDS 900 新创建的待决问题组的默认超时时间为 timeout_seconds 省略。 当人工响应SLA因环境而异时,会发生变化。
HITL_PENDING_TIMEOUT_POLL_INTERVAL_SECONDS 5 Redis超时工作器的轮询间隔。与精确超时边界的微小偏差是可以接受的。 更改您想要更严格或更宽松的超时触发粒度。
HITL_AGENT_SESSION_HEADER x-agent-session-id 用于读取的标头名称 agent_session_id. 与使用其他会话标头的现有网关或客户端集成时进行更改。
HITL_CREATE_CONFLICT_POLICY error 在配置界面中创建冲突策略。 保持默认值。当前代码验证并加载它,但它当前未被请求处理程序应用。
HITL_LOG_LEVEL info 结构化日志记录级别: debug, info, warn, error 提高或降低详细程度,以满足调试和生产噪音要求。
HITL_ENABLE_METRICS true 在配置中启用指标收集。 保持启用状态,除非您有意将可观测性开销降至最低。

YAML示例

http:
  host: 0.0.0.0
  port: 3000
  apiPrefix: /api/v1
storage:
  kind: redis
redis:
  url: redis://127.0.0.1:6379
  keyPrefix: hitl
ttl:
  defaultSeconds: 604800
  answeredRetentionSeconds: 2592000
pending:
  maxWaitSeconds: 0
  waitMode: terminal_only
  defaultTimeoutSeconds: 900
  timeoutPollIntervalSeconds: 5
agentIdentity:
  sessionHeader: x-agent-session-id
  createConflictPolicy: error
observability:
  logLevel: info
  enableMetrics: true

配置建议

本地开发

  • HITL_STORAGE=memory
  • 发送 x-agent-identity 来自您的客户或测试工具
  • 保持 HITL_WAIT_MODE=terminal_only
  • 超时自动响应在单进程内存模式下可用

共享开发或暂存

  • HITL_STORAGE=redis
  • 设置一个真实 MCP_URL
  • 使用独特的 HITL_REDIS_PREFIX
  • 审查 HITL_PENDING_DEFAULT_TIMEOUT_SECONDS
  • 确保上游呼叫者始终提供 x-agent-identity

生产

  • HITL_STORAGE=redis
  • 集 MCP_URL 指向外部可访问的URL
  • 电线准备就绪 /api/v1/readyz
  • 明确审查TTL和保留值
  • 明确查看超时和轮询值
  • 确保上游呼叫者始终提供 x-agent-identity

它在内部是如何工作的

作用域状态机

hitl-mcp 在调用者范围级别保持问题进度。

每个状态更改操作都会更新一个范围快照,其中包含:

  • 未决问题
  • 已解决的问题,包含完整的问题有效载荷和答案(如果可用)
  • 已回答的问题ID
  • 跳过的问题ID
  • 已取消的问题ID
  • 更改了问题ID
  • 完成状态

此快照是等待结果的真实来源。

服务员通知模型

服务器保存一个由调用者作用域键入的进程内服务员注册表。

当收到回复或取消时:

  1. 存储已更新
  2. 计算新的作用域快照
  3. 通知了那个炉灶的服务员
  4. hitl_wait 根据配置的等待模式进行解析

在Redis模式下,超时自动响应也会产生服务员通知。超时工作器通过Redis pub/sub发布作用域更新,因此当另一个实例执行自动响应时,在一个实例上被阻止的服务员仍然可以醒来。

在内存模式下,超时自动响应通过相同的进程内进程生成服务员通知 Waiter 用于应答和取消事件。

存储选择

存在两种存储模式:

  • 记忆:简单,流程本地化,适合测试和本地开发
  • 瑞迪斯:跨流程耐用,适合实际部署

这两种模式都启用超时自动响应,但范围不同:

  • 记忆:单进程超时工作程序和直接服务员通知
  • 瑞迪斯:跨实例超时调度和分布式唤醒

Redis模式还使用:

  • 一个用于定时调度的Redis排序集
  • 短寿命分布式锁,避免多实例部署中的双重处理
  • Redis pub/sub跨实例通知服务员

如果在运行时初始化期间选择了Redis但不可用,服务器将回退到内存存储并记录警告。

这种回退在本地开发中很有用,但生产环境应将其视为配置错误的信号。

身份处理

对于HTTP问题API:

  • 会话标识来自 HITL_AGENT_SESSION_HEADER
  • 呼叫者身份来自 x-agent-identity

对于MCP工具调用:

  • 服务器从请求上下文将调用者作用域注入MCP工具状态
  • 工具层读取 agent_identity 和 agent_session_id 从注射状态

建筑

运行时有五个主要层。

1.配置层

从默认值、YAML加载并验证配置, .env,以及运行时环境变量。

2.服务器层

构建MCP服务器和HTTP应用程序,连接中间件,并注册路由和工具。

3.服务层

HitlService 定义以下操作行为:

  • 创建
  • 列表待定
  • 等待
  • 提交答案
  • 取消
  • 获取问题

这是主要的应用边界。

4.存储层

提供以下存储库实现:

  • 内存开发和测试
  • Redis支持的持久性

Redis实现还具有超时调度、分布式锁获取和超时驱动的默认应答持久性。

5.观测层

提供:

  • 结构化日志
  • 请求ID
  • 准备就绪检查
  • 度量快照

请求流摘要

对于HTTP:

  1. 调用者上下文中间件解析身份和会话
  2. 路由处理程序验证输入和调用 HitlService
  3. 存储库更新状态
  4. 回复被包裹在标准信封中

对于MCP:

  1. 检查MCP请求上下文
  2. 调用者作用域被注入到工具状态中
  3. 工具处理程序委托给 HitlService
  4. 工具输出反映了最新的作用域状态

运营

健康和准备

  • 生活: GET /api/v1/healthz
  • 准备就绪: GET /api/v1/readyz
  • 韵律学: GET /api/v1/metrics

使用就绪性而非活性来保护Redis支持的生产流量。

日志记录

服务器发出结构化的请求和错误日志。

集 HITL_LOG_LEVEL=debug 在对请求流或存储库行为进行故障排除时。

指标

指标通过以下方式以JSON快照的形式公开 /api/v1/metrics.

当前的实现跟踪操作信号,如等待时间和待处理计数。

常见部署检查

在声明部署正常之前,请验证:

  1. MCP_URL 匹配外部可访问的URL。
  2. 暴露的HTTP端口与您的运行时或入口映射匹配。
  3. HITL_STORAGE=redis 与可访问的Redis实例配对。
  4. 上游呼叫者发送 x-agent-identity 以及所配置的会话报头。
  5. /api/v1/healthz, /api/v1/readyz,以及 /api/v1/metrics 所有人都按照预期做出了回应。

项目结构

.
├── config/                     # YAML configuration examples
├── docs/
│   ├── api/                    # MCP tool and HTTP API reference docs
│   └── runbooks/               # Operational runbooks
├── src/
│   ├── config/                 # Config schema, defaults, loaders
│   ├── core/                   # HitlService application logic
│   ├── domain/                 # Domain types, schemas, validators
│   ├── http/                   # Hono routes, middleware, response helpers
│   ├── mcp/                    # MCP tool registration and caller-scope helpers
│   ├── observability/          # Logging and metrics
│   ├── state/                  # Waiter and state-machine helpers
│   └── storage/                # In-memory and Redis repositories
├── tests/                      # Unit and integration coverage
├── Dockerfile                  # Production-oriented container build
└── index.ts                    # Runtime entrypoint

相关文档