news 2026/9/17 4:10:22

从零开发MCP Server:用Python实现AI Agent工具调用与知识库接入

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零开发MCP Server:用Python实现AI Agent工具调用与知识库接入

大概半年前,我还在为每一个 AI Agent 项目手写 Function Calling 的壳子:模型每说要调一个工具,我就得补一段解析逻辑;每换一个供应商,之前的适配代码基本作废。后来我把一个内部知识库工具改成了 MCP Server,接入 Claude、接入自研 Agent,几乎是一套代码到处用。今天这篇就算是一条龙记录:从 MCP 和 AI Agent 的基本理论,到怎么用 Python 从零开发一个可运行的 MCP Server,再接到真实客户端里跑通“让大模型替你调用工具”的完整链路。

这篇文章做的事很具体:讲清楚 MCP 协议到底解决了什么、为什么 AI Agent 开发离不开它;然后一步步实现一个带搜索、新增、阅读功能的本地知识库 MCP Server;最后教你调试、排错,以及往生产环境演进时要注意什么。适合正在做 AI Agent(无论你用 LangChain、LangGraph、Spring AI 还是自研框架)的开发者,也适合想把业务系统能力安全开放给大模型的团队。

1. MCP 到底是什么?AI Agent 开发者需要先想清楚的事

1.1 补课:AI Agent 的工具调用困局

想象一下,只靠大模型本身,它是没法去查你的数据库、发工单、改文件权限的。模型是“读上下文、预测输出”的机器,没有手也没有眼睛。为了让模型完成真实任务,OpenAI 最早设计了 Function Calling,让模型输出一个结构化的工具调用请求,再由程序执行。这确实解决了一部分问题,但很快大家发现,工具接入这件事被做成了“熔炉测试”:每个模型厂商一套工具声明格式,每个 Agent 框架又有一层自己的抽象,每接一个内部系统就得写一次适配层。更麻烦的是,同一个工具在新模型上可能需要重新定义提示词和参数描述,否则模型“不会用”。

我见过不少团队,Agent 代码里一半是工具适配器,另一半是解析不同模型返回结果的补丁。内部的搜索 API、工单系统、审批流,每个都要一份单独的 schema 解析,测试成本成倍上涨。这就是 MCP 出现之前最常见的工具调用困局:不是模型不够强,而是工具接入的“物理链路”太乱。

1.2 MCP 的核心架构与工作原理

MCP 的全称是 Model Context Protocol,翻译过来就是“模型上下文协议”。它的思路很简单:把工具、数据、提示词都变成可以动态发现和调用的资源,用一份统一的协议把它们暴露给大模型应用。类比一下最直观:传统做法是给每个外设都焊一根专用线,鼠标口、键盘口、打印机口各不兼容;MCP 想做的是 USB-C,所有设备都用同一个接口标准,插上就能认。

角色上分三层。Host 是大模型应用,比如 Claude Desktop、IDE 插件、自研 Agent;Client 是 Host 内部负责跟单个 Server 通信的模块;Server 暴露具体能力。协议底层是 JSON-RPC 2.0,消息有明确的生命周期。连接建立后先initialize,双方交换能力信息;接着客户端会主动list_toolslist_resourceslist_prompts,把能力列表拉到本地;真正要用时再发一个call_tool请求。整个过程对模型透明,模型只需要按照工具名称和参数 JSON 去“请求”,具体怎么执行是 Server 的事。

传输上最常见的是 stdio,也就是 Host 直接拉起一个子进程跑 Server,简单、安全、适合本机;跨机器部署则用 Streamable HTTP 或 SSE,本质上就是把 MCP 消息包装成 HTTP 请求。无论哪种传输,MCP 的核心能力域是一致的:Tools 是可执行操作,Resources 是只读上下文,Prompts 是提示词模板,Sampling 和 Roots 则是更进阶的能力,日常开发前三个就够用了。

1.3 MCP 能给 AI Agent 带来什么

MCP 最实际的价值是解耦。拿同一个 MCP Server 来说,接入 Claude Desktop 和接入自研的 Agent 框架,配置完全不同,但 Server 代码不用改。只要对方实现了 MCP Client,工具描述、参数类型、输出格式都是“自带说明书”的。这等于把工具接入从“定制开发”降级成了即插即用。

第二是动态能力发现。MCP Server 在启动时告诉客户端自己有哪些工具、资源、提示词模板,客户端基于这份清单决定让模型去调用什么。你新加一个工具,旧客户端只要重新加载就能看到,不需要升级部署。第三是上下文控制。工具返回的内容由 Server 决定,可以只回摘要、状态码,避免把整个数据库倒进 Prompt 里。最后是权限边界,Server 可以严格限制只暴露白名单操作,大模型再聪明也没法调用没暴露的方法。

但它也不是万能的。MCP 适合有一定工具复杂度、需要跨客户端复用的场景;如果只是给 Prompt 塞一两段静态知识,完全用不到 MCP。这个判断标准后面实操部分还会再强调。

2. 动手前先把方案定下来:设计一个知识库 MCP Server

2.1 选语言和 SDK:Python 还是 TypeScript

MCP 官方提供 Python 和 TypeScript SDK,体验已经很成熟。选哪个主要看团队技术栈。如果是 Java 后端团队,也有 Spring AI 等封装方式,但需要留意版本更新节奏;我更推荐先从官方 SDK 跑通逻辑,再考虑框架集成。

维度Python SDKTypeScript SDK
上手成本低,FastMCP 封装非常友好中等,类型定义更严格
适合场景数据类工具、机器学习服务、内部脚本前端生态、Node 服务、CLI 工具
部署方式pip 包,要求 Python 环境npm 包,Node 环境
周边生态文档全面,Inspector 支持好文档全面,Playground 常用

对于本文示例,我选 Python,因为它代码量最少,几乎不需要样板代码,适合把注意力放在理解协议本身。你不需要担心 Python 版本,3.9 以上都行。

2.2 拆解业务需求:这个 Server 到底提供哪些工具

先把需求讲清楚。我们要做的“知识库 MCP Server”,解决的是两种高频场景:一是让 Agent 在回答之前先检索团队积累的文档、笔记,把检索结果作为上下文补充进回复;二是让 Agent 能把新想法“存”回知识库,形成闭环。围绕这两个场景,我设计了三个工具和一个资源、一个提示词模板。

名称类型说明输入参数输出
search_docstool检索与关键词匹配的文档片段query: string, top_k?: intMarkdown 文本
add_notetool新增一条知识库笔记title: string, content: string保存成功提示
recent_notesresource返回最近保存的笔记标题列表Markdown 列表
summarize_noteprompt生成“总结笔记”的提示词模板title: string文本 Prompt

这里资源是只读的,工具是可执行的。Agent 在调用前会先通过能力列表发现它们,不需要我们额外写说明文件。之所以把资源单独拆出来,是为了让模型在不需要执行副作用的情况下,也能拿到“最近有哪些笔记”这类轻量信息。

2.3 接口设计里容易踩的坑

设计工具接口时最容易犯的错误是“只写工具名,不写工具描述”。模型靠描述判断什么时候该调用,含糊的描述会让它在最该调的时候不调,在最不该调的时候乱调。我一般把描述写成“返回给谁看、输入是什么、输出是什么”三句话,比如 search_docs 的描述是:“在本地知识库中检索与 query 相关的文档片段,返回 Markdown 格式的结果,用于回答问题时补充事实依据。”

第二个坑是参数类型和必填约束不明确。MCP 走 JSON-RPC,参数本质上是一个 JSON 对象,Agent 会根据 schema 构造参数。如果可空字段没标好,调用时就会缺参或者多参。尽量给每个参数写一个 example,Agent 见过示例后成功率会高很多。第三个坑是错误信息不能太“人类化”。内部抛出的异常要转成可以被 Agent 理解的错误描述,比如“数据库连接失败,请稍后重试”,而不是一长串 traceback,否则模型会把这堆日志当成正常回复的一部分,很影响后续判断。

3. 实战:用 FastMCP 半小时做一个可用的 Server

3.1 环境准备与项目初始化

先准备虚拟环境,我习惯用 venv:

python -m venv .venv source .venv/bin/activate # Windows 下是 .venv\Scripts\activate pip install "mcp[cli]"

装好之后,你会在 bin 里拿到 mcp 命令。这个命令除了启动服务,还能调用官方 Inspector 打开调试面板,非常有用。项目结构不用复杂:

knowledge-base-server/ ├── .venv/ ├── server.py └── data/ └── notes.json

我给示例项目准备了一个 notes.json,存两条种子数据,方便测试时能搜到结果。

3.2 实现工具、资源和提示词

在 server.py 里写核心逻辑:

# server.py import json from pathlib import Path from mcp.server.fastmcp import FastMCP DATA_FILE = Path(__file__).parent / "data" / "notes.json" mcp = FastMCP("knowledge-base") def _load_notes() -> dict: if DATA_FILE.exists(): return json.loads(DATA_FILE.read_text(encoding="utf-8")) return {} def _save_notes(notes: dict) -> None: DATA_FILE.write_text(json.dumps(notes, ensure_ascii=False, indent=2), encoding="utf-8") @mcp.tool() def search_docs(query: str, top_k: int = 3) -> str: """在本地知识库中检索与query相关的文档片段,返回Markdown格式的结果,用于回答问题时补充事实依据。""" notes = _load_notes() query_lower = query.lower() hits = [title for title, content in notes.items() if query_lower in title.lower() or query_lower in content.lower()] hits = hits[:top_k] if not hits: return "没有找到相关内容,请尝试更换关键词。" return "\n\n".join([f"### {title}\n\n{notes[title]}" for title in hits]) @mcp.tool() def add_note(title: str, content: str) -> str: """保存一条新的知识库笔记,title为完整标题,content为笔记正文,返回保存状态。""" notes = _load_notes() notes[title] = content _save_notes(notes) return f"已保存笔记:{title}" @mcp.resource("notes://recent") def recent_notes() -> str: """返回最近保存的笔记标题列表,按保存顺序倒序展示。""" notes = _load_notes() if not notes: return "当前知识库为空。" return "\n".join([f"- {title}" for title in list(notes)[-10:]]) @mcp.prompt() def summarize_note(title: str) -> str: """生成一个用于总结指定笔记的提示词模板。""" notes = _load_notes() content = notes.get(title, "未找到该笔记") return f"请阅读以下笔记并给出三句话总结和三件事项建议:\n\n标题:{title}\n\n内容:{content}" if __name__ == "__main__": mcp.run()

这段代码有几个细节值得展开。第一,所有读写都走 JSON 文件,避免引入外部依赖;真实场景可以替换成 SQLite 或向量数据库。第二,FastMCP 会根据函数签名自动生成工具 schema,包括参数类型、描述和必填项,所以代码里一定要写类型标注和 docstring。第三,@mcp.resource("notes://recent")这个 URI 是给客户端读取用的,Agent 可以通过资源接口直接拿到“最近笔记”列表;而 prompt 模板则是给模型预置提示词,让模型知道该怎么答、怎么总结。

工具只暴露了两个,但已经覆盖“查”和“存”两个核心动作。你可以照着这个模式继续加工具,比如删除记录、更新标签、导入文件等。MCP 本身不限制工具数量,但建议一个 Server 保持内聚,一个领域一个 Server,别把所有功能堆在一起。

3.3 本地启动与命令行自测

写完先别急着接前端,直接在终端启动:

python server.py

正常会看到一段日志,提示 MCP server running。到这一步,已经算把最小的 MCP Server 跑起来了。但怎么验证它能不能被 Agent 正确调用?我推荐用官方 Inspector:

mcp dev server.py

这个命令会拉起一个本地调试面板,你可以在里面看到所有已注册的 tools/resources/prompts,还能直接模拟调用 search_docs。我每次写完新工具都会先用它做一次冒烟测试,确认 schema 描述足够清晰,再交给客户端。

如果不想开浏览器,也可以写一段 Python 脚本直接发起调用,下一节会给出最小 Client 的写法。这里补充一个容易忽略的点:MCP Server 在 stdio 模式下启动后,只从标准输入读、往标准输出写,所以不要在代码里随便 print 日志,这会污染协议数据。想要更详细的运行日志,请使用 logging 模块输出到 stderr,很多客户端也默认看 stderr 的日志来定位问题。

4. 把 MCP Server 接进 Claude Desktop 和其他 Agent 客户端

4.1 配置 Claude Desktop:三分钟跑通

Claude Desktop 当前支持在配置文件里声明 MCP Server。以 macOS 为例,配置文件一般在~/Library/Application Support/Claude/claude_desktop_config.json,Windows 则在%APPDATA%\Claude\claude_desktop_config.json。打开后加入:

{ "mcpServers": { "knowledge-base": { "command": "python", "args": ["/absolute/path/to/server.py"] } } }

这里必须填绝对路径,尤其是python命令,如果系统里有多个 Python 环境,建议直接写虚拟环境里的解释器路径,例如/path/to/.venv/bin/python,否则 Claude 可能找不到依赖包。配置保存后,完全退出 Claude 再重新打开,左下角工具图标里就能看到 knowledge-base 下面的工具列表了。

一个小技巧:配置成功后,先在对话框里直接问一句“帮我搜索最近的知识库笔记”,它会自动调用 recent_notes 资源或 search_docs 工具。如果报错,打开 Claude 的日志文件,它会同时记录启动 MCP Server 时 stderr 的输出,很多问题都在这里直接体现。

4.2 用 MCP Client SDK 写一个最小调用端

不只有商业客户端可以用,自己写 Agent 也可以直接消费 MCP Server。官方 Python SDK 里提供了 ClientSession,下面是一个最小示例:

# client.py import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): server_params = StdioServerParameters(command="python", args=["server.py"]) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() print("发现工具:", [t.name for t in tools]) if any(t.name == "search_docs" for t in tools): result = await session.call_tool("search_docs", {"query": "MCP"}) print(result) if __name__ == "__main__": asyncio.run(main())

这段代码里,stdio_client 负责把 MCP Server 作为子进程拉起,并建立标准输入输出管道;ClientSession 封装了 JSON-RPC 消息的发送和接收。跑通这个脚本,说明你的 MCP Server 对任何 MCP Client 都可用,而不是只能被 Claude 识别。真正做 Agent 时,你只需要把 list_tools 的结果转成大模型能读的工具列表,把 call_tool 的返回作为工具结果回传给模型,就能实现“Agent 自主调用工具”的闭环。

4.3 远程部署与 HTTP 模式要点

如果知识库要放在服务器上,让公司多台电脑的 Agent 都能访问,就得切换到 HTTP 模式。FastMCP 也直接支持:

if __name__ == "__main__": mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)

这样会暴露一个 HTTP 端点,客户端在配置里填上 URL 即可。但远程暴露要注意两点:一是必须加认证,最简单的方案是在服务层校验 Bearer Token,或者让 MCP Server 处于内网,只允许公司网络访问;二是要处理超时配置,Agent 调用工具可能要持续几秒,不要把超时设得太短。如果走的是外网,强烈建议用 HTTPS 加密传输,避免敏感数据明文散落。

5. 常见问题与排查技巧实录

5.1 连接类问题速查表

我整理了实际调试 MCP Server 过程中最容易遇到的几个问题,按“现象-原因-解法”列在下面:

现象可能原因处理建议
客户端看不到任何工具服务启动失败,或 schema 生成异常先用mcp dev server.py看错误日志
工具列表存在但调用报错参数 schema 与实际函数签名不匹配检查工具定义是否缺少类型标注或 docstring
调用一直卡住不返回工具内部同步阻塞,或超时时间太短耗时的 IO 改成异步,或调大客户端超时时间
返回内容被模型理解错返回格式太复杂尽量返回 Markdown 或纯文本,限制长度
端口启动失败端口被占用换端口,用lsof -i:端口查占用进程
配置 Claude 后不生效未完全退出重启,或配置路径不对强制退出 Claude 重开,检查日志文件

表格之外还要说一个重要原则:MCP Client 不会帮模型做“拼装”,模型看到的工具描述和返回内容完全来自 Server,所以 Server 的返回要尽量“自解释”。比如返回“没有找到相关内容”时,最好顺带补一句“请尝试更换关键词或查看最近笔记”,模型就会接着做下一步。

5.2 我在调试中踩过的土坑

第一个坑是 Python 环境的绝对路径。我有一次配置 Claude Desktop 时 command 写成了python,但那个终端会话里激活的是某虚拟环境,Claude 子进程却用的是系统 Python,结果找不到所有依赖。后来统一写成.venv/bin/python的绝对路径,问题立刻消失。

第二个坑是工具描述太简短。早期我只写“搜索笔记”,模型在回答简单问题时偶尔会跳过工具、直接编答案;把描述改完整后,调用准确率高了很多。第三个坑是误用 print 调试。前面提过,stdio 模式下所有 stdout 都算作协议数据,一次调试时我在工具里 print 了一行日志,导致客户端无法解析后续 JSON-RPC 消息,表现就是工具调用返回空。排查半天才想到是 print 的锅,改用 logging 到 stderr 后就没再犯过。

这些“土办法”看着不起眼,但能帮你省一晚上。另外一个建议:在正式交付前,一定要用两个不同客户端各测一遍,很多“只能在我的 Agent 里用”的 Server,换到其他客户端就暴露了过度依赖某个 Host 内部行为的隐藏问题。

6. 进阶:从单 Server 到多 Agent 生产级实践

6.1 多 Server 和多 Agent 如何共存

实践中一个 AI Agent 经常要同时使用多个 MCP Server,比如一个是知识库,一个是设计稿读取,一个负责统计数据。MCP Host 在配置层面天然支持多 Server,每个 Server 有独立的命名空间,Client 会分别连接,工具列表也按来源分开。这个设计很关键:不同 Server 之间不会互相污染命名,同一个工具名在不同 Server 里也可以同时存在。

自研 Agent 里,我推荐在发起模型请求前把多个 Server 的 tools 合并成一个列表,但给每个工具名前加上 Server 前缀,或者显式记录工具来源,提示词里说明“哪个工具属于哪个服务”,避免模型选错。多 Agent 场景也类似。你完全可以让“数据分析 Agent”和“文档整理 Agent”共享同一个 MCP Server,也可以给每个 Agent 配各自的 Server。MCP 不是 Multi-Agent 框架,但它是 Agent 之间共享能力的底层协议,配合 LangGraph 或自研编排时,MCP Server 就是每个 Agent 的“手和脚”。

垂直领域里,Figma MCP 把设计稿数据结构化暴露给 Agent,Blender MCP 让模型能操作 3D 建模,这些案例的思路都一样:把专业工具封装成标准能力,再交给大模型调度。你的业务系统只要想清楚要暴露哪些能力,完全可以照着这个模式接入。

6.2 生产环境还需要的三件事:认证、观测、版本管理

本地调试可以不管安全,但生产环境把 MCP Server 开放给多个 Agent 时,至少要补三块。

第一是认证与授权。HTTP 传输模式下必须校验调用方身份,工具粒度上做白名单;在很多公司里,“允许 Agent 读知识库”和“允许 Agent 改知识库”是两码事,最好是工具级权限。第二是可观测性。每一次工具调用都要有日志,记录调用方、时间、参数、返回码,最好加上耗时,否则出了错根本没法复盘。第三是版本管理。MCP Server 本身也是一个软件,工具签名和行为变更应该走版本发布流程;客户端初始化时会做能力协商,但为了兼容老客户端,建议工具接口保持向后兼容,非必要不删除已有工具。

我见过不少团队直接把本地 demo 推到服务器上,结果没有认证日志,一个幻觉生成的 Agent 请求把测试数据全改掉了。所以生产化不是功能做完再说的事,而是从设计接口那天就要考虑进去。

6.3 什么时候不要用 MCP

最后说点冷静话。MCP 不是所有工具接线的银弹,简单的场景滥用反而增加复杂度。比如你只是给某个固定 Prompt 塞一段当前时间、一条静态说明,用常规的上下文模板注入就够了,不用跑一个独立进程;如果只有一个客户端使用、并且未来也不会复用,写一个普通函数调用可能是更省事的选择。MCP 的收益在“一次实现、多处复用”,只有当这个条件成立时才值得为每个能力单独建 Server。

我的习惯是:先看这个工具是否需要被不同 Host 复用,是否含有比较复杂的输入输出结构,是否希望由 Server 控制返回给模型的上下文。只要命中两条,再动手写 MCP Server;否则继续走轻量封装,没必要为了追概念而加一层协议。等你真的做过一两个 Server,再回头看 MCP 协议里的那些设计细节,会发现每一个约定都在为“稳定、可复用、可运维”服务。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 4:08:58

MATLAB二维频谱分析实战:从fft2到f-k谱与相速度提取

简介:面向需要分析二维波形数据的Matlab使用者,如地震、雷达、超声波等复杂信号处理场景,这份rar压缩包提供了一套从频谱基础到频率-波数谱、能量谱绘制的完整讲解与可直接运行的示例代码,重点解决如何将时间域或空间域信号转换到…

作者头像 李华
网站建设 2026/9/17 4:04:49

击穿电压与电气强度怎么分?绝缘检测术语辨析及测试仪选型指南

做绝缘检测这些年,我见过太多因为术语没对齐而翻车的场合。报告评审会上,专家问“你这写的是击穿电压还是击穿强度?单位是kV还是kV/mm?”采购沟通时,甲方要一台“介电强度测试仪”,供应商却坚持说“我们只有…

作者头像 李华
网站建设 2026/9/17 4:04:17

Windows F1-F12功能键:默认行为、冲突排查与改键方案

装机这些年,被问到频率最高的问题里,“F1 到 F12 到底有什么用”绝对能排进前三。上周帮朋友调一台新入手的紧凑布局机械键盘,他连着抛了三个问题:为什么 F 区一按就变成音量加减、为什么 F5 不刷新网页、为什么 F12 弹出来的是浏…

作者头像 李华
网站建设 2026/9/17 4:02:25

大模型私有化部署实战:从选型到落地,LLM如何重构企业研发流程

把大模型“搬进”公司:我们的研发部,正在被 LLM 重新定义去年年底,我们研发部做了一次“豪赌”——把大模型(LLM)真正接到自己的业务线里来,而不是继续当一个只会聊天的玩具。从前端的代码补全,…

作者头像 李华