Mcp 基础

官网开发文档

概念

自己的理解:原来给大模型使用的外部方法需要自己一个一个写,现在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 <要执行文件或命令的绝对路径> 命令

  1. 直接运行server.py
powershell 复制代码
--directory
/Users/yuelei/learn/ai/code/mcp/mcp-server-weather/src/mcp_server_weather
run
server.py
  1. 运行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       
相关推荐
大模型真好玩2 天前
LangGraph实战项目:从零手搓DeepResearch(四)——OpenDeepResearch源码解析与本地部署
人工智能·agent·mcp
昭昭日月明2 天前
🚀 告别手动调试,Chrome DevTools MCP 推荐
ai编程·mcp
扫地僧20212 天前
ASP.NET Core WebApi 集成 MCP 协议完全指南
mcp
后端小肥肠3 天前
效率狂飙!n8n 无人值守工作流,每天自动把领域最新热点做成小红书卡片存本地
人工智能·agent·mcp
程序员辉哥6 天前
在Cursor中通过SSH MCP运维自己的服务器
ssh·cursor·mcp
后端小肥肠7 天前
【n8n入门系列】输入抖音分享链接,3步自动提无水印视频 + 文案,小白也能上手!
agent·deepseek·mcp
beyond阿亮7 天前
nacos支持MCP Server注册与发现
java·python·ai·nacos·mcp
todoitbo7 天前
我用 TRAE 做了一个不一样的 MySQL MCP
数据库·mysql·adb·ai工具·mcp·trae·mysql-mcp
大模型真好玩7 天前
低代码Agent开发框架使用指南(五)—Coze消息卡片详解
人工智能·coze·mcp