尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

从Function Calling到MCP:大模型工具调用范式的演进与实践

从Function Calling到MCP:大模型工具调用范式的演进与实践 1. 项目概述从Function Calling到MCP的范式演进最近在跟几个做AI应用落地的朋友聊天发现一个挺有意思的现象大家聊到如何让大模型LLM调用外部工具或数据时第一反应还是“Function Calling”。这很正常毕竟过去一年多Function Calling几乎是所有主流模型API比如OpenAI、Anthropic提供的标准解决方案也催生了LangChain这类框架的繁荣。但聊深了就会发现不少团队在实际项目里踩了坑——工具描述写起来又臭又长还容易出错、不同模型间的Function Calling格式不兼容、复杂的工具链管理起来像一团乱麻。这时候我通常会提一嘴“你们有没有试过用MCP的思路来重构一下”MCP全称是Model Context Protocol你可以把它理解成Function Calling的“升级版”或“标准化版本”。它不是某个公司突然推出的新API而是一套由多方包括Anthropic等参与设计的开放协议。简单说Function Calling像是每家餐厅都有自己的点菜单API格式而MCP则试图定义一套通用的“餐饮业点餐标准协议”。这个协议的核心目标是解决一个根本问题如何让大模型与外部世界数据、工具、系统进行更安全、更标准化、更易管理的交互。为什么Function Calling会“不够”想象一个场景你需要让模型能查询数据库、调用内部CRM API、还能在特定条件下发送邮件。用传统的Function Calling你需要在每次对话的初始系统提示词里塞进几十甚至上百行的JSON Schema来描述这些函数不仅消耗宝贵的上下文窗口而且一旦某个函数的参数有变动就得重新部署整个提示词工程。更麻烦的是如果你同时接入了GPT-4和Claude可能还得维护两套格式略有不同的函数定义。MCP的诞生正是为了把工具和数据的“定义”与“调用”解耦提供一个统一的、声明式的接口层。2. 核心需求解析Function Calling的四大痛点与MCP的应对要理解MCP的价值得先看清楚Function Calling在复杂生产环境中到底遇到了哪些天花板。我结合自己过去在几个AI智能体项目中遇到的实际情况总结了四个最典型的痛点。2.1 工具定义的臃肿与低效在Function Calling模式下工具函数的定义是以JSON Schema的形式直接嵌入到对话的初始系统消息或每次请求中的。对于只有三五个简单工具的场景这没问题。但一旦工具数量增长到几十个或者单个工具的参数结构非常复杂比如一个创建工单的API涉及十几个嵌套字段这个“工具定义块”就会变得极其庞大。我经历过一个项目工具定义部分就占用了近4000个Token。这不仅浪费了宝贵的上下文窗口意味着能处理的对话历史变短更糟糕的是影响了模型性能。有些模型在处理超长系统提示时对工具调用的理解和准确性会下降。MCP通过引入“服务器Server”和“资源Resource”的概念将工具和数据的元信息与对话流分离。模型客户端Client通过标准的MCP协议向服务器查询“现在有哪些工具可用”服务器返回一个轻量级的工具列表。只有当模型真正需要调用某个工具时才会去获取其详细的参数schema。这种按需加载的机制从根源上解决了定义臃肿的问题。2.2 工具管理的复杂与僵化在动态的业务环境中可用的工具集不是一成不变的。比如你的系统接入了新的数据分析平台或者某个内部API进行了版本升级。在纯Function Calling架构下这意味着你需要更新所有可能用到这个工具链的AI应用代码并重新部署。整个过程是“硬编码”和“紧耦合”的。MCP协议将工具和数据源抽象为独立的“服务器”。这些服务器可以独立开发、部署和更新。一个MCP客户端比如一个AI助手应用可以同时连接多个这样的服务器。当数据分析平台发布了新的MCP服务器时你只需要将该服务器配置到客户端的连接列表中客户端在启动时就会自动发现其提供的所有新工具。这实现了工具生态的“热插拔”极大地提升了系统的可扩展性和可维护性。2.3 安全与权限控制的缺失这是Function Calling模式下一个非常严峻的问题。当你把内部数据库的查询函数、发送邮件的函数、操作云资源的函数全部暴露给模型时你实际上是在模型面前挂了一把“万能钥匙”。模型理论上可以调用任何它被赋予的函数而缺乏基于会话、用户或上下文的细粒度权限控制。虽然可以在函数实现内部写校验逻辑但这分散在各处难以统一审计和管理。MCP协议在设计之初就考虑了安全性。首先工具在MCP中称为“工具”和数据源称为“资源”由独立的服务器托管这些服务器可以运行在受控的网络环境或沙箱中。其次MCP支持在协议层进行认证和授权。客户端与服务器建立连接时可以进行鉴权。更重要的是MCP服务器可以在提供工具列表时根据当前会话的上下文动态决定暴露哪些工具或者返回带有特定权限标记的工具定义从而在协议层面实现权限隔离。2.4 开发者体验与互操作性的挑战如果你为GPT-4的Function Calling写好了一套工具现在想迁移到Claude的API上很可能需要重写一大部分定义因为两者的JSON格式细节和最佳实践可能有差异。此外调试Function Calling也很痛苦——你需要模拟模型的思考过程看它是否正确地“选择”了你想让它调用的函数。MCP作为一个开放标准旨在统一这类交互的“语言”。无论后端是哪个模型它们都通过相同的MCP协议与工具服务器通信。这大大提升了互操作性。同时MCP生态正在涌现出丰富的开发工具比如MCP服务器SDK、客户端库、以及像mcp inspector这样的调试工具可以让你直观地看到模型与服务器之间的请求响应流极大地改善了开发体验。3. MCP架构深度拆解核心组件与通信流程理解了“为什么需要MCP”我们再来深入看看MCP“是什么”。它的架构非常清晰遵循了经典的客户端-服务器模型但针对AI智能体的场景做了精心设计。3.1 核心组件三角色一个完整的MCP交互涉及三个核心角色理解它们的关系是掌握MCP的关键MCP 客户端 (Client)通常就是大模型本身或者是一个封装了大模型的应用程序如AI助手。客户端的核心职责是“思考”和“发起请求”。它根据用户的问题和上下文决定是否需要调用外部工具或读取外部数据并通过MCP协议向服务器发送标准化请求。MCP 服务器 (Server)这是工具和数据的提供方。一个服务器可以封装一个单一功能如“查询天气”也可以封装一整套相关功能如“数据库所有CRUD操作”。服务器的职责是宣告能力在初始化连接时告诉客户端“我这里有这些工具Tools和资源Resources可用”。处理请求接收客户端对工具调用的请求执行实际的代码如调用API、查询数据库并将结果返回。提供数据响应客户端对资源如某个文件、一段文本的读取请求。传输层 (Transport)连接客户端和服务器的“管道”。MCP协议本身是传输无关的这意味着它可以运行在多种通道上。目前最常用的两种是stdio (标准输入输出)服务器作为一个子进程启动客户端通过stdin/stdout与其通信。这种方式简单适合本地集成。SSE (Server-Sent Events)或WebSocket服务器作为一个独立的HTTP服务运行客户端通过HTTP请求或长连接与其通信。这种方式更适合远程、跨网络的部署。3.2 协议通信流程实录让我们通过一个具体的用户查询“帮我总结一下/projects/report.md文件的主要内容并发送给项目组邮箱”来走一遍MCP的通信流程。假设我们有两个MCP服务器一个文件系统服务器提供读取文件资源的能力一个邮件服务器提供发送邮件的工具。初始化与握手AI应用作为MCP客户端启动时会同时连接到文件服务器和邮件服务器。两个服务器会分别发送initialize请求并回复一个initialization响应其中包含它们各自提供的tools和resources列表。例如文件服务器宣告它提供了read资源的能力邮件服务器宣告它提供了send_email工具。模型思考与规划用户提出请求。模型客户端分析请求识别出两个关键动作a) 读取一个文件资源b) 调用一个发送邮件的工具。读取资源模型首先向文件服务器发送一个resources/read请求指定资源URI为file:///projects/report.md。文件服务器读取该文件内容并通过resources/read响应将文本内容返回给模型。这里与Function Calling的关键区别显现了模型获取数据是通过一个标准的“资源读取”操作而不是调用一个名为read_file的函数。这更符合“获取上下文”的语义。调用工具模型获得了文件内容后开始构思邮件摘要。然后它向邮件服务器发送一个tools/call请求请求调用send_email工具并在参数中填入收件人、主题、以及生成的邮件正文。邮件服务器执行实际的邮件发送逻辑可能调用SMTP服务或邮件API。返回结果邮件发送成功后邮件服务器返回一个tools/call响应其中包含执行结果如{“status”: “success”, “message_id”: “…”}。模型最终将工具执行的结果整合回复用户“已成功读取报告文件并已将总结发送至项目组邮箱。”整个过程中模型不需要在一开始就知道read_file函数需要path参数或者send_email函数需要to、subject、body参数。它只是在需要时通过标准协议去查询和调用。这种动态性、解耦的设计正是MCP强大之处。注意MCP中的Resource资源概念非常强大。它不仅可以表示文件还可以表示数据库中的一行记录、一个网页的实时内容、甚至一个API的查询结果。客户端可以“订阅”一个资源当资源内容变化时如股票价格更新服务器可以主动推送通知这使得构建实时性强的AI应用成为可能。4. 实操对比用Function Calling与MCP实现同一个需求光讲理论不够直观我们用一个具体的开发场景来对比两种方式。假设我们要构建一个“智能开发助手”它需要具备两个能力1搜索公司的内部技术文档2在GitLab上创建一个新的Merge Request (MR)。4.1 Function Calling 实现方式首先我们需要在系统提示词中定义两个函数{ tools: [ { type: function, function: { name: search_internal_docs, description: 搜索公司内部技术文档库, parameters: { type: object, properties: { query: { type: string, description: 搜索关键词 }, max_results: { type: integer, description: 返回的最大结果数, default: 5 } }, required: [query] } } }, { type: function, function: { name: create_gitlab_mr, description: 在指定的GitLab项目中创建一个Merge Request, parameters: { type: object, properties: { project_id: { type: string, description: GitLab项目ID例如 group/project }, source_branch: { type: string, description: 源分支名 }, target_branch: { type: string, description: 目标分支名默认为 main, default: main }, title: { type: string, description: MR的标题 }, description: { type: string, description: MR的详细描述 } }, required: [project_id, source_branch, title] } } } ] }痛点立刻出现提示词膨胀这段JSON会占用大量Token。硬编码如果我想增加一个“查询Jira工单”的功能必须修改代码、更新提示词并重新部署整个助手服务。权限混合这个助手可能被多个团队使用。A团队只能访问A组的文档和GitLab项目B团队则访问B组的。但在当前定义下模型看到了所有可能的工具权限控制逻辑必须强行写在search_internal_docs和create_gitlab_mr这两个函数的内部实现里变得复杂且容易出错。调试困难当模型没有按预期调用create_gitlab_mr时我需要检查是描述不清还是参数schema定义有问题这个过程缺乏工具支持。4.2 MCP 实现方式在MCP架构下我们会创建两个独立的MCP服务器文档搜索服务器 (docs-mcp-server)这个服务器在初始化时可以根据当前连接的用户/令牌动态决定其有权访问的文档库范围。它向客户端宣告提供一个名为search的工具。GitLab操作服务器 (gitlab-mcp-server)同样它根据认证信息绑定到一个具体的GitLab实例和权限范围。它宣告提供create_mr、list_projects等工具。我们的智能助手应用MCP客户端启动时会读取用户的配置动态地连接到该用户有权限访问的服务器。例如用户Alice登录后客户端会连接到她有权限的“A组文档服务器”和“A组GitLab服务器”。当Alice提问“请帮我搜索一下‘容器部署’的文档然后基于feat/new-ui分支给frontend/app项目创建一个MR标题是‘更新部署说明’。”模型客户端先向docs-mcp-server发起tools/call请求调用search工具参数为query容器部署。拿到搜索结果后模型再向gitlab-mcp-server发起tools/call请求调用create_mr工具参数为project_idfrontend/app,source_branchfeat/new-ui,title更新部署说明并将搜索到的相关文档摘要填入description。MCP带来的优势动态与解耦工具的定义和实现完全封装在独立的服务器中。要新增一个Jira服务器只需开发和部署它然后在客户端配置中为相应用户添加连接即可核心助手代码无需改动。内置安全边界权限控制前置到了服务器连接层。Alice根本无法连接到B组的服务器因此模型根本“不知道”那些工具的存在从根源上消除了越权风险。开发体验我可以使用mcp inspector工具单独调试gitlab-mcp-server模拟客户端的调用请求查看原始响应而无需启动整个AI应用。5. 从理论到实践构建你的第一个MCP服务器理解了优势我们来动手实现一个最简单的MCP服务器直观感受一下协议的工作方式。我们将创建一个“时间服务器”它提供一个工具get_current_time用于获取指定时区的当前时间。我们将使用官方推荐的modelcontextprotocol/sdk来构建。这是一个TypeScript/JavaScript SDK其他语言Python、Rust等的SDK也在陆续完善中。5.1 环境准备与项目初始化首先确保你的环境有Node.js (18)。然后创建一个新目录并初始化项目mkdir mcp-time-server cd mcp-time-server npm init -y npm install modelcontextprotocol/sdk5.2 服务器核心代码实现创建一个server.js文件写入以下代码import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/stdio.js; import { CallToolRequestSchema, ListToolsRequestSchema, } from modelcontextprotocol/sdk/types.js; // 1. 创建Server实例 const server new Server( { name: mcp-time-server, version: 1.0.0, }, { capabilities: { tools: {}, // 声明本服务器提供工具 }, } ); // 2. 定义工具列表 const tools [ { name: get_current_time, description: 获取指定时区的当前日期和时间。, inputSchema: { type: object, properties: { timezone: { type: string, description: IANA时区名称例如 Asia/Shanghai, America/New_York。默认为 UTC。, default: UTC, }, format: { type: string, description: 输出时间格式。可选值iso (ISO 8601), human (人类可读)。默认为 iso。, enum: [iso, human], default: iso, }, }, }, }, ]; // 3. 处理工具列表请求 server.setRequestHandler(ListToolsRequestSchema, async () { return { tools: tools, }; }); // 4. 处理工具调用请求 server.setRequestHandler(CallToolRequestSchema, async (request) { const { name, arguments: args } request.params; if (name ! get_current_time) { throw new Error(未知工具: ${name}); } const timezone args?.timezone || UTC; const format args?.format || iso; try { // 这里使用一个简单的库来处理时区实际项目中可用 date-fns-tz 等 // 为简化演示我们假设时区有效并模拟一个转换 const now new Date(); let result; if (format human) { // 模拟一个人类可读格式 result 当前时间时区 ${timezone}是${now.toLocaleString(en-US, { timeZone: timezone })}; } else { // ISO格式 const isoString now.toLocaleString(sv-SE, { timeZone: timezone, hour12: false }); result isoString.replace( , T) Z; // 简单模拟ISO格式 } return { content: [ { type: text, text: result, }, ], }; } catch (error) { return { content: [ { type: text, text: 错误无效的时区 ${timezone} 或处理时间时出错。, }, ], isError: true, }; } }); // 5. 启动服务器使用stdio传输 async function main() { const transport new StdioServerTransport(); await server.connect(transport); console.error(MCP时间服务器已启动通过stdio); } main().catch((error) { console.error(服务器启动失败:, error); process.exit(1); });5.3 运行与测试首先运行你的服务器node server.js此时服务器会在后台运行通过stdin/stdout等待连接。接下来我们需要一个MCP客户端来测试它。最快捷的方式是使用一个现成的MCP调试工具比如modelcontextprotocol/inspector。先安装它npm install -g modelcontextprotocol/inspector然后在一个新的终端中运行检查器并连接到我们的服务器mcp-inspector node server.jsmcp-inspector会启动一个本地Web界面通常打开浏览器访问http://localhost:5173。在界面中你应该能看到你的服务器mcp-time-server已经连接并且列出了它提供的get_current_time工具。你可以在界面中直接调用这个工具输入参数如{timezone: Asia/Shanghai, format: human}然后观察返回的结果。5.4 关键步骤解析与避坑指南能力声明在创建Server对象时capabilities字段必须准确声明。这里我们只提供了tools所以我们的服务器只会响应工具相关的请求。如果你还想提供resources资源则需要在这里声明resources: {}。工具定义inputSchema必须是一个有效的JSON Schema对象。清晰的description对于模型理解工具用途至关重要。enum和default等约束能有效引导模型生成正确的参数。错误处理在CallToolRequestSchema的处理函数中务必做好错误捕获。返回结果时可以设置isError: true来告知客户端这是一个错误响应。生产环境中错误信息应更详细但避免泄露内部细节。传输层我们使用了StdioServerTransport这是最简单的本地通信方式。对于生产环境你可能需要实现HTTPServerTransport以便通过网络提供服务。实操心得在开发MCP服务器时一个常见的坑是工具定义inputSchema的description写得太模糊。模型完全依赖这个描述来理解工具。我的经验是描述要采用“动词开头宾语目的”的句式并举例说明参数。例如不要只写“获取时间”而是写成“获取指定时区的当前时间用于在回答中插入准确的时间戳。例如当用户问‘纽约现在几点’时使用此工具。”6. 进阶应用场景与生态展望MCP的价值在简单工具上可能不那么明显但在复杂、企业级的AI智能体生态中它的优势是决定性的。下面分享几个我看到的具有潜力的进阶场景。6.1 场景一企业知识库的智能网关很多公司都想用大模型连接内部知识库Confluence、Notion、Wiki等。用Function Calling做每个AI应用都要自己实现一套认证、搜索、解析的逻辑重复造轮子且权限混乱。用MCP的思路可以构建一个统一的“企业知识库MCP服务器”。这个服务器统一认证集成公司的SSO单点登录。统一搜索对接所有后台知识源提供统一的搜索接口。动态权限根据连接客户端的用户身份动态过滤搜索结果只返回该用户有权限查看的内容。资源抽象不仅提供搜索工具还可以将一篇具体的文档作为一个Resource。模型可以直接请求resources/read来获取文档的原始内容或摘要。这样无论是Slack中的AI助手、IDE里的编程副驾还是内部的管理系统都可以通过连接这同一个MCP服务器来安全地获取知识实现了能力的中心化和标准化。6.2 场景二复杂工作流的编排引擎假设有一个“市场活动分析”工作流从数据库拉取销售数据 - 调用Python脚本进行清洗分析 - 将结果生成图表 - 上传到云存储 - 发送带图表的邮件报告。用传统的AI调用你需要定义一个超级复杂的“run_marketing_analysis”函数或者让模型依次调用多个独立函数这要求模型具备很强的多步骤规划能力且中间状态管理困难。MCP可以优雅地解决这个问题。你可以创建一个“工作流编排MCP服务器”。这个服务器本身不执行具体任务但它宣告一个高级工具比如execute_workflow。当模型调用这个工具时服务器内部去协调执行一系列预定义好的子步骤这些子步骤本身可能也是通过调用其他MCP服务器完成的。对模型而言它只进行了一次简单的工具调用但背后完成了一个复杂流程。这降低了模型规划的负担并将复杂的业务逻辑封装在了可靠的服务器端。6.3 生态工具与未来趋势MCP的生态正在快速成长。除了前面提到的官方SDK和Inspector还有一些值得关注的工具和趋势Claude Desktop集成Anthropic的Claude桌面应用已经原生支持MCP。这意味着你可以轻松地将自定义的MCP服务器比如连接你公司内部系统的服务器配置到Claude中瞬间让你的个人Claude拥有处理公司内部事务的能力。服务器市场社区已经开始出现一些开源的通用MCP服务器例如用于文件系统访问、Git操作、网络搜索的服务器。未来可能会出现一个“MCP服务器市场”就像VSCode的插件市场一样让开发者可以轻松地为AI智能体添加新能力。协议扩展目前的MCP核心专注于工具和资源的“拉取”式交互。未来协议可能会扩展支持更复杂的交互模式比如服务器向客户端主动推送通知“订阅模式”或者支持多轮对话式的工具调用“会话式工具”。7. 迁移决策与常见问题排查如果你正在使用Function Calling是否应该立即迁移到MCP这取决于你的项目阶段和复杂度。7.1 什么情况下应该考虑MCP考虑因素建议工具数量与复杂度工具超过10个或单个工具参数结构复杂。MCP的动态加载和清晰分离优势明显。多模型支持需要同时对接GPT、Claude、Gemini等多个模型。MCP的统一协议能减少适配成本。安全与权限要求高需要对不同用户、不同会话进行精细的工具访问控制。MCP的服务器隔离和连接时鉴权是更优解。系统需要高可扩展性工具集需要频繁增减或更新希望实现“热插拔”。MCP的架构天生支持。项目处于早期设计阶段在新项目开始时就采用MCP比后期从Function Calling重构成本低得多。7.2 迁移路径建议对于已有Function Calling项目不建议“一刀切”重写。可以采取渐进式迁移识别边界从系统中识别出一个功能相对独立、边界清晰的模块比如“邮件通知模块”或“数据查询模块”。封装为MCP服务器将该模块的功能重构成一个MCP服务器。这个过程可以独立进行不影响主系统。双模式运行在AI应用中暂时同时支持旧的Function Calling和新的MCP连接。通过配置开关控制使用哪种方式。逐步替换将其他模块逐个重构为MCP服务器并在应用中切换连接。最终移除旧的Function Calling代码。7.3 常见问题与排查技巧在实际开发和集成MCP时你可能会遇到以下问题问题1客户端连接服务器失败报“初始化错误”。排查首先检查传输层。如果是stdio确保服务器进程正确启动且没有崩溃。检查服务器initialize处理函数是否按要求返回了正确的capabilities。使用mcp-inspector进行连接测试是最快的方法。问题2模型无法正确调用工具总是说“没有合适的工具”。排查这通常是工具定义description和inputSchema不够清晰导致的。站在模型的角度思考你的工具描述是否能让一个“外星人”明白在什么场景下使用它参数描述是否举例说明了格式使用mcp-inspector查看服务器宣告的工具列表审视其描述是否足够直白。问题3工具调用成功但返回结果模型无法理解或利用。排查MCP要求工具返回的内容是content数组通常包含type: text的文本。确保返回的文本是结构化的、信息丰富的。例如一个查询数据库的工具返回纯JSON字符串可能不如返回一段总结性的自然语言文本效果好。你可以尝试在返回内容中同时包含原始数据type: text格式化为代码块和自然语言总结。问题4权限控制逻辑应该写在哪里最佳实践权限控制应尽可能前置。连接层在MCP服务器启动或建立连接时通过令牌Token验证客户端身份拒绝非法连接。列表层在ListToolsRequest的处理函数中根据当前连接的身份返回不同的工具列表即只返回该身份有权限的工具。执行层在CallToolRequest的处理函数中进行最终的参数校验和业务权限判断。遵循“最小权限原则”在最早的可能环节进行拦截。MCP代表的是一种思维模式的转变从让模型适配我们杂乱无章的工具世界转向为我们工具世界建立一个模型可以理解的、标准化的“接口层”。它解决的不是一个具体的技术难题而是一个系统工程难题。对于追求长期可维护性、安全性和扩展性的AI应用来说投入时间理解并尝试MCP很可能在项目复杂度攀升时为你省下大量的重构成本和运维风险。
返回列表