MCP学习二——服务端开发与功能验证

156 阅读15分钟

前置 本系列第一篇:UV安装和使用

本系列是笔者在学习MCP中的一些总结和笔记,所以主要以代码实践为主(PS:这也是为什么本系列的第一篇是uv的使用和安装),默认各位读者已经对MCP的概念有一些简单了解。当然一些读者可能只是简单知道MCP这个名词,并不清楚具体是什么东西。所以在这里附上我认为介绍MCP还不错的文章。MCP(模型上下文协议) MCP: 客户端与服务端交互流程 笔者这篇文章的代码来源也主要来自于这篇 MCP:编程实战

MCP简要介绍

MCP(Model Context Protocol)是由Anthropic提出的开源协议,旨在简化大型语言模型(LLM)与外部数据源、工具或资源的集成。它通过标准化规则,为LLM提供一种统一的接口,使其能够灵活连接和操作各种外部系统(如文件、数据库、API等),从而增强模型的实用性和扩展性。

MCP 采用客户端 - 服务器(Client-Server)架构,分为三个核心组件:

  • MCP Host:运行 AI 应用的载体,如 Claude Desktop 或 IDE,负责发起请求。
  • MCP Client:与服务器建立 1:1 连接的协议客户端,负责数据传输。
  • MCP Server:轻量化程序,封装具体工具或数据源(如 GitHub、Slack),通过标准化接口暴露功能。

MCP与Function Call

Function Call(函数调用) 是大模型的基础能力,允许模型在推理过程中生成结构化指令调用外部工具(如 OpenAI 的 function calling 机制,想了解的请看这篇大模型工具调用(function call)原理及实现)。而 MCP 协议则是在此基础上构建的跨模型 / 工具通信框架,二者关系可概括为(大模型生成): image.png

MCP与Agent

Agent(智能体) 是基于大模型构建的自主执行者,具备任务规划、工具调用和结果整合能力。MCP 协议与 Agent 的关系可理解为:

  • MCP 是基础设施:为 Agent 提供统一的工具接入方式,避免重复开发适配代码。
  • Agent 是上层应用:利用 MCP 协议动态调用工具,实现复杂流程自动化。 二者关系可概括为(大模型生成): image.png

以下是大模型对MCP、Function Calling和Agent三者关系的解释,笔者认为还是非常可能的。 MCP与Function Calling正逐渐成为AI智能体(Agent)的核心基石,未来大模型的应用开发可能呈现AI Agent = LLM + Function Call +MCP的趋势。LLM提供智能决策和自然语言理解能力。Function Call实现精准的工具调用,解决单个任务的需求。MCP通过标准化协议,支持多工具协作,降低集成复杂度。 例如:在数据分析场景中,数据分析Agent 通过 MCP 协议连接 Excel 服务器读取数据,使用 Function Call 调用统计工具计算增长率,最后通过 MCP 协议将结果写入报告文档。

MCP服务类型

在 MCP中,服务端(MCP Server)可以为客户端(MCP Client)提供三类核心服务:Resources(资源)Tools(工具)Prompts(提示模板)。这三类服务通过标准化接口,为大语言模型(LLM)与外部系统的集成提供灵活性和扩展性。

Recources提供静态的、只读的上下文数据或内容,作为 LLM 的输入或参考。类似 REST API 的 GET 请求,通过 URI 风格的路径访问且不涉及修改外部系统的状态。。可以支持文本或二进制数据。适合缓存,减少重复请求对系统性能的影响。 典型的应用场景读取文件内容:例如从本地文件系统或云存储中读取文档、代码片段等。获取配置信息:读取系统或应用的配置参数(如数据库连接字符串、API 密钥等)。 查询数据库数据:从数据库中提取特定记录或统计信息。 获取系统状态:例如读取服务器的运行状态、日志信息等。

Tools允许 LLM 调用外部系统执行动态操作或计算。类似 REST API 的 POST/PUT/DELETE 请求,通过工具名称和参数调用,可以修改外部系统的状态或与外部系统交互。工具的范围从简单的计算到复杂的 API 调用(如发送邮件、执行 SQL 语句等)。客户端可以通过 tools/list 接口列出所有可用工具。 典型使用场景数学计算:执行加减乘除、统计分析等计算任务。数据处理:对数据进行清洗、转换或格式化。 API 调用:调用外部服务的 API(如发送邮件、查询天气、调用支付接口等)。 文件操作:创建、修改或删除文件(如保存草稿、上传文件等)。

Prompts定义可复用的提示模板(Prompt Templates),指导 LLM 的输出行为。 支持参数化模板,允许动态填充变量(如用户输入、上下文信息等)。MCP服务通过统一的模板格式,规范 LLM 的输出逻辑。模板可以被多个客户端共享,减少重复定义。 典型使用场景生成文本模板:例如生成报告、合同、邮件草稿等结构化文本。定义对话流程:通过模板引导用户与 LLM 的交互(如客服对话、问答流程)。标准化输出:确保 LLM 的输出符合特定格式(如 JSON、Markdown 等)。任务指令:为复杂任务定义分步骤的提示模板(如代码生成、数据分析等)。

三类服务对比(大模型生成): image.png

MCP服务端编程实现

MCP相关的概念讲完了,下面让我进入激动人心的编码环节。在此之前还要做一项烦人的准备工作——项目环境搭建。

环境搭建

  1. UV安装 :本文使用uv作为包管理工具(当然,读者也可以使用其他的包管理工具如conda)。uv的安装教程请看本系列的第一篇
  2. Node.js安装:uv安装完成后,然后安装node.js。安装教程可参考win系统安装nodejslinux系统安装node.js。然后对npm换源。如果读者只是想学习MCP开发,或者是一个刚入门的小白,那么之间安装node.js就行了。如果读者想使用MCP开发一个应用程序,或者有相关经验,那么笔者推荐先安装nvm,再使用nvm安装node.js。由于笔者使用的是Ubuntu系统,安装和换源分别通过Ubuntu安装nvmnvm换源完成。
  3. PostgreSql安装(可选):如果uv和nodejs都已经搞定了,那么恭喜,你已经完成了项目创建的60%的工作。下面让我们安装pgsql数据库,安装教程:win系统安装PgSqllinux安装PgSql。这些教程需要的配置的很多,笔者可完成为postgres用户设置密码就可以了。创建用户和数据库会在下面讲到。 这是一个可选项,读者也可以不选择安装,只是在编写Resource服务时,会出现报错,但是对编写Prompt和Tools服务没有影响。

初始化项目

让我们使用uv初始化项目,命令

# 创建 python 项目,项目名称为 mcp_learning
uv init mcp_learning
cd mcp_learning

# 激活虚拟环境
uv venv
.venv\Scripts\activate

在这一步,如果读者没有将uv源换成国内源的话可能会出现连接超时的错误。

然后安装相关依赖:

# 安装相关依赖包
uv add mcp[cli] httpx

# 连接 pg 数据库的 python 包,如何读者没有安装数据库,那么也可以选择不安装
uv add psycopg2

创建服务端实例

# 导入必要的模块
import psycopg2  # PostgreSQL 数据库连接库
import json  # JSON 处理库
from psycopg2.extras import RealDictCursor  # 使查询结果以字典形式返回的游标
from mcp.server.fastmcp.prompts import base  # MCP 提示模板的基础类
from mcp.server.fastmcp import FastMCP  # 导入 MCP 服务器的主类


# 创建 MCP 服务器
mcp = FastMCP("MCP Test Server",  # 服务器名称
              debug=True,  # 启用调试模式,会输出详细日志
              host="0.0.0.0",  # 监听所有网络接口,允许远程连接
              port=8002)  # 服务器监听的端口号

这段代码的作用是创建服务端名称为 "MCP Test Server" 的实例 启用了调试模式,产生更详细的日志输出,方便调试。 服务器配置为监听 0.0.0.0(所有网络接口),接受来自本地和远程的连接 端口设置为 8002,这是客户端连接服务器的端口 。 FastMCP 为 MCP 服务端的主要实现类。 注意:host="0.0.0.0" 设置使服务器可以接受来自任何 IP 地址的连接,这在开发环境中很方便,但在生产环境中可能需要更严格的访问控制。

Resources服务实现

如果读者没有安装PgSql数据库,那么可以跳过这一章节。如果已经安装了PgSql数据库,请跟着我左手右手一个慢动作。 首先我们要在PgSql中创建一个新的用户和数据库,第一步就是先登录PgSql。

psql -h localhost -U postgres -d postgres
# 然后输入密码进入pgsql命令行

创建一个新的用户和数据库

## 添加一个用户mcp_learning,并设置密码
create user mcp_learning with password '新的密码';
## 给 mcp_learning 用户,创建一个数据库叫mcp_learningdb
create database mcp_learningdb owner mcp_learning;

授予 mcp_learing 用户对于数据库 mcp_learingdb 的所有权限,以下4步是一个整体,不要跳着执行,否则会出现"明明给A授权的,却发现是给B授予"。参考PostgreSQL创建管理员与数据库

-- 1) 先退出postgre数据库 
\q 
-- 2) 登录 mcp_learingdb 数据库 
psql -h localhost -U postgres -d mcp_learingdb

-- 3) 将 mcp_learingdb 的所有权限赋予 mcp_learing
grant all privileges on database mcp_learingdb to mcp_learing;

-- 4) 授予 mcp_learing 对 mcp_learingdb 数据库的读写权限 
grant usage on schema public to mcp_learing; 
grant all privileges on all tables in schema public to mcp_learing; 
grant all privileges on all sequences in schema public to mcp_learing; 
grant select,insert,update,delete on all tables in schema public to mcp_learing; 
grant all on schema public to mcp_learing;

在 mcp_learingdb 中创建数据表。

-- 1)登录 mcp_learingdb数据库
psql -h localhost -U mcp_learing -d mcp_learingdb

-- 2)创建表 company表
CREATE TABLE COMPANY(
   ID INT PRIMARY KEY     NOT NULL,
   NAME           TEXT    NOT NULL,
   AGE            INT     NOT NULL,
   ADDRESS        CHAR(50),
   SALARY         REAL
);

-- 3) 插入数据
INSERT INTO COMPANY
(ID,NAME,AGE,ADDRESS,SALARY) VALUES
(1, 'Mark', 25, 'Rich-Mond ', 65000.0 ),
(2, 'David', 27, 'Texas', 85000.00);

MCP 服务端可以为 MCP 客户端定义访问数据库数据的资源端点,允许客户端查询数据库的元数据、数据表数据。首先编写数据库连接。

# 数据库连接配置
DB_CONFIG = {
    "dbname": "mcp_learningdb",
    "user": "mcp_learning",
    "password": "自己的密码",
    "host": "localhost",
    "port": "5432"
}

def get_db_connection():
    """创建数据库连接"""
    return psycopg2.connect(**DB_CONFIG)

然后编写资源示例:

1.查询表名

查询数据库中public下的所有表名,并返回表名列表的JSON字符串。

# 定义资源:获取所有表名
@mcp.resource("db://tables")
def list_tables() -> str:
    """获取所有表名列表"""
    with get_db_connection() as conn:
        with conn.cursor() as cur:
            cur.execute("""
                SELECT table_name
                FROM information_schema.tables
                WHERE table_schema = 'public'
            """)
            tables = [row[0] for row in cur.fetchall()]
            return json.dumps(tables)

2.表数据查询

定义数据表查询的资源,允许查询指定表的数据,支持参数: table_name: 要查询的表名 limit: 限制返回的最大行数,默认值为 100 使用了 RealDictCursor 使结果以字典形式返回,使用了参数化查询来防止 SQL 注入攻击,并设置 ensure_ascii=False 以保留中文字符。

# 定义资源:获取表数据
@mcp.resource("db://tables/{table_name}/data/{limit}")
def get_table_data(table_name: str, limit: int = 100) -> str:
    """获取指定表的数据

    参数:
    table_name: 表名
    """
    try:
        with get_db_connection() as conn:
            with conn.cursor(cursor_factory=RealDictCursor) as cur:
                # 使用参数化查询防止 SQL 注入
                cur.execute(f"SELECT * FROM %s LIMIT %s",
                            (psycopg2.extensions.AsIs(table_name), limit))
                rows = cur.fetchall()
                # return json.dumps(list(rows), default=str)
                return json.dumps(list(rows), default=str, ensure_ascii=False)
    except Exception as e:
        return json.dumps({
            "status": "error",
            "message": str(e)
        })

3.表结构查询

定义表结构查询的资源,允许查询指定表的结构信息,包括列名、数据类型、最大长度和列注释。(不同类型的数据库,查询表元数据的 sql 会有所不同)。

# 定义资源:获取表结构
@mcp.resource("db://tables/{table_name}/schema")
def get_table_schema(table_name: str) -> str:
    """获取表结构信息

    参数:
    table_name: 表名
    """
    with get_db_connection() as conn:
        with conn.cursor() as cur:
            cur.execute("""
                select c.column_name, 
                       c.data_type, 
                       c.character_maximum_length,
                       pgd.description as column_comment
                from information_schema.columns c
                left join pg_catalog.pg_statio_all_tables st 
                on c.table_schema = st.schemaname and c.table_name = st.relname
                left join pg_catalog.pg_description pgd 
                on pgd.objoid = st.relid 
                   and pgd.objsubid = c.ordinal_position
                where c.table_name = %s
                order by c.ordinal_position
            """, (table_name,))
            columns = [{"name": row[0], "type": row[1], "max_length": row[2], "comment": row[3]}
                       for row in cur.fetchall()]
            return json.dumps(columns, ensure_ascii=False)

Prompts服务实现

定义 MCP prompt(提示模板),用于指导 LLM 如何回答特定类型的查询。

1.中国省份介绍

使用 @mcp.prompt() 装饰器注册为 MCP 服务器的提示模板,接受一个参数 province,表示要介绍的中国省份名称,返回一个字符串提示模板,引导 LLM 按照特定结构介绍指定省份 提示模板要求 LLM 从四个方面介绍省份:历史沿革、人文地理和风俗习惯、经济发展状况、旅游建议。

# 中国省份介绍
@mcp.prompt()
def introduce_china_province(province: str) -> str:
    """介绍中国某个省份"""
    return f"""请介绍这个省份:{province}

    要求介绍以下内容:
    1. 历史沿革
    2. 人文地理、风俗习惯
    3. 经济发展状况
    4. 旅游建议
"""

2.代码调试提示模板

提示模板的功能:

  • 使用 @mcp.prompt() 装饰器注册
  • 接受两个参数:code(需要调试的代码)和 error_message(错误信息)
  • 与第一个模板不同,这个模板返回的是 list[base.Message] 类型,表示一个多轮对话的消息列表 对话包含五条消息:
    • 系统消息:定义助手的角色和任务
    • 用户消息:请求帮助修复代码
    • 用户消息:包含代码内容(使用代码块格式)
    • 用户消息:包含错误信息
    • 助手消息:初始回应,表明将分析问题 使用场景:当用户遇到代码错误需要帮助调试时,通过预设对话历史,引导 LLM 进入特定的思考模式,提供结构化的上下文,使 LLM 能够更有效地分析和解决代码问题。
# 调试代码提示
@mcp.prompt()
def debug_code(code: str, error_message: str) -> list[base.Message]:
    """调试代码的对话式提示模板

    参数:
    code: 需要调试的代码
    error_message: 错误信息
    """
    return [
        base.AssistantMessage("你是一位专业的代码调试助手。请仔细分析用户提供的代码和错误信息,找出问题所在并提供修复方案。"),
        base.UserMessage("我的代码有问题,请帮我修复:"),
        base.UserMessage(f"```\n{code}\n```"),
        base.UserMessage(f"错误信息:\n{error_message}"),
        base.AssistantMessage("我会帮你分析这段代码和错误信息。首先让我理解问题所在..."),
    ]

Tools 实现

Tools实现是最简单的,下面的代码定义了四个基本的数学运算工具,通过 @mcp.tool() 装饰器,将这些函数注册为 MCP 服务端的工具(可以被客户端直接调用)

@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

MCP Inspector验证服务功能

MCP Inspector 是专为 Model Context Protocol(MCP)服务端设计的交互式调试工具,提供了一个直观的界面,使得开发者能够快速地验证服务端的响应和状态。使用 MCP Inspector 来测试验证上述开发的服务端功能。

运行命令:mvp dev server_see_test.py( mcp dev 命令默认使用 stdio 通信方式来启动 mcp 服务端),对上面开发的 mcp 服务端功能进行测试。点击输出的链接,即可打开浏览器,进入 mcp inspector 用户界面: image.png 剩下的功能验证内容就和这篇文章的内容一模一样了 MCP:编程实战,大家可以参考这篇文章对自己写的MCP服务进行验证测试。

我计划创建一个MCP学习群,大家可以在群里讨论问题,当然不限于MCP,也会不定期分享一些大模型学习资料。有兴趣可以添加jsmxok。添加时请备注 学习)

参考

  1. MCP(模型上下文协议)
  2. MCP: 客户端与服务端交互流程
  3. MCP:编程实战
  4. 大模型工具调用(function call)原理及实现
  5. win系统安装nodejs
  6. linux系统安装node.js
  7. Ubuntu安装nvm
  8. nvm换源
  9. win系统安装PgSql
  10. linux安装PgSql
  11. PostgreSQL创建管理员与数据库