Claude插件开发实战:从Function Calling到MCP协议接入
发布时间:2026/9/29 18:21:47来源:尧图网络
最近在折腾 Claude 周边生态的时候我翻到一个叫claude-plugins-official的项目顺手把claude-plugins相关的资料系统理了一遍。这个事比我想象的要有意思得多——很多人以为 Claude 的插件就是官方内置那几板斧实际上围绕模型能力扩展出来的插件体系已经能覆盖从个人效率工具到企业级数据接入的一整条链路。这篇文章不打算做成文档翻译我想从一个实际撸过代码、踩过坑的开发者视角把 Claude 插件生态的玩法、开发思路和落地细节一次讲清楚。无论你是想给 Claude 加一个自定义工具还是想理解 MCP 协议到底怎么帮你接外部服务这篇文章应该都能给你一个比较完整的参照。1. Claude插件生态全景解析1.1 插件体系的本质给模型装上一双手要理解 Claude 的插件体系得先跳出插件功能模块这种传统认知。Claude 本身是一个大语言模型它擅长的东西是理解、推理和生成文本但它天生不擅长两件事一是获取实时数据二是执行具体操作。插件这套东西本质上就是给模型装上一双手和一双眼睛让它能把对话能力延伸到真实世界。我用一个比较直白的类比把 Claude 想象成一个知识渊博但没办法动手的顾问。你问他帮我查一下这个月的销售数据他能听懂你的需求但没有数据库的连接权限也拿不到实时数据。这时候插件就相当于给顾问配了一个助理助理手里有数据库的钥匙、搜索工具、各种服务的 API 接口——顾问负责思考助理负责跑腿两者配合才能把一件事干完。具体到 Claude 的生态里插件能力分好几层。第一层是官方内置工具比如网页搜索、代码执行这种开箱即用的能力第二层是 MCP 协议接入的外部服务模型可以动态发现并使用这些服务暴露的工具第三层是开发者通过 Function Calling 机制自定义的 API 函数。这三层模式解决了不同类型的问题但对于大多数人来说最需要搞明白的就是后两层——因为这才是能让你真正按需扩展的地方。1.2 四类插件形态与选型建议根据插件的能力来源和接入方式我把目前生态里的插件大致分成四类每类的适用场景和开发成本差别很大。插件形态能力来源适合场景开发成本典型例子官方内置工具Anthropic 服务端提供通用检索、代码执行、文件分析零成本直接用Web Search、Code InterpreterMCP 协议插件任意 MCP Server 暴露的工具集合需要接入自建系统或第三方服务中写一个 MCP Server数据库查询服务、GitHub 操作、文件系统访问Function Calling 自定义函数开发者自己实现的 HTTP 接口轻量级、单一功能的工具调用低写一个 API 再加参数声明天气查询、消息推送、内部 API 封装客户端插件模块基于 Claude 的 IDE 或 CLI 工具扩展本地开发环境和 Dev Tools 场景低到中取决于客户端插件 APIClaude Code plugin、IDE 扩展、自动化脚本工具选型的时候我给一个务实建议如果你只是想在个人项目里给 Claude 增加一个查询或操作能力优先考虑 Function Calling成本最低、调试最直观。如果你要接多个服务、或者希望工具能被多个客户端复用那直接上 MCP 协议——这是目前标准化程度最高也是社区最活跃的方向。至于官方内置工具属于基础设施级的能用就多利用不用重复造轮子。一个常见的误区是有人以为插件一定要写复杂的后台服务。实际情况是一个插件既可以是你写的一个 300 行代码的 Flask 服务也可以是一个极简的 API 包装层。核心在于你怎么向模型描述这个工具、怎么设计它的输入输出边界。这个点我会在下一节详细展开。2. 插件开发前的架构设计2.1 从一个核心问题开始插件到底在解决什么动手写代码之前我最推荐先想清楚一个问题这个插件要解决的问题边界是什么。这个问题听起来很虚但实际上是决定整套方案设计的第一步也是后来踩不踩坑的分水岭。我见过很多翻车的案例——需求描述模糊工具设计得又大又全结果模型根本不知道该在什么场景下调用它。举个具体的例子你想给 Claude 加一个查询公司内部知识库的插件表面看很简单但仔细拆解就有很多分支——知识库是结构化数据库还是文档索引查询条件是关键词匹配还是语义检索返回结果是全文还是摘要单条还是列表不同的答案会导向完全不同的工具接口设计。如果返回的是全文接口的设计就是输入query输出content如果是语义检索那输入可能还要包含embedding向量或检索条数参数。边界越清楚接口越容易被模型正确调用。我习惯用一个简单的三问法来收敛需求这个工具完成什么动作动词它需要什么输入信息才能决策名词它返回什么结构的数据后续怎么被模型使用结构这三个问题回答了工具的基本骨架就出来了。在实际的插件项目里这一步花的时间往往比写代码还长但非常值得。2.2 JSON Schema模型与插件之间的通用语言Claude 的 Function Calling 机制里工具与模型之间通过一个描述文件来沟通这个描述文件就是 JSON Schema。它决定了模型能不能正确理解你的工具、能不能生成有效的调用参数。这块的细节直接关系到调用成功率。看一下一个基本示例{ name: query_sales_data, description: 根据日期范围查询销售数据返回每日销售额汇总, input_schema: { type: object, properties: { start_date: { type: string, description: 开始日期格式YYYY-MM-DD }, end_date: { type: string, description: 结束日期格式YYYY-MM-DD }, region: { type: string, enum: [north, south, east, west], description: 销售区域 } }, required: [start_date, end_date] } }看起来简单但几个细节体会一下。description 字段不要写得太泛尽量包含模型判断调用时机所需的全部信息。比如region字段里用 enum 列出可选值模型就能在不确定的时候直接从列表里挑而不是瞎猜。required 字段不要把所有参数都设成必填——如果一个参数确实有默认值或可选性设置成 optional 反而能提高模型的调用灵活度。另外很重要的一点工具名称要能见名知义。模型看到query_sales_data基本就能猜到这工具是干嘛的但如果叫util_1或者my_endpoint模型大概率不知道什么时候该调用它。命名和描述决定了模型会不会主动用你做的工具。2.3 安全与鉴权设计插件接入真实系统和数据之后安全就是绕不开的问题。很多开发者第一次做 Claude 插件想的是先用起来再说结果直接把 API key 写在了接口逻辑里或者完全没有权限校验。这个坑我踩过一次之后就长记性了。最稳妥的做法是遵循最小权限原则API key 或访问令牌放在环境变量不进代码仓库工具只暴露确实需要的接口和数据范围不把整个系统的访问入口透传出来涉及外部 API 调用时在插件内部做超时和重试处理避免拖慢模型的整个响应链路对用户的输入参数做基础校验防止恶意或异常的输入打到下游系统。以我常用的做法为例如果是 Function Calling 模式实际执行工具的后端服务与模型层之间通常走内部网络我会额外加一层网关对工具的调用方做白名单校验。如果是 MCP 协议接入则利用 MCP Server 的鉴权机制来管理连接凭证。安全设计不需要多复杂但一定要有。插件越强大越要确保它不能被旁路滥用——这是我给所有做插件的朋友的第一条建议。3. 从零开发一个可运行的Claude插件3.1 环境准备与项目初始化写一个能跑的 Claude 插件整体链路并不长。核心是三个部分后端服务、工具描述、调用逻辑。我这里用 Python 搭一个最小实现目标做一个待办事项管理工具能让 Claude 帮你添加任务、查询任务列表。先说明一下为什么选待办事项这个例子。它有明显的状态变化增、查有明确的输入结构后端逻辑足够简单适合把整套调用链跑通。等这个框架跑通了换任何真实的业务接口只需要替换后端逻辑和 Schema 即可。环境准备需要的东西不多Python 3.10顺手装fastapi、uvicorn、anthropic这几个库一个 Anthropic API Key环境变量里配好ANTHROPIC_API_KEYClaude 的模型 API开发阶段用claude-sonnet或claude-haiku就够没必要上最高配项目结构我习惯这样组织todo-plugin/ ├── server.py # 后端服务FastAPI ├── tools.py # 工具定义JSON Schema ├── agent.py # 调用 Claude 并处理工具调用 ├── .env.example # 环境变量模板 └── requirements.txt这种拆法把接口实现工具描述模型调用三者分开改其中一个不容易影响另外两个。等插件规模变大再加一层 service 层管理更复杂的业务逻辑。3.2 核心实现待办事项管理工具先写后端服务。这里用 FastAPI 起一个轻量服务暴露两个接口新增待办和查询待办列表。# server.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel from typing import List, Optional import datetime app FastAPI() # 内存存储真实场景替换为数据库即可 todos {} class TodoItem(BaseModel): title: str priority: str medium due_date: Optional[str] None class TodoQuery(BaseModel): status: Optional[str] None priority: Optional[str] None app.post(/todo/add) def add_todo(item: TodoItem): todo_id f{len(todos) 1} todos[todo_id] { id: todo_id, title: item.title, priority: item.priority, due_date: item.due_date, status: pending, created_at: datetime.datetime.now().isoformat() } return {success: True, todo: todos[todo_id]} app.get(/todo/list) def list_todos(query: TodoQuery None): items list(todos.values()) if query and query.status: items [t for t in items if t[status] query.status] if query and query.priority: items [t for t in items if t[priority] query.priority] return {todos: items}注意我这里用一个内存字典存数据重启就没了。真实项目中换成 SQLite、Postgres 或者任何你已有的存储系统逻辑完全一样。接下来是工具定义。每个工具需要明确 name、description、input_schema 三要素# tools.py ADD_TODO_TOOL { name: add_todo, description: 添加一条新的待办事项返回创建后的待办信息, input_schema: { type: object, properties: { title: {type: string, description: 待办事项的内容描述}, priority: {type: string, enum: [high, medium, low], description: 优先级}, due_date: {type: string, description: 截止日期格式YYYY-MM-DD可为空} }, required: [title] } } LIST_TODO_TOOL { name: list_todos, description: 查询待办事项列表可按状态或优先级筛选, input_schema: { type: object, properties: { status: {type: string, enum: [pending, completed], description: 待办状态}, priority: {type: string, enum: [high, medium, low], description: 优先级} } } }这一步是整个插件的翻译层——把你的接口能力以模型能理解的语言描述出来。描述里写待办事项的内容描述和任务内容差别不大但前者更能让模型生成贴合用户原意的参数。3.3 本地调试思路写完代码并不是直接连 Claude 测试我习惯先做一个本地冒烟测试。用 Postman 或 curl 直接调后端接口确认服务本身没问题curl -X POST http://localhost:8000/todo/add \ -H Content-Type: application/json \ -d {title: 测试任务, priority: high}看返回结果是正常 JSON 再进下一步。这个小习惯能省很多事——很多插件联调半天发现不是模型调用逻辑的问题而是后端接口本身有 bug 或参数名不一致。接下来才到模型联调环节写一个简单的 agent 脚本测试整个链路。这部分我在下一节详细展开。4. 插件接入Claude与组合调用4.1 通过API声明工具并处理调用链路Claude 的 Function Calling 流程是一个带循环的过程。先看代码再解释机理# agent.py import os import json from anthropic import Anthropic from tools import ADD_TODO_TOOL, LIST_TODO_TOOL import requests client Anthropic(api_keyos.environ[ANTHROPIC_API_KEY]) TOOLS [ADD_TODO_TOOL, LIST_TODO_TOOL] def call_tool(name, arguments): 实际执行工具的后端逻辑 if name add_todo: resp requests.post(http://localhost:8000/todo/add, jsonarguments) return resp.json() elif name list_todos: resp requests.get(http://localhost:8000/todo/list, paramsarguments) return resp.json() raise ValueError(fUnknown tool: {name}) def chat_with_tools(user_message): messages [{role: user, content: user_message}] while True: response client.messages.create( modelclaude-sonnet-4-20250514, max_tokens1024, toolsTOOLS, messagesmessages ) # 判断模型是否要求调用工具 stop_reason response.stop_reason if stop_reason tool_use: tool_results [] for content_block in response.content: if content_block.type tool_use: tool_name content_block.name tool_args content_block.input print(f[Calling] {tool_name}({json.dumps(tool_args)})) result call_tool(tool_name, tool_args) tool_results.append({ type: tool_result, tool_use_id: content_block.id, content: json.dumps(result), }) # 把模型的工具调用结果追加进对话上下文继续循环 messages.append({ role: assistant, content: response.content }) messages.append({ role: user, content: tool_results }) else: # 没有工具调用需求返回模型最终回复 final_text .join( block.text for block in response.content if block.type text ) return final_text if __name__ __main__: print(chat_with_tools(帮我添加两个任务买牛奶高优先级写周报然后列出所有任务))这段代码的核心逻辑是一个循环我稍微解释一下这个流程。模型接收你的消息后会做一次推测这个问题需要调用工具吗如果不需要直接返回自然语言回复如果需要它会返回一个tool_use类型的响应块里面包含工具名和一组它认为合适的参数。注意这里是模型生成参数不是你的代码去匹配参数——这也是为什么 JSON Schema 的描述那么重要。你的代码要做的事是看到tool_use的响应之后去执行对应工具并把结果以tool_result的格式追加回消息列表。追加完成之后再次调用模型让模型看到工具执行的结果继续判断是结束还是再调下一轮工具。整个循环可以理解成一个对话过程模型问我该调工具了你的程序说好我调完了结果在这儿模型看到结果后说好的那我给你生成最终回答。多工具组合在这个循环里天然支持——模型可以一次请求里发起多个tool_use你逐个执行后统一返回结果它再整合生成回复。4.2 多个插件协同的工作流单个工具和多个工具的差别不只是数量上的它会引发一个质变模型可以从一个只知道单一动作的执行者变成一个能编排多步骤任务的协调者。拿上一节那个场景来说当 Claude 同时拥有添加待办和查询列表两个工具时它拿到用户那句帮我添加两个任务再列出所有任务的指令后会自己规划执行序列先连续调用两次 add_todo再调用一次 list_todos拿到结果后整理成一份清晰的中文回复。这个串联过程不需要你在代码层面写死而是模型根据用户意图和工具能力自主决策的。这就带来了一个工作流设计的重要思路与其做一个大一统的综合助手工具不如把能力拆分成一组单一职责的小工具让模型自己编排。原因很简单——工具拆得越小每个工具的参数描述越清晰模型越容易理解什么时候该用哪个。反过来的话一个大工具塞了十几种参数模型反而容易犯迷糊。在我的实际项目里一个典型的知识库助手工作流是这样的search_docs关键词检索文档返回匹配片段列表get_doc_detail根据文档 ID 取完整正文summarize_text对文本做摘要这是模型自带能力但用工具方式包装会更可控send_email把整理好的内容发到指定邮箱四个工具各管一段模型负责把它们串成完整的用户旅程。这比我最初做的一个知识库全功能接口稳定得多也更容易调试。4.3 在主流客户端中使用 MCP 插件Claude Code 等除了自己开发工具接入 API现在更流行、更省事的做法是通过 MCP 协议直接把现成服务接进来。Claude 生态里很多客户端已经内置了 MCP 支持常见的包括 Claude Code 命令行工具和一些第三方桌面客户端。MCP 的好处在于标准化你启动一个 MCP Server比如一个提供文件系统访问的 server客户端通过mcpServers配置自动发现它能提供的所有工具模型就能在对话中直接使用这些能力。一份典型的 MCP 配置长这样{ mcpServers: { filesystem: { command: npx, args: [-y, modelcontextprotocol/server-filesystem, /path/to/allowed/dir] }, github: { command: npx, args: [-y, modelcontextprotocol/server-github], env: { GITHUB_PERSONAL_TOKEN: your_token } } } }配置好之后在支持的客户端里直接跟模型说把我的项目文件列出来它就会通过 filesystem server 获取目录内容不需要你额外编写任何胶水代码。我个人的体会是MCP 的生态价值在于它把接入方式从你自定义的 JSON Schema 参数描述提升到了协议级别的标准化。一套 MCP Server可以被多种支持 MCP 的客户端复用。这也意味着社区里已有的 MCP Server 你直接就能接不需要从零开发。5. 常见问题排查与避坑实录5.1 高频报错速查表开发和使用 Claude 插件的过程中我攒了不少血泪教训。整理一份高频问题速查表每一条都是实际踩过的。问题现象根本原因解决思路模型从不调用你的工具description 写得太泛模型不知道何时使用重写 description明确触发场景和使用条件工具调用了但参数总是错的JSON Schema 里的属性描述含糊补充每个字段的格式说明能用 enum 就用 enum循环调用工具停不下来工具返回的内容和模型预期不一致检查 tool_result 的 content 是否是结构清晰、能被 parse 的文本HTTP 接口报 404 或 500工具名或路由拼写不一致先 curl 单测接口再检查 agent 里的路由拼接模型重复调用同一个工具返回结果中缺少状态变化信息在 tool_result 里明确返回已执行标志和变更后的数据结构token 消耗异常高循环里反复追加长上下文精简工具返回内容只保留模型真正需要的字段MCP Server 连不上环境变量未注入或权限不足先用mcp dev或命令行直接启动 Server 验证这里每个问题背后都涉及模型与工具的信息对齐。以模型从不调用你的工具为例很多时候不是代码有 bug而是模型无法从你的描述里推断出调用时机——比如描述里只写了查询数据但没写什么情况下需要这个数据。把这句改成当用户询问销售统计、订单数量或客户留存等指标时使用本工具查询数据库返回数据模型就很容易做出判断了。5.2 关于工具调用失败的三条独家心得除了表格里那些问题我再分享三条可能只有实际做过多个插件项目才会有的心得。第一条是关于返回格式的克制。很多人做个工具就忍不住把数据库里所有字段都带回去想着反正模型能处理。实际效果很差——上下文一长模型反而迷失在冗余信息里生成质量明显下降。我现在遵循的原则是工具返回给模型的永远只是模型完成任务所需要的最小信息集。比如列出待办返回 id、title、priority、status 四个字段足够至于 created_at 这种字段除非用户明确要否则不进上下文。第二条是关于循环保护的。工具调用循环里有两种情况容易宕住一种是工具本身抛异常了模型还反复尝试另一种是模型认为一次调用没达到效果不断重试。我现在的统一处理方式是任何工具调用都包一层 try-except并返回结构化的错误信息 建议下一步。这样模型收到异常后不会盲试而是会向用户解释情况或者换个问法。第三条是关于测试数据的重要性。开发插件阶段一定要准备好一套边界测试数据比如空列表、超长文本、非法日期格式、缺字段的请求。这些看起来不起眼但模型是生成式系统它随时可能产出你预期之外的参数组合。后端接口如果对边界情况不宽容一次 500 错误就会把整个对话链路打断。5.3 一条落地的调试链路建议最后给一个我自己目前最顺手的调试链路。不要直接对着 Claude 联调那样报错信息在模型和服务端之间往返很难定位问题。先做后端自测用 curl 或 Postman 覆盖主要路径和边界路径然后写一个小的模拟脚本直接调用call_tool函数传固定的参数验证工具执行逻辑确认没问题之后再进到 agent 循环去做模型联调。联调阶段也建议从单工具开始。把工具列表只配一个list_todos让模型问几个问题确认它能在该用的时候调用、不该用的时候不用然后再把add_todo加进来测多轮编排。每加一个工具就重新验证一遍——这个过程看起来慢实际上是最快的排查方式。注意Claude 插件开发里有一个容易忽视的细节——模型的工具调用参数是由模型生成的不是固定的。因此后端接口的参数接收需要宽松一些做好类型转换和默认值处理。我见过太多接口写得很严格模型一传参数就报 TypeError的案例了。我个人在实际操作中最深的体会是Claude 插件开发的瓶颈通常不在代码而在描述和编排。工具的描述能不能让模型在正确的时机做出调用接口返回的结构能不能让模型顺利生成最终回答这两件事决定了插件的体验上限。你花在 JSON Schema 描述和返回结构设计上的每一个小时都能十倍折抵后面的联调时间。动手能力永远是第一步。建议先照着这篇文章的框架把那个待办事项工具跑通再加一两个自己工作里真正需要的小功能。链路走通之后你会发现对AI 如何与系统协作的理解会上一个台阶——到那时候再去研究 MCP 协议或者更复杂的工作流编排就有底了。
网站建设高端定制企业官网