EvoX 文档
  • 产品
  • 价格
  • 文档
  • 能力市场
返回 EvoMap
登录注册
概览
实践案例
功能
配置
开发者
开发工作流
在项目中开发代码审阅与变更处理集成终端Git worktree
扩展开发
编写技能接入 MCP 工具Hooks
经验与协议集成
GEP 数据结构蒸馏 Gene、Skill 与 RecipeRecipe 与经验复用Evolver Proxy 与 MCP同步、导入与导出A2A 与任务网络接入
独立 Evolver
安装与首次运行审查与验证持续运行与任务队列身份与连接进化策略与边界资产发布与积分存储、容器与 CI
架构与源码仓库
安全
管理
概览
实践案例
功能
配置
开发者
开发工作流
在项目中开发代码审阅与变更处理集成终端Git worktree
扩展开发
编写技能接入 MCP 工具Hooks
经验与协议集成
GEP 数据结构蒸馏 Gene、Skill 与 RecipeRecipe 与经验复用Evolver Proxy 与 MCP同步、导入与导出A2A 与任务网络接入
独立 Evolver
安装与首次运行审查与验证持续运行与任务队列身份与连接进化策略与边界资产发布与积分存储、容器与 CI
架构与源码仓库
安全
管理
EvoX 文档/使用 EvoX 开发/接入 MCP 工具

接入 MCP 工具

通过 MCP 服务把自己的工具提供给 EvoX。本页完成一个只读接入:让 Agent 读取固定项目的 npm 脚本,验证工具发现、调用和错误处理。连接管理与凭证表单见MCP 服务器;向其他 Agent 提供 Evolver 经验工具见Evolver Proxy 与 MCP。

定义工具的作用范围

本地示例只用于运行账号控制的项目目录及父目录;其他用户不得替换这些目录或在调用期间修改文件。服务拒绝符号链接、核对已打开文件的身份,并从同一描述符最多读取 64 KiB 加一个溢出检测字节;共享恶意文件系统的隔离由系统账号与权限负责。

示例工具 read_project_scripts 只读取启动时指定项目的 package.json,返回 scripts,不执行脚本、不写文件、不访问网络。项目路径由服务启动参数指定,不接受 Agent 随意传入文件路径。

契约本例的决定
输入空对象 {};拒绝额外字段
输出文本内容中的 JSON 对象,包含 scripts
资源固定项目的普通 package.json,上限 64 KiB
错误无效参数返回协议错误;文件读取失败返回 isError: true
副作用无文件写入与命令执行

创建 stdio 服务

将以下代码保存为项目之外的 read-project-scripts.mjs。它使用 Node.js 内置模块,标准输出只写 MCP 协议消息。实际服务还需要按部署方式补充会话管理、资源限制与访问控制。

javascript
import { createInterface } from "node:readline";
import { realpathSync, lstatSync, openSync, fstatSync, readSync, closeSync, constants } from "node:fs";
import { join } from "node:path";

const root = realpathSync(process.argv[2]);
const file = join(root, "package.json");
const tool = {
  name: "read_project_scripts",
  description: "Read npm scripts from the configured project without executing them.",
  inputSchema: { type: "object", properties: {}, additionalProperties: false }
};
const send = (id, result) => process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, result }) + "\n");
const fail = (id, code, message) => process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } }) + "\n");
const protocolVersion = "2024-11-05";
let phase = "new";
const input = createInterface({ input: process.stdin });
input.on("line", (line) => {
  let request;
  try { request = JSON.parse(line); }
  catch { fail(null, -32700, "Invalid JSON"); return; }
  if (!request || typeof request !== "object" || Array.isArray(request)) {
    fail(null, -32600, "Invalid request"); return;
  }
  const { id, method } = request;
  if (id !== undefined && typeof id !== "string" && !(typeof id === "number" && Number.isFinite(id))) {
    fail(null, -32600, "Invalid request ID"); return;
  }
  if (request.jsonrpc !== "2.0" || typeof method !== "string") {
    fail(id ?? null, -32600, "Invalid request"); return;
  }
  if (request.params !== undefined && (!request.params || typeof request.params !== "object" || Array.isArray(request.params))) {
    if (id !== undefined) fail(id, -32602, "Invalid params");
    return;
  }
  const params = request.params ?? {};
  if (id === undefined) {
    if (method === "notifications/initialized" && phase === "negotiated") phase = "ready";
    return;
  }
  if (method === "initialize") {
    const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
    if (phase !== "new" || typeof params.protocolVersion !== "string" || !params.protocolVersion ||
        !object(params.capabilities) || !object(params.clientInfo) ||
        typeof params.clientInfo.name !== "string" || !params.clientInfo.name ||
        typeof params.clientInfo.version !== "string" || !params.clientInfo.version) {
      fail(id, -32602, "Invalid or repeated initialization"); return;
    }
    phase = "negotiated";
    send(id, { protocolVersion, capabilities: { tools: {} }, serverInfo: { name: "project-scripts", version: "1.0.0" } });
  } else if (method === "ping") {
    send(id, {});
  } else if (phase !== "ready") {
    fail(id, -32002, "Complete initialization before using tools");
  } else if (method === "tools/list") {
    send(id, { tools: [tool] });
  } else if (method === "tools/call") {
    const args = params.arguments === undefined ? {} : params.arguments;
    if (params.name !== tool.name || !args || typeof args !== "object" || Array.isArray(args) || Object.keys(args).length) {
      fail(id, -32602, "Unknown tool or invalid arguments"); return;
    }
    try {
      if (realpathSync(file) !== file) throw new Error("Linked file rejected");
      const before = lstatSync(file, { bigint: true });
      if (!before.isFile() || before.ino === 0n || before.size > 65536n) throw new Error("File rejected");
      const fd = openSync(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
      let text;
      try {
        const opened = fstatSync(fd, { bigint: true });
        if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino) throw new Error("File changed");
        const buffer = Buffer.alloc(65537);
        let size = 0, count;
        while (size < buffer.length && (count = readSync(fd, buffer, size, buffer.length - size, null)) > 0) size += count;
        if (size > 65536) throw new Error("File too large");
        text = buffer.toString("utf8", 0, size);
      } finally { closeSync(fd); }
      const pkg = JSON.parse(text);
      const scripts = pkg.scripts ?? {};
      if (!scripts || typeof scripts !== "object" || Array.isArray(scripts) || Object.values(scripts).some(v => typeof v !== "string")) throw new Error("Invalid scripts");
      send(id, { content: [{ type: "text", text: JSON.stringify({ scripts }) }] });
    } catch {
      send(id, { isError: true, content: [{ type: "text", text: "Cannot read a regular package.json with valid scripts within 64 KiB." }] });
    }
  } else {
    fail(id, -32601, "Method not found");
  }
});

这是验证单个只读工具的最小服务。输入按 JSON-RPC 单行消息处理,不提供 HTTP 端点、批量请求或完整生产服务治理;它也不提供操作系统沙箱。服务进程仍拥有启动用户的系统权限。

在 EvoX 中添加并验证

在扩展的 MCP 入口添加手动连接,选择 stdio,并分别填写:

字段示例
名称project-scripts
命令Node.js 可执行文件的绝对路径
参数read-project-scripts.mjs 的绝对路径,以及目标项目的绝对路径;作为两个独立参数

保存后检查连接和工具发现结果。向 Agent 发送:

text
使用 project-scripts 的 read_project_scripts 读取这个项目的 npm 脚本。
列出脚本名称和对应命令,不执行任何脚本。

把返回结果与目标项目的 package.json 对照。保存成功、进程启动、工具发现和实际调用分别确认;只看到服务名称不能证明工具可用。

检查协议与错误

下面是工具调用消息;连接建立时还需由 MCP 客户端完成 initialize 与 initialized 握手。

initialize 请求提供 protocolVersion、capabilities 对象,以及包含 name 和 version 字符串的 clientInfo。服务端支持协议 2024-11-05,协商时返回该版本;客户端不支持时断开连接。initialize 与 notifications/initialized 完成前拒绝工具发现和调用;ping 可在初始化前使用。

json
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"read_project_scripts","arguments":{}}}
验证项应得到的结果
普通项目返回与文件相同的脚本,不执行脚本
添加 path 参数返回无效参数错误,不读取其他文件
项目没有 package.json工具返回读取错误,不伪造空项目结果
文件超出限制或为符号链接拒绝读取
服务进程退出连接报错,恢复服务后重新验证发现与调用

扩展为写入工具

写入使用独立工具名和 schema,明确资源标识、修改内容、预览、授权与幂等行为。服务端验证范围和账号权限,不能依赖模型遵守工具描述。超时后查询真实操作结果,避免重试导致重复写入。

升级时比较工具名称、schema、错误和副作用,并重跑相同接入用例。停止 MCP 连接后,远程服务的账号授权和凭证仍需在提供方撤销。

上一篇编写技能下一篇Hooks