这篇适合谁、读完能做到什么
你已经能写一个 MCP 客户端,会列出服务器的工具并手动调用。接下来想做的是让用户直接用一句话提问,由模型判断该调用哪个工具。读完这篇,你应该能把服务器的工具转换成模型可用的格式,并写出一个从用户问题到工具调用的完整流程。[1]
为什么要给客户端接上大语言模型
出发点是:让用户自己去调用 MCP 的工具并不实用。用户期待的是用自然语言交流,并不关心能力是不是通过 MCP 存放的。解决办法就是在客户端里加一个大语言模型。[1] 学习目标有三个:用大模型创建客户端,通过模型与 MCP 服务器交互,并让客户端的最终用户体验更好。[1]
整体流程
把流程分成四步:
- 连接服务器。
- 列出服务器的能力,包括提示词、资源和工具,并保存它们的结构(schema)。
- 加入大语言模型,把保存下来的能力和结构转换成模型能理解的格式交给它。
- 处理用户的提问:把提问和客户端列出的工具一起交给模型。[1]
前两步在连接服务器时做一次,后两步则在每次用户提问时进行,所以列出和转换工具的代码应该放在连接之后、处理提问之前。[1]
准备:模型服务和环境变量
练习使用 Microsoft Foundry。步骤是创建一个 Foundry 资源,部署一个可用的模型,例如 gpt-5.1,然后设置三个环境变量:AZURE_OPENAI_ENDPOINT、AZURE_OPENAI_API_KEY 和 AZURE_OPENAI_DEPLOYMENT。[1]
特别提醒两点。第一,GitHub Models 已在 2026 年 7 月 30 日退役,所以不能再沿用它。第二,API 调用里使用的是部署名称,它可能和底层模型名称不同。选模型之前,还要查看 Microsoft Foundry 的模型退役时间表。[1]
第一步:连接服务器
TypeScript 版在类的构造函数里创建 OpenAI 客户端,baseURL 由端点加上 /openai/v1/ 组成;如果 AZURE_OPENAI_ENDPOINT 或 AZURE_OPENAI_API_KEY 没有设置,构造函数会直接抛出错误。同一个类里还有一个 MCP Client,名称是 example-client。[1]
Python 版的导入和连接方式与前面的客户端相同:从 mcp 导入 ClientSession、StdioServerParameters 和 types,从 mcp.client.stdio 导入 stdio_client,服务器的启动命令是 mcp run server.py。[1]
第二步:列出能力并保存结构
Python 版列出资源和工具,对每个工具打印名称和 inputSchema 中的 properties。工具的结构要保存下来,后面才能交给模型:
# 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)
print("Tool", tool.inputSchema["properties"])TypeScript 版用 this.client.listTools() 拿到工具列表,每个工具的 inputSchema 作为 input_schema 传给转换函数。[1]
第三步:把工具转换成模型能理解的格式
大模型需要的是 OpenAI 风格的函数工具定义。TypeScript 版的 openAiToolAdapter 返回一个 type 为 function 的对象,参数的 properties 和 required 取自工具的 input_schema:
return {
type: "function" as const, // Explicitly set type to "function"
function: {
name: tool.name,
description: tool.description,
parameters: {
type: "object",
properties: tool.input_schema.properties,
required: tool.input_schema.required,
},
},
};Python 版用 convert_to_llm_tool 完成同样的事,它把 MCP 工具转换成一个字典,parameters 的 properties 同样来自 inputSchema:
def convert_to_llm_tool(tool):
tool_schema = {
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"type": "function",
"parameters": {
"type": "object",
"properties": tool.inputSchema["properties"]
}
}
}
return tool_schema转换完成后,Python 版把所有工具收集到 functions 列表里:
functions = []
for tool in tools.tools:
print("Tool: ", tool.name)
print("Tool", tool.inputSchema["properties"])
functions.append(convert_to_llm_tool(tool))第四步:处理用户的提问
TypeScript 版的 run 方法把用户消息 What is the sum of 2 and 3? 交给模型。模型名取自环境变量 AZURE_OPENAI_DEPLOYMENT,没有设置时用 gpt-5.1,max_completion_tokens 设为 1000,转换好的工具通过 tools 传入:
let response = this.openai.chat.completions.create({
model: process.env.AZURE_OPENAI_DEPLOYMENT ?? "gpt-5.1",
max_completion_tokens: 1000,
messages,
tools: tools,
});模型返回的工具调用由 callTools 处理。它读出每个调用的名称和参数,再通过 MCP 客户端执行:
// 2. Call the server's tool
const toolResult = await this.client.callTool({
name: toolName,
arguments: JSON.parse(args),
});Python 版把模型调用封装在 call_llm 里。它用 AZURE_OPENAI_ENDPOINT 和 AZURE_OPENAI_API_KEY 创建 OpenAI 客户端,把转换好的函数作为 tools 传入:
def call_llm(prompt, functions):
client = OpenAI(
base_url=(
f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/"
),
api_key=os.environ["AZURE_OPENAI_API_KEY"],
)
print("CALLING LLM")
response = client.chat.completions.create(
messages=[
{
"role": "system",
"content": "You are a helpful assistant.",
},
{
"role": "user",
"content": prompt,
},
],
model=os.getenv("AZURE_OPENAI_DEPLOYMENT", "gpt-5.1"),
tools=functions,
max_completion_tokens=1000,
)函数的后半部分读取模型给出的工具调用,把参数用 json.loads 解析,返回 name 和 args 组成的列表:
response_message = response.choices[0].message
functions_to_call = []
if response_message.tool_calls:
for tool_call in response_message.tool_calls:
print("TOOL: ", tool_call)
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
functions_to_call.append({ "name": name, "args": args })
return functions_to_call主程序用一个提问调用模型,然后逐个执行建议的函数:
prompt = "Add 2 to 20"
# ask LLM what tools to all, if any
functions_to_call = call_llm(prompt, functions)
# call suggested functions
for f in functions_to_call:
result = await session.call_tool(f["name"], arguments=f["args"])
print("TOOLS result: ", result.content)这段代码的作用是:用 session.call_tool 调用模型选出的函数,并打印结果。[1]
常见问题和限制
- 工具结果还没有交回模型。 TypeScript 片段在处理工具结果的地方只留了
TODO注释,Python 片段打印结果后就结束了。要得到模型最终的自然语言回答,需要把工具结果再作为消息交给模型,这几段代码没有展示这一步。[1] - 模型返回的参数要解析。 工具调用的参数是字符串,TypeScript 用
JSON.parse,Python 用json.loads解析后才能传给工具。[1] - 自定义模型名要看端点。 Java 示例里,显式设置的
MINIMAX_MODEL_ID会原样传下去,包括旧版或自定义的模型名,能不能用取决于所连的端点。不设置OPENAI_BASE_URL时,由MINIMAX_REGION决定地址:cn_zh对应https://api.minimaxi.com/v1,global_en对应https://api.minimax.io/v1。[1] - 注意 SDK 和传输方式的版本。 提醒,Java 示例用的是旧的 HTTP+SSE 传输,针对
2025-11-25版的 SDK;新的远程客户端应使用兼容2026-07-28的 SDK 和 Streamable HTTP。[1]
其他语言怎么做
.NET 版把 MCP 工具的 properties 取出来,用 ChatTool.CreateFunctionTool 生成 OpenAI 的工具定义。模型返回 ToolCalls 后,循环读取每个调用的 FunctionName 和 FunctionArguments,再交给 mcpClient.CallToolAsync 执行。[1]
Rust 版没有官方的 OpenAI 库,使用的是社区维护的 async-openai 库。[1]
Java 版使用 LangChain4j。McpToolProvider 自动发现 MCP 客户端的工具,AiServices 把聊天模型和工具绑在一起,工具结构的转换和函数调用由框架处理。示例中的模型服务是 MiniMax,默认模型是 MiniMax-M3.1-Flash,另一个当前模型 MiniMax-M3。环境变量 MINIMAX_REGION 可以选择 global_en 或 cn_zh,后者对应 https://api.minimaxi.com/v1。想显式指定端点和模型时,可以这样设置:
export OPENAI_API_KEY=your_minimax_api_key_here
export OPENAI_BASE_URL=https://api.minimax.io/v1
export MINIMAX_MODEL_ID=MiniMax-M3.1-Flash这段命令把端点设为海外地址。如果想按地区选择,做法是不设置 OPENAI_BASE_URL,改设 MINIMAX_REGION。[1]
下一步
这个客户端能把自然语言变成工具调用,接下来可以补上工具结果的回传,让模型给出最终回答,也可以把 Python 版的主程序改成交互式的循环。 Java 示例还演示了用一个 Bot 接口处理多轮自然语言请求,适合做成更完整的应用。[1]
如果想让普通用户直接使用,可以在 Python 版的主程序外面加一层循环:读入用户的一句话,交给 call_llm 选出函数,执行后把结果显示出来。这是在代码之上的延伸,没有给出这段代码,本文也不展示。[1]
本文改编自 Microsoft 开源课程 *mcp-for-beginners* 入门部分第 3 课《Creating a client with LLM》[1],原文以 MIT 许可证发布。
来源
- 1Creating a client with LLMMicrosoft mcp-for-beginners (MIT)
引用本页: 让大模型通过 MCP 客户端调用工具. TellWorld, https://www.tellworld.net/zh/practice/practice-mcp-llm-client. 更新于 2026-11-14