关于MCP的学习与开发

得益于大模型越来越强大的语言、思维能力,给我的学习带来了很大的便利,现在很多知识 AI 都能教会我,一定程度上免去了在网络上检索、筛选的繁琐过程。而现在加上 MCP 这一框架的使用,使得大模型不仅可以说话,还可以调用由 MCP 提供的工具,实现动手操作。

这段时间我初步学习了一些关于 MCP 的概念和应用层开发,着手将学校的公文通封装成了一个能被大模型调用的 MCP 服务器,这样就可以让 AI 自己每天去抓取最新更新的内容并总结给我,效果大概如下:

项目链接是 https://github.com/lenonline2021-source/SZU_Announcement_MCP,欢迎各位部署体验。

MCP概念

MCP (Model Context Protocol),中文翻译为模型上下文协议,该协议为大模型和外部工具之间的连接提供了标准化的接口,大模型可以通过调用基于 MCP 开发的工具,实时获取外部信息以丰富其上下文,从而更好地执行任务。

MCP组成

在 MCP 协议中含有三个核心角色:HostClientServer,Host 就是我们和大模型的对话框,比如 Codex、Cherry Studio 都是 Host,而 Client 是在单个 MCP 连接中的调用者,Server 则是 MCP 中函数工具(Tool)、提示词(Prompt)、资源(Resource)的提供者。

MCP 服务器可以部署在本地计算机上,与 Host 通过进程间通信(stdio)进行交互;或者部署在远程服务器上,通过网络协议(sse或streamableHttp)进行交互。

MPC与RPC协议

RPC(Remote Procedure Call Protocol),翻译为远程过程调用协议,它可以实现客户端在不知道调用细节的情况下,调用远程计算机上的某个对象、函数。很容易可以发现 MCP 正是基于这种协议(JSON-RPC 2.0)实现对远程主机的操作,只不过单纯的 RPC 是给程序员使用的,而 MCP 提供的工具是由大模型调用的。

可以发现大模型在调用 MCP 时,传参的形式也是 JSON 格式。

MCP提供的功能

目前官方文档中被广泛支持的功能有工具(Tool)提示词(Prompt)资源(Resource),而官方文档中宣称的取样(Sampling)等特性各家模型的支持度还亟待提升,并且也很少被实际开发中使用。

Tool

Tool 是被预定义在 MCP 服务器中的代码,大模型依据 RPC 协议可以传递参数并调用指定的代码块、获得返回结果。需要指出的是,MCP 提供的 Tool 在 Server 内运行,大模型负责向 MCP Client 发送调用指令,从 Server 获取调用结果。

Prompt

Prompt 被调用后会直接返回给大模型,大模型可以根据 Prompt 的要求有针对性地设计回答,使每一次调用 MCP 的回复都高度统一。Prompt 也可以被设计为一个模版,里面可以嵌入各种变量,这样可以适配不同的信息。

Resource

Resource 根据词义,是一个用于提供资源的组件。其实 Resource 的功能可以完全被 Tool 实现,但是 Resource 在仅读取的情况下有自身独特的优势:支持缓存、无副作用等,并且一定程度上可以优化代码的逻辑和可读性。大模型在请求 Resource 时使用类似 URI 的格式。

MCP工程开发

目前 MCP 的开发工具包已经被高度封装,使用兼容 FastMCP 的声明式语法可以轻松地写出你的第一个 MCP 项目。

首先,需要导入官方的 MCP-SDK 并声明一个 MCP 应用,比如

from mcp.server import MCPServer
​
app = MCPServer(
    'SZU公文通阅览', 
    version='1.0', 
    description='深圳大学公文通阅览工具'
)

声明Tool

使用 @app.tool 注解在函数签名上,就能将该函数声明为工具,届时可以被大模型看到并调用,比如

@app.tool(
    name="update_cookie",
    description="自动登录, 更新深圳大学公文通的Cookie"
)
async def update_cookie() -> str:
    """
    自动登录验证账号, 更新深圳大学公文通的Cookie, 当工具调用结果为空时, 可调用此方法更新cookie
​
    Returns:
        更新后的Cookie字符串
    """
    global cookie, headers
    cookie = await auth.get_szu_cookie(account=account, password=password)
    headers["Cookie"] = cookie
    return f"Cookie已更新为: {cookie}"

代码中的 namedescription 和文本块都可以被大模型看到,但是无法看到代码的具体实现。

声明Prompt

使用@app.tool 注解在函数签名上,就能将该函数声明为 Prompt。

@app.prompt(
    name="写邮件",
    description="生成一封正式邮件"
)
async def write_email(收件人: str, 主题: str) -> str:
    return f"""
请帮写一封正式邮件。
收件人:{收件人}
主题:{主题}
​
要求:
1. 开头用"尊敬的"
2. 结尾用"此致 敬礼"
3. 正文简洁明了
"""

如果用户发送了向某人写邮件的请求,大模型可能会调用到该 MCP Prompt,这样即使用户并没有说明写邮件的具体要求,大模型也会输出符合 MCP Prompt 的回复。

声明Resource

使用 @app.resource 注解在函数签名上,就能将该函数声明为 Resource。

@app.resource(
    uri="user://{user_id}/profile",
    name="用户资料",
    description="根据用户ID查询个人资料"
)
async def get_user_profile(user_id: str) -> str:
    # 连接数据库
    conn = await asyncpg.connect(
        host="localhost",
        database="mydb",
        user="admin",
        password="123456"
    )
    
    # 执行SQL查询
    row = await conn.fetchrow(
        "SELECT name, age, job FROM users WHERE id = $1",
        user_id
    )
    
    await conn.close()
    
    # 返回结果
    if row:
        return f"{row['name']}, {row['age']}岁, {row['job']}"
    else:
        return "用户不存在"

可圈可点的是,Resource 的查询可以使用 RESTful 的查询接口,对前端友好。Resource 的 URI 可以定义为静态的,也可以使用模版嵌入变量。内部的具体实现其实和 Tool 差不多。

调试MCP

使用官方的 MCP Inspector 可以对定义的 MCP 功能进行调试,使用以下命令安装或使用

npx @modelcontextprotocol/inspector

这里可以对特定的 Tool、Prompt、Resource 进行调试,以确保后续大模型调用时不会产生错误。