Python MCP 工具开发入门:Server、Client 和 LLM 集成

Python MCP 工具开发入门:Server、Client 和 LLM 集成

1. 从零开始:如何用 Python 创建你的第一个 MCP(Model Context Protocol)

1.1 什么是 MCP?

Model Context Protocol (MCP) 是一个标准化协议,允许应用程序与大语言模型(LLM)进行安全、结构化的交互。通过 MCP,你可以:

  • 为 LLM 提供自定义工具和资源
  • 实现 LLM 和外部系统的无缝集成
  • 构建可复用的、模块化的 AI 应用

1.2 核心概念

1.2.1 MCP Server(服务器)

定义工具、资源和提示词,通过 stdio 或其他传输方式提供给客户端。

1.2.2 MCP Client(客户端)

连接到 MCP 服务器,获取工具列表,调用工具,并与 LLM 集成。

1.2.3 Tools(工具)

服务器暴露给 LLM 的可调用函数,LLM 可以根据用户需求调用这些工具。

1.3 项目结构

hello-world/ ├── server.py # MCP 服务器定义 ├── client-deepseek.py # 使用 Deepseek LLM 的客户端 ├── client.py # 基础客户端 ├── pyproject.toml # 项目配置 └── uv.lock # 依赖锁定文件 

2. 第一步:创建 MCP 服务器

2.1 安装依赖

0

cd hello-world UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ uv sync

主要依赖:

  • mcp[cli]>=1.6.0 - MCP 框架
  • openai>=1.75.0 - OpenAI 兼容的 LLM 客户端
  • python-dotenv>=1.1.0 - 环境变量管理

2.2 编写 Server 端代码

创建 server.py

from mcp.server.fastmcp import FastMCP # 创建一个 MCP 服务器实例 mcp = FastMCP("Demo")# 定义一个工具:两数相加@mcp.tool()defadd(a:int, b:int)->int:"""Add two numbers"""return a + b # 定义一个资源:个性化问候@mcp.resource("greeting://{name}")defget_greeting(name:str)->str:"""Get a personalized greeting"""returnf"Hello, {name}!"# 启动服务器if __name__ =="__main__": mcp.run("stdio")

核心概念解析:

  • FastMCP - 简化的 MCP 服务器框架
  • @mcp.tool() - 装饰器定义工具函数
  • @mcp.resource() - 装饰器定义资源(带 URI 模式)
  • mcp.run(“stdio”) - 通过标准输入输出运行服务器

3. 第二步:创建 MCP 客户端

3.1 基础客户端(无 LLM)

client.py 展示了如何直接调用工具:

import sys import asyncio from mcp import ClientSession from mcp.client.stdio import stdio_client, StdioServerParameters asyncdefmain():# 1. 启动 MCP 服务器进程 server_script ="server.py" params = StdioServerParameters( command=sys.executable, args=[server_script],) transport = stdio_client(params) stdio, write =await transport.__aenter__()# 2. 建立客户端会话 session =await ClientSession(stdio, write).__aenter__()await session.initialize()# 3. 调用工具 result =await session.call_tool("add",{"a":3,"b":5})print(f"3 + 5 = {result}")# 4. 关闭连接await session.__aexit__(None,None,None)await transport.__aexit__(None,None,None)if __name__ =="__main__": asyncio.run(main())

3.2 与 LLM 集成的客户端

client-deepseek.py 展示了如何让 LLM 自动调用工具:

import sys import asyncio import os import json from mcp import ClientSession from mcp.client.stdio import stdio_client, StdioServerParameters from openai import OpenAI from dotenv import load_dotenv load_dotenv()asyncdefmain():# 1. 连接到 MCP 服务器print(">>> 初始化加法 LLM 工具客户端") server_script ="server.py" params = StdioServerParameters( command=sys.executable, args=[server_script],) transport = stdio_client(params) stdio, write =await transport.__aenter__() session =await ClientSession(stdio, write).__aenter__()await session.initialize()print(">>> 连接到MCP服务器成功")# 2. 初始化 LLM 客户端(使用通义千问) client = OpenAI( api_key=os.getenv("QWEN_API_KEY"), base_url=os.getenv("QWEN_BASE_URL"))# 3. 从 MCP 服务器获取工具列表 resp =await session.list_tools() tools =[{"type":"function","function":{"name": tool.name,"description": tool.description,"parameters": tool.inputSchema }}for tool in resp.tools]print("可用工具:",[t["function"]["name"]for t in tools])# 4. 主交互循环whileTrue:print("\n请输入你的加法问题(如:5加7是多少?或'退出'):") user_input =input("> ")if user_input.strip().lower()=='退出':breakprint(f"\n📝 用户问题: {user_input}")# 构造对话 messages =[{"role":"system","content":"你是一个加法助手,遇到加法问题请调用工具add,最后用自然语言回答用户。"},{"role":"user","content": user_input}]# 5. LLM 与工具调用循环 iteration =0whileTrue: iteration +=1print(f"\n🔄 第 {iteration} 次 LLM 调用...")# 调用 LLM response = client.chat.completions.create( model="qwen-plus", messages=messages, tools=tools, tool_choice="auto") message = response.choices[0].message messages.append(message)# 检查是否有工具调用ifnot message.tool_calls:print(f"\n✅ LLM 最终回答:")print(f"\nAI 回答:\n {message.content}")break# 6. 执行工具调用for tool_call in message.tool_calls: args = json.loads(tool_call.function.arguments)print(f"\n🔧 调用工具: {tool_call.function.name}")print(f"📥 工具参数: {args}") result =await session.call_tool(tool_call.function.name, args)print(f"📤 工具返回结果: {result}")# 将工具结果加入对话历史 messages.append({"role":"tool","content":str(result),"tool_call_id": tool_call.id})await session.__aexit__(None,None,None)await transport.__aexit__(None,None,None)print(">>> 客户端已关闭")if __name__ =="__main__": asyncio.run(main())

4. 第三步:配置环境变量

创建或修改 .env 文件:

# 通义千问 API 配置(使用阿里云 DashScope) QWEN_API_KEY=your-api-key-here QWEN_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 

5. 第四步:运行程序

5.1 方式一:两个终端分别运行

终端 1:启动服务器

cd 01-hello-world UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ uv run python server.py 

终端 2:运行客户端

cd 01-hello-world UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ \QWEN_API_KEY="your-api-key"\QWEN_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"\ uv run python client-deepseek.py 

5.2 方式二:非交互式测试

cd 01-hello-world timeout60bash -c 'echo -e "15加8等于多少?\n退出" | \ UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ \ QWEN_API_KEY="your-api-key" \ QWEN_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1" \ uv run python client-deepseek.py'

6. 执行流程示例

当用户输入 “15加8等于多少?” 时:

📝 用户问题: 15加8等于多少? 🔄 第 1 次 LLM 调用... 🔧 调用工具: add 📥 工具参数: {'a': 15, 'b': 8} 📤 工具返回结果: meta=None content=[TextContent(type='text', text='23', annotations=None)] isError=False 🔄 第 2 次 LLM 调用... ✅ LLM 最终回答: AI 回答: 15加8等于23。 

7. 关键要点

7.1 异步编程

MCP 使用 asyncio,所有网络操作都是异步的:

asyncdefmain():# 异步操作await session.initialize() result =await session.call_tool(...)

7.2 工具定义

简单易用的装饰器风格:

@mcp.tool()defmy_tool(param1:int, param2:str)->str:"""Tool description"""returnf"Result: {param1}{param2}"

7.3 工具调用链

LLM 根据需要多次调用工具,直到得到最终答案:

用户输入 → LLM 分析 → 调用工具 → 获得结果 → LLM 再次分析 → 最终回答 

7.4 消息历史

保持对话历史以便 LLM 理解上下文:

messages =[{"role":"system","content":"..."},{"role":"user","content":"..."},{"role":"assistant","content":"..."},{"role":"tool","content":"..."},]

8. 扩展应用

8.1 添加更多工具

@mcp.tool()defmultiply(a:int, b:int)->int:"""Multiply two numbers"""return a * b @mcp.tool()defdivide(a:float, b:float)->float:"""Divide two numbers"""if b ==0:raise ValueError("Cannot divide by zero")return a / b 

8.2 添加资源

@mcp.resource("user://{user_id}")defget_user_info(user_id:str)->str:"""Get user information"""returnf"User {user_id} information"
8.2.1 什么是资源(Resource)?

@mcp.resource() 装饰器用于定义一个可以根据参数返回不同数据的接口。资源是不同于工具的数据取…

资源 vs 工具的对比:

特性Resource(资源)Tool(工具)
用途提供只读或结构化的数据执行操作或计算
调用方式read_resource("uri://path")call_tool("name", args)
参数传递URI 路径参数函数参数
使用场景获取文件、查询数据库计算、修改数据
8.2.2 URI 模式详解
@mcp.resource("greeting://{name}")defget_greeting(name:str)->str:"""Get a personalized greeting"""returnf"Hello, {name}!"

语法分解:

部分含义说明
@mcp.resource()资源装饰器定义资源的标记
"greeting://"资源协议(Scheme)user://file://api://
{name}动态参数占位符类似路由参数,接收不同值
get_greeting(name: str)处理函数参数名必须与 URI 占位符一致

工作流程:

客户端请求 greeting://Alice ↓ MCP 框架识别 URI 模式 ↓ 提取参数 name = "Alice" ↓ 调用函数 get_greeting("Alice") ↓ 返回 "Hello, Alice!" 给客户端 
8.2.3 常见资源示例

示例 1:用户信息资源

@mcp.resource("user://{user_id}")defget_user_info(user_id:str)->str: users ={"1":"Alice","2":"Bob"}return users.get(user_id,"Not found")# 客户端调用:await session.read_resource("user://1")# 返回:Alice

示例 2:文件资源(多参数)

@mcp.resource("file://{folder}/{filename}")defread_file(folder:str, filename:str)->str:# 注意:参数名必须与 URI 中的占位符一致 path =f"{folder}/{filename}"try:withopen(path,'r')as f:return f.read()except FileNotFoundError:return"File not found"# 客户端调用:await session.read_resource("file://docs/readme.txt")# 返回:文件内容

示例 3:API 文档资源

@mcp.resource("docs://api/{version}")defget_api_docs(version:str)->str: docs ={"v1":"API v1: 支持基础功能","v2":"API v2: 新增高级功能"}return docs.get(version,"Version not found")

示例 4:多参数资源

@mcp.resource("product://{category}/{product_id}")defget_product(category:str, product_id:str)->str:# 访问歩骤:product://electronics/12345returnf"Product {product_id} from {category}"
8.2.4 客户端中使用资源
# 方法 1:直接读取资源 result =await session.read_resource("greeting://Alice")print(result)# 输出: Hello, Alice!# 方法 2:列出所有资源 resources =await session.list_resources()for resource in resources.resources:print(f"Resource: {resource.uri} - {resource.description}")
8.2.5 资源 URI 命名最佳实践

✅ 好的设计:

@mcp.resource("user://{user_id}")# 清晨的协议名@mcp.resource("file://{path}/{filename}")# 多参数路径@mcp.resource("docs://api/{version}")# 泊根路径

❌ 避免的做法:

@mcp.resource("res://{id}")# ❌ 不清楚的协议名@mcp.resource("user://{user_id}")defget_user(uid:str):# ❌ 参数名不匹配!pass

8.3 自定义 System Prompt

system_prompt =""" 你是一个数学助手。 当用户问到加法、减法、乘法、除法时,请调用相应的工具。 最后用清晰的中文回答用户的问题。 """ messages =[{"role":"system","content": system_prompt},{"role":"user","content": user_input}]

9. 常见问题

9.1 Q1: 如何调试 MCP 服务器?

使用日志输出:

import logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__)@mcp.tool()defmy_tool(x:int): logger.info(f"Tool called with x={x}")return x *2

9.2 Q2: 如何处理工具调用错误?

try: result =await session.call_tool(tool_name, args)except Exception as e:print(f"Tool call failed: {e}")# 错误恢复逻辑

9.3 Q3: 如何支持多轮对话?

保持 messages 列表,每次交互都添加新消息:

whileTrue: user_input =input("> ") messages.append({"role":"user","content": user_input})# ... 处理响应,添加到 messages ...

10. 性能优化建议

  1. 连接复用 - 不要频繁创建/销毁连接
  2. 超时设置 - 为 LLM 调用设置合理超时
  3. 错误重试 - 实现指数退避重试机制
  4. 日志级别 - 生产环境减少日志输出

11. 总结

通过 MCP,你可以轻松构建强大的 AI 应用,让 LLM 能够访问和使用自定义工具。核心步骤是:

  1. 定义工具(Server)
  2. 连接到服务器(Client)
  3. 让 LLM 自动调用工具
  4. 处理工具结果并返回给用户

希望这个教程能帮助你开始 MCP 的学习之旅!

12. 参考资源

Read more

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

摘要:本文聚焦OpenClaw从测试环境走向生产环境的核心痛点,围绕“性能优化、安全加固、监控运维”三大维度展开实操讲解。先明确生产环境硬件/系统选型标准,再通过硬件层资源管控、模型调度策略、缓存优化等手段提升响应速度(实测响应效率提升50%+);接着从网络、权限、数据三层构建安全防护体系,集成火山引擎安全方案拦截高危操作;最后落地TenacitOS可视化监控与Prometheus告警体系,配套完整故障排查清单和虚拟实战案例。全文所有配置、代码均经实测验证,兼顾新手入门实操性和进阶读者的生产级部署需求,帮助开发者真正实现OpenClaw从“能用”到“放心用”的跨越。 优质专栏欢迎订阅! 【DeepSeek深度应用】【Python高阶开发:AI自动化与数据工程实战】【YOLOv11工业级实战】 【机器视觉:C# + HALCON】【大模型微调实战:平民级微调技术全解】 【人工智能之深度学习】【AI 赋能:Python 人工智能应用实战】【数字孪生与仿真技术实战指南】 【AI工程化落地与YOLOv8/v9实战】【C#工业上位机高级应用:高并发通信+性能优化】 【Java生产级避坑指南:

By Ne0inhk
ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

🎬 渡水无言:个人主页渡水无言 ❄专栏传送门: 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》 ❄专栏传送门: 《freertos专栏》《STM32 HAL库专栏》 ⭐️流水不争先,争的是滔滔不绝  📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生 | 省级优秀毕业生获得者 | ZEEKLOG新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生 在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连 目录 前言  一、实验基础说明 1.1、互斥体简介 1.2 本次实验设计思路 二、硬件原理分析(看过之前博客的可以忽略) 三、实验程序编写 3.1 互斥体 LED 驱动代码(mutex.c) 3.2.1、设备结构体定义(28-39

By Ne0inhk
Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 后端工程师扔给你一个 Swagger (OpenAPI) 文档地址,你会怎么做? 1. 对着文档,手写 Dart Model 类(容易写错字段类型)。 2. 手写 Retrofit/Dio 的 API 接口定义(容易拼错 URL)。 3. 当后端修改了字段名,你对着报错修半天。 这是重复劳动的地狱。 swagger_dart_code_generator 可以将 Swagger (JSON/YAML) 文件直接转换为高质量的 Dart 代码,包括: * Model 类:支持 json_serializable,带 fromJson/

By Ne0inhk
Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

文章目录 * 前言 * make/makefile * 文件的三个时间 * Linux第一个小程序-进度条 * 回车和换行 * 缓冲区 * 程序的代码展示 * git指令 * 关于gitee * Linux调试器-gdb使用 * 作业部分 前言 做 Linux 开发时,你是不是也遇到过这些 “卡脖子” 时刻?写 makefile 时,明明语法没错却报错,最后发现是依赖方法行没加 Tab;想提交代码到 gitee,记不清 git add/commit/push 的 “三板斧”,还得反复搜教程;用 gdb 调试程序,输了命令没反应,才想起编译时没加-g生成 debug 版本;甚至连写个进度条,都搞不懂\r和\n的区别,导致进度条乱跳…… 其实这些问题,

By Ne0inhk