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 文件/Build with EvoX/接入 MCP 工具

Connect MCP tools

Expose your tools to EvoX through an MCP server. This guide implements a read-only integration: an agent reads npm scripts from a fixed project while you verify discovery, invocation, and errors. Connection management and credentials belong in MCP servers. To expose Evolver experience tools to other agents, see Evolver Proxy and MCP.

Define tool scope

Run this local example only in a project directory and parent directories controlled by the runtime account. Other users must not replace these directories or modify the file during a call. The server rejects symbolic links, checks the opened file identity, and reads at most 64 KiB plus one overflow byte from that descriptor; it does not isolate a hostile shared filesystem or provide an OS sandbox.

The example read_project_scripts reads scripts from the startup project's package.json. It does not execute scripts, write files, or access the network. The server's startup argument selects the project; the agent cannot supply arbitrary file paths.

ContractThis example
InputEmpty object {}; additional fields rejected
OutputJSON text containing scripts
ResourceA regular package.json in a fixed project, limited to 64 KiB
ErrorsProtocol errors for invalid arguments; isError: true for file failures
Side effectsNo file writes or command execution

Create a stdio server

Save the following as read-project-scripts.mjs outside the source project. It uses Node.js built-ins and writes only MCP messages to stdout. A deployed service also needs session management, resource limits, and access controls appropriate to its environment.

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");
  }
});

This minimal server validates one read-only tool. It handles newline-delimited JSON-RPC messages, without HTTP endpoints, batch requests, or full production service governance. It provides no operating-system sandbox: the process retains its startup user's permissions.

Add and verify in EvoX

Add a manual MCP connection under extensions, select stdio, and supply:

FieldExample
Nameproject-scripts
CommandAbsolute path to the Node.js executable
ArgumentsAbsolute server-script path and absolute project path, as two separate arguments

Save, then inspect connection and tool discovery. Send the agent:

text
Use project-scripts/read_project_scripts to read this project's npm scripts.
List script names and commands without executing any scripts.

Compare the result with the project's package.json. Confirm configuration save, process startup, discovery, and invocation separately. A listed server name does not establish a usable tool.

Check protocol and errors

This is a tool-call message. The MCP client must first complete initialize and notifications/initialized. The initialize request supplies protocolVersion, a capabilities object, and clientInfo with name and version strings. This server supports protocol 2024-11-05 and returns that version during negotiation; a client that does not support it must disconnect. Tool calls before the completed handshake are rejected, and ping remains available before initialization.

json
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"read_project_scripts","arguments":{}}}
CaseRequired result
Regular projectScripts match the file; none execute
Extra path argumentInvalid-argument error; no other file is read
Missing package.jsonRead error rather than a fabricated empty project
Oversize or symlink fileRead rejected
Server exitsConnection failure; discovery and invocation rechecked after recovery

Extend with write tools

Give writes separate names and schemas. Define resource identity, edits, preview, consent, and idempotency. Enforce scope and account permissions on the server rather than relying on model compliance. Reconcile real operation state after timeouts to prevent duplicate writes.

On upgrades, compare names, schemas, errors, and side effects, then rerun integration cases. Stopping a connection does not revoke remote account access or credentials.

上一篇编写技能下一篇Hooks