Skip to main content
Glama

server_secure

Harden Kastell server security by applying SSH hardening, configuring firewalls, managing domains, and running security audits through specific actions.

Instructions

Secure Kastell servers. Secure: 'secure-setup' applies SSH hardening + fail2ban, 'secure-audit' runs security audit with score. Firewall: 'firewall-setup' installs UFW with Coolify ports, 'firewall-add'/'firewall-remove' manage port rules, 'firewall-status' shows current rules. Domain: 'domain-set'/'domain-remove' manage custom domain with optional SSL, 'domain-check' verifies DNS, 'domain-info' shows current FQDN. All require SSH access to server. For full one-shot hardening (SSH + fail2ban + UFW + sysctl + unattended-upgrades), use server_lock instead.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYesAction: Secure: 'secure-setup' hardens SSH + installs fail2ban, 'secure-audit' runs security audit with score. Firewall: 'firewall-setup' installs UFW, 'firewall-add'/'firewall-remove' manage port rules, 'firewall-status' shows rules. Domain: 'domain-set'/'domain-remove' manage FQDN, 'domain-check' verifies DNS, 'domain-info' shows current FQDN.
serverNoServer name or IP. Auto-selected if only one server exists.
portNoPort number. Required for firewall-add/remove. Optional SSH port for secure-setup.
protocolNoProtocol for firewall rules. Default: tcp.tcp
domainNoDomain name. Required for domain-set and domain-check.
sslNoEnable SSL (https) for domain. Default: true.

Implementation Reference

  • Main handler for 'server_secure' tool that dispatches actions to specific handlers.
    export async function handleServerSecure(params: {
      action: Action;
      server?: string;
      port?: number;
      protocol?: "tcp" | "udp";
      domain?: string;
      ssl?: boolean;
    }, mcpServer?: McpServer): 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(", ")}`,
          );
        }
    
        const domainActions = ["domain-set", "domain-remove", "domain-check", "domain-info"];
        if (domainActions.includes(params.action)) {
          const modeError = requireManagedMode(server, params.action);
          if (modeError) {
            return mcpError(modeError, "Domain management requires a managed platform (Coolify or Dokploy). Use SSH for bare server DNS configuration.");
          }
        }
    
        await mcpLog(mcpServer, `Applying ${params.action} on ${server.name}`);
    
        switch (params.action) {
          case "secure-setup":   return handleSecureSetup(server, params.port);
          case "secure-audit":   return handleSecureAudit(server);
          case "firewall-setup": return handleFirewallSetup(server);
          case "firewall-add":   return handleFirewallAdd(server, params.port, params.protocol || "tcp");
          case "firewall-remove": return handleFirewallRemove(server, params.port, params.protocol || "tcp");
          case "firewall-status": return handleFirewallStatus(server);
          case "domain-set":    return handleDomainSet(server, params.domain, params.ssl ?? true);
          case "domain-remove": return handleDomainRemove(server);
          case "domain-check":  return handleDomainCheck(server, params.domain);
          case "domain-info":   return handleDomainInfo(server);
          default: {
            return mcpError(`Unknown action: ${params.action as string}`);
          }
        }
      } catch (error: unknown) {
        return mcpError(getErrorMessage(error));
      }
    }
  • Specific implementation logic for security, firewall, and domain actions used by 'server_secure'.
    export async function handleSecureSetup(
      server: ServerRecord,
      port: number | undefined,
    ): Promise<McpResponse> {
      const result = await applySecureSetup(server.ip, port ? { port } : undefined);
    
      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 message = result.fail2ban
        ? "Security setup complete: SSH hardened + fail2ban active"
        : "Security setup partially complete: SSH hardened, fail2ban failed";
    
      return {
        content: [{ type: "text", text: JSON.stringify({
          success: true,
          server: server.name,
          ip: server.ip,
          message,
          sshHardening: result.sshHardening,
          fail2ban: result.fail2ban,
          sshKeyCount: result.sshKeyCount,
          ...(result.hint ? { hint: result.hint } : {}),
          suggested_actions: [
            { command: `server_secure { action: 'secure-audit', server: '${server.name}' }`, reason: "Verify security configuration" },
          ],
        }) }],
        ...(!result.fail2ban ? { isError: true } : {}),
      };
    }
    
    export async function handleSecureAudit(server: ServerRecord): Promise<McpResponse> {
      const result = await runSecureAudit(server.ip);
    
      if (result.error) {
        return {
          content: [{ type: "text", text: JSON.stringify({
            server: server.name,
            ip: server.ip,
            error: result.error,
            ...(result.hint ? { hint: result.hint } : {}),
          }) }],
          isError: true,
        };
      }
    
      const suggestedActions = result.score < 100
        ? [{ command: `server_secure { action: 'secure-setup', server: '${server.name}' }`, reason: "Improve security score" }]
        : [{ command: `server_secure { action: 'firewall-status', server: '${server.name}' }`, reason: "Check firewall configuration" }];
    
      return mcpSuccess({
        server: server.name,
        ip: server.ip,
        score: result.score,
        maxScore: 100,
        checks: {
          passwordAuth: result.audit.passwordAuth,
          rootLogin: result.audit.rootLogin,
          fail2ban: result.audit.fail2ban,
          sshPort: result.audit.sshPort,
        },
        suggested_actions: suggestedActions,
      });
    }
    
    // ─── Firewall handlers ────────────────────────────────────────────────────────
    
    export async function handleFirewallSetup(server: ServerRecord): Promise<McpResponse> {
      const platform = resolvePlatform(server);
      const result = await setupFirewall(server.ip, 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 } : {}),
          }) }],
          isError: true,
        };
      }
    
      const ports = getPortsForPlatform(platform);
      const platformLabel = platform ?? "bare";
      return mcpSuccess({
        success: true,
        server: server.name,
        ip: server.ip,
        message: `UFW enabled with ${platformLabel} ports (${ports.join(", ")}) + SSH (22)`,
        suggested_actions: [
          { command: `server_secure { action: 'firewall-status', server: '${server.name}' }`, reason: "Verify firewall rules" },
        ],
      });
    }
    
    export async function handleFirewallAdd(
      server: ServerRecord,
      port: number | undefined,
      protocol: "tcp" | "udp",
    ): Promise<McpResponse> {
      if (port === undefined) {
        return mcpError(
          "Port is required for firewall-add action",
          "Specify a port number (1-65535)",
        );
      }
    
      const result = await addFirewallRule(server.ip, port, protocol);
    
      if (!result.success) {
        return {
          content: [{ type: "text", text: JSON.stringify({
            server: server.name,
            ip: server.ip,
            error: result.error,
            ...(result.hint ? { hint: result.hint } : {}),
          }) }],
          isError: true,
        };
      }
    
      return mcpSuccess({
        success: true,
        server: server.name,
        ip: server.ip,
        message: `Port ${port}/${protocol} opened`,
        suggested_actions: [
          { command: `server_secure { action: 'firewall-status', server: '${server.name}' }`, reason: "Verify firewall rules" },
        ],
      });
    }
    
    export async function handleFirewallRemove(
      server: ServerRecord,
      port: number | undefined,
      protocol: "tcp" | "udp",
    ): Promise<McpResponse> {
      if (port === undefined) {
        return mcpError(
          "Port is required for firewall-remove action",
          "Specify a port number (1-65535)",
        );
      }
    
      const platform = resolvePlatform(server);
      const result = await removeFirewallRule(server.ip, port, protocol, 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 } : {}),
            ...(result.warning ? { warning: result.warning } : {}),
          }) }],
          isError: true,
        };
      }
    
      return mcpSuccess({
        success: true,
        server: server.name,
        ip: server.ip,
        message: `Port ${port}/${protocol} closed`,
        ...(result.warning ? { warning: result.warning } : {}),
        suggested_actions: [
          { command: `server_secure { action: 'firewall-status', server: '${server.name}' }`, reason: "Verify firewall rules" },
        ],
      });
    }
    
    export async function handleFirewallStatus(server: ServerRecord): Promise<McpResponse> {
      const result = await getFirewallStatus(server.ip);
    
      if (result.error) {
        return {
          content: [{ type: "text", text: JSON.stringify({
            server: server.name,
            ip: server.ip,
            error: result.error,
            ...(result.hint ? { hint: result.hint } : {}),
          }) }],
          isError: true,
        };
      }
    
      const suggestedActions = !result.status.active
        ? [{ command: `server_secure { action: 'firewall-setup', server: '${server.name}' }`, reason: "Enable firewall" }]
        : [{ command: `server_secure { action: 'firewall-add', server: '${server.name}', port: 3000 }`, reason: "Open additional ports if needed" }];
    
      return mcpSuccess({
        server: server.name,
        ip: server.ip,
        active: result.status.active,
        rules: result.status.rules,
        ruleCount: result.status.rules.length,
        suggested_actions: suggestedActions,
      });
    }
    
    // ─── Domain handlers ──────────────────────────────────────────────────────────
    
    export async function handleDomainSet(
      server: ServerRecord,
      domainName: string | undefined,
      ssl: boolean,
    ): Promise<McpResponse> {
      if (!domainName) {
        return mcpError(
          "Domain is required for domain-set action",
          "Specify a domain name (e.g., coolify.example.com)",
        );
      }
    
      const result = await setDomain(server.ip, domainName, ssl, resolvePlatform(server));
    
      if (!result.success) {
        return {
          content: [{ type: "text", text: JSON.stringify({
            server: server.name,
            ip: server.ip,
            error: result.error,
            ...(result.hint ? { hint: result.hint } : {}),
          }) }],
          isError: true,
        };
      }
    
      const protocol = ssl ? "https" : "http";
      return mcpSuccess({
        success: true,
        server: server.name,
        ip: server.ip,
        message: `Domain set to ${domainName}`,
        url: `${protocol}://${domainName}`,
        suggested_actions: [
          { command: `server_secure { action: 'domain-check', server: '${server.name}', domain: '${domainName}' }`, reason: "Verify DNS points to this server" },
          { command: `server_info { action: 'health', server: '${server.name}' }`, reason: "Verify Coolify is accessible" },
        ],
      });
    }
    
    export async function handleDomainRemove(server: ServerRecord): Promise<McpResponse> {
      const platform = resolvePlatform(server);
      const result = await removeDomain(server.ip, 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 } : {}),
          }) }],
          isError: true,
        };
      }
    
      return mcpSuccess({
        success: true,
        server: server.name,
        ip: server.ip,
        message: "Domain removed. Platform reset to default.",
        url: `http://${server.ip}:${platform === "dokploy" ? DOKPLOY_PORT : COOLIFY_PORT}`,
        suggested_actions: [
          { command: `server_info { action: 'health', server: '${server.name}' }`, reason: "Verify Coolify is accessible" },
        ],
      });
    }
    
    export async function handleDomainCheck(
      server: ServerRecord,
      domainName: string | undefined,
    ): Promise<McpResponse> {
      if (!domainName) {
        return mcpError(
          "Domain is required for domain-check action",
          "Specify a domain name to check DNS for",
        );
      }
    
      const result = await checkDns(server.ip, domainName);
    
      if (result.error) {
        return {
          content: [{ type: "text", text: JSON.stringify({
            server: server.name,
            ip: server.ip,
            domain: domainName,
            error: result.error,
            ...(result.hint ? { hint: result.hint } : {}),
          }) }],
          isError: true,
        };
      }
    
      return mcpSuccess({
        server: server.name,
        ip: server.ip,
        domain: domainName,
        resolvedIp: result.resolvedIp,
        match: result.match,
        ...(result.hint ? { hint: result.hint } : {}),
        suggested_actions: result.match
          ? [{ command: `server_secure { action: 'domain-set', server: '${server.name}', domain: '${domainName}' }`, reason: "Set this domain as Coolify FQDN" }]
          : [{ command: `server_secure { action: 'domain-info', server: '${server.name}' }`, reason: "Check current domain setting" }],
      });
    }
    
    export async function handleDomainInfo(server: ServerRecord): Promise<McpResponse> {
      const platform = resolvePlatform(server);
      const result = await getDomain(server.ip, platform);
    
      if (result.error) {
        return {
          content: [{ type: "text", text: JSON.stringify({
            server: server.name,
            ip: server.ip,
            error: result.error,
            ...(result.hint ? { hint: result.hint } : {}),
          }) }],
          isError: true,
        };
      }
    
      const domainSuggestedActions = [];
      if (result.fqdn) {
        const cleanFqdn = result.fqdn.replace(/^https?:\/\//, "");
        domainSuggestedActions.push({
          command: `server_secure { action: 'domain-check', server: '${server.name}', domain: '${cleanFqdn}' }`,
          reason: "Verify DNS",
        });
      } else {
        domainSuggestedActions.push({
          command: `server_secure { action: 'domain-set', server: '${server.name}', domain: 'coolify.example.com' }`,
          reason: "Set a custom domain",
        });
      }
    
      return mcpSuccess({
        server: server.name,
        ip: server.ip,
        fqdn: result.fqdn,
        message: result.fqdn
          ? `Current domain: ${result.fqdn}`
          : `No custom domain set. Default: http://${server.ip}:${platform === "dokploy" ? DOKPLOY_PORT : COOLIFY_PORT}`,
        suggested_actions: domainSuggestedActions,
      });
    }
  • Input schema definition for the 'server_secure' tool.
    export const serverSecureSchema = {
      action: z.enum([
        "secure-setup", "secure-audit",
        "firewall-setup", "firewall-add", "firewall-remove", "firewall-status",
        "domain-set", "domain-remove", "domain-check", "domain-info",
      ]).describe(
        "Action: Secure: 'secure-setup' hardens SSH + installs fail2ban, 'secure-audit' runs security audit with score. Firewall: 'firewall-setup' installs UFW, 'firewall-add'/'firewall-remove' manage port rules, 'firewall-status' shows rules. Domain: 'domain-set'/'domain-remove' manage FQDN, 'domain-check' verifies DNS, 'domain-info' shows current FQDN.",
      ),
      server: z.string().optional().describe(
        "Server name or IP. Auto-selected if only one server exists.",
      ),
      port: z.number().min(1).max(65535).optional().describe(
        "Port number. Required for firewall-add/remove. Optional SSH port for secure-setup.",
      ),
      protocol: z.enum(["tcp", "udp"]).default("tcp").describe(
        "Protocol for firewall rules. Default: tcp.",
      ),
      domain: z.string().optional().describe(
        "Domain name. Required for domain-set and domain-check.",
      ),
      ssl: z.boolean().default(true).describe(
        "Enable SSL (https) for domain. Default: true.",
      ),
    };
  • Tool registration for 'server_secure' in the MCP server.
    server.registerTool("server_secure", {
      description:
        "Secure Kastell servers. Secure: 'secure-setup' applies SSH hardening + fail2ban, 'secure-audit' runs security audit with score. Firewall: 'firewall-setup' installs UFW with Coolify ports, 'firewall-add'/'firewall-remove' manage port rules, 'firewall-status' shows current rules. Domain: 'domain-set'/'domain-remove' manage custom domain with optional SSL, 'domain-check' verifies DNS, 'domain-info' shows current FQDN. All require SSH access to server. For full one-shot hardening (SSH + fail2ban + UFW + sysctl + unattended-upgrades), use server_lock instead.",
      inputSchema: serverSecureSchema,
      annotations: {
        title: "Server Security",
        readOnlyHint: false,
        destructiveHint: false,
        idempotentHint: true,
        openWorldHint: true,
      },
    }, async (params) => {
      return handleServerSecure(params, server);

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