Files
agent-platform/mcp_tools/server.py
T
Leopold 64b840c94c 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
2026-07-05 22:18:00 +02:00

144 lines
4.3 KiB
Python

"""MCP-Tool-Server für die Agent Platform."""
import os
import ast
import operator
import ipaddress
import socket
from datetime import datetime, timezone, timedelta
from urllib.parse import urlparse
import httpx
from fastmcp import FastMCP
mcp = FastMCP("Agent Platform Tools")
@mcp.tool()
def echo(text: str) -> dict:
"""Echo-Tool zum Testen der MCP-Verbindung."""
return {"input": text, "timestamp": datetime.utcnow().isoformat()}
@mcp.tool()
def get_time(timezone_name: str = "UTC") -> dict:
"""Gibt aktuelle Zeit in angegebener Zeitzone zurück."""
tz_offsets = {"UTC": 0, "CET": 1, "CEST": 2, "EST": -5, "PST": -8, "JST": 9}
offset = tz_offsets.get(timezone_name.upper(), 0)
tz = timezone(timedelta(hours=offset))
return {"timezone": timezone_name, "time": datetime.now(tz).isoformat()}
@mcp.tool()
def calculate(expression: str) -> dict:
"""Berechnet einen mathematischen Ausdruck (sicher, nur Zahlen).
Erlaubt: +, -, *, /, **, %.
Beispiel: calculate("2 + 3 * 4") -> 14
"""
ops = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Pow: operator.pow,
ast.Mod: operator.mod,
}
try:
tree = ast.parse(expression, mode="eval")
except SyntaxError:
return {"error": "Ungültiger Ausdruck"}
def _eval(node):
if isinstance(node, ast.Constant):
if not isinstance(node.value, (int, float)):
raise ValueError("Nur Zahlen erlaubt")
return node.value
if isinstance(node, ast.BinOp):
if type(node.op) not in ops:
raise ValueError("Operation nicht erlaubt")
return ops[type(node.op)](_eval(node.left), _eval(node.right))
if isinstance(node, ast.UnaryOp):
if isinstance(node.op, ast.USub):
return -_eval(node.operand)
if isinstance(node.op, ast.UAdd):
return _eval(node.operand)
raise ValueError("Ungültiger Knoten")
try:
result = _eval(tree.body)
return {"expression": expression, "result": result}
except Exception as e:
return {"expression": expression, "error": str(e)}
@mcp.tool()
def http_get(url: str, timeout: int = 10) -> dict:
"""Holt eine URL via HTTP. SSRF-Schutz aktiv.
Nur http/https, keine Auth, keine privaten IPs.
Beispiel: http_get("https://example.com")
"""
if not url.startswith(("http://", "https://")):
return {"error": "Nur http/https URLs erlaubt"}
parsed = urlparse(url)
try:
ip = socket.gethostbyname(parsed.hostname)
ip_obj = ipaddress.ip_address(ip)
if ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_reserved:
return {"error": f"IP {ip} blockiert (privat/loopback/reserved)"}
except Exception:
pass
try:
with httpx.Client(timeout=timeout, follow_redirects=True) as client:
r = client.get(url)
return {
"status": r.status_code,
"url": str(r.url),
"content_type": r.headers.get("content-type", ""),
"length": len(r.text),
"text_preview": r.text[:2000],
}
except Exception as e:
return {"error": str(e)}
@mcp.tool()
def list_env(prefix: str = "") -> dict:
"""Listet Umgebungsvariablen mit optionalem Prefix-Filter.
Sensitive Werte (KEY/SECRET/TOKEN/PASSWORD) werden maskiert.
"""
env = {}
for k, v in os.environ.items():
if prefix and not k.startswith(prefix.upper()):
continue
upper = k.upper()
if any(s in upper for s in ["KEY", "SECRET", "TOKEN", "PASSWORD"]):
env[k] = "***MASKED***"
else:
env[k] = v[:200]
return {"count": len(env), "vars": env}
@mcp.tool()
def json_format(data: str, indent: int = 2) -> dict:
"""Formatiert einen JSON-String lesbar.
Beispiel: json_format('{"a":1}')
"""
try:
parsed = json.loads(data)
return {"formatted": json.dumps(parsed, indent=indent, ensure_ascii=False)}
except Exception as e:
return {"error": str(e)}
if __name__ == "__main__":
host = os.getenv("MCP_HOST", "0.0.0.0")
port = int(os.getenv("MCP_PORT", "8501"))
mcp.run(transport="streamable-http", host=host, port=port)