Skip to main content
Glama
jmagar

Homelab MCP Server

by jmagar

Synapse MCP

MCP (Model Context Protocol) server providing Flux (Docker management) and Scout (SSH operations) tools for homelab infrastructure. The neural connection point for your distributed systems.

Designed for use with Claude Code and other MCP-compatible clients.

Documentation

Document

Description

README.md

This file — setup, configuration, usage, reference

ARCHITECTURE.md

Internal architecture, DI system, file structure, design decisions

TRANSPORTS.md

Transport modes (local stdio, remote stdio-over-SSH)

SECURITY.md

Security policy, known issues, best practices

DESTRUCTIVE-ACTIONS.md

Catalog of all destructive operations, risk levels, safety gates

NON-DESTRUCTIVE-ACTIONS.md

Full reference of all read-only and reversible operations

CHANNEL.md

Real-time infrastructure event channel (Docker events, log tailing)

RESOURCES.md

MCP read-only resources exposed at synapse:// URIs

STYLE.md

Output formatting and style guide for tool responses

HANDLERS.md

Handler development patterns (for contributors)

ERROR-HANDLING

Error handling guide and custom error classes

CHANGELOG.md

Version history

Installation

# Add the synapse marketplace
/plugin marketplace add jmagar/synapse-mcp

# Install the synapse-mcp plugin
/plugin install synapse-mcp@synapse

What you get:

  • /flux and /scout commands

  • ✅ Auto-configured MCP server

  • ✅ Complete documentation and examples

  • ✅ SSH host auto-discovery

Usage

# List Docker containers
/flux list containers

# Check SSH hosts
/scout list hosts

# Monitor system resources
/flux show resources

Direct MCP Server Setup

For non-Claude Code MCP clients, see Transport Quick Start below.

Transport Quick Start

Choose one:

  1. Local use: stdio (default)

  2. Secure remote with minimal setup: stdio over SSH

See docs/TRANSPORTS.md for exact setup and configs for all transport modes.

Features

Flux Tool (Docker Infrastructure Management)

  • Container lifecycle: Start, stop, restart, pause/resume, pull, recreate, exec

  • Docker Compose: Full project management (up, down, restart, logs, build, pull, recreate)

  • Image operations: List, pull, build, remove Docker images

  • Host operations: Status checks, resource monitoring, systemd services, network info

  • Log retrieval: Advanced filtering with time ranges, grep (safe patterns only), stream selection

  • Resource monitoring: Real-time CPU, memory, network, I/O statistics

  • Smart search: Find containers by name, image, or labels across all hosts

  • Pagination & filtering: All list operations support limits, offsets, and filtering

Scout Tool (SSH Remote Operations)

  • File operations: Read files, directory trees, file transfer (beam), diff comparison

  • Remote execution: Execute commands with allowlist security

  • Process monitoring: List and filter processes by user, CPU, memory

  • ZFS management: Pools, datasets, snapshots with health monitoring

  • System logs: Access syslog, journald, dmesg, auth logs with filtering (safe grep patterns only)

  • Disk monitoring: Filesystem usage across all mounts

  • Multi-host operations: Execute commands or read files across multiple hosts (emit)

Infrastructure

  • Multi-host support: Manage Docker and SSH across Unraid, Proxmox, bare metal

  • Auto-detect local Docker: Automatically adds local Docker socket if available

  • stdio transport: Direct integration with Claude Code and MCP-compatible clients

  • O(1) validation: Discriminated union pattern for instant schema validation

  • SSH connection pooling: 50× faster repeated operations

  • Safety gates: MCP elicitation confirmation for all 10 destructive operations — no silent data loss

Tools

The server provides two powerful tools with discriminated union schemas for O(1) validation:

Available Tools

flux

Docker infrastructure management - container, compose, docker, and host operations

scout

SSH remote operations - file, process, and system inspection

Getting Help

Both tools include auto-generated help:

{ "action": "help" }
{ "action": "help", "topic": "container:resume" }
{ "action": "help", "format": "json" }

Breaking change from V2: The unified tool has been completely removed and replaced with flux and scout.


Tool 1: flux - Docker Infrastructure Management

43 operations across 5 actions - Container lifecycle, compose orchestration, system management

FLUX OPERATIONS:

Container (14 operations)
  🔒  exec       - Execute command inside a container (safety-gated: prompts for confirmation)
  ●  inspect    - Get detailed container information
  ●  list       - List containers with optional filtering
  ●  logs       - Get container logs with optional filtering
  ●  pause      - Pause a running container
  ●  pull       - Pull latest image for a container
  ⚠️🔒 recreate   - Recreate a container with optional image pull (safety-gated: prompts for confirmation)
  ●  restart    - Restart a container
  ●  resume     - Resume a paused container
  ●  search     - Search containers by query string
  ●  start      - Start a stopped container
  ●  stats      - Get resource usage statistics
  🔒  stop       - Stop a running container (safety-gated: prompts for confirmation)
  ●  top        - Show running processes in a container

Compose (10 operations)
  ●  build      - Build Docker Compose project images
  🔒  down       - Stop a Docker Compose project (safety-gated: prompts for confirmation)
  ●  list       - List all Docker Compose projects
  ●  logs       - Get Docker Compose project logs
  ●  pull       - Pull Docker Compose project images
  ⚠️🔒 recreate   - Recreate Docker Compose project containers (safety-gated: prompts for confirmation)
  ●  refresh    - Refresh compose project cache by scanning filesystem
  ●  restart    - Restart a Docker Compose project
  ●  status     - Get Docker Compose project status
  ●  up         - Start a Docker Compose project

Docker (9 operations)
  ●  build      - Build a Docker image
  ●  df         - Get Docker disk usage information
  ●  images     - List Docker images
  ●  info       - Get Docker daemon information
  ●  networks   - List Docker networks
  ⚠️🔒 prune      - Remove unused Docker resources (safety-gated: prompts for confirmation)
  ●  pull       - Pull a Docker image
  ⚠️🔒 rmi        - Remove a Docker image (safety-gated: prompts for confirmation)
  ●  volumes    - List Docker volumes

Host (7 operations)
  ✓  status     - Check Docker connectivity to host
  ●  resources  - Get CPU, memory, and disk usage via SSH
  ●  info       - Get OS, kernel, architecture, and hostname information
  ●  uptime     - Get system uptime
  ●  services   - Get systemd service status
  ●  network    - Get network interfaces and IP addresses
  ●  mounts     - Get mounted filesystems

Tool 2: scout - SSH Remote Operations

16 operations across 11 actions - File operations, process inspection, system logs

SCOUT OPERATIONS:

Simple Actions (9 operations)
  🔒  beam       - File transfer between remote host paths (safety-gated: prompts for confirmation)
  ●  delta      - Compare files or content between locations
  ●  df         - Disk usage information for a remote host
  🔒  emit       - Multi-host operations (safety-gated: prompts for confirmation)
  🔒  exec       - Execute command on a remote host (safety-gated: prompts for confirmation)
  ●  find       - Find files by glob pattern on a remote host
  ●  nodes      - List all configured SSH hosts
  ●  peek       - Read file or directory contents on a remote host
  ●  ps         - List and search processes on a remote host

ZFS (3 operations)
  ●  pools      - List ZFS storage pools
  ●  datasets   - List ZFS datasets
  ●  snapshots  - List ZFS snapshots

Logs (4 operations)
  ●  syslog     - Access system log files (/var/log)
  ●  journal    - Access systemd journal logs
  ●  dmesg      - Access kernel ring buffer logs (requires root or CAP_SYSLOG)
  ●  auth       - Access authentication logs

Legend:

  • Non-destructive operation (read-only or reversible state change: start, restart, build, pull, etc.)

  • ⚠️ Destructive operation (requires force: true)

  • 🔒 Safety-gated operation (MCP elicitation prompt or SYNAPSE_ALLOW_DESTRUCTIVE_ACTIONS=true bypass)

  • ⚠️🔒 Destructive + safety-gated (requires both force: true and confirmation)

  • Diagnostic/health check

Simple Actions (9)

Action

Description

nodes

List all configured SSH hosts

peek

Read file or directory contents (with tree mode)

exec

Execute command on remote host (allowlist validated)

find

Find files by glob pattern

delta

Compare files or content between locations

emit

Multi-host operations (read files or execute commands)

beam

File transfer between remote host paths

ps

List and search processes with filtering

df

Disk usage information

ZFS Operations (action: "zfs") - 3 subactions

Subaction

Description

pools

List ZFS storage pools with health status

datasets

List ZFS datasets (filesystems and volumes)

snapshots

List ZFS snapshots

Log Operations (action: "logs") - 4 subactions

Subaction

Description

syslog

Access system log files (/var/log)

journal

Access systemd journal logs with unit filtering

dmesg

Access kernel ring buffer logs

auth

Access authentication logs


Compose Auto-Discovery

The MCP server automatically discovers and caches Docker Compose project locations, eliminating the need to specify file paths for every operation.

How It Works

The discovery system uses a multi-layer approach:

  1. Cache Check: Looks up project in local cache (.cache/compose-projects/)

  2. Docker List: Queries docker compose ls for running projects

  3. Filesystem Scan: Scans configured search paths for compose files

  4. Error: Returns error if project not found in any layer

Discovery results are cached for 24 hours (hardcoded TTL).

Configuration

Add optional composeSearchPaths to your host configuration:

{
  "hosts": [
    {
      "name": "my-host",
      "host": "192.168.1.100",
      "protocol": "ssh",
      "composeSearchPaths": ["/opt/stacks", "/srv/docker"]
    }
  ]
}

Default search paths: ["/compose", "/mnt/cache/compose", "/mnt/cache/code"] if not specified.

Optional Host Parameter

Most compose operations accept an optional host parameter. When omitted, the system automatically searches all configured hosts in parallel to find the project:

// Explicit host (faster - no search needed)
{ "action": "compose", "subaction": "up", "project": "plex", "host": "server1" }

// Auto-discover (searches all hosts in parallel)
{ "action": "compose", "subaction": "up", "project": "plex" }

Auto-discovery times out after 30 seconds if the project cannot be found on any host. If a project exists on multiple hosts, you'll receive an error asking you to specify the host parameter explicitly.

Cache Management

  • TTL: 24 hours (hardcoded)

  • Storage: .cache/compose-projects/ directory (gitignored)

  • Invalidation: Automatic when operations fail due to stale paths

  • Manual Refresh: Use compose:refresh subaction

Manual Cache Refresh

Force a cache refresh by scanning the filesystem:

// Refresh all hosts
{ "action": "compose", "subaction": "refresh" }

// Refresh specific host
{ "action": "compose", "subaction": "refresh", "host": "server1" }

Returns a list of discovered projects with their paths and discovery source (docker-ls or filesystem scan).

Architecture

┌─────────────┐
│   Handler   │
└──────┬──────┘
       │
       v
┌──────────────┐      ┌──────────────┐
│ HostResolver │─────>│  Discovery   │
└──────────────┘      └──────┬───────┘
                             │
                    ┌────────┴────────┐
                    v                 v
             ┌──────────┐      ┌──────────┐
             │  Cache   │      │ Scanner  │
             └──────────┘      └──────────┘

Components:

  • HostResolver: Finds which host contains the project (parallel search)

  • ComposeDiscovery: Orchestrates cache, docker-ls, and filesystem scanning

  • ComposeProjectCache: File-based cache with TTL validation

  • ComposeScanner: Filesystem scanning for compose files (respects max depth of 3)


Example Usage

Flux Tool Examples

// List running containers
{ "tool": "flux", "action": "container", "subaction": "list", "state": "running" }

// Restart a container
{ "tool": "flux", "action": "container", "subaction": "restart", "container_id": "plex", "host": "tootie" }

// Start a compose project (auto-discovers location and host)
{ "tool": "flux", "action": "compose", "subaction": "up", "project": "media-stack" }

// Start a compose project on specific host
{ "tool": "flux", "action": "compose", "subaction": "up", "host": "tootie", "project": "media-stack" }

// Refresh compose project cache
{ "tool": "flux", "action": "compose", "subaction": "refresh" }

// Get host resources
{ "tool": "flux", "action": "host", "subaction": "resources", "host": "tootie" }

// Pull an image
{ "tool": "flux", "action": "docker", "subaction": "pull", "host": "tootie", "image": "nginx:latest" }

// Execute command in container
{ "tool": "flux", "action": "container", "subaction": "exec", "container_id": "nginx", "command": "nginx -t" }

Scout Tool Examples

// List configured SSH hosts
{ "tool": "scout", "action": "nodes" }

// Read a remote file
{ "tool": "scout", "action": "peek", "target": "tootie:/etc/nginx/nginx.conf" }

// Show directory tree
{ "tool": "scout", "action": "peek", "target": "dookie:/var/log", "tree": true }

// Execute remote command
{ "tool": "scout", "action": "exec", "target": "tootie:/var/www", "command": "du -sh *" }

// Transfer file between hosts
{ "tool": "scout", "action": "beam", "source": "tootie:/tmp/backup.tar.gz", "destination": "dookie:/backup/" }

// Check ZFS pool health
{ "tool": "scout", "action": "zfs", "subaction": "pools", "host": "dookie" }

// View systemd journal
{ "tool": "scout", "action": "logs", "subaction": "journal", "host": "tootie", "unit": "docker.service" }

// Multi-host command execution
{ "tool": "scout", "action": "emit", "targets": ["tootie:/tmp", "dookie:/tmp"], "command": "df -h" }

Installation

# Clone or copy the server files
cd synapse-mcp

# Install dependencies
pnpm install

# Build
pnpm run build

The server will create a .cache/compose-projects/ directory for storing discovered project locations. This directory is automatically gitignored.

Configuration

SSH Config Auto-Loading

Zero configuration required! Synapse-MCP automatically discovers hosts from your ~/.ssh/config file.

All SSH hosts with a HostName directive are automatically available for Docker management via SSH tunneling to the remote Docker socket. Manual configuration is completely optional.

Priority order:

  1. Manual config file (highest) - synapse.config.json

  2. SYNAPSE_HOSTS_CONFIG environment variable

  3. SSH config auto-discovery - ~/.ssh/config

  4. Local Docker socket (fallback)

Example SSH config:

Host production
  HostName 192.168.1.100
  User admin
  Port 22
  IdentityFile ~/.ssh/id_ed25519

Host staging
  HostName 192.168.1.101
  User deploy
  Port 2222
  IdentityFile ~/.ssh/staging_key

Both hosts are immediately available as flux targets with SSH tunneling to /var/run/docker.sock. No additional configuration needed!

Manual override: If you create a synapse.config.json entry with the same name as an SSH host, the manual config completely replaces the SSH config (no merging).

Manual Configuration (Optional)

Create a config file at one of these locations (checked in order):

  1. Path in SYNAPSE_CONFIG_FILE env var

  2. ./synapse.config.json (current directory)

  3. ~/.config/synapse-mcp/config.json

  4. ~/.synapse-mcp.json

Example Config

{
  "hosts": [
    {
      "name": "local",
      "host": "localhost",
      "protocol": "ssh",
      "dockerSocketPath": "/var/run/docker.sock",
      "tags": ["development"]
    },
    {
      "name": "production",
      "host": "192.168.1.100",
      "port": 22,
      "protocol": "ssh",
      "sshUser": "admin",
      "sshKeyPath": "~/.ssh/id_rsa",
      "tags": ["production"]
    }
  ]
}

Copy config/synapse.config.example.json as a starting point:

cp config/synapse.config.example.json ~/.config/synapse-mcp/config.json
# or
cp config/synapse.config.example.json ~/.synapse-mcp.json

Note: If /var/run/docker.sock exists and isn't already in your config, it will be automatically added as a host using your machine's hostname. This means the server works out-of-the-box for local Docker without any configuration.

Host Configuration Options

Field

Type

Description

name

string

Unique identifier for the host

host

string

Hostname or IP address

port

number

SSH port (default: 22)

protocol

"ssh"

Connection protocol (SSH only — no TCP Docker port)

dockerSocketPath

string

Path to Docker socket (for local connections)

sshUser

string

SSH username for remote connections (protocol: "ssh")

sshKeyPath

string

Path to SSH private key for authentication

tags

string[]

Optional tags for filtering

Environment Variables Reference

Complete reference for all environment variables that control server behavior.

Server Configuration

Variable

Type

Default

Description

SYNAPSE_CONFIG_FILE

string

Auto-detect

Path to config file. Overrides default search paths.

SYNAPSE_HOSTS_CONFIG

JSON string

undefined

JSON config as environment variable. Fallback if no config file found.

SYNAPSE_EXCLUDE_HOSTS

string

undefined

Comma-separated list of SSH host names to exclude from auto-discovery.

SYNAPSE_DEFAULT_HOST

string

undefined

When set, all flux/scout calls without an explicit host parameter target this host name instead of requiring host to be specified. Must match a configured host name.

NODE_ENV

string

production

Node environment. Affects stack traces and error verbosity.

Performance Tuning

Variable

Type

Default

Description

DOCKER_SSH_CONNECT_TIMEOUT_MS

number

5000 (5s)

SSH tunnel timeout for Docker API connections. Increase for slow networks (10000-15000).

COMPOSE_HOST_RESOLUTION_TIMEOUT_MS

number

30000 (30s)

Timeout for auto-discovering which host owns a Compose project. Lower for faster failure.

COMPOSE_CONFLICT_GRACE_MS

number

200

Grace window when multiple hosts find the same project simultaneously.

Note: SSH connection pool settings (max connections, idle timeout, health check interval) and compose cache TTL are hardcoded defaults. They are not currently configurable via environment variables.

Safety Gate Options

Variable

Type

Default

Description

⚠️ Security Impact

SYNAPSE_ALLOW_DESTRUCTIVE_ACTIONS

boolean

false

Bypasses MCP elicitation confirmation for all 10 safety-gated destructive operations.

Removes the interactive confirmation gate. Use only in CI/automation with full trust.

SYNAPSE_ALLOW_ROOT_LOGIN

boolean

false

Bypasses MCP elicitation confirmation for SSH root login.

Allows root SSH without interactive prompt. Use only in automation with full trust.

Operator note — SYNAPSE_ALLOW_DESTRUCTIVE_ACTIONS=true

What it does: When set to true, all MCP elicitation confirmation prompts for safety-gated destructive operations are skipped entirely. Every destructive action (container stop/restart/remove, compose down/recreate, image removal, volume prune, exec, etc.) executes immediately without interactive confirmation.

When to use it: Headless and automated environments where no MCP client capable of displaying elicitation prompts is present (CI pipelines, scripted test runs, server-side automation). The E2E test suite (tests/test-mcporter.sh) requires this flag to exercise destructive actions.

Security implications: This flag has no granularity. It bypasses confirmation for all destructive actions across all configured hosts simultaneously. There is no way to enable it for a subset of hosts or a subset of actions. Any agent or pipeline with access to the MCP server can execute destructive operations without further prompting. Only set this in environments where you fully control who can call the server and trust every caller unconditionally.

Debug and Logging

Variable

Type

Default

Description

DEBUG

string

undefined

Enable debug logging. Set to synapse:* for all debug output or specific namespaces like synapse:ssh, synapse:docker.

LOG_LEVEL

string

info

Logging level: error, warn, info, debug, trace.

Example Configurations

Development (Local):

export NODE_ENV=development
export SYNAPSE_CONFIG_FILE=~/.config/synapse-mcp/config.json
export DEBUG=synapse:*
export LOG_LEVEL=debug
node dist/index.js

Production (Stdio Mode for Claude Code):

export NODE_ENV=production
export SYNAPSE_CONFIG_FILE=/etc/synapse-mcp/config.json
export LOG_LEVEL=warn
node dist/index.js

High-Latency Network:

export DOCKER_SSH_CONNECT_TIMEOUT_MS=15000  # 15s Docker SSH timeout
node dist/index.js

Local vs Remote Execution

The server automatically determines whether to use local execution or SSH based on your host configuration:

Local Execution (No SSH)

Commands run directly on localhost using Node.js for best performance:

{
  "name": "local",
  "host": "localhost",
  "protocol": "ssh",
  "dockerSocketPath": "/var/run/docker.sock"
}

Requirements: Host must be localhost/127.x.x.x/::1 AND no sshUser specified.

Benefits:

  • ~10x faster than SSH for Compose and host operations

  • No SSH key management needed

  • Works out of the box

Remote Execution (SSH)

Commands run via SSH on remote hosts or when sshUser is specified:

{
  "name": "production",
  "host": "192.168.1.100",
  "protocol": "ssh",
  "sshUser": "admin",
  "sshKeyPath": "~/.ssh/id_rsa"
}

When SSH is used:

  • Host is NOT localhost/127.x.x.x

  • sshUser is specified (even for localhost)

  • For all Scout operations (file operations always use SSH)

Docker API vs Command Execution

These are independent:

Operation

Local Host

Remote Host

Docker API (container list, stats)

Unix socket

SSH tunnel

Commands (compose, systemctl)

Local execFile

SSH

See docs/ARCHITECTURE.md for detailed architecture documentation.

Resource Limits & Defaults

Setting

Value

Description

CHARACTER_LIMIT

40,000

Maximum response size (~12.5k tokens)

DEFAULT_LIMIT

20

Default pagination limit for list operations

MAX_LIMIT

100

Maximum pagination limit

DEFAULT_LOG_LINES

50

Default number of log lines to fetch

MAX_LOG_LINES

500

Maximum log lines allowed

API_TIMEOUT

30s

Docker API operation timeout

STATS_TIMEOUT

5s

Stats collection timeout

Performance Characteristics

Understanding performance expectations helps optimize your usage and troubleshoot slow operations.

Response Time Expectations

Operation Type

Expected Latency

Notes

Single-host operations

50-150ms

Container list, stats, logs, inspect

Multi-host container discovery

100-500ms

Depends on host count and network latency

Compose auto-discovery

1-500ms

Cache hit: 1ms, docker-ls: 50-100ms, filesystem scan: 200-500ms

SSH connection (warm)

<10ms

Connection pool hit

SSH connection (cold)

200-300ms

New connection establishment

Container exec

100ms-30s

Depends on command execution time

Configuration Loading

  • Config files: Loaded at server startup (synchronous read)

  • Config changes: Require server restart (no hot reload)

  • SSH config: Changes detected automatically on next operation

  • Cache: Compose project cache has a hardcoded 24-hour TTL

Buffer and Output Limits

Resource

Limit

Behavior on Exceed

Response character limit

40,000 chars (~12.5k tokens)

Truncated with warning

Container exec output

10MB per stream (stdout/stderr)

Stream terminated with error

Log lines

50 default, 500 maximum

Paginate with lines parameter

Find results

100 default, 1000 maximum

Paginate with limit parameter

Connection Pooling

Setting

Default

Notes

SSH connections per host

5

Hardcoded default (not configurable via env)

Idle timeout

60 seconds

Hardcoded default (not configurable via env)

Docker SSH timeout

5 seconds

DOCKER_SSH_CONNECT_TIMEOUT_MS

Health check interval

30 seconds

Hardcoded default (not configurable via env)

Performance Impact:

  • Warm connections: 20-30× faster than establishing new connections

  • Pool exhaustion: Operations queue until connection available

  • Health checks: Detect and remove stale connections automatically

Compose Discovery Cache

Three-tier strategy:

  1. Cache check (fastest, 0-1ms) - .cache/compose-projects/

  2. docker compose ls (medium, 50-100ms) - running projects only

  3. Filesystem scan (slowest, 200-500ms) - all projects

Cache behavior:

  • TTL: 24 hours (hardcoded; not configurable via env)

  • Invalidation: Automatic on stale path detection

  • Storage: Local filesystem (.cache/compose-projects/)

  • Refresh: Manual via compose:refresh or automatic on cache miss

Scaling Characteristics

Host Count Impact:

  • 1-5 hosts: Optimal performance, minimal latency

  • 6-10 hosts: Good performance, consider explicit host parameter for frequent operations

  • 11-15 hosts: Increased latency, recommend explicit host for all operations

  • 16+ hosts: Consider splitting into multiple MCP server instances

Container Count Impact:

  • 1-50 containers: No impact, all operations fast

  • 51-100 containers: Pagination recommended for list operations

  • 101-500 containers: Always paginate, avoid state: "all" without filters

  • 500+ containers: Use host-specific operations, always specify host explicitly

Network Latency Impact:

  • Low latency (<10ms): Minimal impact on multi-host operations

  • Medium latency (10-50ms): 2-3× slower for multi-host discovery

  • High latency (>50ms): Explicitly specify host parameter to avoid discovery overhead

Tuning for Large Deployments

If managing 15+ hosts with 100+ containers:

# Increase Docker SSH connection timeout for slow networks
export DOCKER_SSH_CONNECT_TIMEOUT_MS=10000

Operational strategies:

  • Always specify host: Avoid auto-discovery overhead for known locations

  • Use pagination: Set limit: 20 for list operations

  • Batch operations: Group related operations to reuse warm connections

  • Split by environment: Run separate MCP instances for dev/staging/prod hosts

Performance Monitoring

Monitor response times:

# Watch logs for slow operations
journalctl -u synapse-mcp.service | grep -E "took [0-9]{3,}ms"

# Check connection pool utilization
# (Low availability = need more connections)

Enabling Docker API on Hosts

synapse-mcp connects to Docker exclusively over SSH — there is no need to expose the Docker API on any TCP port. All Docker management traffic tunnels securely over the existing SSH connection.

Requirements on managed hosts:

  • SSH daemon running and accessible

  • The SSH user must be in the docker group (or have access to /var/run/docker.sock)

  • Docker must be installed and the daemon running

# Add SSH user to docker group on managed host
sudo usermod -aG docker <your-ssh-user>

⚠️ Do NOT expose Docker on TCP ports (2375/2376) — synapse-mcp does not use them, and doing so creates unnecessary attack surface.

Usage

With Claude Code

Add to ~/.claude.json:

{
  "mcpServers": {
    "synapse": {
      "command": "node",
      "args": ["/absolute/path/to/synapse-mcp/dist/index.js"],
      "env": {
        "SYNAPSE_CONFIG_FILE": "/home/youruser/.config/synapse-mcp/config.json"
      }
    }
  }
}

Or if your config is in one of the default locations, you can skip the env entirely:

{
  "mcpServers": {
    "synapse": {
      "command": "node",
      "args": ["/absolute/path/to/synapse-mcp/dist/index.js"]
    }
  }
}

Then in Claude Code:

> List all running containers on tootie (uses flux tool)
> Restart the plex container (uses flux tool)
> Show me the logs from sonarr with errors in the last hour (uses flux tool)
> Which containers are using the most memory? (uses flux tool)
> Read the nginx config on tootie (uses scout tool)
> Check ZFS pool health on dookie (uses scout tool)
> Show me systemd journal errors from the last hour (uses scout tool)

CLI Help

node dist/index.js --help

Example Interactions

Flux Tool - Container Management

User: What containers are running on tootie?

Claude: [calls flux with action="container", subaction="list", host="tootie", state="running"]

I found 23 running containers on tootie:

🟢 plex (tootie) - Image: linuxserver/plex | Up 3 days
🟢 sonarr (tootie) - Image: linuxserver/sonarr | Up 3 days
🟢 radarr (tootie) - Image: linuxserver/radarr | Up 3 days
...

Flux Tool - Log Analysis

User: Show me any errors from nginx in the last hour

Claude: [calls flux with action="container", subaction="logs",
        container_id="nginx", since="1h", grep="error"]

Found 3 error entries in nginx logs:
[14:23:15] 2024/12/15 14:23:15 [error] connect() failed...

Scout Tool - Remote File Access

User: Read the nginx config on tootie

Claude: [calls scout with action="peek", target="tootie:/etc/nginx/nginx.conf"]

Here's the nginx configuration from tootie:

user nginx;
worker_processes auto;
...

Scout Tool - ZFS Health Check

User: Check ZFS pool health on dookie

Claude: [calls scout with action="zfs", subaction="pools", host="dookie"]

ZFS Pools on dookie:

tank - ONLINE | Size: 24TB | Free: 8.2TB | Health: 100%
backup - ONLINE | Size: 12TB | Free: 5.1TB | Health: 100%

Scout Tool - System Logs

User: Show me Docker service errors from systemd journal

Claude: [calls scout with action="logs", subaction="journal",
        host="tootie", unit="docker.service", priority="err"]

Recent errors from docker.service:

[15:42:10] Failed to allocate directory watch: Too many open files
[15:42:15] containerd: connection error: desc = "transport: error while dialing"

Troubleshooting

Common issues and their solutions.

Service Won't Start

Stdio Mode Issues

Symptom: Server starts but MCP client cannot connect.

Cause: stdout/stdin are not available or the process was launched directly in a terminal.

Solution:

# Check if running in terminal (stdio requires parent process)
# Don't run stdio mode directly in terminal - use via MCP client only

Missing Dependencies

Symptom:

Error: Cannot find module '@modelcontextprotocol/sdk'

Cause: Dependencies not installed or node_modules corrupted.

Solution:

# Reinstall dependencies
rm -rf node_modules pnpm-lock.yaml
pnpm install

# Rebuild
pnpm run build

# Verify installation
pnpm list @modelcontextprotocol/sdk

Permission Denied on Startup

Symptom:

Error: EACCES: permission denied, open '/var/run/docker.sock'

Cause: User not in docker group.

Solution:

# Add user to docker group
sudo usermod -aG docker $USER

# Log out and back in for group change to take effect
# Or use newgrp to activate immediately
newgrp docker

# Verify docker access
docker ps

SSH Connection Failures

Host Key Verification Failed

Symptom:

[SSH] [Host: production] Permission denied (publickey)
# or
Host key verification failed

Cause: SSH key not in ~/.ssh/known_hosts or key mismatch.

Solution:

Option 1: Pre-seed known_hosts (Recommended)

# Add host key to known_hosts
ssh-keyscan -H hostname >> ~/.ssh/known_hosts

# For all configured hosts
for host in production staging dev; do
  ssh-keyscan -H $host >> ~/.ssh/known_hosts
done

Option 2: Manual verification

# Connect manually first to accept key
ssh user@hostname

# Verify fingerprint matches (check console/IPMI)
ssh-keygen -l -f ~/.ssh/known_hosts | grep hostname

Option 3: Remove stale key (if host key changed)

# Remove old key
ssh-keygen -R hostname

# Re-add current key
ssh-keyscan -H hostname >> ~/.ssh/known_hosts

SSH Key Permission Errors

Symptom:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/home/user/.ssh/id_rsa' are too open.

Cause: SSH private key has insecure permissions.

Solution:

# Fix key permissions (required: 600)
chmod 600 ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_ed25519

# Fix directory permissions
chmod 700 ~/.ssh

# Verify
ls -la ~/.ssh/
# Should show: -rw------- for keys

Connection Timeout

Symptom:

[SSH] [Host: production] SSH command timeout after 5000ms

Cause: Network latency, firewall blocking, or host unreachable.

Solution:

Increase timeout:

# Set longer Docker SSH connection timeout (15 seconds)
export DOCKER_SSH_CONNECT_TIMEOUT_MS=15000
node dist/index.js

Check network connectivity:

# Test SSH access manually
ssh -v user@hostname

# Check network latency
ping hostname

# Check firewall rules
sudo ufw status
# or
sudo iptables -L

Verify host is reachable:

# Test basic connectivity
nc -zv hostname 22

# Check if SSH daemon is running
ssh user@hostname 'systemctl status sshd'

SSH Agent Not Running

Symptom:

Could not open a connection to your authentication agent

Cause: SSH agent not started or key not added.

Solution:

# Start SSH agent
eval $(ssh-agent)

# Add key to agent
ssh-add ~/.ssh/id_rsa

# Verify key is loaded
ssh-add -l

# Add to shell startup (~/.bashrc or ~/.zshrc)
if [ -z "$SSH_AUTH_SOCK" ]; then
  eval $(ssh-agent)
  ssh-add ~/.ssh/id_rsa
fi

Docker API Connection Errors

Socket Permission Denied

Symptom:

Error: connect EACCES /var/run/docker.sock

Cause: User not in docker group or socket permissions incorrect.

Solution:

Add user to docker group:

# Add current user
sudo usermod -aG docker $USER

# Log out and back in
# Verify group membership
groups | grep docker

# Test docker access
docker ps

Check socket permissions:

# Socket should be owned by docker group
ls -la /var/run/docker.sock
# Should show: srw-rw---- root docker

# If permissions wrong, fix ownership
sudo chown root:docker /var/run/docker.sock
sudo chmod 660 /var/run/docker.sock

Connection Refused

Symptom:

Error: connect ECONNREFUSED 192.168.1.100:22

Cause: SSH daemon not running, wrong SSH port, or firewall blocking SSH.

Note: synapse-mcp uses SSH-only transport. Do NOT expose Docker on TCP port 2375 — all Docker API traffic tunnels over SSH.

Solution:

Check Docker daemon status (on target host):

systemctl status docker

# Start if not running
sudo systemctl start docker
sudo systemctl enable docker

Verify SSH connectivity:

# Test SSH connection from synapse-mcp host
ssh -i ~/.ssh/your_key user@192.168.1.100 docker info

# Check SSH daemon is running on target
systemctl status ssh || systemctl status sshd

Check firewall (SSH port only):

# Allow SSH from synapse-mcp server to managed hosts
sudo ufw allow from 192.168.1.10 to any port 22

# Block raw Docker API ports as general hardening (synapse-mcp does not use these)
sudo ufw deny 2375/tcp
sudo ufw deny 2376/tcp

Docker Daemon Not Ready

Symptom:

Cannot connect to the Docker daemon. Is the docker daemon running?

Cause: Docker service not started or crashed.

Solution:

# Check status
systemctl status docker

# View logs
journalctl -u docker.service -n 50

# Restart daemon
sudo systemctl restart docker

# Check for errors
docker info

High Latency Issues

Slow Container Discovery

Symptom: Container operations taking 5-30 seconds across multiple hosts.

Cause: Sequential host scanning without explicit host parameter.

Solution:

Always specify host when known:

// Instead of:
{ "action": "container", "subaction": "start", "container_id": "plex" }

// Use:
{ "action": "container", "subaction": "start", "container_id": "plex", "host": "production" }

Reduce host count:

# Split large deployments into multiple MCP instances
# Production hosts: synapse-mcp-prod
# Development hosts: synapse-mcp-dev

Increase Docker SSH timeout:

# SSH pool size is hardcoded — increase Docker SSH connection timeout instead
export DOCKER_SSH_CONNECT_TIMEOUT_MS=10000
node dist/index.js

Slow Configuration Loading

Symptom: Every request takes 5-10ms longer than expected.

Cause: Config loaded synchronously on every request (PERF-C1).

Solution:

Optimize config file size:

# Keep config under 10KB
# Split large host lists into multiple files

# Use SSH config auto-discovery instead
# (parsed once at startup)

Use SSH config auto-discovery:

# ~/.ssh/config
Host production
  HostName 192.168.1.100
  User admin
  IdentityFile ~/.ssh/id_rsa

# No manual synapse.config.json needed

Network Latency

Symptom: Operations on remote hosts much slower than local.

Cause: High network latency (>50ms).

Solution:

Increase timeouts for slow networks:

# Increase Docker SSH connection timeout (the only configurable timeout)
export DOCKER_SSH_CONNECT_TIMEOUT_MS=15000  # 15s
# Note: SSH pool idle timeout and pool size are hardcoded and cannot be configured via env vars

Use local cache more aggressively:

# Note: Compose cache TTL is hardcoded to 24 hours and cannot be configured via env vars

Deploy MCP server closer to hosts:

# Run synapse-mcp on same network segment as managed hosts
# Or use VPN to reduce latency

Container Not Found Errors

Container ID Too Short

Symptom:

Container "abc" not found on any host

Cause: Multiple containers match short prefix, or ID doesn't exist.

Solution:

Use longer container ID:

// Instead of:
{ "container_id": "abc" }

// Use at least 8 characters:
{ "container_id": "abc12345" }

Use container name:

{ "container_id": "plex" }

List all containers to find correct ID:

{ "action": "container", "subaction": "list", "state": "all" }

Container on Unexpected Host

Symptom: Container exists but not found by auto-discovery.

Cause: Discovery timeout before reaching correct host.

Solution:

Specify host explicitly:

{
  "action": "container",
  "subaction": "start",
  "container_id": "plex",
  "host": "media-server"
}

Increase discovery timeout:

# Increase Docker SSH connection timeout
export DOCKER_SSH_CONNECT_TIMEOUT_MS=10000

Check host is reachable:

ssh user@hostname docker ps

Compose Project Not Detected

Project Not in Cache

Symptom:

Project "media-stack" not found on any configured host

Cause: Cache miss, project in non-standard location, or project name mismatch.

Solution:

Refresh cache:

{ "action": "compose", "subaction": "refresh" }

Check actual project name:

# SSH to host
docker compose ls

# Or check compose.yaml
cat /path/to/compose.yaml | grep "^name:"

Add search path to host config:

{
  "name": "production",
  "host": "192.168.1.100",
  "protocol": "ssh",
  "sshUser": "admin",
  "composeSearchPaths": [
    "/compose",
    "/opt/stacks", // Add custom path
    "/srv/docker" // Add another path
  ]
}

Specify explicit path (bypass discovery):

{
  "action": "compose",
  "subaction": "up",
  "project": "media-stack",
  "host": "production",
  "path": "/opt/stacks/media" // Explicit path
}

Stopped Project Not Found

Symptom: Project exists but not detected by docker compose ls.

Cause: docker compose ls only shows running projects.

Solution:

Force filesystem scan:

// Refresh cache triggers full scan
{ "action": "compose", "subaction": "refresh" }

Or use explicit path:

{
  "action": "compose",
  "subaction": "up",
  "path": "/path/to/project",
  "host": "production"
}

Search Depth Too Shallow

Symptom: Deeply nested compose projects not found.

Cause: Default max depth is 3 levels.

Solution:

Organize projects at shallower depth:

# Instead of:
/compose/apps/production/services/media/plex/

# Use:
/compose/media-plex/

Or manually add specific paths:

{
  "composeSearchPaths": ["/compose/apps/production/services/media/plex"]
}

Debug Logging

Enable detailed logging for troubleshooting:

Enable all debug output:

DEBUG=* node dist/index.js 2>debug.log

Enable specific namespaces:

# SSH operations only
DEBUG=synapse:ssh node dist/index.js

# Docker operations only
DEBUG=synapse:docker node dist/index.js

# Multiple namespaces
DEBUG=synapse:ssh,synapse:docker node dist/index.js

Increase log level:

export LOG_LEVEL=debug
node dist/index.js

Monitor logs in real-time:

# Systemd service
journalctl -u synapse-mcp.service -f

# Or write to file
node dist/index.js 2>&1 | tee -a synapse.log

Getting Help

If you can't resolve the issue:

  1. Check logs:

    journalctl -u synapse-mcp.service -n 100
  2. Check docs/SECURITY.md: For security-related issues

  3. Open GitHub issue: Include:

    • Error message and full stack trace

    • Steps to reproduce

    • Environment details (Node version, OS, host count)

    • Relevant config (redact sensitive info)

  4. Community support: Tag maintainers in issues for faster response

Security

Command Allowlist (CWE-78)

Scout exec operations are restricted to a curated allowlist of 20 read-only commands, defined in src/constants.ts:

Allowed commands:

  • File reading: cat, head, tail

  • Search: grep, rg, find

  • Directory listing: ls, tree

  • Text processing: wc, sort, uniq, diff

  • File info: stat, file, du, df

  • System info: pwd, hostname, uptime, whoami

Security guarantees:

  • No destructive operations allowed (no rm, mv, chmod, kill, etc.)

  • Shell argument escaping prevents injection

  • No environment variable bypass for the command allowlist — SYNAPSE_ALLOW_DESTRUCTIVE_ACTIONS=true bypasses the elicitation confirmation gate only and does not affect which commands are permitted

  • All commands validated before execution against the immutable ALLOWED_COMMANDS set (Object.freeze)

Path Traversal Protection (CWE-22)

The image_build tool implements strict path validation to prevent directory traversal attacks:

  • Absolute paths required: All paths (context, dockerfile) must start with /

  • Traversal blocked: Paths containing .. or . components are rejected

  • Character validation: Only alphanumeric, dots (in filenames), hyphens, underscores, and forward slashes allowed

  • Pre-execution validation: Paths validated before SSH commands are executed

Example of rejected paths:

# Rejected: Directory traversal
../../../etc/passwd
/app/../../../etc/passwd

# Rejected: Relative paths
./build
relative/path

# Accepted: Absolute paths without traversal
/home/user/docker/build
/opt/myapp/Dockerfile.prod

Safety Gates (MCP Elicitation)

Synapse-MCP implements a three-path safety gate for all destructive and sensitive operations, using the MCP elicitation spec:

The three paths:

  1. Env bypass (SYNAPSE_ALLOW_DESTRUCTIVE_ACTIONS=true): Operation proceeds immediately. Use in CI/automation pipelines where you control the environment.

  2. MCP elicitation: The server sends an interactive confirmation form to your MCP client (e.g., Claude Code). A boolean "Confirm?" dialog appears in the UI. The operation proceeds only if you accept.

  3. Hard block: If your MCP client does not support elicitation, the operation is rejected with a descriptive error. Set the env bypass or upgrade your client.

The 10 safety-gated operations (🔒):

Operation

Guard Type

container:stop

Elicitation

container:recreate

Elicitation + force: true required

container:exec

Elicitation

compose:down

Elicitation (force: true only required when remove_volumes: true)

compose:recreate

Elicitation + force: true required

docker:prune

Elicitation + force: true required

docker:rmi

Elicitation

scout:exec

Elicitation

scout:emit

Elicitation

scout:beam

Elicitation

Root login is separately gated by SYNAPSE_ALLOW_ROOT_LOGIN=true (or MCP elicitation for the root SSH warning).

Audit trail: Most confirmed destructive operations (including container:stop, container:recreate, container:exec, and compose destructive actions) emit a DestructiveOperationEvent before execution. This event includes the operation name, target, host, and timestamp, and can be consumed by event listeners for logging, metrics, or alerting. Note: not all safety-gated operations emit this event — consult the handler source for the authoritative list.

General Security Notes

  • synapse-mcp is stdio-only — no network port is exposed; all Docker API traffic tunnels over SSH

  • Never expose Docker on TCP ports (2375/2376) — synapse-mcp does not use them

  • Always use execFile for shell commands (prevents injection)

  • Validate host config fields with regex

  • Safety-gated destructive operations require force: true (where applicable) AND elicitation confirmation

Testing

Unit + Integration Tests

pnpm test                    # Run all tests
pnpm run test:coverage       # With coverage report
pnpm run test:watch          # TDD watch mode
pnpm run test:integration    # Integration tests only

End-to-End Integration Tests (mcporter)

tests/test-mcporter.sh exercises every flux and scout action end-to-end through a live MCP server via the mcporter CLI.

# Full run (non-destructive + destructive) against default host
./tests/test-mcporter.sh

# Non-destructive only (safe to run anytime)
./tests/test-mcporter.sh --skip-destructive

# Target a different host
./tests/test-mcporter.sh --host myhost --verbose

Destructive tests deploy a dedicated synapse-test Docker Compose stack, run all lifecycle operations against it, then tear it down completely. No production containers or volumes are affected.

Required for destructive E2E tests: Set SYNAPSE_ALLOW_DESTRUCTIVE_ACTIONS=true so the test script can exercise gated operations (stop, recreate, prune, rmi, exec, emit, beam) without needing a live MCP elicitation-capable client.

See tests/README.md for the full testing strategy, safe destructive test design, MCP-first fallback pattern, and per-action test commands.


Development

# Watch mode for development
pnpm run dev

# Build
pnpm run build

# Run performance benchmarks (opt-in)
RUN_SSH_BENCHMARKS=true pnpm test src/services/ssh-pool.benchmark.test.ts
RUN_CACHE_BENCHMARKS=true pnpm test src/services/cache-layer.benchmark.test.ts

# Test with MCP Inspector
npx @modelcontextprotocol/inspector node dist/index.js

Architecture

Core Components

Event System (src/events/)

  • Type-safe EventEmitter with discriminated unions

  • Events: container_state_changed, cache_invalidated, destructive_operation

  • Decouples cross-cutting concerns (cache invalidation, audit trail, metrics)

Safety Gate (src/utils/elicitation.ts)

  • Three-path confirmation for all destructive operations: env bypass → MCP elicitation → hard block

  • confirmDestructiveAction() — gates 10 operations (container stop/recreate/exec, compose down/recreate, docker prune/rmi, scout exec/emit/beam)

  • confirmRootLogin() — separate gate for SSH root access

  • Emits DestructiveOperationEvent before every confirmed destructive execution

Lifecycle Management (src/services/container.ts)

  • State machine: uninitializedinitializingreadyshutting_downshutdown

  • Hooks: initialize(), healthCheck(), shutdown()

  • Graceful cleanup on process termination

Tool Registry (src/tools/registry.ts)

  • Plugin-style tool registration

  • Zero modification required to add new tools

  • Declarative tool definitions in src/tools/definitions/

Formatter Strategy (src/formatters/strategy.ts)

  • IFormatter interface for output formats

  • Implementations: MarkdownFormatter, JSONFormatter

  • FormatterFactory for format selection

  • Open/Closed Principle: Add formats without modifying handlers

For detailed architecture documentation, see:

  • src/services/LIFECYCLE.md - Lifecycle management guide

  • src/tools/EXTENDING.md - Tool extension guide

  • src/formatters/EXTENDING.md - Formatter extension guide

  • docs/HANDLERS.md - Handler patterns and implementation guidance

  • docs/TRANSPORTS.md - Transport options (stdio, SSH stdio)

Directory Structure

synapse-mcp/
├── src/
│   ├── index.ts                         # Entry point, transport setup
│   ├── types.ts                         # TypeScript interfaces
│   ├── constants.ts                     # Configuration constants (ALLOWED_COMMANDS, limits)
│   ├── events/                          # Type-safe EventEmitter (container_state_changed, etc.)
│   ├── formatters/                      # Per-domain markdown formatters (see formatters/EXTENDING.md)
│   ├── tools/
│   │   ├── index.ts                     # Tool registration router
│   │   ├── flux.ts                      # Flux tool entry point
│   │   ├── scout.ts                     # Scout tool entry point
│   │   └── handlers/                    # Action handlers
│   │       ├── base-handler.ts          # Shared handler utilities
│   │       ├── container.ts             # handleContainerAction()
│   │       ├── compose-handlers.ts      # handleComposeAction()
│   │       ├── docker.ts                # handleDockerAction()
│   │       ├── host.ts                  # handleHostAction()
│   │       └── scout-simple.ts          # Scout simple action handlers
│   ├── services/
│   │   ├── container.ts                 # ServiceContainer — DI root
│   │   ├── docker.ts                    # DockerService
│   │   ├── compose.ts                   # ComposeService
│   │   ├── ssh-pool.ts                  # SSH connection pool (50× speedup)
│   │   └── file-service.ts              # Scout file/exec operations
│   ├── schemas/
│   │   ├── common.ts                    # Shared schemas (pagination, response_format)
│   │   ├── flux/                        # Flux action schemas
│   │   │   ├── container.ts             # Container subaction schemas (14)
│   │   │   ├── compose.ts               # Compose subaction schemas (10)
│   │   │   ├── docker.ts                # Docker subaction schemas (9)
│   │   │   └── host.ts                  # Host subaction schemas (9)
│   │   └── scout/                       # Scout action schemas (16 operations)
│   ├── channel/                         # Real-time infrastructure event channel
│   ├── resources/                       # MCP read-only resources (synapse:// URIs)
│   └── utils/                           # Helpers: command-security, errors, pagination,
│                                        #   path-security, sanitization, time, elicitation, etc.
├── dist/                                # Compiled JavaScript
├── tests/                               # End-to-end mcporter tests
├── package.json
├── tsconfig.json
└── README.md

Key Architectural Decisions

Two-Tool Architecture:

  • Flux: 5 actions (help, container, compose, docker, host) with 43 total operations

  • Scout: 11 actions (9 simple + 2 with subactions) for 16 total operations

  • Clean separation: Flux = Docker/state changes, Scout = SSH/read operations

  • Total: 59 operations across both tools

Discriminated Union for O(1) Validation:

  • Flux: action + subaction fields with per-action nested discriminated unions

  • Scout: Primary action discriminator with nested discriminators for zfs and logs

  • Validation latency: <0.005ms average across all operations

  • Zero performance degradation regardless of which operation is called

Help System:

  • Auto-generated help handlers for both tools

  • Introspects Zod schemas using .describe() metadata

  • Supports topic-specific help (e.g., flux help container:logs)

  • Available in markdown or JSON format

SSH Connection Pooling:

  • 50× faster for repeated operations

  • Automatic idle timeout and health checks

  • Pool size (5/host) and idle timeout (60s) are hardcoded defaults

  • Transparent integration (no code changes required)

Test Coverage:

  • Unit tests for all services, schemas, and tools

  • Integration tests for end-to-end workflows

  • Performance benchmarks for schema validation

  • TDD approach for all new features

Performance

Schema Validation

Both Flux and Scout tools use Zod discriminated unions for constant-time schema dispatch:

  • Validation latency: <0.005ms average across all operations

  • Flux optimization: action + subaction with nested subaction discriminators

  • Scout optimization: Primary action discriminator with nested discriminators for zfs/logs

  • Consistency: All operations perform identically fast (no worst-case scenarios)

SSH Connection Pooling

All SSH operations use connection pooling for optimal performance:

  • 50× faster for repeated operations

  • Connections reused across compose operations

  • Automatic idle timeout (60s) and health checks (30s interval)

  • Pool size (5/host) and timeouts are hardcoded; only DOCKER_SSH_CONNECT_TIMEOUT_MS is configurable

Key Benefits:

  • Eliminate 250ms connection overhead per operation

  • Support high-concurrency scenarios (configurable pool size)

  • Automatic connection cleanup and health monitoring

  • Zero code changes required (transparent integration)

Benchmarks

Run performance benchmarks:

npm run test:bench

Expected results:

  • Worst-case validation: <0.005ms (0.003ms typical)

  • Average-case validation: <0.005ms (0.003ms typical)

  • Performance variance: <0.001ms (proves O(1) consistency)

Troubleshooting

Common Issues

"Cannot connect to Docker socket"

Symptoms:

  • Error: connect EACCES /var/run/docker.sock

  • Error: connect ENOENT /var/run/docker.sock

Solutions:

  1. Permissions - Add your user to the docker group:

    sudo usermod -aG docker $USER
    newgrp docker  # Apply without logout
  2. Socket path - Check if Docker socket exists:

    ls -la /var/run/docker.sock
    # If not found, Docker may not be installed or running
    sudo systemctl status docker
  3. Docker not running - Start Docker daemon:

    sudo systemctl start docker
    sudo systemctl enable docker  # Start on boot

"SSH connection failed" / "All configured authentication methods failed"

Symptoms:

  • Error: HostOperationError: SSH connection failed

  • Operations timeout on remote hosts

Solutions:

  1. Test SSH manually - Verify SSH access works:

    ssh -i ~/.ssh/id_rsa user@hostname
    # Should connect without password prompt
  2. Check SSH key permissions - Keys must not be world-readable:

    chmod 600 ~/.ssh/id_rsa
    chmod 644 ~/.ssh/id_rsa.pub
  3. Verify host config - Ensure sshUser and sshKeyPath are correct:

    {
      "name": "remote",
      "host": "192.168.1.100",
      "protocol": "ssh",
      "sshUser": "admin",
      "sshKeyPath": "~/.ssh/id_rsa" // or absolute: "/home/user/.ssh/id_rsa"
    }
  4. SSH agent - If using SSH agent, ensure key is loaded:

    ssh-add -l  # List loaded keys
    ssh-add ~/.ssh/id_rsa  # Add if missing

"Docker Compose project not found"

Symptoms:

  • Error: No Docker Compose projects found on host

  • Expected projects don't appear in listings

Solutions:

  1. Check search paths - Add custom compose paths to config:

    {
      "name": "myhost",
      "composeSearchPaths": ["/opt/appdata", "/mnt/docker", "/home/user/compose"]
    }
  2. Verify compose files exist - SSH to host and check:

    find /path/to/search -name "docker-compose.y*ml" -o -name "compose.y*ml"
  3. Force cache refresh - Use the refresh subaction:

    { "action": "compose", "subaction": "refresh", "host": "myhost" }
  4. Check file permissions - Ensure compose files are readable:

    ls -la /path/to/docker-compose.yml
    # Should show -rw-r--r-- (at minimum)

"Operation timed out"

Symptoms:

  • Requests hang for 30+ seconds then fail

  • Stats collection fails intermittently

Solutions:

  1. Check host connectivity - Test network latency:

    ping -c 4 hostname
    # Should show <50ms latency for local network
  2. Docker daemon responsive - Check if Docker is overloaded:

    ssh user@host "docker ps"  # Should respond in <1s
  3. Reduce parallelism - Query hosts sequentially instead of "all":

    { "action": "container", "subaction": "list", "host": "specific-host" }

"Command not in allowed list"

Symptoms:

  • Error: Command 'X' not in allowed list

  • Container exec or Scout commands fail

Solutions:

  1. Use allowed commands only - Check the allowlist in src/constants.ts:

    # File reading:    cat, head, tail
    # Search:          grep, rg, find
    # Directory:       ls, tree
    # Text processing: wc, sort, uniq, diff
    # File info:       stat, file, du, df
    # System info:     pwd, hostname, uptime, whoami
  2. Request addition - If command is needed, open an issue with:

    • Command name and purpose

    • Security justification

    • Example use cases

Diagnostic Steps

1. Check MCP Server Logs

# If running via stdio
tail -f ~/.mcp/logs/synapse-mcp.log

2. Test Host Configuration

# Test Docker API connection
docker -H ssh://user@host ps

# Test SSH command execution
ssh user@host "docker ps"

# Test Docker Compose
ssh user@host "docker compose -f /path/to/compose.yml ps"

3. Validate Configuration

# Check config file syntax
cat ~/.config/synapse-mcp/config.json | jq .

# Verify paths exist
ls -la ~/.ssh/id_rsa
ls -la /var/run/docker.sock

4. Enable Debug Logging

# Set environment variable
export DEBUG=synapse:*
# Or for specific modules
export DEBUG=synapse:ssh,synapse:docker

# Restart server to see detailed logs

Recovery Procedures

Service Down

  1. Check process - Ensure server is running:

    ps aux | grep synapse-mcp
  2. Restart server - Restart via your MCP client (Claude Code):

    • Restart Claude Code application

    • Or restart specific MCP server from settings

  3. Check config - Validate JSON syntax:

    jq . ~/.config/synapse-mcp/config.json
    # Should output formatted JSON (no errors)

High Error Rate

  1. Check Docker health - All hosts:

    for host in host1 host2 host3; do
      ssh user@$host "docker info"
    done
  2. Reduce load - Limit concurrent operations:

    • Use specific host instead of "all"

    • Add delays between operations

    • Reduce pagination limits

  3. Clear cache - Force SSH connection pool reset:

    # Restart server (closes all connections)
    # Connections will be recreated on demand

Rollback Procedure

If an update causes issues:

  1. Check version - Note current version:

    cd ~/path/to/synapse-mcp
    git log --oneline -1
  2. Rollback - Revert to previous working version:

    git log --oneline -10  # Find last working commit
    git checkout <commit-hash>
    pnpm install
    pnpm run build
  3. Restart - Restart MCP server in Claude Code

  4. Report issue - Open GitHub issue with:

    • Version that failed

    • Error messages

    • Steps to reproduce

Getting Help

  • GitHub Issues: https://github.com/jmagar/synapse-mcp/issues

  • Documentation: See docs/ARCHITECTURE.md for detailed architecture documentation

  • Transport setup: See docs/TRANSPORTS.md for secure local/remote connection patterns

License

MIT

Install Server
A
security – no known vulnerabilities
F
license - not found
-
quality - not tested

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Tools

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/jmagar/homelab-mcp'

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