feat: agent platform initial commit
- 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
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user