We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/jmagar/homelab-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
import { readFileSync } from "node:fs";
import Docker from "dockerode";
import { API_TIMEOUT } from "../../../constants.js";
import type { HostConfig } from "../../../types.js";
const DEFAULT_DOCKER_SSH_CONNECT_TIMEOUT_MS = 5000;
/**
* Resolve Docker-over-SSH connection timeout from environment.
*
* Uses a conservative default (5s) so unreachable SSH hosts fail fast.
* Override with DOCKER_SSH_CONNECT_TIMEOUT_MS when needed.
*/
function getDockerSshConnectTimeoutMs(): number {
const raw = process.env.DOCKER_SSH_CONNECT_TIMEOUT_MS;
if (!raw) {
return DEFAULT_DOCKER_SSH_CONNECT_TIMEOUT_MS;
}
const parsed = Number.parseInt(raw, 10);
if (!Number.isFinite(parsed) || parsed <= 0) {
return DEFAULT_DOCKER_SSH_CONNECT_TIMEOUT_MS;
}
return parsed;
}
/**
* Interface for Docker client factory.
* Allows dependency injection of custom Docker client creation logic.
*/
export interface IDockerClientFactory {
createClient(config: HostConfig): Promise<Docker>;
}
/**
* Check if a string looks like a Unix socket path
*/
export function isSocketPath(value: string): boolean {
return (
value.startsWith("/") &&
(value.endsWith(".sock") || value.includes("/docker") || value.includes("/run/"))
);
}
/**
* Default Docker client factory implementation.
* Creates Docker clients based on host configuration (socket, SSH, HTTP/HTTPS).
*
* Architecture Note: Dual SSH Connection Strategy
* ------------------------------------------------
* This system uses TWO separate SSH connection mechanisms:
*
* 1. Docker API SSH (via dockerode/ssh2):
* - Used for Docker API calls (listContainers, stats, etc.)
* - Managed by dockerode library with native SSH tunnel support
* - Connections created per Docker client instance
* - Cached at the client level by ClientManager
*
* 2. SSH Command Execution (via node-ssh/SSHConnectionPool):
* - Used for shell commands (compose, find, scan, exec)
* - Managed by SSHConnectionPool with health checks and pooling
* - Connections pooled and reused across operations
*
* This separation is intentional and provides:
* - API operations isolated from command execution failures
* - Different retry/timeout strategies for different operation types
* - No dependency between Docker API and shell command availability
*
* Future optimization: Consider SSH multiplexing (ControlMaster) at the OS level
* to share TCP connections between both mechanisms.
*/
export class DefaultDockerClientFactory implements IDockerClientFactory {
private keyCache = new Map<string, Buffer>();
/**
* Get private key content with caching.
* Caching eliminates redundant file I/O for repeated client creation.
* Matches SSHConnectionPool pattern for consistency.
*/
private getPrivateKey(keyPath: string): Buffer {
const cached = this.keyCache.get(keyPath);
if (cached) return cached;
const key = readFileSync(keyPath);
this.keyCache.set(keyPath, key);
return key;
}
/**
* Clear cached SSH private keys.
* Call during shutdown to release sensitive key material from memory.
*/
clearKeyCache(): void {
this.keyCache.clear();
}
async createClient(config: HostConfig): Promise<Docker> {
// Check for explicit socket path OR socket path in host field
const socketPath = config.dockerSocketPath || (isSocketPath(config.host) ? config.host : null);
if (socketPath) {
// Unix socket connection
return new Docker({ socketPath });
}
if (config.protocol === "ssh") {
// SSH tunneling to Docker socket
// Dockerode supports SSH protocol natively via ssh:// URL
// Format: ssh://user@host:port
// SECURITY (CWE-250): Require explicit SSH user; fallback to process user, not root
const user = config.sshUser || process.env.USER;
if (!user) {
throw new Error(
`No SSH username configured for Docker host ${config.name}. Set sshUser in config or USER env var.`
);
}
if (user === "root") {
throw new Error(
`SSH as root is not allowed for Docker host ${config.name}. Use a non-root user with Docker group access.`
);
}
const port = config.port || 22;
const dockerOptions: Docker.DockerOptions = {
protocol: "ssh",
host: config.host,
port: port,
username: user,
};
// Configure SSH options for agent and/or private key authentication
// Always use SSH agent if available (matches OpenSSH CLI behavior)
const sshOptions: Docker.DockerOptions["sshOptions"] = {};
sshOptions.readyTimeout = getDockerSshConnectTimeoutMs();
if (process.env.SSH_AUTH_SOCK) {
sshOptions.agent = process.env.SSH_AUTH_SOCK;
}
if (config.sshKeyPath) {
try {
sshOptions.privateKey = this.getPrivateKey(config.sshKeyPath);
} catch (_error) {
/* Intentionally swallow errors - will fallback to SSH agent authentication */
}
}
// Only set sshOptions if we have authentication configured
if (sshOptions.agent || sshOptions.privateKey) {
dockerOptions.sshOptions = sshOptions;
}
return new Docker(dockerOptions);
}
// Remote TCP connection (HTTP/HTTPS)
// Default to HTTP if no protocol specified
const protocol = config.protocol || "http";
if (protocol !== "http" && protocol !== "https") {
throw new Error(`Unsupported protocol: ${protocol}`);
}
return new Docker({
host: config.host,
port: config.port || 2375,
protocol: protocol,
timeout: API_TIMEOUT,
});
}
}