MCP 实战——“‘Hello, World’ 体验:你的第一个 MCP 服务器”

257 阅读13分钟

在上一章中,你了解了模型上下文协议(Model Context Protocol,MCP)的高层概览。你明白了它为何具有颠覆性:它能把孤立的 LLM 变成能够与世界互动的强大代理。你也看到了 MCP 如何充当 AI 工具的通用适配器的全景图。

现在,是时候下水实操,深入细节了。

本章就是那个充满魔力的 “Hello, World!” 时刻——理论与实践在此交汇。你将从一个空文件夹出发,跑起你的第一个 MCP 服务器:既有本地(在你的机器上),也有远程(通过网络)。这项能力是本书其他内容的地基。

到本章结束时,你将完成:

  • 使用 uv 搭建专业的 Python 开发环境。
  • fastmcpmcp 中,分别用 @mcp.tool 装饰器创建你的第一个工具。
  • 掌握两种基础传输方式:本地(STDIO)与远程(HTTP)。
  • 学会使用 MCP Inspector——你新的调试“好搭档”。

让我们开工吧。

搭建专业开发环境

在构建任何东西之前,你需要“车间”。在现代 Python 里,这意味着为项目创建独立环境,以便安装工具而不影响系统其他部分。我们选用 uv——一个极速的项目与包管理器。
你可以从此处下载并安装 uv。
打开终端(或命令提示符),开始设置。

首先,为项目创建新目录并进入该目录:

图 4. 创建目录

$ mkdir mcp_tutorial
$ cd mcp_tutorial

接着,用 uv 初始化一个新的虚拟环境。这就像为本项目准备一个干净、空的工具箱。

图 5. 初始化目录

$ uv init

你会看到出现一个 .venv 目录。项目的所有依赖库都会安装在这里。

现在,往工具箱里添置工具:我们需要官方的 mcp 库和更高级别的 fastmcp 库。uv add 会把它们下载并安装进新环境。

图 6. 安装依赖

$ uv add fastmcp
$ uv add mcp[cli]

为什么是 mcp[cli]
你可能注意到了方括号里的 [cli]mcp 包带有可选的 “extras”。cli 这个 extra 包含命令行工具。为了完整的开发体验,建议一并安装。

最后,你需要“激活”环境。这样终端才会优先使用 .venv 里的 Python 与库,而不是系统全局的。

不同操作系统的激活命令略有差异:

Windows(PowerShell)
图 7. 激活环境

> .venv\Scripts\Activate

Linux 或 macOS
图 8. 激活环境

$ source .venv/bin/activate

激活后,你会在命令行提示符前看到 (.venv) 或(你的目录名)。恭喜!你的专业开发环境就绪。现在可以构建你的第一个 MCP 服务器了。

创建基础工具(@mcp.tool

任何 MCP 服务器的核心都是“工具”(tool)。工具本质上就是一个普通的 Python 函数,你把它暴露给 LLM,从而赋予它新能力。

我们先从最简单的工具开始:接收一个名字并返回问候语。你将分别用 fastmcpmcp 构建这个简单服务器,以直观感受它们的异同。

使用 fastmcp 的方式

先从 fastmcp 入手,因为它的高层 API 让过程非常直观。

新建文件 hello_world_local_server_fast.py,写入以下代码:

图 9. hello_world_local_server_fast.py

from fastmcp import FastMCP

# 1. 初始化 MCP 服务器
mcp = FastMCP("My FastMCP Server")

# 2. 定义一个工具
@mcp.tool()
def greet(name: str) -> str:
    """A simple tool that returns a greeting."""
    return f"Hello, {name}!"

# 3. 让脚本可运行
if __name__ == "__main__":
    mcp.run()

逐段解析:

  • 初始化服务器:导入 FastMCP 并创建实例。"My FastMCP Server" 是服务器的人类可读名称。

  • 定义工具:魔法就在这里。把普通函数 greet@mcp.tool() 装饰。fastmcp 会自动检查函数:

    • 用函数名(greet)作为工具名;
    • 依据类型注解(name: str-> str)生成 schema,告诉客户端输入/输出的类型。
  • 可运行脚本if __name__ == "__main__": 是标准 Python 惯例,确保只在直接运行脚本时调用 mcp.run()。这很关键,因为稍后你的客户端脚本也会运行这个服务器脚本。

就是这样!你已经编写了一个完整的 MCP 服务器。接下来需要一个客户端与之对话。

本地运行:STDIO 传输

当工具与客户端运行在同一台机器上时,MCP 提供了一种简单高效的通信方式:STDIO 传输

STDIO 即标准输入/输出。使用这种传输时,客户端不会连接网络端口,而是把服务器脚本作为子进程启动,然后通过向子进程的标准输入写消息、从标准输出读响应来“对话”。

这非常适合本地开发:速度快、无需任何网络配置。

fastmcp 客户端

再创建一个文件 hello_world_local_client_fast.py 作为客户端。

图 10. hello_world_local_client_fast.py

import asyncio
from fastmcp import Client

# 1. 创建指向服务器脚本的客户端
client = Client("hello_world_local_server_fast.py")

async def call_tool(name: str):
    # 2. 连接服务器
    async with client:
        # 3. 调用名为 'greet' 的工具并传入参数
        output = await client.call_tool("greet", {"name": name})

        # 4. 从响应中提取文本
        extracted_text = output.content[0].text
        
        print(extracted_text)

# 5. 运行异步代码
asyncio.run(call_tool("Arjuna"))

逐步说明:

  • 创建客户端:用 Client 并传入服务器脚本路径。这个 fastmcp 的“捷径”表示:“需要通信时,就运行这个文件。”
  • 建立连接:MCP 中的客户端交互是异步的。async with client: 负责启动服务器子进程并建立 STDIO 通道。
  • 调用工具client.call_tool() 是主角。传入工具名 "greet" 与参数字典 {"name": name}
  • 提取结果call_tool 返回结构化对象。对于简单文本响应,文本位于 output.content[0].text
  • 运行asyncio.run() 启动整个异步流程。

现在,从已激活的终端运行客户端。注意你只运行客户端脚本:

图 11. 运行 hello_world_local_client_fast.py

> python .\hello_world_local_client_fast.py
Hello, Arjuna!

使用 mcp 库的方式

现在用官方 mcp 库构建同样的本地服务器与客户端,来体会设计哲学上的差异。

先写服务器,新建 hello_world_local_server_mcp.py

图 12. hello_world_local_server_mcp.py

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("My MCP Server")

@mcp.tool()
def greet(name: str) -> str:
    return f"Hello, {name}!"

if __name__ == "__main__":
    mcp.run()

看起来几乎与 fastmcp 版本一模一样!确实如此。唯一区别是导入语句:from mcp.server.fastmcp import FastMCP

关于这两个库(再谈)

正如第 1 章提到的,fastmcp 的高层服务器 API 表现出色,因此被直接合并进官方 mcp 库。这就是为什么服务器代码几乎一致。

接下来是客户端。mcp 库更加底层与显式,你需要更细粒度地配置连接细节。

新建 hello_world_local_client_mcp.py(你也可以改名或放在其他目录):

图 13. hello_world_local_client_mcp.py

import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

# 1. 定义如何运行服务器
server_params = StdioServerParameters(
    command="python",
    args=["hello_world_local_server_mcp.py"]
)

async def call_tool(name: str):
    # 2. 创建底层 STDIO 客户端
    async with stdio_client(server_params) as (read, write):
        # 3. 在其之上创建协议会话
        async with ClientSession(read, write) as session:
            # 4. 显式初始化会话
            await session.initialize()

            # 5. 在会话对象上调用工具
            result = await session.call_tool("greet", arguments={"name": name})

            extracted_text = result.content[0].text
            
            print(extracted_text)

if __name__ == "__main__":
    asyncio.run(call_tool("Arjuna"))

可以看出,这更为冗长。差异如下:

  • 服务器参数:不再只传文件名,而是创建 StdioServerParameters 对象,明确告诉客户端如何运行服务器:使用 python 命令并传入 hello_world_local_server_mcp.py 作为参数。它更灵活,但代码更多。
  • 底层客户端stdio_client 上下文管理器负责启动子进程并提供 read/write 流。
  • 客户端会话:将原始流封装进 ClientSession,该对象理解 MCP 协议本身(如何格式化请求、解析响应等)。
  • 初始化:在 mcp 库中,你必须显式调用 await session.initialize() 来完成初始握手;fastmcp 会自动处理。
  • 调用工具:在会话对象上调用工具,而不是在客户端对象上。

现在运行该客户端脚本:

图 14. 运行 hello_world_local_client_mcp.py

> python .\hello_world_local_client_mcp.py
Hello, Arjuna!

你会得到完全相同的结果。fastmcp 的客户端是一层高阶封装,替你完成了 mcp 客户端的所有步骤。对简单场景而言,fastmcp 能节省不少样板代码;而官方 mcp 库则提供对连接生命周期的更精细控制。

远程运行:HTTP 传输

STDIO 很适合本地开发,但 MCP 的真正威力在于它能跨网络连接服务。假如你的工具需要运行在一台性能更强的服务器上,或者你想把它作为服务提供给其他开发者,该怎么办?
这时你就需要 HTTP 传输

使用 HTTP 时,你会把服务器作为一个独立的、常驻进程运行,它会在特定的网络地址与端口上监听连接(例如 127.0.0.1:9000)。客户端随后可以从任何地方连接到这个 URL——同一台机器、本地网络中的另一台机器,甚至互联网。

fastmcp 方式(远程)

让我们把 fastmcp 服务器改为通过 HTTP 运行。改动非常小。
新建文件 hello_world_remote_server_fast.py

图 15. hello_world_remote_server_fast.py

from fastmcp import FastMCP

mcp = FastMCP("My Remote Server")

@mcp.tool
def greet(name: str) -> str:
    return f"Hello, {name}!"

if __name__ == "__main__":
    # 唯一的改动就在这里!
    mcp.run(transport="http", host="127.0.0.1", port=9000)

与之前相比,区别只在 mcp.run() 的调用:你现在需要指定 transporthostport

这一次,你需要运行服务器脚本,并保持它在前台运行。

图 16. 运行远程 MCP 服务器(fastmcp)

> python .\hello_world_remote_server_fast.py

你会看到 fastmcp 打印出一个很“酷”的启动横幅:

图 17. 远程 MCP 服务器脚本输出

╭─ FastMCP 2.0 ────────────────────────────────────────────────────────╮
│                                                                        │
│     _ __ ___ ______           __  __  _____________       ____    ____ │
│    _ __ ___ / ____/___ ______/ /_/  |/  / ____/ __ \     |___ \  / __ \│
│   _ __ ___ / /_  / __ `/ ___/ __/ /|_/ / /   / /_/ /     ___/ / / / / /│
│  _ __ ___ / __/ / /_/ (__  ) /_/ /  / / /___/ ____/     /  __/_/ /_/ / │
│ _ __ ___ /_/    __,_/____/__/_/  /_/____/_/         /_____(_)____/  │
│                                                                        │
│     🖥️ Server name:     My Remote Server                              │
│     📦 Transport:       Streamable-HTTP                               │
│     🔗 Server URL:      http://127.0.0.1:9000/mcp/                    │
│                                                                        │
...
INFO:     Uvicorn running on http://127.0.0.1:9000 (Press CTRL+C to quit)

务必注意其中的 Server URLhttp://127.0.0.1:9000/mcp/。客户端需要连接的就是这个端点。先把这个终端窗口留着不要关。

现在来写客户端。打开一个新的终端窗口,并在其中同样激活虚拟环境。创建 hello_world_remote_client_fast.py

图 18. hello_world_remote_client_fast.py

import asyncio
from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport

# 1. 使用服务器 URL 定义传输方式
transport = StreamableHttpTransport("http://localhost:9000/mcp/")

# 2. 通过该传输创建客户端
client = Client(transport=transport)

async def main(name: str):
    async with client:
        output = await client.call_tool("greet", {"name": name})

        extracted_text = output.content[0].text

        print(extracted_text)

asyncio.run(main("Arjuna"))

改动点依然体现在客户端的配置方式上:

  • 定义传输:引入 StreamableHttpTransport 并实例化,传入你刚才从服务器输出中记下的 URL。
  • 创建客户端:将该传输对象通过 transport= 传给 Client 构造函数。

现在运行客户端:

图 19. 运行远程客户端(fastmcp)

> python .\hello_world_remote_client_fast.py
Hello, Arjuna!

成功!你的客户端通过 HTTP 连接到了正在运行的服务器,并获得了正确响应。

mcp 库方式(远程)

我们用官方 mcp 库做同样的事情。服务器代码在配置方式上有一个小但重要的不同。
创建 hello_world_remote_server_mcp.py

图 20. hello_world_remote_server_mcp.py

from mcp.server.fastmcp import FastMCP

# 1. 在构造函数中配置主机与端口
mcp = FastMCP("My MCP Server", host="127.0.0.1", port=9000)

@mcp.tool()
def greet(name: str) -> str:
    return f"Hello, {name}!"

if __name__ == "__main__":
    # 2. 在 run 方法中指定传输类型
    mcp.run(transport="streamable-http")

mcp 中,hostport 通过 FastMCP 的构造函数传入,而传输类型("streamable-http")通过 run() 指定。

确保你停止了之前的服务器。运行这个服务器,你会看到更接近底层 Uvicorn 的标准输出:

图 21. 运行远程 MCP 服务器(mcp)

> python .\hello_world_remote_server_mcp.py
INFO:     Started server process [28768]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:9000 (Press CTRL+C to quit)

保持该进程运行。接下来在新的已激活终端中创建 hello_world_remote_client_mcp.py

图 22. hello_world_remote_client_mcp.py

import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

async def main(name: str):
    # 1. 使用 streamablehttp_client 上下文管理器
    async with streamablehttp_client("http://127.0.0.1:9000/mcp/") as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()

            output = await session.call_tool("greet", arguments={"name": name})
            
            extracted_text = output.content[0].text

            print(extracted_text)

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

整体结构与 mcp 的 STDIO 客户端非常相似。关键变化是将 stdio_client 替换为 streamablehttp_client,并传入服务器 URL。

关于下划线变量是什么?
你可能注意到了 (read, write, _) 这个三元组。streamablehttp_client 会返回第三个值,包含一些 HTTP 特有的信息,比如响应头。对于我们这个简单的工具调用不需要它,所以按 Python 惯例把它赋给占位变量 _

运行客户端:

图 23. 运行远程客户端(mcp)

> python .\hello_world_remote_client_mcp.py
Hello, Arjuna!

就这样!你已经分别使用两大库,成功构建并与一个网络化的 MCP 服务器通信。

MCP Inspector:你的调试“必备搭档”

到目前为止,你一直用 Python 客户端测试 Python 服务器。但如果你只是想快速确认某个工具是否工作正常,或者想查看它返回的原始 JSON,每次都写一个客户端脚本就太麻烦了。

这就轮到 MCP Inspector 登场了。它是一个基于 Web 的应用,作用类似 Postman 或 Insomnia,但专为 Model Context Protocol 设计。对开发与调试来说,它是不可或缺的工具。

Inspector 是一个 Node.js 应用。请先确保已安装 Node.js。npx(随 Node.js 附带)可以一步下载并运行它。

确保你的远程服务器仍在运行。然后在一个新的终端里,运行:

图 24. 运行 MCP Inspector

> npx @modelcontextprotocol/inspector

第一次运行时,npx 会询问是否允许安装该包。输入 y 并回车。随后会看到类似这样的启动输出:

图 25. MCP Inspector 启动日志

...
Starting MCP inspector...
⚙️ Proxy server listening on localhost:6277
🔑 Session token: 2de1f4fe39be7c7a2e5ce2b3df254861689a0f0602756baca19ea3a4dad25217
   Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth

🚀 MCP Inspector is up and running at:
   http://localhost:6274

🌐 Opening browser...

浏览器应会自动打开 http://localhost:6274。如果没有,请手动复制粘贴该地址。你将看到 Inspector 的界面。

现在,用它来测试正在运行的 greet 工具:

  1. 配置传输:在 “Transport Type” 下拉菜单中选择 Streamable HTTP
  2. 填写 URL:在 “URL” 输入框中填入服务器的 MCP 端点:http://localhost:9000/mcp/
  3. 输入令牌:回到终端,复制那串很长的 Session token。在 Inspector 网页的 “Configuration” 区域,把它粘贴到 Proxy Session Token 字段。这是一种安全措施,确保只有你能使用这次 Inspector 会话。
  4. 连接:点击大的蓝色 Connect 按钮。如果成功,按钮会变成绿色并显示 “Connected”。
  5. 列出工具:在左侧导航到 Tools 选项卡,点击 List Tools
  6. 运行工具:在列表中点击 greet 工具。调用界面会出现。在参数区你会看到 name 字段,在值处输入 Inspector
  7. 查看结果:点击 Run Tool。右侧会显示来自服务器的原始 JSON 响应。你应能在 content 区块里看到文本 "Hello, Inspector!"

image.png

你已经在一行客户端代码都没写的情况下,成功测试了你的 MCP 服务器!Inspector 是个强大的助手,它能让你把精力专注在构建服务器逻辑上。

关键要点(Key Takeaways)

  • 开发环境:你已掌握如何用 uv 搭建干净、隔离的 Python 项目,并安装所需的 MCP 库。
  • 核心工具@mcp.tool 装饰器是 MCP 服务器的“心脏”,能把一个普通的 Python 函数瞬间变成 AI 可用的能力。
  • STDIO vs. HTTP:你已经理解两种主要传输方式——STDIO 适合快速的本地子进程通信,HTTP 用于创建可独立运行、可联网访问的服务器。
  • fastmcp vs. mcp:你看到了两者的实践差异——fastmcp 提供更简洁的高层客户端体验,而官方 mcp 库在连接生命周期上提供更显式、细粒度的控制。
  • Inspector 是你的朋友:你学会使用 MCP Inspector 连接并测试服务器,这会显著提升你的开发调试效率。

你已经完成了自己的 “Hello, World”。你拨下开关,看见了指示灯亮起。在下一章里,我们将超越简单的问候语,开始构建更复杂的工具,为你的 AI 赋予真正有用的能力。