编写MCP Server

一、FastMCP简介

FastMCP是一个基于Python的高级框架,专为构建MCP服务器而设计。它极大简化了MCP服务器的开发流程,让开发者能够以最小的代码量创建功能强大的MCP服务器。

FastMCP的主要特点包括:

简洁的API:通过装饰器模式,简化MCP服务器的创建
丰富的功能:支持工具(Tools)、资源(Resources)、提示模板(Prompts)等MCP核心元素
多种传输方式:支持stdio和SSE等不同传输协议
类型安全:利用Python的类型提示,自动生成MCP协议所需的模式定义
内置图像处理:支持图像数据的自动格式转换和处理。

二、MCP中SSE和STDIO的区别

MCP服务端当前支持两种与客户端的数据通信方式:标准输入输出(stdio)和基于HTTP的服务器推送事件(SSE)。这两种方式有着明显的区别和各自的适用场景。

3.1 标准输入输出(STDIO)
原理:

STDIO是一种用于本地通信的传输方式。在这种模式下,MCP客户端会将服务器程序作为子进程启动,双方通过标准输入和标准输出进行数据交换。具体而言:

客户端通过标准输入(stdin)向服务器发送请求
服务器通过标准输出(stdout)返回响应
服务器可以通过标准错误(stderr)输出日志和错误信息
特点:

低延迟:本地进程间通信,无网络开销
简单直接:不需要网络配置和端口管理
安全性高:通信限制在本地进程内,无需考虑网络安全
适合单会话:一个客户端对应一个服务器实例
适用场景:

客户端和服务器在同一台机器上运行的场景
需要本地访问文件系统或设备的工具
对延迟敏感的应用
开发测试阶段
3.2 服务器推送事件(SSE)
原理:
SSE(Server-Sent Events)是一种基于HTTP的单向通信技术,允许服务器向客户端推送数据。在MCP中,SSE实现了:
客户端通过HTTP POST请求发送数据到服务器
服务器通过持久的HTTP连接向客户端推送事件和数据
通信基于标准的HTTP协议,便于在网络环境中部署

特点:
网络传输:基于HTTP协议,可跨网络通信
多客户端支持:单个服务器实例可同时服务多个客户端
易于部署:可部署在云服务、容器或微服务架构中
扩展性好:适合分布式环境

适用场景:
客户端和服务器位于不同物理位置
需要支持多客户端连接
需要远程访问的服务
生产环境和企业级应用
3.3 SSE与STDIO的详细比较

特性 STDIO SSE
通信方式 进程间通信 HTTP网络通信
部署位置 本地 本地或远程
客户端数量 单客户端 多客户端
延迟 极低 受网络影响
安全性考虑 本地安全 需考虑网络安全
可扩展性 有限
运行方式 作为子进程 作为网络服务
适用环境 开发环境、桌面应用 生产环境、云服务、分布式系统

三、FastMCP实现SSE方式

3.1 基本示例

下面是使用FastMCP实现SSE通信方式的示例,创建一个简单的天气服务:

# weather_sse.py
from fastmcp import FastMCP
import random

# 创建MCP服务器实例,指定端口
mcp = FastMCP("Weather Service", port=8000)

# 模拟的天气数据
weather_data = {
    "大连": {"temp": range(10, 25), "conditions": ["晴", "多云", "小雨"]},
    "北京": {"temp": range(5, 20), "conditions": ["cloudy", "rainy", "foggy"]},
    "齐齐哈尔": {"temp": range(15, 30), "conditions": ["sunny", "cloudy", "humid"]},
    "呼伦贝尔": {"temp": range(20, 35), "conditions": ["sunny", "clear", "hot"]},
}


@mcp.tool()
def get_weather(city: str) -> dict:
    """获取指定城市的当前天气"""
    if city not in weather_data:
        return {"error": f"无法找到城市 {city} 的天气数据"}

    data = weather_data[city]
    temp = random.choice(list(data["temp"]))
    condition = random.choice(data["conditions"])

    return {
        "city": city,
        "temperature": temp,
        "condition": condition,
        "unit": "celsius"
    }


@mcp.resource("weather://cities")
def get_available_cities() -> list:
    """获取所有可用的城市列表"""
    return list(weather_data.keys())


@mcp.resource("weather://forecast/{city}")
def get_forecast(city: str) -> dict:
    """获取指定城市的天气预报资源"""
    if city not in weather_data:
        return {"error": f"无法找到城市 {city} 的天气预报"}

    forecast = []
    for i in range(5):  # 5天预报
        data = weather_data[city]
        temp = random.choice(list(data["temp"]))
        condition = random.choice(data["conditions"])
        forecast.append({
            "day": i + 1,
            "temperature": temp,
            "condition": condition
        })

    return {
        "city": city,
        "forecast": forecast,
        "unit": "celsius"
    }


if __name__ == "__main__":
    # 使用SSE传输方式启动服务器
    mcp.run(transport="sse")

运行SSE模式的MCP服务器:
python weather_sse.py

在Pycharm中启动的SSE服务

服务器将在指定端口(本例中为8000)启动,并监听HTTP连接。您可以通过浏览器访问:
http://localhost:8000/sse

可以使用Cherry Studio来测试SSE的server。
配置MCP server:



问答测试:


另外一种方式,可以使用支持SSE传输的MCP客户端,或者使用如下Python代码创建一个简单的客户端:

# sse_client.py
import asyncio
from mcp import ClientSession
from mcp.client.sse import sse_client


async def main():
    # 连接到SSE服务器
    async with sse_client(url="http://localhost:8000/sse") as streams:
        async with ClientSession(*streams) as session:
            # 初始化会话
            await session.initialize()

            # 列出可用工具
            tools_response = await session.list_tools()
            print("Available tools:")
            for tool in tools_response.tools:
                print(f" - {tool.name}: {tool.description}")

            # 列出可用资源
            resources_response = await session.list_resources()
            print("\nAvailable resources:")
            for resource in resources_response.resources:
                print(f" - {resource.uri}: {resource.description}")

            # 调用天气工具
            print("\nCalling get_weather tool for 大连...")
            weather_response = await session.call_tool("get_weather", {"city": "大连"})
            print(weather_response.content[0].text)

            # 读取资源
            print("\nReading weather://cities resource...")
            cities_response = await session.read_resource("weather://cities")
            print(cities_response[0].content)

            # 读取带参数的资源
            print("\nReading weather forecast for Tokyo...")
            forecast_response = await session.read_resource("weather://forecast/大连")
            print(forecast_response[0].content)


if __name__ == "__main__":
    asyncio.run(main())
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容