@wonderwhy-er/desktop-commander
Version:
MCP server for terminal operations and file editing
728 lines (711 loc) • 29.8 kB
JavaScript
import os from 'os';
import fs from 'fs';
import path from 'path';
import { execFileSync } from 'child_process';
import { getDefaultShell, resolveShellPath } from './shell.js';
/**
* Detect container environment and type
*/
function detectContainerEnvironment() {
// Method 1: Check environment variables first (most reliable when set)
// Docker-specific
if (process.env.MCP_CLIENT_DOCKER === 'true') {
return { isContainer: true, containerType: 'docker', orchestrator: null };
}
// Kubernetes detection
if (process.env.KUBERNETES_SERVICE_HOST || process.env.KUBERNETES_PORT) {
return { isContainer: true, containerType: 'kubernetes', orchestrator: 'kubernetes' };
}
// Podman detection
if (process.env.PODMAN_CONTAINER || process.env.CONTAINER_HOST?.includes('podman')) {
return { isContainer: true, containerType: 'podman', orchestrator: null };
}
// Method 2: Check for container indicator files
if (fs.existsSync('/.dockerenv')) {
return { isContainer: true, containerType: 'docker', orchestrator: null };
}
// Method 3: Check /proc/1/cgroup for container indicators (Linux only)
if (os.platform() === 'linux') {
try {
const cgroup = fs.readFileSync('/proc/1/cgroup', 'utf8');
// Docker detection
if (cgroup.includes('docker')) {
return { isContainer: true, containerType: 'docker', orchestrator: null };
}
// Kubernetes detection (pods run in containerd/cri-o)
if (cgroup.includes('kubepods') || cgroup.includes('pod')) {
return { isContainer: true, containerType: 'kubernetes', orchestrator: 'kubernetes' };
}
// Podman detection
if (cgroup.includes('podman') || cgroup.includes('libpod')) {
return { isContainer: true, containerType: 'podman', orchestrator: null };
}
// LXC detection
if (cgroup.includes('lxc')) {
return { isContainer: true, containerType: 'lxc', orchestrator: null };
}
// systemd-nspawn detection
if (cgroup.includes('machine.slice')) {
return { isContainer: true, containerType: 'systemd-nspawn', orchestrator: null };
}
// Generic containerd detection
if (cgroup.includes('containerd')) {
return { isContainer: true, containerType: 'other', orchestrator: null };
}
}
catch (error) {
// /proc/1/cgroup might not exist
}
}
// Method 4: Check /proc/1/environ for container indicators
if (os.platform() === 'linux') {
try {
const environ = fs.readFileSync('/proc/1/environ', 'utf8');
if (environ.includes('container=')) {
// systemd-nspawn sets container=systemd-nspawn
if (environ.includes('container=systemd-nspawn')) {
return { isContainer: true, containerType: 'systemd-nspawn', orchestrator: null };
}
// LXC sets container=lxc
if (environ.includes('container=lxc')) {
return { isContainer: true, containerType: 'lxc', orchestrator: null };
}
// Generic container detection
return { isContainer: true, containerType: 'other', orchestrator: null };
}
}
catch (error) {
// /proc/1/environ might not exist or be accessible
}
}
// Method 5: Check hostname for Kubernetes patterns
try {
const hostname = os.hostname();
// Kubernetes pods often have hostnames like: podname-deploymentid-randomid
if (hostname && (hostname.includes('-') && hostname.split('-').length >= 3)) {
// Additional check for Kubernetes service account
if (fs.existsSync('/var/run/secrets/kubernetes.io')) {
return { isContainer: true, containerType: 'kubernetes', orchestrator: 'kubernetes' };
}
}
}
catch (error) {
// Hostname check failed
}
// Method 6: Check for orchestrator-specific indicators
// Docker Compose detection
if (process.env.COMPOSE_PROJECT_NAME || process.env.COMPOSE_SERVICE) {
return { isContainer: true, containerType: 'docker', orchestrator: 'docker-compose' };
}
// Docker Swarm detection
if (process.env.DOCKER_SWARM_MODE || process.env.DOCKER_NODE_ID) {
return { isContainer: true, containerType: 'docker', orchestrator: 'docker-swarm' };
}
return { isContainer: false, containerType: null, orchestrator: null };
}
/**
* Discover container mount points
*/
function discoverContainerMounts(isContainer) {
const mounts = [];
if (!isContainer) {
return mounts;
}
// Method 1: Parse /proc/mounts (Linux only)
if (os.platform() === 'linux') {
try {
const mountsContent = fs.readFileSync('/proc/mounts', 'utf8');
const mountLines = mountsContent.split('\n');
// System filesystem types that are never user mounts
const systemFsTypes = new Set([
'overlay', 'tmpfs', 'proc', 'sysfs', 'devpts', 'cgroup', 'cgroup2',
'mqueue', 'debugfs', 'securityfs', 'pstore', 'configfs', 'fusectl',
'hugetlbfs', 'autofs', 'devtmpfs', 'bpf', 'tracefs', 'shm'
]);
// Filesystem types that indicate host mounts
const hostMountFsTypes = new Set(['fakeowner', '9p', 'virtiofs', 'fuse.sshfs']);
for (const line of mountLines) {
const parts = line.split(' ');
if (parts.length >= 4) {
const device = parts[0];
const mountPoint = parts[1];
const fsType = parts[2];
const options = parts[3].split(',');
// Skip system mount points
const isSystemMountPoint = mountPoint === '/' ||
mountPoint.startsWith('/dev') ||
mountPoint.startsWith('/sys') ||
mountPoint.startsWith('/proc') ||
mountPoint.startsWith('/run') ||
mountPoint.startsWith('/sbin') ||
mountPoint === '/etc/resolv.conf' ||
mountPoint === '/etc/hostname' ||
mountPoint === '/etc/hosts';
if (isSystemMountPoint) {
continue;
}
// Detect user mounts by:
// 1. Known host-mount filesystem types (fakeowner, 9p, virtiofs)
// 2. Device from /run/host_mark/ (docker-mcp-gateway pattern)
// 3. Non-system filesystem type with user-like mount point
const isHostMountFs = hostMountFsTypes.has(fsType);
const isHostMarkDevice = device.startsWith('/run/host_mark/');
const isNonSystemFs = !systemFsTypes.has(fsType);
const isUserLikePath = mountPoint.startsWith('/mnt/') ||
mountPoint.startsWith('/workspace') ||
mountPoint.startsWith('/data/') ||
mountPoint.startsWith('/home/') ||
mountPoint.startsWith('/Users/') ||
mountPoint.startsWith('/app/') ||
mountPoint.startsWith('/project/') ||
mountPoint.startsWith('/src/') ||
mountPoint.startsWith('/code/');
if (isHostMountFs || isHostMarkDevice || (isNonSystemFs && isUserLikePath)) {
const isReadOnly = options.includes('ro');
mounts.push({
hostPath: device,
containerPath: mountPoint,
type: 'bind',
readOnly: isReadOnly,
verifiedMount: true,
description: `Mounted directory: ${path.basename(mountPoint)}`
});
}
}
}
}
catch (error) {
// /proc/mounts might not be available
}
}
// Method 2: Check /mnt directory contents
try {
if (fs.existsSync('/mnt')) {
const contents = fs.readdirSync('/mnt');
for (const item of contents) {
const itemPath = `/mnt/${item}`;
try {
const stats = fs.statSync(itemPath);
if (stats.isDirectory()) {
// Check if we already have this mount
const exists = mounts.some(m => m.containerPath === itemPath);
if (!exists) {
mounts.push({
hostPath: `<host>/${item}`,
containerPath: itemPath,
type: 'bind',
readOnly: false,
verifiedMount: false,
description: `Candidate folder under /mnt: ${item}`
});
}
}
}
catch (itemError) {
// Skip items we can't stat
}
}
}
}
catch (error) {
// /mnt directory doesn't exist or not accessible
}
// Method 3: Check /home directory for user-mounted folders (Desktop Commander Docker installer pattern)
try {
if (fs.existsSync('/home')) {
const contents = fs.readdirSync('/home');
for (const item of contents) {
// Skip the root user directory and common system directories
if (item === 'root' || item === 'node' || item === 'bin' || item === 'sbin' ||
item === 'usr' || item === 'lib' || item === 'lib64' || item === 'var' ||
item === 'tmp' || item === 'opt' || item === 'sys' || item === 'proc') {
continue;
}
const itemPath = `/home/${item}`;
try {
const stats = fs.statSync(itemPath);
if (stats.isDirectory()) {
// Check if we already have this mount
const exists = mounts.some(m => m.containerPath === itemPath);
if (!exists) {
mounts.push({
hostPath: `<host>/${item}`,
containerPath: itemPath,
type: 'bind',
readOnly: false,
verifiedMount: false,
description: `Candidate folder under /home: ${item}`
});
}
}
}
catch (itemError) {
// Skip items we can't stat
}
}
}
}
catch (error) {
// /home directory doesn't exist or not accessible
}
return mounts;
}
/**
* Get container environment information
*/
function getContainerEnvironment(containerType) {
const env = {};
// Try to get container name from hostname (often set to container ID/name)
try {
const hostname = os.hostname();
if (hostname && hostname !== 'localhost') {
env.containerName = hostname;
}
}
catch (error) {
// Hostname not available
}
// Docker-specific environment
if (containerType === 'docker') {
// Try multiple sources for Docker image name
if (process.env.DOCKER_IMAGE) {
env.dockerImage = process.env.DOCKER_IMAGE;
}
else if (process.env.IMAGE_NAME) {
env.dockerImage = process.env.IMAGE_NAME;
}
else if (process.env.CONTAINER_IMAGE) {
env.dockerImage = process.env.CONTAINER_IMAGE;
}
// Try to read from Docker labels if available (less common but possible)
try {
if (fs.existsSync('/proc/self/cgroup')) {
const cgroup = fs.readFileSync('/proc/self/cgroup', 'utf8');
// Extract container ID from cgroup path
const containerIdMatch = cgroup.match(/docker\/([a-f0-9]{64})/);
if (containerIdMatch && !env.containerName) {
// Use short container ID as fallback name
env.containerName = containerIdMatch[1].substring(0, 12);
}
}
}
catch (error) {
// Ignore errors reading cgroup
}
}
// Kubernetes-specific environment
if (containerType === 'kubernetes') {
if (process.env.KUBERNETES_NAMESPACE || process.env.POD_NAMESPACE) {
env.kubernetesNamespace = process.env.KUBERNETES_NAMESPACE || process.env.POD_NAMESPACE;
}
if (process.env.POD_NAME || process.env.HOSTNAME) {
env.kubernetesPod = process.env.POD_NAME || process.env.HOSTNAME;
}
if (process.env.NODE_NAME || process.env.KUBERNETES_NODE_NAME) {
env.kubernetesNode = process.env.NODE_NAME || process.env.KUBERNETES_NODE_NAME;
}
// Try to get container image from common Kubernetes environment variables
if (process.env.CONTAINER_IMAGE) {
env.dockerImage = process.env.CONTAINER_IMAGE;
}
else if (process.env.IMAGE_NAME) {
env.dockerImage = process.env.IMAGE_NAME;
}
// Try to read Kubernetes service account info
try {
if (fs.existsSync('/var/run/secrets/kubernetes.io/serviceaccount/namespace')) {
const namespace = fs.readFileSync('/var/run/secrets/kubernetes.io/serviceaccount/namespace', 'utf8').trim();
if (namespace && !env.kubernetesNamespace) {
env.kubernetesNamespace = namespace;
}
}
}
catch (error) {
// Service account info not available
}
}
// Podman-specific environment
if (containerType === 'podman') {
// Podman uses similar environment variables to Docker
if (process.env.CONTAINER_IMAGE || process.env.PODMAN_IMAGE) {
env.dockerImage = process.env.CONTAINER_IMAGE || process.env.PODMAN_IMAGE;
}
}
// LXC-specific environment
if (containerType === 'lxc') {
// LXC containers might have different naming conventions
if (process.env.LXC_NAME) {
env.containerName = process.env.LXC_NAME;
}
}
// Try to detect host platform
if (process.env.HOST_PLATFORM) {
env.hostPlatform = process.env.HOST_PLATFORM;
}
return Object.keys(env).length > 0 ? env : undefined;
}
/**
* Detect Node.js installation and version from current process
*/
function detectNodeInfo() {
try {
// Get Node.js version from current process
const version = process.version.replace('v', ''); // Remove 'v' prefix
// Get Node.js executable path from current process
const path = process.execPath;
// Get npm version from environment if available
const npmVersion = process.env.npm_version;
return {
version,
path,
...(npmVersion && { npmVersion })
};
}
catch (error) {
return undefined;
}
}
/**
* Detect Python installation and version and put on systeminfo.pythonInfo
*/
function detectPythonInfo() {
// Try python commands in order of preference
const pythonCommands = process.platform === 'win32'
? ['python', 'python3', 'py'] // Windows: 'python' is common, 'py' launcher
: ['python3', 'python']; // Unix: prefer python3
for (const cmd of pythonCommands) {
// The python on PATH, never one in the working folder (which Windows
// searches first for a bare name); not on PATH means not available
const program = resolveShellPath(cmd);
if (!program)
continue;
try {
const version = execFileSync(program, ['--version'], {
encoding: 'utf8',
timeout: 5000,
stdio: ['pipe', 'pipe', 'pipe']
}).trim();
// Verify it's Python 3.x
if (version.includes('Python 3')) {
return {
available: true,
command: cmd,
version: version.replace('Python ', '')
};
}
}
catch {
// Command not found or failed, try next
}
}
return { available: false, command: '' };
}
/**
* Get comprehensive system information for tool prompts
*/
export function getSystemInfo() {
const platform = os.platform();
const isWindows = platform === 'win32';
const isMacOS = platform === 'darwin';
const isLinux = platform === 'linux';
// Container detection
const containerDetection = detectContainerEnvironment();
const mountPoints = containerDetection.isContainer ? discoverContainerMounts(containerDetection.isContainer) : [];
let platformName;
const defaultShell = getDefaultShell();
let pathSeparator;
let examplePaths;
if (isWindows) {
platformName = 'Windows';
pathSeparator = '\\';
examplePaths = {
home: 'C:\\Users\\username',
temp: 'C:\\Temp',
absolute: 'C:\\path\\to\\file.txt'
};
}
else if (isMacOS) {
platformName = 'macOS';
pathSeparator = '/';
examplePaths = {
home: '/Users/username',
temp: '/tmp',
absolute: '/path/to/file.txt'
};
}
else if (isLinux) {
platformName = 'Linux';
pathSeparator = '/';
examplePaths = {
home: '/home/username',
temp: '/tmp',
absolute: '/path/to/file.txt'
};
}
else {
// Fallback for other Unix-like systems
platformName = 'Unix';
pathSeparator = '/';
examplePaths = {
home: '/home/username',
temp: '/tmp',
absolute: '/path/to/file.txt'
};
}
// Adjust platform name for containers
if (containerDetection.isContainer) {
let containerLabel = '';
if (containerDetection.containerType === 'kubernetes') {
containerLabel = 'Kubernetes';
if (containerDetection.orchestrator === 'kubernetes') {
containerLabel += ' Pod';
}
}
else if (containerDetection.containerType === 'docker') {
containerLabel = 'Docker';
if (containerDetection.orchestrator === 'docker-compose') {
containerLabel += ' Compose';
}
else if (containerDetection.orchestrator === 'docker-swarm') {
containerLabel += ' Swarm';
}
}
else if (containerDetection.containerType === 'podman') {
containerLabel = 'Podman';
}
else if (containerDetection.containerType === 'lxc') {
containerLabel = 'LXC';
}
else if (containerDetection.containerType === 'systemd-nspawn') {
containerLabel = 'systemd-nspawn';
}
else {
containerLabel = 'Container';
}
platformName = `${platformName} (${containerLabel})`;
// Add accessible paths from mounts
if (mountPoints.length > 0) {
examplePaths.accessible = mountPoints.map(mount => mount.containerPath);
}
}
// Detect Node.js installation from current process
const nodeInfo = detectNodeInfo();
// Detect Python installation
const pythonInfo = detectPythonInfo();
// Get process information
const processInfo = {
pid: process.pid,
arch: process.arch,
platform: process.platform,
versions: process.versions
};
return {
platform,
platformName,
defaultShell,
pathSeparator,
isWindows,
isMacOS,
isLinux,
docker: {
// New container detection fields
isContainer: containerDetection.isContainer,
containerType: containerDetection.containerType,
orchestrator: containerDetection.orchestrator,
// Backward compatibility - keep old field
isDocker: containerDetection.isContainer && containerDetection.containerType === 'docker',
mountPoints,
containerEnvironment: getContainerEnvironment(containerDetection.containerType)
},
isDXT: !!process.env.MCP_DXT,
nodeInfo,
pythonInfo,
processInfo,
examplePaths
};
}
/**
* Generate OS-specific guidance for tool prompts
*/
export function getOSSpecificGuidance(systemInfo) {
const { platformName, defaultShell, isWindows, docker } = systemInfo;
let guidance = `Running on ${platformName}. Default shell: ${defaultShell}.`;
// Container-specific guidance
if (docker.isContainer) {
const containerTypeLabel = docker.containerType === 'kubernetes' ? 'KUBERNETES POD' :
docker.containerType === 'docker' ? 'DOCKER CONTAINER' :
docker.containerType === 'podman' ? 'PODMAN CONTAINER' :
docker.containerType === 'lxc' ? 'LXC CONTAINER' :
docker.containerType === 'systemd-nspawn' ? 'SYSTEMD-NSPAWN CONTAINER' :
'CONTAINER';
guidance += `
🐳 ${containerTypeLabel} ENVIRONMENT DETECTED:`;
if (docker.containerType === 'kubernetes') {
guidance += `
This Desktop Commander instance is running inside a Kubernetes pod.`;
// Add Kubernetes-specific info
if (docker.containerEnvironment?.kubernetesNamespace) {
guidance += `
Namespace: ${docker.containerEnvironment.kubernetesNamespace}`;
}
if (docker.containerEnvironment?.kubernetesPod) {
guidance += `
Pod: ${docker.containerEnvironment.kubernetesPod}`;
}
if (docker.containerEnvironment?.kubernetesNode) {
guidance += `
Node: ${docker.containerEnvironment.kubernetesNode}`;
}
}
else if (docker.containerType === 'docker') {
guidance += `
This Desktop Commander instance is running inside a Docker container.`;
if (docker.orchestrator === 'docker-compose') {
guidance += ` (Docker Compose)`;
}
else if (docker.orchestrator === 'docker-swarm') {
guidance += ` (Docker Swarm)`;
}
}
else {
guidance += `
This Desktop Commander instance is running inside a ${docker.containerType || 'container'} environment.`;
}
if (docker.mountPoints.length > 0) {
guidance += `
AVAILABLE MOUNT LOCATIONS:`;
for (const mount of docker.mountPoints) {
const access = mount.readOnly ? '(read-only)' : '(read-write)';
const verification = mount.verifiedMount ? '[verified mount]' : '[unverified candidate]';
guidance += `
- ${mount.containerPath} ${access} ${verification} - ${mount.description}`;
}
guidance += `
Entries marked [verified mount] are suitable for persistent file work.
Entries marked [unverified candidate] are inferred from common container paths and may not persist.
Files outside verified mounts may be lost when the container stops.
PATH TRANSLATION IN DOCKER:
Host paths may require translation to their corresponding container paths:
Windows: "C:\\projects\\data\\file.txt" → "/home/projects/data/file.txt"
Linux/Mac: "/Users/john/projects/data/file.txt" → "/home/projects/data/file.txt"
Typical translation removes the drive letter or user prefix, keeps the full folder structure, and maps it under /home/.
NOTE: Desktop Commander Docker installer mounts host folders to /home/[folder-name].`;
}
else {
guidance += `
⚠️ WARNING: No mounted directories detected.
Files created outside mounted volumes will be lost when the container stops.
Persistent file work requires a mounted directory, available through the Docker installer or a -v mount.
Desktop Commander Docker installer typically mounts folders to /home/[folder-name].`;
}
if (docker.containerEnvironment?.containerName) {
guidance += `
Container: ${docker.containerEnvironment.containerName}`;
}
}
if (isWindows) {
guidance += `
WINDOWS-SPECIFIC TROUBLESHOOTING:
- If Node.js/Python commands fail with "not recognized" errors:
* Alternative shells include "cmd" and "powershell.exe"
* PowerShell may have execution policy restrictions for some tools
* CMD typically has better compatibility with development tools
* defaultShell can be changed with set_config_value if needed
- Windows services and processes use different commands (Get-Process vs ps)
- Package managers: choco, winget, scoop instead of apt/brew
- Environment variables: $env:VAR instead of $VAR
- File permissions work differently than Unix systems`;
}
else if (systemInfo.isMacOS) {
guidance += `
MACOS-SPECIFIC NOTES:
- Package manager: brew (Homebrew) is commonly used
- Python 3 might be 'python3' command, not 'python'
- Some GNU tools have different names (e.g., gsed instead of sed)
- System Integrity Protection (SIP) may block certain operations
- The 'open' command opens files and applications from the terminal
- mdfind (Spotlight) provides fast exact filename searches`;
}
else {
guidance += `
LINUX-SPECIFIC NOTES:
- Package managers vary by distro: apt, yum, dnf, pacman, zypper
- Python 3 might be 'python3' command, not 'python'
- Standard Unix shell tools available (grep, awk, sed, etc.)
- File permissions and ownership important for many operations
- Systemd services common on modern distributions`;
}
return guidance;
}
/**
* Get common development tool guidance based on OS
*/
export function getDevelopmentToolGuidance(systemInfo) {
const { isWindows, isMacOS, isLinux, platformName, nodeInfo, processInfo } = systemInfo;
// Add detected Node.js info to guidance
const nodeGuidance = nodeInfo
? `Node.js: v${nodeInfo.version} (${nodeInfo.path})${nodeInfo.npmVersion ? ` | npm: v${nodeInfo.npmVersion}` : ''}`
: 'Node.js: Not detected';
// Add process environment info
const envInfo = `
Current Process Environment:
- Node: v${processInfo.versions.node}
- V8: v${processInfo.versions.v8}
- Architecture: ${processInfo.arch}
- Platform: ${processInfo.platform}
- Process ID: ${processInfo.pid}`;
if (isWindows) {
return `
COMMON WINDOWS DEVELOPMENT TOOLS:
- ${nodeGuidance}
- Python: May be 'python' or 'py' command, check both
- Git: Git Bash provides Unix-like environment
- WSL: Windows Subsystem for Linux available for Unix tools
- Visual Studio tools: cl, msbuild for C++ compilation
${envInfo}`;
}
else if (isMacOS) {
return `
COMMON MACOS DEVELOPMENT TOOLS:
- Xcode Command Line Tools: Required for many development tools
- Homebrew: Primary package manager for development tools
- ${nodeGuidance}
- Python: Usually python3, check if python points to Python 2
- Ruby: System Ruby available, rbenv/rvm for version management
${envInfo}`;
}
else {
return `
COMMON LINUX DEVELOPMENT TOOLS:
- Package managers: Install tools via distribution package manager
- Python: Usually python3, python may point to Python 2
- ${nodeGuidance}
- Build tools: gcc, make typically available or easily installed
- Container tools: docker, podman common for development
${envInfo}`;
}
}
/**
* Get path guidance (simplified since paths are normalized)
*/
export function getPathGuidance(systemInfo) {
let guidance = `Absolute paths are the most reliable because they do not depend on the current working directory. Paths are automatically normalized regardless of slash direction.`;
if (systemInfo.docker.isContainer && systemInfo.docker.mountPoints.length > 0) {
const containerLabel = systemInfo.docker.containerType === 'kubernetes' ? 'KUBERNETES' :
systemInfo.docker.containerType === 'docker' ? 'DOCKER' :
systemInfo.docker.containerType === 'podman' ? 'PODMAN' :
'CONTAINER';
const verifiedMounts = systemInfo.docker.mountPoints.filter(m => m.verifiedMount).map(m => m.containerPath);
const unverifiedCandidates = systemInfo.docker.mountPoints.filter(m => !m.verifiedMount).map(m => m.containerPath);
if (verifiedMounts.length > 0) {
guidance += `
🐳 ${containerLabel}: Verified mounts for persistent file operations: ${verifiedMounts.join(', ')}.
Files outside verified mounts may not persist after the container stops.`;
}
if (unverifiedCandidates.length > 0) {
guidance += `
🐳 ${containerLabel}: Unverified mount candidates: ${unverifiedCandidates.join(', ')}. These paths are inferred from common container directories and may not persist.`;
}
}
return guidance;
}