Ask your agent questions about your tailnet and have it act on the answers - devices, ACLs, DNS, keys, users and audit logs, from Claude Code, Cursor, or any MCP client.
@yawlabs/tailscale-mcp puts the Tailscale v2 API behind a typed tool surface an agent can compose: devices, ACLs, DNS, keys and trust credentials, users, invites, webhooks, log streaming, posture, services, and organization tailnets. Built and maintained by Yaw Labs, MIT licensed, published on npm.
npx -y @yawlabs/tailscale-mcp@latestGitHub · npm · Add to Yaw MCP
Anyone who administers a tailnet and would rather ask a question than write a script. You could curl the Tailscale API - the point is not replacing curl, it is letting an agent compose multi-endpoint workflows in one turn:
If all you need is one endpoint in a CI job, use curl - or the ACL CLI subcommands below. The MCP server is for the interactive, exploratory, "I don't know what I need yet" work.
1. Set your API key. Create one in the Tailscale admin console under Settings > Keys, then add it to your shell profile (~/.bashrc, ~/.zshrc, or Windows system environment variables) rather than to your client's JSON config, which is world-readable on most systems and easy to commit by accident.
export TAILSCALE_API_KEY="tskey-api-..."2. Add the server to your client. On macOS, Linux and WSL, a .mcp.json in your project root:
{
"mcpServers": {
"tailscale": {
"command": "npx",
"args": ["-y", "@yawlabs/tailscale-mcp@latest"]
}
}
}On Windows, wrap the call in cmd /c: npx is a .cmd file there, and Node 20+ refuses to spawn .cmd files directly.
{
"mcpServers": {
"tailscale": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@yawlabs/tailscale-mcp@latest"]
}
}
}3. Restart and approve. Restart Claude Code (or your MCP client) and approve the Tailscale server when prompted. Then ask it something: "List my Tailscale devices that haven't been seen in the last 7 days."
Running Yaw MCP? One click adds it to your local config, and it is then available in every Yaw Terminal session.
Tools are organized into groups, and the group names are also what you pass when you want to load only some of them:
| Group | What it covers |
|---|---|
status | Verify the API connection, tailnet info, device count |
devices | List and inspect devices, authorize, expire, rename, tag, routes, posture attributes, delete |
acl | Get, validate, preview and update policy, plus an access diff of a proposed policy against the live one |
dns | Nameservers, search paths, split DNS, MagicDNS preferences, unified get/set |
keys | Auth keys, OAuth clients, federated identities, OAuth apps |
users | List, approve, suspend, restore, change role, delete |
tailnet | Tailnet settings and contacts |
webhooks | Create, update, delete, rotate the secret, send a test event |
posture | Device-posture integrations |
audit | Configuration audit log and network flow logs |
invites | Device invites and user invites |
services | Tailscale Services, their hosts, and per-device approval |
log-streaming | Where audit and network logs are sent, delivery status, AWS S3 trust-policy helpers |
local-cli | Opt-in read-only diagnostics from the local tailscale binary |
ACL work is the part most worth calling out: tailscale_get_acl returns the policy with its HuJSON formatting and comments preserved plus an ETag, and tailscale_update_acl requires that ETag, so concurrent edits conflict instead of silently overwriting each other. tailscale_diff_acl_access answers the question review actually cares about: who gains and loses access.
Four MCP resources expose read-only data a client can browse without a tool call: tailnet status (tailscale://tailnet/status), devices, the ACL policy with HuJSON preserved, and the DNS configuration.
If your client is already carrying a dozen MCP servers, trim what this one exposes. Three knobs, combinable, all set in the env block of your client config:
TAILSCALE_PROFILE - a preset: minimal (status, devices, audit), core (adds ACLs, DNS, keys, users - the day-to-day admin surface), or full, the default.TAILSCALE_TOOLS - an explicit comma-separated group list, for when the presets are not quite right. Overrides TAILSCALE_PROFILE when both are set.TAILSCALE_READONLY=1 - drops every tool that is not marked read-only. Intersects with the other two.The server logs the active filter to stderr at startup, so you can confirm what actually loaded. Your agent gets the same answer in-band: tailscale_tool_groups is always registered whatever the filters say, and tells the model whether a tool does not exist at all, exists but is not loaded, or exists with writes withheld - plus the exact environment change that would restore it. Without that, a withheld tool and a tool that was never built look identical from the agent's side, which is how an agent ends up working around a restriction instead of reporting it.
readOnlyHint, destructiveHint and idempotentHint let a client skip confirmation on reads and require it on mutations.TAILSCALE_WRITE_GROUPS=devices,keys registers the writes in those groups and withholds the rest; everything stays readable. A misspelled group grants nothing and names the typo rather than falling back to all writes. Upgrading the package can never widen the set of areas an agent may write to.TAILSCALE_REQUIRE_APPROVAL=1 advertises the tools this server cannot undo with information you still hold - the ACL rewrite, the deletes - with the annotation that makes supported clients confirm even when an allow-rule would auto-approve. It is opt-in because a client that never prompts will deny those calls instead of running them.Retry-After, with jittered exponential backoff and a wall-clock budget per request; 429s on POST and PATCH are never retried.TAILSCALE_DEBUG=1 logs method, URL, status and elapsed time to stderr - authorization headers are never logged.tailscale up, route advertising, lock signing) are deliberately not exposed.For the plain "deploy the ACL from git on merge" workflow you do not need an MCP server or a model at all - the same package ships the CLI:
# PR check: validate the proposed policy without touching the tailnet
npx -y @yawlabs/tailscale-mcp@latest validate-acl tailscale/acl.json
# On merge: ETag fetch + validate + deploy with If-Match, fail-closed at every step
npx -y @yawlabs/tailscale-mcp@latest deploy-acl tailscale/acl.jsonWorks in any CI system - set TAILSCALE_API_KEY and TAILSCALE_TAILNET as environment variables. Both commands exit non-zero on any failure, and deploy-acl refuses to deploy without an ETag, reporting a 412 as the concurrent-edit conflict it is. Hand-rolling this with curl has a trap worth knowing about: Tailscale returns the ETag header on GET but not on HEAD, so a curl -I fetch yields an empty value and an empty If-Match either deploys unguarded or fails the job.
It exposes the Tailscale v2 admin API as MCP tools, so an agent can answer questions about your tailnet and act on the answers in the same turn. Devices, ACLs, DNS, auth keys and trust credentials, users, invites, webhooks, log streaming, posture, services and organization tailnets are all covered, along with read-only resources a client can browse without a tool call.
Any client that speaks MCP. Unlike a Claude Code skill, which only loads in Claude Code, this server works in Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, and anything else that speaks the protocol. Version bumps ship through npx, so you do not re-author anything when Tailscale adds an endpoint.
Set TAILSCALE_API_KEY for the simple path, or TAILSCALE_OAUTH_CLIENT_ID and TAILSCALE_OAUTH_CLIENT_SECRET for scoped OAuth access. The server checks for an API key first and falls back to OAuth. If neither is set it still starts, and tools return a clear error telling you what to configure, so your client does not loop restarting. Your default tailnet is used automatically; set TAILSCALE_TAILNET to name one explicitly.
Yes. TAILSCALE_READONLY=1 drops every tool that is not marked read-only, and TAILSCALE_WRITE_GROUPS names the areas writes are allowed in while everything else stays readable. Read the caveat first: those knobs filter the tool list, not your API token. The server still holds one credential with full tailnet authority, so an agent that also has a shell can reach the API directly. Scope the Tailscale OAuth client itself to the areas you actually need.
No. The same package ships validate-acl and deploy-acl CLI subcommands for the deploy-ACL-from-git workflow. Both exit non-zero on any failure, and deploy-acl refuses to deploy without an ETag, so a concurrent Admin Console edit can never be silently clobbered. Use the MCP server for interactive, exploratory work and the CLI for the merge hook.
Published by Yaw Labs.