server_fleet
Get fleet-wide health and security posture for all registered servers. Returns status, audit score, SSH response time. Sort by score, name, or provider.
Instructions
Get fleet-wide health and security posture for all registered servers. Returns server name, IP, provider, health status (ONLINE/DEGRADED/OFFLINE), cached audit score, and SSH response time. Use sort parameter to order results. For per-server cloud status or available server sizes, use server_info instead.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| sort | No | Sort field: score (descending), name (A-Z), provider (A-Z). Default: name. | name |
| categories | No | Include weakest audit category per server. Default: false. |
Implementation Reference
- src/mcp/tools/serverFleet.ts:20-42 (handler)The handler function for the server_fleet tool. Calls runFleet() from core logic, returns JSON success with server rows.
export async function handleServerFleet(params: { sort?: "score" | "name" | "provider"; categories?: boolean; }): Promise<McpResponse> { try { const servers = getServers(); if (servers.length === 0) { return mcpError("No servers found", undefined, [ { command: "kastell add", reason: "Add a server first" }, ]); } const rows = await runFleet({ json: true, sort: params.sort ?? "name", categories: params.categories, }); return mcpSuccess({ servers: rows.length, rows }); } catch (error: unknown) { return mcpError(sanitizeStderr(getErrorMessage(error))); } } - src/mcp/server.ts:241-254 (registration)Registration of the server_fleet tool on the MCP server with description, inputSchema, and handler binding.
server.registerTool("server_fleet", { description: "Get fleet-wide health and security posture for all registered servers. Returns server name, IP, provider, health status (ONLINE/DEGRADED/OFFLINE), cached audit score, and SSH response time. Use sort parameter to order results. For per-server cloud status or available server sizes, use server_info instead.", inputSchema: serverFleetSchema, annotations: { title: "Fleet Visibility", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true, }, }, async (params) => { return handleServerFleet(params); }); - src/mcp/tools/serverFleet.ts:7-18 (schema)Zod-based input schema for server_fleet: optional sort (score/name/provider, default name) and categories boolean.
export const serverFleetSchema = { sort: z .enum(["score", "name", "provider"]) .optional() .default("name") .describe("Sort field: score (descending), name (A-Z), provider (A-Z). Default: name."), categories: z .boolean() .optional() .default(false) .describe("Include weakest audit category per server. Default: false."), }; - src/core/fleet.ts:79-206 (helper)Core runFleet function that probes all servers in parallel (p-limit 5), checks health, gets audit scores, and optionally fetches weakest categories.
export async function runFleet(options: FleetOptions): Promise<FleetRow[]> { const servers = getServers(); if (servers.length === 0) { console.log("No servers found. Deploy one with: kastell init"); return []; } const spinner = createSpinner(`Probing ${servers.length} server(s)...`); spinner.start(); const limit = pLimit(5); const tasks = servers.map((server: ServerRecord) => limit(async () => { const health = await checkServerHealth(server); const auditScore = getLatestAuditScore(server.ip); const weakest = options.categories ? await getWeakestCategory(server.ip) : undefined; return { health, auditScore, weakest }; }), ); const results = await Promise.allSettled(tasks); spinner.stop(); const rows: FleetRow[] = results.map((result, i) => { const server = servers[i]; if (result.status === "rejected") { return { name: server.name, ip: server.ip, provider: server.provider, status: "OFFLINE", auditScore: null, responseTime: null, errorReason: String(result.reason), } satisfies FleetRow; } const { health, auditScore, weakest } = result.value; let status: FleetRow["status"]; if (health.status === "healthy") { status = "ONLINE"; } else if (health.status === "unhealthy") { status = "DEGRADED"; } else { status = "OFFLINE"; } return { name: server.name, ip: server.ip, provider: server.provider, status, auditScore, responseTime: health.responseTime, errorReason: null, ...(weakest ? { weakestCategory: weakest.name, weakestCategoryScore: weakest.score } : {}), } satisfies FleetRow; }); const sorted = sortRows(rows, options.sort ?? "name"); if (options.json) { console.log(JSON.stringify(sorted, null, 2)); return sorted; } renderTable(sorted); const online = sorted.filter((r) => r.status === "ONLINE").length; const degraded = sorted.filter((r) => r.status === "DEGRADED").length; const offline = sorted.filter((r) => r.status === "OFFLINE").length; const parts: string[] = []; if (online > 0) parts.push(chalk.green(`${online} online`)); if (degraded > 0) parts.push(chalk.yellow(`${degraded} degraded`)); if (offline > 0) parts.push(chalk.red(`${offline} offline`)); console.log(parts.join(", ")); return sorted; } function renderTable(rows: FleetRow[]): void { const hasCategories = rows.some((r) => r.weakestCategory); let header = `${"Name".padEnd(20)} ${"IP".padEnd(16)} ${"Provider".padEnd(14)} ${"Status".padEnd(12)} ${"Score".padEnd(8)} ${"Response".padEnd(10)}`; if (hasCategories) header += ` ${"Weakest Category".padEnd(25)}`; console.log(header); console.log("─".repeat(header.length)); const scores = rows.map((r) => r.auditScore).filter((s): s is number => s !== null); const avg = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : null; for (const row of rows) { const statusColored = colorStatus(row.status); const scoreStr = row.auditScore !== null ? String(row.auditScore) : "--"; const response = row.responseTime !== null ? `${row.responseTime}ms` : "--"; let line = `${row.name.padEnd(20)} ${row.ip.padEnd(16)} ${row.provider.padEnd(14)} ${statusColored.padEnd(12)} ${scoreStr.padEnd(8)} ${response.padEnd(10)}`; if (hasCategories) { const catStr = row.weakestCategory ? `${row.weakestCategory} (${row.weakestCategoryScore})` : "--"; line += ` ${catStr.padEnd(25)}`; } console.log(line); } if (avg !== null) { console.log(); console.log(chalk.dim(`Fleet average score: ${avg}`)); } console.log(); } function colorStatus(status: FleetRow["status"]): string { if (status === "ONLINE") return chalk.green(status); if (status === "DEGRADED") return chalk.yellow(status); return chalk.red(status); } - src/types/index.ts:187-203 (helper)Type definitions for FleetRow (server name, IP, provider, status, auditScore, responseTime, weakestCategory) and FleetOptions (json, sort, categories).
export interface FleetRow { name: string; ip: string; provider: string; status: "ONLINE" | "DEGRADED" | "OFFLINE"; auditScore: number | null; responseTime: number | null; errorReason: string | null; weakestCategory?: string; weakestCategoryScore?: number; } export interface FleetOptions { json?: boolean; sort?: string; categories?: boolean; }