TellWorldAI 世界档案
问档案English

首页›实践›实践

实践

写一个 MCP 客户端:连接服务器并调用工具

写一个能启动 MCP 服务器、列出并调用它的工具、资源和提示词的客户端,先用 TypeScript 或 Python 跑通第一次调用。

这篇适合谁、读完能做到什么

你已经写过一个 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 许可证发布。

来源

  1. 1
    Creating a clientMicrosoft mcp-for-beginners (MIT)

引用本页: 写一个 MCP 客户端:连接服务器并调用工具. TellWorld, https://www.tellworld.net/zh/practice/practice-mcp-client. 更新于 2026-11-14

相关动态