0.24.0 — MCP tool improvements: type filter for get_relations, health filter for get_health_summary, live HTTP probe for ping_service
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled

get_relations now accepts an optional 'types' (comma-separated) parameter
to filter relationship types — filters out the noisy exec/targets edges
that previously drowned useful host/provides edges.

get_health_summary now accepts an optional 'health' (comma-separated)
parameter to return only entities in specific health states (e.g.
'health=down,stale') instead of the full 100+ entity list.

ping_service now:
- Falls back to e.attributes->>'public_host' when 'url' is not set
  (covers LXCs that only have public_host in the graph)
- Performs a live HTTP HEAD probe against the resolved URL, returning
  the actual status code instead of just the scheduler's stale health
  state

Also: fixed matrix.hubris.network DNS record (was pointing to dead VPS),
pruned 6 dead graph edges, wired url attributes on 7 LXCs, added VPS
HTTP monitoring check, and resolved the 18k-occurrence unmonitored signal.

This session's audit is documented as
document:nomos/2026-08-05-dns-monitoring-improvements-for-strong-hosted-services.
This commit is contained in:
2026-08-05 15:12:04 +02:00
parent 86fa57b5cd
commit a126cfa710
2 changed files with 51 additions and 15 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
@@ -63,18 +64,30 @@ func allTools(pool *db.Pool, agentID uuid.UUID) []toolReg {
nStr(args["type"]), nStr(args["state"]), nStr(args["q"]), limit), "entity_table"), nil
}},
{tool: &mcp.Tool{Name: "get_relations", Description: "List inbound/outbound edges for one entity",
InputSchema: objSchema(prop{"entity_id", "string", "Entity slug"}),
{tool: &mcp.Tool{Name: "get_relations", Description: "List inbound/outbound edges for one entity, optionally filtered by relationship type",
InputSchema: objSchema(
prop{"entity_id", "string", "Entity slug"},
prop{"types", "string", "Comma-separated relationship types to include (e.g. 'hosts,provides,depends-on'). Omit for all."},
),
}, handler: func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
args := argsMap(req)
slug, _ := args["entity_id"].(string)
return queryRows(ctx, pool, `
typesStr, _ := args["types"].(string)
if slug == "" {
return textResult("entity_id is required"), nil
}
query := `
SELECT r.type, src.slug AS source, tgt.slug AS target
FROM relationships r
JOIN entities src ON src.id = r.source_id
JOIN entities tgt ON tgt.id = r.target_id
WHERE (src.slug = $1 OR tgt.slug = $1) AND r.valid_to IS NULL
ORDER BY r.type`, slug), nil
WHERE (src.slug = $1 OR tgt.slug = $1) AND r.valid_to IS NULL`
if typesStr != "" {
query += ` AND r.type = ANY(string_to_array($2, ','))`
return queryRows(ctx, pool, query, slug, typesStr), nil
}
query += ` ORDER BY r.type`
return queryRows(ctx, pool, query, slug), nil
}},
{tool: &mcp.Tool{Name: "get_blast_radius", Description: "Find entities affected if this entity goes down",
@@ -90,14 +103,23 @@ func allTools(pool *db.Pool, agentID uuid.UUID) []toolReg {
slug, depth), nil
}},
{tool: &mcp.Tool{Name: "get_health_summary", Description: "Full fleet health per entity — healthy/degraded/down/unknown",
InputSchema: objSchema(),
{tool: &mcp.Tool{Name: "get_health_summary", Description: "Fleet health per entity — optionally filter by health state(s)",
InputSchema: objSchema(
prop{"health", "string", "Comma-separated health states to include (e.g. 'down,stale'). Omit for all."},
),
}, handler: func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return queryRows(ctx, pool, `
args := argsMap(req)
healthStr, _ := args["health"].(string)
query := `
SELECT e.slug, e.type, st.health, st.last_check_at
FROM entity_status st JOIN entities e ON e.id = st.entity_id
WHERE e.type <> 'check'
ORDER BY e.slug`), nil
WHERE e.type <> 'check'`
if healthStr != "" {
query += ` AND st.health = ANY(string_to_array($1, ','))`
return queryRows(ctx, pool, query, healthStr), nil
}
query += ` ORDER BY e.slug`
return queryRows(ctx, pool, query), nil
}},
{tool: &mcp.Tool{Name: "get_audit_trail", Description: "Query the audit log",
@@ -726,7 +748,7 @@ func allTools(pool *db.Pool, agentID uuid.UUID) []toolReg {
(e.attributes->>'pve_id')::int`, statePtr), "lxc_list"), nil
}},
{tool: &mcp.Tool{Name: "ping_service", Description: "Check if a service is reachable via HTTP",
{tool: &mcp.Tool{Name: "ping_service", Description: "Check if a service is reachable via HTTP — returns scheduler health state plus a live HTTP probe",
InputSchema: objSchema(prop{"service_slug", "string", "Service entity slug"}),
}, handler: func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
args := argsMap(req)
@@ -735,7 +757,13 @@ func allTools(pool *db.Pool, agentID uuid.UUID) []toolReg {
return textResult("service_slug is required"), nil
}
rows, err := pool.Query(ctx, `
SELECT st.health, st.last_check_at, e.attributes->>'url' AS url
SELECT st.health, st.last_check_at,
COALESCE(
e.attributes->>'url',
CASE WHEN e.attributes->>'public_host' IS NOT NULL
THEN 'https://' || e.attributes->>'public_host'
END
) AS url
FROM entity_status st
JOIN entities e ON e.id = st.entity_id
WHERE e.slug = $1`, slug)
@@ -749,9 +777,17 @@ func allTools(pool *db.Pool, agentID uuid.UUID) []toolReg {
var health, lastCheck, url string
rows.Scan(&health, &lastCheck, &url)
if url == "" {
url = "(no URL in entity attributes)"
return textResult(fmt.Sprintf("health=%s last_check=%s url=no-url (entity has no url or public_host attribute)", health, lastCheck)), nil
}
return textResult(fmt.Sprintf("health=%s last_check=%s url=%s", health, lastCheck, url)), nil
// Live HTTP probe — HEAD request to check current state
code := "n/a"
if resp, err := http.Head(url); err == nil {
resp.Body.Close()
code = fmt.Sprintf("%d", resp.StatusCode)
} else {
code = fmt.Sprintf("err: %v", err)
}
return textResult(fmt.Sprintf("health=%s last_check=%s url=%s http=%s", health, lastCheck, url, code)), nil
}},
{tool: &mcp.Tool{Name: "tail_log", Description: "Get recent log lines from a service via journalctl",