oclaw/docs/MCP_LOCAL_SERVER.md
oliver f6f129931f Ship Cursor-only MCP admin and modular Admin/Chat UI.
Plugins/Skills install and edit via mcpServers JSON, with clearer row actions, soft reloads, and instant loading placeholders; also drop MCP market and harden related scheduler/logging/exec guards.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 19:38:26 +08:00

17 KiB
Raw Blame History

本地 MCP 工具开发与接入指南(stdio / JSON-RPC)

本文档用于指导你在本地编写 MCP Server,并接入当前系统的 MCP 市场。

当前系统对 MCP 的主流程已统一为标准协议:

  • initialize
  • notifications/initialized
  • tools/list
  • tools/call

兼容说明:系统内部仍保留对旧 op 风格消息的回退兼容,但不建议新工具继续使用旧协议。


1. 你要实现什么

你写的本地工具不是直接写成 ToolSpec,而是写成一个 MCP Server 子进程,通过标准输入输出(stdio)和系统通信。

系统会:

  1. 启动你的进程(entry_command + entry_args)
  2. 发送 initialize
  3. 接收 tools/list
  4. 在用户调用时发送 tools/call

2. 最小可运行示例(Python)

保存为 mcp_echo_server.py:

from __future__ import annotations

import json
import sys
from typing import Any


def ok(rid: Any, result: dict[str, Any]) -> None:
    sys.stdout.write(json.dumps({"jsonrpc": "2.0", "id": rid, "result": result}, ensure_ascii=False) + "\n")
    sys.stdout.flush()


def err(rid: Any, code: int, message: str) -> None:
    sys.stdout.write(
        json.dumps(
            {"jsonrpc": "2.0", "id": rid, "error": {"code": code, "message": message}},
            ensure_ascii=False,
        )
        + "\n"
    )
    sys.stdout.flush()


for raw in sys.stdin:
    raw = raw.strip()
    if not raw:
        continue

    try:
        req = json.loads(raw)
    except Exception:
        continue

    rid = req.get("id")
    method = str(req.get("method") or "")
    params = req.get("params") if isinstance(req.get("params"), dict) else {}

    if method == "initialize":
        ok(
            rid,
            {
                "protocolVersion": "2024-11-05",
                "capabilities": {"tools": {}},
                "serverInfo": {"name": "echo-mcp", "version": "0.1.0"},
            },
        )
        continue

    if method == "notifications/initialized":
        # 通知类消息不需要返回
        continue

    if method == "tools/list":
        ok(
            rid,
            {
                "tools": [
                    {
                        "name": "echo",
                        "description": "回显输入文本。",
                        "inputSchema": {
                            "type": "object",
                            "properties": {
                                "text": {"type": "string", "description": "要回显的文本"}
                            },
                            "required": ["text"],
                            "additionalProperties": False,
                        },
                    }
                ]
            },
        )
        continue

    if method == "tools/call":
        tool_name = str(params.get("name") or "")
        arguments = params.get("arguments") if isinstance(params.get("arguments"), dict) else {}

        if tool_name != "echo":
            err(rid, -32601, f"unknown tool: {tool_name}")
            continue

        text = str(arguments.get("text") or "")
        ok(rid, {"content": [{"type": "text", "text": text}]})
        continue

    err(rid, -32601, f"method not found: {method}")

3. 在管理台中接入

3.0 已安装 MCP 从库里消失时(换库 / 表被清空)

mcp_server_registry 存在默认 SQLite(见 data/ai_ops.sqlite)中。若列表变成 0 条,可在仓库根执行 python scripts/seed_mcp_registry.py,从 data/mcp_registry.seed.json 写回示例条目(含 local-echo 与 mcp-context7,可按需编辑该 JSON 再执行)。写回后在管理台对各服务执行 Health → Sync Tools;若曾换过 OPS_ASSISTANT_DB_PATH,请确认网关与脚本指向同一库文件。

3.1 表单安装(推荐)

在 MCP 页面填:

  • source_type: 可自定义用于记录(如 pypi)
  • source_ref: 自定义来源标识(如 local-echo)
  • entry_command: python
  • entry_args: D:/path/to/mcp_echo_server.py

然后执行:

  1. Install
  2. Health
  3. Sync Tools

工具数量 > 0 即接通成功。

3.2 Cursor JSON 安装

在 Plugins 【2】从 Cursor JSON 安装 粘贴与 Cursor mcp.json 相同的文档(仅支持 mcpServers,不再兼容旧的 servers[] / 表单字段):

{
  "mcpServers": {
    "local-echo": {
      "command": "python",
      "args": ["__REPO_ROOT__/examples/mcp_echo_server.py"]
    },
    "remote-demo": {
      "url": "https://example.com/mcp",
      "headers": { "Authorization": "Bearer ${API_KEY}" }
    }
  }
}
  • stdio:command / args / env
  • 远程:仅 url(可带 headers)时由服务端归一成 npx -y mcp-remote … 桥接
  • __REPO_ROOT__/… 在安装时展开为仓库根路径

导出与自动备份

  • Export JSON 下载当前全部已安装 MCP 的 Cursor mcpServers 快照。
  • 安装 / 改配置 / 卸载删记录后刷新 oclaw/_local/mcp_registry_migrated.json(同结构)。可用 python -m runtime.operations.scripts.seed_mcp_registry path/to/file.json 灌库。密钥仍放 oclaw/_local/mcp_local.env。

3.3 MCP 工具可见性(仅绑定)

线侧「打压」策略(分层压缩 / 闲置惩罚 / 9999 永禁 / role_mode)已移除。

发给 LLM 的 MCP 工具范围只由 专家 ↔ MCP server 绑定 决定:

  • 管理台 Plugins:【6】专家 MCP 绑定看板、【7】MCP 专家绑定(编辑)
  • 持久化键:mcp_specialist_server_binding(及粗粒度兜底 mcp_allowed_specialists / AIA_MCP_SPECIALISTS)
  • 运行时:materialize_mcp_tools_for_specialist;上送前仅做 schema complete 与可选 JSON 体积压缩(prepare_openai_tools_for_llm_api)

4. 专家分配(谁能用这个工具)

当前系统支持“按专家组”分配 MCP 工具可见性:

  • 在 MCP 页面 MCP specialists 勾选可用专家
  • 保存后立即生效(持久化在数据库)

注意:当前是“专家级分配”,不是“单工具级分配”。


5. 编写规范(强烈建议)

  1. stdout 只输出 JSON-RPC 响应行
    日志请写到 stderr,否则容易触发 protocol_mismatch。

  2. tools/list 返回稳定 schema
    建议所有参数都写明 type 与 required,并设 additionalProperties: false。

  3. tools/call 出错要可解释
    优先通过 JSON-RPC error 返回明确错误信息。

  4. 避免长时间阻塞
    长任务应拆分或优化,否则会出现 mcp_runtime_timeout。


6. 常见错误与排查

  • mcp_runtime_timeout
    含义:子进程超时未返回。
    排查:先本地单独运行 server,确认单次调用耗时;必要时调大 timeout_s。

  • mcp_runtime_protocol_mismatch
    含义:收到的不是 JSON-RPC 响应。
    排查:检查是否把日志打印到了 stdout。

  • mcp_runtime_bad_json
    含义:输出不是合法 JSON。
    排查:检查编码、换行、对象结构。

  • mcp_tools_list_invalid
    含义:tools/list 返回结构不符合预期。
    排查:确认返回 result.tools 为数组,元素包含 name、inputSchema。


7. 建议开发流程

  1. 本地先用脚本手动跑通 JSON-RPC
  2. 管理台安装(建议先 dry_run)
  3. 执行 Health、Sync Tools
  4. 在 Check Installed 做批量体检
  5. 按专家映射开放给目标专家

8. 与原有内置工具关系

MCP 工具是增量能力,不会替代原有内置工具体系。
最终都走统一 ToolExecutor 执行链(策略、超时、审计一致)。

Local MCP Server Guide (stdio)

Goal

Write local tools as a standard MCP server over stdio (JSON-RPC), then connect them from Admin → Plugins → Install from Cursor JSON.

This project now uses MCP standard flow in runtime:

  • initialize
  • notifications/initialized
  • tools/list
  • tools/call

Legacy custom op messages are only compatibility fallback.


Minimal Python MCP Server

Save as mcp_echo_server.py:

from __future__ import annotations

import json
import sys
from typing import Any


def _ok(rid: Any, result: dict[str, Any]) -> None:
    sys.stdout.write(json.dumps({"jsonrpc": "2.0", "id": rid, "result": result}, ensure_ascii=False) + "\n")
    sys.stdout.flush()


def _err(rid: Any, code: int, message: str) -> None:
    sys.stdout.write(
        json.dumps(
            {"jsonrpc": "2.0", "id": rid, "error": {"code": code, "message": message}},
            ensure_ascii=False,
        )
        + "\n"
    )
    sys.stdout.flush()


for line in sys.stdin:
    line = line.strip()
    if not line:
        continue
    try:
        req = json.loads(line)
    except Exception:
        continue
    rid = req.get("id")
    method = str(req.get("method") or "")
    params = req.get("params") if isinstance(req.get("params"), dict) else {}

    if method == "initialize":
        _ok(rid, {"protocolVersion": "2024-11-05", "capabilities": {"tools": {}}, "serverInfo": {"name": "echo-mcp", "version": "0.1.0"}})
        continue

    if method == "notifications/initialized":
        # Notification has no response.
        continue

    if method == "tools/list":
        _ok(
            rid,
            {
                "tools": [
                    {
                        "name": "echo",
                        "description": "Echo input text.",
                        "inputSchema": {
                            "type": "object",
                            "properties": {"text": {"type": "string"}},
                            "required": ["text"],
                            "additionalProperties": False,
                        },
                    }
                ]
            },
        )
        continue

    if method == "tools/call":
        name = str(params.get("name") or "")
        args = params.get("arguments") if isinstance(params.get("arguments"), dict) else {}
        if name != "echo":
            _err(rid, -32601, f"unknown tool: {name}")
            continue
        text = str(args.get("text") or "")
        _ok(rid, {"content": [{"type": "text", "text": text}]})
        continue

    _err(rid, -32601, f"method not found: {method}")

Run manually (sanity check):

python mcp_echo_server.py

Then send one JSON-RPC request line from stdin to verify.


Install in Admin (Cursor JSON)

Paste Cursor mcpServers JSON in Plugins → Install from Cursor JSON. There is no MCP Market UI; edit installed servers via the Edit action (same Cursor entry shape).

For a local Python script:

{
  "mcpServers": {
    "local-echo": {
      "command": "python",
      "args": ["D:/project/chatgpt/examples/mcp_echo_server.py"]
    }
  }
}

After install:

  1. Health
  2. Sync Tools
  3. Verify tool count > 0

@modelcontextprotocol/server-filesystem 与网关工作区

官方 @modelcontextprotocol/server-filesystem 只在进程启动时把命令行里列出的目录当作可访问根;多装一个路径就要多传一个 argv,否则 list_directory 等工具无法列出该目录。

本仓库在启动该 MCP 时会自动合并与内置工作区一致的路径来源,并去重后追加到 entry_command + entry_args 之后(不改变你在管理台填写的主根,只追加额外根):

来源 说明
OPS_WORKSPACE_EXTRA_ROOTS 环境变量,| 分隔
OPS_MCP_FILESYSTEM_EXTRA_ROOTS 环境变量或 SQLite settings 表同名键,仅影响该 MCP
Admin「工作区路径」 当前用户聊天会话(ui_session_owner 绑定的 session_id)对应账号的 extra_roots(| 拆分);不会合并其他用户。若 ui_session_owner 行缺失,会从请求里携带的 tenant_id / user_id(metadata)再拉一份同一条 allowlist,与内置 resolve_workspace_path 及 MCP 追加 argv 对齐。
Windows 路径 在网关侧与 MCP argv 中会对路径作规范化;若仍报「无权限」或子进程报路径不在根下,可对比管理台中保存的「绝对路径」与资源管理器里实际盘符/大小写是否一致,修改工作区后对该 MCP Health → Sync Tools。

与 allow_any_path 的关系:管理台里的 allow_any_path 只影响网关内置工具(走 resolve_workspace_path 的读文件、glob、run_command 等),相当于在 Python 侧跳过「必须在 workspace 根或 extra_roots 下」的检查。官方 server-filesystem 不认这个字段:子进程只认启动时写在 argv 里的具体目录列表,没有「允许任意路径」的等价开关,因此单靠 allow_any_path: true 不会把 D:\download 等路径自动加进 MCP。要让 MCP 列到这些目录,请把它们写进 extra_roots(或 OPS_WORKSPACE_EXTRA_ROOTS / OPS_MCP_FILESYSTEM_EXTRA_ROOTS),再 Health → Sync Tools。

运维注意:同一网关进程内,不同用户对话会各自 materialize 一套 MCP 工具绑定(argv 含该用户 extra_roots + 全局 env)。管理台 Health / Sync Tools 无用户会话上下文,此时仅合并 环境变量与 settings,不含任一用户的 DB extra_roots。

修改环境或 DB 后,请对相应 MCP 执行 Health → Sync Tools(或重启网关),以便新进程带上更新后的 argv。


Tool Result Format Recommendations

For tools/call result:

  • success: return {"content":[{"type":"text","text":"..."}]}
  • failure: return JSON-RPC error or result with isError=true

Keep responses deterministic and JSON-serializable.


Common Errors

  • mcp_runtime_timeout
    Server did not answer in time. Check blocking calls, raise timeout_s, or optimize startup.

  • mcp_runtime_protocol_mismatch
    Output is not JSON-RPC response line. Ensure stdout only emits JSON-RPC lines (move logs to stderr).

  • mcp_runtime_bad_json
    Response line is malformed JSON. Validate serialization and newline framing.

  • mcp_tools_list_invalid
    tools/list did not return valid tools array.


通识工具库与 Cursor / Claude Code / oclaw(能力对齐说明)

已能覆盖的常见编码助手能力:仓库读写与搜索(内置 workspace + MCP filesystem)、Git 本地与 GitHub 远端、网页抓取与浏览器自动化(fetch / playwright)、会话库 SQLite、日历与时间、PDF、顺序思考与 memory MCP 等。

单靠 MCP 无法等价的部分:IDE 内 LSP 实时红线(Cursor 编辑器集成)、oclaw 式 ACP 外接 Claude Code/Codex 子进程(需单独编排/通道产品化)。

Context7(库文档时效)

  • 作用:按库名/版本拉取较新的官方文档片段,减少「API 记错版本」类幻觉。
  • 安装:python scripts/install_mcp_context7.py,或管理台 POST /admin/api/mcp/install 使用 examples/mcp_install_context7.json 中的 payload。
  • 密钥:在 oclaw/_local/mcp_local.env(推荐)或 data/mcp_local.env(兼容)设置 CONTEXT7_API_KEY(见 context7.com/dashboard)。两处都存在时同键以 oclaw/_local/mcp_local.env 为准(覆盖 data 中的同键)。写入任一合并 mcp_local.env 的键会自动传入 MCP 子进程;若密钥只配在宿主/Docker 环境、不进文件,才依赖内置或自定义的 AIA_MCP_ENV_ALLOWLIST 补充名单。
  • 装完后:Health → Sync Tools → 将 mcp-context7 加入通识 specialist 的 MCP 绑定(若脚本已成功 Sync,会自动追加)。

Bailian WebSearch(DashScope)

  • 密钥:在 oclaw/_local/mcp_local.env(推荐)设置 DASHSCOPE_API_KEY=...。
  • 关键注意:密钥写在 mcp_local.env 里即可传入 MCP。若 DASHSCOPE_API_KEY 只存在于宿主环境、未写入 mcp_local.env,须确保其出现在 AIA_MCP_ENV_ALLOWLIST 默认或自定义补充名单中(或用 AIA_MCP_ENV_ALLOWLIST_EXTRA 追加)。常见表现是:
    • error_code: mcp_runtime_empty_response
    • error: empty_response
  • 排查顺序:
    1. 确认 mcp_local.env 已写 DASHSCOPE_API_KEY
    2. 确认 allowlist 包含 DASHSCOPE_API_KEY
    3. 在 Admin 对该 MCP 执行 Health → Sync Tools
    4. 确认该 MCP 已绑定到当前会话使用的 specialist(不只是 generalist)

通识侧终端能力(run_command)

与 Claude Code「在仓库里跑命令」类似的能力来自内置 run_command,但通识 lane 需同时满足:

  1. 环境 OPS_ENABLE_RUN_COMMAND=1(见 oclaw/tools/catalog.py 与 oclaw/tools/experts/workspace/shell_tools.py 门控)。
  2. 仅在 可信仓库 / 内网 开启;否则易误执行高危命令。

Postgres / Linear / Slack / Sentry 等

按实际业务栈再装对应 MCP 即可;无相关系统则不必安装,避免工具膨胀与误选。


Multi-specialist Assignment

MCP tools are assigned in Admin UI by specialist mapping.
Only selected specialists can see/use MCP tools at runtime.