Add GrampsWeb MCP server with Phase 1 read tools (STI-104)
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>
This commit is contained in:
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/", "form"),
|
||||||
|
("/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
|
- ./app:/app
|
||||||
command: ["python", "-m", "mcp_server"]
|
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:
|
ollama:
|
||||||
image: ollama/ollama:latest
|
image: ollama/ollama:latest
|
||||||
# Kein externes Port-Mapping — nur über internes Docker-Netzwerk erreichbar
|
# Kein externes Port-Mapping — nur über internes Docker-Netzwerk erreichbar
|
||||||
|
|||||||
16
compose.yml
16
compose.yml
@@ -149,6 +149,22 @@ services:
|
|||||||
stdin_open: true
|
stdin_open: true
|
||||||
command: ["python", "-m", "mcp_server"]
|
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:
|
ollama:
|
||||||
image: ollama/ollama:latest
|
image: ollama/ollama:latest
|
||||||
# Kein externes Port-Mapping — nur über internes Docker-Netzwerk erreichbar
|
# Kein externes Port-Mapping — nur über internes Docker-Netzwerk erreichbar
|
||||||
|
|||||||
Reference in New Issue
Block a user