MCP实战-基于Ollama+qwen2.5以sse方式实现MCP协议工具调用

目录

0 环境准备

1 开发环境准备

1.1 创建项目python环境

1.2 在pycharm创建项目

1.3 安装项目依赖

2 资源准备

2.1 访问openweather

2.2 进入api key页面

2.3 生成apikey

3  程序逻辑实现

3.1 服务端逻辑实现

3.1.1 新建sse_weather_server.py文件

3.1.2 导入相关依赖包

3.1.3 初始化MCP服务

3.1.4  定义openweather配置

3.1.5 定义获取城市天气方法

3.1.6 定义格式话天气数据方法

3.1.7 注册MCP工具

3.1.8 定义sse服务接口

3.1.9 定义main方法

3.2 客户端逻辑实现

3.2.1 新建sse_weather_client文件

3.2.2 导入相关依赖包

3.2.3 加载ollama配置

3.2.4 定义配置文件

3.2.5 定义客户端处理类

3.2.5.1 定义初始化方法

3.2.5.2 定义连接至服务器方法

3.2.5.3 定义查询方法

3.2.5.4 定义问答方法

3.2.5.5 定义连接关闭方法

3.2.6 定义主流程方法

3.2.7 定义main方法

4 测试验证

4.1 运行server方法

4.1.1 控制台输出

4.2 运行Client方法

4.2.1 控制台输出

4.3 输入问题

4.4 查询回答结果

5 完整代码

5.1 服务端完整代码

5.2 客户端完整代码

附录

项目结构

问题一:exceptiongroup.ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)

问题描述

解决办法

问题二:Error in post_writer: Client error '405 Method Not Allowed' for url 'http://127.0.0.1:18081/messages/?session_id=33c15a50103a421b9ee6de59f7a391ca'

问题描述

解决办法


0 环境准备

  • ollama已部署推理模型qwen:7b(deepseek目前不支持function calling)
  • 已安装miniconda环境
  • 具备科学上网条件

1 开发环境准备


1.1 创建项目python环境

        通过conda命令创建项目的python开发环境

 conda create -n mcp_demo python=3.10

1.2 在pycharm创建项目

  • 解释器类型:选择自定义环境
  • 环境:选择现有
  • 类型:选择conda
  • 环境:选择上一步创建的环境

1.3 安装项目依赖

 pip install mcp openai python-dotenv 

2 资源准备

        本项目中需要调用天气接口获取城市的天气信息,因此需要注册天气API网站,并且获取调用key。

2.1 访问openweather

Current weather and forecast - OpenWeatherMap

点击以上连接,使用邮箱注册账号,过程简单,此处不做赘述。

2.2 进入api key页面

2.3 生成apikey

        按上图所示,在create key列中输入learn,然后点击generate按钮,就会生成一个名字为learn的key,后续使用就是这个key。如下图:

3  程序逻辑实现

         mcp是c/s架构,客户端和服务端是一一对应的。因此在实现基于MCP协议工具调用时,要分别实现客户端和服务端。

3.1 服务端逻辑实现

3.1.1 新建sse_weather_server.py文件

3.1.2 导入相关依赖包

import argparse
import json

import httpx
import uvicorn
from mcp.server import FastMCP, Server
from mcp.server.sse import SseServerTransport
from starlette.applications import Starlette
from starlette.routing import Route, Mount

3.1.3 初始化MCP服务

mcp = FastMCP("sse_WeatherServer")

3.1.4  定义openweather配置

OPENWEATHER_API_KEY = "5082d49dxxxx51163cb"
OPENWEATHER_BASE_URL = "https://api.openweathermap.org/data/2.5/weather"
USER_AGENT = "weather-app/1.0"

注意将APIkey替换为自己申请的key。

3.1.5 定义获取城市天气方法

async def get_weather(city):
    """
    从OpenWeather API 获取天气信息
    :param city: 城市名称(需要试用英文,如 beijing)
    :return: 天气数据字典;若发生错误,返回包含error信息的字典
    """
    params = {
        "q": city,
        "appid": OPENWEATHER_API_KEY,
        "units": "metric",
        "lang": "zh_cn",
    }
    headers = {"User-Agent": USER_AGENT}

    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(OPENWEATHER_BASE_URL, params=params, headers=headers, timeout=30)
            response.raise_for_status()
            return response.json()
        except httpx.HTTPStatusError as e:
            return {"error": f"HTTP请求错误:{e}"}
        except Exception as e:
            return {"error": f"发生错误:{e}"}

3.1.6 定义格式话天气数据方法

def format_weather_data(data):
    """
    格式化天气数据
    :param data: 天气数据字典
    :return: 格式化后的字符串;若发生错误,返回包含error信息的字符串
    """

    #  如果传入的是字符串,则先转换成字典
    if isinstance(data, str):
        data = json.loads(data)

    if "error" in data:
        return data["error"]
    weather = data["weather"][0]["description"]
    temperature = data["main"]["temp"]
    city = data["name"]
    country = data["sys"]["country"]
    humidity = data["main"]["humidity"]
    wind = data["wind"]["speed"]

    return f"城市:{city}, {country}\n天气:{weather}\n温度:{temperature}°C\n湿度:{humidity}%\n风速:{wind}m/s"

3.1.7 注册MCP工具


@mcp.tool()
async def get_weather_tool(city: str):
    """
    获取城市的天气信息
    :param city: 城市名称(需要试用英文,如 beijing)
    :return: 天气数据字典;若发生错误,返回包含error信息的字典
    """
    weather_data = await get_weather(city)
    return format_weather_data(weather_data)

3.1.8 定义sse服务接口


def create_starlette_app(mcp_server: Server, *, debug: bool = False):
    """创建 Starlette 应用能通过sse提供mcp服务"""
    sse = SseServerTransport("/messages/")

    async def handle_sse(request):
        async with sse.connect_sse(
            request.scope,
            request.receive,
            request._send,
        ) as (read_stream,write_stream):
            await mcp_server.run(
                read_stream,
                write_stream,
                mcp_server.create_initialization_options(),
            )

    return Starlette(
        debug=debug,
        routes=[
            Route("/sse", endpoint=handle_sse),
            Mount("/messages/", app=sse.handle_post_message),
        ],
    )

3.1.9 定义main方法

if __name__ == "__main__":
    mcp_server = mcp._mcp_server

    parser = argparse.ArgumentParser(description='Run MCP SSE-based server')
    parser.add_argument("--host", default="0.0.0.0", help="MCP server host")
    parser.add_argument("--port", default=18081, type=int, help="MCP server port")
    args = parser.parse_args()

    starlette_app = create_starlette_app(mcp_server, debug=True)
    uvicorn.run(starlette_a
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值