Skip to main content
Glama
HyperbolicLabs

Hyperbolic GPU MCP Server

Official
index.ts26.1 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import dotenv from "dotenv"; import { sshManager } from "./ssh-manager.js"; import { wait } from "./utils.js"; // Load environment variables dotenv.config(); // Hyperbolic API base URL const HYPERBOLIC_API_BASE = "https://api.hyperbolic.xyz/v1"; // Create MCP server instance const server = new McpServer({ name: "hyperbolic-gpu-server", version: "1.0.0", }); // Utility function to make authenticated API requests to Hyperbolic async function makeHyperbolicRequest( endpoint: string, method: string = "GET", body?: any ) { const token = process.env.HYPERBOLIC_API_TOKEN; if (!token) { throw new Error("HYPERBOLIC_API_TOKEN environment variable is not set"); } const headers = { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }; const requestOptions: RequestInit = { method, headers, body: body ? JSON.stringify(body) : undefined, }; try { const response = await fetch( `${HYPERBOLIC_API_BASE}${endpoint}`, requestOptions ); if (!response.ok) { const errorText = await response.text(); throw new Error( `Hyperbolic API error (${response.status}): ${errorText}` ); } return await response.json(); } catch (error) { console.error("Error making Hyperbolic API request:", error); throw error; } } // Utility function to format RAM values function formatRAM(ramInMB: number): string { if (ramInMB >= 1048576) { // 1 TB in MB return `${(ramInMB / 1048576).toFixed(2)} TB`; } else if (ramInMB >= 1024) { // 1 GB in MB return `${(ramInMB / 1024).toFixed(2)} GB`; } else { return `${ramInMB} MB`; } } // Register tool for listing available GPUs server.tool( "list-available-gpus", { filters: z .object({}) .passthrough() .optional() .describe("Optional filters for GPU listing"), }, async ({ filters = {} }) => { try { const data = await makeHyperbolicRequest("/marketplace", "POST", { filters, }); if (!data.instances || !Array.isArray(data.instances)) { throw new Error("Invalid response format from Hyperbolic API"); } // Format the response for better readability const availableGPUs = data.instances .filter((instance: any) => instance.status === "node_ready") .map((instance: any) => { // Extract GPU information const gpuModels = instance.hardware?.gpus ? [...new Set(instance.hardware.gpus.map((gpu: any) => gpu.model))] : ["Unknown"]; const totalGPUs = instance.gpus_total || 0; const reservedGPUs = instance.gpus_reserved || 0; const availableGPUCount = totalGPUs - reservedGPUs; // Extract RAM and storage const totalRAM = instance.hardware?.ram?.[0]?.capacity || 0; const totalStorage = instance.hardware?.storage?.[0]?.capacity || 0; // Get price information const pricePerHour = instance.pricing?.price?.amount || 0; return { cluster_name: instance.cluster_name || "unknown", node_name: instance.id || "unknown", gpu_model: gpuModels.join(", "), gpu_count: totalGPUs, available_gpus: availableGPUCount, gpu_vram_gb: instance.hardware?.gpus?.[0]?.ram ? Math.round(instance.hardware.gpus[0].ram / 1024) : "Unknown", cpu_cores: instance.hardware?.cpus?.[0]?.virtual_cores || "Unknown", ram_gb: totalRAM, storage_gb: totalStorage, price_per_hour: `$${(pricePerHour / 100).toFixed(2)}`, reserved: instance.reserved ? "Yes" : "No", region: instance.location?.region || "Unknown", }; }) // Filter out instances with no available GPUs .filter((instance: any) => instance.available_gpus > 0); if (availableGPUs.length === 0) { return { content: [ { type: "text", text: JSON.stringify({ status: "success", available_gpus: [], message: "No available GPU instances found. All instances are currently reserved or in use." }, null, 2) }, ], }; } // Format the response as JSON const jsonResponse = { status: "success", available_gpus: availableGPUs.map((gpu: { cluster_name: string; node_name: string; gpu_model: string; available_gpus: number; gpu_count: number; gpu_vram_gb: number | string; cpu_cores: number | string; ram_gb: number; storage_gb: number; price_per_hour: string; region: string; reserved: boolean; }) => ({ cluster_name: gpu.cluster_name, node_name: gpu.node_name, gpu_model: gpu.gpu_model, gpu_count: { available: gpu.available_gpus, total: gpu.gpu_count }, specifications: { vram_gb: gpu.gpu_vram_gb, cpu_cores: gpu.cpu_cores, ram_gb: gpu.ram_gb, storage_gb: gpu.storage_gb }, pricing: { per_hour: gpu.price_per_hour }, region: gpu.region, is_reserved: gpu.reserved })), total_available_instances: availableGPUs.length, rental_instructions: { tool: "rent-gpu-instance", required_parameters: ["cluster_name", "node_name", "gpu_count"] } }; return { content: [ { type: "text", text: JSON.stringify(jsonResponse, null, 2) }, ], }; } catch (error) { return { content: [ { type: "text", text: JSON.stringify({ status: "error", error: (error as Error).message }, null, 2) }, ], isError: true, }; } } ); // Register tool for renting a GPU instance server.tool( "rent-gpu-instance", { cluster_name: z .string() .describe( "The name of the cluster to rent (e.g., extrasmall-chamomile-duck)" ), node_name: z .string() .describe("The name of the node (e.g., prd-acl-msi-02.fen.intra)"), gpu_count: z.number().int().min(1).describe("Number of GPUs to rent"), }, async ({ cluster_name, node_name, gpu_count }) => { try { // First, verify that the cluster exists and has available GPUs const marketplaceData = await makeHyperbolicRequest( "/marketplace", "POST", { filters: {} } ); if ( !marketplaceData.instances || !Array.isArray(marketplaceData.instances) ) { throw new Error("Invalid response format from Hyperbolic API"); } // Find the specified cluster const instance = marketplaceData.instances.find( (i: any) => i.cluster_name === cluster_name && i.id === node_name ); if (!instance) { return { content: [ { type: "text", text: `Error: The specified cluster (${cluster_name}) or node (${node_name}) was not found. Please check the names and try again.`, }, ], isError: true, }; } // Check if the instance is available const availableGPUCount = (instance.gpus_total || 0) - (instance.gpus_reserved || 0); if (availableGPUCount < gpu_count) { return { content: [ { type: "text", text: `Error: The requested number of GPUs (${gpu_count}) exceeds the available GPUs (${availableGPUCount}) on this cluster.`, }, ], isError: true, }; } // If validation passes, proceed with the rental const requestBody = { cluster_name, node_name, gpu_count, }; const data = await makeHyperbolicRequest( "/marketplace/instances/create", "POST", requestBody ); // Wait for 60 seconds to allow the instance to start up const waitMessage = "Waiting 100 seconds for the instance to start up..."; console.error(waitMessage); await wait(100000); const completeMessage = "Wait complete. Instance should be ready for SSH connection."; console.error(completeMessage); // Format the response as JSON const jsonResponse = { status: "success", rental_details: { cluster_name, node_name, gpus_rented: gpu_count, instance_id: data.instance_id || null, status: data.status || "created" }, hardware_details: { gpu_model: instance.hardware?.gpus?.[0]?.model || null, gpu_vram: instance.hardware?.gpus?.[0]?.ram ? formatRAM(instance.hardware.gpus[0].ram) : null, cpu: { model: instance.hardware?.cpus?.[0]?.model || null, virtual_cores: instance.hardware?.cpus?.[0]?.virtual_cores || null } }, pricing: { amount: instance.pricing?.price?.amount || null, period: instance.pricing?.price?.period || "hour" }, connection_info: data.connection_info || null, startup_status: { wait_message: waitMessage, complete_message: completeMessage, wait_duration_seconds: 60 } }; return { content: [ { type: "text", text: JSON.stringify(jsonResponse, null, 2) }, ], }; } catch (error) { return { content: [ { type: "text", text: JSON.stringify({ status: "error", error: (error as Error).message }, null, 2) }, ], isError: true, }; } } ); // Register tool for terminating a GPU instance server.tool( "terminate-gpu-instance", { instance_id: z .string() .describe("The ID of the instance to terminate"), }, async ({ instance_id }) => { try { // First, verify that the instance exists and belongs to the user const instancesData = await makeHyperbolicRequest("/marketplace/instances"); if (!instancesData.instances || !Array.isArray(instancesData.instances)) { throw new Error("Invalid response format from Hyperbolic API"); } // Find the specified instance const instance = instancesData.instances.find( (i: any) => i.id === instance_id ); if (!instance) { return { content: [ { type: "text", text: `Error: Instance with ID "${instance_id}" was not found. Please check the ID and try again.`, }, ], isError: true, }; } // Proceed with termination const requestBody = { id: instance_id, }; const data = await makeHyperbolicRequest( "/marketplace/instances/terminate", "POST", requestBody ); // Format the response in a more readable way const formattedResponse = `# GPU Instance Successfully Terminated! ## Termination Details - Instance ID: ${instance_id} - Status: Terminated ## Instance Information - GPU Model: ${instance.instance?.hardware?.gpus?.[0]?.model || "Unknown"} - GPU Count: ${instance.instance?.gpu_count || "Unknown"} - Created: ${instance.created ? new Date(instance.created).toLocaleString() : "Unknown"} - Terminated: ${new Date().toLocaleString()} The instance has been terminated and all associated resources have been released.`; return { content: [ { type: "text", text: formattedResponse, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error terminating GPU instance: ${(error as Error).message}`, }, ], isError: true, }; } } ); // Register tool for getting details of a specific cluster server.tool( "get-cluster-details", { cluster_name: z .string() .describe("The name of the cluster to get details for"), }, async ({ cluster_name }) => { try { // First, get all instances const data = await makeHyperbolicRequest("/marketplace", "POST", { filters: {}, }); if (!data.instances || !Array.isArray(data.instances)) { throw new Error("Invalid response format from Hyperbolic API"); } // Find the specific instance by cluster_name const instance = data.instances.find( (i: any) => i.cluster_name === cluster_name ); if (!instance) { return { content: [ { type: "text", text: `Cluster "${cluster_name}" not found. Please check the cluster name and try again.`, }, ], isError: true, }; } // Format detailed information in a more readable way const gpuInfo = instance.hardware?.gpus ?.map( (gpu: any, index: number) => `GPU ${index + 1}: ${gpu.model || "Unknown"}, VRAM: ${formatRAM( gpu.ram || 0 )}` ) .join("\n") || "No GPU information available"; const cpuInfo = instance.hardware?.cpus?.[0] ? `${instance.hardware.cpus[0].model || "Unknown"}, ${ instance.hardware.cpus[0].virtual_cores || 0 } virtual cores` : "No CPU information available"; const ramInfo = instance.hardware?.ram?.[0] ? `${formatRAM(instance.hardware.ram[0].capacity || 0)}` : "No RAM information available"; const storageInfo = instance.hardware?.storage?.[0] ? `${instance.hardware.storage[0].capacity || 0} GB` : "No storage information available"; const pricing = instance.pricing?.price ? `${instance.pricing.price.amount || 0} per ${ instance.pricing.price.period || "hour" }` : "Pricing information not available"; const availableGPUCount = (instance.gpus_total || 0) - (instance.gpus_reserved || 0); const availabilityStatus = availableGPUCount > 0 ? "Available" : "Fully Reserved"; // Format the response as JSON const jsonResponse = { status: "success", cluster_info: { name: cluster_name, node_name: instance.id || null, status: instance.status || null, availability_status: availabilityStatus, region: instance.location?.region || null, is_reserved: instance.reserved || false, has_persistent_storage: instance.has_persistent_storage || false }, hardware_specs: { cpu: { model: instance.hardware?.cpus?.[0]?.model || null, virtual_cores: instance.hardware?.cpus?.[0]?.virtual_cores || null }, ram: instance.hardware?.ram?.[0]?.capacity ? formatRAM(instance.hardware.ram[0].capacity) : null, storage: instance.hardware?.storage?.[0]?.capacity ? `${instance.hardware.storage[0].capacity} GB` : null, gpus: { total: instance.gpus_total || 0, available: availableGPUCount, reserved: instance.gpus_reserved || 0, details: instance.hardware?.gpus?.map((gpu: any, index: number) => ({ index: index + 1, model: gpu.model || null, vram: gpu.ram ? formatRAM(gpu.ram) : null })) || [] } }, pricing: { amount: instance.pricing?.price?.amount || null, period: instance.pricing?.price?.period || "hour" }, running_instances: instance.instances?.map((inst: any) => ({ id: inst.id, status: inst.status, gpu_count: inst.hardware?.filter((h: any) => h.gpu).length || 0, storage: inst.hardware?.find((h: any) => h.storage)?.storage?.capacity || 0 })) || [], rental_instructions: { tool: "rent-gpu-instance", parameters: { cluster_name: cluster_name, node_name: instance.id || "", gpu_count: Math.min(availableGPUCount, 1), max_available: availableGPUCount } } }; return { content: [ { type: "text", text: JSON.stringify(jsonResponse, null, 2) }, ], }; } catch (error) { return { content: [ { type: "text", text: JSON.stringify({ status: "error", error: (error as Error).message }, null, 2) }, ], isError: true, }; } } ); // Register SSH connection tool server.tool( "ssh-connect", { host: z.string().describe("Hostname or IP address of the remote server. Always query the list-user-instances tool to get the host."), username: z.string().describe("SSH username for authentication"), password: z .string() .optional() .describe("SSH password for authentication (optional if using key)"), private_key_path: z .string() .optional() .describe( "Path to private key file (optional, uses SSH_PRIVATE_KEY_PATH from environment if not provided)" ), port: z .number() .int() .min(1) .max(65535) .default(22) .describe("SSH port number (default: 22)"), }, async ({ host, username, password, private_key_path, port }) => { try { // console.log( // `Attempting SSH connection to ${host}:${port} as ${username}` // ); const result = await sshManager.connect( host, username, password, private_key_path, port ); return { content: [ { type: "text", text: JSON.stringify({ status: result.startsWith("SSH Connection Error") || result.startsWith("SSH Key Error") ? "error" : "success", message: result }, null, 2) }, ], isError: result.startsWith("SSH Connection Error") || result.startsWith("SSH Key Error"), }; } catch (error) { console.error("SSH connection failed with error:", error); const errorMessage = error ? error instanceof Error ? error.message || "Unknown error" : String(error) : "Unknown error"; return { content: [ { type: "text", text: JSON.stringify({ status: "error", error: errorMessage }, null, 2) }, ], isError: true, }; } } ); // Register SSH command execution tool server.tool( "remote-shell", { command: z.string().describe("Command to execute on the remote server"), }, async ({ command }) => { try { if (!sshManager.isConnected()) { return { content: [ { type: "text", text: JSON.stringify({ status: "error", error: "No active SSH connection. Please connect first using the ssh-connect tool." }, null, 2) }, ], isError: true, }; } // console.log(`Executing remote command: ${command}`); const result = await sshManager.execute(command); return { content: [ { type: "text", text: JSON.stringify({ status: result.startsWith("Error:") || result.startsWith("SSH Command Error:") ? "error" : "success", command, output: result || "(Command executed with no output)" }, null, 2) }, ], isError: result.startsWith("Error:") || result.startsWith("SSH Command Error:"), }; } catch (error) { console.error("SSH command execution error:", error); const errorMessage = error ? error instanceof Error ? error.message || "Unknown error" : String(error) : "Unknown error"; return { content: [ { type: "text", text: JSON.stringify({ status: "error", error: errorMessage }, null, 2) }, ], isError: true, }; } } ); // Register SSH status tool server.tool("ssh-status", {}, async () => { try { const status = sshManager.getConnectionInfo(); return { content: [ { type: "text", text: JSON.stringify({ status: "success", connection_status: status }, null, 2) }, ], }; } catch (error) { console.error("SSH status error:", error); const errorMessage = error ? error instanceof Error ? error.message || "Unknown error" : String(error) : "Unknown error"; return { content: [ { type: "text", text: JSON.stringify({ status: "error", error: errorMessage }, null, 2) }, ], isError: true, }; } }); // Register SSH disconnect tool server.tool("ssh-disconnect", {}, async () => { try { if (!sshManager.isConnected()) { return { content: [ { type: "text", text: JSON.stringify({ status: "success", message: "No active SSH connection to disconnect." }, null, 2) }, ], }; } await sshManager.disconnect(); return { content: [ { type: "text", text: JSON.stringify({ status: "success", message: "SSH connection closed successfully." }, null, 2) }, ], }; } catch (error) { console.error("SSH disconnect error:", error); const errorMessage = error ? error instanceof Error ? error.message || "Unknown error" : String(error) : "Unknown error"; return { content: [ { type: "text", text: JSON.stringify({ status: "error", error: errorMessage }, null, 2) }, ], isError: true, }; } }); // Register tool for listing user's active instances server.tool("list-user-instances", {}, async () => { try { const data = await makeHyperbolicRequest("/marketplace/instances"); if (!data.instances || !Array.isArray(data.instances)) { throw new Error("Invalid response format from Hyperbolic API"); } if (data.instances.length === 0) { return { content: [ { type: "text", text: JSON.stringify({ status: "success", instances: [], message: "You don't have any active instances on Hyperbolic." }, null, 2) }, ], }; } // Format the response as JSON const jsonResponse = { status: "success", instances: data.instances.map((instance: any, index: number) => ({ id: instance.id || null, basic_info: { status: instance.instance?.status || null, created: instance.created ? new Date(instance.created).toISOString() : null, started: instance.start ? new Date(instance.start).toISOString() : null, ended: instance.end ? new Date(instance.end).toISOString() : null, owner_id: instance.instance?.owner || null }, hardware: { cpu: { model: instance.instance?.hardware?.cpus?.[0]?.model || null, virtual_cores: instance.instance?.hardware?.cpus?.[0]?.virtual_cores || null }, ram: instance.instance?.hardware?.ram?.[0]?.capacity ? formatRAM(instance.instance.hardware.ram[0].capacity) : null, storage: instance.instance?.hardware?.storage?.[0]?.capacity ? `${instance.instance.hardware.storage[0].capacity} GB` : null, gpus: { allocated: instance.instance?.gpu_count || 0, total: instance.instance?.gpus_total || 0, details: instance.instance?.hardware?.gpus?.map((gpu: any) => ({ model: gpu.model || null, vram: gpu.ram ? formatRAM(gpu.ram) : null })) || [] } }, pricing: { amount: instance.instance?.pricing?.price?.amount || null, period: instance.instance?.pricing?.price?.period || "hour" }, connection: { ssh_command: instance.sshCommand || null, port_mappings: instance.portMappings || [] } })), total_instances: data.instances.length }; return { content: [ { type: "text", text: JSON.stringify(jsonResponse, null, 2) }, ], }; } catch (error) { return { content: [ { type: "text", text: JSON.stringify({ status: "error", error: (error as Error).message }, null, 2) }, ], isError: true, }; } }); // Start the server async function main() { const transport = new StdioServerTransport(); console.error("Starting Hyperbolic GPU MCP Server..."); await server.connect(transport); console.error("Hyperbolic GPU MCP Server connected and ready"); } main().catch((error) => { console.error("Fatal error:", error); process.exit(1); });

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/HyperbolicLabs/hyperbolic-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server