92 lines
5.2 KiB
Python
92 lines
5.2 KiB
Python
"""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)
|