64b840c94c
- FastAPI + HTMX UI: dashboard, agents CRUD, chat, audit, tools - SQLite schema: agents, conversations, messages, audit, sessions - Code-agent sync (agents/*.py -> DB on startup) - MCP client with health check - Token auth (Bearer + session) - LiteLLM integration via llm.py - Docker Compose: agent-platform + mcp-tools services - Smoke tests pass: /health 200, /api/agents 200, / 401
99 lines
3.0 KiB
Python
99 lines
3.0 KiB
Python
"""MCP-Client: Verbindung zum Tool-Server."""
|
|
from typing import List, Dict, Any, Optional
|
|
from mcp import ClientSession
|
|
from mcp.client.streamable_http import streamablehttp_client
|
|
|
|
from config import settings
|
|
|
|
|
|
class MCPClient:
|
|
def __init__(self, server_url: str = None):
|
|
self.server_url = server_url or settings.MCP_SERVER_URL
|
|
self._session: Optional[ClientSession] = None
|
|
self._streams = None
|
|
self._ctx = None
|
|
|
|
async def connect(self):
|
|
"""Stellt Verbindung zum MCP-Server her (lazy + reconnect-fähig)."""
|
|
if self._session:
|
|
return
|
|
self._ctx = streamablehttp_client(url=self.server_url)
|
|
read, write, _ = await self._ctx.__aenter__()
|
|
self._session = ClientSession(read, write)
|
|
await self._session.__aenter__()
|
|
await self._session.initialize()
|
|
|
|
async def disconnect(self):
|
|
if self._session:
|
|
try:
|
|
await self._session.__aexit__(None, None, None)
|
|
except Exception:
|
|
pass
|
|
if self._ctx:
|
|
try:
|
|
await self._ctx.__aexit__(None, None, None)
|
|
except Exception:
|
|
pass
|
|
self._session = None
|
|
self._ctx = None
|
|
|
|
async def list_tools(self) -> List[Dict[str, Any]]:
|
|
await self.connect()
|
|
result = await self._session.list_tools()
|
|
tools = []
|
|
for tool in result.tools:
|
|
t = tool.model_dump() if hasattr(tool, "model_dump") else tool
|
|
tools.append(t)
|
|
return tools
|
|
|
|
async def call_tool(self, name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
await self.connect()
|
|
result = await self._session.call_tool(name, arguments)
|
|
return result.model_dump() if hasattr(result, "model_dump") else result
|
|
|
|
async def get_tools_for_llm(self, allowed: Optional[List[str]] = None) -> List[Dict[str, Any]]:
|
|
"""Konvertiert MCP-Tools in LiteLLM-Tool-Format.
|
|
|
|
Optional: Filter auf erlaubte Tool-Namen.
|
|
"""
|
|
tools = await self.list_tools()
|
|
result = []
|
|
for tool in tools:
|
|
name = tool.get("name")
|
|
if allowed and name not in allowed:
|
|
continue
|
|
result.append({
|
|
"type": "function",
|
|
"function": {
|
|
"name": name,
|
|
"description": tool.get("description", ""),
|
|
"parameters": tool.get("inputSchema", {"type": "object", "properties": {}}),
|
|
},
|
|
})
|
|
return result
|
|
|
|
async def health(self) -> bool:
|
|
"""Prüft ob MCP-Server erreichbar ist."""
|
|
try:
|
|
await self.connect()
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
_client: Optional[MCPClient] = None
|
|
|
|
|
|
def get_mcp_client() -> MCPClient:
|
|
global _client
|
|
if _client is None:
|
|
_client = MCPClient()
|
|
return _client
|
|
|
|
|
|
async def close_mcp_client():
|
|
global _client
|
|
if _client:
|
|
await _client.disconnect()
|
|
_client = None
|