概念
自己的理解:原来给大模型使用的外部方法需要自己一个一个写,现在Mcp规范了调用外部方法和提供方法的协议,你的Mcp客户端可以调用别人已经写好的外部方法(比如魔塔社区里面Mcp广场中提供的方法),也可以自己写一个方法供他人使用
UV
- uv 是一个高性能的 Python 包管理工具,并且提供虚拟环境管理功能
安装UV
shell
pip install uv
创建虚拟环境,并初始化工作目录
shell
# 在新目录中创建虚拟环境并初始化
uv venv myenv # 会在当前目录生成myenv目录,并创建虚拟环境
uv init myenv
# 在已有目录中创建虚拟环境并初始化
cd myenv
uv venv # 会将当前目录作为虚拟环境目录,并创建基础文件
uv init
激活(进入)虚拟环境
shell
# 在目录下执行
source .venv/bin/activate #mac/linux
.venv\Scripts\activate #windows
# 此时在终端中应该显示
(myenv) myenv>
退出虚拟环境
powershell
(myenv) myenv> deactivate
虚拟环境目录及文件结构
| 文件/文件夹 | 作用 |
|---|---|
.git/ |
Git 版本控制目录 |
.venv/ |
虚拟环境 |
.gitignore |
Git 忽略规则 |
.python-version |
Python版本声明 |
main.py |
主程序入口 |
pyproject.toml |
项目配置文件 |
README.md |
项目说明文档 |
在虚拟环境下安装插件使用uv add
powershell
uv add mcp python-dotenv openai
按照习惯创建存放代码的主目录src,在src下创建客户端代码文件和服务器端代码文件
客户端
代码结合了官网示例及教程,修改了两个地方,1. 可以保存历史对话记录,2. 如果需要多个工具配合处理则会循环调用大模型,直到大模型不需要再调用工具
安装所需插件
powershell
uv add mcp python-dotenv openai
powershell
import asyncio
import os
import sys
import json
from typing import Optional
from contextlib import AsyncExitStack
from openai import OpenAI
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from dotenv import load_dotenv
load_dotenv() # 从 .env 加载环境变量
class MCPClient:
def __init__(self):
# 初始化会话和客户端对象
self.session: Optional[ClientSession] = None
self.exit_stack = AsyncExitStack()
self.model = os.getenv("DEEPSEEK_MODEL_CHAT")
self.client = OpenAI(
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url=os.getenv("DEEPSEEK_BASE_URL"),
)
self.chat_history = [
{
"role": "system",
"content": (
"你是一个只能通过工具完成任务的助手。"
"请根据工具返回的结果,判断是否需要继续调用工具。"
"如果已经获取足够信息,请直接回答用户问题。"
),
}
]
async def connect_to_server(self, server_script_path: str):
"""连接到 MCP 服务器,获取服务器中可用的工具列表"""
is_python = server_script_path.endswith(".py")
is_js = server_script_path.endswith(".js")
if not (is_python or is_js):
raise ValueError("服务器脚本路径必须是 Python 或 JavaScript 文件")
command = "python" if is_python else "node"
server_params = StdioServerParameters(
command=command, args=[server_script_path], env=None
)
# 创建会话
stdio_transport = await self.exit_stack.enter_async_context(
stdio_client(server_params)
)
self.stdio, self.write = stdio_transport
self.session = await self.exit_stack.enter_async_context(
ClientSession(self.stdio, self.write)
)
await self.session.initialize()
response = await self.session.list_tools()
tools = response.tools
# print("\n已连接到服务器的可用工具:", [tool.name for tool in tools])
async def process_query(self, query: str) -> str:
"""处理用户查询,支持多轮工具调用"""
self.chat_history.append({"role": "user", "content": query})
response = await self.session.list_tools()
available_tools = [
{
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.inputSchema,
},
}
for tool in response.tools
]
# print("从服务器端获取到的工具列表:", available_tools)
# 循环处理工具调用,直到模型返回最终答案
while True:
# 调用大模型获取决策
response = self.client.chat.completions.create(
model=self.model,
messages=self.chat_history,
tools=available_tools,
tool_choice="auto",
)
# print("大模型返回的内容:", response)
content = response.choices[0]
# 如果不需要调用工具,直接返回结果
if content.finish_reason != "tool_calls":
final_response = content.message.content
self.chat_history.append({
"role": "assistant",
"content": final_response
})
return final_response
# 处理工具调用
tool_call = content.message.tool_calls[0]
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.function.arguments)
# 调用工具并获取结果
result = await self.session.call_tool(tool_name, tool_args)
print(f"调用工具: {tool_name},参数: {tool_args},返回结果: {result.content[0].text}")
tool_result = result.content[0].text
# print(f"工具返回结果: {tool_result}")
# 将工具调用记录和结果加入对话历史
self.chat_history.append(content.message.model_dump())
self.chat_history.append({
"role": "tool",
"content": tool_result,
"tool_call_id": tool_call.id,
})
async def chat_loop(self):
print("\n🤖 欢迎来到 MCP 聊天循环!输入 'exit' 退出。")
while True:
try:
query = input("\n你: ").strip()
if query.lower() in ["exit", "quit"]:
print("\n🤖 再见!")
break
response = await self.process_query(query)
print("\n🤖:", response)
except Exception as e:
print("\n🤖 发生错误:", e)
async def cleanup(self):
"""清理会话和客户端对象"""
await self.exit_stack.aclose()
async def main():
if len(sys.argv) < 2:
print("用法: python client.py <服务器脚本路径>")
sys.exit(1)
client = MCPClient()
try:
await client.connect_to_server(sys.argv[1])
await client.chat_loop()
finally:
await client.cleanup()
if __name__ == "__main__":
asyncio.run(main())
服务端
1. 代码
代码结合了官网与教程
powershell
import json
import httpx
import os
from mcp.server.fastmcp import FastMCP
from dotenv import load_dotenv
load_dotenv() # 从 .env 加载环境变量
mcp = FastMCP("weatherServer")
API_KEY = os.getenv("WEATHER_API_KEY")
WEATHER_API_URL = "https://api.seniverse.com/v3/weather/daily.json"
async def fetch_weather(loc: str) -> str:
"""
获取指定位置的天气信息
params:
loc: 城市名称的拼音,例如:查询"北京"则需传入"beijing"
"""
params = {
"key":API_KEY,
"location":loc,
"language":"zh-Hans",
"unit": "c",
"start": "0",
"days": "5",
}
async with httpx.AsyncClient() as client:
response = await client.get(WEATHER_API_URL, params=params,timeout=10)
response.raise_for_status()
return response.json()
def format_weather(data) -> str:
"""
格式化天气数据为易读的字符串格式
params:
data: 天气数据字典
"""
if isinstance(data, str):
data = json.loads(data)
if isinstance(data, dict):
results = data.get("results", [])
if results:
location = results[0].get("location", {})
daily = results[0].get("daily", [])
if daily:
return f"城市: {location.get('name', '未知')}\n" + "\n".join(
[
f"日期: {day.get('date', '未知')}, 天气: {day.get('text_day', '未知')}, 温度: {day.get('high', '未知')}°C"
for day in daily
]
)
return "无法格式化天气数据"
@mcp.tool()
async def get_weather(loc: str) -> str:
"""
获取指定城市的天气信息
params:
loc: 城市名称的拼音,例如:查询"北京"则需传入"beijing"
"""
return format_weather(await fetch_weather(loc))
def main():
mcp.run(transport='stdio')
if __name__ == "__main__":
main()
2. 打包
1. 完善项目
目录结构如下图:
text
mcp-server-weather/ ← 项目根目录
├── src/ ← ✅ 所有业务代码都集中在这里
│ └── mcp_server_weather/ ← 服务端代码目录
│ └── __init__.py
│ └── __main__.py
│ └── server.py ← 服务端代码
├── .env ← 存放key等
├── .venv/ ← 虚拟环境(本地依赖隔离)
├── .git/ ← Git 管理
├── pyproject.toml ← 项目配置
├── README.md ← 说明文件
├── .python-version ← Python版本声明
├── .gitignore ← Git 忽略规则
└── uv.lock ← uv 的依赖锁文件
init.py
python
from .server import main
main.py
python
from weather import main
main()
修改pyproject.toml
python
[build-system]
requires = ["setuptools>=61.0", "wheel"] # 使用 `setuptools` 来构建项目,依赖 `wheel`,构建成 `.whl` 包
build-backend = "setuptools.build_meta"
[project]
name = "weather-mcp" # 自定义该服务的名称
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"anthropic>=0.71.0",
"mcp>=1.19.0",
"openai>=2.6.1",
"python-dotenv>=1.2.1",
]
[project.scripts]
# 1. 会在 .venv/bin 下创建可执行文件:weather-mcp
# 2. weather-mcp 文件内容:导入模块 mcp_server_weather.server 并调用其中的 main() 函数
weather-mcp = "mcp_server_weather.server:main"
[tool.setuptools]
package-dir = {"" = "src"} # 项目代码都在 src 目录下
[tool.setuptools.packages.find]
where = ["src"] # 要打包的目录,目录中需包含 __init__.py 文件
2. 打包
安装build并打包
powershell
(mcp-server-weather) mcp-server-weather ➤ uv pip install build
(mcp-server-weather) mcp-server-weather ➤ uv run -m build
启动Mcp服务端
1. 在Cherry Stdio使用

参数配置:
与命令uv拼接后其实就是:uv --directory <要执行文件或命令的绝对路径> 命令
powershell
--directory
/Users/yuelei/learn/ai/code/mcp/mcp-server-weather/src/mcp_server_weather
run
server.py
- 运行weather-mcp
powershell
--directory
/Users/yuelei/learn/ai/code/mcp/mcp-server-weather/.venv/bin
run
weather-mcp
在对话中使用mcp


2. 在终端中使用自定义的客户端和服务端
这种方式使用Mcp服务时,启动命令中服务端必须跟在客户端后面,服务端必须为js或py类型文件
uv run <路径>客户端 <路径>服务端
shell
(mcp-server-weather) mcp_server_weather ➤ uv run client.py server.py
