New MCP server (app/gramps_mcp_server/) that exposes the GrampsWeb REST API as 12 MCP tools for genealogy data access: person_suchen, person_details, familie_details, ereignis_details, ort_suchen, ort_details, quelle_suchen, quelle_details, stammbaum_export, stammbaum_info, medien_liste, notiz_details. Includes HTTP client with auto-login/token management and Docker compose services for both prod and dev environments. Co-Authored-By: Paperclip <noreply@paperclip.ing>
271 lines
10 KiB
Python
271 lines
10 KiB
Python
"""
|
||
Lese-Tools für den GrampsWeb MCP Server (Phase 1).
|
||
|
||
12 Tools für Lese-Zugriff auf die GrampsWeb REST API:
|
||
- person_suchen, person_details
|
||
- familie_details
|
||
- ereignis_details
|
||
- ort_suchen, ort_details
|
||
- quelle_suchen, quelle_details
|
||
- stammbaum_export, stammbaum_info
|
||
- medien_liste
|
||
- notiz_details
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
from base64 import b64encode
|
||
|
||
|
||
def _fmt(data) -> str:
|
||
"""Formatiert Daten als JSON-String."""
|
||
return json.dumps(data, ensure_ascii=False, indent=2, default=str)
|
||
|
||
|
||
def _client():
|
||
from gramps_mcp_server.client import get_client
|
||
return get_client()
|
||
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Personen
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
def person_suchen(
|
||
suchbegriff: str = "",
|
||
seite: int = 1,
|
||
pro_seite: int = 20,
|
||
) -> str:
|
||
"""
|
||
Sucht Personen im Stammbaum nach Name.
|
||
|
||
Args:
|
||
suchbegriff: Name oder Suchbegriff (Vor-/Nachname)
|
||
seite: Seitennummer (ab 1)
|
||
pro_seite: Ergebnisse pro Seite (max. 100)
|
||
"""
|
||
pro_seite = min(pro_seite, 100)
|
||
client = _client()
|
||
|
||
if suchbegriff:
|
||
results = client.get(
|
||
"/api/search/",
|
||
params={
|
||
"query": suchbegriff,
|
||
"page": seite,
|
||
"pagesize": pro_seite,
|
||
},
|
||
)
|
||
else:
|
||
results = client.get(
|
||
"/api/people/",
|
||
params={
|
||
"page": seite,
|
||
"pagesize": pro_seite,
|
||
"sort": "surname",
|
||
},
|
||
)
|
||
|
||
return _fmt({"anzahl": len(results) if isinstance(results, list) else 0, "ergebnisse": results})
|
||
|
||
|
||
def person_details(handle: str) -> str:
|
||
"""
|
||
Gibt vollständige Details einer Person zurück inkl. Ereignisse, Familien, Medien.
|
||
|
||
Args:
|
||
handle: GrampsWeb-Handle der Person (z.B. aus person_suchen)
|
||
"""
|
||
client = _client()
|
||
person = client.get(f"/api/people/{handle}", params={"extend": "all", "profile": "all"})
|
||
return _fmt(person)
|
||
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Familien
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
def familie_details(handle: str) -> str:
|
||
"""
|
||
Gibt Details einer Familie zurück (Eltern, Kinder, Ereignisse).
|
||
|
||
Args:
|
||
handle: GrampsWeb-Handle der Familie
|
||
"""
|
||
client = _client()
|
||
family = client.get(f"/api/families/{handle}", params={"extend": "all", "profile": "all"})
|
||
return _fmt(family)
|
||
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Ereignisse
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
def ereignis_details(handle: str) -> str:
|
||
"""
|
||
Gibt Details eines Ereignisses zurück (Geburt, Tod, Heirat, etc.).
|
||
|
||
Args:
|
||
handle: GrampsWeb-Handle des Ereignisses
|
||
"""
|
||
client = _client()
|
||
event = client.get(f"/api/events/{handle}", params={"extend": "all", "profile": "all"})
|
||
return _fmt(event)
|
||
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Orte
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
def ort_suchen(
|
||
suchbegriff: str = "",
|
||
seite: int = 1,
|
||
pro_seite: int = 20,
|
||
) -> str:
|
||
"""
|
||
Sucht Orte im Stammbaum.
|
||
|
||
Args:
|
||
suchbegriff: Ortsname oder Suchbegriff
|
||
seite: Seitennummer (ab 1)
|
||
pro_seite: Ergebnisse pro Seite (max. 100)
|
||
"""
|
||
pro_seite = min(pro_seite, 100)
|
||
client = _client()
|
||
params = {"page": seite, "pagesize": pro_seite}
|
||
if suchbegriff:
|
||
params["q"] = suchbegriff
|
||
results = client.get("/api/places/", params=params)
|
||
return _fmt({"anzahl": len(results) if isinstance(results, list) else 0, "orte": results})
|
||
|
||
|
||
def ort_details(handle: str) -> str:
|
||
"""
|
||
Gibt Details eines Ortes zurück.
|
||
|
||
Args:
|
||
handle: GrampsWeb-Handle des Ortes
|
||
"""
|
||
client = _client()
|
||
place = client.get(f"/api/places/{handle}", params={"extend": "all", "profile": "all"})
|
||
return _fmt(place)
|
||
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Quellen
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
def quelle_suchen(
|
||
suchbegriff: str = "",
|
||
seite: int = 1,
|
||
pro_seite: int = 20,
|
||
) -> str:
|
||
"""
|
||
Sucht Quellen (Kirchenbücher, Urkunden, etc.) im Stammbaum.
|
||
|
||
Args:
|
||
suchbegriff: Quellenname oder Suchbegriff
|
||
seite: Seitennummer (ab 1)
|
||
pro_seite: Ergebnisse pro Seite (max. 100)
|
||
"""
|
||
pro_seite = min(pro_seite, 100)
|
||
client = _client()
|
||
params = {"page": seite, "pagesize": pro_seite}
|
||
if suchbegriff:
|
||
params["q"] = suchbegriff
|
||
results = client.get("/api/sources/", params=params)
|
||
return _fmt({"anzahl": len(results) if isinstance(results, list) else 0, "quellen": results})
|
||
|
||
|
||
def quelle_details(handle: str) -> str:
|
||
"""
|
||
Gibt Details einer Quelle zurück inkl. Zitierungen.
|
||
|
||
Args:
|
||
handle: GrampsWeb-Handle der Quelle
|
||
"""
|
||
client = _client()
|
||
source = client.get(f"/api/sources/{handle}", params={"extend": "all", "profile": "all"})
|
||
return _fmt(source)
|
||
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Stammbaum-Export & Info
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
def stammbaum_export(
|
||
format: str = "gedcom",
|
||
) -> str:
|
||
"""
|
||
Exportiert den Stammbaum als GEDCOM oder Gramps-XML.
|
||
|
||
Args:
|
||
format: Export-Format – 'gedcom' oder 'gramps' (Gramps-XML)
|
||
|
||
Gibt den Export als Base64-kodierten String zurück.
|
||
"""
|
||
allowed = {"gedcom", "gramps"}
|
||
if format not in allowed:
|
||
return _fmt({"fehler": f"Ungültiges Format '{format}'. Erlaubt: {', '.join(allowed)}"})
|
||
|
||
client = _client()
|
||
# GrampsWeb exporters endpoint
|
||
ext = "ged" if format == "gedcom" else "gramps"
|
||
data = client.get_raw(f"/api/exporters/{ext}/file")
|
||
encoded = b64encode(data).decode("ascii")
|
||
return _fmt({
|
||
"format": format,
|
||
"dateiname": f"stammbaum.{ext}",
|
||
"groesse_bytes": len(data),
|
||
"inhalt_base64": encoded[:200] + "..." if len(encoded) > 200 else encoded,
|
||
"hinweis": "Vollständiger Export als Base64. Bei großen Dateien ggf. abgeschnitten in der Anzeige.",
|
||
})
|
||
|
||
|
||
def stammbaum_info() -> str:
|
||
"""
|
||
Gibt Metadaten und Statistiken des Stammbaums zurück
|
||
(Anzahl Personen, Familien, Orte, etc.).
|
||
"""
|
||
client = _client()
|
||
metadata = client.get("/api/metadata/")
|
||
return _fmt(metadata)
|
||
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Medien
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
def medien_liste(
|
||
seite: int = 1,
|
||
pro_seite: int = 20,
|
||
) -> str:
|
||
"""
|
||
Listet Medienobjekte (Fotos, Dokumente, Scans) im Stammbaum auf.
|
||
|
||
Args:
|
||
seite: Seitennummer (ab 1)
|
||
pro_seite: Ergebnisse pro Seite (max. 50)
|
||
"""
|
||
pro_seite = min(pro_seite, 50)
|
||
client = _client()
|
||
results = client.get("/api/media/", params={"page": seite, "pagesize": pro_seite})
|
||
return _fmt({"anzahl": len(results) if isinstance(results, list) else 0, "medien": results})
|
||
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Notizen
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
def notiz_details(handle: str) -> str:
|
||
"""
|
||
Gibt den Inhalt einer Notiz zurück.
|
||
|
||
Args:
|
||
handle: GrampsWeb-Handle der Notiz
|
||
"""
|
||
client = _client()
|
||
note = client.get(f"/api/notes/{handle}")
|
||
return _fmt(note)
|