simplify Athena runtime and centralize MCP management
This commit is contained in:
@@ -99,6 +99,9 @@ class Handler(BaseHTTPRequestHandler):
|
||||
self.send_header("Connection", "close")
|
||||
self.end_headers()
|
||||
model = body.get("model")
|
||||
delay = body.get("mock_stream_delay", 0.05)
|
||||
if not isinstance(delay, (int, float)) or delay < 0 or delay > 10:
|
||||
delay = 0.05
|
||||
for tok in ["Mock-", "Streaming", "-Antwort", f" ({model})"]:
|
||||
chunk = {
|
||||
"id": "chatcmpl-mock",
|
||||
@@ -109,9 +112,12 @@ class Handler(BaseHTTPRequestHandler):
|
||||
}
|
||||
self.wfile.write(f"data: {json.dumps(chunk)}\n\n".encode())
|
||||
self.wfile.flush()
|
||||
time.sleep(0.05)
|
||||
self.wfile.write(b"data: [DONE]\n\n")
|
||||
self.wfile.flush()
|
||||
time.sleep(delay)
|
||||
try:
|
||||
self.wfile.write(b"data: [DONE]\n\n")
|
||||
self.wfile.flush()
|
||||
except (BrokenPipeError, ConnectionResetError):
|
||||
pass
|
||||
|
||||
def _json(self, code: int, payload: dict) -> None:
|
||||
body = json.dumps(payload).encode()
|
||||
|
||||
@@ -183,6 +183,34 @@ echo "$RESP"
|
||||
echo "$RESP" | grep -q "data: " && echo "$RESP" | grep -q "\[DONE\]" \
|
||||
&& ok "SSE-Stream mit [DONE] erhalten" || bad "Streaming"
|
||||
|
||||
echo "== Test 4b: Client-Abbruch gibt den Router-Slot frei"
|
||||
command curl -H "Authorization: Bearer $TEST_ROUTER_KEY" -sfN \
|
||||
"$BASE/v1/chat/completions" -H "Content-Type: application/json" \
|
||||
-d '{"model":"qwen-fast","stream":true,"mock_stream_delay":2,"messages":[{"role":"user","content":"Abbruch"}]}' \
|
||||
>/tmp/aborted-stream.txt 2>/dev/null &
|
||||
ABORT_PID=$!
|
||||
sleep 0.4
|
||||
kill "$ABORT_PID" 2>/dev/null || true
|
||||
wait "$ABORT_PID" 2>/dev/null || true
|
||||
for _ in $(seq 1 30); do
|
||||
ACTIVE=$(curl -sf "$BASE/status" | python3 -c 'import json,sys; print(json.load(sys.stdin)["qwen"]["active_chats"])')
|
||||
[[ $ACTIVE == 0 ]] && break
|
||||
sleep 0.1
|
||||
done
|
||||
CODE=$(curl -s -o /tmp/after-abort.json -w "%{http_code}" "$BASE/v1/chat/completions" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"qwen-fast","messages":[{"role":"user","content":"noch frei?"}]}')
|
||||
[[ ${ACTIVE:-1} == 0 && $CODE == 200 ]] \
|
||||
&& ok "abgebrochener Stream gibt Lease frei; Folgerequest erfolgreich" \
|
||||
|| bad "Stream-Abbruch hinterließ active_chats=${ACTIVE:-?}, HTTP $CODE"
|
||||
|
||||
if [[ ${ROUTER_TEST_QUICK:-0} == 1 ]]; then
|
||||
echo
|
||||
echo "== Schnellergebnis: $PASS bestanden, $FAIL fehlgeschlagen =="
|
||||
[[ $FAIL -eq 0 ]]
|
||||
exit
|
||||
fi
|
||||
|
||||
# --- 5. Tool Calls -----------------------------------------------------------------
|
||||
echo "== Test 5: Tool Calls"
|
||||
RESP=$(curl -sf "$BASE/v1/chat/completions" -H "Content-Type: application/json" \
|
||||
|
||||
@@ -66,6 +66,41 @@ class RegistryTests(unittest.TestCase):
|
||||
con.close()
|
||||
self.assertEqual(ids, ["unmanaged", "one"])
|
||||
|
||||
def test_production_registry_has_unique_ids_and_fritzbox(self):
|
||||
document = json.loads((ROOT / "config/mcp-registry.json").read_text())
|
||||
ids = [item["id"] for item in document["servers"]]
|
||||
hermes_ids = [
|
||||
item.get("hermes_id", item["id"])
|
||||
for item in document["servers"] if "hermes" in item.get("clients", [])
|
||||
]
|
||||
self.assertEqual(len(ids), len(set(ids)))
|
||||
self.assertEqual(len(hermes_ids), len(set(hermes_ids)))
|
||||
fritz = next(item for item in document["servers"] if item["id"] == "fritzbox-local")
|
||||
self.assertEqual(fritz["hub"]["type"], "stdio")
|
||||
self.assertIn("fritz-mcp", fritz["hub"]["args"][-1])
|
||||
self.assertEqual(len(fritz["tool_include"]), 4)
|
||||
|
||||
def test_hermes_tool_filter_is_generated(self):
|
||||
item = {
|
||||
"id": "wide", "name": "Wide", "description": "Test",
|
||||
"url": "http://wide/mcp", "clients": ["hermes"],
|
||||
"tool_include": ["list", "describe", "call"],
|
||||
}
|
||||
block = self.module.hermes_block([item])
|
||||
self.assertIn(" tools:\n include:", block)
|
||||
self.assertIn(' - "describe"', block)
|
||||
|
||||
def test_raw_mcphub_token_can_replace_host_specific_env_file(self):
|
||||
item = {
|
||||
"id": "hub", "name": "Hub", "description": "Test",
|
||||
"url": "http://hub/mcp/test", "clients": ["hermes"],
|
||||
"env_file": "/missing/client.env",
|
||||
"key_env": "MCPHUB_BEARER_TOKEN",
|
||||
}
|
||||
self.module.CLIENT_TOKEN = "local-token"
|
||||
self.assertTrue(self.module.enabled(item))
|
||||
self.assertEqual(self.module.resolved(item), ("http://hub/mcp/test", "local-token"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Regression tests for declarative, update-safe MCPHub settings."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).parents[1]
|
||||
SOURCE = ROOT / "platform/mcphub/configure-settings.py"
|
||||
|
||||
|
||||
def load_module():
|
||||
spec = importlib.util.spec_from_file_location("configure_settings", SOURCE)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
assert spec.loader
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
class MCPHubSettingsTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.module = load_module()
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.root = Path(self.temp.name)
|
||||
self.secrets = self.root / "secrets"
|
||||
self.secrets.mkdir()
|
||||
(self.secrets / "remote.env").write_text(
|
||||
"REMOTE_URL=http://example.test/mcp\nTOKEN=secret-value\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
self.registry = self.root / "registry.json"
|
||||
self.registry.write_text(json.dumps({
|
||||
"version": 1,
|
||||
"servers": [
|
||||
{
|
||||
"id": "remote-local",
|
||||
"hermes_id": "remote",
|
||||
"hub": {
|
||||
"type": "streamable-http",
|
||||
"secret_file": "remote.env",
|
||||
"url": "${REMOTE_URL}",
|
||||
"headers": {"Authorization": "Bearer ${TOKEN}"},
|
||||
"enabled": True,
|
||||
},
|
||||
},
|
||||
{"id": "client-only", "url": "http://unused/mcp"},
|
||||
],
|
||||
}), encoding="utf-8")
|
||||
|
||||
def tearDown(self):
|
||||
self.temp.cleanup()
|
||||
|
||||
def test_registry_renders_only_hub_servers_and_expands_secrets(self):
|
||||
servers = self.module.registry_servers(self.registry, self.secrets, {})
|
||||
self.assertEqual(list(servers), ["remote"])
|
||||
self.assertEqual(servers["remote"]["url"], "http://example.test/mcp")
|
||||
self.assertEqual(
|
||||
servers["remote"]["headers"]["Authorization"],
|
||||
"Bearer secret-value",
|
||||
)
|
||||
|
||||
def test_existing_enabled_toggle_survives_reconciliation(self):
|
||||
servers = self.module.registry_servers(
|
||||
self.registry, self.secrets, {"remote": {"enabled": False}}
|
||||
)
|
||||
self.assertFalse(servers["remote"]["enabled"])
|
||||
|
||||
def test_missing_secret_fails_closed(self):
|
||||
os.unlink(self.secrets / "remote.env")
|
||||
with self.assertRaises(SystemExit):
|
||||
self.module.registry_servers(self.registry, self.secrets, {})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user