You are currently viewing MCP for Pi-Haole

MCP for Pi-Haole

With a local MCP for Pi-Hole in a Docker container, Claude can read my Pi-hole on the home network without the DNS server having to be exposed to the internet.

Why this approach

There is no ready-made connector for Pi-hole in the Claude directory, but there are several community servers for the Pi-hole v6 API. I use hexamatic/pihole-mcp: a Docker image with broad API coverage (76 tools according to the project page), a read-only switch and a filter for toolsets.

Three requirements shaped the approach:

  • Nothing on the internet. A DNS server with admin access is too sensitive for a tunnel or HTTPS exposure. Anyone with write access can redirect domains or switch off blocking. So: HTTP on my own LAN only.
  • No additional installer. Docker must be installed and running. The image is loaded on first start, and the container is removed after every session.
  • Start safe. Read first, write only once everything works.

The prerequisite is Pi-hole v6, because only it has the REST API with app passwords. The version is shown at the very bottom of the web UI.

Video: MCP for Pi-Haole

Language: 🇩🇪|🇬🇧
☝️ Use YouTube subtitles for all languages.

Why the MCP server must run locally and in Docker

Claude Desktop starts the server as a local process and talks to it over standard input and output (stdio). This has three consequences:

  • The computer running Claude Desktop needs Docker. It starts the container itself as soon as Claude needs it. A pure browser session cannot start a local container.
  • The server must run in the Pi-hole’s network. The Pi-hole has a private address such as 192.168.1.10. It cannot be reached from the internet or the cloud, and that should stay that way.
  • There is no open port. The server can also run over HTTP or SSE, but then without authentication as long as no PIHOLE_HTTP_AUTH_TOKEN is set. The project page warns against exposing these transports beyond loopback. With stdio the question does not arise.

Docker also brings the same behavior everywhere: the same config on Windows, macOS and Linux, no dependencies on the computer, and updates by pulling a new image.

Read-only and write access

My config contains PIHOLE_READ_ONLY=true.

The file claude_desktop_config.json is probably located here:

SystemLikely path
macOS~/Library/Application Support/Claude/claude_desktop_config.json
Windows%APPDATA%\Claude\claude_desktop_config.json
Linux~/.config/Claude/claude_desktop_config.json

There is no official Claude Desktop app for Linux. With unofficial builds the file is usually at this path. The safest way to open it is in the app via Settings → Developer → Edit Config.

Here is how to switch:

  1. Allow writing: Remove the line PIHOLE_READ_ONLY=true from the config or set it to false, then restart Claude Desktop.
  2. Allow only some: Set PIHOLE_TOOLSETS to a comma-separated list, for example dashboard,domains. Both switches can be combined; read-only takes precedence.
  3. Back up first: Create a Teleporter backup of the Pi-hole. The export needs a mounted folder (-v /path/on/the/computer:/backups), otherwise the file is lost with the container.

A second brake sits in the Pi-hole itself: According to the Pi-hole documentation, an app password may not change configuration settings as long as webserver.api.app_sudo is set to false (default). That is good for getting started. For configuration changes you would have to set the value to true deliberately.

Use a dedicated app password for the MCP server, not the admin password. It is stored in plain text in the local config, but can be revoked at any time in the Pi-hole web UI.

Scope of features

In read-only mode these areas are available:

AreaWhat Claude can query
StatisticsSummary, top clients, top domains, upstream response times, query types
Query logSearch by domain, client, status and time range, also in the long-term database
Lists and rulesBlocklists, allow and deny domains, groups, searching a domain across all lists
Configurationthe complete Pi-hole configuration, by section
System and diagnosticsVersions, FTL status, load, diagnostic messages, DNS, FTL and web server logs
NetworkDevices, gateway, interfaces, routes, DHCP leases
Local DNSA and CNAME records
BackupTeleporter export

Without read-only, the project description says write tools are added, for example for blocking on and off, domains and lists, gravity update and local DNS records. Several Pi-holes can be connected via numbered variables (PIHOLE_1_URL, PIHOLE_2_URL).

Typical questions

  • “Show me the Pi-hole status.”
  • “Which devices send the most queries?”
  • “Why is this domain blocked?”
  • “Check my regex rules for errors.”

Why YAML doesn’t work here

Pi-hole itself runs for me via docker-compose.yml. That is YAML, and that is where it belongs. I deliberately do not add the MCP server there:

  1. Claude Desktop reads JSON. The file is called claude_desktop_config.json. A YAML entry is not understood.
  2. A stdio server is a child process of the client. Claude starts it and keeps input and output open. A Compose service, by contrast, starts independently in the background and keeps running when Claude is closed. Claude could then not talk to it. That is why the JSON config uses docker run -i --rm: -i keeps the input open, --rm cleans up the container after the session.

Compose would only make sense if the server were to run as a permanent HTTP service. That requires a token and hardening and was not my goal.

If you want to set up the server through a YAML file like other services, you would write a config.yaml, for example. The example shows how it does not work (anonymized):

# config.yaml (this does not work)
mcpServers:
  pihole:
    command: docker
    args:
      - run
      - -i
      - --rm
      - -e
      - PIHOLE_URL=http://192.168.1.10:8080
      - -e
      - PIHOLE_PASSWORD=<APP-PASSWORD>
      - -e
      - PIHOLE_READ_ONLY=true
      - ghcr.io/hexamatic/pihole-mcp:latest

Why neither config.yaml nor docker-compose.yml works with MCP:

  • The client only reads its own file. Claude Desktop loads only claude_desktop_config.json. Other files, whether config.yaml or docker-compose.yml, are neither searched for nor read.
  • The server is not configured through a file. According to the project page, it receives URL, password and switches as environment variables (-e with docker run) and start parameters. It would not evaluate a YAML file.
  • A Compose file starts the container independently of the client. It defines how a service runs in the background, but does not connect it to Claude. The connection only arises when Claude starts the process itself and talks to it over stdio.

The MCP entry therefore always belongs in the client’s JSON file. Everything else the server needs, you pass there as -evariables.

Typical JSON pitfalls in the Claude config:

  • There may be only one object at the top level. A second block after the end of the file leads to “Unexpected non-whitespace character after JSON”, and the app starts without its settings.
  • No comments and no comma after the last entry.
  • Every text is in double quotes, including PIHOLE_PASSWORD=... as a whole. The password follows the = without further quotation marks.

Step-by-step setup

In the examples the Pi-hole runs at http://192.168.1.10:8080. Adjust the address and port to your setup. Prerequisites: Pi-hole v6, Docker Desktop and Claude Desktop on a computer that can reach the Pi-hole on the network.

1. Create an app password

In the Pi-hole web UI: Settings → Web interface / API → Configure app password. The password is shown only once. Copy it, but do not share it, not even in a chat.

2. Test reachability

curl -I http://192.168.1.10:8080/admin/login

On Windows, use curl.exe -I ... in PowerShell. If the server answers with 200 or 302, the address is correct. Otherwise check port, firewall and network.

3. Check Docker and pull the image

docker ps
docker pull ghcr.io/hexamatic/pihole-mcp:latest

The pull is optional. Otherwise Docker loads the image on first start.

4. Open the config file

The easiest way is in Claude Desktop via Settings → Developer → Edit Config. The app creates the file. By hand it is located here:

SystemPath
macOS~/Library/Application Support/Claude/claude_desktop_config.json
Windows%APPDATA%\Claude\claude_desktop_config.json

In the macOS terminal the path must be in quotation marks because it contains a space. Make a backup copy before changing anything.

5. Insert the entry

The file is new or empty. Then the whole file looks like this:

{
  "mcpServers": {
    "pihole": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "PIHOLE_URL=http://192.168.1.10:8080",
        "-e", "PIHOLE_PASSWORD=<APP-PASSWORD>",
        "-e", "PIHOLE_READ_ONLY=true",
        "-e", "TZ=Europe/Berlin",
        "ghcr.io/hexamatic/pihole-mcp:latest"
      ]
    }
  }
}

Replace <APP-PASSWORD> including the angle brackets with your app password.

The file already has content. Then mcpServers goes in as an additional entry in the existing object, with a comma before it. Never append a second block after the end of the file:

{
  "preferences": {
    "sidebarMode": "chat"
  },
  "mcpServers": {
    "pihole": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "PIHOLE_URL=http://192.168.1.10:8080",
        "-e", "PIHOLE_PASSWORD=<APP-PASSWORD>",
        "-e", "PIHOLE_READ_ONLY=true",
        "-e", "TZ=Europe/Berlin",
        "ghcr.io/hexamatic/pihole-mcp:latest"
      ]
    }
  }
}

If mcpServers already exists, you only add the block "pihole": { ... } inside it, with a comma. The preferencesblock is only a placeholder here for your existing settings.

6. Validate the JSON

macOS and Linux:

python3 -m json.tool "$HOME/Library/Application Support/Claude/claude_desktop_config.json" > /dev/null && echo OK

Windows (PowerShell):

Get-Content "$env:APPDATA\Claude\claude_desktop_config.json" -Raw | ConvertFrom-Json | Out-Null

If it prints OK, or shows no error message, the file is valid. Otherwise the message shows where the error is.

7. Restart Claude Desktop

Quit the app completely: on macOS with Cmd+Q, on Windows via the icon in the notification area. Closing the window is not enough. Then reopen it.

8. Test

Ask Claude: “Show me the Pi-hole status.” You should get the versions of Core, FTL and Web, the blocking status and the number of queries.

9. Optional: write access

Only after a few days in read-only mode and with a backup, as described in the section on read-only.

Troubleshooting

SymptomCauseSolution
At startup “Unexpected non-whitespace character after JSON”Second block after the end of the file, or wrong commaMove the entry into the existing object, then step 6
authentication failed: HTTP 401Password wrong, masked or with additional quotation marksCreate a new app password and check the line
Pi-hole tools are missingDocker is not running or the app was not fully restartedCheck docker ps, quit the app and restart it
No connectionAddress, port or firewallRepeat step 2; with Docker Desktop alternatively try http://host.docker.internal:8080
Platform error (Apple Silicon)Image architecture does not matchAfter "--rm", add the entries "--platform", "linux/amd64",

Sources


Donate Bild

Support / Donation Link for the Channel
If my posts have been helpful or supported you in any way, I’d truly appreciate your support 🙏

PayPal Link
Bank transfer, Bitcoin and Lightning


#PiHole #MCP #SelfHosted #Docker #Claude

Leave a Reply