Files
2026-07-13 12:49:17 +08:00

105 lines
3.9 KiB
Python

from contextlib import AsyncExitStack
from enum import Enum
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from ..tools.mcp_tool import MCPTool
from .config import MCPServerConfig
class MCPServerStatus(Enum):
DISCONNECTED = "disconnected" # Server is disconnected or experiencing errors
CONNECTING = "connecting" # Server is in the process of connecting
CONNECTED = "connected" # Server is connected and ready to use
class MCPDiscoveryState(Enum):
"""State of MCP discovery process."""
NOT_STARTED = "not_started" # Discovery has not started yet
IN_PROGRESS = "in_progress" # Discovery is currently in progress
# Discovery has completed (with or without errors)
COMPLETED = "completed"
class MCPClient:
def __init__(self):
# Initialize session and client objects
self.session: ClientSession | None = None
self.exit_stack = AsyncExitStack()
self.mcp_servers_status: dict[str, MCPServerStatus] = {}
def get_mcp_server_status(self, mcp_server_name: str) -> MCPServerStatus:
return self.mcp_servers_status.get(mcp_server_name, MCPServerStatus.DISCONNECTED)
def update_mcp_server_status(self, mcp_server_name, status: MCPServerStatus):
self.mcp_servers_status[mcp_server_name] = status
async def connect_and_discover(
self,
mcp_server_name: str,
mcp_server_config: MCPServerConfig,
mcp_tools_container: list,
model_provider,
):
transport = None
if mcp_server_config.http_url:
raise NotImplementedError("HTTP transport is not implemented yet")
elif mcp_server_config.url:
raise NotImplementedError("WebSocket transport is not implemented yet")
elif mcp_server_config.command:
params = StdioServerParameters(
command=mcp_server_config.command,
args=mcp_server_config.args,
env=mcp_server_config.env,
cwd=mcp_server_config.cwd,
)
transport = await self.exit_stack.enter_async_context(stdio_client(params))
else:
# error
raise ValueError(
f"Invalid MCP server configuration for {mcp_server_name}. "
"Please provide either a command or a URL."
)
try:
await self.connect_to_server(mcp_server_name, transport)
mcp_tools = await self.list_tools()
for tool in mcp_tools.tools:
mcp_tool = MCPTool(self, tool, model_provider)
mcp_tools_container.append(mcp_tool)
except Exception as e:
raise e
async def connect_to_server(self, mcp_server_name, transport):
"""Connect to an MCP server
Args:
server_params: Parameters for connecting to the MCP server.
"""
if self.get_mcp_server_status(mcp_server_name) != MCPServerStatus.CONNECTED:
self.update_mcp_server_status(mcp_server_name, MCPServerStatus.CONNECTING)
try:
stdio, write = transport
self.session = await self.exit_stack.enter_async_context(
ClientSession(stdio, write)
)
await self.session.initialize()
self.update_mcp_server_status(mcp_server_name, MCPServerStatus.CONNECTED)
except Exception as e:
self.update_mcp_server_status(mcp_server_name, MCPServerStatus.DISCONNECTED)
raise e
async def call_tool(self, name, args):
output = await self.session.call_tool(name, args)
return output
async def list_tools(self):
tools = await self.session.list_tools()
return tools
async def cleanup(self, mcp_server_name):
"""Clean up resources"""
await self.exit_stack.aclose()
self.update_mcp_server_status(mcp_server_name, MCPServerStatus.DISCONNECTED)