Files
2026-07-13 13:31:35 +08:00

208 lines
7.6 KiB
Python

"""Connect model with mcp tools in Python
# Run this python script
> pip install mcp azure-ai-inference
> python <this-script-path>.py
"""
import asyncio
import json
import os
from typing import Dict, Optional
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from mcp.client.sse import sse_client
from azure.ai.inference import ChatCompletionsClient
from azure.ai.inference.models import AssistantMessage, SystemMessage, UserMessage, ToolMessage
from azure.ai.inference.models import ImageContentItem, ImageUrl, TextContentItem
from azure.core.credentials import AzureKeyCredential
class MCPClient:
def __init__(self):
# Initialize session and client objects
self._servers = {}
self._tool_to_server_map = {}
self.exit_stack = AsyncExitStack()
# To authenticate with the model you will need to generate a personal access token (PAT) in your GitHub settings.
# Create your PAT token by following instructions here: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens
self.azureai = ChatCompletionsClient(
endpoint = "https://models.inference.ai.azure.com",
credential = AzureKeyCredential(os.environ["GITHUB_TOKEN"]),
api_version = "2024-08-01-preview",
)
async def connect_stdio_server(self, server_id: str, command: str, args: list[str], env: Dict[str, str]):
"""Connect to an MCP server using STDIO transport
Args:
server_id: Unique identifier for this server connection
command: Command to run the MCP server
args: Arguments for the command
env: Optional environment variables
"""
server_params = StdioServerParameters(
command=command,
args=args,
env=env
)
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
stdio, write = stdio_transport
session = await self.exit_stack.enter_async_context(ClientSession(stdio, write))
await session.initialize()
# Register the server
await self._register_server(server_id, session)
async def connect_sse_server(self, server_id: str, url: str, headers: Dict[str, str]):
"""Connect to an MCP server using SSE transport
Args:
server_id: Unique identifier for this server connection
url: URL of the SSE server
headers: Optional HTTP headers
"""
sse_context = await self.exit_stack.enter_async_context(sse_client(url=url, headers=headers))
read, write = sse_context
session = await self.exit_stack.enter_async_context(ClientSession(read, write))
await session.initialize()
# Register the server
await self._register_server(server_id, session)
async def _register_server(self, server_id: str, session: ClientSession):
"""Register a server and its tools in the client
Args:
server_id: Unique identifier for this server
session: Connected ClientSession
"""
# List available tools
response = await session.list_tools()
tools = response.tools
# Store server connection info
self._servers[server_id] = {
"session": session,
"tools": tools
}
# Update tool-to-server mapping
for tool in tools:
self._tool_to_server_map[tool.name] = server_id
print(f"\nConnected to server '{server_id}' with tools:", [tool.name for tool in tools])
async def chatWithTools(self, messages: list[any]) -> str:
"""Chat with model and using tools
Args:
messages: Messages to send to the model
"""
if not self._servers:
raise ValueError("No MCP servers connected. Connect to at least one server first.")
# Collect tools from all connected servers
available_tools = []
for server_id, server_info in self._servers.items():
for tool in server_info["tools"]:
available_tools.append({
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.inputSchema
},
})
while True:
# Call model
response = self.azureai.complete(
messages = messages,
model = "gpt-4o",
tools=available_tools,
response_format = "text",
temperature = 1,
top_p = 1,
)
hasToolCall = False
if response.choices[0].message.tool_calls:
for tool in response.choices[0].message.tool_calls:
hasToolCall = True
tool_name = tool.function.name
tool_args = json.loads(tool.function.arguments)
messages.append(
AssistantMessage(
tool_calls = [{
"id": tool.id,
"type": "function",
"function": {
"name": tool.function.name,
"arguments": tool.function.arguments,
}
}]
)
)
# Find the appropriate server for this tool
if tool_name in self._tool_to_server_map:
server_id = self._tool_to_server_map[tool_name]
server_session = self._servers[server_id]["session"]
# Execute tool call on the appropriate server
result = await server_session.call_tool(tool_name, tool_args)
print(f"[Server '{server_id}' call tool '{tool_name}' with args {tool_args}]: {result.content}")
messages.append(
ToolMessage(
tool_call_id = tool.id,
content = str(result.content)
)
)
else:
messages.append(
AssistantMessage(
content = response.choices[0].message.content
)
)
print(f"[Model Response]: {response.choices[0].message.content}")
if not hasToolCall:
break
async def cleanup(self):
"""Clean up resources"""
await self.exit_stack.aclose()
await asyncio.sleep(1)
async def main():
client = MCPClient()
messages = [
SystemMessage(content = "You are my browser automation assistant, helping me operate the browser"),
UserMessage(content = [
TextContentItem(text = "Navigation to github.com/kinfey"),
]),
]
try:
await client.connect_stdio_server(
"mcp-mbnn4zlx",
"npx",
[
"-y",
"@playwright/mcp@latest",
],
{
}
)
await client.chatWithTools(messages)
except Exception as e:
print(f"\nError: {str(e)}")
finally:
await client.cleanup()
if __name__ == "__main__":
asyncio.run(main())