跳转至

使用python开发mcp server

上一篇文章介绍了UV工具的使用,有了uv 的基础,本篇我们来看一下如何使用python开发mcp server,目前主流的 mcp 分为 stdio 和 sse,stdio 为标准输入输出,通过调用工具获取工具的输出来交互,比如你在终端输入 ping host 命令,返回ping host 的输出,SSE 稍微复杂一些,通过http 接口,返回一个sse长连接,之后工具调用输入输出遵循JSONRPC规范,通过http 调用,但是结果通过SSE长连接返回,实现起来稍微复杂一些,但是调用工具和工具配置sse会更简单一些,本篇先介绍开发简单的 stdio 类型的 mcp server。工具为调用高德地图的天气查询接口返回某地的天气情况。

初始化项目

使用uv 创建一个package 类型的项目

1
2
3
4
5
6
7
8
# 初始化项目文件
uv init --package gaodeweather-mcp-server
Initialized project `gaodeweather-mcp-server` at `C:\Users\yangyanxing\Desktop\uvtest\gaodeweather-mcp-server`

cd gaodeweather-mcp-server

# 创建虚拟环境
uv sync

这个项目需要使用的mcp,所以通过uv 进行安装

uv add mcp

编写代码

编写工具函数

src\gaodeweather_mcp_server 目录下创建 server.py 文件

写入mcp 服务代码

# -*- coding: utf-8 -*-

from mcp.server.fastmcp import FastMCP
from pydantic import Field
import httpx
import json
import os
import logging

logger = logging.getLogger("mcp")


# 初始化mcp服务
mcp = FastMCP("hello-mcp-server")

# 定义工具
@mcp.tool(name="高德天气查询助手", description="高德天气查询,输入城市名,返回该城市的天气情况,例如:北京")
async def query_logistics(city: str = Field(description="要查询天气的城市名称")) -> str:
    """高德天气查询

    Args:
        city: 要查询天气的城市名称

    Returns:
        要查询城市的天气信息
    """

    logger.info("收到查询天气请求,城市名:{}".format(city))
    api_key = os.getenv("GAODE_KEY", "")
    if not api_key:
        return "请先设置GAODE_KEY环境变量"
    api_domain = 'https://restapi.amap.com/v3'
    url = f"{api_domain}/config/district?keywords={city}"f"&subdistrict=0&extensions=base&key={api_key}"
    headers = {"Content-Type": "application/json; charset=utf-8"}
    async with httpx.AsyncClient(headers=headers) as client:
        response = await client.get(url)
        if response.status_code != 200:
            return "查询失败"

        city_info = response.json()
        if city_info["info"] != "OK":
            return "获取城市信息查询失败"
        CityCode = city_info['districts'][0]['adcode']
        weather_url = f"{api_domain}/weather/weatherInfo?city={CityCode}&extensions=all&key={api_key}"
        weatherInfo_response = await client.get(weather_url)
        if weatherInfo_response.status_code != 200:
            return "查询天气信息失败"
        weatherInfo = weatherInfo_response.json()
        if weatherInfo['info'] != "OK":
            return "查询天气信息失败"
        weatherInfo_data = weatherInfo_response.json()
        contents = []
        if len(weatherInfo_data.get('forecasts')) <= 0:
            return "没有获取到该城市的天气信息"
        for item in weatherInfo_data['forecasts'][0]['casts']:
            content = {
                'date': item.get('date'),
                'week': item.get('week'),
                'dayweather': item.get('dayweather'),
                'daytemp_float': item.get('daytemp_float'),
                'daywind': item.get('daywind'),
                'nightweather': item.get('nightweather'),
                'nighttemp_float': item.get('nighttemp_float')
            }
            contents.append(content)
        return json.dumps(contents, ensure_ascii=False)

def run():
    mcp.run(transport="stdio")


if __name__ == "__main__":
   run()

FastMCP 类是高层的封装,如果没有什么特殊的需求,使用FastMCP即可,省去很多开发量,开发者只需要关注工具的具体实现即可,当然 mcp 也提供了底层的实现,这个适用于额外需求的服务开发,本文使用FastMCP开发。以后再扩展 mcp.server.lowlevel 中的Server 。

使用

mcp = FastMCP("hello-mcp-server")

初始化一个 FastMCP 对象,之后使用 @mcp.tool 装饰器装饰在具体的工具函数上, 工具具体的实现就不展开了,就是通过高德地图的天气查询接口返回对应的天气信息,但是注意由于调用高德地图的接口需求一个apikey,之后如果我们将mcp发布到公网上,需要用户使用自己的key,所以这里我并没有将apikey 固定,而是通过环境变量传入。

api_key = os.getenv("GAODE_KEY", "")

如果用户没有设置环境变量,则返回"请先设置GAODE_KEY环境变量"。 具体环境变量如何设置,后面再介绍。

我们也可以定义多个工具函数,只需要使用 @mcp.tool 装饰起来即可。

之后定义了一个 run 函数

def run():
    mcp.run(transport="stdio")

run 函数为启动FastMCP对象,启动方式为 stdio, 这种是标准的输入输出的方式,后面再介绍sse的方式。

最后在server.py中添加启动函数,这主要是用于之后的本地调试。

if __name__ == "__main__":
   run()

修改启动函数

此时 pyproject.toml 文件中脚本定义为

[project.scripts]
gaodeweather-mcp-server = "gaodeweather_mcp_server:main"

这意味着当运行 uvx gaodeweather-mcp-server 时会调用 gaodeweather_mcp_server 下的main 函数。转换成代码应该为

1
2
3
from gaodeweather_mcp_server import main

main()

而此时 src\gaodeweather_mcp_server\__init__.py 中还是uv 默认生成的内容

def main() -> None:
    print("Hello from gaodeweather-mcp-server!")

这里我们修改一下

1
2
3
4
from .server import run

def main() -> None:
    run()

以上我们就完成了一个简单的stdio类型的mcp server 的开发,接下来我们本地调试一下。

本地调试

mcp 官方提供了一个本地调试工具 inspector, https://github.com/modelcontextprotocol/inspector , 我们先使用它来进行调试。

安装 mcp[cli]

uv add mcp[cli]

启动调试命令

mcp dev src/gaodeweather_mcp_server/server.py

这里注意,在windows 下路径中需要使用 / , 或者 \\, 不要写成 src\gaodeweather_mcp_server\server.py

image.png

使用浏览器打开 http://localhost:5173

image.png

此时还没有连接这个mcp 工具,点击 Connect 按钮进行连接。当连接成功以后,会显示 Connected

连接成功以后,点击右侧导航中的Tools,再点击下面的 List Tools 按钮

image.png

显示在该mcp server 中定义的工具, 上面只定义了一个工具,这里只显示 "高德天气查询助手"。

image.png

点击工具名称,右侧会展示该工具的调试窗口

image.png

这里的输入参数是真实的调用工具时的参数,并不是用户与大模型交互时的对话,如我们和大模型交互时会问:“北京明天的天气怎么样?”,大模型会根据mcp 工具的参数定义解析出工具的入参,city="北京", 但是这个调试界面,我们不能输入 "北京明天的天气怎么样?", 而是输入真正调用工具函数时的输入参数:"北京",也就是说,这个 Inspector 调试工具,只是调试工具函数本身的输入输出,并不能调试大模型的交互。

这里输入 "北京" 来测试一下。

image.png

不出意外的话会报错,由于在上面的工具实现中有定义,如果在环境变量中未设置 GAODE_KEY 变量,将返回失败。我们来设置一下这个环境变量。

点击 Environment Variables 按钮

image.png

拉到最下面,点击 Add Environment Variable 按钮,添加环境变量

image.png

再点击 Connect 按钮,重新连接一下。之后再点击右侧的 Run Tool 按钮,即可获得工具的输出了。

image.png

我们通过使用 Inspector 来观察工具的输出,进而修改工具的实现。

如何定义工具说明

和function call 或者 tool call 类似,大模型是需要了解函数能做什么以及它需要哪些参数,才能从自然语言中分析解析出要调用哪个工具函数以及参数是什么,如果开发人员没有写清楚函数的作用和参数的定义,那么再牛的大模型也不能很好的分析出来,在前面的代码中我是这样定义函数说明和参数定义的

@mcp.tool(name="高德天气查询助手", description="高德天气查询,输入城市名,返回该城市的天气情况,例如:北京")
async def query_logistics(city: str = Field(description="要查询天气的城市名称")) -> str:
    """高德天气查询

    Args:
        city: 要查询天气的城市名称

    Returns:
        要查询城市的天气信息
    """

这里使用了两种书写工具说明的方式

第一种将工具的定义以及描述信息通过 @mcp.tool 装饰器的 name 和 description 参数传入。name 和description 说明该工具能做什么,再工具函数的实现上,async def query_logistics(city: str = Field(description="要查询天气的城市名称")) 通过pydantic.Field 来定义参数的说明。

第二种通过书写工具函数的注释。这种就没有什么规则可言,完全凭开发人员的习惯,当mcp.tool 装饰器中未定义工具名称和描述时,会使用工具的注释。

就我个人而言,更加倾向于第一种方式,因为可以很明确的说明工具的作用以及参数的描述,单纯的靠函数注释的方式编写则更加考验大模型的分析解析能力。

client 调试

上面使用 Inspector 工具只是调试了工具函数本身,而对于mcp server 的调试,应该更加关注与大模型应用的交互调用,目前有很多工具(在mcp 规范里叫 host)已经支持了MCP的调用,下面以 Cline 和 cherry studio 为例,演示如何接入本地 mcp 。

cherry studio

先复制在 Inspector 中启动的命令行

image.png

uv run --with mcp mcp run src/gaodeweather_mcp_server/server.py

点击 cherry studio 中添加服务器按钮

image.png

在弹出的对话框中填写相应的信息

image.png

这里的名称和描述随便写的,并不是mcp server 本身的名称和描述,只是方便使用者查看的。

类型选择STDIO

命令填写 uv

参数这里比较重要,将之前 Inspector 中复制的命令参数填到这里

run --with mcp mcp run src/gaodeweather_mcp_server/server.py

然后将每个参数另起一行,再添加上脚本本身的目录,最终参数填写为

1
2
3
4
5
6
7
8
--directory
C:\\Users\\yangyanxing\\Desktop\\uvtest\\gaodeweather-mcp-server
run
--with
mcp
mcp 
run 
src/gaodeweather_mcp_server/server.py

环境变量那里需要设置GAODE_KEY 变量,点击确认填加mcp 服务。

返回助手页面,设置一个支持函数调用的模型,这里我选择 qwen-plus,如果模型不支持function call, 则在对话框中是不显示mcp是否开启的图标的。

之后在对话框中将刚刚添加的mcp 开启

image.png

然后问一个查询天气相关的问题,如 “我明天要去上海出差,我应该穿什么衣服?” 虽然没有直接问天气,但是大模型也是可以分析出要调用天气接口来回答问题的。

image.png

Cline

Cline 是 vs code 的插件,也支持 mcp 的调用。

在 cherry studio 的MCP 配置界面,点击编辑JSON

image.png

复制刚刚测试成功的 gaode_weather json 信息。

image.png

打开Cline 的配置,点击 configure MCP servers 按钮

image.png

将刚才复制的配置信息填到这里, 注意修改一下路径的分隔符。

{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": [
        "--directory",
        "C:/Users/yangyanxing/Desktop/uvtest/gaodeweather-mcp-server",
        "run",
        "--with",
        "mcp",
        "mcp",
        "run",
        "src/gaodeweather_mcp_server/server.py"
      ],
      "disabled": false,
      "env": {
        "GAODE_KEY": "xxxxxx"
      }
    }
  }
}

然后在Cline 的对话框中再问一下刚才的问题, 当需要调用mcp 时,默认Cline 不会调用,而是询问用户是否调用,这时点击 Approve 进行实际的调用

image.png

打包发布

上面经过本地调试,功能正常,但是我们注意到,无论是Cherry studio 还是Cline,都需要设置 --directory 参数指定脚本文件位置,如果我们想要让mcp 工具提供给别人使用,不可能把将原文件发给每个人,然后使用者配置复杂的json,所以需要将mcp server 发布到pypi 中,让用户可以使用uvx 直接运行。

1
2
3
4
5
# 打包
uv build

# 发布
uv publish --token xxxxx

将本地写的mcp工具发布成功以后,就可以很简单的配置了

cherry studio

image.png

将命令改为 uvx, uvx 是 uv tool run 的简称,参数只需要改为 gaodeweather-mcp-server, 这个是在 project.scripts 中定义的命令,

[project.scripts]
gaodeweather-mcp-server = "gaodeweather_mcp_server:main"

uvx 会从pypi 中自动下载安装对应的包,用户不用在配置目录信息。

问答效果和之前一样。

Cline

编辑 Cline 中的mcp json 配置

{
  "mcpServers": {
    "weather": {
      "command": "uvx",
      "args": [
        "gaodeweather-mcp-server"
      ],
      "disabled": false,
      "env": {
        "GAODE_KEY": "xxxx"
      }
    }
  }
}

可以看到,命令行简单了很多。

总结

本文介绍了使用python 开发一个stdio 类型的mcp server, 主要的步骤如下

  1. 使用 uv init --package 初始化 package 类型的项目
  2. 使用FastMCP编写简单的 mcp 工具
  3. 使用 Inspector 进行工具本身的调试
  4. 使用cherry studio 或者 Cline 进行本地大模型交互测试
  5. 打包上传 pypi
  6. 重新配置 cherry studio 和 Cline,使用uvx 运行 mcp 服务

除了stdio 还有另外一种mcp 服务 SSE,sse 类型的mcp 在使用和配置上会更加简单,下一篇文章再做详细介绍,敬请期待!