Skip to content

The client has been stuck while performing initialization, and the shutdown has no effect #468

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
hackerHiJu opened this issue Apr 10, 2025 · 2 comments

Comments

@hackerHiJu
Copy link

The client has been stuck while performing initialization, and the shutdown has no effect.

mcp:1.6.0

When I executed the await self.session.initialize() code, it was blocked and stopped executing

client

import asyncio
import json
from typing import Optional

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from openai import OpenAI
from contextlib import AsyncExitStack

BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
MODEL_NAME = "qwen-max"
KEY = ""


class MCPClient:

    def __init__(self):
        """初始化MCP客户端"""
        self.openai_api_key = KEY
        self.openai_api_base = BASE_URL
        self.model = MODEL_NAME

        if not self.openai_api_key:
            raise ValueError("请设置您的OpenAI API密钥")

        self.client = OpenAI(
            api_key=self.openai_api_key,
            base_url=self.openai_api_base,
        )
        self.session: Optional[ClientSession] = None
        self.exit_stack = AsyncExitStack()

    # 处理对话请求
    async def process_query(self, query: str) -> str:
        messages = [{
            "role": "system",
            "content": "你是一个智能助手,帮助用户回答问题",
        }, {
            "role": "user",
            "content": query,
        }]

        # 获取到工具列表
        response = await self.session.list_tools()
        available_tools = [
            {
                "type": "function",
                "function": {
                    "name": tool.name,
                    "description": tool.description,
                    "input_schema": tool.inputSchema,
                }
            }
            for tool in response.tools]

        try:
            response = await asyncio.get_event_loop().run_in_executor(
                None,
                lambda: self.client.chat.completions.create(
                    model=MODEL_NAME,
                    messages=messages,
                    tools=available_tools,
                )
            )
            content = response.choices[0]
            if content.finish_reason == "tool_calls":
                # 如果使用的是工具,解析工具
                tool_call = content.message.tool_calls[0]
                tool_name = tool_call.function.name
                tool_args = json.loads(tool_call.function.arguments)

                # 执行工具
                result = await self.session.call_tool(tool_name, tool_args)
                print(f"\n\n工具调用:[{tool_name}],参数:[{tool_args}]")

                # 将工具返回结果存入message中,model_dump()克隆一下消息
                messages.append(content.message.model_dump())
                messages.append({
                    "role": "tool",
                    "content": result.content[0].text,
                    "tool_call_id": tool_call.id,
                })

                response = self.client.chat.completions.create(
                    model=MODEL_NAME,
                    messages=messages,
                )

                return response.choices[0].message.content

            # 正常返回
            return content.message.content
        except Exception as e:
            return f"调用OpenAI API错误:{str(e)}"

    async def connect_to_server(self, server_script_path: str):
        """连接到 MCP 服务器的连接 """
        is_python = server_script_path.endswith(".py")
        is_js = server_script_path.endswith(".js")
        if not (is_python or is_js):
            raise ValueError("服务器脚本路径必须以 .py 或 .js 结尾")

        command = "python" if is_python else "node"

        server_params = StdioServerParameters(
            command=command,
            args=[server_script_path],
            env=None
        )

        # stdio_client()通过 yield 关键字返回两个对象
        async with stdio_client(server_params) as (read, write):
            # 使用 ClientSession来管理连接可以自动查询相关工具
            async with await self.exit_stack.enter_async_context(ClientSession(read, write)) as session:
                # 使用 ClientSession来管理连接可以自动查询相关工具
                self.session = session
                # 初始化会话
                await self.session.initialize()

                # 列出工具
                response = await self.session.list_tools()
                tools = response.tools
                print("\n已经连接到服务器,支持以下工具:", [tools.name for tools in tools])


    async def chat_loop(self):
        """运行交互式聊天循环"""
        print("\n MCP客户端已启动!输入 ‘quit’ 退出")

        while True:
            try:
                user_input = input("请输入您的问题:").strip()
                if user_input.lower() == "quit":
                    print("退出交互式聊天")
                    break
                response = await self.process_query(user_input)
                print(f"大模型:{response}")
            except Exception as e:
                print(f"发生错误:{str(e)}")

    async def cleanup(self):
        """清理资源"""
        print("Cleaning up resources...")
        await self.exit_stack.aclose()


async def main():
    mcp_client = MCPClient()

    try:
        await mcp_client.connect_to_server("./mcp_client.py")
        await mcp_client.chat_loop()
    finally:
        await mcp_client.cleanup()


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

server

from typing import Any

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("WeatherServer")

async def fetch_weather(city: str) -> dict[str, Any] | None:
    """
    获取天气的api
    :param city: 城市名称(需要使用英文,如Beijing)
    :return: 天气数据字典;若出错返回包含 error信息的字典
    """
    return {
        "城市": f"{city}\n",
        "温度": "25.5°C\n",
        "湿度": "25%\n",
        "风俗": "12 m/s\n",
        "天气": "晴\n",
    }


@mcp.tool()
async def get_weather(city: str) -> dict[str, Any] | None:
    """
    获取天气
    :param city: 城市名称(需要使用英文,如Beijing)
    :return: 天气数据字典;若出错返回包含 error信息的字典
    """
    return await fetch_weather(city)


if __name__ == '__main__':
    # 使用标准 I/O 方式运行MCP服务器
    mcp.run(transport='stdio')
@Elijas
Copy link

Elijas commented Apr 19, 2025

How did you solve this?

@Elijas
Copy link

Elijas commented Apr 20, 2025

Related? #395

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants