business-action-lab
Business Action Lab
一个本地运行的业务动作参考实现:Agent 可以查询合成工单、追加工作日志;业务服务验证权限、状态和重复请求。
要验证的问题:一次业务写入被重试时,怎样保持一条业务日志,并明确返回拒绝或冲突?
当前状态
Python + SQLite 业务服务、开发 CLI、两个 stdio MCP 工具已经运行验证。
测试覆盖正常写入、权限拒绝、状态校验、重试、参数冲突、多个进程并发和事务失败。
MCP 演示启动真实服务子进程,验证工具发现、调用、错误标记和重启后的重复请求。
另已完成真实 Codex CLI 模型调用:正常写入、提交后超时重试、只读身份拒绝,各观测一次。
新增启动端固定操作编号的模式:模型只提供工单和正文,不能在重试时换键。
当前本地版本为 0.2.0,56 项确定性测试通过。真实 Agent 演示记录模型调用;验收记录记录当前版本;首轮记录保留历史结果。
三步跑起来
需要 uv。在本目录运行:
uv sync --locked
uv run --locked pytest -q
uv run --locked bal-demoPython 版本由 .python-version 指定为 3.12.13;依赖版本和下载校验值由 uv.lock 固定。
演示不需要模型密钥。每次演示在 .local/demo-<随机标识>/ 创建新的合成数据库、身份配置和 report.json,保留此前记录。
预期看到 12 条 PASS,末尾显示:
{
"passed": true,
"counts": {
"work_orders": 3,
"work_logs": 1,
"idempotency_records": 1,
"audit_events": 8
}
}报告记录原始 MCP 返回、工具定义、数据库回读和运行环境。只有业务写入实际成功且回读一致,演示才成功退出。
想看真实模型的行为,先读 提交后超时演示。其中的可选运行命令需要已有 Codex 登录并使用账号额度;基础演示不调用模型。
固定操作编号模式也有独立演示:
uv run --locked bal-demo --fixed-operation预期 8 条 PASS,回读 2 条日志、2 条幂等记录、4 条审计。两条日志分别来自启动端明确选择的两个操作;同一操作的重试和重启不增加日志。
自己操作一次
初始化会保留现有业务日志和工单状态;不会重置数据库。
uv run --locked bal init
uv run --locked bal --identity examples/reader.json get-work-order WO-1001
uv run --locked bal --identity examples/operator.json add-work-log WO-1001 \
--text '合成演示:检查完成。' --key my-operation-1
uv run --locked bal --identity examples/operator.json add-work-log WO-1001 \
--text '合成演示:检查完成。' --key my-operation-1
uv run --locked bal inspect两次写入返回相同 work_log_id、时间戳和 audit_id;inspect 中只有一条业务日志。
把第二次正文改掉会得到 IDEMPOTENCY_CONFLICT;换成 reader.json 会得到 FORBIDDEN。
CLI 业务失败返回退出码 1,并输出 JSON 错误。inspect 是开发者回读入口,不向 MCP 暴露。
合成工单 | 状态 / 用途 |
| 开放,可用于合法写入 |
| 已关闭,用于验证业务拒绝 |
| 开放,但不在两种演示身份的授权范围内 |
| 身份配置允许查询该编号,但数据库中不存在,用于验证 |
这些是本项目自行定义的演示规则,与真实企业系统无关。
接入一个现有 MCP 客户端
先运行 bal init。以项目虚拟环境中的 Python 启动 business_action_lab.mcp_server,提供数据库和身份文件的绝对路径:
{
"mcpServers": {
"business-action-lab": {
"command": "/absolute/path/business-action-lab/.venv/bin/python",
"args": [
"-m", "business_action_lab.mcp_server",
"--db", "/absolute/path/business-action-lab/.local/lab.sqlite3",
"--identity", "/absolute/path/business-action-lab/examples/reader.json"
]
}
}
}这是通用 MCP 配置示例,具体配置位置和格式以所用客户端为准。模板保存在 examples/mcp-config.example.json。
初始示例选择只读身份。操作者若要演示合法写入,在启动配置中选择 operator.json 并重启服务。
不要同时把两个身份都当作可供模型切换的权限选项。
工具参数只有:
工具 | 参数 |
|
|
|
|
服务拒绝多余参数。业务错误同时返回 MCP isError=true 和结构化的 ok=false / 错误码。
启动配置只读入一次;角色或范围变更需要重启服务。身份 JSON 是本地演示配置,不是远程身份凭据。
让启动端管理一次操作
对于已经明确的一次写入,在服务启动参数中增加 --operation-key example-operation-1。这时 add_work_log 只接受 work_order_id 和 text,键由适配层补入。模型尝试额外传入键会被拒绝。
启动者为每个独立操作分配唯一编号,并将它与任务一起保存;重试、重连和重启都使用原编号。只有明确开始另一个操作时才换号。服务不会推断两段自然语言是否表示同一意图,也不会在重启时自动分配新号。
这种配置一次承载一个写入意图,可多次查询和重试;通用多操作会话仍须由调用方管理各自的键。 参见 操作编号的生命周期和配置示例。
关键语义
reader可查询授权范围内的工单;operator可为范围内的开放工单追加日志。正文保留原样,允许 1–2000 个字符,拒绝纯空白、NUL 和无效 UTF-8。
幂等键为 1–128 个 ASCII 字符,首字符为字母或数字,其后允许字母、数字、
.、_、:、-。同一主体 + 动作 + 键,相同正文和目标返回原结果;不同参数返回冲突。不同键表示不同操作。
成功日志、幂等记录、成功审计在一个事务中提交。并发写入使用 SQLite 写锁和复合主键约束。
工单随后关闭时,原请求仍能返回历史结果;新的请求被拒绝。权限校验在重复请求检查之前执行。
查询完全只读;成功重试引用原审计。进入业务服务的拒绝和失败写入尝试会尽力单独记录审计。
详细取舍、失败边界见 设计说明。
适用边界
全部数据均为合成数据。拥有宿主机 Shell、配置或数据库控制权的客户端能绕过这些工具;本项目不提供这种隔离。 幂等保证仅限当前本地数据库,不覆盖外部系统或分布式“恰好一次”。
这是使用 AI 辅助完成的个人参考实现,署名 ls2212895。首轮只做有界验证,不承诺即时维护。
许可文本见 MIT 许可证,实现与依赖来源见说明。
外部复现和同行回复不作为验收条件。可直接阅读工程案例,了解问题、取舍与证据。
展示与讲解
真实调用记录回放:下载后直接用浏览器打开,约一分钟看完“提交 → 超时 → 重试 → 回读”。它不发起模型调用,也不写数据库。
五分钟讲解与代码导读:关键路径、设计取舍和常见问题。
发布前验证:当前内容与已验证源码的对应关系。
仓库准备了 GitHub 自动检查配置,运行测试和两套确定性演示,不调用模型。远程检查尚未运行,已验证平台仍以验收记录为准。
框架事实依据:FastMCP 授权、工具定义、stdio 客户端。 代码没有依赖 stdio 下的 OAuth 授权检查;角色和工单权限始终由业务服务判断。
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ls2212895/business-action-lab'
If you have feedback or need assistance with the MCP directory API, please join our Discord server