Skip to main content
Glama

server_manage

Destructive

Register, unregister, or permanently delete Kastell servers from cloud providers like Hetzner, DigitalOcean, Vultr, and Linode. Manage server configurations locally and in the cloud.

Instructions

Manage Kastell servers. Actions: 'add' registers an existing Coolify or bare server to local config (validates API token, optionally verifies Coolify via SSH — pass mode:'bare' for servers without Coolify). 'remove' unregisters a server from local config only (cloud server keeps running). 'destroy' PERMANENTLY DELETES the server from the cloud provider and removes from local config. Requires provider API tokens as environment variables. Destroy is blocked when KASTELL_SAFE_MODE=true. Server mode for 'add' action: 'coolify', 'dokploy', or 'bare'. Default: coolify

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYesAction: 'add' register an existing server, 'remove' unregister from local config (server stays running), 'destroy' permanently delete from cloud provider AND local config
serverNoServer name or IP (required for 'remove' and 'destroy' actions)
providerNoCloud provider: 'hetzner', 'digitalocean', 'vultr', 'linode' (required for 'add' action)
ipNoServer public IP address (required for 'add' action)
nameNoServer name, 3-63 chars, lowercase alphanumeric and hyphens (required for 'add' action)
skipVerifyNoSkip Coolify SSH verification when adding a server (only for 'add' action)
modeNoServer mode for 'add' action: 'coolify', 'dokploy', or 'bare'. Default: coolifycoolify

Implementation Reference

  • The handleServerManage function implements the core logic for the server_manage MCP tool, handling 'add', 'remove', and 'destroy' actions.
    export async function handleServerManage(params: {
      action: "add" | "remove" | "destroy";
      server?: string;
      provider?: string;
      ip?: string;
      name?: string;
      skipVerify?: boolean;
      mode?: "coolify" | "dokploy" | "bare";
    }): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
      try {
        switch (params.action) {
          case "add": {
            if (!params.provider) {
              return mcpError(
                "Missing required parameter: provider",
                "Specify provider: 'hetzner', 'digitalocean', 'vultr', or 'linode'",
              );
            }
            if (!params.ip) {
              return mcpError(
                "Missing required parameter: ip",
                "Specify the server's public IP address",
              );
            }
            if (!params.name) {
              return mcpError(
                "Missing required parameter: name",
                "Specify a server name (3-63 chars, lowercase, alphanumeric and hyphens)",
              );
            }
    
            const mode = params.mode ?? "coolify";
    
            const result = await addServerRecord({
              provider: params.provider,
              ip: params.ip,
              name: params.name,
              skipVerify: params.skipVerify ?? false,
              mode,
            });
    
            if (!result.success) {
              return mcpError(
                result.error ?? "Add server failed",
                undefined,
                [
                  { command: "server_info { action: 'list' }", reason: "Check existing servers" },
                ],
              );
            }
    
            const suggestedActions =
              mode === "bare"
                ? [
                    {
                      command: `server_info { action: 'status', server: '${result.server!.name}' }`,
                      reason: "Check server status",
                    },
                    {
                      command: `server_secure { action: 'secure-setup', server: '${result.server!.name}' }`,
                      reason: "Harden SSH security + install fail2ban",
                    },
                    {
                      command: `server_logs { action: 'logs', server: '${result.server!.name}' }`,
                      reason: "View server logs",
                    },
                  ]
                : [
                    {
                      command: `server_info { action: 'status', server: '${result.server!.name}' }`,
                      reason: "Check server status",
                    },
                    {
                      command: `server_info { action: 'health', server: '${result.server!.name}' }`,
                      reason: "Check Coolify health",
                    },
                    {
                      command: `server_logs { action: 'logs', server: '${result.server!.name}' }`,
                      reason: "View server logs",
                    },
                  ];
    
            return mcpSuccess({
              success: true,
              message: `Server "${result.server!.name}" added successfully`,
              server: {
                name: result.server!.name,
                ip: result.server!.ip,
                provider: result.server!.provider,
                id: result.server!.id,
                mode,
              },
              platformStatus: result.platformStatus,
              suggested_actions: suggestedActions,
            });
          }
    
          case "remove": {
            if (!params.server) {
              const servers = getServers();
              if (servers.length === 0) {
                return mcpError(
                  "No servers found",
                  undefined,
                  [{ command: "kastell init", reason: "Deploy a server first" }],
                );
              }
              return {
                content: [{ type: "text", text: JSON.stringify({
                  error: "Missing required parameter: server",
                  available_servers: servers.map((s) => ({ name: s.name, ip: s.ip })),
                  hint: "Specify which server to remove by name or IP",
                }) }],
                isError: true,
              };
            }
    
            const result = await removeServerRecord(params.server);
    
            if (!result.success) {
              const servers = getServers();
              return {
                content: [{ type: "text", text: JSON.stringify({
                  error: result.error,
                  available_servers: servers.map((s) => s.name),
                }) }],
                isError: true,
              };
            }
    
            return mcpSuccess({
              success: true,
              message: `Server "${result.server!.name}" removed from local config`,
              note: "The cloud server is still running. Use 'destroy' to delete it from the provider.",
              server: {
                name: result.server!.name,
                ip: result.server!.ip,
                provider: result.server!.provider,
              },
              suggested_actions: [
                { command: "server_info { action: 'list' }", reason: "View remaining servers" },
              ],
            });
          }
    
          case "destroy": {
            // SAFE_MODE check
            if (isSafeMode()) {
              return mcpError(
                "Destroy is disabled in SAFE_MODE (enabled by default for safety)",
                "Set KASTELL_SAFE_MODE=false to enable destructive operations. WARNING: This will permanently delete the server from the cloud provider.",
                [
                  {
                    command: `server_manage { action: 'remove', server: '${params.server ?? ""}' }`,
                    reason: "Remove from local config only (non-destructive)",
                  },
                ],
              );
            }
    
            if (!params.server) {
              const servers = getServers();
              if (servers.length === 0) {
                return mcpError(
                  "No servers found",
                  undefined,
                  [{ command: "kastell init", reason: "Deploy a server first" }],
                );
              }
              return {
                content: [{ type: "text", text: JSON.stringify({
                  error: "Missing required parameter: server",
                  available_servers: servers.map((s) => ({ name: s.name, ip: s.ip, provider: s.provider })),
                  hint: "Specify which server to destroy by name or IP",
                  warning: "This will PERMANENTLY DELETE the server from the cloud provider",
                }) }],
                isError: true,
              };
            }
    
            const result = await destroyCloudServer(params.server);
    
            if (!result.success) {
              return {
                content: [{ type: "text", text: JSON.stringify({
                  error: result.error,
                  ...(result.hint ? { hint: result.hint } : {}),
                  ...(result.server ? {
                    server: { name: result.server.name, ip: result.server.ip, provider: result.server.provider },
                  } : {}),
                  suggested_actions: [
                    { command: `server_manage { action: 'remove', server: '${params.server}' }`, reason: "Remove from local config only" },
                    { command: "kastell doctor --check-tokens", reason: "Verify API tokens" },
                  ],
                }) }],
                isError: true,
              };
            }
    
            return mcpSuccess({
              success: true,
              message: `Server "${result.server!.name}" destroyed`,
              cloudDeleted: result.cloudDeleted,
              localRemoved: result.localRemoved,
              ...(result.hint ? { note: result.hint } : {}),
              server: {
                name: result.server!.name,
                ip: result.server!.ip,
                provider: result.server!.provider,
              },
              suggested_actions: [
                { command: "server_info { action: 'list' }", reason: "Verify server was removed" },
              ],
            });
          }
          default: {
            return mcpError(`Unknown action: ${params.action as string}`);
          }
        }
      } catch (error: unknown) {
        return mcpError(
          error instanceof Error ? error.message : String(error),
        );
      }
    }
  • The serverManageSchema defines the input parameters for the server_manage tool using Zod.
    export const serverManageSchema = {
      action: z.enum(["add", "remove", "destroy"]).describe(
        "Action: 'add' register an existing server, 'remove' unregister from local config (server stays running), 'destroy' permanently delete from cloud provider AND local config",
      ),
      server: z.string().optional().describe(
        "Server name or IP (required for 'remove' and 'destroy' actions)",
      ),
      provider: z.enum(SUPPORTED_PROVIDERS).optional().describe(
        "Cloud provider: 'hetzner', 'digitalocean', 'vultr', 'linode' (required for 'add' action)",
      ),
      ip: z.string().optional().describe(
        "Server public IP address (required for 'add' action)",
      ),
      name: z.string().optional().describe(
        "Server name, 3-63 chars, lowercase alphanumeric and hyphens (required for 'add' action)",
      ),
      skipVerify: z.boolean().default(false).describe(
        "Skip Coolify SSH verification when adding a server (only for 'add' action)",
      ),
      mode: z
        .enum(["coolify", "dokploy", "bare"])
        .default("coolify")
        .describe(
          "Server mode for 'add' action: 'coolify', 'dokploy', or 'bare'. Default: coolify",
        ),
    };
  • The server_manage tool is registered in src/mcp/server.ts using the handleServerManage function and serverManageSchema.
    server.registerTool("server_manage", {
      description:
        "Manage Kastell servers. Actions: 'add' registers an existing Coolify or bare server to local config (validates API token, optionally verifies Coolify via SSH — pass mode:'bare' for servers without Coolify). 'remove' unregisters a server from local config only (cloud server keeps running). 'destroy' PERMANENTLY DELETES the server from the cloud provider and removes from local config. Requires provider API tokens as environment variables. Destroy is blocked when KASTELL_SAFE_MODE=true. Server mode for 'add' action: 'coolify', 'dokploy', or 'bare'. Default: coolify",
      inputSchema: serverManageSchema,
      annotations: {
        title: "Server Management",
        readOnlyHint: false,
        destructiveHint: true,
        idempotentHint: false,
        openWorldHint: true,
      },
Behavior4/5

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

Supplements annotations (destructiveHint: true, readOnlyHint: false) with crucial behavioral details: destroy requires env var auth, is blocked by SAFE_MODE, add validates tokens and optionally verifies SSH. Explains the persistence model (local config vs cloud). Does not describe failure modes or output behavior, leaving minor gaps.

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

Conciseness3/5

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

While information-dense and front-loaded, the description is a single dense block with repetitive clauses (mode options mentioned twice). The content earns its place, but the structure hinders quick parsing of critical distinctions between the three destructive severity levels (add/remove/destroy).

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

Completeness4/5

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

For a complex 7-parameter tool with destructive operations and no output schema, the description adequately covers safety mechanisms, authentication requirements, and action semantics. Missing explicit mention of return values or success indicators, but the operational scope is sufficiently documented given the schema coverage.

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

Parameters4/5

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

With 100% schema coverage, the baseline is adequately met. The description adds valuable action-specific context explaining which parameters apply to which actions (e.g., 'only for add action', 'pass mode:bare for servers without Coolify'), clarifying relationships not obvious from isolated schema descriptions.

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 precisely defines the tool's scope using specific verbs (registers, unregisters, deletes) and clearly distinguishes the three actions (add/remove/destroy). It differentiates from siblings like server_provision by specifying 'add' is for registering existing servers rather than creating new ones, and explicitly contrasts local config changes vs cloud provider operations.

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

Usage Guidelines4/5

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

Provides explicit guidance on when to use each action (e.g., 'remove' keeps cloud server running while 'destroy' permanently deletes). Documents critical prerequisites (provider API tokens as environment variables) and safety guards (KASTELL_SAFE_MODE blocks destroy). Could improve by explicitly contrasting with server_provision for creation workflows.

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