在本章中,我们将介绍构建与测试服务器的基础。我们会从一个使用 STDIO 传输方式的简单服务器开始。STDIO 传输是一种通过标准输入与标准输出流与服务器通信的简单方法。用 STDIO 构建服务器非常适合入门 模型上下文协议(MCP) 并理解其工作原理。还需要补充的是,STDIO 传输是与服务器通信的最常见方式,主要用于运行在你本机上的服务器。
作为本章的一部分,我们还将介绍测试服务器的不同方式。确保我们构建的内容按预期工作非常重要。我们会涵盖可视化工具、CLI(命令行)以及代码中的多种测试方法。
在本章中,你将学到如何:
- 使用 STDIO 传输构建一个简单服务器
- 描述服务器的核心概念
- 通过不同工具测试服务器
本章涵盖以下主题:
- 一个 STDIO 服务器
- 概念
- 运行时
- 测试服务器
- 第一个服务器
一个 STDIO 服务器
使用 STDIO 传输意味着服务器通过标准输入/输出流与客户端通信。这表示服务器可以通过标准输入(stdin)读取来自客户端的输入,并通过标准输出(stdout)把输出返回给客户端。听起来很简单,但它具体如何工作呢?
MCP 使用 JSON-RPC 2.0 作为其线路格式。因此,它需要把流式消息转换为 JSON-RPC 格式以进行传输,并将接收到的 JSON-RPC 消息再转换回流式消息。想象一下在终端中的如下示例:
>> hello world
要在 STDIO 服务器中使用,上述消息会被转换为 JSON-RPC 格式,看起来如下:
{
"jsonrpc": "2.0",
"method": "sendMessage",
"params": {
"message": "hello world"
},
"id": 1
}
这个例子很有意思,但对开发者而言,真正重要的是两点:
- 如何构建一个 STDIO 服务器。 我们很快会在后续小节中展示。
- 为什么重要,以及“你的服务器是 STDIO 服务器”意味着什么。 这意味着你的服务器运行在本机上,并通过标准输入/输出流与客户端通信。我们会在第 4 章和第 5 章展示如何构建能通过互联网与其他服务器通信的服务器。
概念
接下来看看服务器可以提供的核心概念(也可理解为“特性”)。
Resources(资源)
这是服务器可以提供给客户端的数据与上下文。MCP 的预期用法是:客户端在与服务器通信时带有一个 LLM。此用例中,资源作为上下文,在提示(prompt)时可添加给 LLM。设想如下场景的发生:
图 3.1——资源场景
在该场景中,这些上下文确保终端用户得到更好的结果,因为服务器的上下文与用户的提示配对,有点像简化版的 RAG(检索增强生成) 模式。也就是说,你将用户的提示与自己的数据配对,以获得更优的响应。
一个按这种思路实现的具体示例是用户像这样请求商品:
Prompt
用户:我在找一台新笔记本电脑
图 3.2——资源交互示例
在这个特定的商品查询中,我们先调用资源以了解该查询应访问哪张表,然后让 LLM 识别应调用哪一个工具。由于调用了资源,我们获得了关于工具选择与参数设置的额外知识。
资源也可以在不使用 LLM的情况下被使用,但思考你服务器的方式应当是:它的存在是为了增强客户端的 LLM。这意味着你提供的工具(tools)、资源(resources)和提示(prompts)都应当是有帮助的。
既然明白了资源何时使用,我们再谈谈它们的性质。资源是静态的,可以是服务器能够访问并与客户端共享的任何内容。需要了解的是:你可以带模板或不带模板地请求某个资源。如果只有一个文件或一项应用配置,使用固定名称(如 config)就很合理:
server.resource(
"config",
"config://app",
async (uri) => ({
contents: [{
uri: uri.href,
text: "App configuration here"
}]
})
);
在这里,每次我们都返回相同类型的信息。
然而,如果设置项种类很多,比如用户设置、日历设置等等,那么创建一个模板化版本就更合理,例如 settings://{type}:
@mcp.resource("settings://{type}")
def get_setting(type: str) -> str:
"""Get a specific setting"""
return f"Setting from file {type}!"
这些设置仍然是非变更的(静态) ,但它们数量众多,因此我们选择在同一命名空间下进行归类。
在此例中,资源名为 settings,并接收 type 作为参数。例如,settings://hello 就会匹配这个模式。
Tools(工具)
工具是服务器可以执行的函数或能力。例如数据处理函数、调用某个 API 抓取数据的工具等。对于工具,我们需要通过提供**模式(schema)**来定义输入与输出。具体写法会随所用的运行时不同而有所差异,但核心思想是:应当让工具的消费者清楚输入与输出是什么。例如,如果我们有一个接收两个数字并返回其乘积的工具,可以这样定义:
# Add a multiplication tool
@mcp.tool()
def multiply(a: int, b: int) -> int:
"""Multiply two numbers"""
return a * b
在这个例子中,我们定义了一个名为 multiply 的工具,它接收两个数字作为输入并返回它们的乘积。输入与输出通过 Python 的类型注解来定义。
Prompts(提示)
提示是模板化的消息或工作流,用来引导客户端与服务器之间的交互。一个好的例子是:在电商应用的上下文中,帮助客户端撰写商品描述或标语的模板。与资源和工具类似,提示也可以接收输入。下面是一个接收商品名称并返回商品描述的提示示例:
@mcp.prompt()
def describe_product(product: str) -> str:
return f"Write a product description for {product}"
这里我们定义了一个名为 describe_product 的提示,它接收商品名称作为输入并返回商品描述。输入通过 Python 类型注解来定义。
现在我们知道服务器可以包含哪些内容,那么我们还需要了解些什么呢?
运行时(Runtimes)
当前 MCP 官方支持的运行时包括 TypeScript、Python、.NET、Java 与 Kotlin、Rust、Go。支持列表仍在不断增加。要获取最新的运行时清单,请参阅 MCP 文档:modelcontextprotocol.io/docs/sdk。每种运行时都有相应可用的 SDK。它们的实现思路相似,但也存在一些差异。
很兴奋吧?那就开始吧!
测试服务器(Testing the server)
用于测试服务器的工具有很多。之所以要测试,是为了确保服务器按预期工作。本章将介绍以下工具。
Inspector
这是一个可以同时提供 UI 和 CLI 界面的命令行工具;CLI 更适合脚本与自动化场景。
Inspector 工具通过 npx 运行一个 Node.js 包,因此请确保已安装 Node.js 运行时。即便你通过 Python 命令来调用 inspector,本质上也是 Python 去包装并调用底层的 Node.js 进程。
UI 主要用于手动测试与调试。下例展示如何运行 inspector 工具。运行下面的命令时,请确保所在目录与服务器文件相同。
Python SDK 安装了一个可执行程序 mcp,可用于帮助运行服务器:
mcp dev server.py
通过该工具,我们可以在可视化模式下测试服务器。下图是 inspector 工具的截图:
图 3.3——Inspector 工具
请确保在可视化工具中指定以下字段:
- Transport Type:STDIO
- Command:
mcp - Arguments:
run server.py
你也可以在 CLI 模式 下运行 inspector,这对脚本与自动化很有用。命令与前面几乎相同,但需要增加 --cli 标志:
npx @modelcontextprotocol/inspector --cli mcp run server.py --method tools/list
这里我们添加了 --method 参数,并跟上 tools/list,表示要列出服务器上的所有工具。
请注意,前面 Python 侧的 mcp dev 实际是对 Node.js 工具(即 inspector)的一层封装。如果你想完全访问 inspector 的全部功能,建议直接运行它(无论是给 Node.js 还是 Python 用),也就是如下所示的命令:
npx @modelcontextprotocol/inspector
cURL
像 cURL 这样的标准命令行工具可用于向服务器发送请求,这通常用于测试 SSE 或 Streamable HTTP 作为传输方式的服务器。其他能发起 Web 请求的工具也同样适用。需要注意的是,curl 只有在服务器对外提供网络访问(例如 SSE 服务器)时才可用。对于 STDIO 服务器,你需要使用 inspector 或自定义客户端来发送请求。来看一个典型的 curl 命令:
curl -X POST -H "Content-Type: application/json" -d '{"method": "tools/list", "params": {}, "id": 1}' http://localhost:3000/sse
在上述命令中,我们向服务器发送了一个 POST 请求,调用 tools/list 方法。服务器会返回可用工具的列表。这是使用 CLI 模式 inspector 的一个不错替代方案。
测试(Tests)
你也可以为服务器编写单元测试。这不仅是对使用 inspector 的有益补充,还能在 CI/CD 流水线中运行,用于确保服务器按预期工作。你可以使用任意测试框架,并向服务器添加资源、工具与提示,然后对其进行测试。下面是一个测试示例:
@pytest.mark.anyio
async def test_add_tool_decorator(self):
mcp = FastMCP()
@mcp.tool()
def add(x: int, y: int) -> int:
return x + y
assert len(mcp._tool_manager.list_tools()) == 1
在上述示例中,我们完成了以下工作:
- 使用 pytest 框架测试服务器
- 创建一个新的服务器实例并添加名为 add 的工具
- 断言该工具已被添加到服务器上,且工具数量等于 1
这是一个简单的测试,但展示了如何利用 pytest 来测试服务器。
现在,我们已经对服务器的概念与特性有了良好的把握;接下来,让我们写下构建第一个服务器的计划。
第一个服务器(First server)
要构建我们的第一个服务器,先按以下步骤搭建一个简单服务器:
-
创建新项目:新建项目并安装所需依赖,设置必要的环境。强烈建议创建虚拟环境,以避免全局安装库。使用虚拟环境可使各项目相互隔离,避免库版本冲突。
-
安装依赖:根据你所用的运行时不同,列出并安装本项目的依赖。
-
添加服务器代码:在这里加入服务器功能;同时讨论该功能的输入、输出及其 schema。
-
用 Inspector 测试服务器:Inspector 可以帮助你确认新功能工作正常。它既能以 CLI 模式 运行,也能以可视化模式(提供浏览器 UI)运行:
- CLI 模式适合 CI/CD 场景,因为它在终端返回 JSON
- 可视化模式更适合开发者手动确认服务器行为是否正确
让我们开始写代码吧!
第 1 步:创建新项目
在编写任何代码之前,我们需要一个项目骨架,以便顺利开展。项目中将包含编写服务器所需的一切,以及用于测试与运行服务器的测试与脚本。
创建如下目录结构:
├── src/
|---- server.py
创建虚拟环境:
python -m venv venv
激活虚拟环境:
venv\Scripts\activate
你应当能在终端提示符中看到虚拟环境的名称。这说明虚拟环境已被激活,之后安装的包都会进入该环境。
很好!现在我们已经有了虚拟环境与目录结构。接下来安装依赖。
第 2 步:安装依赖
在终端运行以下命令:
pip install "mcp[cli]"
这将安装 MCP SDK 以及 CLI 工具。
第 3 步:添加服务器代码
将以下代码加入 server.py:
# server.py
from mcp.server.fastmcp import FastMCP
# Create an MCP server
mcp = FastMCP("Demo")
# Add a multiply tool
@mcp.tool()
def multiply(first: int, second: int) -> int:
"""Multiply two numbers"""
return first * second
# Add a dynamic greeting resource
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
"""Get a personalized greeting"""
return f"Hello, {name}!"
@mcp.prompt()
def review_code(code: str) -> str:
return f"Please review this code:\n\n{code}"
上述代码完成了以下工作:
- 创建名为 Demo 的 MCP 服务器实例
- 添加名为 multiply 的工具,接收两个数字输入并返回它们的乘积(译注:原文一句话写成“返回它们的和”,应为乘积,以代码为准)
- 添加名为 greeting 的资源,接收姓名并返回个性化问候
- 创建名为 review_code 的提示(prompt),接收代码片段并返回代码评审提示语
第 4 步:使用 Inspector 测试服务器
这里我们使用 Inspector 测试服务器。Inspector 是一个 CLI 工具,既能提供 UI,也能提供 CLI 界面;CLI 适合脚本与自动化,UI 适合手动测试与调试。
在终端运行:
mcp dev server.py
这会在 端口 6274 上启动 Inspector 工具。
在浏览器访问:
http://localhost:6274
它会启动一个带可视化界面的 Web 服务,允许你测试示例。
在可视化界面中,请按如下填写字段:
- Command:
"mcp" - Arguments:
"run server.py"
点击 Connect。
切换到 Tools 标签并点击 List Tools,应能看到 multiply。点击 multiply,并按如下填写:
- first:
2 - second:
4
你应在 Tool Result 字段中看到结果 8。
很好!现在我们已经有了一个可工作的服务器,并能用 Inspector 进行测试。
第 5 步:以 CLI 模式使用 Inspector 测试
在本小节中,我们直接以 CLI 模式 运行 Inspector。Inspector 是一个 Node.js 应用,而 mcp dev 只是其包装。
在撰写本文时,mcp dev 尚未支持 Inspector 的全部功能。因此我们展示如何直接以 Node.js 应用的方式运行 Inspector(这也是它的实现方式)。
下面是一些在 Inspector 中可运行的实用命令:
列出工具(List tools)
npx @modelcontextprotocol/inspector --cli mcp run server.py --method tools/list
这会列出服务器上所有可用工具,你应看到如下输出(示例):
{
"tools": [
{
"name": "multiply",
"description": "Multiply two numbers",
"inputSchema": {
"type": "object",
"properties": {
"first": { "title": "First", "type": "integer" },
"second": { "title": "Second", "type": "integer" }
},
"required": ["first", "second"],
"title": "multiplyArguments"
},
"outputSchema": {
"type": "object",
"properties": {
"result": { "title": "Result", "type": "integer" }
},
"required": ["result"],
"title": "multiplyOutput"
}
}
]
}
这里你能以 JSON 形式看到服务器上的全部工具。我们当前只有一个 multiply,可以看到其 inputSchema 里包含 first 与 second 参数。
调用工具(Call a tool)
npx @modelcontextprotocol/inspector --cli mcp run server.py --method tools/call --tool-name multiply --tool-arg first=2 --tool-arg second=4
你应看到类似以下响应:
{
"content": [
{ "type": "text", "text": "8" }
],
"structuredContent": { "result": "8" },
"isError": false
}
列出资源(List resources)
npx @modelcontextprotocol/inspector --cli mcp run server.py --method resources/list
这会列出服务器上所有资源,你可能会看到如下输出:
{
"resources": []
}
怎么是空的?原因在于资源与模板化资源的区别。用下面命令即可列出模板化资源:
npx @modelcontextprotocol/inspector --cli mcp run server.py --method resources/templates/list
你应得到类似:
{
"resourceTemplates": [
{
"uriTemplate": "greeting://{name}",
"name": "get_greeting",
"description": "Get a personalized greeting"
}
]
}
读取模板化资源(Call a templated resource)
npx @modelcontextprotocol/inspector --cli mcp run server.py --method resources/read --uri greeting://chris
你应看到如下响应:
{
"contents": [
{
"uri": "greeting://chris",
"mimeType": "text/plain",
"text": "Hello, chris!"
}
]
}
列出提示(List prompts)
npx @modelcontextprotocol/inspector --cli mcp run server.py --method prompts/list
输出类似:
{
"prompts": [
{
"name": "review_code",
"description": "",
"arguments": [
{ "name": "code", "required": true }
]
}
]
}
调用提示(Call a prompt)
npx @modelcontextprotocol/inspector --cli mcp run server.py --method prompts/get --prompt-name review_code --prompt-args code="print('Hello World')"
你应看到如下响应:
{
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Please review this code:\n\nprint('Hello World')"
}
}
]
}
(可选)你还可以在 server.py 中再添加一个资源,例如:
@mcp.resource("command://ping")
def get_echo() -> str:
"""Send pong"""
return "Pong"
然后这样读取:
npx @modelcontextprotocol/inspector --cli mcp run server.py --method resources/read --uri command://ping
你应看到如下响应:
{
"contents": [
{
"uri": "command://ping",
"mimeType": "text/plain",
"text": "Pong"
}
]
}
请注意,你在调用资源与资源模板时,都是通过同一个 uri 参数来完成的。
总结
在本章中,我们讲解了如何构建你的第一个服务器。对于入门示例,我们使用 STDIO 传输方式,创建了一个运行在本机上的服务器。我们还借助名为 inspector 的工具,探索了多种测试服务器功能的方法。Inspector 提供两种模式:CLI 模式与可视化模式。前者适用于 CI/CD 场景,后者便于开发者快速尝试与调试功能。
在接下来的章节中,我们将介绍 SSE 传输方式——当你希望服务器通过 URL 被访问与使用时,可以选用该方式。