让聊天机器人学会用工具,星悟接 MCP 的实践

MCP 已经是 chatbot 的必需能力,各家 AI 客户端基本都接了。我们项目组也要把这块补上,作为组里平时写对话这块的人,这活顺理成章落到了我头上。

接之前,星悟能拿外部信息的通道只有两个,应用侧的 search_webweb_fetch,再加上一部分模型自带的联网搜索。这两条通道对付一般问题够用,内部知识就没办法了。团队的文档、代码库、某个服务的 wiki,搜索引擎收录不到,机器人有联网能力也查不到。我们想要的形态是,用户把知识源配进星悟,对话里说一句"帮我查一下 XX 模块的接口文档",机器人自己去翻。

接法上有两条路,厂商原生 MCP 和应用侧执行。厂商原生 MCP 我们看过,模型厂商代调工具,会在对话里留下厂商私有的工具记录。星悟通过 OpenAI 网关接了多个模型,消息历史在 prepareMessageHistory 里只保留纯文本,换一个模型,这种私有记录就回放不了。看着省事,代价是把工具的解释权交出去,我们接受不了。所以定了走应用侧执行,自己负责 tools/listtools/call,模型只看到普通的 function calling,MCP 返回的东西必须是普通 JSON 或文本。

几条边界也在这时候定下来。对话主流程不动,MCP 以工具形态并入现有的 streamText。会话消息仍无状态,服务端不按会话存 MCP 工具状态。mcp.json 做成双写,浏览器 localStorage 负责编辑器即时读写,用户维度的配置表再存一份权威副本,换设备、清缓存不会丢。首版只做读,不做 OAuth,不做写操作自动执行。

为什么选 @ai-sdk/mcp

路线定下来以后,选型反而没什么好纠结的。AI SDK 官方有 @ai-sdk/mcpcreateMCPClient 能把任意 MCP Server 暴露的工具直接转成 AI SDK 的 ToolSet,并进现有 streamText。不用自研协议层,接入成本压到最低。

传输上以 Streamable HTTP 为主,配置里 type 缺省或者写成 sse,就走 SSE。stdio 这条路径留到安全那节再讲,你先记住,它不进用户可配置的生产路径。

两条路放在一起看。

维度 应用侧执行 厂商原生 MCP
谁调工具 应用自己 tools/listtools/call 模型厂商代调
模型看到什么 普通 function calling 厂商私有的工具记录
换模型、跨网关 可迁移,回放不受影响 回放不了,绑死厂商
可控性 完全可控 受厂商实现限制

表里第一行决定了很多事。谁调工具这件事定死以后,工具的执行和解释权留在应用侧,后面要讲的预热和渐进式注入才有空间。

配置双写,连接在服务端

只放 localStorage 编辑器很爽,产品上撑不住。清站点数据、换浏览器、换电脑,配置就没了。Token 跟着丢,用户会以为星悟把知识源弄丢了。我们做成双写。浏览器 localStorage(Zustand persist,key 是 xingwu-mcp-config)负责编辑器即时读写。用户维度的配置表再存一份权威副本,按登录账号隔离。打开页面时以数据库为准回填 localStorage。保存时两边一起写。

这和「全员共用一份服务端 config/mcp.json」不是一回事。仓库里不放真实 Token,也不新增全局 config/mcp.json。库里是当前用户自己的 mcp.jsonheaders 加密落库,访问日志仍然打码。对话热路径不必每轮先打数据库,请求里仍带上已选 Server 的 urlheaders,服务端只在这次请求里建连。数据库解决的是持久化和换端,进程内池解决的是同进程复用。

配置格式和常见 MCP Host 的 mcpServers 对象一致,Cursor 和 Claude Desktop 都用这套,用户零学习成本。字段不算多。

字段 含义
mcpServers.<id> Server 标识,同时是工具名前缀
type httpsse,缺省且只有 url 时按 http
url 远程 MCP 端点
description 给人看的能力摘要,也会进系统提示
headers 原样带到 MCP 请求,Token、知识库 ID 都放这里
disabled true 时不默认勾选,不参与预热

下面是一个示例配置,跟 Cursor 里写得差不多。

json 复制代码
{
  "mcpServers": {
    "context7": {
      "type": "http",
      "url": "https://mcp.context7.com/mcp",
      "description": "Context7,给 AI 提供最新的开源库文档"
    },
    "docs": {
      "url": "https://your-mcp.example.com/mcp",
      "description": "内部知识库搜索",
      "headers": {
        "Authorization": "Bearer <token>"
      },
      "disabled": true
    }
  }
}

这里的规矩先说清楚。Token 不进仓库,不进服务端环境变量,不进全局 config/mcp.json。鉴权字段写在用户自己 mcp.jsonheaders 里,浏览器存一份,数据库按用户加密再存一份。仓库里可以放不带密钥的格式说明,真实 Token 禁止提交。

服务端把建好的 MCP Client 缓存在进程内存的池里,方便预热和同一进程内复用。池的 key 用连接指纹,urlheaders 摘要出来的,避免把 Token 明文当 map key 存一份。原始 Token 不写日志,落库只走加密字段。

整体数据流是下面这张图。

flowchart TB editor[&#34;用户编辑 mcp.json&#34;] --> ls[&#34;localStorage&#34;] ls --> ui[&#34;勾选 Server&#34;] ls --> warmup[&#34;页面加载或保存后预热&#34;] ui --> probe[&#34;勾选探测&#34;] warmup --> pool[&#34;进程内 MCP 池&#34;] probe --> pool ui --> chatApi[&#34;chat 接口带上已选 Server 的 url 与 headers&#34;] pool --> catalog[&#34;Server 摘要与工具目录&#34;] catalog --> sysPrompt[&#34;系统提示里的能力摘要&#34;] chatApi --> meta[&#34;元工具 mcp_inspect_tools&#34;] chatApi --> streamText[&#34;streamText&#34;] sysPrompt --> streamText meta --> streamText streamText --> prepareStep[&#34;prepareStep 按已激活名单收紧 activeTools&#34;]

多 worker 部署时有个坑要兜住。预热可能打在 A 进程,对话打在 B 进程。我们的处理是,聊天请求必须再次带上已选 Server 的连接信息,B 进程按请求里的 urlheaders 即时建连,写进自己的池,不阻塞对话。预热是提效,不是必需,即时建连是保底。

预热和渐进式工具注入

渐进式注入算法

工具不能一次全给,这是我们要先解决的一个问题。

用户配了五个 Server,每个暴露十个工具,五十个 schema 全塞进 function calling,上下文先吃掉一大截。工具一多,模型还容易在错的时机选中错的工具。MCP Client 干的事是翻译,把 Server 的工具翻成模型看得懂的东西,翻译也得看场合,五十份材料一次全译出来,模型反而看不过来。我们的做法是预热建连,按需注入,模型点到哪个翻哪个。

先看看要翻的材料长什么样。一个 MCP tool 的核心字段不多,按披露成本可以分成三层。

层级 字段 作用 示例
L1 核心命名(高) nametitle 工具的身份标识,name 是模型调用时的句柄 APICallTool、API 调用工具
L2 功能描述(中) description 一句话讲清工具干什么 调用指定接口并传递参数,返回标准化响应数据
L3 输入输出模式(低) inputSchemaOutputSchema 输入参数与返回结构的完整定义 输入 url、method、headers、body,输出 data、code、message、latency

name 是模型调用工具时用的句柄,title 给人看。description 决定模型在什么场景想起这个工具。inputSchema 最重,列出每个参数的名称、类型、是否必填,OutputSchema 描述返回结构。三层的信息量差着量级,L1 一个名字就能说清,L3 是一份完整定义,渐进式披露就是按这个成本梯度一层层放。

预热时机

预热的目的是把第一句话的延迟省掉。用户打开星悟或者保存配置之后,已启用的 Server 尽快连上,工具目录备好,等发第一句话时,就不用再付 initializetools/list 的开销。

三个时机触发。打开页面时先按登录账号把数据库里的 mcp.json 回填到 localStorage,MCP 默认开启则对没 disabled 的 Server 调预热接口。用户保存 mcp.json 后双写,并对当前启用列表再预热一遍。勾选某一台时单独探测。

预热接口很薄,解析请求体后丢给连接池。packages/chatbot/app/api/mcp/warmup/route.ts 就是这几行。

ts 复制代码
export async function POST(request: Request) {
  const body = (await request.json()) as { mcpServers?: unknown };
  const servers = parseSelectedMcpServers(body.mcpServers);
  const snapshots = await warmupMcpServers(servers);
  return Response.json({ servers: snapshots });
}

真正干活的是 lib/mcp/pool.ts。指纹用 urlheaders 的 SHA-256,Token 不当明文 key。已就绪且心跳窗口内直接复用,否则 createMCPClient 建连,再 listTools 填目录。

ts 复制代码
export function fingerprintMcpConnection(
  url: string,
  headers: Record<string, string>,
): string {
  const normalizedHeaders = Object.keys(headers)
    .sort((left, right) => left.localeCompare(right))
    .map((key) => [key.toLowerCase(), headers[key]] as const);
  return createHash("sha256")
    .update(JSON.stringify({ headers: normalizedHeaders, url }))
    .digest("hex");
}

async function openMcpServer(server: ParsedMcpServer, fingerprint: string) {
  const client = await createMCPClient({
    clientName: "xingwu-chatbot",
    initializationOptions: { timeout: MCP_CONNECT_TIMEOUT_MS },
    maxRetries: 2,
    transport: {
      headers: server.headers,
      type: server.transport,
      url: server.url,
    },
  });
  const listed = await client.listTools();
  const toolSet = (await client.tools()) as ToolSet;
  // catalog.tools 只留 name / description / inputSchema,快照返回前端时不含 headers
}

心跳 60 秒,TTL 15 分钟。用户删掉或改掉某台 Server 后,前端不再带它,池条目靠 TTL 淘汰。没有做跨用户精确回收。

勾选探测走 POST /api/mcp/probe,body 是 { id, server }server 里是该台的 urltypeheaders。成功才保持勾选,失败提示"某知识源不可用"并取消勾选。已经 ready 且心跳没过期的,直接算成功。

MCP Tool 调用详细实现

工具的加载分三层,每一层被不同的角色看到。

披露层 对应字段层 谁看见 内容 何时出现
目录摘要 L1 + L2 系统提示 name、title、description,工具名和一句话简介 预热或探测成功后
详细 schema L3 本轮及后续 step 的 function calling inputSchema、OutputSchema 完整定义 mcp_inspect_tools 成功之后
工具结果 L3 输出值 消息里的 tool 记录 tools/call 返回的真实数据 模型真正调用业务工具之后

理解方式可以往招聘上靠。系统提示里先放目录摘要,等于递一份候选人名单,只有名字和一句话简介,这是 L1 和 L2,成本最低。模型点名要看某位的完整资料,mcp_inspect_tools 才把 L3 的完整 schema 递过去。资料看完、人也见了,最后才真正调用工具干活,拿回 L3 输出字段的真实值。

三层靠元工具 mcp_inspect_tools 串起来。入参是一串带前缀的名字,格式 serverId_originalName,比如 docs_search。它只允许加载当前请求里已选且 ready 的 Server。超过 16 个就拒绝。激活是替换语义,不是累加,缩小名单再 inspect 一次就能换一批。它只把参数说明带回给模型,不在这一步代调业务接口。元工具不占 16 的业务槽位。search_webweb_fetch 仍由搜索开关控制,也不占。

lib/mcp/tools.ts 里,会话创建时会把已选 Server 的全部 execute 挂进 tools,但 getActiveTools() 一开始只返回 inspect。inspectMcpTools 成功后,下一轮 prepareStep 才能看见业务工具名。

ts 复制代码
export function createMcpToolSession(
  entries: McpPoolEntry[],
  initialActivated: string[] = [],
): McpToolSession {
  const catalogByPrefixed = new Map<string, { description: string; inputSchema: unknown; serverId: string }>();
  const tools: ToolSet = {};
  // 把已选 Server 的 catalog 和 execute 全部挂进 tools,前缀为 serverId_originalName
  const allowed = new Set(catalogByPrefixed.keys());
  const activated = new Set(
    initialActivated.filter((name) => allowed.has(name)).slice(0, MAX_MCP_BUSINESS_TOOLS),
  );

  tools[MCP_INSPECT_TOOL_NAME] = tool({
    description:
      "查看并激活 MCP 工具的完整参数说明。传入带知识源前缀的工具名;激活后下一轮才能调用这些工具。一次最多 16 个 MCP 业务工具。",
    execute: async ({ names }) => inspectMcpTools(names, catalogByPrefixed, allowed, activated),
    inputSchema: jsonSchema<{ names: string[] }>({
      additionalProperties: false,
      properties: {
        names: {
          description: "要加载的 MCP 工具名,例如 iwiki_search_document",
          items: { type: "string" },
          type: "array",
        },
      },
      required: ["names"],
      type: "object",
    }),
  });

  return {
    getActiveTools: () => [MCP_INSPECT_TOOL_NAME, ...activated],
    tools,
    catalogSummary: formatMcpCatalogSummary(entries),
    inspectToolName: MCP_INSPECT_TOOL_NAME,
  };
}

function inspectMcpTools(
  names: unknown,
  catalogByPrefixed: Map<string, { description: string; inputSchema: unknown; serverId: string }>,
  allowed: Set<string>,
  activated: Set<string>,
) {
  const requested = Array.isArray(names)
    ? names.filter((name): name is string => typeof name === "string" && name.trim() !== "")
    : [];
  const unique = [...new Set(requested.map((name) => name.trim()))];
  const valid = unique.filter((name) => allowed.has(name));
  if (valid.length > MAX_MCP_BUSINESS_TOOLS) {
    return {
      error: `一次最多激活 ${MAX_MCP_BUSINESS_TOOLS} 个 MCP 工具,请缩小名单后重试。`,
      remaining: Math.max(0, MAX_MCP_BUSINESS_TOOLS - activated.size),
    };
  }
  activated.clear();
  for (const name of valid) activated.add(name);
  return {
    activated: [...activated],
    remaining: MAX_MCP_BUSINESS_TOOLS - activated.size,
    tools: valid.map((name) => ({
      name,
      description: catalogByPrefixed.get(name)?.description ?? "",
      inputSchema: catalogByPrefixed.get(name)?.inputSchema ?? { type: "object" },
    })),
  };
}

对话入口在 app/api/chat/route.ts。有 MCP 时 stopWhenisStepCount(10)prepareStep 每一步问会话要当前 activeToolstools 里挂了元工具加全部 execute,发给模型的定义仍靠 activeTools 裁。

ts 复制代码
const mcpSession =
  mcpEntries.length > 0
    ? createMcpToolSession(mcpEntries, parseActivatedMcpTools(body.activatedMcpTools))
    : undefined;

const result = streamText({
  messages: await convertToModelMessages(history, hasTools ? { tools } : {}),
  model: createOpenAILanguageModel(modelId, protocol),
  prepareStep: prepareChatToolStep({
    mcpActiveTools: mcpSession ? () => mcpSession.getActiveTools() : undefined,
    systemPrompt: system,
    // ...
  }),
  stopWhen: isStepCount(mcpSession ? 10 : 4),
  tools,
  system,
});

星悟走 OpenAI 兼容网关的 Responses API,createOpenAI().responses(modelId),并且 store: false。渐进式注入落到网关上,就是每一步 POST /v1/responsestools 数组不一样。step 0 模型只能看见 inspect,业务工具的完整 schema 还没进请求。

json 复制代码
{
  "model": "gpt-5.5",
  "store": false,
  "instructions": "已启用知识源 MCP。当前可用知识源:\n## docs(内部知识库搜索)\n- docs_search:按关键词检索文档\n先调用 mcp_inspect_tools,再在下一轮调用这些工具。一次最多激活 16 个 MCP 业务工具。",
  "input": [
    {
      "role": "user",
      "content": "帮我查一下 XX 模块的接口文档"
    }
  ],
  "tools": [
    {
      "type": "function",
      "name": "mcp_inspect_tools",
      "description": "查看并激活 MCP 工具的完整参数说明。传入带知识源前缀的工具名;激活后下一轮才能调用这些工具。一次最多 16 个 MCP 业务工具。",
      "parameters": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "names": {
            "type": "array",
            "items": { "type": "string" },
            "description": "要加载的 MCP 工具名,例如 iwiki_search_document"
          }
        },
        "required": ["names"]
      }
    }
  ],
  "tool_choice": "auto"
}

模型在这一步只会调 mcp_inspect_tools,比如传入 ["docs_search"]。inspect 的 tool result 把完整 inputSchema 带回给下一跳。step 1 的 tools 才出现业务工具。store 为 false,上一跳的 function call 和 output 要写进 input 里一起回放,不能靠 previous_response_id 去服务端取。

json 复制代码
{
  "model": "gpt-5.5",
  "store": false,
  "instructions": "...同上,工具返回后追加 afterTools 说明...",
  "input": [
    { "role": "user", "content": "帮我查一下 XX 模块的接口文档" },
    {
      "type": "function_call",
      "call_id": "call_inspect_1",
      "name": "mcp_inspect_tools",
      "arguments": "{\"names\":[\"docs_search\"]}"
    },
    {
      "type": "function_call_output",
      "call_id": "call_inspect_1",
      "output": "{\"activated\":[\"docs_search\"],\"remaining\":15,\"tools\":[{\"name\":\"docs_search\",\"description\":\"按关键词检索文档\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"query\":{\"type\":\"string\"}},\"required\":[\"query\"]}}]}"
    }
  ],
  "tools": [
    {
      "type": "function",
      "name": "mcp_inspect_tools",
      "description": "查看并激活 MCP 工具的完整参数说明。...",
      "parameters": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "names": {
            "type": "array",
            "items": { "type": "string" }
          }
        },
        "required": ["names"]
      }
    },
    {
      "type": "function",
      "name": "docs_search",
      "description": "按关键词检索文档",
      "parameters": {
        "type": "object",
        "properties": {
          "query": { "type": "string", "description": "检索关键词" }
        },
        "required": ["query"]
      }
    }
  ],
  "tool_choice": "auto"
}

对比两份请求就能看清渐进式注入在干什么。tools 里挂的 execute 早就在 Node 里备好了,发给模型的定义靠 activeTools 裁。step 0 的 tools 只有 inspect,step 1 才把 docs_search 的完整 parameters 塞进 Responses API。上下文按需涨,而不是开场就把五十个 schema 铺上去。

跨轮次时,前端把 activatedMcpTools 和已选 Server 配置一起 POST。取消勾选,或者从 mcp.json 里删掉某台,就丢掉对应前缀。服务端不按会话存 MCP 状态,状态都随请求带来。

安全,几层防线和一条红线

前面讲的是怎么把功能做出来,这一节讲怎么让它安全落地。MCP 接入最怕的两件事,一件是连上一个不该连的东西,一件是执行了一段不该执行的代码。功能做错了最多不好用,安全做错了是机器被黑,这两件事的优先级不是一个量级。

第一层是密钥按用户加密保管。Token 写进用户 mcp.jsonheaders,浏览器 localStorage 存一份,用户配置表加密再存一份。不做全员共用的 config/mcp.json,也不把别人的 Token 下发给当前用户。访问日志和错误信息对 headers 与 Authorization 打码。连接指纹用稳定哈希,Token 不当明文 key。

Token 放在 localStorage 意味着编辑器这一侧仍受 XSS 影响,这是双写里客户端缓存自带的代价,我们用 CSP 和现有前端安全基线去对冲。数据库这一侧要防的是库泄露,所以 headers 必须加密落库,不能明文 JSON 一把塞进去。

第二层是 SSRF。用户可填任意 url,理论上存在 SSRF 风险。首版面向司内同事,实施时至少拒绝非 http(s) 协议,设连接超时。内网网段要不要加允许列表,交给部署方另定。

第三层是红线,也是全文最重要的一条。禁止根据用户 mcp.json 里的 commandargs 在服务端 spawn。这是远程代码执行,和 SSRF 不是一个量级。这段如果只记一句,记这一条。

用户配置里就算出现 commandargs,我们也一律不执行,界面明确提示不能用 mcp.json 拉起本地进程。stdio 只允许走下面的方案 B,而且默认关闭。

方案 B

方案 B 是服务端白名单 stdio,给自托管场景准备的。如果必须接一个只能走 stdio 的 MCP Server,把"可执行什么"从用户配置里拿掉,改成运营方登记。白名单只放在服务端,每条记录固定 command 绝对路径、args 模板、工作目录、允许的环境变量名、超时、并发上限。用户请求里只出现 id,不出现 commandargs、可执行路径。解析时遇到用户侧的 command 直接校验失败。spawn 的参数只从白名单拼,用户输入不拼进可执行位,绝不用 shell: true。进程进池,心跳、TTL、对话结束或者超时后 kill 掉,避免僵尸进程。

这个方案只在自托管、长驻 Node、worker 数量可控的环境打开,默认关闭,要显式环境开关。Serverless 不开。

方案 B 一旦做错就是服务端远程代码执行。我们把风险列成了一张表,每条都写了"若要做必须守住"。

风险 说明 若要做必须守住
任意命令执行 用户或 XSS 改掉的 localStoragecommand 设成 bash、curl,经 /api/chat 在 Node 里拉起 永不信任请求体里的 commandargs,只 spawn 白名单绝对路径
白名单被掏空 条目写成 npx、uvx、bash -c,或 args 允许拼接任意包名 禁止包管理器入口当 command,禁止 shell: trueargs 只能是常量或枚举过的占位符
参数注入 把用户字符串拼进 argv 或 env,变成 --eval、-c、NODE_OPTIONS 允许覆盖的参数单独白名单,拒绝 NODE_OPTIONS、LD_PRELOAD、PATH 由用户改
密钥进子进程 子进程继承整个 process.env,MCP 工具或恶意包读到网关 Key、数据库 URL env 显式白名单,默认空或最小集,不要 env: process.env
文件系统与内网 本地 MCP 往往能读盘、打内网,模型一调用等于登录这台机器的只读代理 首版只接只读工具,cwd 限制在固定目录,写操作必须 needsApproval
进程生命周期 请求结束子进程还在,异常没 kill,并发把机器打满 超时、并发上限、池淘汰时 kill,监听子进程退出,worker 退出时拆掉全部 stdio Client
Serverless 或多 worker 函数实例短命,stdio 连不上长会话,预热和对话各拉起一套进程 方案 B 不在 Serverless 开,多 worker 接受每进程一份子进程,或改 sidecar
和用户自管配置冲突 产品叙事是用户自己写 mcp.json,方案 B 却不能让用户写 command 文档、UI、校验三条线一致,stdio 是运营方登记
依赖投毒 白名单指向的 node dist/index.js 若来自未锁版本的 npm 包,更新即换代码 固定绝对路径与版本,变更白名单走发布评审

还有三个明确不做的变体。开发机信任 localStorage 里的 command,开发配置被拷到生产,或者同事误开开关,就是 RCE。前端把 command 提交上来,服务端再检查一下是不是 npx,命令名黑名单防不住路径绕过。把白名单做成用户可编辑的服务端 mcp.json 下载,图省事,把安全模型拆了。

能接受这些约束再实现方案 B,接受不了就用 sidecar,把 stdio Server 包成 HTTP 或 SSE,星悟继续只连远程端点。我们现在的选择就是后者。

踩坑与边界

复盘下来有几处取舍值得记。

无状态和缓存池放在一起,分工要分清。状态随请求走是原则,进程内池只是性能优化。池条目回收不用做精确,TTL 淘汰就够,用一点内存换实现简单。

多 worker 的建连兜底前面提过。预热是提效,不是必需,即时建连是保底。就算预热全失效,对话也能正常完成,只是慢一点。

会话压缩的时候,旧的 MCP 工具结果会被 prune,近窗内可以留短结果。这和"MCP 结果必须是普通 JSON 或文本"的约束是配套的,工具轨迹随时可能被裁剪,关键状态不能放在工具结果里,要靠下一次调用来重取。

系统提示里的 MCP 块由服务端根据本次已选 Server 的 description 和预热目录拼接,使用说明写在 prompt.json。必须 inspect 再调用。headers 和 Token 禁止写进提示词,禁止出现在模型可见的工具结果里。

有些边界我们明确不碰,写下来比做了再后悔强。全员共用一份带密钥的服务端 mcp.json,或者把别人的 Token 下发给当前用户,不做。按用户 mcp.jsoncommandargs、npx 在服务端拉起进程,不做,这是红线。Serverless 或多租户公网部署上开方案 B,不做。写操作自动执行,不做,以后要做必须走 needsApproval。厂商原生 MCP 当默认,不做,多模型下回放问题无解。把 MCP 目录当向量库,或者每轮自动 compact,也不做,目录是工具清单,不是知识库。

未来的方向也清楚。写操作加审批流程。真要接本地进程,优先 sidecar,运营方把 stdio Server 包成 HTTP 或 SSE,星悟只连远程端点,命令执行能力不放开给用户。

小结

最后说一句给正在接 MCP 的人。

工具给谁调、命令听谁的,这两件事想明白,剩下的都是体力活。协议是开源的,边界是自己的。

参考

相关推荐
clorinda1 小时前
OpenCV 学习实践:从人脸检测到人脸识别
人工智能·opencv·学习
用户921080262861 小时前
从单线程到事件循环:彻底理解 JS 的同步、异步、微任务和宏任务
前端
财复视界1 小时前
光智科技磷化铟第二曲线:从红外感知到光通信链接
人工智能
beiju1 小时前
为什么4个并行Agent,不该一起改同一个文件
人工智能
dh2711987791 小时前
AI幻觉与信任赤字:南京GEO服务商如何帮企业重建AI时代的品牌信用
大数据·人工智能
不一样的少年_1 小时前
明明做了很多事,为什么简历看起来还是没含金量?
前端·后端·招聘
mashang1234567891 小时前
mac安装SnailGitLite并配置Beyond Compare
前端·macos
雪芽蓝域zzs1 小时前
第三十四节:用户新增编辑弹窗增加 el‑tree‑select 部门选择器
前端·javascript·vue.js
YOLO数据集集合1 小时前
遥感影像树木检测数据集 | 遥感树木 目标检测 城市绿化 森林监测 YOLO格式9052期
人工智能·yolo·目标检测·计算机视觉·目标跟踪·树木检测·遥感树影