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_TOKENis 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:
| System | Likely 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:
- Allow writing: Remove the line
PIHOLE_READ_ONLY=truefrom the config or set it tofalse, then restart Claude Desktop. - Allow only some: Set
PIHOLE_TOOLSETSto a comma-separated list, for exampledashboard,domains. Both switches can be combined; read-only takes precedence. - 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:
| Area | What Claude can query |
|---|---|
| Statistics | Summary, top clients, top domains, upstream response times, query types |
| Query log | Search by domain, client, status and time range, also in the long-term database |
| Lists and rules | Blocklists, allow and deny domains, groups, searching a domain across all lists |
| Configuration | the complete Pi-hole configuration, by section |
| System and diagnostics | Versions, FTL status, load, diagnostic messages, DNS, FTL and web server logs |
| Network | Devices, gateway, interfaces, routes, DHCP leases |
| Local DNS | A and CNAME records |
| Backup | Teleporter 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:
- Claude Desktop reads JSON. The file is called
claude_desktop_config.json. A YAML entry is not understood. - 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:-ikeeps the input open,--rmcleans 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, whetherconfig.yamlordocker-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 (
-ewithdocker 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:
| System | Path |
|---|---|
| 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
| Symptom | Cause | Solution |
|---|---|---|
| At startup “Unexpected non-whitespace character after JSON” | Second block after the end of the file, or wrong comma | Move the entry into the existing object, then step 6 |
authentication failed: HTTP 401 | Password wrong, masked or with additional quotation marks | Create a new app password and check the line |
| Pi-hole tools are missing | Docker is not running or the app was not fully restarted | Check docker ps, quit the app and restart it |
| No connection | Address, port or firewall | Repeat step 2; with Docker Desktop alternatively try http://host.docker.internal:8080 |
| Platform error (Apple Silicon) | Image architecture does not match | After "--rm", add the entries "--platform", "linux/amd64", |
Sources
- hexamatic/pihole-mcp, project page with configuration and tools
- Pihole-mcp by lloydmcl, Pi-hole forum
- Python MCP Server for Pi-hole by sbarbett, Pi-hole forum

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