这篇适合谁、读完能做到什么
你已经写过一个 MCP 服务器,想让自己的脚本或程序直接调用它,而不是每次都靠图形界面手动试。读完这篇,你应该能写出一个客户端:从代码里启动服务器,列出它提供的工具、资源和提示词,并调用其中的工具。[1]
客户端是什么,和 Inspector 有什么不同
MCP 客户端是直接与 MCP 服务器通信的应用或脚本,用来请求服务器的资源(resources)、工具(tools)和提示词(prompts)。Inspector 提供图形界面,适合手动试用服务器;自己写客户端则可以用代码发出同样的请求,因此能做自动化任务,也能把 MCP 能力接进自己的工作流。[1]
写一个客户端通常分四步:导入库;创建客户端并连接到选定的传输方式;决定要列出哪些资源、工具和提示词;最后把客户端接入宿主应用,让用户的请求能触发对应的服务器功能。学习目标是理解客户端能做什么、写出自己的客户端,并用它连接服务器,确认服务器按预期工作。[1]
准备:选择传输方式
本文用的是 stdio 传输。它适合在自己电脑上运行的程序,另一种选择是 SSE。[1] 在 stdio 传输里,command 和 args 告诉客户端去哪里找服务器、怎么启动它,所以客户端会自己把服务器进程拉起来。客户端也可以连接一个已经在运行的服务器,小结里专门提到了这两种情况。[1]
TypeScript 版:连接、列出、调用
第一步是导入 Client 和 StdioClientTransport。Client 来自 SDK 的 client/index.js,StdioClientTransport 来自 client/stdio.js。[1]
第二步是创建传输和客户端,然后连接。客户端创建时传入名称和版本号,这里分别是 example-client 和 1.0.0:
const transport = new StdioClientTransport({
command: "node",
args: ["server.js"]
});
const client = new Client(
{
name: "example-client",
version: "1.0.0"
}
);
await client.connect(transport);这段代码执行后,客户端就通过 stdio 与 server.js 相连。[1]
第三步是列出服务器的功能。用 listPrompts、listResources 和 listTools 分别列出提示词、资源和工具:
// List prompts
const prompts = await client.listPrompts();
// List resources
const resources = await client.listResources();
// list tools
const tools = await client.listTools();第四步是调用。调用工具时传入 name 和 arguments:
const result = await client.callTool({
name: "example-tool",
arguments: {
arg1: "value"
}
});获取提示词用 getPrompt,同样传入名称和参数。例子请求的是 review-code,并传入 code 参数,它对应服务器端用 code: z.string() 声明的提示词:
const promptResult = await client.getPrompt({
name: "review-code",
arguments: {
code: "console.log(\"Hello world\")"
}
})要让它跑起来,做法是在 package.json 的 scripts 里加一条 client 命令,内容是 tsc && node build/client.js,然后执行 npm run client。[1]
Python 版:同样的步骤
Python 版用 StdioServerParameters 描述怎样启动服务器。这里的可执行文件是 mcp,参数是 run server.py,env 可以不填:
server_params = StdioServerParameters(
command="mcp", # Executable
args=["run", "server.py"], # Optional command line arguments
env=None, # Optional environment variables
)接着用 stdio_client 建立连接,再用 ClientSession 包住它,并调用 session.initialize() 完成初始化:
async with stdio_client(server_params) as (read, write):
async with ClientSession(
read, write
) as session:
# Initialize the connection
await session.initialize()列出资源和工具用 session.list_resources() 和 session.list_tools():
# List available resources
resources = await session.list_resources()
print("LISTING RESOURCES")
for resource in resources:
print("Resource: ", resource)
# List available tools
tools = await session.list_tools()
print("LISTING TOOLS")
for tool in tools.tools:
print("Tool: ", tool.name)读取资源和调用工具分别用 read_resource 和 call_tool。读取的是 greeting://hello,调用的是 add 工具:
# Read a resource
print("READING RESOURCE")
content, mime_type = await session.read_resource("greeting://hello")
# Call a tool
print("CALL TOOL")
result = await session.call_tool("add", arguments={"a": 1, "b": 7})
print(result.content)运行方式是 python client.py。[1]
被调用的服务器长什么样
上面的客户端要和一个服务器配合。练习服务器提供一个加法工具 add,以及一个按名字问候的资源 greeting://{name}。TypeScript 版用 server.tool 声明加法工具:
server.tool("add",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }]
})
);Python 版用 FastMCP,通过装饰器声明工具和资源:
# server.py
from mcp.server.fastmcp import FastMCP
# Create an MCP server
mcp = FastMCP("Demo")
# Add an addition tool
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
# Add a dynamic greeting resource
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
"""Get a personalized greeting"""
return f"Hello, {name}!"客户端请求的名称和参数必须和服务器声明的一致。强调,调用功能时要传对参数,有时还要传对名称。[1]
组装一个客户端的顺序
把两种语言的例子放在一起看,结构是一致的:先导入库,再创建传输并启动、连接服务器,然后列出功能,最后在需要的地方调用。Python 版用两层 async with 嵌套,外层建立 stdio 连接,内层创建 ClientSession 并初始化,列出和调用都放在这个上下文里完成。TypeScript 版的 connect 接收的就是前面创建的 transport,传输方式和客户端在这一步绑在一起。[1]
常见问题和限制
- 先确认服务器能单独启动。 客户端按
command和args启动服务器,如果这条启动命令本身跑不通,客户端就连不上,问题往往出在服务器而不是客户端。[1] - 名称或参数对不上。 客户端调用时的工具名、提示词名、资源 URI 和参数,要和服务器端的声明一致,否则调用会失败。[1]
- Java 示例要先启动服务器。 Java 版使用 SSE 传输,指向
http://localhost:8080,所以必须先让 MCP 服务器在该地址上运行,再启动客户端。[1] - 这个客户端很简单。 它只在代码里明确写出调用时才执行,没有模型替它做决定。[1]
- 语言不同,命令也不同。 .NET 版用
dotnet run运行;StdioClientTransport的Arguments既可以指向.csproj,也可以指向可执行文件。[1]
为什么先列出再调用
思路是先发现、再调用。列出工具、提示词和资源,客户端就能知道服务器实际提供了什么,再按名称和参数去调用。写自动化脚本时,这一步可以让代码核对服务器的实际能力,而不是只依赖写在代码里的假设。小结也把客户端概括为两件事:既能发现服务器的功能,也能调用它们。[1]
资源地址和模板怎么对应
读取资源时传入的 URI 要和服务器声明的模板对应。例子里,file://example.txt 匹配服务器的 file://{name} 模板,example.txt 被映射到 name 参数上。Python 版的练习服务器也是用 @mcp.resource("greeting://{name}") 这样的模板声明问候资源的,客户端读取 greeting://hello 时走的是同一套匹配规则。[1]
客户端和 Inspector 怎么配合
Inspector 适合人工逐个试:看工具列表、手动填参数、看返回结果。客户端适合把同样的调用写成代码,反复执行,或者嵌入自己的程序。把客户端和 Inspector 并列,称两者都是测试服务器能力的好办法。调试服务器时可以先用 Inspector 看清它提供了什么,再写客户端固定下来。[1]
下一步
先用 Inspector 检查服务器的工具和资源,再写客户端调用它们,两者结合起来测试最省事。仓库的 solution 目录给出了 TypeScript、Java、Python、.NET 和 Rust 的完整客户端和服务器,每个目录都有自己的说明,可以直接对照运行。[1]
本文改编自 Microsoft 开源课程 *mcp-for-beginners* 入门部分第 2 课《Creating a client》[1],原文以 MIT 许可证发布。
来源
- 1Creating a clientMicrosoft mcp-for-beginners (MIT)
引用本页: 写一个 MCP 客户端:连接服务器并调用工具. TellWorld, https://www.tellworld.net/zh/practice/practice-mcp-client. 更新于 2026-11-14