MCP 服务器实现计划#
面向 agent worker: 必须使用子技能 superpowers:executing-plans 逐项实现本计划。每项任务使用 superpowers:test-driven-development,在报告完成前使用 superpowers:verification-before-completion。
目标: 添加经过身份验证、仅支持 stdio 的 openprogram mcp serve 端点,恰好公开六个固定 MCP 工具,并将其映射到现有会话、Runtime 工具、审批、取消、进度与审计机制,不授予 owner 权限。
架构: 新增同级 openprogram/mcp/server/ 包,与现有 MCP 客户端包及 Web owner 控制面分离。入口在打开 stdin 前完成身份验证,创建由凭据派生的客户端身份,固定使用 paired 权限和 non-interactive 交互,并在协议基线 2025-11-25 上运行锁定的 MCP 1.29.0 SDK 服务器。六个面向协议的工具委托给服务层;底层 Runtime 工具的公开集合为默认空配置允许列表、实时注册表与固定 paired 能力表的交集。SDK 取消负责 JSON-RPC 取消错误;OpenProgram 仅停止后续副作用、清理请求/问题状态并生成审计证据。
技术栈: Python 3.11+、mcp==1.29.0、MCP 2025-11-25、AnyIO、Pydantic、jsonschema、pytest、stdio JSON-RPC,以及现有 SessionDB/dispatcher/Runtime/事件总线。
全局约束#
- 从
origin/main@9fb70113fd31dc2acf80a341e5ac51d244e1c35a创建隔离 worktree 实现。不得修改有未提交更改的 main checkout。 - 服务器唯一传输方式为
openprogram mcp serve的 stdio。不得新增 HTTP、SSE、Streamable HTTP、OAuth、Web 路由、socket 或部署监听器。 - 行为锁定为仓库的
mcp==1.29.0和协议版本2025-11-25;不得增加自定义握手字段或扩大依赖版本范围。 - 恰好公开以下 MCP 工具名:
sessions_list、session_get、prompt_send、prompt_cancel、tools_list、tool_call。 - 使用独立且由凭据派生的 MCP 客户端身份。
initialize.clientInfo仅为诊断元数据,不得影响身份验证、身份、作用域、允许列表或审批。 - 每个请求使用
source="mcp"、permission_mode="ask"、authority_tier="paired"和interaction="non-interactive";调用方均不可配置。 mcp_server.exposed_tools默认为[]。底层 Runtime 工具的发现与执行均使用configured allowlist ∩ live registry ∩ paired capability。- 读取 POSIX 权限为
0600的<state>/mcp_server_token,并在进入 SDK stdio 上下文前用hmac.compare_digest()与OPENPROGRAM_MCP_TOKEN比较。serve永不生成 token。 - 不得导入或复用
openprogram.webui.owner_auth、backend_endpoint、Web owner token、cookie 或 owner bearer header。 - 未知和未公开的底层工具名不可区分,均返回方法级 MCP 错误。已配置工具缺少 paired 作用域、硬约束/审批拒绝或工具执行失败时返回
CallToolResult(isError=True)。 - 被取消的原始
prompt_send永不返回应用工具结果。MCP SDK 1.29.0 返回标准 JSON-RPC 取消错误;服务停止后续副作用,清理活跃请求与问题,并记录mcp.request.cancelled。 prompt_cancel是独立普通工具。它只能取消由同一已验证服务器连接发起的当前活跃轮次,并返回自己的正常执行结果。- 普通问题与审批不得等待非交互式 MCP 请求。审批门禁立即拒绝;活跃 MCP 会话的其他
question.asked事件立即解析为 declined。 - 不得修改或扩展现有 ACP 服务器。共享 Runtime 类型发生变化时运行 ACP 回归。
- 每项任务严格遵循 RED → 验证失败 → GREEN → 验证通过 → 提交。不得合并任务提交。
固定协议约定#
全部六个 schema 设置 additionalProperties: false。空输入工具使用 { "type": "object", "properties": {} }。
| MCP 工具 | 输入 schema | 成功 payload |
|---|---|---|
sessions_list |
无字段 | 最多 100 条最近的 {id,title,updated_at},按 SessionDB 顺序排列 |
session_get |
必需的非空字符串 session_id |
活跃分支消息 {id,role,content,timestamp} |
prompt_send |
必需的非空字符串 prompt;可选非空字符串 session_id |
{session_id,text,assistant_msg_id,failed};缺少 session_id 时创建 mcp_<uuid> 会话,提供未知 id 则无效 |
prompt_cancel |
必需的非空字符串 session_id |
{session_id,cancelled};仅同一连接的活跃 MCP 轮次使 cancelled 为 true |
tools_list |
无字段 | 允许列表/注册表/paired 交集中的底层工具,格式为 {name,description,inputSchema} |
tool_call |
必需的非空字符串 name;可选对象 arguments,默认为 {} |
转换后的底层 AgentToolResult 内容与错误状态 |
成功的结构化应用 payload 序列化为一个 UTF-8 JSON TextContent;v1 不增加第二种输出表示。tool_call 保留 Runtime 文本/图像内容:文本映射为 MCP 文本内容,图像映射为含媒体类型的 MCP 图像内容。不支持的内容以执行错误拒绝,不转换为字符串。
MCP tools/list 本身始终返回六个固定包装工具。名为 tools_list 的包装工具返回筛选后的底层 Runtime 工具。这是两个不同约定,必须分别断言。
任务 1:固定六个 JSON Schema 与协议验证#
文件:
- 创建:
openprogram/mcp/server/__init__.py - 创建:
openprogram/mcp/server/contracts.py - 创建:
tests/unit/test_mcp_server_contracts.py
接口:
MCP_TOOL_SCHEMAS: tuple[mcp.types.Tool, ...]
TOOL_BY_NAME: Mapping[str, mcp.types.Tool]
def validate_tool_call(name: str, arguments: Mapping[str, Any] | None) -> dict[str, Any]
对于六个固定名称之外的包装工具名,validate_tool_call() 必须抛出 mcp.shared.exceptions.McpError(ErrorData(code=METHOD_NOT_FOUND, ...));schema 验证失败时抛出 McpError(... INVALID_PARAMS ...)。它返回复制的 dict,永不修改调用方输入。
RED: 为六个名称的顺序、每个完整输入 schema、additionalProperties: false、必需字段、空/空白 prompt 和 id、非对象 arguments、未知包装工具名和输入不可变性添加严格相等测试。还要断言锁定依赖解析为 mcp==1.29.0,且 LATEST_PROTOCOL_VERSION == "2025-11-25"。
运行:
uv run --locked pytest -q tests/unit/test_mcp_server_contracts.py
预期:FAIL,因为 openprogram.mcp.server.contracts 不存在。
GREEN: 将 schema 定义为不可变模块数据,从中构造 SDK Tool 对象,并使用 jsonschema.Draft202012Validator 验证。仅将省略/None 的 tool_call.arguments 归一化为 {};拒绝所有其他未声明或类型错误的字段。不得添加服务器、传输、身份验证或执行逻辑。
运行相同命令;预期:PASS。
提交:
git add openprogram/mcp/server/__init__.py openprogram/mcp/server/contracts.py tests/unit/test_mcp_server_contracts.py
git commit -m "feat: define MCP server tool contracts"
任务 2:让 Runtime 工具错误在全流程中类型化#
文件:
- 修改:
openprogram/agent/types.py - 修改:
openprogram/programs/_runtime.py - 修改:
openprogram/mcp/adapter.py - 修改:
openprogram/agent/agent_loop.py - 修改:
openprogram/agent/permissions/approval.py - 修改:
openprogram/agentic_programming/call_state.py - 修改:
openprogram/programs/tools/files/bash/bash.py - 修改:
tests/unit/test_tools_runtime.py - 修改:
tests/agent/test_loop_options.py - 修改:
tests/agent/test_tool_gate.py - 修改:
tests/unit/test_acp_server.py
接口:
class AgentToolResult(BaseModel):
content: list[TextContent | ImageContent]
details: Any = None
is_error: bool = False
RED: 修改测试,对归一化的 ToolReturn 失败、超时、异常、远程 MCP isError、审批拒绝和 bash 失败断言 result.is_error。添加 agent-loop 断言,要求 ToolResultMessage.is_error 等于 AgentToolResult.is_error。断言 details 保留 denied、timeout、reason_code 等诊断字段,但不再传递权威 is_error 标志。
运行:
uv run --locked pytest -q tests/unit/test_tools_runtime.py tests/agent/test_loop_options.py tests/agent/test_tool_gate.py tests/unit/test_acp_server.py
预期:FAIL,因为 AgentToolResult 没有独立错误字段,使用方仍检查 details。
GREEN: 添加带默认值的字段;在 _normalize_result()、MCP 客户端转换、审批 _denied()、超时/异常路径和显式失败工具中设置它。将缓存及 agent-loop 转换中的 details.get("is_error") 替换为 result.is_error。保留默认 false,使无关构造器保持源码兼容。不得改变 ACP 协议结构。
运行相同命令;预期:PASS。
提交:
git add openprogram/agent/types.py openprogram/programs/_runtime.py openprogram/mcp/adapter.py openprogram/agent/agent_loop.py openprogram/agent/permissions/approval.py openprogram/agentic_programming/call_state.py openprogram/programs/tools/files/bash/bash.py tests/unit/test_tools_runtime.py tests/agent/test_loop_options.py tests/agent/test_tool_gate.py tests/unit/test_acp_server.py
git commit -m "refactor: type runtime tool error results"
任务 3:添加默认空公开集合、paired MCP 身份与非交互门禁#
文件:
- 修改:
openprogram/config_schema.py - 修改:
openprogram/agent/authority.py - 修改:
openprogram/agent/permissions/approval.py - 修改:
tests/unit/test_config_schema.py - 修改:
tests/unit/test_authority_scope.py - 修改:
tests/unit/test_permission_rules.py - 修改:
tests/unit/test_spawn_hard_constraints.py - 创建:
tests/unit/test_mcp_server_security.py
接口:
def mcp_client_authority(client_id: str) -> dict[str, Any]
SettingSpec(
key="mcp_server.exposed_tools",
path=("mcp_server", "exposed_tools"),
default=[],
apply=APPLY_NEXT_START,
...,
)
权限映射固定为 speaker_kind="client"、speaker_id=f"mcp/{client_id}"、speaker_display="MCP client"、principal_id=owner_principal_id()、authority_tier="paired"、interaction="non-interactive"。拒绝空或格式错误的客户端 id,不合成身份。
RED: 断言缺失设置读取为 [];验证拒绝非列表及非字符串条目;归一化 MCP 权限稳定且仅拥有 paired 能力。添加 source="mcp" 用例,证明 _NON_INTERACTIVE_SOURCES 立即拒绝审批,且 _hard_constraint_violation() 在规则、允许列表或 bypass 授权之前拒绝 _RISKY_TOOLS、worktree 工具和工作目录之外的 write/patch 路径。断言未注册任何问题。
运行:
uv run --locked pytest -q tests/unit/test_config_schema.py tests/unit/test_authority_scope.py tests/unit/test_permission_rules.py tests/unit/test_spawn_hard_constraints.py tests/unit/test_mcp_server_security.py
预期:FAIL,因为设置、权限构造器和 MCP source 门禁均缺失。
GREEN: 添加 JSON 列表设置验证器、mcp_client_authority()、将 "mcp" 加入 _NON_INTERACTIVE_SOURCES,并在 _hard_constraint_violation() 中显式处理 MCP。硬约束保持在权限规则及所有 bypass 之前。不得扩展 _PAIRED_CAPABILITIES。
运行相同命令;预期:PASS。
提交:
git add openprogram/config_schema.py openprogram/agent/authority.py openprogram/agent/permissions/approval.py tests/unit/test_config_schema.py tests/unit/test_authority_scope.py tests/unit/test_permission_rules.py tests/unit/test_spawn_hard_constraints.py tests/unit/test_mcp_server_security.py
git commit -m "feat: add MCP server security boundary"
任务 4:实现独立的 0600 token 生命周期#
文件:
- 创建:
openprogram/mcp/server/auth.py - 修改:
openprogram/_cli_cmds/mcp.py - 修改:
openprogram/cli.py - 创建:
tests/unit/test_mcp_server_auth.py - 创建:
tests/unit/test_mcp_server_cli.py
接口:
MCP_TOKEN_ENV = "OPENPROGRAM_MCP_TOKEN"
def token_path() -> Path
def create_token(path: Path | None = None) -> str
def authenticate_from_environment(
environ: Mapping[str, str] = os.environ,
path: Path | None = None,
) -> str # returns credential fingerprint/client id
def _cmd_mcp_token_create() -> int
指纹为 sha256(stored_token.encode()).hexdigest()[:16];它是稳定客户端 id,绝不包含 token 文本。create_token() 使用 secrets.token_urlsafe(32),拒绝已存在目标,以 0600 写入独占临时文件,执行 flush 和 fsync(),然后通过不覆盖操作安装(POSIX 使用 os.link(temp, target) 后删除临时链接;其他平台使用经过同等测试的禁止替换原语)。它重新应用仅用户可访问权限,验证权限,并返回 token 供 CLI 仅打印一次。不得在预检查后使用 os.replace(),因为它可能覆盖并发进程创建的 token。
RED: 覆盖创建、长度/熵形式、已有文件不覆盖并拒绝、父目录创建、POSIX 0600、不可读/权限错误 token 拒绝、缺失环境变量、不匹配、使用 hmac.compare_digest、稳定指纹、异常/日志无 token,以及 mcp token create 解析器/退出行为。对 _require_backend_endpoint 打补丁,使其被调用即失败,以证明 token 创建在本地完成。
运行:
uv run --locked pytest -q tests/unit/test_mcp_server_auth.py tests/unit/test_mcp_server_cli.py
预期:FAIL,因为 token 身份验证和嵌套 mcp token create 命令不存在。
GREEN: 在 mcp/server/auth.py 实现独立写入器;不得导入 Web 身份验证辅助函数。为现有 mcp 解析器扩展 token create,保留所有管理命令。在任何后端 HTTP 辅助函数之前直接分发 token 创建。
运行相同命令;预期:PASS。
提交:
git add openprogram/mcp/server/auth.py openprogram/_cli_cmds/mcp.py openprogram/cli.py tests/unit/test_mcp_server_auth.py tests/unit/test_mcp_server_cli.py
git commit -m "feat: add MCP server token lifecycle"
任务 5:实现会话读取和筛选后的 Runtime 发现#
文件:
- 创建:
openprogram/mcp/server/service.py - 创建:
openprogram/mcp/server/tools.py - 创建:
tests/unit/test_mcp_server_tools.py
接口:
@dataclass(frozen=True)
class MCPClientContext:
client_id: str
authority: Mapping[str, Any]
class MCPService:
def sessions_list(self) -> AgentToolResult: ...
def session_get(self, session_id: str) -> AgentToolResult: ...
def tools_list(self) -> AgentToolResult: ...
def exposed_runtime_tools(self) -> tuple[AgentTool, ...]: ...
def json_result(payload: Any, *, is_error: bool = False) -> AgentToolResult: ...
RED: 使用伪造 SessionDB 和注册表,断言 sessions_list 调用 list_sessions(limit=100) 且仅输出 id/title/updated_at;session_get 拒绝未知 id,通过 get_branch(session_id) 读取当前活跃分支,且仅输出 id/role/content/timestamp。断言 tools_list 默认为空,忽略已配置但未注册名称,排除缺少 paired 能力的已注册名称,去重后保留配置顺序,并返回精确的 name/description/inputSchema 键。断言它从不读取 clientInfo 或 Web 身份验证。
运行:
uv run --locked pytest -q tests/unit/test_mcp_server_tools.py -k "sessions or session_get or tools_list"
预期:FAIL,因为服务与转换模块不存在。
GREEN: 向 MCPService 注入 SessionDB/config/registry 访问器,以实现确定性测试。使用 functions._runtime.get()/注册表公开机制和 decide_tool_authority() 求交集。返回确定性 JSON 文本。本任务不实现 tool_call、prompt 执行、传输或 owner 回退。
运行相同命令;预期:PASS。
提交:
git add openprogram/mcp/server/service.py openprogram/mcp/server/tools.py tests/unit/test_mcp_server_tools.py
git commit -m "feat: add MCP session and tool discovery"
任务 6:实现失败时拒绝的 tool_call 与进度映射#
文件:
- 修改:
openprogram/mcp/server/service.py - 修改:
openprogram/mcp/server/tools.py - 修改:
tests/unit/test_mcp_server_tools.py
接口:
async def MCPService.tool_call(
self,
name: str,
arguments: Mapping[str, Any],
*,
call_id: str,
cancel_event: asyncio.Event,
on_progress: Callable[[str], None] | None,
) -> AgentToolResult: ...
def to_mcp_content(result: AgentToolResult) -> list[mcp.types.ContentBlock]: ...
RED: 添加表驱动测试:未知名称与已知但未公开名称返回相同 METHOD_NOT_FOUND 错误;已公开名称缺少 paired 能力时返回 is_error=True 并说明缺失能力;硬约束、策略和不可用审批的拒绝返回类型化错误;无效底层参数返回 INVALID_PARAMS;成功文本/图像转换;底层异常/失败转换;每种拒绝均不执行调用。验证执行使用固定 source="mcp"、permission_mode="ask"、paired 权限且无请求可控覆盖的 TurnRequest。验证更新回调保序,缺少 on_progress 时忽略。
运行:
uv run --locked pytest -q tests/unit/test_mcp_server_tools.py -k tool_call
预期:FAIL,因为 tool_call() 缺失。
GREEN: 先解析公开范围且不泄露注册表成员关系,验证所选 Runtime 工具 schema,检查 decide_tool_authority(),通过 wrap_with_approval() 包装,再调用 AgentTool.execute(call_id, arguments, cancel_event, on_update)。仅转换支持的内容。保留 AgentToolResult.is_error;不得从自由文本推断失败。
进度约定:服务仅报告有序更新消息。任务 8 的传输层将其转换为单调递增的 MCP 进度值,且仅在请求元数据包含 progressToken 时发送。
运行相同命令;预期:PASS。
提交:
git add openprogram/mcp/server/service.py openprogram/mcp/server/tools.py tests/unit/test_mcp_server_tools.py
git commit -m "feat: add scoped MCP runtime tool calls"
任务 7:实现 prompt 归属、非交互问题和取消清理#
文件:
- 修改:
openprogram/mcp/server/service.py - 修改:
openprogram/mcp/server/tools.py - 创建:
tests/unit/test_mcp_server_turns.py
接口:
@dataclass
class ActiveMCPRequest:
request_id: str
session_id: str
client_id: str
thread_cancel: threading.Event
tool_cancel: asyncio.Event
async def MCPService.prompt_send(..., request_id: str, ...) -> AgentToolResult: ...
def MCPService.prompt_cancel(session_id: str) -> AgentToolResult: ...
def MCPService.cancel_request(request_id: str, *, reason: str) -> None: ...
def MCPService.close() -> None: ...
RED: 断言省略 session id 时使用 agent main 和 source mcp 创建 mcp_<uuid>;提供已有会话时保留存储的 agent id;提供未知 id 返回 INVALID_PARAMS;process_user_turn() 接收固定安全字段和已注册线程事件。断言并发请求拥有请求级记录。prompt_cancel 仅对同一客户端、同一连接的活跃轮次成功;未知、已完成或外部轮次均拒绝,且不设置其事件。
订阅测试事件总线,为活跃及无关会话发出 question.asked。活跃 MCP 问题必须立即解析为 declined;无关问题不得受影响。取消时断言 thread_cancel.set()、tool_cancel.set()、mark_cancelled(session_id)、kill_active_subprocess(session_id)、kill_active_runtime(session_id)、QuestionRegistry.cancel_session(session_id)、unregister_cancel_event(session_id, exact_event)、移除活跃映射,以及包含 request/session/client 指纹但不含 prompt/token 的 mcp.request.cancelled 审计。重复取消和清理以证明幂等性。
运行:
uv run --locked pytest -q tests/unit/test_mcp_server_turns.py
预期:FAIL,因为 prompt 生命周期及活跃请求归属缺失。
GREEN: 使用 anyio.to_thread.run_sync(..., abandon_on_cancel=True) 运行同步 process_user_turn(),并始终在 finally 中注销精确的取消事件。问题总线订阅限定于服务生命周期,在 close() 中取消订阅。在请求边界捕获取消,调用 cancel_request() 后重新抛出,让 SDK 1.29.0 生成标准 JSON-RPC 取消错误。该取消路径绝不构造 AgentToolResult。保护完成操作,防止被放弃的 worker 后续发布结果或修改活跃请求记录。
运行相同命令;预期:PASS。
提交:
git add openprogram/mcp/server/service.py openprogram/mcp/server/tools.py tests/unit/test_mcp_server_turns.py
git commit -m "feat: add MCP prompt lifecycle and cancellation"
任务 8:连接 MCP 1.29.0 stdio 并添加端到端协议测试#
文件:
- 创建:
openprogram/mcp/server/server.py - 修改:
openprogram/mcp/server/__init__.py - 修改:
openprogram/_cli_cmds/mcp.py - 修改:
openprogram/cli.py - 修改:
tests/unit/test_mcp_server_cli.py - 创建:
tests/integration/test_mcp_server.py
接口:
def build_server(context: MCPClientContext) -> mcp.server.Server: ...
async def serve_stdio(context: MCPClientContext) -> None: ...
def serve() -> int: ...
def _cmd_mcp_serve() -> int: ...
serve() 在进入 mcp.server.stdio.stdio_server() 前调用 authenticate_from_environment() 并构造 MCPClientContext。身份验证错误向 stderr 打印脱敏文本,返回非零值,保持 stdout 为空,且从不读取 stdin。
使用 Server.run(read, write, server.create_initialization_options())。在 server.request_handlers 中显式注册 CallToolRequest 处理器,不能仅依赖将全部异常转换为 isError 的装饰器;包装工具名和无效参数错误必须保留为 MCP 错误。从 server.request_context 取得 request id、progressToken 和 session。仅为脱敏诊断读取 client_params.clientInfo。
RED: 先扩展 CLI 单元测试:mcp serve 在本地分发且永不调用 _require_backend_endpoint;身份验证发生在 stdio 上下文之前。再添加 SDK 客户端子进程测试,覆盖:
- 缺失 token 文件、错误权限、缺失环境变量和环境变量不匹配均在 initialize 前退出,状态非零、stdout 为空、stderr 脱敏;
- 有效 token 完成 2025-11-25 initialize,更改
clientInfo.name/version不影响身份、工具列表或结果; - MCP
tools/list恰好返回有序的六个固定工具,tools_list包装工具仅返回允许列表/注册表/paired 交集; - 六种映射均返回固定结构;未知包装工具名和无效参数为 MCP 错误;
- 未知/未公开底层名称不可区分,作用域/硬约束/审批失败为
isError,且被拒绝工具均不执行; - 审批或普通问题永不阻塞 MCP 调用;
- 当且仅当请求提供
progressToken时,进度通知按单调顺序到达; - 取消进行中的
prompt_send为原请求产生 SDK 1.29.0 标准 JSON-RPC 取消错误,绝不产生工具结果/isError;后续副作用停止,活跃请求/问题状态清除,且存在审计事件; prompt_cancel保持普通工具调用,不能取消外部/已完成轮次;- stdout 仅包含 JSON-RPC;诊断和审计文本绝不写入其中。
运行:
uv run --locked pytest -q tests/unit/test_mcp_server_cli.py tests/integration/test_mcp_server.py
预期:FAIL,因为 stdio 服务及 mcp serve 缺失。
GREEN: 仅实现 stdio 服务器和本地 CLI 分发。通过内存对象流或 loop.call_soon_threadsafe 将 worker 线程回调的进度转交 AnyIO 循环;仅在存在 token 时调用 ServerSession.send_progress_notification()。请求处理器捕获普通执行失败并按固定矩阵转换,但重新抛出取消。确保关闭时执行 MCPService.close()。
运行相同命令;预期:PASS。
同时运行共享回归:
uv run --locked pytest -q tests/unit/test_authority_scope.py tests/unit/test_permission_rules.py tests/unit/test_spawn_hard_constraints.py tests/agent/test_questions.py tests/unit/test_acp_server.py tests/unit/test_tools_runtime.py
预期:PASS。
提交:
git add openprogram/mcp/server/server.py openprogram/mcp/server/__init__.py openprogram/_cli_cmds/mcp.py openprogram/cli.py tests/unit/test_mcp_server_cli.py tests/integration/test_mcp_server.py
git commit -m "feat: serve authenticated MCP over stdio"
附录:当前实现状态#
本计划描述的 MCP 服务器已存在于当前源码。
实现位于 openprogram/mcp/server/(auth.py、contracts.py、
server.py、service.py 和 tools.py),CLI 分发位于
openprogram/_cli_cmds/mcp.py 和 openprogram/cli.py。当前服务器
在进入 stdio 前完成身份验证,公开固定包装工具,应用
paired 非交互权限,并负责取消清理。锁定的
依赖和协议基线仍为 mcp==1.29.0 与 MCP 2025-11-25。
下面的计划记录历史实现顺序,保留它是为了 说明约定和审查顺序,不是未实现工作列表。 当前完成证据及剩余的已安装/运行时验收 边界由权威 MCP 服务器设计页及其测试维护。本次 审计未运行测试或文档门禁,因此不声称新增的 通过数量或发布验收。
历史任务 9:记录实现证据并运行完整发布门禁#
文件:
- 修改:
docs/reference/design/integrations/mcp-server.html - 修改:
docs/reference/design/plans/mcp-server-implementation.md,仅在执行路径或命令明显不同时修改 - 更新:
.superpowers/sdd/2026-08-12-mcp-server/ledger.md(已 gitignore;不得提交)
RED: 编辑状态前运行完整门禁,在台账中记录精确命令/输出/提交证据:
uv run --locked pytest -q tests/unit/test_mcp_server_contracts.py tests/unit/test_mcp_server_auth.py tests/unit/test_mcp_server_cli.py tests/unit/test_mcp_server_security.py tests/unit/test_mcp_server_tools.py tests/unit/test_mcp_server_turns.py tests/integration/test_mcp_server.py
uv run --locked pytest -q tests/unit/test_authority_scope.py tests/unit/test_permission_rules.py tests/unit/test_spawn_hard_constraints.py tests/agent/test_questions.py tests/unit/test_acp_server.py tests/unit/test_tools_runtime.py
uv run --locked --with mdit-py-plugins python -m scripts.docs_site.build
uv run --locked --with mdit-py-plugins python -m scripts.docs_site.checklinks
git diff --check
预期:全部测试和文档命令通过。若任一命令失败,保持 HTML 实现状态不变,返回对应任务;如果缺少覆盖,则新增 RED 测试并在该任务修复。
GREEN: 仅在完整门禁通过后,使用实际文件、测试、SDK/协议版本和观测数量更新 HTML 实现状态表与证据单元格。保留文档顺序:当前状态 → 竞品设计 → 后续计划 → 实现证据。不得声称 HTTP/OAuth/Web 路由、ACP 修改、SDK 扩展或门禁未证实的结果。取消措辞保持明确:SDK JSON-RPC 取消错误、无应用工具结果、OpenProgram 仅清理/审计。
文档编辑后重新运行上述五个命令。预期:PASS 且 diff 检查无问题。
提交:
git add docs/reference/design/integrations/mcp-server.html docs/reference/design/plans/mcp-server-implementation.md
git commit -m "docs: record MCP server implementation evidence"
审查检查点#
- 继续实现前审查任务 1:schema 是公开 v1 接口,后续任务不得附带修改。
- 合并审查任务 3–4 的安全性:不复用 owner 凭据,不以 clientInfo 授权,空列表不回退为全部工具,请求不能控制权限字段。
- 合并审查任务 6–7 的失败拒绝行为:每次拒绝发生在调用前,问题不等待,取消后不留下后续结果或副作用。
- 使用 SDK 客户端审查任务 8,不使用手写 JSON-RPC 帧。SDK 实测取消行为属于验收基线。
- 原任务 9 是记录 Layer 3 证据的历史步骤;当前实现状态由权威 MCP 服务器设计页维护。