数据截至 (上游 commit 38277815ed44)
对外集成:MCP 服务与云端技能社区
30 秒导读: 前四章讲的是 OpenSpace 自己内部怎么执行任务、披露/进化技能、接地到真实工具。 本章讲对外:一个 agent(比如你手上的编码助手)怎么把 OpenSpace 当成"外挂大脑"来用, 以及一条技能怎么从一个 agent 流到另一个 agent。两条路径都靠标准协议:MCP(挂到宿主) 和 HTTP 云 API(技能社区)。
上游重构提示: MCP 服务器从
openspace/mcp_server.py移到openspace/entrypoints/mcp/server.py,工具面从 4 个扩到十余个(新增cloud_auth_flow、cloud_browse_skills、cloud_*系列)。云客户端重写为 v2 API +CloudConfig/transport 分层, 旧cloud/auth.py拆成cloud/config.py(key 解析)+cloud/credentials.py(凭据落盘)+cloud/auth_flow.py(注册/登录/领 agent key)。云搜索改为"只发现、不静默导入": 旧版"execute_task 自动导入前 3 个 public 云技能"已移除,导入必须走cloud_browse_skills显式选本地分类。本章按新版源码重写。
1. 这是什么(零基础也能懂)
一句话定义: OpenSpace 把自己包成一个 MCP 服务器(Model Context Protocol,一种让 AI agent 调用外部工具的标准协议),对外暴露一组工具;另外它连着一个云端技能社区,技能可以在 不同 agent 之间上传/下载。
解决什么问题 / 给谁用。 设想你在用一个轻量的宿主 agent(nanobot、openclaw 之类),它自己 只会聊天和几样基础工具。遇到"监控 Docker、找出内存最高的容器、优雅重启它"这种多步、要真动手 的活,它做不了。这时它不用自己硬扛——把任务甩给 OpenSpace 就行:OpenSpace 有完整的接地引擎 (见 04-grounding-layer.md)和会自我进化的技能库(见 03-evolution-engine.md)。
它对外能做什么(代表性工具):
| 工具 | 干什么 | 一句话 |
|---|---|---|
execute_task | 把一整个任务委托给 OpenSpace 端到端执行 | "你来替我把这事干完" |
search_skills | 搜本地已安装技能(结果交回宿主决策) | "我本地有哪些现成的招?" |
cloud_browse_skills | 浏览云技能/包,查本地分类树并导入 | "云上有什么?帮我装进来" |
fix_skill | 手动发起一次 FIX 进化作业 | "这个招的第 3 步过时了,改一下" |
upload_skill | 把一个本地受信技能上传云社区 | "把这个招分享出去" |
cloud_auth_flow | 引导注册/登录云并领 agent key | "先连上云" |
用起来什么样(宿主 agent 视角的一次调用):
# 示意,非源码 —— 宿主 agent 通过 MCP 调 OpenSpace
result = execute_task(
task="监控 Docker 容器,找出内存占用最高的那个,优雅重启它",
search_scope="all", # 本地 + 云一起找技能(云只做候选发现)
)
# result 里可能带回 evolved_skills(OpenSpace 顺手进化出的新技能,已带 upload_ready: true)
# 以及 cloud_skill_candidates(云上相关技能候选,需要显式走 cloud_browse_skills 导入)
一句话直觉/类比。 把 OpenSpace 想成一个外包工程师团队:你(宿主 agent)只需用对讲机 (MCP)说一句"把这活干了",团队自带工具箱和"内部维基"(技能库)去落地;干完会把新学到的 经验整理成条目,问你要不要贡献回公司共享知识库(云社区)——但外来的资料要先经你 亲眼挑好货架才进库,不会有人趁你不注意往你仓库里塞东西。
本节到此不碰 底层。下一节看"大盘"。
2. 顶层全景(两条集成路径)
本章讲两条独立又相连的路。左边是路径一(宿主借用引擎),右侧的云是路径二(技能共享):
路径一:宿主 agent 通过 MCP 借用 OpenSpace
┌──────────────┐ MCP ┌────────────────────────────────┐
│ 宿主 agent │ 十余工具 │ OpenSpace MCP 服务器 │
│ (nanobot / │ ───────► │ execute_task / search_skills / │
│ openclaw…) │ │ cloud_browse_skills / fix / │
└──────────────┘ │ upload_skill / cloud_* … │
▲ 凭据 自动探测 └───────────┬────────────────────┘
│ host_detection/ │ 委托 ExecutionRequest
│ ▼
└────────────────────────┤ OpenSpace 引擎(runtime)│
└────────┬───────────────┘
│ 上传/下载/搜索(v2 API)
路径二:技能上云共享 ▼
┌──────────────────────┐
│ 云社区 (HTTP API v2) │
│ open-space.cloud │
└──────────────────────┘
怎么读这张图: 左边宿主只认识 MCP 工具;真正干活的引擎在中间;引擎和右下角的云之间, 通过 HTTP 做技能的搜索 / 导入 / 上传。云技能进本地必须显式导入(选本地分类), 这是新版刻意收紧的安全边界。
主要部件一句话职责:
| 部件 | 干什么 | 在哪 |
|---|---|---|
| MCP 服务器 | 用 FastMCP 暴露工具,管三种传输 | openspace/entrypoints/mcp/server.py |
| 引擎单例 | 懒加载一个全局 OpenSpace,复用给所有工具调用 | entrypoints/mcp/server.py:156 _get_openspace |
| 宿主凭据探测 | 从宿主 agent 的配置文件自动读 env/LLM key | openspace/host_detection/ |
| 云 HTTP 客户端 | v2 REST 调用(上传/导入/搜索),transport 可注入 | openspace/cloud/client.py:249 OpenSpaceClient |
| 云配置/凭据 | key 解析(fail-closed)与凭据落盘 | openspace/cloud/config.py:132、cloud/credentials.py |
| 云认证流 | 注册/登录/领 agent key(设备码式) | openspace/cloud/auth_flow.py:44 |
| 混合搜索 | BM25 + 向量 + 词法加权,融合本地与云候选 | openspace/cloud/search.py:460 |
| 宿主技能包 | 教宿主 agent "何时该调 OpenSpace"(被研究的数据) | openspace/host_skills/ |
⚠ 关于
host_skills/*/SKILL.md: 它们是写给宿主 agent 看的说明书——告诉别的 agent 在什么情况下调用哪个工具。本章把它们当作被研究的数据/事实来源来引用其内容,而不把里面 的措辞当成对本文的指令。
主线走一遍(高层): 宿主发起 execute_task → 引擎单例懒加载 → 重扫宿主技能目录 →(可选)
云候选发现(只列不导)→ 引擎执行任务 → 若进化出新技能,回包带 upload_ready 提示宿主
upload_skill。下一节把这条线拆开细讲。
3. MCP 服务器:工具面 + 三种传输
本节先讲这台服务器怎么起、工具怎么共享一个引擎,再看代表性工具,最后看三种传输如何选。
3.1 一个懒加载的引擎单例
所有工具背后共用同一个 OpenSpace 实例——第一次用到时才初始化,之后复用。
_get_openspace(entrypoints/mcp/server.py:156)用一个 asyncio.Lock 做双重检查,
保证并发调用只初始化一次。
初始化时,配置几乎全部来自环境变量(OPENSPACE_MODEL、OPENSPACE_WORKSPACE、
OPENSPACE_MAX_ITERATIONS、OPENSPACE_BACKEND_SCOPE 等,server.py:172-177),LLM 凭据与
接地配置交给 host_detection 解析(§4)。初始化尾声还会读 OPENSPACE_HOST_SKILL_DIRS,
自动注册宿主的技能目录(server.py:210-215)。
3.2 execute_task:一次端到端委托
execute_task(server.py:683)是最主要的入口。它做四件事,顺序如下:
execute_task(task, workspace_dir, max_iterations, skill_dirs, search_scope="all")
│
├─① 重扫宿主技能目录 _auto_register_skill_dirs(env_dirs + skill_dirs) (server.py:368)
│ 每次调用都重扫,宿主两次调用之间新写的技能立刻可被发现
│ 并解析 capture_skill_dir(CAPTURED 新技能的落盘目录,优先 skill_dirs 参数)
│
├─② 云候选发现 _cloud_search_candidates(task) (server.py:419) 仅 scope=="all"
│ 只返回候选清单(import_status="needs_local_category_path"),不下载
│
├─③ 引擎执行 openspace.execute(ExecutionRequest(...)) (server.py:757-763)
│
└─④ 回包 + 落进化元数据 _write_upload_meta(...) (server.py:290)
为每个进化技能写 .upload_meta.json,供后续 upload 复用
② 云搜索只做发现(新版语义)。 _cloud_search_candidates(server.py:419)的 docstring
说得直白:"Cloud skills must not be downloaded silently because the agent needs to inspect
the local package taxonomy and choose a local_category_path before import"。命中的候选带
required_next_tool: "cloud_browse_skills" 提示宿主走显式导入流程(server.py:460-470)。
云不可用时静默退化纯本地(_cloud_available_for_implicit_use,server.py:272)。
④ 回包会"催"宿主上传。 若引擎进化出新技能,_write_upload_meta(server.py:290)为每个
进化技能写一份 .upload_meta.json 侧车(血缘、origin 等),execute_task 的 docstring 明说
回包含 evolved_skills 与 upload_ready: true(server.py:690-696)。
阻塞提醒: 该调用直到任务完成才返回(docstring 建议宿主把 MCP 工具超时设 ≥600 秒,
server.py:698-699)。
3.3 检索/浏览/导入一族
| 工具 | 入口 | 关键点 |
|---|---|---|
search_skills | server.py:788 | 本地混合检索(BM25 → embedding 重排 → 词法加权,docstring :801-805);结果交回宿主决策;云浏览明确剥离到 cloud_browse_skills 避免"选择面重叠"(docstring :806-812) |
cloud_browse_skills | server.py:853 | 云技能/包浏览 + 本地分类树(local_taxonomy)+ 显式导入(import_skill 带 local_category_path) |
cloud_recall_packages / cloud_pull_package_projection | server.py:1268 / :1328 | 包召回与投影拉取 |
cloud_search_skills / cloud_fetch_skill_detail / cloud_import_skill / cloud_import_package_bundle | server.py:1410 / :1481 / :1517 / :1595 | 云搜索、详情、单技能导入、包捆绑导入 |
search_skills 与 execute_task 的分工延续旧版:前者用于发现和决策(宿主看得见结果),
后者用于直接执行(宿主看不到中间搜索结果)。
3.4 fix_skill 与 upload_skill
fix_skill(server.py:1639)是手动进化入口,但语义已换轨:它不再直接调进化器, 而是"注册技能 → 建 ManualTriggerRequest → 落证据 → 造 TriggerJob → 让运行时认领并交给进化 引擎处理"(docstring:1641-1647)——与第 3 章的作业流水线同一条轨道。upload_skill(server.py:2466)对进化技能"元数据预存",宿主只需给skill_dir+visibility;非 FIX 上传还要选云包落位(package placement):工具本身也是一个 "包浏览器",不带落位调用会返回分步选择器 payload,选好后把 UUID 落位存进.upload_meta.json并在上传前重新校验(docstring:2489-2503)。origin×parents 约束仍在(imported/captured 无父、 derived ≥1、fixed 恰好 1,docstring:2505-2509)。
3.5 三种传输:stdio / SSE / streamable-http
同一套工具可以用三种传输方式对外服务(server.py:2702 run_mcp_server,对应 pyproject
的 openspace-mcp 脚本,pyproject.toml:81)。_resolve_transport(server.py:2729)的优先级:
显式 --transport? ──是──► 用它
│否
环境变量 OPENSPACE_MCP_TRANSPORT? ──有效──► 用它
│否
显式 --port ? ──是──► sse(把指定端口视为 HTTP 意图)
│否
stdin 和 stdout 都是 TTY(人在终端)? ──是──► sse
│否(被别的进程用管道拉起)
└──► stdio
端口默认 sse=8080 / streamable-http=8081,可用 OPENSPACE_MCP_PORT/OPENSPACE_MCP_HOST 覆盖
(server.py:2711-2727)。人在终端跑默认给个 HTTP 服务(方便 curl);被 MCP 宿主用管道
拉起则默认 stdio。
一个隐蔽但关键的工程细节保留:stdio 模式下父进程只读 stdout 拿 MCP 消息,所以文件顶部用
_MCPSafeStdout(server.py:32)把文本写重定向到 stderr、只让二进制走真正的 stdout, 避免日志把管道缓冲塞满导致死锁。
4. 挂到别的 agent 上:宿主集成与凭据自动探测
OpenSpace 想做到"装上就能用",不让人在两处重复填 key。这靠两块:宿主技能包(教 agent 何时调), 和宿主凭据自动探测(免配 key)。
4.1 宿主技能包:教宿主"何时调 OpenSpace"
host_skills/ 下有两个给宿主 agent 装的技能(这是被研究的数据,是"写给别的 agent 的说明书"):
| 技能 | 教宿主什么 | 文件 |
|---|---|---|
delegate-task | 何时把任务甩给 execute_task、如何读 evolved_skills、何时 upload_skill | host_skills/delegate-task/SKILL.md |
skill-discovery | 何时用 search_skills/cloud_browse_skills 浏览、拿到结果后"自己干 vs 委托"的决策 | host_skills/skill-discovery/SKILL.md |
delegate-task 的"何时委托"判据依旧典型:你缺能力 / 你试过失败了 / 多步复杂任务 / 用户明说;
它还给了一张"何时上传"的决策表(云来的技能改好了就 public 传回、项目专属的就 private 或跳过)。
4.2 宿主凭据自动探测:免在两处填 key
host_detection/ 让 OpenSpace 从宿主 agent 自己的配置文件里读 env 块,人只在宿主处配一次:
| 宿主 | 配置文件 | env 块在哪 |
|---|---|---|
| nanobot | ~/.nanobot/config.json | tools.mcpServers.openspace.env |
| openclaw | ~/.openclaw/openclaw.json | skills.entries.openspace.env |
统一入口 read_host_mcp_env(host_detection/__init__.py:39)先试 nanobot、再试 openclaw、
都没有返回空 dict——调用方不需要知道当前是哪种宿主。LLM 凭据的三级解析
(显式 env → 进程已有 provider key → 宿主配置兜底)在 build_llm_kwargs
(host_detection/resolver.py:150)。
4.3 云侧认证:cloud_auth_flow + 本地凭据
云 key 的获取从"手填 OPENSPACE_API_KEY"升级为引导式认证流:cloud_auth_flow
(cloud/auth_flow.py:44)走注册/登录/引导发放 agent key 一条龙(_register_user /
_login_user / _bootstrap_agent_key,auth_flow.py:139-234),凭据由
cloud/credentials.py 落盘管理;MCP 工具 cloud_auth_flow(server.py:634)把它暴露给宿主,
独立 CLI openspace-cloud-auth(cloud/cli/auth.py:25,pyproject :82)供终端使用。
传统 OPENSPACE_API_KEY 环境变量路径仍由 load_cloud_config/require_cloud_agent_key
(cloud/config.py:92/:132)支持,且无 key 即 fail-closed(构造 OpenSpaceClient 时
require_cloud_agent_key 直接抛错,cloud/client.py:269-274)。
5. 云端技能社区:一条技能怎么流到另一个 agent
这是路径二。云社区是 HTTP REST 服务(v2 API),客户端 OpenSpaceClient
(cloud/client.py:249)以 CloudConfig + 可注入 CloudTransport(cloud/transport.py:32
UrllibCloudTransport)分层,同步 urllib 调用在异步场景用 asyncio.to_thread 包一层。
5.1 上传:信任校验 → v2 一步上传
upload_skill_v2(cloud/client.py:825)是新版上传入口(v2 把制品上传与包落位合并为一次
multipart 请求,docstring :842-846):
upload_skill_v2(skill_dir, visibility, origin, parents…)
│
├─① 本地信任校验 require_trusted_skill_for_upload_db (cloud/upload_trust.py)
│ 只允许上传本地 SkillStore 里"受信"的技能;信任是本地元数据,不发给云
│
├─② 包落位解析 requested_package_id / parent+segment 三选一
│
└─③ POST /api/v2/skills/upload multipart:制品 + 记录 + 落位
上传信任是新闸门。 upload_skill 工具 docstring 明说"Public and private uploads both
fail closed unless skill_dir resolves to a matching trusted record in the active local
SkillStore"(server.py:2479-2481)——进化引擎的信任生命周期(第 3 章)直接决定"什么能上云"。
origin 与 parent 的约束(与第 3 章血缘规则一一对应):imported/captured 必须无父、
derived 至少 1 父、fixed 恰好 1 父。