DeepSeeker-Code源码导读09-MCP集成

读懂 MCP 集成:把第三方工具接进来,边界在哪

这篇讲什么

前面八篇讲的工具------read_file、edit_file、run_command、undo------全是内置的、自己写的、可信的。它们的行为你清楚,参数校验你做了,安全闸门你层层加了。

MCP(Model Context Protocol)不一样。MCP server 是用户自己配置的第三方进程或服务 ------可能是社区开源的,可能是同事写的,跑在你机器上,能执行任意逻辑。你不知道它会读什么文件、调什么命令、吐什么回来。所以 MCP 集成的第一性问题,从来不是"怎么把它接进来",而是"怎么防它"。

这篇拆 tool/mcp/,两个文件------client.ts(客户端,零依赖手搓 JSON-RPC,三种 transport)和 loader.ts(加载器,配置→连接→工具注入)。核心是"不可信输入的边界":环境变量白名单、默认高危、行长上限、SSRF 防护。读完你会看到,"接第三方工具"这件事,安全功夫在"接"之外。

一、先看全貌:什么时候会有 MCP、它接在哪

读这篇的钥匙,是下面这张 transport 选择表------MCP server 按 transport 分三类,每类连法、生命周期、风险点都不同:

transport 配置特征 连法 生命周期 主要风险
stdio command/args spawn 本地子进程 进程级,退出即断 子进程孤儿、stderr 死锁、机密泄露
http(streamable) type:"http" 或有 url 逐请求 POST 无状态,按需 超大响应、远程服务不可控
sse(legacy) type:"sse" GET 常驻流 + POST endpoint 常驻流 endpoint SSRF、流悬挂

type 缺省时按配置推断(loader.ts :96):有 url → http,否则 stdio。三种 transport 对应 MCP 规范的三种演进------stdio 是最早的本地协议,streamable-http 是 2025-03-26 的现代规范,SSE 是 legacy(被 streamable-http 取代但仍在用)。

拿几个场景盘活"什么时候触发 MCP":

  • 场景 A:用户在 ~/.deepseeker-code/mcp.json 配了一个 filesystem server。 服务启动时(serve/index.ts)调 initMcpTools(agentTools),读配置→连 server。默认 dispatcher 档下,不管这个 server 暴露 3 个还是 30 个工具,注入工具表的永远是两个恒定 schemamcp_list_tools(查目录)+ mcp_call(分发执行)。模型先 list 拿目录,再 call 指定 server/工具。

  • 场景 B:用户没配 mcp.json,或文件不存在。 readMcpConfig 静默返回空,loadMcpTools 返回空数组------零污染,工具表一个 MCP 工具都不多。MCP 是可选的,不是必需的。

  • 场景 C:配了三个 server,其中一个连不上(命令错了/端口没开)。 那个 server 被 catch 住、dispose、打告警,另外两个照常连、照常注入。单点失败不拖垮全局。

  • 场景 D:某 server 声明了 resources 能力(能提供数据源)。 loader 检测到后,额外注入 list_mcp_resources / read_mcp_resource 两个聚合工具。没声明就不注入------能力旗标驱动注入,缺能力零负担

  • 场景 E:agent 调了一个 MCP 工具。 mcp_callsafetyLevel=DANGER + requireApproval,每次调用走第 7 篇的审批网关,弹窗详列"服务名/工具名/参数",用户确认才执行。权限规则也不松:resolveMcpPermissionNamemcp_call(server, tool) 还原成 mcp__server__tool 合成名,settings.json 里按旧粒度配的 deny/allow 规则照常生效。

理解了这张表和这些场景,再去看两个文件,client.ts 是"怎么连、怎么通信",loader.ts 是"怎么配置、怎么包装注入"。下面先看加载链路。

二、加载链路:从配置到工具注入

initMcpTools(loader.ts :412)是入口,串起完整链路:

bash 复制代码
readMcpConfig(读配置+占位符展开)
  → loadMcpTools(逐 server:createMcpClient → start → listTools → wrapTool)
    → 分档注入(appConfig.mcpExposeMode)
       dispatcher(默认):只注入 mcp_list_tools + mcp_call 两个恒定 schema
       schemas(回退):逐工具独立 schema 常驻
    → 能力旗标判断(resources/prompts 有才注入聚合工具)

配置:占位符让配置跨机器复用

loader.ts 的配置格式兼容 Claude Code,扩展了远程 transport。有个细节值得注意------占位符展开:46):

ts 复制代码
const expandPlaceholders = (s) => s
    .replace(/\$\{dataDir\}/g, appConfig.dataDir)
    .replace(/\$\{home\}/g, homedir());

mcp.json 里可以写 ${dataDir}/mcp-servers/... 而不是 C:\Users\xxx\...。这样同一份配置拷到任何机器、任何用户目录下都能用------路径不写死。这是个"配置可移植性"的小工程,但对"配一次到处用"很重要。

配置文件读不到/解析失败,一律返回空对象静默跳过------MCP 是可选能力,不该因为配置问题让整个服务起不来。

包装:命名空间化防冲突(schemas 回退档)

每个 MCP server 的工具,被 wrapTool:104)包装成一个 CustomTool。关键是命名空间化

ts 复制代码
const namespaced = `mcp__${sanitize(serverName)}__${toolName}`;

mcp__ 前缀 + server 名 + tool 名。这样两个 server 都叫 read_file 的工具不会撞名(mcp__fs__read_file vs mcp__db__read_file)。sanitize 把非 [a-zA-Z0-9_] 字符替成 _,保证 OpenAI 工具名合法。

更关键的是包装时的安全设定(:118):

ts 复制代码
safetyLevel: ToolSafetyLevel.DANGER,  // MCP server 可执行任意逻辑,默认高危
requireApproval: (args) => `⚠️【MCP 工具审批】\n服务: ${serverName} / 工具: ... \n参数: ...`,

默认 DANGER + 动态审批提示。这是"不可信第三方"的体现------你不知道这个 server 的 read_file 会不会顺手把文件传出去,所以每次调用都要人审。审批提示里带服务名和参数,让用户知道"我正在批准谁、做什么"。

这条 wrapTool 路径现在是 schemas 回退档 (env DEEP_SEEK_MCP_EXPOSE_MODE=schemas 时启用),默认走的已经不是它------原因是下一小节要讲的:它和前缀缓存有一笔算不过来的账。

分发器档:两个恒定 schema 替代 N 个(默认)

内置工具约 45 个、10--15K schema token,常驻可容忍。但 MCP 是无界轴 ------挂 3 个 server × 每个 15 个工具,就是再添 5--15K schema;而且每个工具一个独立 schema,全序列化在请求头部(第 5 篇讲过,tools 计入 DeepSeek 前缀哈希)。实测挂一个 playwright server(24 个工具)就是 24 份 schema 常驻头部。中途加 server?工具表变了,前缀缓存击穿,全历史 re-prefill。

解法是 loader.ts :298dispatcher 双工具------不管接多少 server、多少工具,工具表里永远只有这两个:

ts 复制代码
const mcpDispatcherTools: CustomTool[] = [
    {
        name: "mcp_list_tools",   // SAFE:实时查询目录(server/工具名/一行描述/参数,必填带 *)
        safetyLevel: ToolSafetyLevel.SAFE,
        async execute(args) { /* 遍历 clients,逐 server listTools,拼目录 */ },
    },
    {
        name: "mcp_call",         // DANGER:按 (server, tool) 分发执行
        safetyLevel: ToolSafetyLevel.DANGER,
        requireApproval: (args) => `⚠️【MCP 工具审批】\n服务: ${args?.server} / 工具: ${args?.tool}\n参数: ${args?.args}`,
        async execute(args) { /* findClient(server).callTool(tool, JSON.parse(args.args)) */ },
    },
];

三个设计点值得细看:

目录走 L3 尾部,绝不进 fencemcp_list_tools 的输出是工具结果 (进上下文尾部),不是注入 message0 的 fence 块------fence 在头部(L1),目录一刷新头部就变,缓存击穿。工具结果天然可变、可截断,放尾部零伤害。副作用免费送:中途新增/重连 server,工具表零变化,下一次 list 就能查到------"MCP 热加载"这个本来要做一堆机制的事,被分发器顺手解决了。

args 参数收 JSON 字符串 而非对象嵌套。mcp_call 的第三个参数是 type: "string"------模型传 "{\"query\":\"...\"}"。少一层对象嵌套,DeepSeek 的 arguments 解析更稳(嵌套 JSON in JSON 是真实风险);解析失败有专门的引导文案让模型重传。

安全语义一条不松mcp_call 直接 DANGER + requireApproval(比原计划的按配置映射更严);权限规则和分类器经 resolveMcpPermissionName:284)把 mcp_call(server=X, tool=Y) 还原成 mcp__X__Y 合成名------用户按旧粒度配的 deny/allow 规则、auto 分类器的 mcp__ 通配,全部照常生效。换成 dispatcher 没有牺牲任何一道闸门。

为什么内置的 45 个工具不上分发器?权衡相反:分发器的嵌套 JSON 参数对 DeepSeek 是解析风险,且损失"工具名即发现性"(模型看到 read_file 就知道有它,不用先 list)。45 个的体积可容忍,不值得冒这个险;MCP 是无界轴,必须收敛。按轴的性质选策略,不一刀切。

能力旗标驱动注入

MCP server 除了 tools,还可能暴露 resources(数据源)和 prompts(提示模板)。但这俩能力不是所有 server 都有。loader 的做法是按能力旗标有条件注入:428):

ts 复制代码
if (clients.some(c => c.supportsResources)) {
    into.push(...mcpResourceTools);  // 有任一 server 支持 resources 才注入 list/read
}
if (clients.some(c => c.supportsPrompts)) {
    into.push(...mcpPromptTools);
}

supportsResourcesstart() 握手时从 server 的 capabilities 旗标读的(client.ts :163),即时无网络往返。没有一个 server 声明 resources 能力,就不注入那两个聚合工具------工具表零污染,模型上下文里不会多出用不上的工具描述。这是"大模型注意力经济"在 MCP 层的体现。

而且聚合工具本身也分级:list_*(列清单)是 SAFE(只读元数据),read_*/get_*(拉内容入上下文)是 DANGER------因为 server 提供的内容会进模型上下文,是注入面(同 web_fetch)。

三、三种 transport 的统一抽象

client.ts 最值得学的工程,是把三种 transport 的公共逻辑上提到基类

McpBaseClient:transport 无关的公共部分

listTools / callTool / listResources / readResource 这些方法,三种 transport 实现完全一致(都是发 JSON-RPC 请求、拿结果),只有"请求怎么发"(request)不同。所以上提到 McpBaseClient(client.ts :153):

ts 复制代码
abstract class McpBaseClient implements McpClient {
    protected abstract request(method, params): Promise<any>;  // 子类实现 transport
    async listTools() { const r = await this.request("tools/list", {}); return r?.tools ?? []; }
    async callTool(name, args) { const r = await this.request("tools/call", {name, arguments: args}); return joinContentText(r); }
    // ... listResources / readResource / listPrompts / getPrompt 同理
}

三个子类(Stdio / Http / SSE)只需实现 start / request / dispose 三个 transport 相关方法。"变的部分抽象成抽象方法,不变的部分留在基类"------教科书级的模板方法模式,避免了三份重复的 listTools/callTool。

三种 transport 各自的"变"

  • stdio:203):spawn 子进程,stdin/stdout 换行分隔 JSON-RPC。request 往 stdin 写 JSON、pending map 按 id 路由 stdout 的响应行。
  • http:341):逐请求 POST 到 url,响应可能是 application/json(单个对象)或 text/event-stream(SSE 帧按 id 路由)。readSseResponse 解析 SSE 流取匹配 id 的帧。
  • sse:460):GET 开一条常驻流收 server→client 消息,client→server 经 server 指定的 endpoint POST。复用 stdio 的 pending-map 路由。

注意协议版本的差异(:59-60):stdio 保持旧版 2024-11-05(不破坏既有本地 server),http/sse 用新版 2025-03-26。这是兼容性优先------本地 server 跑了好几年,不为了追新协议把它们全搞挂。

四、五个设计决策

决策一:第三方不可信,默认高危 + 边界防护

这是 MCP 集成的总纲。MCP server 能执行任意逻辑,所以包装工具默认 DANGER(每次人审)、环境变量白名单透传(防机密泄露)、单行 JSON 上限(防 OOM)、SSE endpoint 同 host 校验(防 SSRF)。每一道防护都假设"server 可能是恶意的或 buggy 的"。 这和内置工具(可信,SAFE 免审)形成鲜明对比------同一个 agent,对自己写的工具放手,对接进来的第三方工具死死盯住。

决策二:零依赖手搓 JSON-RPC

client.ts 文件头第一行就标了"零依赖手搓 JSON-RPC 2.0"。不引入 @modelcontextprotocol/sdk,只用 Node 自带的 child_process + undici(fetch,本来就有)。好处是减依赖面------少一个依赖少一个供应链风险,少一份要锁的版本。代价是自己处理 JSON-RPC 分帧、SSE 解析这些体力活。对"单人本地工具"的定位,这个取舍合理:宁可自己写几百行,也不背一个大 SDK。

决策三:命名空间化防冲突

mcp__server__tool 的三段式命名。多 server 共存时,工具名绝不撞------server A 的 read_file 和 server B 的 read_file 各有各的命名空间。这还顺带让工具名自带来源信息(模型和用户一眼看出"这是哪个 server 的工具")。

决策四:能力旗标驱动注入(零污染)

resources/prompts 聚合工具,只在有 server 声明对应能力时才注入。没声明就不占工具位、不进上下文。这避免了"为了少数 server 的能力,给所有用户的模型上下文塞一堆用不上的工具描述"。 注意力是稀缺资源,工具表越干净,模型越聚焦。

决策五:无界轴用分发器收敛,有界轴不动(L1 恒定)

内置 45 个工具是有界的、可控的,逐工具 schema 常驻;MCP 是无界的(用户想挂几个 server 挂几个),必须收敛成 mcp_list_tools + mcp_call 两个恒定 schema。判据是第 3/5 篇讲的前缀缓存:工具表序列化在请求头部,头部必须全会话恒定 。分发器让"工具数量"和"头部体积"彻底解耦------挂 1 个 server 和挂 10 个,头部字节一样。目录这种动态内容全部消化在尾部(工具结果),永不回写头部。这和第 13 篇要讲的 fence 会话首锁是同一原则的两处落地:头部字节是稀缺资源,一切动态性从尾部消化。

五、六个技术难点

MCP 的难点集中在两块:鲁棒性 (进程/流的种种死法)和安全(不可信边界)。每个都是真实踩过的坑。

难点一:stderr 不消费会死锁

stdio 用三根管道(stdin/stdout/stderr)。如果只读 stdin/stdout、不读 stderr,server 往 stderr 写满约 64KB(管道缓冲区)后会写阻塞 ,进而停止读 stdin,导致所有 tools/listtools/call 卡满 30s 超时(client.ts :236 注释)。解法是持续吸收 stderr,留最近 8KB 片段供崩溃诊断:

ts 复制代码
this.proc.stderr?.on("data", (d) => {
    this.stderrBuf += d.toString();
    if (this.stderrBuf.length > 8192) this.stderrBuf = this.stderrBuf.slice(-8192);
});

这是个"三管道必须全消费"的经典坑------少读一根管道就可能死锁。

难点二:进程退出只触发 exit 不触发 error

子进程被信号杀死(或正常退出)时,Node 只触发 exit 事件,不触发 error 。如果只在 error 里 reject pending,server 崩溃后所有调用会挂满 30s 超时(:250 注释)。解法是 exit 事件里也 reject 所有 pending,并带上 stderr 尾部做诊断:

ts 复制代码
this.proc.on("exit", (code, signal) => {
    for (const p of this.pending.values()) {
        clearTimeout(p.timer);
        p.reject(new Error(`MCP server 已退出(${reason})${hint}`));  // hint 含 stderr 尾部
    }
    this.pending.clear();
});

让"server 挂了"立刻反馈,而不是等 30s 超时。

难点三:杀整个进程树(孤儿进程)

MCP server 常常自己再 spawn worker 子进程。如果只 kill 主进程,worker 会孤儿化 继续跑、占着端口/资源。所以 dispose 要杀整棵树(:322,标 S-3):

ts 复制代码
// spawn 时:非 Win 用 detached 建独立进程组
detached: process.platform !== "win32",
// dispose 时:killTree 杀整树(Win taskkill /T/F,非 Win kill 进程组 -pid)
const proc = this.proc;
if (proc) void killTree(proc);

Windows 靠 taskkill /T/F 杀树,非 Windows 靠 process.kill(-pid) 杀整个进程组(依赖 spawn 时的 detached)。跨平台的进程树清理,是个容易遗漏的点。

难点四:单行 JSON 上限防 OOM(S-2)

异常或恶意的 server 可能吐一行巨型 JSON-RPC。readline 已经把它缓冲进内存了,如果再 JSON.parse,会把巨型字符串放大成深层对象树,OOM 或 CPU 飙升。所以有硬限(:64):

ts 复制代码
const MCP_MAX_LINE_CHARS = 2 * 1024 * 1024;  // 2MB
// handleLine 里:
if (trimmed.length > MCP_MAX_LINE_CHARS) {
    console.warn(`⚠️ 丢弃超长 JSON-RPC 行...`);
    return;  // 超限丢弃,不 parse
}

2MB 覆盖合理大响应,超了直接丢。结果文本还有第二道闸(capResultText,与 MAX_TOOL_RESULT_CHARS 对齐),防 server 返回超大内容撑爆 token。两道闸:传输层防单行炸内存,内容层防结果撑爆上下文。

难点五:SSE endpoint 的 SSRF 防护

这是 SSE transport 最隐蔽的安全点。legacy SSE 协议里,server 会在 GET 流里发一个 endpoint 事件,告诉客户端"你的 POST 要发到这个 URL"。问题是------这个 URL 是 server 单方面指定的 。恶意 server 可以指定一个内网地址(如 http://192.168.x.x/admin),诱导客户端把带 Authorization 头的 JSON-RPC POST 转发过去(:519 注释)。解法是同 host 校验:

ts 复制代码
const u = new URL(payload, this.baseUrl);
if (u.host !== new URL(this.baseUrl).host) {
    console.warn(`⚠️ endpoint host 不一致,已拒绝(防 SSRF/凭据泄露)`);
    return;
}

server 指定的 endpoint 必须和 baseUrl 同 host,否则拒绝。这是个典型的 SSRF 防护------绝不让不可信方决定你的请求目标

难点六:环境变量白名单,机密不泄露给第三方

spawn 子进程时,不全量透传 process.env,而是白名单透传(:133):

ts 复制代码
const MCP_ENV_WHITELIST = ["PATH", "HOME", "USERPROFILE", ...];  // 只基础变量
env: { ...buildSafeEnv(), ...env },  // 白名单 + 配置里显式要的

为什么?因为 process.env 里有 DEEPSEEKER_CODE_TOKEN、用户的 API key 等机密。MCP server 是第三方进程,它一旦拿到这些机密就能外传 。白名单只透传 PATH/HOME 这类基础变量,机密留在宿主进程内。这和第 7 篇的 scrubCommandEnv(命令脱敏)是同一个思路的两种应用------内置命令脱敏是"防命令执行时泄露",MCP 白名单是"防子进程继承时泄露"。

六、推荐的源码阅读顺序

  1. 先读 loader.ts 全文 :它是加载链路的骨架。从 initMcpToolsloadMcpTools → 分档注入(dispatcher / schemas)顺下来,理解"配置怎么变成工具表里的 CustomTool"。
  2. 重点读 mcpDispatcherTools(:298:看两个恒定 schema 怎么替代 N 个逐工具 schema------目录走尾部、args 收 JSON 字符串、resolveMcpPermissionName 还原合成名保权限语义。这是默认档,也是本篇最有味道的一段。
  3. 再读 wrapTool(:104:看命名空间化 + 默认 DANGER + 动态审批提示。这是 schemas 回退档,也是"不可信第三方"在工具包装层的原始体现。
  4. 读 client.ts 的文件头(:1-13)和 McpClient 接口(:37:建立"三种 transport 统一抽象"的全景。
  5. 读 McpBaseClient(:153-201:看模板方法模式------listTools/callTool 上提,request 抽象。
  6. 读 McpStdioClient(:203-335 :它最复杂、坑最多(stderr 死锁、exit reject、killTree)。逐个注释看,每个 都是个真实坑。
  7. 读 McpStreamableHttpClient(:341-454:看逐请求 POST + SSE 响应路由。对比 stdio 的"常驻连接"模型。
  8. 读 McpSSEClient 的 readStream(:507-569 :重点看 endpoint 的 SSRF 校验(:519)------这是 legacy transport 特有的安全点。

七、关联:MCP 在整个系统里的位置

MCP 是工具系统的"开放边界":

  • 注入点initMcpTools(agentTools) 在服务启动时调用,把 MCP 工具 push 进和内置工具同一个 agentTools 数组。之后它们和内置工具走完全一样的执行管线(第 7 篇的 processToolCall)。
  • 安全接力 :MCP 工具的 safetyLevel=DANGER,意味着它一进 processToolCall 就会触发审批网关------MCP 的"默认高危"和第 7 篇的"审批硬闸门"是配套的。MCP 负责声明"我很危险",guard 负责执行"那每次都审"。
  • 不可信输入:和第 7 篇的 run_command 一样,MCP 是"不可信输入"的来源。run_command 的输出不可信(要 verifyResult 防乐观),MCP 的返回也不可信(要 capResultText 防撑爆、要人审防恶意)。
  • 零污染原则 :和第 1 篇的 load_skill(按需加载 skill)、本篇的能力旗标注入一脉相承------没有就用不到的东西,绝不进模型上下文

你会看到,MCP 集成的功夫大半不在"接",而在"防"------防机密泄露(环境变量白名单)、防撑爆(行长上限)、防 SSRF(endpoint 同 host)、防孤儿(杀进程树)、防死锁(消费 stderr)。把第三方接进来只需几十行 spawn/fetch,把第三方防住却要六道闸门。这就是"不可信边界"的工程重量。

最后

接第三方工具,听起来是个连通性问题------spawn 个进程、发几条 JSON-RPC、把工具名加进列表,就通了。但真正难的是:这个第三方你不了解、控制不了、甚至可能是恶意的,你怎么让它在你机器上安全地干活?

DeepSeeker-Code 的回答是,把 MCP server 当"不可信第三方"对待:默认高危每次人审、环境变量白名单防机密泄露、传输层和内容层双闸防撑爆、SSE endpoint 同 host 校验防 SSRF、杀整棵进程树防孤儿、消费 stderr 防死锁。这些防护没有一个是"接工具"必需的,但每一个都是"安全地接工具"必需的。

读这段源码,最值得带走的是这个视角:集成第三方能力,第一性问题是边界,不是连通。 连通只是 spawn 和 fetch,边界才是那六道闸门。内置工具可信所以放手,第三方工具不可信所以死盯------同一个 agent,两种信任等级,全靠安全字段和执行管线分层落地。

下一篇,我们换到另一个方向------读 TypeScript 代码智能 tsHost,看 agent 怎么用 in-process 的 LanguageService 做代码导航和诊断,那是一类"重型依赖怎么按需启用"的工程。

项目源码开源在 github.com/xnk/deepSee... ,文章里提到的文件都在 src/core/src/tool/mcp/ 下,欢迎对着源码读。觉得这个导读系列有点意思,点个 star 是对我最大的鼓励。

总结

  1. 第一性问题不是"怎么接"是"怎么防" :MCP server 是用户自配的第三方进程/服务,能执行任意逻辑,所以包装工具默认 safetyLevel=DANGER(每次人审),环境变量白名单透传(防 DEEPSEEKER_CODE_TOKEN/API key 泄露);
  2. 三种 transport 统一抽象:stdio(spawn 子进程)/ streamable-http(逐请求 POST)/ SSE(GET 常驻流 + POST endpoint),公共的 listTools/callTool 上提 McpBaseClient,子类只实现 transport 相关的 request/dispose(模板方法模式);
  3. 加载链路 :readMcpConfig(占位符 dataDir/ {dataDir}/ dataDir/{home} 跨机器复用)→ createMcpClient → start 握手 → listTools → 分档注入------dispatcher 档(默认)只注入 mcp_list_tools + mcp_call 两个恒定 schema(工具表不随 server 工具数增长,目录走 L3 尾部、中途加 server 零变化),schemas 档(回退)wrapTool 逐工具注入(命名空间 mcp__server__tool 防冲突);权限经 resolveMcpPermissionName 合成名走既有规则;单 server 失败不影响其他;能力旗标(resources/prompts)驱动聚合工具注入,缺能力零污染;
  4. 五个设计决策:第三方不可信(默认高危 + 边界防护)、零依赖手搓 JSON-RPC(减供应链风险)、命名空间化防冲突、能力旗标驱动注入(注意力经济)、无界轴用分发器收敛有界轴不动(头部字节恒定);
  5. 六个技术难点:stderr 不消费会死锁(三管道全消费)、进程退出只触发 exit 不触发 error(必须靠 exit reject pending)、杀整个进程树防孤儿(Win taskkill / 非 Win 进程组)、单行 JSON 2MB 上限防 OOM(S-2)、SSE endpoint 同 host 校验防 SSRF、环境变量白名单防机密泄露。
相关推荐
MindUp1 小时前
自然语言处理驱动的PPT自动生成:4款工具的技术实现与实测对比
人工智能·自然语言处理·powerpoint
今天AI了吗1 小时前
AI 数据安全治理框架:模型能力与数据权限的边界在哪里
java·linux·开发语言·人工智能·python·深度学习·机器学习
Canace1 小时前
Fable 像素游戏复盘,Vibe Coding 的 10 条工程规则与赛车 Demo 实践
前端·人工智能·游戏开发
程序员柒叔1 小时前
luna 的内心独白:我把一个本该暂停的任务,跑成了几十轮空转
agent·ai编程·vibecoding
OpenPie|拓数派1 小时前
拓数派入选杭州国际数据标注联盟副理事长单位,夯实πDataCS本体能力
大数据·人工智能·openpie·拓数派·piedatacs
林伽一1 小时前
林伽一 · AI科技周报 | 2026年08月第3周
人工智能·科技
beiju1 小时前
把 Prompt 当字符串,是 AI 创作系统最早的技术债
人工智能
半个落月1 小时前
用 Harness 工程化缓解 LLM 幻觉:Best of N Sampling + LLM as Judge 实战
javascript·人工智能
neocheng_5221 小时前
2026 AI 认证选型指南:区分平台、技术、通用 AI 应用三大能力
人工智能