66 lines
2.5 KiB
Python
66 lines
2.5 KiB
Python
"""Einheitliches Ergebnisformat aller DB-Dienste.
|
|
|
|
Jedes Tool liefert Text für das Modell (Markdown-Tabelle) und dieselben Daten
|
|
strukturiert (structuredContent), damit Clients sie als Tabelle, CSV oder
|
|
Diagramm zeigen können:
|
|
|
|
{"columns": [...], "rows": [[...], ...], "row_count": n, "truncated": bool}
|
|
"""
|
|
|
|
import base64
|
|
import datetime
|
|
import decimal
|
|
import uuid
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
from mcp.types import CallToolResult, TextContent
|
|
|
|
|
|
def jsonable(value: Any) -> Any:
|
|
if value is None or isinstance(value, (bool, int, str)):
|
|
return value
|
|
if isinstance(value, float):
|
|
return value if value == value and value not in (float("inf"), float("-inf")) else str(value)
|
|
if isinstance(value, decimal.Decimal):
|
|
# Einheitlich als Zahl (für Diagramme); nur was eine Kommazahl nicht genau fasst, als Text
|
|
return float(value) if value.is_finite() and len(value.as_tuple().digits) <= 15 else str(value)
|
|
if isinstance(value, (datetime.date, datetime.time)):
|
|
return value.isoformat()
|
|
if isinstance(value, datetime.timedelta):
|
|
return str(value)
|
|
if isinstance(value, uuid.UUID):
|
|
return str(value)
|
|
if isinstance(value, (bytes, bytearray, memoryview)):
|
|
return "base64:" + base64.b64encode(bytes(value)[:1024]).decode()
|
|
if isinstance(value, (list, tuple)):
|
|
return [jsonable(v) for v in value]
|
|
if isinstance(value, dict):
|
|
return {str(k): jsonable(v) for k, v in value.items()}
|
|
return str(value)
|
|
|
|
|
|
def as_table(columns: list[str], rows: list[list[Any]], cell_chars: int) -> str:
|
|
def cell(value: Any) -> str:
|
|
text = "NULL" if value is None else str(value)
|
|
text = text.replace("\n", " ").replace("|", "\\|")
|
|
return text if len(text) <= cell_chars else text[:cell_chars] + "…"
|
|
lines = ["| " + " | ".join(columns) + " |", "|" + "---|" * len(columns)]
|
|
lines += ["| " + " | ".join(cell(v) for v in row) + " |" for row in rows]
|
|
return "\n".join(lines)
|
|
|
|
|
|
@dataclass
|
|
class Table:
|
|
columns: list[str]
|
|
rows: list[list[Any]]
|
|
truncated: bool = False
|
|
extra: dict[str, Any] = field(default_factory=dict)
|
|
|
|
def structured(self) -> dict[str, Any]:
|
|
rows = [[jsonable(v) for v in row] for row in self.rows]
|
|
return {"columns": self.columns, "rows": rows, "row_count": len(rows), "truncated": self.truncated,
|
|
**self.extra}
|
|
|
|
def result(self, text: str) -> CallToolResult:
|
|
return CallToolResult(content=[TextContent(type="text", text=text)], structured_content=self.structured())
|