Skip to main content
Glama
jmagar

Homelab MCP Server

by jmagar
README.md
# Synapse MCP

[![npm](https://img.shields.io/npm/v/synapse-mcp)](https://www.npmjs.com/package/synapse-mcp) [![ghcr.io](https://img.shields.io/badge/ghcr.io-jmagar%2Fsynapse--mcp-blue?logo=docker)](https://github.com/jmagar/synapse-mcp/pkgs/container/synapse-mcp)

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`

```json
{ "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)

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

### Local build

```bash
npm install
npm run build
npm start
```

The published binary:

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

### Docker

```bash
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:

```json
{
  "$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:

```ssh-config
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):
   ```bash
   ssh-keygen -t ed25519 -C "synapse-mcp"
   ```

2. Copy the public key to each host:
   ```bash
   ssh-copy-id admin@192.168.1.100
   ```

3. Test connectivity:
   ```bash
   ssh admin@192.168.1.100 "echo ok"
   ```

When running in Docker, mount your SSH directory read-only:
```yaml
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:

```yaml
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:

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

Find your Docker socket GID:
```bash
stat -c '%g' /var/run/docker.sock
```

---

## Deployment

### Full docker-compose.yaml example

```yaml
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:

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

For stdio transport:

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

---

## Usage examples

### List containers across hosts

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

Target a specific host:

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

### Get container logs

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

### Restart a container

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

### Pull and recreate a container

```json
{ "action": "container", "subaction": "pull", "container_id": "nginx", "host": "nas" }
```

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

### Update a Compose project

```json
{ "action": "compose", "subaction": "pull", "project": "media-stack", "host": "nas" }
```

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

### Check host resources

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

### Run diagnostics

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

### List all configured hosts (scout)

```json
{ "action": "nodes" }
```

### Read a remote file

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

### Read a remote directory as a tree

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

### Find files by pattern

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

### Check disk usage

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

### View systemd journal with filters

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

### List ZFS pools

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

### List ZFS datasets with health filter

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

### Run a command on multiple hosts

```json
{
  "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:

```bash
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

```bash
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:

```bash
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

```bash
npm run typecheck
npm test
npm run lint
```

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

```bash
npm run test:integration
```

Health check (HTTP transport):

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

---

## Related plugins

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

## License

MIT

TDQS

A3.8/5.0

Scored across 1 tool

Disambiguation5/5

The single tool 'homelab' has clearly distinct subactions organized by resource type (container, compose, host, docker, image, scout), each with specific operations that don't overlap. Subactions like 'list', 'start', 'logs' are unambiguous within their categories.

Naming Consistency5/5

All subactions follow a consistent verb_noun pattern (e.g., 'list', 'start', 'logs', 'inspect') across all categories. The naming is uniform with clear, descriptive verbs that match their functions without mixing conventions.

Tool Count2/5

With only 1 tool, the server feels under-scoped for its broad domain of homelab Docker management. The single tool bundles multiple distinct functionalities (container management, compose operations, host monitoring, etc.) that might be better served as separate tools for clarity and modularity.

Completeness4/5

The tool provides comprehensive coverage for Docker and homelab management, including CRUD-like operations for containers, images, and compose projects, plus host monitoring and remote file operations. Minor gaps might include advanced features like container networking or volume management, but core workflows are well-covered.

Maintenance

ActivityStale
ResponsivenessSyncing