server_manage
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
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | Action: 'add' register an existing server, 'remove' unregister from local config (server stays running), 'destroy' permanently delete from cloud provider AND local config | |
| server | No | Server name or IP (required for 'remove' and 'destroy' actions) | |
| provider | No | Cloud provider: 'hetzner', 'digitalocean', 'vultr', 'linode' (required for 'add' action) | |
| ip | No | Server public IP address (required for 'add' action) | |
| name | No | Server name, 3-63 chars, lowercase alphanumeric and hyphens (required for 'add' action) | |
| skipVerify | No | Skip Coolify SSH verification when adding a server (only for 'add' action) | |
| mode | No | Server mode for 'add' action: 'coolify', 'dokploy', or 'bare'. Default: coolify | coolify |
Implementation Reference
- src/mcp/tools/serverManage.ts:39-263 (handler)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), ); } } - src/mcp/tools/serverManage.ts:12-37 (schema)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", ), }; - src/mcp/server.ts:86-96 (registration)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, },