Skip to main content
Glama

server_info

Retrieve and monitor Kastell-managed server information including status, health checks, available types, and pricing across supported cloud providers.

Instructions

Get information about Kastell-managed servers. Actions: 'list' all servers, 'status' check cloud provider + Coolify/bare status, 'health' check Coolify reachability or SSH access for bare servers, 'sizes' list available server types with prices for a provider+region. Requires provider API tokens as environment variables (HETZNER_TOKEN, DIGITALOCEAN_TOKEN, VULTR_TOKEN, LINODE_TOKEN) for status/sizes checks. Avoid calling repeatedly in short intervals to prevent provider API rate limiting. For fleet-wide health and audit scores across all servers, use server_fleet instead.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYesAction to perform: 'list' all servers, 'status' check server/cloud status, 'health' check Coolify reachability (or SSH reachability for bare servers), 'sizes' list available server types with prices for a provider+region
serverNoServer name or IP. Required for single-server status/health. Omit for all servers.
providerNoCloud provider (required for 'sizes' action)
regionNoRegion/location ID (required for 'sizes' action, e.g. 'nbg1' for Hetzner, 'fra1' for DigitalOcean)
modeNoServer mode filter for 'sizes' action. Coolify requires min 2GB RAM. Default: coolify

Implementation Reference

  • The handleServerInfo function processes actions (list, status, health, sizes) for server info management and interacts with various utilities to fetch server details or health status.
    export async function handleServerInfo(params: {
      action: "list" | "status" | "health" | "sizes";
      server?: string;
      provider?: SupportedProvider;
      region?: string;
      mode?: ServerMode;
    }): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
      try {
        switch (params.action) {
          case "list": {
            const servers = getServers();
            return mcpSuccess(formatServerList(servers));
          }
    
          case "status": {
            const servers = getServers();
            if (servers.length === 0) {
              return mcpError(
                "No servers found",
                undefined,
                [{ command: "kastell init", reason: "Deploy a server first" }],
              );
            }
    
            if (params.server) {
              const server = findServer(params.server);
              if (!server) {
                return {
                  content: [{ type: "text", text: JSON.stringify({
                    error: `Server not found: ${params.server}`,
                    available_servers: servers.map((s) => s.name),
                  }) }],
                  isError: true,
                };
              }
    
              const token = server.id.startsWith("manual-") ? "" : (getProviderToken(server.provider) ?? "");
              if (!token && !server.id.startsWith("manual-")) {
                return {
                  content: [{ type: "text", text: JSON.stringify({
                    error: `No API token found for provider: ${server.provider}`,
                    hint: `Set environment variable: ${server.provider.toUpperCase()}_TOKEN`,
                    server: { name: server.name, ip: server.ip, provider: server.provider },
                  }) }],
                  isError: true,
                };
              }
    
              const result = await checkServerStatus(server, token);
              return mcpSuccess(formatStatusResults([result]));
            }
    
            // All servers
            const tokenMap = collectProviderTokensFromEnv(servers);
            const missingTokenProviders = [
              ...new Set(servers.filter((s) => !s.id.startsWith("manual-")).map((s) => s.provider)),
            ].filter((p) => !tokenMap.has(p));
    
            if (missingTokenProviders.length > 0) {
              return {
                content: [{ type: "text", text: JSON.stringify({
                  error: "Missing API tokens for providers",
                  missing: missingTokenProviders.map((p) => ({
                    provider: p,
                    envVar: `${p.toUpperCase()}_TOKEN`,
                  })),
                  hint: "Set environment variables for each provider",
                }) }],
                isError: true,
              };
            }
    
            const results = await checkAllServersStatus(servers, tokenMap);
            return mcpSuccess(formatStatusResults(results));
          }
    
          case "health": {
            const servers = getServers();
            if (servers.length === 0) {
              return mcpError(
                "No servers found",
                undefined,
                [{ command: "kastell init", reason: "Deploy a server first" }],
              );
            }
    
            if (params.server) {
              const server = findServer(params.server);
              if (!server) {
                return {
                  content: [{ type: "text", text: JSON.stringify({
                    error: `Server not found: ${params.server}`,
                    available_servers: servers.map((s) => s.name),
                  }) }],
                  isError: true,
                };
              }
    
              // Bare server: check SSH reachability
              if (isBareServer(server)) {
                const sshResult = await checkBareServerSsh(server);
                const suggestedActions: SuggestedAction[] = [];
    
                if (sshResult.hostKeyMismatch) {
                  suggestedActions.push({
                    command: `ssh-keygen -R ${server.ip}`,
                    reason: "Remove stale host key to fix SSH connection",
                  });
                } else if (sshResult.reachable) {
                  suggestedActions.push({ command: `ssh root@${server.ip}`, reason: "Connect to your bare server" });
                } else {
                  suggestedActions.push({ command: `kastell status ${server.name}`, reason: "Check server cloud status" });
                }
    
                return mcpSuccess({
                  server: server.name,
                  ip: server.ip,
                  mode: "bare",
                  sshReachable: sshResult.reachable,
                  ...(sshResult.hostKeyMismatch ? { hostKeyMismatch: true } : {}),
                  suggested_actions: suggestedActions,
                });
              }
    
              // Platform server: use adapter health check
              const platform = resolvePlatform(server);
              if (!platform) {
                return mcpSuccess({ server: server.name, ip: server.ip, platformStatus: "unknown" });
              }
              const adapter = getAdapter(platform);
              const healthResult = await adapter.healthCheck(server.ip, server.domain);
              const port = platform === "dokploy" ? DOKPLOY_PORT : COOLIFY_PORT;
              const suggestedActions: SuggestedAction[] = healthResult.status === "not reachable"
                ? [{ command: `kastell status ${server.name} --autostart`, reason: `Try auto-restart ${platform}` }]
                : [{ command: `http://${server.ip}:${port}`, reason: `Access ${platform} dashboard` }];
    
              return mcpSuccess({
                server: server.name,
                ip: server.ip,
                platformStatus: healthResult.status,
                [`${platform}Url`]: healthResult.status === "running" ? `http://${server.ip}:${port}` : null,
                suggested_actions: suggestedActions,
              });
            }
    
            // All servers health — route based on mode
            const healthResults = await Promise.all(
              servers.map(async (s) => {
                if (isBareServer(s)) {
                  const sshResult = await checkBareServerSsh(s);
                  return {
                    name: s.name,
                    ip: s.ip,
                    mode: "bare" as const,
                    sshReachable: sshResult.reachable,
                    ...(sshResult.hostKeyMismatch ? { hostKeyMismatch: true } : {}),
                  };
                }
                const plat = resolvePlatform(s);
                const platStatus = plat
                  ? (await getAdapter(plat).healthCheck(s.ip, s.domain)).status
                  : "unknown";
                return {
                  name: s.name,
                  ip: s.ip,
                  mode: plat ?? s.mode ?? "coolify",
                  platform: plat ?? "coolify",
                  platformStatus: platStatus,
                };
              }),
            );
    
            const coolifyResults = healthResults.filter((r) => r.mode === "coolify");
            const bareResults = healthResults.filter((r) => r.mode === "bare");
            const notReachableCoolify = coolifyResults.filter(
              (r) => "platformStatus" in r && r.platformStatus === "not reachable",
            );
    
            const suggestedActions: SuggestedAction[] = notReachableCoolify.length > 0
              ? notReachableCoolify.map((r) => ({
                  command: `kastell status ${r.name} --autostart`,
                  reason: `Coolify not reachable on ${r.name}`,
                }))
              : [{ command: "server_info { action: 'status' }", reason: "All healthy, check full status" }];
    
            return mcpSuccess({
              results: healthResults,
              summary: {
                total: healthResults.length,
                running: coolifyResults.filter(
                  (r) => "platformStatus" in r && r.platformStatus === "running",
                ).length,
                notReachable: notReachableCoolify.length,
                bare: bareResults.length,
              },
              suggested_actions: suggestedActions,
            });
          }
    
          case "sizes": {
            if (!params.provider) {
              return mcpError(
                "Provider is required for 'sizes' action",
                "Specify provider: 'hetzner', 'digitalocean', 'vultr', or 'linode'",
              );
            }
            if (!params.region) {
              return mcpError(
                "Region is required for 'sizes' action",
                "Specify region (e.g. 'nbg1' for Hetzner, 'fra1' for DigitalOcean, 'ewr' for Vultr, 'us-east' for Linode)",
              );
            }
    
            const token = getProviderToken(params.provider);
            if (!token) {
              return mcpError(
                `No API token found for provider: ${params.provider}`,
                `Set environment variable: ${params.provider.toUpperCase()}_TOKEN`,
              );
            }
    
            const provider = createProviderWithToken(params.provider, token);
            const mode: ServerMode = params.mode ?? "coolify";
            const sizes = await provider.getAvailableServerTypes(params.region, mode);
    
            return mcpSuccess({
              provider: params.provider,
              region: params.region,
              mode,
              sizes: sizes.map((s) => ({
                id: s.id,
                name: s.name,
                vcpu: s.vcpu,
                ram: `${s.ram}GB`,
                disk: `${s.disk}GB`,
                price: s.price,
              })),
              total: sizes.length,
              suggested_actions: [
                {
                  command: `server_provision { provider: '${params.provider}', name: 'my-server', region: '${params.region}', size: '${sizes[0]?.id ?? "..."}', mode: '${mode}' }`,
                  reason: "Provision a server with one of these sizes",
                },
              ],
            });
          }
    
          default:
            return mcpError(
              `Invalid action: ${String(params.action)}`,
              "Valid actions: list, status, health, sizes",
            );
        }
      } catch (error: unknown) {
        return mcpError(getErrorMessage(error));
      }
    }
  • The serverInfoSchema defines the input parameters for the server_info tool, including action, server name, provider, region, and server mode.
    export const serverInfoSchema = {
      action: z.enum(["list", "status", "health", "sizes"]).describe(
        "Action to perform: 'list' all servers, 'status' check server/cloud status, 'health' check Coolify reachability (or SSH reachability for bare servers), 'sizes' list available server types with prices for a provider+region",
      ),
      server: z.string().optional().describe(
        "Server name or IP. Required for single-server status/health. Omit for all servers.",
      ),
      provider: z.enum(SUPPORTED_PROVIDERS).optional().describe(
        "Cloud provider (required for 'sizes' action)",
      ),
      region: z.string().optional().describe(
        "Region/location ID (required for 'sizes' action, e.g. 'nbg1' for Hetzner, 'fra1' for DigitalOcean)",
      ),
      mode: z.enum(["coolify", "bare"]).optional().describe(
        "Server mode filter for 'sizes' action. Coolify requires min 2GB RAM. Default: coolify",
      ),
    };
  • The server_info tool is registered here in the MCP server, binding it to the handleServerInfo function and the serverInfoSchema.
    server.registerTool("server_info", {
      description:
        "Get information about Kastell-managed servers. Actions: 'list' all servers, 'status' check cloud provider + Coolify/bare status, 'health' check Coolify reachability or SSH access for bare servers, 'sizes' list available server types with prices for a provider+region. Requires provider API tokens as environment variables (HETZNER_TOKEN, DIGITALOCEAN_TOKEN, VULTR_TOKEN, LINODE_TOKEN) for status/sizes checks. Avoid calling repeatedly in short intervals to prevent provider API rate limiting. For fleet-wide health and audit scores across all servers, use server_fleet instead.",
      inputSchema: serverInfoSchema,
      annotations: {
        title: "Server Information",
        readOnlyHint: true,
        destructiveHint: false,
        idempotentHint: true,
        openWorldHint: true,
      },
    }, async (params) => {
      return handleServerInfo(params);

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