Skip to main content
Glama
jmagar

Homelab MCP Server

by jmagar

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 admin@192.168.1.100
  3. Test connectivity:

    ssh admin@192.168.1.100 "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 .

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

Install Server
A
license - permissive license
A
quality
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
1Releases (12mo)

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/synapse-mcp'

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