FastMCP:构建 MCP 服务器和客户端的高效 Python 框架

发布于:2025-06-04 ⋅ 阅读:(38) ⋅ 点赞:(0)

在人工智能领域,模型上下文协议(Model Context Protocol,简称 MCP)作为一种标准化的协议,为大型语言模型(LLM)提供了丰富的上下文和工具支持。而 FastMCP 作为构建 MCP 服务器和客户端的 Python 框架,以其简洁的 API 设计、高效的开发体验以及强大的扩展能力,正逐渐成为开发者们的首选工具。

一、FastMCP 简介

FastMCP 是一个用于构建 MCP 服务器和客户端的 Python 框架,其目标是让开发者能够以更少的代码、更高的效率构建出功能强大的 MCP 应用。相比官方的 MCP Python SDK,FastMCP 在多个方面进行了优化和改进,提供了更简洁的 API 设计、更高效的开发体验以及更强大的扩展能力。

FastMCP 的核心优势主要体现在以下几个方面:

(一)简洁的 API 设计

FastMCP 采用了 Pythonic 的设计风格,通过装饰器(Decorator)即可轻松定义工具(Tools)、资源(Resources)和提示(Prompts)。这种设计方式极大地减少了开发过程中的样板代码,让开发者能够更加专注于业务逻辑的实现。

(二)高效的开发体验

FastMCP 提供了丰富的内置功能,如服务器组合、远程服务器代理、OpenAPI/FastAPI 集成等,这些功能不仅提高了开发效率,还为开发者提供了更多的灵活性。此外,FastMCP 还支持多种客户端/服务端传输模式,包括 Stdio、SSE 和内存传输,能够满足不同场景下的开发需求。

(三)强大的扩展能力

FastMCP 的设计具有高度的可扩展性,开发者可以根据自己的需求轻松添加新的功能和模块。无论是构建复杂的 AI 应用,还是进行简单的数据处理,FastMCP 都能够提供强大的支持。

二、FastMCP 与官方 SDK 的关系

FastMCP 1.0 的核心概念已经被纳入官方的 MCP Python SDK,而当前的 FastMCP 2.0 则是在此基础上进行的进一步扩展和优化。FastMCP 2.0 不仅继承了 1.0 版本的基础功能,还引入了完整的客户端支持、服务器组合、OpenAPI/FastAPI 集成、远程服务器代理以及内置测试工具等新功能,显著提升了开发效率和应用的灵活性。

三、开发示例

为了更好地理解 FastMCP 的强大功能和简洁的开发方式,我们以一个基于 FastMCP 的数学运算智能问答应用为例,详细介绍其开发过程。

(一)安装 FastMCP

首先,需要安装 FastMCP 框架。通过以下命令即可完成安装:

uv pip install fastmcp

(二)服务端实现

服务端的实现非常简单,只需要定义几个基本的数学运算工具即可。以下是服务端的代码示例:

from fastmcp import FastMCP

mcp = FastMCP(name="MyAssistantServer")


@mcp.tool()
def add(a: float, b: float) -> float:
    """加法运算

    参数:
    a: 第一个数字
    b: 第二个数字

    返回:
    两数之和
    """
    return a + b


@mcp.tool()
def subtract(a: float, b: float) -> float:
    """减法运算

    参数:
    a: 第一个数字
    b: 第二个数字

    返回:
    两数之差 (a - b)
    """
    return a - b


@mcp.tool()
def multiply(a: float, b: float) -> float:
    """乘法运算

    参数:
    a: 第一个数字
    b: 第二个数字

    返回:
    两数之积
    """
    return a * b


@mcp.tool()
def divide(a: float, b: float) -> float:
    """除法运算

    参数:
    a: 被除数
    b: 除数

    返回:
    两数之商 (a / b)

    异常:
    ValueError: 当除数为零时
    """
    if b == 0:
        raise ValueError("除数不能为零")
    return a / b


if __name__ == "__main__":
    mcp.run(transport='sse', host="127.0.0.1", port=8001)

在上述代码中,我们定义了四个基本的数学运算工具:加法、减法、乘法和除法。通过装饰器 @mcp.tool(),这些函数被注册为 MCP 服务器的工具,可供客户端调用。最后,通过调用 mcp.run() 方法启动服务器,并指定使用 SSE 传输模式。

(三)客户端实现

客户端的实现同样简单。通过一行代码即可创建一个 MCP 客户端,并连接到服务端。以下是客户端的代码示例:

from fastmcp import Client
import asyncio


async def main():
    # 测试 mcp 客户端的功能
    async with Client("http://127.0.0.1:8001/sse") as mcp_client:
        tools = await mcp_client.list_tools()
        print(f"Available tools: {tools}")
        result = await mcp_client.call_tool("add", {"a": 5, "b": 3})
        print(f"Result: {result[0].text}")


if __name__ == "__main__":
    asyncio.run(main())

在上述代码中,我们通过 Client 类创建了一个 MCP 客户端,并连接到服务端。通过调用 list_tools() 方法,我们可以获取服务端提供的所有工具列表。然后,通过调用 call_tool() 方法,我们可以调用服务端的工具并获取结果。

(四)数学运算智能问答应用

基于 FastMCP,我们还可以构建一个数学运算智能问答应用。该应用通过与大语言模型(LLM)进行交互,根据用户的输入调用相应的工具进行计算,并返回结果。以下是该应用的代码示例:

import asyncio
import json
import logging
import os
from typing import List, Dict

from fastmcp import Client
from openai import OpenAI


class LLMClient:
    """LLM客户端,负责与大语言模型API通信"""

    def __init__(self, model_name: str, url: str, api_key: str) -> None:
        self.model_name: str = model_name
        self.url: str = url
        self.client = OpenAI(api_key=api_key, base_url=url)

    def get_response(self, messages: List[Dict[str, str]]) -> str:
        """发送消息给LLM并获取响应"""
        response = self.client.chat.completions.create(
            model=self.model_name,
            messages=messages,
            stream=False
        )
        return response.choices[0].message.content


class ChatSession:
    """聊天会话,处理用户输入和LLM响应,并与MCP工具交互"""

    def __init__(self, llm_client: LLMClient, mcp_client: Client) -> None:
        self.mcp_client: Client = mcp_client
        self.llm_client: LLMClient = llm_client

    async def process_llm_response(self, llm_response: str) -> str:
        """处理LLM响应,解析工具调用并执行"""
        try:
            # 尝试移除可能的markdown格式
            if llm_response.startswith('```json'):
                llm_response = llm_response.strip('```json').strip('```').strip()
            tool_call = json.loads(llm_response)
            if "tool" in tool_call and "arguments" in tool_call:
                # 检查工具是否可用
                tools = await self.mcp_client.list_tools()
                if any(tool.name == tool_call["tool"] for tool in tools):
                    try:
                        # 执行工具调用
                        result = await self.mcp_client.call_tool(
                            tool_call["tool"], tool_call["arguments"]
                        )

                        return f"Tool execution result: {result}"
                    except Exception as e:
                        error_msg = f"Error executing tool: {str(e)}"
                        logging.error(error_msg)
                        return error_msg
                return f"No server found with tool: {tool_call['tool']}"
            return llm_response
        except json.JSONDecodeError:
            # 如果不是JSON格式,直接返回原始响应
            return llm_response

    async def start(self, system_message: str) -> None:
        """启动聊天会话的主循环"""
        messages = [{"role": "system", "content": system_message}]
        while True:
            try:
                # 获取用户输入
                user_input = input("用户: ").strip().lower()
                if user_input in ["quit", "exit", "退出"]:
                    print('AI助手退出')
                    break
                messages.append({"role": "user", "content": user_input})

                # 获取LLM的初始响应
                llm_response = self.llm_client.get_response(messages)
                print("助手: ", llm_response)

                # 处理可能的工具调用
                result = await self.process_llm_response(llm_response)

                # 如果处理结果与原始响应不同,说明执行了工具调用,需要进一步处理
                while result != llm_response:
                    messages.append({"role": "assistant", "content": llm_response})
                    messages.append({"role": "system", "content": result})

                    # 将工具执行结果发送回LLM获取新响应
                    llm_response = self.llm_client.get_response(messages)
                    result = await self.process_llm_response(llm_response)
                    print("助手: ", llm_response)

                messages.append({"role": "assistant", "content": llm_response})

            except KeyboardInterrupt:
                print('AI助手退出')
                break


async def main():
    async with Client("http://127.0.0.1:8001/sse") as mcp_client:
        # 初始化LLM客户端,使用通义千问模型
        llm_client = LLMClient(model_name='qwen-plus-latest', api_key=os.getenv('DASHSCOPE_API_KEY'),
                               url='https://dashscope.aliyuncs.com/compatible-mode/v1')

        # 获取可用工具列表并格式化为系统提示的一部分
        tools = await mcp_client.list_tools()
        dict_list = [tool.__dict__ for tool in tools]
        tools_description = json.dumps(dict_list, ensure_ascii=False)

        # 系统提示,指导LLM如何使用工具和返回响应
        system_message = f'''
                你是一个智能助手,严格遵循以下协议返回响应:

                可用工具:{tools_description}

                响应规则:
                1、当需要计算时,返回严格符合以下格式的纯净JSON:
                {{
                    "tool": "tool-name",
                    "arguments": {{
                        "argument-name": "value"
                    }}
                }}
                2、禁止包含以下内容:
                 - Markdown标记(如```json)
                 - 自然语言解释(如"结果:")
                 - 格式化数值(必须保持原始精度)
                 - 单位符号(如元、kg)

                校验流程:
                ✓ 参数数量与工具定义一致
                ✓ 数值类型为number
                ✓ JSON格式有效性检查

                正确示例:
                用户:单价88.5买235个多少钱?
                响应:{{"tool":"multiply","arguments":{{"a":88.5,"b":235}}}}

                错误示例:
                用户:总金额是多少?
                错误响应:总价500元 → 含自然语言
                错误响应:```json{{...}}```→ 含Markdown

                3、在收到工具的响应后:
                 - 将原始数据转化为自然、对话式的回应
                 - 保持回复简洁但信息丰富
                 - 聚焦于最相关的信息
                 - 使用用户问题中的适当上下文
                 - 避免简单重复使用原始数据
                '''
        # 启动聊天会话
        chat_session = ChatSession(llm_client=llm_client, mcp_client=mcp_client)
        await chat_session.start(system_message=system_message)


if __name__ == "__
__main__":
    asyncio.run(main())

(五)运行验证

运行服务端代码:

python fast_mcp_server.py

运行客户端代码:

python fast_mcp_client.py

在客户端中输入数学问题,例如:

用户: 现在要购买一批货,单价是 1034.32423,数量是 235326。商家后来又说,可以在这个基础上,打95折,折后总价是多少?

客户端会调用服务端的 multiply 工具进行计算,并返回结果:

助手:  {
  "tool": "multiply",
  "arguments": {
    "a": 1034.32423,
    "b": 235326
  }
}
助手:  {
  "tool": "multiply",
  "arguments": {
    "a": 243403383.74898,
    "b": 0.95
  }
}
助手:  折后总价是231233214.56。

四、FastMCP 的更多功能

除了上述提到的功能外,FastMCP 还提供了许多其他强大的功能,例如:

(一)服务器组合

通过 mcp.mount()mcp.import_server() 方法,可以将多个 FastMCP 实例组合到一个父服务器中,从而构建出模块化的应用程序。这种方式不仅提高了代码的可维护性,还方便了功能的扩展。

(二)OpenAPI/FastAPI 集成

FastMCP 提供了从现有的 OpenAPI 规范或 FastAPI 应用程序生成 FastMCP 服务器的功能。通过这种方式,开发者可以轻松地将现有的 Web API 集成到 MCP 生态系统中,进一步扩展了 MCP 的应用场景。

(三)代理服务器

通过 FastMCP.as_proxy() 方法,可以创建一个代理服务器,该服务器可以作为本地或远程 MCP 服务器的中间层。这种方式特别适用于桥接不同的传输协议(例如,将远程 SSE 服务器代理到本地 Stdio 客户端)或为不受控制的服务器添加逻辑层。

(四)内置测试工具

FastMCP 提供了强大的内置测试工具,支持通过内存传输直接连接到 FastMCP 服务器实例,从而在测试过程中无需进行进程管理和网络调用。这种方式极大地提高了测试效率,降低了测试成本。

五、总结

FastMCP 作为一个高效、简洁且功能强大的 Python 框架,为构建 MCP 服务器和客户端提供了极大的便利。通过其简洁的 API 设计、高效的开发体验和强大的扩展能力,开发者可以快速构建出功能丰富的 MCP 应用。无论是简单的工具开发,还是复杂的 AI 应用构建,FastMCP 都能够满足开发者的需求。

如果你对 MCP 或 FastMCP 感兴趣,不妨尝试使用它来构建自己的应用。相信你一定会被其强大的功能和简洁的设计所吸引。同时,也欢迎关注我的后续文章,我将继续介绍更多关于 MCP 的内容,以及 FastMCP 的高级用法和实战案例。



网站公告

今日签到

点亮在社区的每一天
去签到