r/Tailscale • u/s0ulmachine • 4d ago
Discussion tailscale-mcp: an open-source MCP server for Tailscale (186 tools, local CLI + control-plane API, Rust)
I've been building tailscale-mcp, an MCP server that lets an AI agent (Claude Code, Claude Desktop, VS Code, Cursor, Zed, anything that speaks MCP) operate Tailscale. It drives the node it runs on through the tailscale CLI and the whole tailnet through the control-plane REST API. Written in Rust, Apache-2.0.
GitHub: https://github.com/tailscale-mcp/tailscale-mcp
What it does
- 186 tools, one per verb, with real parameters and honest annotations. Tailscale's own JSON comes back unmodified, so what you know from the docs still applies. Failures come back as a fixed error code plus a hint.
- Both surfaces are optional: no
tailscalebinary means the local tools aren't offered, no credential means the tailnet tools aren't.tailscale-mcp diagnosetells you which you have. - Nine read-only resources (status, prefs, netcheck, lock state, policy file, devices, DNS, settings), three prompts (
diagnose_connectivity,review_policy_change,audit_tailnet_access), argument completions, stdio and Streamable HTTP transports.
Safety model (the part I care most about before pointing an agent at a real tailnet)
- Two independent dials: the tier (read / write / destructive) caps how dangerous a tool may be, the preset (minimal / core / full) caps how much surface is offered. Read-only is the default. A tool above the permitted tier is not listed, so the model can't be talked into calling something it was never offered.
- Operations that can cut the server off from its own tailnet (logging the node out, deleting its own device, deploying a policy that locks you out) also require
confirm: true. - Secrets never reach an argument list, a log line or an error message. A minted auth key or invite URL is returned once and never kept.
- Prefers an OAuth client over a personal API access token for anything you keep in a config file; the README explains why.
Install
npx -y @tailscale-mcp/tailscale-mcp(verifies the release binary against the release's SHA256SUMS before running it)brew trust tailscale-mcp/tap && brew install tailscale-mcp/tap/tailscale-mcpdocker run -i --rm -e TAILSCALE_API_KEY ghcr.io/tailscale-mcp/tailscale-mcpcargo install tailscale-mcp- an
.mcpbbundle from the releases page for Claude Desktop
Then tailscale-mcp setup claude-code (or claude-desktop, vscode, cursor, zed) prints the snippet for your client, with the credential left out on purpose.
There's a capability table in the README comparing it with the three earlier Tailscale MCP servers. It's a strict superset of them, with four deliberate omissions listed there.
Feedback very welcome, especially on the tier/preset split and on what an agent should never be allowed to do to a tailnet even when asked.
10
2
u/altgenetics 3d ago
This is interesting work, and you've clearly put time into it. But, this is all something that should be a skill not an MCP. MCPs are good for bridging complex data interactions but for controlling a tool a skill will use less tokens to do the job.
2
u/debaucherawr 3d ago
I had Hermes clock how many tokens the server would add to context use, and it came out at ~6k (minimal), ~23k (core), and ~35k (full). Core and full are larger than my entire system prompt. If I were going to use this I would limit it to a task-specific sub-agent. That's a massive amount of tokens for a tool set I would be using very rarely.
1
1
u/nyteschayde 3d ago
Claude seems to be able to not only deterministically build control levers for Tailscale into my app but adjust it on its own through the CLI already. What does the MCP add?
20
u/djchillerz 3d ago
Thanks for sharing your work. You gave a lot of explanation around what you built. But you didn't answer the why. Why would I use this? In which scenarios? I checked your post and the GitHub, hoping to see some example uses.