Skip to content

jmagar/synapse-mcp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

711 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Synapse MCP

npm ghcr.io

MCP server for homelab infrastructure. Provides two tools: flux for Docker/Compose/host management and scout for SSH remote operations.

Version: 2.2.3 | Node.js ≥ 24 | MIT

Overview

Synapse MCP exposes Docker infrastructure and SSH operations as MCP tools. Connect it to any MCP client (Claude Code, Cursor, Gemini) and manage containers, inspect remote files, read logs, and run diagnostics across multiple hosts.

What it includes:

  • src/ — TypeScript implementation (tools, schemas, services, transport)
  • config/ — Config examples and JSON Schema
  • skills/synapse/ — Client-facing skill docs
  • tests/ — Vitest test suite
  • .claude-plugin/, .codex-plugin/, gemini-extension.json — Client manifests

Tools

flux — Docker infrastructure management

Routes by action, then subaction. Total: 43 subactions.

container (14 subactions)

Subaction Description Destructive
list List containers. Filter by state, name_filter, image_filter, label_filter
inspect Detailed container info. summary=true for basic info
logs Container logs. Supports lines, since, until, grep, stream
stats CPU/memory resource usage. Omit container_id for all containers
top Running processes inside a container
search Full-text search across containers
start Start a stopped container
stop Stop a running container Yes
restart Stop then start a container
pause Freeze container processes via cgroups
resume Unfreeze a paused container
pull Pull the latest image for a container
recreate Delete and recreate container. pull=true by default Yes
exec Execute a command inside a running container Yes

All subactions accept an optional host field. Destructive subactions require confirmation via MCP elicitation (or SYNAPSE_MCP_ALLOW_DESTRUCTIVE=true).

container:exec parameters: container_id, command, user (optional), workdir (optional), timeout (1000–300000 ms, default 30000).

container:logs parameters: lines (1–500, default 50), since/until (ISO 8601 or relative like "1h"), grep, stream (stdout/stderr/both).

compose (10 subactions)

Subaction Description Destructive
list List all Compose projects. Filter with name_filter
status Project service status. Filter with service_filter
logs Project logs. Supports service, lines, since, until, grep
pull Pull images for a project or specific service
build Build project images. no_cache=true to force rebuild
up Start project. detach=true by default
down Stop and remove containers. remove_volumes=true requires force=true Yes
restart Restart all project services Yes
recreate Recreate all project containers. Requires force=true Yes
refresh Rescan filesystem to refresh Compose project cache

compose:down note: remove_volumes=true AND force=true are both required to delete volumes. This prevents accidental data loss.

compose:logs time filter: Accepts durations (30s, 5m, 1.5h, 100ms), dates (2024-01-01), RFC3339 timestamps, or Unix timestamps.

Default Compose search paths (Unraid-centric defaults, override per host):

  • /compose
  • /mnt/cache/compose
  • /mnt/cache/code

docker (9 subactions)

Subaction Description Destructive
info Docker daemon information
df Docker disk usage
images List images. dangling_only=true for untagged only
networks List Docker networks
volumes List Docker volumes
pull Pull an image by name
build Build an image. context must be absolute path
rmi Remove an image. Requires force=true Yes
prune Remove unused resources. prune_target required, force=true required Yes

docker:prune targets: containers, images, volumes, networks, buildcache, all.

docker:build parameters: context (absolute path, no ..), tag, dockerfile (relative to context, optional), no_cache.

host (9 subactions)

Subaction Description
status Docker connectivity check
info OS, kernel, architecture, hostname
uptime System uptime
resources CPU, memory, disk usage via SSH
services Systemd service status. Filter by service name and state
network Network interfaces and IP addresses
mounts Mounted filesystems
ports All port mappings for containers. Filter by protocol, state, source
doctor Diagnostic checks. Optional checks array: resources, containers, logs, processes, docker, network

help

{ "action": "help", "topic": "container:list", "format": "markdown" }

Returns auto-generated documentation. topic is optional.


scout — SSH remote operations

Routes by action. Total: 16 operations.

Simple actions (9)

Action Description Destructive
nodes List all configured SSH hosts
peek Read file or directory on a remote host. tree=true shows directory tree
find Find files by glob pattern. Parameters: host, path, pattern, depth, limit
ps List processes. Sort by cpu/mem/pid/time. Filter by grep or user
df Disk usage for a remote host. Optional path to target a specific mount
delta Compare files between two remote locations, or a remote file against inline content
exec Run an allowlisted command on a remote host Yes
emit Run a command on multiple hosts simultaneously Yes
beam Transfer a file from one remote path to another Yes

peek parameters: host, path (safe chars only, no ..), tree (boolean), depth (1–10, default 3).

exec and emit — command allowlist: Commands are validated at runtime. Only these commands are permitted: cat, head, tail, grep, rg, find, ls, tree, wc, sort, uniq, diff, stat, file, du, df, pwd, hostname, uptime, whoami, git. Write commands (rm, mkdir, cp) are blocked.

exec parameters: host, path (absolute, working directory), command, timeout (ms, default 30000, max 300000).

emit parameters: targets (array of { host, path } objects), command, timeout.

beam parameters: source and destination as { host, path } objects.

delta parameters: source ({ host, path }), then either target ({ host, path }) or content (inline string up to 1 MB).

zfs (3 subactions)

Subaction Description
pools List ZFS pools. Filter by pool name or health (online/degraded/faulted)
datasets List datasets. Filter by pool, type (filesystem/volume), recursive
snapshots List snapshots. Filter by pool, dataset, limit

All zfs subactions require host.

logs (4 subactions)

Subaction Description
syslog Read /var/log system logs. Supports lines, grep
journal Systemd journal. Supports lines, since, until, unit, priority, grep
dmesg Kernel ring buffer. Requires root or CAP_SYSLOG
auth Authentication logs (/var/log/auth.log or equivalent)

journal priority levels: emerg, alert, crit, err, warning, notice, info, debug.

journal time formats: ISO 8601 timestamps or relative strings like "2h ago", "yesterday".

All logs subactions accept host, lines (1–500, default 50), and grep.


Installation

Marketplace (Claude Code)

/plugin marketplace add jmagar/claude-homelab
/plugin install synapse-mcp @jmagar-claude-homelab

Local build

npm install
npm run build
npm start

The published binary:

synapse-mcp          # stdio transport (default)
synapse-mcp --http   # HTTP transport

Docker

docker compose up -d

See the full docker-compose.yaml example below.


Configuration

Host configuration

Synapse MCP loads hosts in this priority order:

  1. SYNAPSE_CONFIG_FILE env var (explicit path)
  2. ./synapse.config.json (current directory)
  3. ~/.config/synapse-mcp/config.json (XDG)
  4. ~/.synapse-mcp.json (home directory)
  5. SYNAPSE_HOSTS_CONFIG env var (JSON array)
  6. ~/.ssh/config (auto-discovery)
  7. /var/run/docker.sock (local Docker fallback)

You do not need a config file. If ~/.ssh/config lists hosts with HostName and User, they are discovered automatically.

synapse.config.json

Full example:

{
  "$schema": "./config/config.schema.json",
  "hosts": [
    {
      "name": "local",
      "host": "localhost",
      "protocol": "ssh",
      "dockerSocketPath": "/var/run/docker.sock",
      "tags": ["local", "development"]
    },
    {
      "name": "nas",
      "host": "192.168.1.100",
      "port": 22,
      "protocol": "ssh",
      "sshUser": "admin",
      "sshKeyPath": "~/.ssh/id_ed25519",
      "dockerSocketPath": "/var/run/docker.sock",
      "composeSearchPaths": ["/opt/stacks", "/srv/docker"],
      "tags": ["production", "storage"]
    },
    {
      "name": "api-only-host",
      "host": "docker.local",
      "port": 2375,
      "protocol": "http",
      "tags": ["api-only"]
    }
  ]
}

Host fields:

Field Required Description
name Yes Unique identifier. Alphanumeric, _, - only
host Yes Hostname, IP, or localhost
protocol Yes ssh (recommended), http, or https
sshUser If protocol=ssh SSH username
port No SSH port (default 22) or Docker API port (default 2375/2376)
sshKeyPath No Path to SSH private key. Defaults to SSH agent
dockerSocketPath No Docker socket path (default /var/run/docker.sock)
composeSearchPaths No Dirs to search for Compose projects. Overrides Unraid defaults
tags No String array for grouping and filtering

Security warning: protocol: "http" exposes Docker over TCP without authentication. Use ssh for all real deployments.

SSH config auto-discovery

Any entry in ~/.ssh/config with HostName and User is loaded automatically:

Host nas
  HostName 192.168.1.100
  User admin
  IdentityFile ~/.ssh/id_ed25519

Host pi
  HostName 192.168.1.50
  User pi
  IdentityFile ~/.ssh/id_rsa

Manual synapse.config.json entries override SSH config entries with the same name.

SSH key setup

  1. Generate a key (if needed):

    ssh-keygen -t ed25519 -C "synapse-mcp"
  2. Copy the public key to each host:

    ssh-copy-id [email protected]
  3. Test connectivity:

    ssh [email protected] "echo ok"

When running in Docker, mount your SSH directory read-only:

volumes:
  - ${HOME}/.ssh:/home/node/.ssh:ro

The container runs as the node user, so keys must be readable by that user. The mount path /home/node/.ssh is used by the container.


Environment variables

Variable Required Default Description
SYNAPSE_CONFIG_FILE No Explicit path to synapse.config.json
SYNAPSE_HOSTS_CONFIG No JSON array of host configs as a fallback
SYNAPSE_DEFAULT_HOST No Default host when none is specified in a request
SYNAPSE_EXCLUDE_HOSTS No Comma-separated host names to skip during discovery
SYNAPSE_ALLOW_ROOT_LOGIN No false Allow SSH as root without elicitation prompt
SYNAPSE_MCP_ALLOW_DESTRUCTIVE No false Skip elicitation prompts for destructive operations
SYNAPSE_MCP_ALLOW_YOLO No false Bypass ALL elicitation confirmation gates (strict =true check only)
SYNAPSE_DEBUG_ERRORS No false Include full error details in responses (do not use in production)
SYNAPSE_MCP_TRANSPORT No stdio Set to http to enable HTTP transport
SYNAPSE_MCP_PORT No 3000 HTTP server listen port
SYNAPSE_MCP_HOST No 127.0.0.1 HTTP server bind address. Set 0.0.0.0 for external access
SYNAPSE_MCP_TOKEN If HTTP Bearer token for HTTP transport. Generate with openssl rand -hex 32
SYNAPSE_MCP_NO_AUTH No Set true to disable bearer token auth. Not recommended
SYNAPSE_MCP_SESSION_TTL_MS No 1800000 Session idle timeout in ms (30 minutes)
SYNAPSE_MCP_ALLOWED_HOSTS No Comma-separated allowed Host header values (DNS rebinding protection)
DOCKER_SSH_CONNECT_TIMEOUT_MS No 5000 Docker-over-SSH connection timeout in ms
COMPOSE_HOST_RESOLUTION_TIMEOUT_MS No 30000 Compose project host auto-resolution timeout in ms
PUID No 1000 User ID for Docker container process
PGID No 1000 Group ID for Docker container process
DOCKER_NETWORK No mcp-net Docker network name for Compose

SYNAPSE_ALLOW_ROOT_LOGIN: When false, SSH operations that would run as root require confirmation via MCP elicitation. If your client does not support elicitation, the operation is blocked.

SYNAPSE_MCP_ALLOW_DESTRUCTIVE: When false, these operations require MCP elicitation confirmation: container:stop, container:recreate, container:exec, compose:down, compose:restart, compose:recreate, docker:rmi, docker:prune, scout:exec, scout:emit, scout:beam. When your client does not support elicitation, the operation is blocked entirely.

SYNAPSE_MCP_ALLOW_YOLO: When true, bypasses ALL elicitation confirmation gates — both confirmDestructiveAction and confirmRootLogin return immediately without prompting. Only the exact string "true" activates this; 1, yes, and TRUE do not. YOLO fires before SYNAPSE_MCP_ALLOW_DESTRUCTIVE when both are set. Does NOT bypass schema-level force: true fields — callers must still supply those explicitly. A startup warning is logged whenever this is active.

SYNAPSE_DEBUG_ERRORS: When true, full stack traces and internal error details appear in tool responses. Do not enable in production — this may expose sensitive host information.


SSH connection pooling

Synapse MCP maintains a per-host SSH connection pool. This avoids repeated handshakes when executing many operations against the same host.

Default pool behavior:

  • Max 3 connections per host
  • Idle timeout: 60 seconds
  • Connection timeout: 5 seconds
  • Health checks every 30 seconds

Pool configuration is not currently exposed via environment variables. To change defaults, edit src/services/ssh-pool.ts.


Docker socket

The container needs access to the Docker socket to manage local Docker resources:

volumes:
  - /var/run/docker.sock:/var/run/docker.sock

The container runs as user 1000:1000 by default. Add the container to the Docker socket group so it can read the socket without running as root:

group_add:
  - "${DOCKER_SOCKET_GID:-981}"

Find your Docker socket GID:

stat -c '%g' /var/run/docker.sock

Deployment

Full docker-compose.yaml example

networks:
  mcp-net:
    name: ${DOCKER_NETWORK:-mcp-net}
    external: true

services:
  synapse-mcp:
    image: ghcr.io/jmagar/synapse-mcp:latest
    container_name: synapse-mcp
    restart: unless-stopped
    user: "${PUID:-1000}:${PGID:-1000}"
    group_add:
      - "${DOCKER_SOCKET_GID:-981}"
    environment:
      SYNAPSE_MCP_TRANSPORT: http
      SYNAPSE_MCP_PORT: 3000
      SYNAPSE_MCP_TOKEN: "${SYNAPSE_MCP_TOKEN}"
      SYNAPSE_DEFAULT_HOST: nas
    ports:
      - "${SYNAPSE_MCP_PORT:-3000}:${SYNAPSE_MCP_PORT:-3000}"
    volumes:
      - ./config/synapse.config.json:/config/synapse.config.json:ro
      - ${HOME}/.ssh:/home/node/.ssh:ro
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - mcp-net
    deploy:
      resources:
        limits:
          memory: 1024M
          cpus: "1"
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:${SYNAPSE_MCP_PORT:-3000}/health || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 5
      start_period: 30s

HTTP transport endpoints

When running with --http or SYNAPSE_MCP_TRANSPORT=http:

Endpoint Method Description
/mcp POST Client-to-server (new session or route existing)
/mcp GET Server-to-client SSE stream (requires Mcp-Session-Id header)
/mcp DELETE Terminate a session
/health GET Liveness check (no auth required)
/ready GET Readiness check (no auth required)

MCP client configuration (Claude Code)

For HTTP transport, configure the MCP server in your client:

{
  "mcpServers": {
    "synapse": {
      "type": "http",
      "url": "http://synapse-mcp:3000/mcp",
      "headers": {
        "Authorization": "Bearer <your-token>"
      }
    }
  }
}

For stdio transport:

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

Usage examples

List containers across hosts

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

Target a specific host:

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

Get container logs

{
  "action": "container",
  "subaction": "logs",
  "container_id": "nginx",
  "host": "nas",
  "lines": 100,
  "since": "1h",
  "grep": "error"
}

Restart a container

{ "action": "container", "subaction": "restart", "container_id": "nginx", "host": "nas" }

Pull and recreate a container

{ "action": "container", "subaction": "pull", "container_id": "nginx", "host": "nas" }
{ "action": "container", "subaction": "recreate", "container_id": "nginx", "host": "nas", "pull": false }

Update a Compose project

{ "action": "compose", "subaction": "pull", "project": "media-stack", "host": "nas" }
{ "action": "compose", "subaction": "recreate", "project": "media-stack", "host": "nas", "force": true }

Check host resources

{ "action": "host", "subaction": "resources", "host": "nas" }

Run diagnostics

{
  "action": "host",
  "subaction": "doctor",
  "host": "nas",
  "checks": ["resources", "containers", "docker"]
}

List all configured hosts (scout)

{ "action": "nodes" }

Read a remote file

{ "action": "peek", "host": "nas", "path": "/etc/docker/daemon.json" }

Read a remote directory as a tree

{ "action": "peek", "host": "nas", "path": "/opt/stacks", "tree": true, "depth": 2 }

Find files by pattern

{ "action": "find", "host": "nas", "path": "/opt/stacks", "pattern": "*.yaml", "depth": 3 }

Check disk usage

{ "action": "df", "host": "nas" }

View systemd journal with filters

{
  "action": "logs",
  "subaction": "journal",
  "host": "nas",
  "unit": "docker.service",
  "priority": "err",
  "since": "2h ago",
  "lines": 100
}

List ZFS pools

{ "action": "zfs", "subaction": "pools", "host": "nas" }

List ZFS datasets with health filter

{ "action": "zfs", "subaction": "datasets", "host": "nas", "pool": "tank", "recursive": true }

Run a command on multiple hosts

{
  "action": "emit",
  "targets": [
    { "host": "nas", "path": "/home" },
    { "host": "pi", "path": "/home" }
  ],
  "command": "df -h"
}

Security

Path validation

All remote paths are validated against a safe character set ([a-zA-Z0-9._\-/]) and checked for path traversal (.. as a path component). Shell metacharacters are rejected to prevent command injection (CWE-78).

Command allowlist

scout:exec and scout:emit run commands through ALLOWED_READ_COMMANDS — a static read-only allowlist (see the full list under exec above). mkdir, rm, and all other write commands are blocked. cp is write-capable and intentionally excluded from the public surface; it is only available to internal services (e.g., scout:beam file transfer).

SSH username validation

SSH usernames and key paths are validated before shell interpolation. Non-alphanumeric characters (with limited exceptions) are rejected.

HTTP authentication

HTTP transport requires a Bearer token set via SYNAPSE_MCP_TOKEN. To generate one:

openssl rand -hex 32

If neither SYNAPSE_MCP_TOKEN nor SYNAPSE_MCP_NO_AUTH=true is set, the server refuses to start.

DNS rebinding protection

Set SYNAPSE_MCP_ALLOWED_HOSTS to a comma-separated list of allowed Host header values. Requests with other Host values are rejected.


Development

npm run build            # compile TypeScript
npm run typecheck        # type-check without emitting
npm test                 # run unit tests
npm run test:coverage    # run tests with coverage report
npm run test:integration # run integration tests
npm run lint             # lint with Biome
npm run format           # format with Biome

Via Justfile:

just build
just test
just lint
just up       # docker compose up -d
just down     # docker compose down
just logs     # docker compose logs -f
just health   # curl /health endpoint
just gen-token  # generate a new bearer token

Verification

npm run typecheck
npm test
npm run lint

Integration tests require live SSH/Docker access to configured hosts:

npm run test:integration

Health check (HTTP transport):

curl -sf http://localhost:3000/health | jq .

Related plugins

Plugin Category Description
homelab-core core Core agents, commands, skills, and setup/health workflows for homelab management.
overseerr-mcp media Search movies and TV shows, submit requests, and monitor failed requests via Overseerr.
unraid-mcp infrastructure Query, monitor, and manage Unraid servers: Docker, VMs, array, parity, and live telemetry.
unifi-mcp infrastructure Monitor and manage UniFi devices, clients, firewall rules, and network health.
gotify-mcp utilities Send and manage push notifications via a self-hosted Gotify server.
swag-mcp infrastructure Create, edit, and manage SWAG nginx reverse proxy configurations.
arcane-mcp infrastructure Manage Docker environments, containers, images, volumes, networks, and GitOps via Arcane.
syslog-mcp infrastructure Receive, index, and search syslog streams from all homelab hosts via SQLite FTS5.
plugin-lab dev-tools Scaffold, review, align, and deploy homelab MCP plugins with agents and canonical templates.

License

MIT

About

MCP server providing Flux (Docker management) and Scout (SSH operations) tools for homelab infrastructure.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Languages