+73
@@ -1111,6 +1111,32 @@ def validate_servers(servers: list[ServerEntry]) -> list[str]:
|
||||
return problems
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Search / filter
|
||||
# --------------------------------------------------------------------------- #
|
||||
def server_matches_filter(entry: ServerEntry, query: str) -> bool:
|
||||
"""
|
||||
Case-insensitive substring match against a server's name, and its
|
||||
command (stdio) or url (remote). An empty/whitespace-only query matches
|
||||
everything -- that's what lets the search box double as "no filter".
|
||||
"""
|
||||
q = (query or "").strip().lower()
|
||||
if not q:
|
||||
return True
|
||||
if q in entry.name.lower():
|
||||
return True
|
||||
if entry.kind == "remote":
|
||||
haystack = str(entry.data.get("url", ""))
|
||||
else:
|
||||
haystack = str(entry.data.get("command", ""))
|
||||
return q in haystack.lower()
|
||||
|
||||
|
||||
def filter_servers(entries: list[ServerEntry], query: str) -> list[ServerEntry]:
|
||||
"""Return only the entries that match `query` (see server_matches_filter)."""
|
||||
return [e for e in entries if server_matches_filter(e, query)]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Dependency / PATH checking
|
||||
# --------------------------------------------------------------------------- #
|
||||
@@ -1580,6 +1606,53 @@ def spawn_test(data: dict, timeout: float = 3.0) -> dict:
|
||||
}
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Health status (maps a spawn_test() result to a simple tri-state for the
|
||||
# server-list UI's per-row status dot; see "Test all" in bcc.py)
|
||||
# --------------------------------------------------------------------------- #
|
||||
class HealthStatus:
|
||||
"""
|
||||
Tri-state health for the server-list status dot. A plain class of string
|
||||
constants -- not an Enum -- to match the plain-string status values used
|
||||
elsewhere in this module (see check_dependency's 'status').
|
||||
"""
|
||||
|
||||
UNTESTED = "untested"
|
||||
OK = "ok"
|
||||
FAILED = "failed"
|
||||
|
||||
|
||||
def health_from_spawn_result(result: dict) -> tuple[str, str]:
|
||||
"""
|
||||
Map a spawn_test() result dict to (HealthStatus, short_summary) for the
|
||||
server-list status column. Reuses spawn_test's own outcome classification
|
||||
rather than re-deriving pass/fail from returncode/stderr:
|
||||
|
||||
outcome "ok" -> OK (server started and kept running)
|
||||
outcome "not_applicable" -> UNTESTED (remote server, or no command set)
|
||||
anything else -> FAILED (exited, crashed, or not found)
|
||||
|
||||
The summary is short enough for a table cell/tooltip; when the process
|
||||
wrote to stderr before dying, its first line is appended for context.
|
||||
"""
|
||||
outcome = result.get("outcome", "")
|
||||
detail = result.get("detail", "") or ""
|
||||
stderr = (result.get("stderr") or "").strip()
|
||||
|
||||
if outcome == "ok":
|
||||
return HealthStatus.OK, detail or "started"
|
||||
if outcome == "not_applicable":
|
||||
return HealthStatus.UNTESTED, detail or "not applicable"
|
||||
|
||||
# exited / crashed / not_found: the server didn't come up cleanly.
|
||||
summary = detail or outcome
|
||||
if stderr:
|
||||
first_line = stderr.splitlines()[0].strip()
|
||||
if first_line:
|
||||
summary = f"{summary} — {first_line}"
|
||||
return HealthStatus.FAILED, summary
|
||||
|
||||
|
||||
def test_remote(url: str, timeout: float = 5.0) -> tuple[bool, str]:
|
||||
"""
|
||||
Reachability check for a url-based MCP server. ANY HTTP response (even 4xx/5xx)
|
||||
|
||||
Reference in New Issue
Block a user