在上一章中,你了解了模型上下文协议(Model Context Protocol,MCP)的高层概览。你明白了它为何具有颠覆性:它能把孤立的 LLM 变成能够与世界互动的强大代理。你也看到了 MCP 如何充当 AI 工具的通用适配器的全景图。
现在,是时候下水实操,深入细节了。
本章就是那个充满魔力的 “Hello, World!” 时刻——理论与实践在此交汇。你将从一个空文件夹出发,跑起你的第一个 MCP 服务器:既有本地(在你的机器上),也有远程(通过网络)。这项能力是本书其他内容的地基。
到本章结束时,你将完成:
- 使用 uv 搭建专业的 Python 开发环境。
- 在 fastmcp 与 mcp 中,分别用
@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,从而赋予它新能力。
我们先从最简单的工具开始:接收一个名字并返回问候语。你将分别用 fastmcp 与 mcp 构建这个简单服务器,以直观感受它们的异同。
使用 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() 的调用:你现在需要指定 transport、host 和 port。
这一次,你需要先运行服务器脚本,并保持它在前台运行。
图 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 URL:http://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 中,host 与 port 通过 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 工具:
- 配置传输:在 “Transport Type” 下拉菜单中选择 Streamable HTTP。
- 填写 URL:在 “URL” 输入框中填入服务器的 MCP 端点:
http://localhost:9000/mcp/。 - 输入令牌:回到终端,复制那串很长的 Session token。在 Inspector 网页的 “Configuration” 区域,把它粘贴到 Proxy Session Token 字段。这是一种安全措施,确保只有你能使用这次 Inspector 会话。
- 连接:点击大的蓝色 Connect 按钮。如果成功,按钮会变成绿色并显示 “Connected”。
- 列出工具:在左侧导航到 Tools 选项卡,点击 List Tools。
- 运行工具:在列表中点击
greet工具。调用界面会出现。在参数区你会看到name字段,在值处输入Inspector。 - 查看结果:点击 Run Tool。右侧会显示来自服务器的原始 JSON 响应。你应能在
content区块里看到文本 "Hello, Inspector!" 。
你已经在一行客户端代码都没写的情况下,成功测试了你的 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 赋予真正有用的能力。