Install
$ agentstack add mcp-yawlabs-tailscale-mcp ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ● Shell / process execution Used
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
@yawlabs/tailscale-mcp
[](https://www.npmjs.com/package/@yawlabs/tailscale-mcp) [](https://opensource.org/licenses/MIT) [](https://github.com/YawLabs/tailscale-mcp/stargazers) [](./release.sh) [](https://x.com/TokenLimitNews)
Ask your agent questions about your tailnet and have it act on the answers. 97 admin-API tools + 6 optional local-CLI diagnostics + 1 always-on catalog tool + 4 resources spanning the Tailscale v2 API — devices, ACLs, DNS, keys and trust credentials, users, invites, webhooks, log streaming, posture, services, and organization tailnets. Backed by 1100+ unit tests and an opt-in live-tailnet integration suite.
Built and maintained by Yaw Labs.
[](https://yaw.sh/mcp/install?name=Tailscale&command=npx&args=-y%2C%40yawlabs%2Ftailscale-mcp&description=Manage%20your%20Tailscale%20tailnet%20-%20devices%2C%20ACLs%2C%20DNS%2C%20keys&source=https%3A%2F%2Fgithub.com%2FYawLabs%2Ftailscale-mcp)
One click adds this to your local Yaw MCP config so it's available in every Yaw Terminal session. Or install manually below.
What's the point if the API already exists?
You could curl the Tailscale API. The point isn't replacing curl — it's letting an agent compose multi-endpoint workflows in one turn without writing a script:
- "Which devices haven't checked in for 30 days and have key expiry disabled?" — lists devices, filters by
lastSeen, filters bykeyExpiryDisabled, returns a table. Three endpoints, one question. - "Someone broke DNS at 2am — who changed what in the last 24 hours?" — pulls the audit log, filters by DNS-related actors and endpoints, reads each change's before/after, summarizes in English.
- "Draft an ACL change that lets
tag:mobilereachtag:dashboardbut nottag:db, preserving my comments" — reads the current HuJSON, proposes a minimal diff, validates it against the API, returns the diff for you to apply. - "Rotate every auth key older than 90 days and print the new ones" — iterates, creates new keys with matching tags, revokes the old ones.
- "Create an OAuth client for our CI pipeline scoped to
devices:readanddns" — creates a trust credential viatailscale_create_keywithkeyType=client, returns the credentials once (save them immediately).
A curl can do each step. The agent composes them. That's where the lift is, and that's what the tool surface is designed for — every read endpoint is first-class so the agent can synthesize, and every write endpoint is tagged destructiveHint or idempotentHint so your MCP client can gate mutations the way you configured it.
If all you need is one endpoint in a CI job, use curl — we even have a [CLI subcommand](#gitops-deploy-acls-from-ci) for the common ACL-from-git case. The MCP is for the interactive, exploratory, "I don't know what I need yet" work.
Why MCP vs. a skill or the tailscale CLI?
Reasonable question. Both have their place. Where this MCP is better:
- Broad admin API coverage. The
tailscaleCLI is scoped to the node it runs on. Admin concerns — ACLs, users, invites, webhooks, log streaming, posture integrations, auth keys, OAuth clients, and federated identities — live in the v2 HTTP API. You'd be shelling out tocurlanyway. - Typed tool surface, not string parsing. Every tool has a Zod-validated input schema and a structured response. No brittle
tailscale status --json | jqpipelines that break when the schema evolves. - Cross-client, no user rewriting. A Claude Code skill only loads in Claude Code. An MCP server works in Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, and anything else that speaks MCP. Version bumps ship through
npx— users don't re-author their skill when Tailscale adds an endpoint. - Safe-by-default writes. Every tool declares
readOnlyHint/destructiveHint/idempotentHintso clients can skip confirmation on reads and require it on mutations. A skill that shells out to the CLI can't express that. - Real tests. 700+ unit tests covering every tool's input validation, API shape, and error handling. Plus an opt-in live-tailnet integration suite (
RUN_INTEGRATION_TESTS=1+ a tailnet API key) for shape-drift detection. Most skills are short markdown prompts without their own test layer — if the vendor changes output format, nothing catches it for you.
If you already have a skill that covers your 10% of Tailscale workflows, great — keep it. The MCP is for the other 90%.
Trust signals
Fair critique from Reddit: a new repo claiming "actively maintained" with no visible tests is worth exactly zero trust. Here's what's actually verifiable:
- 700+ tests (
node --test) covering every tool's input validation, API shape, and error handling. Runnpm testto see them pass locally. - Local release flow via [
release.sh](./release.sh): lint + test + bump + tag + push + npm publish + MCP Registry publish, all from the workstation. No CI workflow to babysit. - Dependabot alerts surface on this repo and get fixed, not ignored.
- Every tool verified against the live API. If it's in the tool list, it calls a real endpoint that exists in the current v2 API. No placeholder 404 tools.
Issues and PRs are triaged. File one if something is off — github.com/YawLabs/tailscale-mcp/issues.
Quick start
1. Set your API key
Get an API key from Tailscale Admin Console > Settings > Keys and add it to your shell profile (~/.bashrc, ~/.zshrc, or Windows system environment variables):
export TAILSCALE_API_KEY="tskey-api-..."
2. Create .mcp.json in your project root
macOS / Linux / WSL:
{
"mcpServers": {
"tailscale": {
"command": "npx",
"args": ["-y", "@yawlabs/tailscale-mcp@latest"]
}
}
}
Windows:
{
"mcpServers": {
"tailscale": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@yawlabs/tailscale-mcp@latest"]
}
}
}
> Why the extra step on Windows? On Windows, npx is a .cmd file, and Node 20+ refuses to spawn .cmd files directly. Wrapping with cmd /c is the standard workaround.
3. Restart and approve
Restart Claude Code (or your MCP client) and approve the Tailscale MCP server when prompted.
That's it. Now ask your agent:
> "List my Tailscale devices that haven't been seen in the last 7 days" > > "Summarize every ACL change in the audit log from yesterday" > > "Draft an ACL rule that lets tag:ci reach tag:registry on port 5000 only"
Too many tools? Subset them.
97 tools is a lot. If you've already got a dozen MCP servers and your client is feeling heavy, trim what this one exposes. Three knobs, combinable:
> The env blocks below show only the variable under discussion. Your credentials come from the environment, as set in [Quick start](#quick-start) — keep them in your shell profile rather than in the client's JSON config, which is world-readable on most systems and easy to commit by accident.
Option 1: TAILSCALE_PROFILE (preset, easiest)
{
"env": {
"TAILSCALE_PROFILE": "core"
}
}
minimal(20 tools) —status,devices,audit. Observe the tailnet, read the audit log.core(52 tools) — addsacl,dns,keys,users. The day-to-day admin surface.full(97 tools, default) — everything. Same as omitting the env var.
Option 2: TAILSCALE_TOOLS (explicit group list)
{
"env": {
"TAILSCALE_TOOLS": "devices,acl,dns,audit"
}
}
Comma-separated group names. Overrides TAILSCALE_PROFILE when both are set — use this when the presets aren't quite right.
Valid group names: status, devices, acl, dns, keys, users, tailnet, webhooks, posture, audit, invites, services, log-streaming. The local-cli group is also available, but only when TAILSCALE_LOCAL_CLI=1 is set — see [Local CLI integration](#local-cli-integration-opt-in).
Option 3: TAILSCALE_READONLY (drop mutations)
{
"env": {
"TAILSCALE_PROFILE": "core",
"TAILSCALE_READONLY": "1"
}
}
Set to 1 or true to drop every tool without readOnlyHint: true. Stacks with TAILSCALE_PROFILE or TAILSCALE_TOOLS as an intersection — combine for maximum minimalism.
Confirming what loaded
The server logs the active filter to stderr on startup:
@yawlabs/tailscale-mcp v0.12.0 ready (20 tools, profile=minimal, readonly)
When both TAILSCALE_PROFILE and TAILSCALE_TOOLS are set, TAILSCALE_TOOLS wins. The banner marks the profile as overridden so the precedence is obvious at a glance — no need to guess which filter actually applied:
@yawlabs/tailscale-mcp v0.12.0 ready (22 tools, profile=core (overridden by TAILSCALE_TOOLS), groups=devices,acl)
The "(overridden)" marker only fires for substantive profiles (minimal / core); profile=full is a no-op preset, so it's shown without the marker when TAILSCALE_TOOLS is also set.
If you don't set any filter, startup prints a tip pointing you at the profiles.
And how the agent knows
Everything above is stderr -- your MCP client's log. The model never sees it, so a withheld tool and a tool that was never built look identical from the agent's side. That is how an agent ends up working around a restriction instead of reporting it.
tailscale_tool_groups closes that gap. It is always registered, whatever the filters say, and answers the question in-band:
> "Why can't you delete that device?"
tailscale_tool_groups({ toolName: "tailscale_delete_device" })
{
"tool": "tailscale_delete_device",
"available": false,
"group": "devices",
"kind": "write",
"reason": "TAILSCALE_WRITE_GROUPS is set to \"dns\", which does not grant writes here",
"toEnable": "add \"devices\" to TAILSCALE_WRITE_GROUPS (e.g. \"dns,devices\")"
}
It separates the three cases an agent otherwise cannot tell apart:
| Case | What the agent should do | |---|---| | No such tool exists, under any configuration | Find another approach -- no setting will produce it | | Exists, but its group is not loaded | Report toEnable to you; do not work around it | | Exists and loaded, but writes are withheld there | Same -- the fix is yours, not a workaround |
Called with no arguments it lists every group with its availability and, for anything withheld, the exact environment change that would restore it. It reads no network and needs no credentials, so it works even when the server is misconfigured.
Scoping writes to areas
TAILSCALE_WRITE_GROUPS names the areas an agent may write to. Everything else stays readable:
{
"env": {
"TAILSCALE_WRITE_GROUPS": "devices,keys"
}
}
That serves all 47 read tools plus the 18 writes in devices and keys, and withholds the other 38 writes — the ACL, DNS, users, webhooks, posture, services, invites, org-tailnets and log-streaming writes are simply not registered. Unset means no write gate, which is the shipped default.
> Read this first: this filters the tool list, not your API token. The server still holds one credential with full tailnet authority in every configuration. An agent that also has a shell can curl api.tailscale.com with that same token and do everything this knob withheld. Scope the Tailscale OAuth client itself to the areas you actually need — that bound survives outside this process; this one does not. TAILSCALE_WRITE_GROUPS is the low-friction complement to credential scoping, not a replacement for it.
What a grant actually contains
Group names are the same ones TAILSCALE_TOOLS uses. Writes per group:
| Group | Writes | Group | Writes | |---|---|---|---| | devices | 13 | webhooks | 5 | | invites | 7 | posture | 3 | | dns | 6 | services | 3 | | keys | 5 | tailnet | 3 | | users | 5 | log-streaming | 3 | | org-tailnets | 2 | acl | 1 |
status, audit and the opt-in local-cli group contain no writes at all, so granting them does nothing.
devices is the widest grant, and the one most likely to be set. In a compose file write=devices reads like "device admin", but it hands over delete_device, set_devices_authorized, expire_device, deauthorize_device, set_device_routes, set_device_tags and update_device_key alongside rename_device. There is no finer setting: an honest "safe subset" of devices is rename_device alone, and a knob whose useful value is one tool is not a knob.
Three grants are tailnet-admin-equivalent
keys, users and acl are not blocked — CI key rotation legitimately needs keys — but grant them knowing:
keys—tailscale_create_keymints an OAuth client with whatever scopes the caller asks for, includingacl. That credential outlives the agent's session and is not subject to this or any other setting here.users—tailscale_update_user_roleacceptsowner.acl—tailscale_update_aclrewrites policy for every principal in the tailnet.
The server prints this on startup when your grant includes one of them.
What it does and does not bound
It bounds where an agent may write. It does not bound severity within a granted area: inside a granted group, writes run unattended, including the grant-direction ones. Pair it with TAILSCALE_REQUIRE_APPROVAL=1 when a human is at the keyboard — but understand that for an unattended agent that pairing contributes nothing, because a never-prompt client denies those calls rather than prompting.
Precedence, and what happens when you get it wrong
| Situation | Result | |---|---| | Unset, empty, whitespace, or commas-only | No write gate. -e VAR with no value must not silently revoke every write. | | TAILSCALE_READONLY=1 also set | Readonly wins; banner says readonly (TAILSCALE_WRITE_GROUPS ignored). | | A name is misspelled (devises) | Grants nothing and names the typo. Unlike TAILSCALE_TOOLS, there is no fallback — a typo'd write grant that fell back would hand over all 56 writes at the moment you were restricting them. | | Partly misspelled (devices,dnss) | Grants the valid half, warns about the rest. | | A granted group is not loaded by TAILSCALE_TOOLS / TAILSCALE_PROFILE | The grant has no effect; a separate warning says so, since the name is not a typo. | | none, all, off, * | Not reserved words — they are unknown group names, so they grant nothing. Use TAILSCALE_READONLY=1 for no writes, or leave this unset for all writes. The server points you at the right spelling. |
Names are case-sensitive, matching TAILSCALE_TOOLS.
The upgrade contract: upgrading this package can never widen the set of areas an agent may write to. A new group is in nobody's grant until a human types its name. It can, however, add tools inside an area you already granted — a pinned test makes that a reviewed line in the diff rather than a silent change.
The startup banner shows what applied:
@yawlabs/tailscale-mcp v0.19.0 ready (59 tools, write=devices,keys)
Requiring approval on irreversible tools
readOnlyHint / destructiveHint are advisory — the MCP spec says clients MUST treat annotations as untrusted, and most don't gate on them. TAILSCALE_REQUIRE_APPROVAL=1 adds a stronger signal that supported clients enforce:
{
"env": {
"TAILSCALE_REQUIRE_APPROVAL": "1"
}
}
Nine tools are then advertised with _meta["anthropic/requiresUserInteraction"], which forces a confirmation prompt even when an allow-rule would otherwise auto-approve the call:
| Tool | Why it's on the list | |---|---| | tailscale_update_acl | Can lock every device out of the tailnet; the previous HuJSON (comments included) is gone unless you captured it | | tailscale_delete_device | The device must re-enroll | | tailscale_delete_user | No undelete | | `tailscale_dele
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: YawLabs
- Source: YawLabs/tailscale-mcp
- License: MIT
- Homepage: https://yaw.sh/mcp-servers/tailscale-mcp/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.