Tailscale: inspect and manage the devices, auth keys, users and ACL policy of a tailnet.
Tailscale ships in the w6w first-party pack. It declares 16 actions, 2 health checks, and the host runs its code in a sandbox that never sees the credential.
io.w6w.tailscaleTailscale builds a private network out of the devices that join it, and this app manages that network’s administrative side: list and inspect every device on a tailnet, approve or remove one, mint the keys that let a new device join, and read the access policy that decides who can reach what.
Device tagging and route approval are handled as careful merges rather than the plain replacements Tailscale’s own API performs — sending a device’s tags or its approved routes as a fresh list would otherwise silently drop whatever wasn’t included, quietly cutting off access nobody meant to touch. The access policy itself can be tested against a proposed change or the live network without ever installing anything, which covers the safe half of policy work; actually rewriting the policy file is left to a reviewed change elsewhere, the same way an infrastructure team already treats it.
Because Tailscale’s data plane is peer-to-peer, devices that have already connected keep working even if this app’s own calls are failing — an outage here means new devices can’t join or policies can’t change, not that the network itself is down. User management rounds it out: suspending someone stops them signing in without touching the devices they’re already using.
Three routes to the same 16 actions. The Workflow tab is generated from Tailscale's own manifest and carries its real ids, so it is copy-pasteable; the Code and CLI examples are the same call for any action on any app, so every app-specific value in them is a blank you fill in.
acl-get The tailnet policy file — every access rule there is. Returned as HUJSON verbatim, because the JSON form drops every comment and a policy file is mostly explanation. Reports Tailscale's WARNINGS: rules that parse and grant access to nobody.
acl-validate Ask whether access would be allowed, changing NOTHING. Send tests to run them against the live policy file, or a whole policy file to check one before anybody installs it — the safe half of policy automation, and the reason this app cannot write policy files.
device-authorize Approve a waiting machine, or cut a registered one off. Only meaningful when DEVICE APPROVAL is enabled for the tailnet — with it off, everything is authorized on arrival and this succeeds without doing anything.
device-delete Remove a machine from the tailnet — GONE, not archived, with its address returned to the pool. It does not stop Tailscale on the machine, so a device holding a valid auth key can rejoin as a new one.
device-expire-key Log a machine out of the tailnet — it cannot reach anything until somebody RE-AUTHENTICATES ON THE DEVICE, which for an unattended server may mean nobody can. There is no un-expire; de-authorizing is the reversible alternative.
device-get One machine in full. Reports how long its key has left — an expired key takes a device OFFLINE while leaving it listed — and whether expiry has been disabled, which is normal for a server and a decision for a laptop.
device-list Every machine in the tailnet. Tailscale has NO PAGINATION — the whole tailnet comes back at once — so the filters here are server-side. Separates shared-in external devices from real members, and flags devices whose node key is live on more than one machine.
device-routes-get What a machine ADVERTISES against what an admin has ENABLED — traffic flows only where the two overlap, and each half alone is a subnet router that silently carries nothing. Says whether the device is acting as an exit node.
device-routes-set Approve what a subnet router may carry. Tailscale REPLACES the enabled list, so `mode: add` merges rather than silently withdrawing every other route. Approving `0.0.0.0/0` makes the machine an EXIT NODE and needs an explicit acknowledgement.
device-tags-set Tag a machine, which TRANSFERS OWNERSHIP from the user who registered it to the tag and stops its key expiring. Tailscale REPLACES the whole tag list, so `mode: add` merges with what is already there rather than quietly dropping it.
dns-get Nameservers, MagicDNS, search paths and split DNS in one read — four endpoints that are one setting in practice. Removing the last nameserver turns MagicDNS OFF and adding one back does not turn it on, so the pair has to be looked at together.
key-create Mint a machine auth key. The secret is RETURNED ONCE and can never be retrieved again, so whatever receives it must store it there and then. Defaults to single-use, ephemeral and not preauthorized — the careful end of each choice.
key-delete Stop a key admitting anything new. Machines that ALREADY JOINED stay joined — they hold their own node keys — so evicting one is `device-delete`. Refuses to revoke an API token or OAuth client without an explicit acknowledgement, since that may be this connection's own.
key-list Auth keys, API access tokens and OAuth clients in one list. Flags the combination worth finding: a REUSABLE, PREAUTHORIZED key that never expires is a standing invitation into the tailnet.
user-list Who is in the tailnet and in what state — including the two states that mean someone is LOCKED OUT with no error to explain it: awaiting approval, and blocked by the plan's user limit. Separates shared users, who are somebody else's staff.
user-suspend Stop a person authenticating, or let them back. Their EXISTING DEVICES KEEP WORKING until their keys expire — up to 180 days, or never for a tagged machine — so full offboarding is this plus expiring those devices, and the count comes back here.
A workflow step names the app and the action, and the editor fills in the
connection when you pick one. This is the Step shape from the
workflow spec, carrying Tailscale's real ids.
{
"manifestVersion": "2",
"name": "tailscale-example",
"steps": [
{
"id": "device-authorize",
"uses": {
"app": "io.w6w.tailscale",
"action": "device-authorize",
"connection": "conn_YOUR_CONNECTION_ID"
},
"with": {
"deviceId": "<deviceId>"
}
}
]
}device-authorize acl-get acl-validate device-expire-key device-get +11 more actions available
Every app-specific value here is a blank you have to fill in. An
app action is reached through the connection that authenticates it, so the
address is a connection id, not the app id — and connections belong to your account,
so a public page cannot know yours. Create one for Tailscale, then fill in
the three blanks: conn_YOUR_CONNECTION_ID, the action key, and the
parameters that action declares. The call itself is real — the shape is transcribed
from the studio's own snippet builder, which prints the same kind of blanks — but
nothing in it is specific to Tailscale. The Workflow tab is where this app's
real ids are.
npm install @w6w/sdkyarn add @w6w/sdkpnpm add @w6w/sdkdeno add npm:@w6w/sdkimport { W6wClient, isActionRun } from "@w6w/sdk";
// Reads W6W_BASE_URL and W6W_TOKEN from the environment when omitted.
const client = new W6wClient();
const envelope = await client.run({
urn: "conn_YOUR_CONNECTION_ID",
action: "device-authorize",
payload: {
deviceId: "<value>",
// authorized: "<value>",
},
});
if (isActionRun(envelope)) console.log(envelope.value); npm install -g @w6w/cli w6w run conn_YOUR_CONNECTION_ID --action device-authorize --payload '{"deviceId":"<value>"}' Give an AI agent Tailscale — without giving it Tailscale's credentials. One MCP endpoint exposes every app, function and workflow the caller is entitled to, as tools it can discover and run. Access is granted per team while we onboard.
One tool call{
"name": "w6w_invoke",
"arguments": {
"ref": "app:io.w6w.tailscale#device-authorize",
"input": {
"deviceId": "<deviceId>"
}
}
}
Every tool names its target with a single ref. The
app: form above doesn't name a connection at all — the
host resolves which of the caller's Tailscale connections to sign
with, and refuses rather than guesses when the answer is ambiguous.
The token is attached host-side, at the moment of the call. It is never a tool argument, never in the model's context, and never in a transcript — so a prompt injection has nothing to exfiltrate.
Tools are derived per end user from what that person has actually connected and is entitled to — not one shared bot identity carrying the union of everyone's access.
Multi-step work runs on the workflow engine and returns a run handle the agent can poll — retries, branching and state survive the conversation that started them.
Tailscale's declared health checks are on the surface too, so an agent can tell "the vendor is down" from "your credential expired" before it burns a retry on either.
The MCP surface is part of the hosted platform. Tailscale itself is MIT, and the runtime that executes it is source-available (FSL).
Tailscale declares its own checks, so its health is a property of the app rather than something the host guesses at.
Reads Tailscale's Statuspage, weighting the API component that every action here calls. Reports the COORDINATION SERVICE and DERP relays separately and never as this app's failure — Tailscale's data plane is peer-to-peer, so an API outage stops CHANGE rather than traffic.
Declared unavailable — measured live, Tailscale publishes no rate-limit header of any kind. The ceiling that actually binds an account is the plan's USER and DEVICE count, which `user-list` and `device-list` report.