Skip to main content
Glama
AlexSKorn

Tailnet MCP Server

by AlexSKorn
README.md
# Tailnet MCP Server

**Why I built this:** I built this to learn Tailscale - this was my first time using it. From past experience debugging network issues, I thought having an MCP to feed network state directly into an LLM could make diagnosing connectivity problems much easier than context-switching between CLI output and chat windows.

---

A Model Context Protocol (MCP) server that exposes your Tailscale tailnet as a queryable resource for LLMs. Query devices, check connectivity, and get network status directly from your AI assistant.

## Prerequisites

- Docker
- A [Tailscale](https://tailscale.com/) account with at least one device on your tailnet

## Setup & Test

1. **Clone & enter the repo:**
   ```bash
   git clone https://github.com/AlexSKorn/TailscaleMCP.git
   cd TailscaleMCP
   ```

2. **Get an Auth Key:** Go to your [Tailscale Admin Console](https://login.tailscale.com/admin/settings/keys), click "Generate auth key", and copy the key (starts with `tskey-auth-`).

3. **Configure & start:**
   ```bash
   make setup                          # creates .env from template
   # edit .env and paste your TS_AUTHKEY
   make docker-up                      # start containers
   make docker-test                    # should print "Connection test: OK"
   ```

4. **Try it interactively** with the MCP Inspector:
   ```bash
   npx @modelcontextprotocol/inspector docker compose exec -T tailnet-mcp python -m tailnet_mcp.server
   ```
   Open the URL shown in your terminal (usually `http://localhost:3000`), click **Connect** and then **"List Tools"**, then pick any tool and click **"Run"**.

### Available Tools

| Tool | Input | What it does |
|------|-------|-------------|
| `list_devices` | `{}` or `{"online_only": true}` | List all devices on your tailnet |
| `get_device` | `{"hostname": "your-device"}` | Detailed info for a specific device |
| `get_tailnet_status` | `{}` | Network overview (MagicDNS, version, device count) |
| `check_connectivity` | `{"hostname": "your-device"}` | Ping a device and report latency |
| `list_exit_nodes` | `{}` | List available exit nodes |

### Cleanup

```bash
make docker-down    # stop containers (keeps state)
make docker-clean   # stop and remove all data
```

---

## Architecture

```
MCP Client (stdio) ←→ tailnet-mcp (FastMCP) ←→ tailscale (Unix socket)
                                                    ↓
                                            /localapi/v0/*
```

1. MCP client sends tool calls via stdio
2. Server translates them to Tailscale Local API requests over a Unix socket
3. Tailscale daemon responds with network state
4. Server formats responses for human readability

**Key design decisions:**
- **Read-only** — the server cannot modify Tailscale settings, add/remove devices, or change ACLs
- **Local API only** — all calls stay on the local machine (no external network calls, no stored credentials)
- **Async throughout** — httpx for socket I/O, subprocess for pings

## Project Structure

```
src/tailnet_mcp/
├── server.py        # MCP server & tool definitions
├── tailscale.py     # Tailscale local API client (async httpx over Unix socket)
├── models.py        # Pydantic data models
tests/
└── test_server.py   # pytest + pytest-asyncio tests
```

## Development

```bash
pip install -e ".[dev]"    # install with dev dependencies
make test                  # run tests
make check-all             # lint + format + type-check + tests
```

## License

MIT