Compare commits
4 Commits
ebf7ec2d9e
...
3f9c3d6527
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f9c3d6527 | ||
|
|
cf7ea8f9a6 | ||
|
|
9f932abd2e | ||
|
|
69adab2c5e |
61
app/gramps_mcp_server/SKILL.md
Normal file
61
app/gramps_mcp_server/SKILL.md
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: grampsweb
|
||||
description: >
|
||||
GrampsWeb Ahnenforschung MCP Server – Zugriff auf Personen, Familien,
|
||||
Ereignisse, Orte, Quellen, Medien und Notizen des Stammbaums der Stiftung.
|
||||
Verwende diesen Skill für alle genealogischen Recherchen.
|
||||
mcp:
|
||||
command: python
|
||||
args:
|
||||
- "-m"
|
||||
- "gramps_mcp_server"
|
||||
cwd: ./app
|
||||
env:
|
||||
GRAMPS_URL: "${GRAMPS_URL:-http://grampsweb:5000}"
|
||||
GRAMPS_USERNAME: "${GRAMPS_USERNAME}"
|
||||
GRAMPS_PASSWORD: "${GRAMPS_PASSWORD}"
|
||||
---
|
||||
|
||||
# GrampsWeb Ahnenforschung
|
||||
|
||||
MCP-Server für die GrampsWeb-Genealogie-Datenbank der Stiftung.
|
||||
|
||||
## Verfügbare Tools (Phase 1 – Lesen)
|
||||
|
||||
| Tool | Beschreibung |
|
||||
|------|-------------|
|
||||
| `person_suchen` | Personen im Stammbaum nach Name suchen |
|
||||
| `person_details` | Vollständige Details einer Person (Events, Familien, Medien) |
|
||||
| `familie_details` | Familienverbindungen (Eltern, Kinder, Ereignisse) |
|
||||
| `ereignis_details` | Ereignis-Details (Geburt, Tod, Heirat, etc.) |
|
||||
| `ort_suchen` | Orte im Stammbaum suchen |
|
||||
| `ort_details` | Orts-Details |
|
||||
| `quelle_suchen` | Quellen (Kirchenbücher, Urkunden) suchen |
|
||||
| `quelle_details` | Quellen-Details mit Zitierungen |
|
||||
| `stammbaum_export` | GEDCOM oder Gramps-XML Export |
|
||||
| `stammbaum_info` | Metadaten und Statistiken des Stammbaums |
|
||||
| `medien_liste` | Medienobjekte (Fotos, Scans) auflisten |
|
||||
| `notiz_details` | Notiz-Inhalt lesen |
|
||||
|
||||
## Nutzung
|
||||
|
||||
### Personen suchen
|
||||
```
|
||||
person_suchen(suchbegriff="Müller", pro_seite=10)
|
||||
```
|
||||
|
||||
### Person-Details abrufen
|
||||
```
|
||||
person_details(handle="abc123def")
|
||||
```
|
||||
|
||||
### Stammbaum-Statistiken
|
||||
```
|
||||
stammbaum_info()
|
||||
```
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- GrampsWeb muss erreichbar sein (intern: `http://grampsweb:5000`)
|
||||
- Gültige `GRAMPS_USERNAME` und `GRAMPS_PASSWORD` Umgebungsvariablen
|
||||
- Python-Paket `mcp>=1.0.0` und `requests` müssen installiert sein
|
||||
0
app/gramps_mcp_server/__init__.py
Normal file
0
app/gramps_mcp_server/__init__.py
Normal file
4
app/gramps_mcp_server/__main__.py
Normal file
4
app/gramps_mcp_server/__main__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
"""Ermöglicht Start via: python -m gramps_mcp_server"""
|
||||
from gramps_mcp_server.server import mcp
|
||||
|
||||
mcp.run(transport="stdio")
|
||||
116
app/gramps_mcp_server/client.py
Normal file
116
app/gramps_mcp_server/client.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
HTTP-Client für die GrampsWeb REST API.
|
||||
|
||||
Konfiguration über Umgebungsvariablen:
|
||||
GRAMPS_URL – Basis-URL (z.B. http://grampsweb:5000)
|
||||
GRAMPS_USERNAME – Benutzername für Login
|
||||
GRAMPS_PASSWORD – Passwort für Login
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
import requests
|
||||
|
||||
logger = logging.getLogger("gramps_mcp_server.client")
|
||||
|
||||
|
||||
class GrampsWebClient:
|
||||
"""HTTP-Client für GrampsWeb mit automatischem Token-Management."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
base_url: str | None = None,
|
||||
username: str | None = None,
|
||||
password: str | None = None,
|
||||
):
|
||||
self.base_url = (base_url or os.environ.get("GRAMPS_URL", "http://grampsweb:5000")).rstrip("/")
|
||||
self.username = username or os.environ.get("GRAMPS_USERNAME", "")
|
||||
self.password = password or os.environ.get("GRAMPS_PASSWORD", "")
|
||||
self._session = requests.Session()
|
||||
self._token: str | None = None
|
||||
|
||||
def _ensure_auth(self) -> None:
|
||||
"""Login falls noch kein Token vorhanden."""
|
||||
if self._token:
|
||||
return
|
||||
if not self.username or not self.password:
|
||||
raise RuntimeError(
|
||||
"GRAMPS_USERNAME und GRAMPS_PASSWORD müssen gesetzt sein."
|
||||
)
|
||||
self._login()
|
||||
|
||||
def _login(self) -> None:
|
||||
"""Authentifizierung bei GrampsWeb und Token-Speicherung."""
|
||||
login_endpoints = [
|
||||
("/api/token/", "json"),
|
||||
("/api/login/", "json"),
|
||||
]
|
||||
for path, mode in login_endpoints:
|
||||
url = f"{self.base_url}{path}"
|
||||
payload = {"username": self.username, "password": self.password}
|
||||
try:
|
||||
if mode == "json":
|
||||
r = self._session.post(url, json=payload, timeout=15)
|
||||
else:
|
||||
r = self._session.post(url, data=payload, timeout=15)
|
||||
if r.status_code in (200, 201):
|
||||
data = r.json()
|
||||
token = (
|
||||
data.get("access_token")
|
||||
or data.get("token")
|
||||
or data.get("access")
|
||||
)
|
||||
if token:
|
||||
self._token = token
|
||||
self._session.headers["Authorization"] = f"Bearer {token}"
|
||||
logger.info("GrampsWeb Login erfolgreich via %s", path)
|
||||
return
|
||||
except Exception:
|
||||
continue
|
||||
raise RuntimeError(
|
||||
f"GrampsWeb Login fehlgeschlagen. URL: {self.base_url}, User: {self.username}"
|
||||
)
|
||||
|
||||
def _request(self, method: str, path: str, **kwargs) -> requests.Response:
|
||||
"""HTTP-Request mit automatischer Re-Auth bei 401."""
|
||||
self._ensure_auth()
|
||||
kwargs.setdefault("timeout", 30)
|
||||
url = f"{self.base_url}{path}"
|
||||
r = self._session.request(method, url, **kwargs)
|
||||
if r.status_code == 401:
|
||||
self._token = None
|
||||
self._login()
|
||||
r = self._session.request(method, url, **kwargs)
|
||||
r.raise_for_status()
|
||||
return r
|
||||
|
||||
def get(self, path: str, **kwargs) -> dict | list:
|
||||
"""GET-Request, gibt JSON zurück."""
|
||||
return self._request("GET", path, **kwargs).json()
|
||||
|
||||
def get_raw(self, path: str, **kwargs) -> bytes:
|
||||
"""GET-Request, gibt Rohdaten zurück (z.B. für Datei-Downloads)."""
|
||||
return self._request("GET", path, **kwargs).content
|
||||
|
||||
def post(self, path: str, **kwargs) -> dict | list:
|
||||
"""POST-Request, gibt JSON zurück."""
|
||||
return self._request("POST", path, **kwargs).json()
|
||||
|
||||
def put(self, path: str, **kwargs) -> dict | list:
|
||||
"""PUT-Request, gibt JSON zurück."""
|
||||
return self._request("PUT", path, **kwargs).json()
|
||||
|
||||
|
||||
# Singleton-Instanz
|
||||
_client: GrampsWebClient | None = None
|
||||
|
||||
|
||||
def get_client() -> GrampsWebClient:
|
||||
"""Gibt die (gecachte) Client-Instanz zurück."""
|
||||
global _client
|
||||
if _client is None:
|
||||
_client = GrampsWebClient()
|
||||
return _client
|
||||
90
app/gramps_mcp_server/server.py
Normal file
90
app/gramps_mcp_server/server.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""
|
||||
MCP Server für GrampsWeb Ahnenforschung.
|
||||
|
||||
Startmodus:
|
||||
python -m gramps_mcp_server
|
||||
|
||||
Konfiguration über Umgebungsvariablen:
|
||||
GRAMPS_URL – GrampsWeb Basis-URL (Standard: http://grampsweb:5000)
|
||||
GRAMPS_USERNAME – GrampsWeb Benutzername
|
||||
GRAMPS_PASSWORD – GrampsWeb Passwort
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.WARNING,
|
||||
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
||||
stream=sys.stderr,
|
||||
)
|
||||
logger = logging.getLogger("gramps_mcp_server")
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Startup-Check: GrampsWeb-Verbindung prüfen
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
_gramps_url = os.environ.get("GRAMPS_URL", "http://grampsweb:5000")
|
||||
_gramps_user = os.environ.get("GRAMPS_USERNAME", "")
|
||||
|
||||
if not _gramps_user:
|
||||
logger.error("GRAMPS_USERNAME nicht gesetzt. Server kann nicht starten.")
|
||||
sys.exit(1)
|
||||
|
||||
logger.info("GrampsWeb MCP Server startet – URL: %s, User: %s", _gramps_url, _gramps_user)
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# MCP Server Initialisierung
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
from mcp.server.fastmcp import FastMCP # noqa: E402
|
||||
|
||||
mcp = FastMCP(
|
||||
"GrampsWeb Ahnenforschung",
|
||||
instructions=(
|
||||
"MCP-Server für die GrampsWeb-Genealogie-Datenbank der Stiftung. "
|
||||
"Bietet Zugriff auf Personen, Familien, Ereignisse, Orte, Quellen, "
|
||||
"Medien und Notizen des Stammbaums. "
|
||||
f"Verbunden mit: {_gramps_url}"
|
||||
),
|
||||
)
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Lese-Tools registrieren (Phase 1)
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
from gramps_mcp_server.tools.lesen import ( # noqa: E402
|
||||
ereignis_details,
|
||||
familie_details,
|
||||
medien_liste,
|
||||
notiz_details,
|
||||
ort_details,
|
||||
ort_suchen,
|
||||
person_details,
|
||||
person_suchen,
|
||||
quelle_details,
|
||||
quelle_suchen,
|
||||
stammbaum_export,
|
||||
stammbaum_info,
|
||||
)
|
||||
|
||||
mcp.tool()(person_suchen)
|
||||
mcp.tool()(person_details)
|
||||
mcp.tool()(familie_details)
|
||||
mcp.tool()(ereignis_details)
|
||||
mcp.tool()(ort_suchen)
|
||||
mcp.tool()(ort_details)
|
||||
mcp.tool()(quelle_suchen)
|
||||
mcp.tool()(quelle_details)
|
||||
mcp.tool()(stammbaum_export)
|
||||
mcp.tool()(medien_liste)
|
||||
mcp.tool()(notiz_details)
|
||||
mcp.tool()(stammbaum_info)
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Server starten
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="stdio")
|
||||
0
app/gramps_mcp_server/tools/__init__.py
Normal file
0
app/gramps_mcp_server/tools/__init__.py
Normal file
270
app/gramps_mcp_server/tools/lesen.py
Normal file
270
app/gramps_mcp_server/tools/lesen.py
Normal file
@@ -0,0 +1,270 @@
|
||||
"""
|
||||
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)
|
||||
@@ -149,6 +149,19 @@ services:
|
||||
- ./app:/app
|
||||
command: ["python", "-m", "mcp_server"]
|
||||
|
||||
gramps-mcp:
|
||||
build: ./app
|
||||
depends_on:
|
||||
- grampsweb
|
||||
environment:
|
||||
- GRAMPS_URL=http://grampsweb:5000
|
||||
- GRAMPS_USERNAME=${GRAMPS_USERNAME:-admin@localhost}
|
||||
- GRAMPS_PASSWORD=${GRAMPS_PASSWORD:-gramps_dev_password}
|
||||
stdin_open: true
|
||||
volumes:
|
||||
- ./app:/app
|
||||
command: ["python", "-m", "gramps_mcp_server"]
|
||||
|
||||
ollama:
|
||||
image: ollama/ollama:latest
|
||||
# Kein externes Port-Mapping — nur über internes Docker-Netzwerk erreichbar
|
||||
|
||||
43
compose.yml
43
compose.yml
@@ -149,6 +149,22 @@ services:
|
||||
stdin_open: true
|
||||
command: ["python", "-m", "mcp_server"]
|
||||
|
||||
gramps-mcp:
|
||||
build:
|
||||
context: ./app
|
||||
args:
|
||||
APP_VERSION: ${APP_VERSION:-unknown}
|
||||
depends_on:
|
||||
- grampsweb
|
||||
environment:
|
||||
- GRAMPS_URL=http://grampsweb:5000
|
||||
- GRAMPS_USERNAME=${GRAMPS_USERNAME}
|
||||
- GRAMPS_PASSWORD=${GRAMPS_PASSWORD}
|
||||
# Kein Port-Mapping – nur internes Netz
|
||||
# Start via: docker compose run --rm gramps-mcp
|
||||
stdin_open: true
|
||||
command: ["python", "-m", "gramps_mcp_server"]
|
||||
|
||||
ollama:
|
||||
image: ollama/ollama:latest
|
||||
# Kein externes Port-Mapping — nur über internes Docker-Netzwerk erreichbar
|
||||
@@ -199,6 +215,33 @@ services:
|
||||
GRAMPSWEB_CELERY_CONFIG__result_backend: "redis://redis:6379/0"
|
||||
GRAMPSWEB_RATELIMIT_STORAGE_URI: "redis://redis:6379/1"
|
||||
GRAMPSWEB_BASE_URL: ${GRAMPSWEB_BASE_URL:-https://ahnenforschung.vhtv-stiftung.de}
|
||||
GRAMPSWEB_ADMIN_EMAIL: ${GRAMPSWEB_ADMIN_EMAIL:-admin@vhtv-stiftung.de}
|
||||
GRAMPSWEB_ADMIN_PASSWORD: ${GRAMPSWEB_ADMIN_PASSWORD:-nHcPMjEKORwqGxEO}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
echo "[grampsweb] Ensuring admin user exists ..."
|
||||
python3 << 'PYEOF' 2>&1 | grep -v Gtk
|
||||
from gramps_webapi.app import create_app
|
||||
from gramps_webapi.auth import add_user, get_number_users, ROLE_OWNER
|
||||
import os
|
||||
email = os.environ.get('GRAMPSWEB_ADMIN_EMAIL', '')
|
||||
pw = os.environ.get('GRAMPSWEB_ADMIN_PASSWORD', '')
|
||||
if email and pw:
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
if get_number_users() == 0:
|
||||
add_user(name='Admin', email=email, password=pw, role=ROLE_OWNER)
|
||||
print('[grampsweb] Admin user created')
|
||||
else:
|
||||
print('[grampsweb] Users already exist, skipping')
|
||||
else:
|
||||
print('[grampsweb] No admin credentials configured, skipping')
|
||||
PYEOF
|
||||
exec gunicorn -w $${GUNICORN_NUM_WORKERS:-4} -b 0.0.0.0:5000 \
|
||||
gramps_webapi.wsgi:app --timeout $${GUNICORN_TIMEOUT:-120} \
|
||||
--limit-request-line 8190
|
||||
volumes:
|
||||
- gramps_users:/app/users
|
||||
- gramps_index:/app/indexdir
|
||||
|
||||
Reference in New Issue
Block a user