Skip to main content
Glama
sondt2709

Docker MCP

by sondt2709
docker-config.ts6.6 kB
import { existsSync, readFileSync } from 'fs'; import { homedir } from 'os'; import { join } from 'path'; import Docker from 'dockerode'; export interface RemoteDockerConfig { /** SSH host name (as defined in ~/.ssh/config) or IP address */ host: string; /** SSH port (default: 22) */ port?: number; /** SSH username */ username?: string; /** Path to SSH private key file */ privateKey?: string; /** SSH private key content as string */ privateKeyContent?: string; /** SSH passphrase for private key */ passphrase?: string; /** Remote Docker socket path (default: /var/run/docker.sock) */ socketPath?: string; /** Connection timeout in milliseconds (default: 10000) */ timeout?: number; } export interface DockerConnectionConfig { /** Use local Docker daemon */ local?: boolean; /** Remote Docker configuration */ remote?: RemoteDockerConfig; /** Override Docker options */ dockerOptions?: Docker.DockerOptions; } /** * Detects the appropriate Docker socket path based on the environment */ export function detectDockerSocketPath(): string { // Common Docker socket paths to check const socketPaths = [ // OrbStack socket join(homedir(), '.orbstack/run/docker.sock'), // Docker Desktop on macOS join(homedir(), '.docker/run/docker.sock'), // Default Unix socket '/var/run/docker.sock', // Docker Desktop on Windows (if running in WSL) '/mnt/wsl/docker-desktop/shared-sockets/guest-services/docker.sock' ]; for (const socketPath of socketPaths) { if (existsSync(socketPath)) { return socketPath; } } // If no socket found, return default and let Docker handle the error return '/var/run/docker.sock'; } /** * Parses SSH config to extract host details */ export function parseSSHConfig(hostname: string): Partial<RemoteDockerConfig> { const sshConfigPath = join(homedir(), '.ssh/config'); if (!existsSync(sshConfigPath)) { return { host: hostname }; } try { const configContent = readFileSync(sshConfigPath, 'utf-8'); const lines = configContent.split('\n'); let currentHost = ''; let hostConfig: Partial<RemoteDockerConfig> = {}; let isTargetHost = false; for (const line of lines) { const trimmed = line.trim(); if (trimmed.startsWith('Host ')) { // Save previous host config if it was our target if (isTargetHost) { break; } currentHost = trimmed.replace('Host ', '').trim(); isTargetHost = currentHost === hostname; hostConfig = { host: hostname }; } else if (isTargetHost && trimmed.includes(' ')) { const [key, ...valueParts] = trimmed.split(' '); const value = valueParts.join(' ').trim(); switch (key.toLowerCase()) { case 'hostname': hostConfig.host = value; break; case 'port': hostConfig.port = parseInt(value, 10); break; case 'user': hostConfig.username = value; break; case 'identityfile': // Handle tilde expansion and quotes const keyPath = value.replace(/^["']|["']$/g, '').replace('~', homedir()); hostConfig.privateKey = keyPath; break; } } } return isTargetHost ? hostConfig : { host: hostname }; } catch (error) { console.error(`Failed to parse SSH config: ${error}`); return { host: hostname }; } } /** * Creates Docker options for remote connection */ export function createRemoteDockerOptions(config: RemoteDockerConfig): Docker.DockerOptions { const sshConfig = parseSSHConfig(config.host); // Merge SSH config with provided config (provided config takes precedence) const mergedConfig = { ...sshConfig, ...config }; // Read private key if file path is provided let privateKeyContent = mergedConfig.privateKeyContent; if (!privateKeyContent && mergedConfig.privateKey) { try { privateKeyContent = readFileSync(mergedConfig.privateKey, 'utf-8'); } catch (error) { console.error(`Failed to read private key from ${mergedConfig.privateKey}:`, error); } } console.error('SSH configuration for Docker connection:', { host: mergedConfig.host, port: mergedConfig.port || 22, username: mergedConfig.username || 'root', socketPath: mergedConfig.socketPath || '/var/run/docker.sock', hasPrivateKey: !!privateKeyContent }); console.error('Note: docker-modem SSH support appears to have issues.'); console.error('Consider using manual SSH tunneling instead:'); console.error(`ssh -L 2375:/var/run/docker.sock -i ${mergedConfig.privateKey} ${mergedConfig.username}@${mergedConfig.host}`); console.error('Then set DOCKER_HOST=tcp://localhost:2375'); // For now, fall back to using SSH protocol with docker-modem const dockerOptions: Docker.DockerOptions = { protocol: 'ssh', host: mergedConfig.host, port: mergedConfig.port || 22, username: mergedConfig.username || 'root', socketPath: mergedConfig.socketPath || '/var/run/docker.sock', timeout: mergedConfig.timeout || 10000 }; // Add SSH options if private key is available if (privateKeyContent) { dockerOptions.sshOptions = { privateKey: privateKeyContent, passphrase: mergedConfig.passphrase }; } console.error('Docker options with SSH protocol:', dockerOptions); return dockerOptions; } /** * Creates Docker options with the best available configuration */ export function createDockerOptions(config?: DockerConnectionConfig): Docker.DockerOptions { // If explicit Docker options are provided, use them if (config?.dockerOptions) { return config.dockerOptions; } // If remote configuration is provided, use it if (config?.remote) { return createRemoteDockerOptions(config.remote); } // Default to local Docker daemon const socketPath = detectDockerSocketPath(); return { socketPath, timeout: 10000 }; } /** * Parses connection string in format: [user@]host[:port] */ export function parseConnectionString(connectionString: string): RemoteDockerConfig { const parts = connectionString.split('@'); let username: string | undefined; let hostPart: string; if (parts.length === 2) { username = parts[0]; hostPart = parts[1]; } else { hostPart = parts[0]; } const [host, portString] = hostPart.split(':'); const port = portString ? parseInt(portString, 10) : undefined; return { host, port, username }; }

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/sondt2709/docker-mcp'

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