举手
政策驱动的MCP工具调用的人工批准。
在明确的批准/拒绝决定背后,通过稳定的请求有效载荷和安全的参数显示(编校+截断)来控制对工具调用的影响。
handraise 是一个OpenCode友好的检查点原语:它允许代理在运行过程中暂停并等待人工输入(循环中的人工),而不需要新的聊天回合。
此存储库提供了一个小型TypeScript(ESM)库,该库封装了一个MCP工具执行器,并在执行前需要明确的人工批准步骤。
\[!注意\] 这个仓库与运行时无关:你注入一个 HandraiseAdapter 它执行实际的“等待人类”行为(例如,OpenCode检查点)。\[!重要\] 不要记录原始工具参数。使用 displayArgs 由门生成(它应用编校+截断规则)。入门
最简单的例子:默认情况下,所有事情都需要批准,允许使用安全的工具,并要求对有风险的工具进行明确批准。
import {
createMcpHumanInLoopGate,
type HandraiseAdapter,
type McpApprovalPolicy,
type McpToolCall,
} from "handraise";
const handraise: HandraiseAdapter = {
async requestApproval(req) {
// Render req.summary + req.displayArgs in your UI, then return a decision.
// In OpenCode this is typically implemented by raising a checkpoint.
return { decision: "approve" };
},
};
const policy: McpApprovalPolicy = {
defaultRequireApproval: true,
allowlist: ["functions.grep"],
tools: [
{
toolName: "functions.bash",
requireApproval: true,
risk: "high",
argDisplay: {
rules: [
{ kind: "redactKey", key: "token" },
{ kind: "redactKey", key: "password" },
],
},
},
],
};
const gate = createMcpHumanInLoopGate({
policy,
handraise,
logger: {
info: (event, payload) => console.info(event, payload),
warn: (event, payload) => console.warn(event, payload),
},
});
const call: McpToolCall = {
toolName: "functions.bash",
args: { command: "ls -la" },
};
const result = await gate.executeWithApproval(call, async (approvedCall) => {
// This is where you call your real MCP tool.
return runMcpTool(approvedCall.toolName, approvedCall.args);
});概念
申请批复
当需要批准时,大门会召唤 handraise.requestApproval(request) 与:
traceId:关联请求、决策和最终的工具执行toolName:MCP工具名称summary:对将发生的事情的人类可读描述risk:low | medium | highdisplayArgs:可以安全地显示参数(已编辑+截断)createdAtMs:时间戳(ms)
决策
返回以下之一:
{ decision: "approve" }{ decision: "approve", overrideArgs: unknown }(可选编辑输入){ decision: "deny" }{ decision: "deny", reason?: string }
拒绝请求抛出 McpHumanApprovalDeniedError 并且不执行该工具。
策略匹配
策略匹配具有稳定的优先级:
allowlist(绕过审批;风险默认为low)denylist(强制批准;风险默认为high)- 根据工具规则(
tools[]) defaultRequireApproval
\[!提示\] 从开始 defaultRequireApproval: true,然后分配安全/只读工具。安全参数显示
displayArgs 由以下人员生产 prepareArgsForDisplay():
- 默认情况下,重置众所周知的密钥(
apiKey,token,password) - 截断长字符串/数组/对象
- 限制递归深度
您可以通过以下方式收紧这些限制或为每个工具添加编辑规则 policy.tools[].argDisplay.
API
大多数消费者只需要这些出口:
createMcpHumanInLoopGate(options)→{ executeWithApproval(call, executor) }defaultMcpApprovalPolicy()和matchPolicy(policy, toolName)prepareArgsForDisplay(args, options)- 类型:
McpApprovalPolicy,McpToolCall,HandraiseAdapter,McpApprovalRequest,McpApprovalDecision - 错误:
McpHumanApprovalDeniedError,McpHumanApprovalInvalidDecisionError
MCP服务器工具由 src/mcp/server.ts:
handraise_preview_approval:评估策略并返回显示安全参数handraise_apply_decision:使用可选参数重写应用allow/denyhandraise_ask_user:仅限CLI askUser网桥(第二个终端响应器)handraise_ask_user_cli_status:显示网桥状态路径和第二个终端响应器命令
OpenCode MCP集成
使用内置的stdio MCP服务器直接从本地连接此存储库 opencode.json.
- 构建项目:
npm install
npm run build- 在此文件夹中配置OpenCode(
opencode.json):
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"handraise": {
"type": "local",
"enabled": true,
"command": ["raisehand-mcp"],
"environment": {
"HANDRAISE_DEFAULT_REQUIRE_APPROVAL": "{env:HANDRAISE_DEFAULT_REQUIRE_APPROVAL}",
"HANDRAISE_ALLOWLIST": "{env:HANDRAISE_ALLOWLIST}",
"HANDRAISE_DENYLIST": "{env:HANDRAISE_DENYLIST}",
"HANDRAISE_ASK_USER_STATE_PATH": "{env:HANDRAISE_ASK_USER_STATE_PATH}",
"HANDRAISE_ASK_USER_TIMEOUT_MS": "{env:HANDRAISE_ASK_USER_TIMEOUT_MS}"
},
"timeout": 10000
}
}
}- 在此存储库中启动OpenCode,并从MCP服务器命名空间调用工具。
MCP工具中的无缝样式askUser
使用 handraise_ask_user 当您需要通过CLI进行用户输入时(不需要MCP启发UI)。
有效载荷示例:
{
"header": "Deployment confirmation",
"question": "How should we proceed?",
"options": [
{ "label": "Deploy now" },
{ "label": "Wait for maintenance window" }
],
"multiple": false,
"custom": true,
"customLabel": "Other"
}在第二个终端中运行此命令:
npm run ask-cli:startTUI别名:
npm run ask-tui:start
npm run handraise-ask-tui兼容性别名:
npm run handraise-ask-cli
npm run handrize-ask-cliMCP工具调用等待,直到响应者接受/拒绝/取消并返回答案。
响应者打开一个带有队列导航和热键的交互式TUI:
up/down:move(提示列表或答案行)enter(列表模式):所选提示的打开应答模式enter(应答模式):切换所选选项或运行所选操作(Submit,Decline,Cancel)esc:退出应答模式(或从列表模式退出应用程序)
答案预览在提示列表模式和答案模式下都是可见的。 一次只允许有一个待定的askUser问题。
结构化结果形状:
action:accept | decline | cancelanswer:string | string[] | nullselectedOptions:选定的预定义选项customResponse:可选的自由用户文本
环境价值观:
HANDRAISE_DEFAULT_REQUIRE_APPROVAL:true或false.HANDRAISE_ALLOWLIST:逗号分隔的工具名称绕过审批。HANDRAISE_DENYLIST:逗号分隔的工具名称始终需要批准。HANDRAISE_ASK_USER_STATE_PATH:服务器使用的共享JSON状态文件ask-cli响应者。HANDRAISE_ASK_USER_TIMEOUT_MS:CLI响应的最大等待时间(毫秒)。HANDRAISE_ASK_USER_AUTOLAUNCH:MCP服务器连接时自动打开响应程序(true默认情况下;集false禁用)。HANDRAISE_ASK_USER_AUTOLAUNCH_CMD:为您的终端环境自定义启动命令。
自动启动默认顺序:
- tmux(
new-window如果已在tmux内部,否则已分离new-session) - GUI终端启动器(
x-terminal-emulator,gnome-terminal,konsole等等) - 如果没有可用的启动器,则手动启动
故障排除:
Error: Cannot find module ./dist/src/mcp/server.js:runnpm run build第一。- MCP服务器已启动,但未显示任何工具:验证
command路径正是./dist/src/mcp/server.js和type是local. - 意外的审批行为:检查环境值并确认allowlist/denylist的CSV格式。
handraise_ask_user在CLI模式下挂起:在终端2中启动响应程序(npm run ask-cli:start)并确保两个流程共享HANDRAISE_ASK_USER_STATE_PATH.- 自动启动显示“跳过”:设置
HANDRAISE_ASK_USER_AUTOLAUNCH_CMD(例如,tmux split/newwindow命令)或手动启动响应程序。
CLI二进制文件:
handraise-mcp和raisehand-mcp两者都启动MCP服务器。handraise-ask-tui,handraise-ask-cli,以及handrize-ask-cli启动第二终端应答器。
启动脚本:
npm run raisehand:start(首选)npm run raisehand-startnpm run mcp:start(旧别名)
发展
先决条件:
- Node.js(使用
node --test) - npm
常用命令:
npm install
npm run build
npm test存储库布局:
src/库源(ESM)src/mcp/门、策略匹配、编校、错误、类型test/节点:测试测试(编译为dist/test)openspec/规范驱动的变更工件(提案/设计/规范/任务)
\[!注意\] 对编译后的输出运行测试:npm test先构建,然后运行node --test dist/test.
