☰
Function Calling、MCP与Skill:三者关系解析与Java实战落地
2026/11/2 13:04:34 网站建设 项目流程

最近一段时间,我经常在技术群和面试复盘里被问到同一个问题:"Function Calling、MCP、Skill,这三者到底啥关系?" 问的人有后端同事,也有正在准备 Java 岗位的朋友。大家普遍把它们当成"三选一"来看,好像选了一个,另外两个就不用管了;甚至有人直接说 "MCP 不就是 Function Calling 换个名字吗?"

有一次我在电话里给人解释,解释到一半自己都有点绕。因为这三个词压根不在同一个抽象平面上:Function Calling 是模型的一种原生能力,MCP 是工具接入的协议标准,Skill 则是把知识和流程打包成可复用技能的封装方式。它们不是竞争关系,而是协作关系。

这篇文章我会先用一个三层框架把关系彻底讲清楚,然后给出一套 Java 落地全流程:用 Spring AI 的 @Tool 实现 Function Calling,用官方 MCP SDK 搭建 MCP Server / Client,再用自研的 SkillRegistry 把"订单售后顾问"这种能力打包复用,最后跑通一个真实场景。适合两类人看:一类是在 Java 业务里接入 AI Agent 的开发者,另一类是面试前想把概念边界理清的朋友。

1. 三者不在一个平面上:先记住"能力、协议、封装"六个字

1.1 一张表看穿三者的位置

我自己的习惯是先给一张对照表,把概念钉在对应的抽象层级上,后面再怎么聊都不会乱:

概念抽象层级解决什么问题类比
Function Calling模型能力层模型如何在回复中声明"我要调用某个函数,参数是什么"员工会说"我要用计算器"
MCP协议/标准层工具如何被标准化地发现、调用、返回结果插座与插头的统一标准
Skill应用封装层如何把知识、多步流程、工具依赖打包成可复用技能员工的作业指导书

拆开解释:

  • Function Calling 回答的是"模型能不能调用"的问题。它是一种模型侧的行为:模型在生成文本之外,还能输出一段结构化 JSON,表示"我决定调用 findOrder,参数是 orderId=xxx"。至于这个 findOrder 在哪里、怎么执行,模型完全不管。
  • MCP 回答的是"工具怎么插进来"的问题。它是一套协议,定义了 AI 应用(Client)如何与工具服务(Server)建立连接、列出工具、发起调用、接收结果。它的生态意义在于:任何工具实现方只要实现一次 MCP Server,所有支持 MCP Client 的应用就都能用。
  • Skill 回答的是"遇到某类任务该怎么干活"的问题。它把触发条件、处理步骤、话术规则、依赖的工具清单打包成一个技能包。模型和工具都有了之后,没有 Skill 的 Agent 只能"随机应变";有了 Skill,Agent 面对特定任务时才能"按套路输出"。

1.2 用同一个 Agent 场景把三层串起来

纸上谈兵容易,我拿一个实际场景"订单售后顾问"来说明:

用户说"我的订单 OD-20240315 还没发货,帮我查查,能不能催一下"。

系统收到这句话后,大概会发生三件事:

  1. Skill 层先判断:这个请求命中了"订单售后顾问"技能,于是把售后流程的 system prompt 注入会话,同时把工具白名单收紧为"订单查询、物流查询、售后登记"这几个。
  2. 模型收到用户消息后,认为需要先查订单状态,于是触发 Function Calling,输出一个 tool_call:函数名 query_order_status,参数 orderId=OD-20240315。
  3. 执行器发现 query_order_status 不是本地方法,而是通过 MCP 协议暴露的远端工具,于是充当 MCP Client,把这次的 tools/call 请求转给远端 MCP Server,Server 再背后的订单系统里查出结果,返还给模型。

整个过程里:Skill 管"按什么流程做",Function Calling 管"什么时候决定调工具",MCP 管"工具请求怎么标准化地传出去"。三者同时在场,缺一个,Agent 都会变成跛脚鸭。

2. Function Calling:模型如何"假装"调用你的代码

2.1 机制:模型只是在输出一行结构化 JSON

早期大家是怎么让模型"调用工具"的?很原始:在 prompt 里写"如果用户想查天气,请输出 WEATHER_QUERY:beijing",然后后端解析字符串。这种 hack 在 demo 里能跑,但模型经常输出偏差,你也不知道该相信哪种格式。

Function Calling 把这件事做成了 API 层面的标准行为。在支持 tool calling 的模型接口里,你传入一个 tools 数组,每个工具用 JSON Schema 描述名字、说明和参数结构。模型生成回复时,会二选一:要么直接回复文本,要么输出一个 tool_call。这个 tool_call 本身也是结构化 JSON,包含函数名和参数。

这里有一个最基础也最容易被忽略的点:模型不会真的执行你的代码。它不会帮你查数据库,不会真的调用订单服务,甚至不会校验参数是否存在。模型只是"宣告"了调用意图。真正拿着 tool_call 去执行函数的是你的宿主程序。执行完,宿主程序把结果作为 toolResult 塞回会话,模型再基于结果继续生成第二段回答。

所以 Function Calling 的本质,是把"模型需要外部数据才能完成回答"这件事,用稳定的结构表达了出来。它是模型能力的一次升级,而不是后端框架的某个 API。

2.2 为什么工具描述(description)比参数本身更重要

模型选择工具靠的不是 Java 反射,也不是方法签名,而是语义理解。模型对你每个工具的感知全部来自 tools 数组里的这份"说明书"。因此:

  • 函数名:要像变量名一样自解释。queryOrderStatus 是好的命名,f0x121_doJob 是灾难。
  • description:必须写清三件事——触发条件、禁止条件、参数约束。
  • 参数 schema:每个字段的类型、必填性、枚举取值要明确。

我举一个真实翻车案例:我之前给一个工具写过 description = "查询订单",结果模型把"查用户积分"的请求也调它。后来改成"仅当用户提供完整订单号如 OD-20240315 时调用;没有订单号不得调用;订单状态包括待支付/已支付/已发货/已取消",模型表现立刻正常。

社区里常有人把这类问题归结为"模型太笨",其实根因往往是工具描述给的信息量不够。你可以把模型理解成一个非常聪明、但对你的代码零知识的新同事——你把说明书写成一句话,他就只能按一句话执行。

2.3 这东西的边界在哪

Function Calling 解决的问题非常具体,所以边界也很清楚:

第一,它不解决"工具怎么注册、怎么运维"的问题。你的工具是本地方法、是 HTTP 接口、还是别人家的服务,Function Calling 一概不管。

第二,不同模型厂商的 tool schema 细节有差异。OpenAI、Anthropic、国产模型基本都参考了类似格式,但 Anthropic 早期对函数名有"只能字母数字下划线"之类的限制,某些模型对并发 tool_call 的支持程度也不同。这也是 MCP 这类统一协议存在的意义之一。

第三,Function Calling 只是一次调用决策。一个完整的 Agent 任务往往需要"模型调用工具 -> 拿结果 -> 再决策 -> 再调用"循环很多轮。这个循环不是模型 API 自动给的,是应用层的 Agent 编排代码写的。忘记了这一点,你会对"为什么我调了一次工具,模型就不接着做事了"感到困惑。

3. MCP:给工具装一个标准插座,插拔即用

3.1 从一个"到处都要写适配器"的痛点说起

在 MCP 出现之前,做一个 AI 应用要接外部能力,基本是一地鸡毛:

  • 接 OpenAI,要按它的 tool schema 写函数描述;
  • 接 Claude,要把同样的函数转成 Claude 的 tool 格式;
  • 接私有化模型,可能还得退回 prompt 解析的老路;
  • 每个工具的鉴权、超时、重试、错误码,全是要单独处理的细节。

这套东西没有标准,每个 AI 应用和每个工具之间都是点对点适配。今天你做了一个"订单查询函数"给 ChatGPT 用,明天想给 Claude 用,又得重写一层。

MCP 的思路非常朴素:与其两两适配,不如大家统一一个协议。工具提供方实现 MCP Server,AI 应用实现 MCP Client,中间用 JSON-RPC 2.0 通信。这样工具方只需要写一次暴露逻辑,所有支持 MCP 的 AI 产品都能用;AI 产品也只需要实现一次协议,所有 MCP 工具都能接。

3.2 MCP 的骨架:Client、Server、原语与传输

MCP 本身不是"某一个库",而是一套协议规范。架构上分三部分:

  • MCP Host:跑着 AI 应用的主体,比如 Claude Desktop、JetBrains 插件、你的 Spring Boot 服务。
  • MCP Client:Host 内部负责和 Server 建连、发请求的组件。
  • MCP Server:暴露能力的一方,可以是本地子进程,也可以是一个远程 HTTP 服务。

协议里有三个原语,我建议用一句话记:

  • Tools:可执行动作,本质上就是 Function Calling 里的 function。
  • Resources:可读取的数据,比如数据库 schema、项目文档、配置文件,用于给模型补充上下文。
  • Prompts:可复用的提示词模板,以标准方式提供给 Client。

传输层则常见四种:stdio(本地子进程通信,开发最方便)、SSE(旧版 HTTP 流式)、Streamable HTTP(新一代 HTTP 模式)、WebSocket(WSS)。Java 官方 SDK 都支持。

生态现状基本不用多说了:Playwright MCP 让 AI 能直接操作浏览器做自动化验证,Chrome DevTools MCP 让 AI 能帮你看前端调试信息,安全测试工具也有自己的 MCP Server,越来越多后端框架和 SaaS 工具都开始暴露 MCP 接口。

3.3 MCP 能解决什么,解决不了什么

MCP 能解决的是"接入标准化"和"能力复用"两件事。写好的"订单查询 Server"可以同时给桌面客户端、IDE 插件、你自己的后端 Agent 用,不用为每个消费方各写一套对接。

但记住,MCP 解决不了的事同样重要:

  • MCP 不负责模型怎么决定调用哪个工具。这个决策仍然是模型推理的事,通常还要靠 Function Calling 触发。
  • MCP 不管 Agent 的多步流程编排。先查订单还是先查物流、查完要不要发话术,这些得你在业务代码或 Skill 里定义。
  • MCP 不包含任何业务逻辑。工具本身的鉴权、权限、数据质量,全是工具方自己的责任。

所以"用了 MCP"不等于"Agent 变聪明了"。MCP 只是让 Agent 的"手"能够标准化地触达外部世界,脑子还是要靠模型和你的编排层。

4. Skill:把知识、流程和工具调用打包成可复用能力

4.1 Skill 不是 Tool,也不是 MCP:它是"作业指导书"

Skill 这个概念最近明显变流行了,但很多人对它理解偏了。说到底,Skill 就是把"遇到某类任务该怎么做"打包成一个可以复用的技能包。

它和 Tool 的区别最直接:Tool 是单个可执行动作,Skill 是一整套流程和知识。比如"查订单状态"是 Tool,"给出完整的售后话术并判断该不该发起退货"是 Skill。

它和 MCP 的区别也要说清:MCP 是协议,解决工具怎么被调用;Skill 是内容,解决模型面对任务时按什么步骤执行。两者经常配合使用——一个 Skill 的内部流程里,会写明"先调用查订单工具,再调用物流工具,最后按规则生成话术",而这些工具本身可能是通过 MCP 暴露的。

生活化类比:Tool 是厨房里的锅和刀,MCP 是统一的"厨具插口标准",Skill 是菜谱。你有整套好厨具(Tools),也统一了接口(MCP),但没有菜谱(Skill),新来的厨师还是不知道先切菜还是先烧水。

4.2 一个 Skill 的内部结构长什么样

参考 Agent Skills 的实践和我自己在项目里的做法,一个工程化的 Skill 通常包含几块:

  • 元信息:id、名称、版本、适用场景。
  • 指令模板:告诉模型何时激活这个技能、按什么步骤做、输入输出要求、哪些事不能做。
  • 依赖工具清单:这个 Skill 会用到哪些工具,显式列出,方便做工具权限过滤。
  • 示例:给一两个 few-shot 输入输出,提高模型执行的稳定性。
  • 资源引用:相关的文档、脚本、配置。

拿"Git 代码审查 Skill"举例更直观:输入是一个 PR 的 diff;流程是先调 git 工具拿到改动内容,然后按技能里的规则逐条审查(只查逻辑缺陷,不做风格建议,不重复 lint 已经报的问题),最后输出问题列表和严重级别。这里的"规则文本"和"审查步骤"就是 Skill,真正执行 git 命令的则是 MCP 暴露的 Tools。

4.3 三者怎么在一个项目里协作

最后把三层协作关系固定下来。项目里遇到一个问题时,你可以按这个顺序问自己:

你想问的问题对应概念
模型怎么决定调用哪个工具、传什么参数?Function Calling
工具怎么标准化暴露给 AI,跨应用复用?MCP
怎么把多步流程、业务规则、话术模板打包复用?Skill

工程上的协作链路是:Skill 命中后注入指令并筛选工具清单 -> 模型在清单内决策 -> 模型输出 tool_call(Function Calling)-> 执行器通过 MCP 协议把 tool_call 转发给对应工具 -> 结果回填 -> 模型继续。三个概念各司其职,缺位任何一个环节,项目都会在某个具体场景里卡壳。

5. Java 落地全流程:从 Spring AI 把三者串成可跑 Agent

5.1 选型:Spring AI / LangChain4j / 原生 MCP SDK,怎么挑

Java 生态里落地这三个概念,主流有三级路线:

  • Spring AI:Spring 官方在推,和 Spring Boot 结合最好,@Tool 注解开发体验最简单。适合团队已经以 Spring 为主、想最快跑通第一版 Agent 的场景。
  • LangChain4j:老牌 Java LLM 框架,概念丰富,社区活跃。适合需要复杂 Agent 编排、且愿意接受框架更多概念的团队。
  • modelcontextprotocol/java-sdk:官方 Java SDK,最贴近协议底层,灵活度最高,但 Agent 循环、上下文管理都得自己写。

我下面演示的组合是 Spring AI + 官方 MCP SDK。这不是唯一答案,但我在实际项目里跑过,比较顺手:Spring AI 管模型对话和本地 @Tool,MCP SDK 管外部工具协议层,Skill 用轻量级注册表管理。这套组合最大的好处是每一层都能被单独替换。

5.2 工程脚手架:JDK 17 + Maven 依赖 + 模型配置

基础环境用 JDK 17 + Spring Boot 3.x。pom.xml 里主要依赖这样加(版本号以你引入时官方最新为准,我当前用的是 1.0.x):

<properties> <java.version>17</java.version> <spring-ai.version>1.0.0</spring-ai.version> <mcp-sdk.version>0.10.0</mcp-sdk.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>${spring-ai.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency> <dependency> <groupId>io.modelcontextprotocol</groupId> <artifactId>sdk</artifactId> <version>${mcp-sdk.version}</version> </dependency> </dependencies>

application.yml 里主要配两样:模型接入信息和 MCP 连接信息。模型部分我用 OpenAI 兼容协议,这样换国产模型也只需要改 base-url 和 api-key:

spring: ai: openai: api-key: ${OPENAI_API_KEY} base-url: ${OPENAI_BASE_URL:https://api.openai.com} chat: options: model: ${OPENAI_MODEL:gpt-4o-mini}

注意:不同小版本 Spring AI 的配置前缀偶尔会调整,我写的是当前版本可用的写法。如果你的 IDE 自动提示里前缀不一样,以提示为准,逻辑不变。

5.3 第一步:用 @Tool 实现 Function Calling

Spring AI 里实现函数调用非常直接:写一个 Spring Bean,方法上加 @Tool 注解,框架会自动收集这些方法并把它们转成模型 API 需要的 tool schema。

import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Service; @Service public class OrderToolService { @Tool(description = "根据订单ID查询订单状态,订单ID格式如 OD-20240315;只有当用户提供完整订单号时才调用") public String queryOrderStatus(String orderId) { // 真实项目里这里调订单服务,demo 直接返回 return """ 订单号: %s 状态: 已发货 发货时间: 2024-03-16 10:30 承运商: 顺丰 运单号: SF1234567890 """.formatted(orderId); } }

这里有两个细节值得说:

  • 方法名 queryOrderStatus 会被作为工具名,所以命名要清晰。
  • 返回值用 String 是最稳的。Spring AI 虽然也能帮你序列化对象,但一旦序列化链路出问题,模型拿到的可能是无意义的 toString 结果。

有了这个工具,用户问"OD-20240315 啥时候发货"时,模型就会决策调用这个函数。但你可能会问:如果订单数据不在自己系统,而是另一个团队用 MCP Server 提供的呢?这就到了第二步。

5.4 第二步:把工具通过 MCP Server 暴露出去

假设"订单查询"不是本地方法,而是另一个系统提供的 MCP 能力。此刻你的应用只需要当一次 MCP Client。

用官方 SDK 连一个远端 MCP Server 的典型代码结构如下:

import io.modelcontextprotocol.client.McpClient; import io.modelcontextprotocol.spec.McpSchema; // 远端 Server 地址,假设它暴露了 query_order_status 工具 McpSchema.Implementation clientInfo = new McpSchema.Implementation( "order-service-demo", "1.0.0" ); McpClient client = McpClient.sync(clientInfo, new HttpClientTransport( "http://order-server.local/mcp" )); client.initialize(); // 列出远端 Server 提供的所有工具 List<McpSchema.Tool> remoteTools = client.listTools();

拿到 remoteTools 之后,把它们和自己的本地 @Tool 合并成一个统一的工具列表,再交给 ChatClient。这样一次模型请求里,模型能同时看到本地函数和 MCP 远端工具。

整个调用链会是这样的:

  1. 模型输出 tool_call:函数名 query_order_status。
  2. 执行器判断:这个函数不在本地 @Tool 列表里,而是注册在 MCP Client 的 remote tool 列表里。
  3. 执行器把参数交给 MCP Client,由它按协议发 tools/call 给远端 Server。
  4. Server 执行真实查询,返回 JSON。
  5. 执行器把结果作为 toolResult 塞回会话。
  6. 模型基于结果生成最终回答。

如果你的需求是把"自己的工具"提供给别的 AI 应用用,那就反过来,在系统里起一个 MCP Server。用官方 SDK 可以这样暴露:

McpSchema.Tool queryOrderTool = McpSchema.Tool.builder() .name("query_order_status") .description("根据订单ID查询订单状态") .inputSchema(""" { "type": "object", "properties": { "orderId": {"type": "string"} }, "required": ["orderId"] } """) .build(); McpServer server = McpServer.sync("order-service-server") .transport(new HttpServerTransport("/mcp", 8081)) .tool(queryOrderTool, this::handleQueryOrder) .register();

到这里,Function Calling 和 MCP 已经在 Java 工程里串起来了。最后一步补上 Skill。

5.5 第三步:把 Skill 做成可注册的"技能包"

Spring AI 没有一个现成的 Skill 类,但这不影响我们做。我习惯的做法是定义一个轻量的 Skill 注册表:

public record SkillDefinition( String id, String activateCondition, String systemPrompt, List<String> requiredTools ) {}

然后注册"订单售后顾问"这个技能:

SkillRegistry registry = new SkillRegistry(); registry.register(new SkillDefinition( "order_after_sale", "用户咨询订单状态、发货、物流、售后相关问题", """ 你是一名订单售后顾问。处理流程: 1. 先调用 query_order_status 获取订单状态。 2. 如果已发货,再调用 query_logistics 查询物流。 3. 如果超过承诺发货时间,向用户致歉并说明原因。 4. 不要编造物流信息,查不到就如实说明。 """, List.of("query_order_status", "query_logistics") ));

真正的执行逻辑很简单:每次收到用户消息时,先在 SkillRegistry 里做一次意图命中判断;命中某个 Skill 后,把它注入到 ChatClient 的 system 消息里,并且只加载该 Skill 白名单里的工具,其他工具对模型全部隐藏。

这里没有魔法。Skill 本质上是"系统性组织 prompt + 工具权限"的工程化方案。它的价值在项目变大后马上体现:不同业务域的规则不再散落在聊天逻辑里,而是一个个可注册、可复用、可单独调整的包。

5.6 完整跑通:一个"订单售后顾问"的调用链

最后,把三件套合体跑一遍。用户消息:"订单 OD-20240315 到底发货没有?"

完整链路如下:

  1. 意图识别层命中 Skill "order_after_sale",注入售后顾问指令,启用工具白名单。
  2. ChatClient 把 system + user 消息、白名单工具一并发给模型。
  3. 模型输出 tool_call:{"name": "query_order_status", "arguments": {"orderId": "OD-20240315"}}。
  4. 执行器检查发现该函数由 MCP Client 注册,于是按 MCP 协议发起 tools/call 请求。
  5. 远端 MCP Server 查出订单状态,返回 JSON:状态已发货、承运商顺丰、运单号 SF1234567890。
  6. 执行器把结果作为 tool 消息塞回会话。
  7. 模型看到结果后,再调用一次 query_logistics 查到最新物流节点。
  8. 模型按售后顾问的话术规则,告诉用户"已发货,预计明天送达"。

把每一步日志打出来,你能非常清楚地看到三层各干了什么:模型何时产生了调用决策(Function Calling),数据以什么协议走了一圈(MCP),最终回答被什么规则约束了(Skill)。这三个层次在一个 Agent 项目里不是可选项,而是互补项。

6. Java 实战里最容易被坑的 6 个点

6.1 工具返回类型:String 与结构化对象的恩怨

Spring AI 的 @Tool 方法返回 String 是最稳妥的,因为模型 API 的 toolResult 字段本身就是一个字符串。你可以返回一个序列化好的 JSON 字符串,模型读起来完全没问题。但如果方法返回一个 Order 对象,框架可能做了序列化,也可能直接 toString,一旦 toString 输出com.example.Order@1a2b3c这种垃圾,模型看到的就是一串无意义地址,后续回答自然一团糟。

我的建议:工具方法一律返回 String;如果你确实需要结构化结果,把工具调用结果当作数据,在执行器层自己解析,而不是在工具方法里给模型返回一个半死不活的对象。

6.2 description 写得糊,模型就瞎调工具

这是 Function Calling 里最常见的调优点,也是最容易被忽略的。一个工具能不能被正确触发,大部分取决于 description 写得好不好。

我踩过的一个典型坑:给工具写 description = "查询订单信息",结果模型在用户报出手机号的时候也调它,因为模型觉得"手机号也能查订单"。后来我把 description 改成"仅当用户提供完整订单号(格式如 OD-20240315)时调用,手机号查询不在本工具支持范围内",误调率立刻降为接近零。

经验格式是固定的:description 里要写清三件事——什么情况下触发、什么情况下禁止触发、参数遵守什么格式。

6.3 同步阻塞:慢工具会把整个 Agent 拖死

Function Calling 的执行模型是"一次 tool_call 回来,执行完再继续生成"。如果你的工具要跑 30 秒,整轮 Agent 就得干等 30 秒。Java 里最常见的坑是工具里直接调外部 HTTP 或者数据库慢查询,没有超时控制。

解法有两个层次:

  • 第一层:给所有工具调用加超时。HTTP 客户端设置 connectTimeout / readTimeout,数据库查询设置 statement timeout,不让某个工具无限阻塞循环。
  • 第二层:对长任务不要同步硬等。改成"任务受理 -> 返回任务ID -> 模型告诉用户正在处理 -> 提供查询任务状态的工具",让 Agent 异步轮询结果。这比同步等待优雅得多,但要注意给模型提供任务状态查询入口,否则模型无法自行确认结果。

6.4 死循环:模型反复调用同一个工具

模型是有概率陷入工具调用死循环的,尤其是在工具执行失败但错误信息不够明确时。典型情况:某个查询工具一直返回错误字符串,模型反复调用同一个工具重试,白烧了十几轮 token 还没结果。

我在工程里会用一道保险:单轮任务限制 tool_call 次数,默认 8 次。一旦超限,把最后一次工具结果替换成"已达到最大工具调用次数,请直接基于已有信息回复用户,不要再调用任何工具"。这样模型会被强制收尾,而不是无限空转。

6.5 MCP 连接:stdio 进程与远程连接的生命周期

本地开发的时候,stdio 模式的 MCP Server 用起来很舒服:SDK 直接拉起一个子进程,通过标准输入输出通信。但到了生产环境,stdio 模式会变成运维噩梦——进程挂了谁来重启?多实例怎么扩容?连接状态怎么管理?

我推荐生产环境使用 Streamable HTTP 或者 WebSocket 方式部署 MCP Server,把 MCP Server 独立成一个 HTTP 服务,应用侧只管发 HTTP 请求。同时要注意,MCP 连接不是完全无状态的:初始化握手、会话管理、连接复用、重连策略,这些都要纳入考虑,不能像普通 REST 接口一样完全裸奔。

6.6 安全红线:别把 MCP 工具无脑暴露给模型

最后一个最容易翻车,也最容易被无视:MCP 生态里有很多"通用工具",比如 Shell 执行、文件读写、外网访问。插上就能用,体验很好,但如果你的模型拥有自主决策调用这些工具的权限,本质上是给 Agent 开了一个不受控的执行后门。

我在内部 demo 里就见过一次事故:模型因为一个 shell 工具的误用,把测试环境搅得一塌糊涂。事后复盘,问题不在模型,而在权限设计。

现在我把这些规则写进上线 checklist:

  • 对模型可见的工具做最小白名单,Skill 的 requiredTools 正好就是落地这个机制的抓手。
  • 远程 MCP Server 必须鉴权,不能裸挂在公网。
  • 写入、删除、命令执行这类高风险工具,调用前必须有人工确认环节。
  • 所有工具调用要记录日志,便于事后审计。

我最后悔的一件事,是第一次做 Agent 时没有把安全清单前置,结果 demo 现场差点把测试机搞挂。现在我把工具权限检查放在任何 Agent 项目的第一条,宁可少接几个工具,也不让模型拿到超出预期的执行能力。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询