Improve external service discovery and tool routing
This commit is contained in:
1 parent
b5118099ed
commit
feb99293b5
10 files changed
+198
-10
No files matched your search
@@ -65,6 +65,15 @@ failure. For public information make at most one focused fallback attempt with
|
|||||||
the general web tool, then synthesize the available evidence or stop clearly.
|
the general web tool, then synthesize the available evidence or stop clearly.
|
||||||
Never enter a fallback or synonym-search loop.
|
Never enter a fallback or synonym-search loop.
|
||||||
|
|
||||||
|
Before designing, installing or migrating a backend, query the versioned
|
||||||
|
external-service catalog and then the listed specialist tool. Existing services
|
||||||
|
on Unraid or elsewhere in the home network are dependencies to integrate, not
|
||||||
|
components to duplicate. If inventory or specialist verification is missing,
|
||||||
|
disabled, unreachable or inconclusive, stop and ask the user. Never fill that
|
||||||
|
knowledge gap by proposing or deploying a replacement service. A duplicate is
|
||||||
|
allowed only when the user explicitly requests migration, replacement,
|
||||||
|
redundancy or an isolated experiment after the existing service was identified.
|
||||||
|
|
||||||
For models and GPU services, introduce changes only through the experimental
|
For models and GPU services, introduce changes only through the experimental
|
||||||
profile or an isolated container. Change one variable at a time, record source,
|
profile or an isolated container. Change one variable at a time, record source,
|
||||||
license, revision, size and SHA256, account for weights, KV cache, projector,
|
license, revision, size and SHA256, account for weights, KV cache, projector,
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"updated": "2026-08-23",
|
||||||
|
"scope": "Verified external services used by Athena; no credentials or secret values.",
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"id": "unraid",
|
||||||
|
"name": "HomeServer Unraid",
|
||||||
|
"location": "home-network",
|
||||||
|
"host": "homeserver.fritz.box",
|
||||||
|
"address": "192.168.1.2",
|
||||||
|
"port": 5001,
|
||||||
|
"protocol": "https",
|
||||||
|
"probe": "tcp",
|
||||||
|
"specialist_tool": "unraid-readonly-local",
|
||||||
|
"purpose": "Existing NAS, Docker host and storage platform. Inspect it before planning any replacement service on Athena."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mua",
|
||||||
|
"name": "MUA Unraid management",
|
||||||
|
"location": "home-network",
|
||||||
|
"host": "192.168.1.2",
|
||||||
|
"address": "192.168.1.2",
|
||||||
|
"port": 3002,
|
||||||
|
"protocol": "http",
|
||||||
|
"probe": "tcp",
|
||||||
|
"specialist_tool": "mua",
|
||||||
|
"purpose": "Explicitly enabled Unraid management actions; not the default diagnostic path."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "deemix",
|
||||||
|
"name": "Existing Deemix on Unraid",
|
||||||
|
"location": "unraid-container",
|
||||||
|
"host": "192.168.1.2",
|
||||||
|
"address": "192.168.1.2",
|
||||||
|
"port": 6595,
|
||||||
|
"protocol": "http",
|
||||||
|
"probe": "http-head",
|
||||||
|
"probe_path": "/",
|
||||||
|
"specialist_tool": "unraid-readonly-local",
|
||||||
|
"purpose": "Existing Deemix backend and download location. An Athena MCP must integrate this instance over WireGuard; it must not create a second Deemix unless the user explicitly requests migration or replacement."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -187,6 +187,16 @@ class AutoToolSelectorTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
["server:mcp:github-local", "server:mcp:athena-operator-local"],
|
["server:mcp:github-local", "server:mcp:athena-operator-local"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def test_existing_unraid_backend_beats_duplicate_operator_plan(self):
|
||||||
|
result = await self._select(
|
||||||
|
"Baue aus https://github.com/foo/deemix einen MCP. Deemix läuft bereits als Container auf Unraid; prüfe ihn zuerst."
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
result["tool_ids"],
|
||||||
|
["server:mcp:github-local", "server:mcp:unraid-readonly-local"],
|
||||||
|
)
|
||||||
|
self.assertIn("Never compensate", result["messages"][0]["content"])
|
||||||
|
|
||||||
async def test_github_and_explicit_web_are_bounded_to_two(self):
|
async def test_github_and_explicit_web_are_bounded_to_two(self):
|
||||||
result = await self._select(
|
result = await self._select(
|
||||||
"Prüfe dieses GitHub Repository und suche zusätzlich im Netz nach Nutzerstimmen."
|
"Prüfe dieses GitHub Repository und suche zusätzlich im Netz nach Nutzerstimmen."
|
||||||
|
|||||||
@@ -30,16 +30,22 @@ def main():
|
|||||||
state = base / "state"
|
state = base / "state"
|
||||||
runtime = base / "runtime.json"
|
runtime = base / "runtime.json"
|
||||||
(repo / "docs").mkdir(parents=True)
|
(repo / "docs").mkdir(parents=True)
|
||||||
|
(repo / "config").mkdir(parents=True)
|
||||||
docs.mkdir()
|
docs.mkdir()
|
||||||
(repo / "docs/PLATFORM_OVERVIEW.md").write_text("# Athena\nRouter and recovery.\n")
|
(repo / "docs/PLATFORM_OVERVIEW.md").write_text("# Athena\nRouter and recovery.\n")
|
||||||
(repo / "docs/OPERATIONS.md").write_text("# Operations\nUse bounded tools.\n")
|
(repo / "docs/OPERATIONS.md").write_text("# Operations\nUse bounded tools.\n")
|
||||||
(docs / "PLATFORM_OVERVIEW.md").write_text("# Athena\nRouter and recovery.\n")
|
(docs / "PLATFORM_OVERVIEW.md").write_text("# Athena\nRouter and recovery.\n")
|
||||||
|
(repo / "config/service-catalog.json").write_text(json.dumps({
|
||||||
|
"version": 1,
|
||||||
|
"services": [{"id": "test", "name": "Test", "address": "127.0.0.1", "port": 9, "protocol": "tcp", "probe": "tcp"}],
|
||||||
|
}))
|
||||||
runtime.write_text(json.dumps({"generated_unix": 4102444800, "generated_at": "2100-01-01T00:00:00Z", "source_commit": "abc", "containers": []}))
|
runtime.write_text(json.dumps({"generated_unix": 4102444800, "generated_at": "2100-01-01T00:00:00Z", "source_commit": "abc", "containers": []}))
|
||||||
m = load_module(repo, docs, runtime, state)
|
m = load_module(repo, docs, runtime, state)
|
||||||
|
|
||||||
assert len(m.TOOLS) == 9
|
assert len(m.TOOLS) == 10
|
||||||
assert m.overview()["source"] == "docs/PLATFORM_OVERVIEW.md"
|
assert m.overview()["source"] == "docs/PLATFORM_OVERVIEW.md"
|
||||||
assert m.current_state()["available"] is True
|
assert m.current_state()["available"] is True
|
||||||
|
assert m.external_services()["services"][0]["id"] == "test"
|
||||||
assert m.search_knowledge({"query": "recovery", "max_results": 3})["count"] >= 1
|
assert m.search_knowledge({"query": "recovery", "max_results": 3})["count"] >= 1
|
||||||
assert "Operations" in m.read_source({"path": "docs/OPERATIONS.md"})["content"]
|
assert "Operations" in m.read_source({"path": "docs/OPERATIONS.md"})["content"]
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ Operator-Kontext in jeden Prompt zu kopieren.
|
|||||||
|
|
||||||
Der MCP ist zugleich das kontrollierte Pflegefenster für seine eigene
|
Der MCP ist zugleich das kontrollierte Pflegefenster für seine eigene
|
||||||
Dokumentation. Er ist **kein** allgemeiner Athena-Administrator und erhält
|
Dokumentation. Er ist **kein** allgemeiner Athena-Administrator und erhält
|
||||||
weder Docker-Socket noch Shell, Netzwerkzugang, Git-Schlüssel oder Secrets.
|
weder Docker-Socket noch Shell, Git-Schlüssel oder Secrets. Sein Netzzugang ist
|
||||||
|
auf feste, versionierte Erreichbarkeitsprüfungen aus dem Diensteverzeichnis
|
||||||
|
beschränkt; Modellparameter können keine freie Adresse vorgeben.
|
||||||
|
|
||||||
## Werkzeuge
|
## Werkzeuge
|
||||||
|
|
||||||
@@ -19,6 +21,7 @@ weder Docker-Socket noch Shell, Netzwerkzugang, Git-Schlüssel oder Secrets.
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `athena_get_overview` | kurze Architektur und Quellenhierarchie |
|
| `athena_get_overview` | kurze Architektur und Quellenhierarchie |
|
||||||
| `athena_get_current_state` | begrenzter aktueller Snapshot ohne Nutzdaten |
|
| `athena_get_current_state` | begrenzter aktueller Snapshot ohne Nutzdaten |
|
||||||
|
| `athena_get_external_services` | vorhandene externe Dienste plus feste, bounded Erreichbarkeitsprüfung |
|
||||||
| `athena_search_knowledge` | Suche in Dokumentation und versionierten Quellen |
|
| `athena_search_knowledge` | Suche in Dokumentation und versionierten Quellen |
|
||||||
| `athena_read_source` | begrenzter Ausschnitt einer ausgewählten Textdatei |
|
| `athena_read_source` | begrenzter Ausschnitt einer ausgewählten Textdatei |
|
||||||
| `athena_get_change_workflow` | verbindlicher Ablauf je Änderungstyp |
|
| `athena_get_change_workflow` | verbindlicher Ablauf je Änderungstyp |
|
||||||
@@ -45,6 +48,13 @@ Dateiinhalte außerhalb der versionierten Dokumentation oder Secretwerte. Der
|
|||||||
Container liest nur die erzeugte JSON-Datei. Ein Snapshot älter als drei
|
Container liest nur die erzeugte JSON-Datei. Ein Snapshot älter als drei
|
||||||
Minuten gilt als veraltet.
|
Minuten gilt als veraltet.
|
||||||
|
|
||||||
|
Das zusätzliche Diensteverzeichnis unter `config/service-catalog.json` enthält
|
||||||
|
nur bekannte interne Namen, Adressen, Ports, Zuständigkeiten und Zwecke, keine
|
||||||
|
Zugangsdaten. `athena_get_external_services` prüft ausschließlich diese festen
|
||||||
|
Einträge. Es ist kein Portscanner, liest keine Antwortinhalte und akzeptiert
|
||||||
|
keine URL oder Adresse aus dem Modell. Für Details bleibt anschließend das im
|
||||||
|
Katalog genannte Fachwerkzeug zuständig.
|
||||||
|
|
||||||
## Dokumentationspflege
|
## Dokumentationspflege
|
||||||
|
|
||||||
Die Pflege ist absichtlich zweistufig:
|
Die Pflege ist absichtlich zweistufig:
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ Host-Snapshot ersetzt einen Docker-Socket. Dokumentationsänderungen laufen nur
|
|||||||
bleiben getrennte, nachzuweisende Abschlussarbeiten. Details stehen in
|
bleiben getrennte, nachzuweisende Abschlussarbeiten. Details stehen in
|
||||||
`PLATFORM_CONTEXT_MCP.md`.
|
`PLATFORM_CONTEXT_MCP.md`.
|
||||||
|
|
||||||
|
Das versionierte, secret-freie Diensteverzeichnis
|
||||||
|
`config/service-catalog.json` dokumentiert bereits vorhandene externe
|
||||||
|
Abhängigkeiten. Vor der Planung eines neuen Backends muss es gelesen und der
|
||||||
|
Bestand mit dem dort genannten Fachwerkzeug geprüft werden. Ein nicht
|
||||||
|
erreichbares Werkzeug bedeutet „nicht verifiziert“, niemals „nicht vorhanden“.
|
||||||
|
|
||||||
## Hardware
|
## Hardware
|
||||||
|
|
||||||
- Debian 13 `trixie`, Kernel 6.12
|
- Debian 13 `trixie`, Kernel 6.12
|
||||||
@@ -58,6 +64,10 @@ Open WebUI ---> Profile Router ---> Profile Controller ---> genau ein llama.cpp-
|
|||||||
+-- Unraid
|
+-- Unraid
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Deemix läuft bereits als Container auf dem Unraid-HomeServer. Eine künftige
|
||||||
|
Deemix-MCP-Integration auf Athena verwendet dieses Backend über WireGuard und
|
||||||
|
erzeugt nicht ungefragt eine zweite Deemix-Instanz.
|
||||||
|
|
||||||
Open WebUI und Router veröffentlichen keinen normalen Host-Port. Der
|
Open WebUI und Router veröffentlichen keinen normalen Host-Port. Der
|
||||||
WireGuard-Gateway-Container stellt nur die vorgesehenen VPN-Endpunkte bereit.
|
WireGuard-Gateway-Container stellt nur die vorgesehenen VPN-Endpunkte bereit.
|
||||||
Anwendungscontainer erreichen Heimnetz und Internet fail-closed über WireGuard;
|
Anwendungscontainer erreichen Heimnetz und Internet fail-closed über WireGuard;
|
||||||
|
|||||||
@@ -37,6 +37,13 @@ Dokumentations-Schreibweg darf erst nach Vorschau und ausdrücklicher Freigabe
|
|||||||
verwendet werden. Eine lokale Dokumentationsänderung ist ohne separaten
|
verwendet werden. Eine lokale Dokumentationsänderung ist ohne separaten
|
||||||
Git-Commit/Push und erneuerten Recovery-Koffer nicht abgeschlossen.
|
Git-Commit/Push und erneuerten Recovery-Koffer nicht abgeschlossen.
|
||||||
|
|
||||||
|
Vor einem neuen Backend, Relay oder MCP liest du zusätzlich mit
|
||||||
|
`athena_get_external_services` das versionierte Diensteverzeichnis. Prüfe den
|
||||||
|
gefundenen Bestand danach mit dem genannten Fachwerkzeug. Scheitert diese
|
||||||
|
Prüfung, stoppst du und meldest die Lücke. Du darfst aus einem Toolfehler oder
|
||||||
|
fehlenden Zugriff niemals ableiten, dass der Dienst nicht existiert, und als
|
||||||
|
Ersatz ungefragt eine zweite Instanz planen.
|
||||||
|
|
||||||
## 2. Auftrag und Einsatzumgebung
|
## 2. Auftrag und Einsatzumgebung
|
||||||
|
|
||||||
MikeAI stellt lokal Inferenz, multimodale Bildanalyse, Bildgenerierung,
|
MikeAI stellt lokal Inferenz, multimodale Bildanalyse, Bildgenerierung,
|
||||||
@@ -235,6 +242,11 @@ Netzzugriff. Kein MCP-Port wird am Host veröffentlicht.
|
|||||||
| Unraid | Host-, Docker-, Array-, Netzwerk- und Logdiagnose | read-only Standard |
|
| Unraid | Host-, Docker-, Array-, Netzwerk- und Logdiagnose | read-only Standard |
|
||||||
| MUA/Admin | eng definierte Unraid-Verwaltung | bewusst aktivieren |
|
| MUA/Admin | eng definierte Unraid-Verwaltung | bewusst aktivieren |
|
||||||
|
|
||||||
|
Der vorhandene Deemix-Dienst läuft auf Unraid und ist als externe Abhängigkeit
|
||||||
|
im Diensteverzeichnis eingetragen. Für ein Deemix-MCP wird standardmäßig nur
|
||||||
|
ein Relay auf Athena gebaut; ein zweites Deemix-Backend erfordert einen
|
||||||
|
ausdrücklichen Migrations-, Ersatz- oder Testauftrag.
|
||||||
|
|
||||||
`Athena Operator` ist die zentrale Arbeitsumgebung für Änderungen an Athena.
|
`Athena Operator` ist die zentrale Arbeitsumgebung für Änderungen an Athena.
|
||||||
Nutze ihn zum Lesen der tatsächlichen Quellen, Erstellen und Anwenden von
|
Nutze ihn zum Lesen der tatsächlichen Quellen, Erstellen und Anwenden von
|
||||||
Dateiänderungen, Testen, Deployen von Compose-Diensten und MCPs, Verwalten der
|
Dateiänderungen, Testen, Deployen von Compose-Diensten und MCPs, Verwalten der
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.platform-context
|
dockerfile: Dockerfile.platform-context
|
||||||
image: mike-ai/mcp-platform-context:1.0.0
|
image: mike-ai/mcp-platform-context:1.1.0
|
||||||
container_name: mike-ai-mcp-platform-context
|
container_name: mike-ai-mcp-platform-context
|
||||||
environment:
|
environment:
|
||||||
ATHENA_REPO_ROOT: /knowledge/repo
|
ATHENA_REPO_ROOT: /knowledge/repo
|
||||||
@@ -158,7 +158,9 @@ services:
|
|||||||
- ${PLATFORM_DOCS_DIR:-/opt/mike-ai/stack/docs}:/workspace/docs:rw
|
- ${PLATFORM_DOCS_DIR:-/opt/mike-ai/stack/docs}:/workspace/docs:rw
|
||||||
- ${PLATFORM_CONTEXT_RUNTIME_DIR:-/var/lib/mike-ai-platform-context}:/runtime:ro
|
- ${PLATFORM_CONTEXT_RUNTIME_DIR:-/var/lib/mike-ai-platform-context}:/runtime:ro
|
||||||
- ${PLATFORM_CONTEXT_STATE_DIR:-/data/mike-ai-platform-context}:/state:rw
|
- ${PLATFORM_CONTEXT_STATE_DIR:-/data/mike-ai-platform-context}:/state:rw
|
||||||
networks: [tools]
|
# The context service has no arbitrary URL input. Its only egress use is a
|
||||||
|
# bounded reachability check of endpoints from config/service-catalog.json.
|
||||||
|
networks: [tools, egress]
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "python", "-c", "import socket; s=socket.create_connection(('127.0.0.1',8000),2); s.close()"]
|
test: ["CMD", "python", "-c", "import socket; s=socket.create_connection(('127.0.0.1',8000),2); s.close()"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
@@ -224,6 +226,10 @@ services:
|
|||||||
UNRAID_RMCP_DISABLE_HTTP_AUTH: "true"
|
UNRAID_RMCP_DISABLE_HTTP_AUTH: "true"
|
||||||
UNRAID_NOAUTH: "true"
|
UNRAID_NOAUTH: "true"
|
||||||
UNRAID_RMCP_ALLOWED_HOSTS: "mike-ai-mcp-unraid-official:8000,mike-ai-mcp-unraid-official,localhost:8000,127.0.0.1:8000"
|
UNRAID_RMCP_ALLOWED_HOSTS: "mike-ai-mcp-unraid-official:8000,mike-ai-mcp-unraid-official,localhost:8000,127.0.0.1:8000"
|
||||||
|
# Public DNS cannot resolve the Fritzbox-only name. Preserve the hostname
|
||||||
|
# used by the TLS endpoint while binding it to the verified home-LAN IP.
|
||||||
|
extra_hosts:
|
||||||
|
- "homeserver.fritz.box:192.168.1.2"
|
||||||
volumes:
|
volumes:
|
||||||
- ${RUNRAID_BINARY:-/usr/local/bin/runraid}:/usr/local/bin/unraid:ro
|
- ${RUNRAID_BINARY:-/usr/local/bin/runraid}:/usr/local/bin/unraid:ro
|
||||||
entrypoint: ["/usr/local/bin/unraid"]
|
entrypoint: ["/usr/local/bin/unraid"]
|
||||||
|
|||||||
@@ -15,15 +15,18 @@ import calendar
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
SERVER_VERSION = "1.0.0"
|
SERVER_VERSION = "1.1.0"
|
||||||
REPO_ROOT = Path(os.environ.get("ATHENA_REPO_ROOT", "/knowledge/repo"))
|
REPO_ROOT = Path(os.environ.get("ATHENA_REPO_ROOT", "/knowledge/repo"))
|
||||||
DOCS_ROOT = Path(os.environ.get("ATHENA_DOCS_ROOT", "/workspace/docs"))
|
DOCS_ROOT = Path(os.environ.get("ATHENA_DOCS_ROOT", "/workspace/docs"))
|
||||||
RUNTIME_FILE = Path(os.environ.get("ATHENA_RUNTIME_FILE", "/runtime/runtime.json"))
|
RUNTIME_FILE = Path(os.environ.get("ATHENA_RUNTIME_FILE", "/runtime/runtime.json"))
|
||||||
@@ -64,6 +67,18 @@ TOOLS = [
|
|||||||
),
|
),
|
||||||
"inputSchema": {"type": "object", "properties": {}, "additionalProperties": False},
|
"inputSchema": {"type": "object", "properties": {}, "additionalProperties": False},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "athena_get_external_services",
|
||||||
|
"description": (
|
||||||
|
"USE before designing or installing an integration that may already run on Unraid "
|
||||||
|
"or elsewhere in the home network. Returns the versioned, secret-free service "
|
||||||
|
"catalog and performs only fixed bounded reachability checks for those catalogued "
|
||||||
|
"endpoints. It accepts no host, URL or port from the model and is not a scanner. "
|
||||||
|
"A failed check means unavailable or unverified; it never authorizes creating a "
|
||||||
|
"duplicate service. Use the listed specialist MCP for detailed current state."
|
||||||
|
),
|
||||||
|
"inputSchema": {"type": "object", "properties": {}, "additionalProperties": False},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "athena_search_knowledge",
|
"name": "athena_search_knowledge",
|
||||||
"description": (
|
"description": (
|
||||||
@@ -293,6 +308,63 @@ def current_state() -> dict[str, Any]:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def external_services() -> dict[str, Any]:
|
||||||
|
catalog_path = REPO_ROOT / "config/service-catalog.json"
|
||||||
|
try:
|
||||||
|
catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
|
||||||
|
except (OSError, json.JSONDecodeError) as exc:
|
||||||
|
return {
|
||||||
|
"available": False,
|
||||||
|
"error": str(exc),
|
||||||
|
"instruction": "The service inventory is unavailable. Do not infer that a replacement service is needed.",
|
||||||
|
}
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for service in catalog.get("services", [])[:24]:
|
||||||
|
item = {key: service.get(key) for key in (
|
||||||
|
"id", "name", "location", "host", "address", "port", "protocol",
|
||||||
|
"specialist_tool", "purpose",
|
||||||
|
)}
|
||||||
|
address = str(service.get("address", ""))
|
||||||
|
port = int(service.get("port", 0))
|
||||||
|
started = time.monotonic()
|
||||||
|
reachable = False
|
||||||
|
http_status = None
|
||||||
|
error = None
|
||||||
|
try:
|
||||||
|
with socket.create_connection((address, port), timeout=2):
|
||||||
|
reachable = True
|
||||||
|
if service.get("probe") == "http-head":
|
||||||
|
path = str(service.get("probe_path", "/"))
|
||||||
|
url = f"{service.get('protocol', 'http')}://{address}:{port}{path}"
|
||||||
|
request = urllib.request.Request(url, method="HEAD", headers={"User-Agent": "MikeAI-Service-Catalog/1"})
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(request, timeout=3) as response:
|
||||||
|
http_status = response.status
|
||||||
|
except urllib.error.HTTPError as exc:
|
||||||
|
http_status = exc.code
|
||||||
|
except (OSError, ValueError, urllib.error.URLError) as exc:
|
||||||
|
error = type(exc).__name__
|
||||||
|
item["check"] = {
|
||||||
|
"reachable": reachable,
|
||||||
|
"http_status": http_status,
|
||||||
|
"elapsed_ms": round((time.monotonic() - started) * 1000),
|
||||||
|
"error_class": error,
|
||||||
|
}
|
||||||
|
results.append(item)
|
||||||
|
return {
|
||||||
|
"available": True,
|
||||||
|
"source": "config/service-catalog.json",
|
||||||
|
"catalog_version": catalog.get("version"),
|
||||||
|
"updated": catalog.get("updated"),
|
||||||
|
"services": results,
|
||||||
|
"instruction": (
|
||||||
|
"Existing catalog entries are architecture constraints, not disposable suggestions. "
|
||||||
|
"If a check or specialist tool fails, report the gap and ask for direction; do not plan a duplicate backend."
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def candidate_files() -> list[Path]:
|
def candidate_files() -> list[Path]:
|
||||||
files: list[Path] = []
|
files: list[Path] = []
|
||||||
for path in REPO_ROOT.rglob("*"):
|
for path in REPO_ROOT.rglob("*"):
|
||||||
@@ -550,6 +622,8 @@ def call_tool(name: str, arguments: dict[str, Any]) -> str:
|
|||||||
result = overview()
|
result = overview()
|
||||||
elif name == "athena_get_current_state":
|
elif name == "athena_get_current_state":
|
||||||
result = current_state()
|
result = current_state()
|
||||||
|
elif name == "athena_get_external_services":
|
||||||
|
result = external_services()
|
||||||
elif name == "athena_search_knowledge":
|
elif name == "athena_search_knowledge":
|
||||||
result = search_knowledge(arguments)
|
result = search_knowledge(arguments)
|
||||||
elif name == "athena_read_source":
|
elif name == "athena_read_source":
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
title: MikeAI Auto Tool Selector
|
title: MikeAI Auto Tool Selector
|
||||||
author: MikeAI
|
author: MikeAI
|
||||||
version: 2.0.0
|
version: 2.1.0
|
||||||
description: Selects a small, relevant set of MCP servers for each user request.
|
description: Selects a small, relevant set of MCP servers for each user request.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -80,7 +80,10 @@ class Filter:
|
|||||||
"unless the user's current message explicitly requests that exact change "
|
"unless the user's current message explicitly requests that exact change "
|
||||||
"and every required preview, confirmation, backup, and validation rule "
|
"and every required preview, confirmation, backup, and validation rule "
|
||||||
"of the tool is satisfied. Do not call unrelated tools merely because "
|
"of the tool is satisfied. Do not call unrelated tools merely because "
|
||||||
"they are available. If the selected tool cannot verify the claim, say so."
|
"they are available. If the selected tool cannot verify the claim, say so. "
|
||||||
|
"A missing, disabled or failed inventory tool is not evidence that a service "
|
||||||
|
"does not exist. Never compensate by planning or installing a duplicate backend; "
|
||||||
|
"stop and request clarification."
|
||||||
)
|
)
|
||||||
messages = body.setdefault("messages", [])
|
messages = body.setdefault("messages", [])
|
||||||
for message in messages:
|
for message in messages:
|
||||||
@@ -165,9 +168,13 @@ class Filter:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# A specialist source is more precise than public web search. Platform
|
# Existing infrastructure must be inspected before a new integration
|
||||||
# wins over a generic Docker mention when Athena is explicitly named.
|
# is designed. For a GitHub-backed MCP that targets a service already
|
||||||
if operator:
|
# running on Unraid, source plus Unraid inventory are the two most
|
||||||
|
# useful bounded capabilities; the Operator follows at implementation.
|
||||||
|
if github and unraid:
|
||||||
|
selected.extend(("github", "unraid"))
|
||||||
|
elif operator:
|
||||||
selected.append("operator")
|
selected.append("operator")
|
||||||
elif platform:
|
elif platform:
|
||||||
selected.append("platform")
|
selected.append("platform")
|
||||||
|
|||||||
Reference in new issue
Block a user