Skip to main content
Glama

server_doctor

Read-onlyIdempotent

Analyze server health proactively to detect disk capacity, swap usage, stale packages, security alerts, and reclaimable space. Provides severity-based findings with remediation commands.

Instructions

Run proactive health analysis on a server. Detects disk trending full, high swap, stale packages, elevated fail2ban bans, audit regression streaks, old backups, and reclaimable Docker space. Uses cached metrics by default — pass fresh=true to fetch live data via SSH. Returns findings grouped by severity (critical/warning/info) with remediation commands. For a full scored security audit across 27 categories, use server_audit instead.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
serverNoServer name or IP. Auto-selected if only one server exists.
freshNoFetch live data via SSH instead of using cached metrics. Default: false (reads cache only).
formatNoOutput format: summary (grouped findings with counts), json (full DoctorResult).summary

Implementation Reference

  • The main handler function for the `server_doctor` tool, which orchestrates the call to `runServerDoctor` and formats the output.
    export async function handleServerDoctor(params: {
      server?: string;
      fresh?: boolean;
      format?: "summary" | "json";
    }): 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 server = resolveServerForMcp(params, servers);
        if (!server) {
          if (params.server) {
            return mcpError(
              `Server not found: ${params.server}`,
              `Available servers: ${servers.map((s) => s.name).join(", ")}`,
            );
          }
          return mcpError(
            "Multiple servers found. Specify which server to use.",
            `Available: ${servers.map((s) => s.name).join(", ")}`,
          );
        }
    
        const fresh = params.fresh ?? false;
        const result = await runServerDoctor(server.ip, server.name, { fresh });
    
        if (!result.success || !result.data) {
          return mcpError(result.error ?? "Doctor analysis failed", result.hint);
        }
    
        const doctorResult = result.data;
        const format = params.format ?? "summary";
    
        if (format === "json") {
          return {
            content: [{ type: "text", text: JSON.stringify(doctorResult) }],
          };
        }
    
        // summary format: group findings by severity
        const bySeverity = {
          critical: doctorResult.findings.filter((f) => f.severity === "critical"),
          warning: doctorResult.findings.filter((f) => f.severity === "warning"),
          info: doctorResult.findings.filter((f) => f.severity === "info"),
        };
    
        const findingLines: string[] = [];
        for (const [severity, findings] of Object.entries(bySeverity)) {
          for (const f of findings) {
            findingLines.push(`  [${severity.toUpperCase()}] ${f.description} (fix: ${f.command})`);
          }
        }
    
        return mcpSuccess({
          server: doctorResult.serverName,
          total: doctorResult.findings.length,
          critical: bySeverity.critical.length,
          warning: bySeverity.warning.length,
          info: bySeverity.info.length,
          ranAt: doctorResult.ranAt,
          usedFreshData: doctorResult.usedFreshData,
          findings: findingLines,
        });
      } catch (error: unknown) {
        return mcpError(getErrorMessage(error));
      }
    }
  • Zod schema definition for the `server_doctor` tool inputs.
    export const serverDoctorSchema = {
      server: z.string().optional().describe("Server name or IP. Auto-selected if only one server exists."),
      fresh: z.boolean().default(false).describe("Fetch live data via SSH instead of using cached metrics. Default: false (reads cache only)."),
      format: z.enum(["summary", "json"]).default("summary").describe("Output format: summary (grouped findings with counts), json (full DoctorResult)."),
    };
  • Registration of the `server_doctor` tool with the MCP server instance.
    server.registerTool("server_doctor", {
      description:
        "Run proactive health analysis on a server. Detects disk trending full, high swap, stale packages, elevated fail2ban bans, audit regression streaks, old backups, and reclaimable Docker space. Uses cached metrics by default — pass fresh=true to fetch live data via SSH. Returns findings grouped by severity (critical/warning/info) with remediation commands. For a full scored security audit across 27 categories, use server_audit instead.",
      inputSchema: serverDoctorSchema,
      annotations: {
        title: "Server Doctor",
        readOnlyHint: true,
        destructiveHint: false,
        idempotentHint: true,
        openWorldHint: true,
      },
    }, async (params) => {
      return handleServerDoctor(params);
    });
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

While annotations declare readOnlyHint and idempotentHint, the description adds crucial behavioral context: it discloses the caching strategy (cached by default, live via SSH), specifies the return structure ('findings grouped by severity (critical/warning/info) with remediation commands'), and lists the specific subsystems examined that the schema doesn't cover.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four tightly constructed sentences with zero redundancy: sentence 1 defines purpose + scope, sentence 2 explains data source behavior, sentence 3 describes output format, and sentence 4 provides sibling differentiation. Every clause earns its place with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite lacking an output schema, the description compensates by detailing the return value structure (severity groups, remediation commands). It comprehensively covers the tool's 7 detection domains, caching implications, and distinguishes from 12 sibling tools by explicitly naming the closest alternative.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 100% schema description coverage, the schema already fully documents all three parameters (server, fresh, format). The description references the fresh parameter behavior ('pass fresh=true') but does not add substantial semantic meaning beyond what the schema already provides, meeting the baseline expectation for high-coverage schemas.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description explicitly states the tool 'Run[s] proactive health analysis on a server' followed by a comprehensive list of 7 specific detection areas (disk trending full, high swap, stale packages, etc.). It clearly distinguishes from the sibling 'server_audit' tool by contrasting this health check with a 'full scored security audit across 27 categories'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit alternative selection guidance: 'For a full scored security audit across 27 categories, use server_audit instead.' Also clearly explains when to override defaults: 'Uses cached metrics by default — pass fresh=true to fetch live data via SSH,' guiding the agent on latency vs. accuracy trade-offs.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kastelldev/kastell'

If you have feedback or need assistance with the MCP directory API, please join our Discord server