bifroest/app/mcp/models.py
2026-10-27 08:34:17 +01:00

92 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""MCP-Server, ihre entdeckten Tools und die Freischaltung je Workspace (→ Konzept 7.1–7.3).
Zwei Arten von Servern in einer Tabelle:
- zentral (workspace_id leer): vom Plattform-Admin registriert, von Workspaces freigeschaltet
- Verbindung eines Workspace (workspace_id gesetzt): vom Workspace-Admin angelegt, nur dort nutzbar;
eine neue URL braucht die Freigabe eines Plattform-Admins. Datenbanken sind solche Verbindungen
zu einem zentral registrierten DB-Dienst (template_id), mit den Zugangsdaten als Header.
"""
from datetime import datetime
from typing import Any
from sqlalchemy import JSON, Boolean, ForeignKey, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base, now
# Freigabe einer Verbindung durch einen Plattform-Admin
APPROVED, PENDING, REJECTED = "approved", "pending", "rejected"
class McpServer(Base):
"""Server mit Streamable HTTP; Endpunkte nur aus AIW_MCP_ALLOWED_HOSTS."""
__tablename__ = "mcp_servers"
id: Mapped[int] = mapped_column(primary_key=True)
# Leer: zentraler Server; gesetzt: Verbindung nur dieses Workspace
workspace_id: Mapped[int | None] = mapped_column(ForeignKey("workspaces.id", ondelete="CASCADE"), index=True)
# Datenbank-Verbindung: der zentrale DB-Dienst, dessen URL und Schlüssel sie nutzt
template_id: Mapped[int | None] = mapped_column(ForeignKey("mcp_servers.id", ondelete="RESTRICT"))
# Kurzname, Präfix der Tool-Namen (<name>__<tool>)
name: Mapped[str] = mapped_column(String(16), unique=True)
label: Mapped[str] = mapped_column(String(200))
url: Mapped[str] = mapped_column(String(500))
# Bearer-Token, verschlüsselt; ein Workspace kann ein eigenes setzen (z. B. Munin-Key je Workspace)
secret: Mapped[str | None] = mapped_column(Text)
secret_hint: Mapped[str] = mapped_column(String(8), default="")
# Zusätzliche Header je Aufruf (z. B. X-DB-Password), als JSON verschlüsselt; angezeigt nur die Namen
headers: Mapped[str | None] = mapped_column(Text)
header_names: Mapped[list[str]] = mapped_column(JSON, default=list)
# Datenbank-Verbindung: host, port, database, user, sslmode (ohne Passwort)
config: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
active: Mapped[bool] = mapped_column(Boolean, default=True)
# memory: Gedächtnis wie Munin (→ Konzept 7.6); Einträge im Workspace sichtbar, löschen nur Menschen
# database: DB-Dienst (services/db-mcp), Vorlage für Datenbank-Verbindungen der Workspaces
role: Mapped[str] = mapped_column(String(20), default="", server_default="")
approval: Mapped[str] = mapped_column(String(20), default=APPROVED, server_default=APPROVED)
approval_note: Mapped[str] = mapped_column(Text, default="", server_default="")
status: Mapped[str] = mapped_column(String(20), default="new") # new | ok | error
last_error: Mapped[str] = mapped_column(Text, default="")
# Ergebnis des letzten Tests, z. B. „Oracle 23…, 4 Tabellen sichtbar“
status_message: Mapped[str] = mapped_column(Text, default="", server_default="")
# Bestätigung des Workspace-Admins, dass alle Mitglieder sehen dürfen, was der Server liefert
exposure_confirmed_by: Mapped[int | None] = mapped_column(ForeignKey("users.id"))
exposure_confirmed_at: Mapped[datetime | None]
discovered_at: Mapped[datetime | None]
server_info: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
created_by: Mapped[int] = mapped_column(ForeignKey("users.id"))
created: Mapped[datetime] = mapped_column(default=now)
class McpTool(Base):
"""Stand der letzten Discovery. Eingestuft wird über tool_definitions (Hash-Pinning)."""
__tablename__ = "mcp_tools"
__table_args__ = (UniqueConstraint("server_id", "remote_name"),)
id: Mapped[int] = mapped_column(primary_key=True)
server_id: Mapped[int] = mapped_column(ForeignKey("mcp_servers.id", ondelete="CASCADE"))
name: Mapped[str] = mapped_column(String(64), unique=True) # Name für das Modell
remote_name: Mapped[str] = mapped_column(String(200)) # Name auf dem Server
description: Mapped[str] = mapped_column(Text, default="")
input_schema: Mapped[dict[str, Any]] = mapped_column(JSON)
# MCP-Annotations (readOnlyHint …): nur Vorschlag für die Einstufung, nie automatisch übernommen
annotations: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
# Hash über URL des Servers, Name auf dem Server, Beschreibung und Schema: gleiche Tools desselben
# Dienstes (z. B. query jeder Oracle-Verbindung) erben die Einstufung
fingerprint: Mapped[str] = mapped_column(String(64), default="", server_default="")
class WorkspaceMcpServer(Base):
"""Freischaltung eines Servers für einen Workspace durch dessen Admin."""
__tablename__ = "workspace_mcp_servers"
workspace_id: Mapped[int] = mapped_column(ForeignKey("workspaces.id", ondelete="CASCADE"), primary_key=True)
server_id: Mapped[int] = mapped_column(ForeignKey("mcp_servers.id", ondelete="CASCADE"), primary_key=True)
secret: Mapped[str | None] = mapped_column(Text)
secret_hint: Mapped[str] = mapped_column(String(8), default="")
enabled_by: Mapped[int] = mapped_column(ForeignKey("users.id"))
enabled: Mapped[datetime] = mapped_column(default=now)