AI Agent三要素:Function Calling、MCP与Skill到底什么关系? 今年只要聊AI Agent就绕不开三个词Function Calling、MCP、Skill。我带着Java团队做Agent底座时被问得最多的一句话是这三个东西到底什么关系有人觉得MCP是Function Calling的升级版有人把Skill当成提示词文件夹还有人拿着MCP协议文档去给硬件团队开会。这几年的技术社区里三个名词天天在热搜里来回穿插但你真要在代码里分清楚它们会发现资料越看越乱。这篇文章先把三者关系讲透再用一个Java订单查询Agent的完整落地流程把它们串起来适合正在用Java做Agent、ChatBot或企业集成层的工程师也适合刚入门、被各种名词绕晕的新人。我会尽量用工程场景说话少聊虚概念。1. 三者关系一句话分清“怎么叫、怎么通、怎么干”1.1 Function Calling模型侧的函数调用约定先讲Function Calling。它并不是一个网络协议也不是某种SDK而是大模型API对外提供的一种能力约定当模型发现自己需要外部数据或执行动作时不再直接回答一个错误结论而是输出一段结构化JSON告诉你“我想调用哪个函数、参数是什么”。模型输出的东西大概是这样的{ name: queryWeather, arguments: {\city\: \北京\} }很多初学者看到这串JSON容易产生一个误会认为模型已经帮你查了天气。实际上模型什么都没做它只是把“要去查天气”这个意图表达成了机器可读格式。真正执行查询的是你的Java代码查完之后把结果拼成一条tool类型的消息重新发给模型模型再基于结果组织语言回复。所以Function Calling的本质是“模型把决策权交回给应用”而不是模型本身就拥有了工具。这个机制很像两个人协作A需要数据但A不知道数据在哪于是它写一张纸条“请B查一下city北京”B查完把结果贴回来给A看。模型就是那个A你的Java服务就是B。没有这张纸条模型只能强行编造答案有了这张纸条模型懂得说“这事我处理不了得让代码来”。1.2 MCP为“工具接入”制定统一插座MCP全称Model Context Protocol由Anthropic发起并开源目前已经在Spring AI、LangChain等框架里被广泛适配。对Java工程师来说最直观的理解是以前你要给Agent接入内部订单系统、数据库、浏览器操作工具每个系统都要写一套自定义适配器工具接口散落在各处、参数格式五花八门现在MCP统一了“模型应用”和“工具/数据源”之间的交互方式。MCP Server负责把工具能力暴露出来MCP Client负责在应用侧发现、调用这些工具。这里要说清楚MCP和Function Calling的关系Function Calling解决“模型怎么表达调用函数的请求”MCP解决“这个函数通过什么标准接口暴露、如何被多个Agent复用”。在一个实际系统里MCP Server返回的Tool清单往往会在接入阶段被转换成模型需要的Function Calling schema。也就是说你既用了MCP也还是在用Function Calling两者不是替代关系。MCP生态现在已经很丰富。举几个真实场景Playwright MCP用于浏览器自动化Chrome DevTools MCP把DevTools协议能力变成工具Burp Suite MCP让AI能直接操控安全测试工具同花顺MCP暴露行情数据Unity MCP面向游戏引擎自动化TIA MCP对应工业自动化PLC调试Vivado MCP用于FPGA设计。这些例子说明MCP的价值不在某一种具体工具而在于“工具接入标准化”这件事本身。一旦一个工具被包装成MCP Server任何支持MCP Client的Agent应用都能直接使用它不用再为不同产品各写一套适配层。1.3 Skill面向某个场景打包好的“干活手册”Skill的定义比前两个更容易产生分歧因为不同平台对Skill的实现完全不一样。在Cline里Skill通常是一组SKILL.md说明文件和辅助脚本在Anthropic Agent Skills里Skill是一个包含提示词、示例和规则的文件集合在社区里你会看到“数学建模Skill”“倪海厦知识Skill”“Book to Skill”这类包本质都是把领域知识、专家步骤、示例答案打包成一个可加载的模块。我更愿意把Skill理解为应用层方案它不规定模型和工具之间怎么通信那是MCP和Function Calling的事它规定“面对某一类任务Agent应当按什么顺序思考、调用哪些工具、遵循什么输出格式”。比如“订单售后Skill”可能会告诉模型第一步先确认订单号第二步调用query_order获取订单状态第三步如果订单状态是已签收就调用query_logistics查询物流详情最后按照客服话术模板组织回复。这套流程绑定的是一个Agent业务系统而不是某个具体模型API或某个MCP Server。有的团队会给技能加统一编号比如skill-247本质上是在技能注册中心里做索引让编排器能快速匹配到对应技能包。那些在社区里流传的领域Skill包除了提示词往往还附带了知识库文件、工具调用示例和结果模板这比单纯一段System Prompt要实用得多。1.4 三者的协作关系同时存在各管一段把三者关系用一套日常比喻收拢Function Calling像“电话规格书”告诉你打电话时要说什么格式、怎么说MCP像“USB-C接口标准”让充电器工具/数据源能插到各种设备Agent上Skill像“一份岗位SOP”告诉你这个岗位遇到具体问题按什么步骤干什么活。一个实际请求里三者是同时存在的。比如用户说“帮我分析一下这份销售数据的异常原因”Skill层会加载数据分析模板把需要调用的数据查询工具列表塞给模型模型通过Function Calling机制表达“我要调用query_sales_data参数是时间范围”实际查询动作则通过MCP Client转发到数据服务。所以三者不是竞争关系而是同一个Agent请求链路里的不同分层。2. 为什么总有人把它们搞混2.1 它们全都在Agent链路上现身混淆的根本原因在于在一个Agent请求里你可能同一时间接触到全部三个概念。比如用户问“帮我查一下上周北京的天气并把结果整理成周报”Agent框架会用Skill中的周报技能加载提示词模板模型收到后触发一个名为queryWeather的函数调用而这个函数实际由MCP Client转发到远程天气服务。每一步都对应一个通用名词很多文章又喜欢把名词混着讲读的时候自然就晕了。我在做Java演示项目时最开始也搭建过一套纯Function Calling实现后来把工具改成MCP接入代码结构确实变了但这并不意味着Function Calling被替代了。实际上应用代码里还是保留了一个FunctionCallingRouter只是这个Router的职责从“新写一个HTTP调用”变成了“把请求转发给McpClient”。理解这一点就不会再纠结“该二选一”的问题。2.2 Skill这个词在不同平台含义飘忽Skill是三个词里最容易被绕晕的。有的平台说“Skill就是提示词”有的说“Skill是代码模板”还有的把整套Agent指令、工具配置和知识库全部叫Skill。同一个词在不同上下文里指的东西完全不同。举几个真实例子。有人做“倪海厦Skill”这其实是一个知识库问答技能包里面既没有代码也没有工具只有一批整理好的文档和提问引导语有人做“数学建模Skill”里面包含的是建模步骤、常用公式和论文模板还有人做“Codex Skill”那更接近工程规范模板告诉模型在编码时遵循什么目录结构和设计约束。所以读资料之前建议先确认平台语境。如果讨论的是SKILL.md这类文件那它更接近“给Agent看的说明书”如果讨论的是上传到技能市场的课程包那它其实是一个“教学场景里的知识产品”如果讨论的是Java代码里的AgentSkill接口那它就变成了运行时的编排规则。三种Skill除了共享一个名字底层结构差异很大混着读资料大概率会一头雾水。2.3 MCP到底是软件协议还是硬件协议热搜里有人问“MCP是软件协议硬件协议那个概念叫什么来着”这个问题值得正面回答MCP是纯软件协议它处在应用层和HTTP、SQL、WebSocket是一个层级的思想。大家常说的“AI界的USB-C”是一种比喻指的是MCP解决了工具接口碎片化不要把这个比喻理解成物理层协议。逻辑上MCP建立在JSON-RPC之上传输层可以用stdio、SSE、Streamable HTTP或WebSocket承载所以做Java对接时你完全可以用一个HttpClient或者WebSocketClient去连它而不需要关心硬件设备。如果是要找“硬件设备的接口标准”那通常叫物理层协议或总线协议比如USB、PCIe、SPI。MCP属于应用层软件协议它的目的是把工具能力描述给模型应用而不是描述电子信号。搞清楚这一层至少不会在技术评审会上闹出“给MCP Server加网卡”这种乌龙。3. Java落地全流程一个订单查询Agent的三种能力3.1 工程选型要不要引入Spring AIJava侧最常见的接入方式有两条路一是直接用官方MCP Java SDK搭配OpenAI兼容接口自己写调用逻辑掌控力强适合项目已经和Spring强耦合的团队二是用Spring AI框架它内置了ToolCalling和McpClient的适配注解化程度高适合新项目快速起盘。如果只想搞懂原理我更推荐第一条路因为你能看到Function Calling和MCP之间所有兼容细节。假设你用的是JDK17、Spring Boot 3.x。我建议的最小依赖是spring-boot-starter-web、jackson-databind、官方MCP Java SDK。不要一上来就引一堆AI框架先把模型调用、函数路由、MCP接入这三个环节拆开后面再套框架会清晰很多。如果项目里已经用了Spring AI那直接使用它的Tool注解和ToolCallback机制会方便很多但底层思路完全一样。3.2 第一步把Java方法注册成模型能看懂的Function模型不会直接读Java方法它只认JSON Schema。所以第一步是把业务方法声明成“工具”。我习惯用一个自定义注解来做这件事比如定义一个AgentTool注解里面写工具名、描述和参数描述然后用启动扫描器把它注册成一个ToolSpec列表。下面给一个精简示例Component public class OrderTool { AgentTool(name query_order, description 根据订单号查询订单状态) public OrderInfo queryOrder( AgentParam(description 订单号例如A12345) String orderId) { return orderService.findByOrderId(orderId); } }扫描器在启动时通过反射读出方法签名用Jackson把参数转换成JSON Schema对象最后组装成模型API需要的tools数组。这一步的坑主要集中在类型映射Java的Long、Integer会映射成JSON的integer类型String映射成stringLocalDate如果不写自定义序列化器会变成一串数组建议直接在AgentParam里额外指定format。参数顺序也很重要模型只会按JSON里的命名参数传值不会管Java方法参数顺序所以参数描述里必须提供足够清楚的字段名和说明。当模型返回tool_calls比如query_order时你调用ToolRouter.dispatch(query_order, args)找到OrderTool实例并执行拿到结果后构造一条roletool的返回消息再发给模型。整个过程就是一个“模型决策 → 本地执行 → 回填再生成”的循环没有任何黑魔法。3.3 第二步把一个远程MCP Server接入Java工程如果你的订单数据不在当前服务里而在一个独立的订单中台而且这个中台已经用MCP Server暴露了查询能力那就可以省去自己手写HTTP调用的步骤。接MCP时先在pom里引入官方SDK然后构建一个McpClient。以WebSocket传输为例McpTransport transport HttpWebSocketTransport.builder() .uri(URI.create(wss://your-host/mcp?tokenyour-token)) .build(); McpClient mcpClient McpClient.builder(transport).build(); mcpClient.initialize().block(); ListTool tools mcpClient.listTools().block().tools();注意MCP Java SDK的API版本之间略有差异具体类名和调用方式以官方SDK当前版本为准上面代码的重点是链路结构。初始化完成后把tools列表里的每个Tool转换成前面Function Calling需要的ToolSpec让模型在对话里看到它们。当模型选择调用某个MCP工具时路由层不再直接调用本地Service方法而是执行CallToolResult result mcpClient.callTool(toolName, arguments).block();这里有几个关键点。第一远程MCP Server可能同时被多个Agent共享所以工具入参里要带userId、tenantId这类上下文防止越权。这正好对应Java侧的行级权限控制不光SQL里要写where owner_id ?工具调用入口也要做数据范围校验。第二callTool结果要统一包一层因为不同Server返回的content类型可能是文本、图片或结构化数据Java侧建议都转成标准ToolInvocationResult。第三如果MCP Server需要认证建议在连接层统一处理而不是让每个业务方法自己传token。3.4 第三步Skill层在Java里怎么落地Skill落到Java代码里我推荐一个非常轻的模式定义AgentSkill接口每个技能一个实现类注册到SkillRegistry中由编排器根据用户请求自主匹配。public interface AgentSkill { String id(); String description(); ListString requiredTools(); String promptTemplate(); } Component public class OrderAfterSaleSkill implements AgentSkill { Override public String id() { return skill-247; } Override public String description() { return 处理订单售后咨询包括状态查询、物流追踪、退款引导; } Override public ListString requiredTools() { return List.of(query_order, query_logistics); } Override public String promptTemplate() { return 你是售后客服。先从用户消息中提取订单号再调用查询工具最后按模板生成回复。禁止编造退款信息。; } }这样做的好处是Skill和工具解耦。以后“订单售后Skill”要接入一个新工具只需修改requiredTools()要调整一个话术只需改promptTemplate()要上线一个“库存预警Skill”新增一个类即可不影响路由层。Skill里的description是给Agent编排器做路由用的务必写清楚适合什么场景、需要哪些工具别写“一个通用技能”这种废话。注意Skill本身并不是运行时对象它只是“场景配置”。真正运行还是那条链路编排器根据用户问题匹配Skill → 把Skill的Prompt和工具列表传给模型 → 模型返回tool_calls→ 路由器执行 → 结果回填。很多Java初学者误以为需要为每个Skill写一套执行引擎其实完全没必要复用一套FunctionCallingRouter就够。3.5 一个综合示例完整请求链路跑一遍假设业务是“用户问我的A12345订单还没到帮我查一下物流”。整个Java链路是AgentController收到用户消息先从Session里取出当前登录用户构建UserContext。编排器调用SkillRegistry.match(订单还没到)命中OrderAfterSaleSkill得到系统提示词和requiredTools。把这批工具通过Function Calling格式传给模型模型生成tool_calls [query_order]参数为orderIdA12345。路由层先用UserContext做行级权限校验确认当前用户是订单owner再执行orderService.findByOrderId。返回订单已签收后模型继续请求query_logistics。如果物流服务走的是MCP Server路由层就转发给mcpClient.callTool。工具结果全部回填后模型输出“您的订单昨天已签收物流显示快递员在18:02派送完成感谢等待”。这条链路同时用到了三个概念Skill负责第2步的场景选择Function Calling负责第3步和第5步的模型决策MCP负责第5步的远端工具调用。这正是三者共存的实际形态。我还建议在工具调用入口维护幂等键因为Agent重试很常见如果query_order是无状态查询问题不大但如果是“扣减库存”“创建退款单”这类写操作同一个tool_calls被重复执行会产生脏数据。Java侧处理起来很简单用请求ID作为幂等键操作前查重、操作中加锁、结果缓存在Redis里即可。4. 踩坑记录与排查技巧4.1 Function Calling参数“对不上”是头号问题模型生成的参数和你定义的方法参数对不上几乎每天都能遇到。常见原因有三个参数描述写得模糊导致模型把orderId和order_no混用Java类型过于严格比如int类型收到字符串A123直接抛异常JSON Schema里没有声明required模型漏传关键参数。我的处理办法是在AgentParam里把可选参数和必填参数分清楚并加上enum约束同时用一个宽松的ObjectNode接收原始参数然后在Java方法体里做类型转换和校验而不是让Jackson直接绑定到强类型参数。4.2 MCP连接不稳定WSS断连和会话重建接入远程MCP Server时WSS或SSE连接断开是常态。最常见的问题Java进程重启后MCP Client没有重新初始化长连接被网关闲置断开后没有重连机制还有一部分是依赖冲突官方MCP SDK里的reactor-core版本和Spring Boot自带版本打架。排查建议先打开MCP SDK日志观察initialize和listTools是否成功给McpClient加一层带指数退避的重连包装在应用启动时做一次健康检查专门探测listTools失败就快速失败并拉起告警。如果SSE断线注意重新初始化时会话ID可能失效需要重新做一次initialize握手。4.3 Skill目录编码和跨平台迁移坑实操中我栽过几个跟头技能目录名带中文或空格在Windows和Linux之间拷贝后路径错乱SKILL.md保存成UTF-8 with BOM模型在解析首行时多出不可见字符不同平台的Skill配置字段不统一导致从Cline导出再导入到另一个Agent工具里提示词和工具列表对不上。我现在做法是所有技能ID用ASCII短横线命名比如order-after-sale另外给每个技能配一个封闭式JSON清单里面固定声明id、description、prompt_file、tools跨平台迁移只迁移这个清单和prompt文件。4.4 实战案例Trae IDE Burp Suite MCP做安全测试很多安全工程师已经把Burp Suite通过MCP接入IDE让AI能直接操控扫描器。具体来说Burp Suite MCP Server会把send_request_to_burp、fetch_burp_scan_status这类动作暴露成工具IDE侧的Agent接到“帮我扫一下这个接口的越权风险”的指令时可以直接转化为一次工具调用。对Java落地来说这意味着你的Agent不必只调业务接口也能把安全检测工具作为MCP Server接入同一个编排链路。需要特别注意的是权限边界Burp这类高危工具不能被任意业务用户触发建议在MCP Client层做角色校验只有安全运维角色能调用这类工具同时给每个扫描请求打上审计ID方便事后追溯。这也是行级权限在工具层面的延伸——不只是在数据查询时控制行而是在“谁能调这个工具”上做控制。4.5 选型速查表你到底需要哪一个场景推荐方案理由只有单体Java服务、工具就三五个只用Function Calling没必要为少量方法引入MCP链路工具分布在多个微服务/第三方系统上MCP一次接入多处复用避免为每个服务写适配需要把浏览器操作、安全工具、行情数据等外部能力接入Agent上MCP直接用现成Server生态省掉大量定制开发同一类业务任务反复出现需要固定步骤和话术设计Skill把流程和领域知识沉淀为可复用模块工具又多、场景又杂团队以Agent为主业三者都用分层落地Skill管编排、MCP管接入、Function Calling管模型交互5. 落地顺序与我的忠告5.1 给Java团队最省心的落地顺序如果让我给一个Java技术团队最省心的落地顺序我建议先把纯Function Calling跑通哪怕只有一个方法也把“模型输出tool_calls→本地执行→回填”这条闭环找好感觉第二步再接入一个现成MCP Server可以是Playwright MCP也可以是你们公司内部系统改造成MCP Server把工具路由层从本地方法扩展成远程调用最后才抽象Skill层按业务场景收敛Prompt和工具组合。别一上来就铺MCP和Skill那样排障时你会分不清是协议问题、工具问题还是编排逻辑问题。5.2 经验与忠告只为复杂度买单我踩过最大的坑是“为了MCP而MCP”有一个内部系统只需要暴露两个查询接口我用Function Calling直接调Service就够非要套一层MCP Server结果每次改接口都要发布两个工程。后来把这个系统回归成普通Function Calling复杂度立刻降下来。反过来公司里另一个公共数据中台被十几个Agent复用最近统一改造成MCP Server后接入成本反而大幅下降。所以这三个概念不是越高级越好核心还是看“复用程度”和“调用场景”。最后分享一个实用小技巧在FunctionCallingRouter里把每次调用参数、路由耗时、返回结果全部打印成结构化日志。跑通阶段这些日志看起来啰嗦但等你要调Skill里的提示词、排查MCP超时问题时它们就是最好的排障线索。很多Agent项目“跑不通”或“效果不稳定”最后查下来都栽在缺少这一层链路日志上。