Skip to main content
Glama

server_maintain

Update, restart, or perform full maintenance on Kastell servers. Supports Coolify-managed servers for updates and maintenance, cloud provider API for restarts.

Instructions

Maintain Kastell servers. Actions: 'update' runs Coolify update via SSH (Coolify servers only — bare servers are blocked), 'restart' reboots server via cloud provider API (works for both Coolify and bare servers), 'maintain' runs full 5-step maintenance (Coolify servers only — bare servers are blocked). Snapshot not included — use server_backup tool. Requires SSH access for update, provider API tokens for restart/status. Manual servers: restart not available.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYesAction: 'update' runs platform update via SSH (managed servers — Coolify or Dokploy), 'restart' reboots server via cloud provider API (both modes), 'maintain' runs full 5-step maintenance (status → update → health → reboot → final, managed servers only)
serverNoServer name or IP. Auto-selected if only one server exists.
skipRebootNoSkip reboot and final check steps (only for 'maintain' action). Useful during business hours.

Implementation Reference

  • The handleServerMaintain function acts as the central handler for the 'server_maintain' MCP tool. It switches between three actions: 'update', 'restart', and 'maintain', implementing necessary environment checks, token validation, and calling appropriate core services (updateServer, rebootAndWait, maintainServer).
    export async function handleServerMaintain(params: {
      action: "update" | "restart" | "maintain";
      server?: string;
      skipReboot?: boolean;
    }): Promise<McpResponse> {
      try {
        const servers = getServers();
        if (servers.length === 0) {
          return mcpError("No servers found", undefined, [
            { command: "kastell init", reason: "Deploy 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(", ")}`,
          );
        }
    
        switch (params.action) {
          case "update": {
            // Guard: update requires managed platform (Coolify/Dokploy)
            const modeError = requireManagedMode(server, "update");
            if (modeError) {
              return mcpError(modeError, "Use SSH to manage bare servers directly");
            }
    
            const platform = resolvePlatform(server);
            if (!platform) {
              return mcpError("No platform adapter available for this server");
            }
    
            // Use core updateServer which validates via provider API before updating
            const apiToken = server.id.startsWith("manual-") ? "" : (getProviderToken(server.provider) ?? "");
            const result = await updateServer(server, apiToken, platform);
    
            if (!result.success) {
              return {
                content: [{ type: "text", text: JSON.stringify({
                  server: server.name,
                  ip: server.ip,
                  error: result.error,
                  ...(result.hint ? { hint: result.hint } : {}),
                  suggested_actions: [
                    { command: `server_info { action: 'health', server: '${server.name}' }`, reason: "Check if server is reachable" },
                  ],
                }) }],
                isError: true,
              };
            }
    
            const displayName = result.displayName ?? "Platform";
            return mcpSuccess({
              success: true,
              server: server.name,
              ip: server.ip,
              message: `${displayName} update completed successfully`,
              suggested_actions: [
                { command: `server_info { action: 'health', server: '${server.name}' }`, reason: "Verify platform is running after update" },
                { command: `server_logs { action: 'logs', server: '${server.name}' }`, reason: "Check logs after update" },
              ],
            });
          }
    
          case "restart": {
            if (isSafeMode()) {
              return mcpError(
                "Restart is disabled in SAFE_MODE",
                "Set KASTELL_SAFE_MODE=false to enable server reboot",
                [{ command: `server_maintain { action: 'update', server: '${server.name}' }`, reason: "Run platform update instead (non-destructive)" }],
              );
            }
    
            // Restart requires API token for cloud provider — no mode guard (works on both)
            const isManual = server.id.startsWith("manual-");
            if (isManual) {
              return mcpError(
                "Cannot reboot manually added server via API",
                `Use SSH: ssh root@${server.ip} reboot`,
              );
            }
    
            const token = getProviderToken(server.provider);
            if (!token) {
              return mcpError(
                `No API token found for provider: ${server.provider}`,
                `Set environment variable: ${server.provider.toUpperCase()}_TOKEN`,
              );
            }
    
            const result = await rebootAndWait(server, token);
    
            if (!result.success) {
              return {
                content: [{ type: "text", text: JSON.stringify({
                  server: server.name,
                  ip: server.ip,
                  error: result.error,
                  ...(result.hint ? { hint: result.hint } : {}),
                  suggested_actions: [
                    { command: `server_info { action: 'status', server: '${server.name}' }`, reason: "Check current server status" },
                  ],
                }) }],
                isError: true,
              };
            }
    
            return mcpSuccess({
              success: true,
              server: server.name,
              ip: server.ip,
              message: "Server restarted successfully",
              finalStatus: result.finalStatus,
              suggested_actions: [
                { command: `server_info { action: 'health', server: '${server.name}' }`, reason: "Verify platform is accessible" },
                { command: `server_logs { action: 'logs', server: '${server.name}' }`, reason: "Check logs after restart" },
              ],
            });
          }
    
          case "maintain": {
            // Guard: maintain requires Coolify
            const modeError = requireManagedMode(server, "maintain");
            if (modeError) {
              return mcpError(modeError, "Use SSH to manage bare servers directly");
            }
    
            if (isSafeMode()) {
              return mcpError(
                "Maintenance is disabled in SAFE_MODE",
                "Set KASTELL_SAFE_MODE=false to enable full maintenance (includes reboot)",
                [{ command: `server_maintain { action: 'update', server: '${server.name}' }`, reason: "Run platform update only (no token needed)" }],
              );
            }
    
            // Maintain requires API token for non-manual servers
            const isManual = server.id.startsWith("manual-");
            let token = "";
    
            if (!isManual) {
              const envToken = getProviderToken(server.provider);
              if (!envToken) {
                return mcpError(
                  `No API token found for provider: ${server.provider}`,
                  `Set environment variable: ${server.provider.toUpperCase()}_TOKEN`,
                  [{ command: `server_maintain { action: 'update', server: '${server.name}' }`, reason: "Run update only (no token needed)" }],
                );
              }
              token = envToken;
            }
    
            const result = await maintainServer(server, token, {
              skipReboot: params.skipReboot ?? false,
            });
    
            const suggestedActions = [];
            const hasFailure = result.steps.some((s) => s.status === "failure");
    
            if (hasFailure) {
              suggestedActions.push(
                { command: `server_info { action: 'health', server: '${server.name}' }`, reason: "Check platform reachability" },
                { command: `server_logs { action: 'logs', server: '${server.name}' }`, reason: "Check logs for errors" },
              );
            } else {
              suggestedActions.push(
                { command: `server_info { action: 'status', server: '${server.name}' }`, reason: "Verify full server status" },
                { command: `server_logs { action: 'logs', server: '${server.name}' }`, reason: "Check platform startup logs" },
              );
            }
    
            return {
              content: [{ type: "text", text: JSON.stringify({
                success: result.success,
                server: result.server,
                ip: result.ip,
                provider: result.provider,
                steps: result.steps,
                summary: {
                  total: result.steps.length,
                  success: result.steps.filter((s) => s.status === "success").length,
                  failure: result.steps.filter((s) => s.status === "failure").length,
                  skipped: result.steps.filter((s) => s.status === "skipped").length,
                },
                suggested_actions: suggestedActions,
              }) }],
              ...(hasFailure ? { isError: true } : {}),
            };
          }
          default: {
            return mcpError(`Unknown action: ${params.action as string}`);
          }
        }
      } catch (error: unknown) {
        return mcpError(getErrorMessage(error));
      }
    }
  • The serverMaintainSchema object defines the input parameters for the 'server_maintain' tool using Zod, covering actions ('update', 'restart', 'maintain'), optional server selection, and a 'skipReboot' flag.
    export const serverMaintainSchema = {
      action: z.enum(["update", "restart", "maintain"]).describe(
        "Action: 'update' runs platform update via SSH (managed servers — Coolify or Dokploy), 'restart' reboots server via cloud provider API (both modes), 'maintain' runs full 5-step maintenance (status → update → health → reboot → final, managed servers only)",
      ),
      server: z.string().optional().describe(
        "Server name or IP. Auto-selected if only one server exists.",
      ),
      skipReboot: z.boolean().default(false).describe(
        "Skip reboot and final check steps (only for 'maintain' action). Useful during business hours.",
      ),
    };
  • Registration of the 'server_maintain' tool in the MCP server instance, mapping it to the serverMaintainSchema and invoking the handleServerMaintain function.
    server.registerTool("server_maintain", {
      description:
        "Maintain Kastell servers. Actions: 'update' runs Coolify update via SSH (Coolify servers only — bare servers are blocked), 'restart' reboots server via cloud provider API (works for both Coolify and bare servers), 'maintain' runs full 5-step maintenance (Coolify servers only — bare servers are blocked). Snapshot not included — use server_backup tool. Requires SSH access for update, provider API tokens for restart/status. Manual servers: restart not available.",
      inputSchema: serverMaintainSchema,
      annotations: {
        title: "Server Maintenance",
        readOnlyHint: false,
        destructiveHint: false,
        idempotentHint: true,
        openWorldHint: true,
      },
    }, async (params) => {
      return handleServerMaintain(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