97 lines
3.9 KiB
Bash
Executable File
97 lines
3.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
CONTAINER=${OPENWEBUI_CONTAINER:-mike-ai-open-webui}
|
|
ENV_FILE=${NAVIDROME_MCP_ENV_FILE:-/etc/mike-ai/navidrome-mcp.env}
|
|
|
|
die() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; }
|
|
[[ $EUID -eq 0 ]] || die "Bitte als root ausführen."
|
|
[[ -s $ENV_FILE ]] || die "Navidrome-Secret-Datei fehlt."
|
|
[[ $(docker inspect -f '{{.State.Health.Status}}' mike-ai-mcp-navidrome 2>/dev/null || true) == healthy ]] || \
|
|
die "Navidrome-MCP ist nicht gesund."
|
|
[[ $(docker inspect -f '{{.State.Health.Status}}' "$CONTAINER" 2>/dev/null || true) == healthy ]] || \
|
|
die "OpenWebUI ist nicht gesund."
|
|
|
|
expect_lastfm=false
|
|
grep -q '^LASTFM_API_KEY=..' "$ENV_FILE" && expect_lastfm=true
|
|
|
|
result=$(docker exec -i -e EXPECT_LASTFM="$expect_lastfm" "$CONTAINER" python - <<'PY'
|
|
import asyncio
|
|
import json
|
|
import os
|
|
import sqlite3
|
|
from mcp import ClientSession
|
|
from mcp.client.streamable_http import streamablehttp_client
|
|
|
|
EXPECTED_LASTFM = {
|
|
"get_similar_artists", "get_similar_tracks", "get_artist_info",
|
|
"get_top_tracks_by_artist", "get_trending_music", "get_artist_albums",
|
|
"get_album_info",
|
|
}
|
|
PLAYBACK = {"play_songs", "pause", "set_volume"}
|
|
|
|
def find_unanchored(value, path=""):
|
|
bad = []
|
|
if isinstance(value, dict):
|
|
for key, child in value.items():
|
|
here = f"{path}.{key}" if path else key
|
|
if key == "pattern" and (
|
|
not isinstance(child, str)
|
|
or not child.startswith("^")
|
|
or not child.endswith("$")
|
|
):
|
|
bad.append(here)
|
|
bad.extend(find_unanchored(child, here))
|
|
elif isinstance(value, list):
|
|
for index, child in enumerate(value):
|
|
bad.extend(find_unanchored(child, f"{path}[{index}]"))
|
|
return bad
|
|
|
|
async def verify():
|
|
async with streamablehttp_client(
|
|
"http://mike-ai-mcp-navidrome:3000/mcp"
|
|
) as (read, write, _):
|
|
async with ClientSession(read, write) as session:
|
|
await session.initialize()
|
|
result = await session.list_tools()
|
|
names = {tool.name for tool in result.tools}
|
|
bad = []
|
|
for tool in result.tools:
|
|
bad.extend(find_unanchored(tool.inputSchema, tool.name))
|
|
if bad:
|
|
raise SystemExit("Unverankerte JSON-Schema-Patterns: " + ", ".join(bad))
|
|
if PLAYBACK & names:
|
|
raise SystemExit("Playback-Werkzeuge sind auf dem Headless-Host aktiv.")
|
|
expect_lastfm = os.environ.get("EXPECT_LASTFM") == "true"
|
|
if expect_lastfm and not EXPECTED_LASTFM <= names:
|
|
raise SystemExit("Last.fm-Werkzeugkatalog ist unvollständig.")
|
|
if not expect_lastfm and EXPECTED_LASTFM & names:
|
|
raise SystemExit("Last.fm-Werkzeuge sind ohne konfigurierten Schlüssel aktiv.")
|
|
if expect_lastfm:
|
|
# Public metadata only. Do not print the returned chart data.
|
|
response = await session.call_tool(
|
|
"get_trending_music", {"type": "artists", "limit": 1}
|
|
)
|
|
if response.isError:
|
|
raise SystemExit("Öffentliche Last.fm-Testabfrage ist fehlgeschlagen.")
|
|
|
|
con = sqlite3.connect("/app/backend/data/webui.db")
|
|
row = con.execute(
|
|
"select value from config where key=?", ("tool_server.connections",)
|
|
).fetchone()
|
|
connections = json.loads(row[0]) if row else []
|
|
ids = {
|
|
str((connection.get("info") or {}).get("id", ""))
|
|
for connection in connections if isinstance(connection, dict)
|
|
}
|
|
if "navidrome-local" not in ids:
|
|
raise SystemExit("OpenWebUI-Verbindung navidrome-local fehlt.")
|
|
print(f"NAVIDROME_ACCEPTANCE_OK tools={len(names)} lastfm={str(expect_lastfm).lower()}")
|
|
|
|
asyncio.run(verify())
|
|
PY
|
|
)
|
|
[[ $result == NAVIDROME_ACCEPTANCE_OK\ * ]] || \
|
|
die "Navidrome-Abnahme lieferte keinen gültigen Erfolgsmarker."
|
|
printf '%s\n' "$result"
|