最全的 MCP协议的 Stdio 机制代码实战

你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益:

  1. 了解大厂经验
  2. 拥有和大厂相匹配的技术等

希望看什么,评论或者私信告诉我!

一、背景

前面我们已经再为更进一步的 MCP 打下了基础 一文搞定 Python 装饰器

解锁 MCP 中的 JSON-RPC

为什么MCP可以适配不同LLM

MCP协议三种传输机制全解析

多智能体大语言模型系统频频翻车?三大失败根源与解决方案全解析

今天就正式进入到代码开发阶段

二、准备工作

2.1 注册 zhipu

通过专属链接获取 2000万Tokens

2.2 进入控制台,生成秘钥

2.3 zhipu 例子

复制代码
pip install zhipuai
python 复制代码
# -*- coding: utf-8 -*-

#%%
from zhipuai import ZhipuAI
client = ZhipuAI(api_key="xxxxxxx")  # 请填写您自己的APIKey
response = client.chat.completions.create(
    model="glm-4-flash-250414",  # 请填写您要调用的模型名称
    messages=[
        {"role": "user", "content": "一个袋子中有5个红球和3个蓝球,随机抽取2个球,抽到至少1个红球的概率为:"}
    ],
    max_tokens=12000,

)
# print(response.choices[0]['message']['content'])
print(response)
print(response.choices[0].message.content)

三、MCP Stido 代码

3.1 mcp server 代码

python 复制代码
# -*- coding: utf-8 -*-
# create by shengjk1 on  2025/6/5
import pathlib
from typing import Any
from mcp.server import FastMCP

# Initialize FastMCP server
mcp = FastMCP("test")

@mcp.tool()
async  def sayHello(name:str)->str:
    """
     greet a user by name.

    :param name: The name of the user to greet.
    :return: A greeting message.
    """
    return f"Hello, {name}!"

@mcp.tool()
async def get_data_add(a: int,b:int) -> Any:
    """
    Fetch data from a given URL.
    :param url: The URL to fetch data from. Default is 'https://api.github.com'.
    :return: The JSON data fetched from the URL.
    """
    return a + b

@mcp.resource("dir://mcp_demo-quickstart")
def list_test_dir() -> list[str]:
    """List the files in the test directory"""
    # Iterate through the test directory and return a list of strings representing the files
    return [str(f) for f in pathlib.Path('mcp_demo-quickstart').iterdir()]


if __name__ == '__main__':
    mcp.run(transport='stdio')

3.2 mcp server 代码详细解释

3.2.1. 导入模块

python 复制代码
import pathlib
from typing import Any
from mcp.server import FastMCP
  • pathlib 是 Python 的标准库模块,用于处理文件路径和目录操作。
  • typing.Any 是一个类型注解,表示函数返回值可以是任意类型。
  • mcp.server.FastMCP 是从 mcp 框架中导入的 FastMCP 类,用于创建和管理服务器。

3.2.2. 初始化 FastMCP 服务器

python 复制代码
mcp = FastMCP("test")
  • 创建了一个 FastMCP 服务器实例,命名为 "test"。这个实例将用于注册工具函数和资源,并启动服务器。

3.2.3 定义工具函数

  • 使用 @mcp.tool() 装饰器将 sayHello 函数注册为一个工具函数。( 扩展知识:一文搞定 Python 装饰器 )
  • 需要注意使用 @mcp.tool() 装饰器装饰的函数,一定写好函数的 doc: 描述清楚函数的作用和参数 这个很重要,不然 tool call 的时候,没有办法选择合适的工具

3.2.4. 定义资源函数

  • 使用 @mcp.resource() 装饰器将 list_test_dir 函数注册为一个资源函数。
  • @mcp.tool() 要描述清楚 doc

3.2.5. 启动服务器

python 复制代码
if __name__ == '__main__':
    mcp.run(transport='stdio')
  • 当脚本作为主程序运行时,调用 mcp.run() 方法启动服务器。
  • 参数 transport='stdio' 表示服务器通过标准输入输出(stdio)进行通信。

3.2.6. 总结

这段代码实现了一个简单的 mcp 服务器,提供了以下功能:

  1. 工具函数
    • sayHello:根据用户的名字生成问候语。
    • get_data_add:计算两个数字的和(但注释中提到的功能与实际实现不符)。
  2. 资源函数
    • list_test_dir:列出指定目录中的所有文件。
  3. 服务器启动
    • 使用 stdio 作为通信方式。

3.3 mcp client 代码

python 复制代码
# -*- coding: utf-8 -*-
import asyncio
import json
from typing import Optional
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from mcp.types import TextContent, ImageContent, EmbeddedResource


class MCPClient:
    def __init__(self):
        self.session:Optional[ClientSession] = None
        self.exit_stack=AsyncExitStack()


    async def connect_to_server(self,server_script_path: str):
        command = "python"
        server_params = StdioServerParameters(
            command=command,
            args=[server_script_path],
            env=None
        )
        stdio_transport =await self.exit_stack.enter_async_context(stdio_client(server_params))
        self.stdio,self.write = stdio_transport
        self.session =await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))

        await self.session.initialize()
        response=await self.session.list_tools()
        tools = response.tools
        print("===========",tools)

    async def close(self):
        await self.exit_stack.aclose()

    async def process_query(self, query: str) -> str:
        """Process a query using available tools"""
        messages = [
            {
                "role": "user",
                "content": query
            }
        ]

        available_tools = [{
            "type": "function",
            "function":{
                "name": tool.name,
                "description": tool.description,
                "parameters": tool.inputSchema
            }
        } for tool in response.tools]

        print("Available tools:", available_tools)

        from zhipuai import ZhipuAI
        zhipu_client = ZhipuAI(api_key="xxxxxx")  # 请填写您自己的APIKey
        response = zhipu_client.chat.completions.create(
            model="glm-4-flash-250414",  # 请填写您要调用的模型名称
            messages=messages,
            max_tokens=12000,
            tools=available_tools
        )
        print("response:",response)

        if response.choices[0].finish_reason == "tool_calls":
            tool_calls = response.choices[0].message.tool_calls
            print("Tool calls:", tool_calls)
            for tool_call in tool_calls:
                tool_name = tool_call.function.name
                tool_input = json.loads(tool_call.function.arguments)
                print(f"Calling tool {tool_name} with input {tool_input}")
                # 在这里调用相应的工具,并获取结果
                result = await self.session.call_tool(tool_name, tool_input)
                #Tool get_data_add returned result: meta=None content=[TextContent(type='text', text='11', annotations=None)] isError=False
                print(f"Tool {tool_name} returned result: {result.content}")

                messages.append({
                    "role": "user",
                    "content": ','.join(contens.text for contens in result.content)
                })
                for item in result.content:
                    if isinstance(item, TextContent):
                        # 将工具调用的结果添加到消息列表中
                        messages.append({
                            "role": "assistant",
                            "content": item.text
                        })

            print("Updated messages:", messages)
            response = zhipu_client.chat.completions.create(
                model="glm-4-flash-250414",  # 请填写您要调用的模型名称
                messages=messages,
                max_tokens=12000,
            )
            print("response:",response.choices[0].message.content)


async def main():
    client = MCPClient()
    try:
        await client.connect_to_server("../server/server.py")
        await client.process_query("两步走,首先像小明问好,其次并把1和10相加,最后返回问好和加法的结果")
    finally:
        await client.close()

if __name__ == '__main__':
    asyncio.run(main())

3.4 mcp client 代码解释

这段代码是一个简化版本的 MCPClient 类实现,它结合了与服务器的通信(通过 mcp 模块)和智谱AI(ZhipuAI)的接口调用。以下是对代码的详细解释:

3.4.1. MCPClient 类的定义

python 复制代码
class MCPClient:
    def __init__(self):
        self.session: Optional[ClientSession] = None
        self.exit_stack = AsyncExitStack()
  • self.session:用于存储与服务器的会话对象,初始值为 None
  • self.exit_stack:用于管理异步上下文,确保在程序结束时正确关闭资源。

3.4.2. 连接到服务器

python 复制代码
async def connect_to_server(self, server_script_path: str):
    command = "python"
    server_params = StdioServerParameters(
        command=command,
        args=[server_script_path],
        env=None
    )
    stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
    self.stdio, self.write = stdio_transport
    self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))

    await self.session.initialize()
    response = await self.session.list_tools()
    tools = response.tools
    print("===========", tools)
  • server_script_path:服务器脚本的路径。
  • command:用于启动服务器的命令(这里是 python)。
  • server_params:定义了服务器的启动参数,包括命令、参数列表和环境变量。
  • stdio_transport:通过 stdio_client 创建一个标准输入输出的传输通道。
  • self.stdio, self.write:从 stdio_transport 中获取输入和输出流。
  • self.session:创建一个 ClientSession 对象,用于与服务器进行通信。
  • await self.session.initialize():初始化会话。
  • await self.session.list_tools():获取服务器支持的工具列表并打印。

3.4.3. 关闭客户端

python 复制代码
async def close(self):
    await self.exit_stack.aclose()
  • await self.exit_stack.aclose():关闭异步上下文管理器,确保所有资源被正确释放。

3.4.4. 处理查询

python 复制代码
async def process_query(self, query: str) -> str:
    messages = [
        {
            "role": "user",
            "content": query
        }
    ]
  • query:用户输入的查询字符串。
  • messages:初始化消息列表,包含用户的消息。

构建可用工具列表

python 复制代码
available_tools = [{
    "type": "function",
    "function": {
        "name": tool.name,
        "description": tool.description,
        "parameters": tool.inputSchema
    }
} for tool in response.tools]
  • 将工具信息转换为特定格式,用于后续的工具调用。

调用智谱AI接口

python 复制代码
from zhipuai import ZhipuAI
zhipu_client = ZhipuAI(api_key="xxxxxx")  # 替换为实际的API Key
response = zhipu_client.chat.completions.create(
    model="glm-4-flash-250414",  # 模型名称
    messages=messages,
    max_tokens=12000,
    tools=available_tools
)
print("response:", response)
  • 使用智谱AI的接口,根据用户输入和可用工具生成响应。

处理工具调用

python 复制代码
if response.choices[0].finish_reason == "tool_calls":
    tool_calls = response.choices[0].message.tool_calls
    print("Tool calls:", tool_calls)
    for tool_call in tool_calls:
        tool_name = tool_call.function.name
        tool_input = json.loads(tool_call.function.arguments)
        print(f"Calling tool {tool_name} with input {tool_input}")
        result = await self.session.call_tool(tool_name, tool_input)
        print(f"Tool {tool_name} returned result: {result.content}")

        messages.append({
            "role": "user",
            "content": ','.join(contens.text for contens in result.content)
        })
        for item in result.content:
            if isinstance(item, TextContent):
                messages.append({
                    "role": "assistant",
                    "content": item.text
                })
  • 如果响应中包含工具调用请求:
    • 遍历工具调用列表,调用相应的工具并获取结果。
    • 将工具调用的结果添加到消息列表中。
    • 根据结果类型(文本、图片、嵌入资源)进行处理。

重新调用智谱AI接口

python 复制代码
response = zhipu_client.chat.completions.create(
    model="glm-4-flash-250414",
    messages=messages,
    max_tokens=12000,
)
print("response:", response.choices[0].message.content)
  • 使用更新后的消息列表重新调用智谱AI接口,生成最终的响应。

3.4.5. 主函数

python 复制代码
async def main():
    client = MCPClient()
    try:
        await client.connect_to_server("../server/server.py")
        await client.process_query("两步走,首先像小明问好,其次并把1和10相加,最后返回问好和加法的结果")
    finally:
        await client.close()

if __name__ == '__main__':
    asyncio.run(main())
  • 创建 MCPClient 实例。
  • 连接到服务器并处理用户查询。
  • 确保在程序结束时关闭客户端。

代码逻辑总结

  1. 连接到服务器:通过 connect_to_server 方法启动服务器脚本,并建立与服务器的通信会话。
  2. 处理用户查询:
    • 将用户输入的查询包装为消息格式。
    • 调用智谱AI接口,获取初步响应。
    • 如果响应中包含工具调用请求,则调用服务器提供的工具。
    • 将工具调用的结果更新到消息列表中。
    • 重新调用智谱AI接口,生成最终的响应。
  3. 关闭客户端:确保在程序结束时释放所有资源。

注意事项

  1. 服务器脚本路径:"../server/server.py" 是服务器脚本的路径,需要根据实际情况调整。
  2. 工具调用:工具调用的结果需要根据实际返回的 result.content 进行处理。

四、执行和结果

执行运行 client.py 文件即可,client 会通过下面的代码以子进程的方式启动 server

python 复制代码
server_params = StdioServerParameters(
            command=command,
            args=[server_script_path],
            env=None
        )

结果

txt 复制代码
response: 你好,小明!1和10相加等于11。

五、总结

文章详细讲解了如何使用 MCP 协议和智谱 AI 平台构建一个工具调用系统。通过注册智谱 AI 平台获取 API Key,并编写了 MCP Server 和 Client 的代码。Server 提供了工具函数,Client 通过智谱 AI 的接口调用这些工具函数,并处理用户查询。代码逻辑清晰,包含了工具函数的定义、服务器的启动、客户端的连接以及查询处理等关键步骤。

相关推荐
Jamence2 分钟前
多模态大语言模型arxiv论文略读(118)
论文阅读·人工智能·语言模型·自然语言处理·论文笔记
愚农搬码5 分钟前
LangChain 调用不同类型的多MCP服务
人工智能·后端
AI速译官13 分钟前
字节跳动推出视频生成新模型Seedance
人工智能
chenquan1 小时前
ArkFlow 流处理引擎 0.4.0-rc1 发布
人工智能·后端·github
Se7en2581 小时前
使用 Higress AI 网关代理 vLLM 推理服务
人工智能
AI大模型技术社1 小时前
PyTorch手撕CNN:可视化卷积过程+ResNet18训练代码详解
人工智能·神经网络
CSTechEi2 小时前
【IEEE/EI/Scopus检索】2025年第六届模式识别与数据挖掘国际会议 (PRDM 2025)
人工智能·数据挖掘·模式识别·ei学术会议
明明跟你说过2 小时前
FastChat 架构拆解:打造类 ChatGPT 私有化部署解决方案的基石
人工智能·语言模型·chatgpt·openai
Listennnn3 小时前
Text2SQL、Text2API基础
数据库·人工智能
钒星物联网3 小时前
256bps!卫星物联网极低码率语音压缩算法V3.0发布!
人工智能·语音识别