@yawlabs/tailscale-mcp
Tailscale MCP server: admin-API tools for devices, ACLs, DNS, auth keys, users, and audit logs, plus a CLI to validate and deploy ACLs.
npm · fingerprint 051d7b379b3535def9627846 · repository · RSS
98tools
2recorded versions
24dtracked
last checked 20 Oct
What changed
Changed between 6 October 2026 and 10 October 2026 (first seen 2026-10-10 12:34 UTC)
17 descriptions rewritten, 22 schema or annotation changes with the description left byte-identical.
--- pinned/tailscale_approve_user +++ observed/tailscale_approve_user { "annotations": { "destructiveHint": false, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_batch_update_posture_attributes +++ observed/tailscale_batch_update_posture_attributes { "annotations": { "destructiveHint": false, - "description": "Batch update custom posture attributes across multiple devices. Each attribute key must start with 'custom:'. Uses JSON Merge Patch semantics — pass null as the attribute config to delete.", + "description": "Batch update custom posture attributes across multiple devices. Each attribute key must start with 'custom:'. Uses JSON Merge Patch semantics -- pass null as the attribute config to delete.", "execution": { "taskSupport": "forbidden" },
--- pinned/tailscale_create_device_invite +++ observed/tailscale_create_device_invite { "annotations": { "destructiveHint": false, + "minLength": 1, "type": "string" }, "email": {
--- pinned/tailscale_create_key +++ observed/tailscale_create_key { "annotations": { "destructiveHint": false, - "type": "number" + "exclusiveMinimum": 0, + "maximum": 9007199254740991, + "type": "integer" }, "issuer": { "description": "(federated only) OIDC issuer URL (e.g. 'https://token.actions.githubusercontent.com')",
--- pinned/tailscale_delete_device +++ observed/tailscale_delete_device { "annotations": { "destructiveHint": true, - "description": "Permanently remove a device from the tailnet. This is irreversible — the device must re-authenticate to rejoin.", + "description": "Permanently remove a device from the tailnet. This is irreversible -- the device must re-authenticate to rejoin.", "execution": { "taskSupport": "forbidden" }, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_delete_device_invite +++ observed/tailscale_delete_device_invite { "annotations": { "destructiveHint": true, - "description": "Delete a device invite. This is irreversible — the invite link will stop working.", + "description": "Delete a device invite. This is irreversible -- the invite link will stop working.", "execution": { "taskSupport": "forbidden" }, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_delete_key +++ observed/tailscale_delete_key { "annotations": { "destructiveHint": true, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_delete_posture_integration +++ observed/tailscale_delete_posture_integration { "annotations": { "destructiveHint": true, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_delete_service +++ observed/tailscale_delete_service { "annotations": { "destructiveHint": true, - "description": "Delete a Tailscale Service. This is irreversible — the service's MagicDNS name and virtual IP will be released.", + "description": "Delete a Tailscale Service. This is irreversible -- the service's MagicDNS name and virtual IP will be released.", "execution": { "taskSupport": "forbidden" }, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_delete_user +++ observed/tailscale_delete_user { "annotations": { "destructiveHint": true, - "description": "Delete a user from the tailnet. This is irreversible — the user and all their devices will be removed.", + "description": "Delete a user from the tailnet. This is irreversible -- the user and all their devices will be removed.", "execution": { "taskSupport": "forbidden" }, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_delete_user_invite +++ observed/tailscale_delete_user_invite { "annotations": { "destructiveHint": true, - "description": "Delete a user invite. This is irreversible — the invite link will stop working.", + "description": "Delete a user invite. This is irreversible -- the invite link will stop working.", "execution": { "taskSupport": "forbidden" }, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_delete_webhook +++ observed/tailscale_delete_webhook { "annotations": { "destructiveHint": true, - "description": "Delete a webhook. This is irreversible — the webhook secret cannot be recovered.", + "description": "Delete a webhook. This is irreversible -- the webhook secret cannot be recovered.", "execution": { "taskSupport": "forbidden" }, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_diff_acl_access +++ observed/tailscale_diff_acl_access { "_meta": { "anthropic/maxResultSizeChars": 500000 - "description": "Answer 'who loses access?' before applying an ACL change. Compares the CURRENT policy against a proposed one and reports, per user, which destinations they gain and lose. Run this before tailscale_update_acl -- validate_acl only checks syntax and the policy's own tests block, so a policy with no tests validates clean while revoking everyone. LIMITS, all reported in the response rather than left to be discovered. It compares USER principals only, so a revocation that runs through a tag or group can show a clean diff, and an empty result is never proof a change is safe. Posture DEFINITION changes ARE detected: posture names are resolved to their rules, so tightening `posture:corp` shows as a change -- except when a preview omits the definitions map, where it falls back to comparing names. It costs two preview requests per user, so it checks the first 25 by default and stops after 60 seconds regardless; either way it sets `truncated`, reports how many were skipped, and says which limit stopped it. Users whose preview fails are listed in `failed` and excluded from the compared count -- a failure is never reported as lost access, and if nothing could be compared the call fails rather than returning an empty diff.", + "description": "Answer 'who loses access?' before applying an ACL change. Compares the CURRENT policy against a proposed one and reports, per user, which destinations they gain and lose. Run this before tailscale_update_acl -- validate_acl only checks syntax and the policy's own tests block, so a policy with no tests validates clean while revoking everyone. LIMITS, all reported in the response rather than left to be discovered. It compares USER principals only, so a revocation that runs through a tag or group can show a clean diff, and an empty result is never proof a change is safe. Posture DEFINITION changes ARE detected: posture names are resolved to their rules, so tightening `posture:corp` shows as a change -- except when a preview omits the definitions map, where it falls back to comparing names. It costs two preview requests per user, so it checks the first 25 by default and stops starting new users after 60 seconds (a user whose previews are already in flight still finishes); either way it sets `truncated`, reports how many were skipped, and says which limit stopped it. Users whose preview fails are listed in `failed` and excluded from the compared count -- a failure is never reported as lost access, and if nothing could be compared the call fails rather than returning an empty diff.", "execution": { "taskSupport": "forbidden" },
--- pinned/tailscale_get_acl +++ observed/tailscale_get_acl { "_meta": { "anthropic/maxResultSizeChars": 500000 - "description": "Get the current ACL policy for your tailnet. Returns the raw policy text with original formatting preserved, including comments and trailing commas (HuJSON). Also returns an ETag — you must pass it to tailscale_update_acl to safely update the policy.", + "description": "Get the current ACL policy for your tailnet. Returns the raw policy text with original formatting preserved, including comments and trailing commas (HuJSON). Also returns an ETag -- you must pass it to tailscale_update_acl to safely update the policy.", "execution": { "taskSupport": "forbidden" },
--- pinned/tailscale_get_device_invite +++ observed/tailscale_get_device_invite { "annotations": { "destructiveHint": false, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_get_key +++ observed/tailscale_get_key { "annotations": { "destructiveHint": false, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_get_network_flow_logs +++ observed/tailscale_get_network_flow_logs { "_meta": { "anthropic/maxResultSizeChars": 500000 - "description": "Get network traffic flow logs showing connections between devices. Shows source/destination nodes, timestamps, and traffic metadata — useful for security monitoring and debugging connectivity.", + "description": "Get network traffic flow logs showing connections between devices. Shows source/destination nodes, timestamps, and traffic metadata -- useful for security monitoring and debugging connectivity.", "execution": { "taskSupport": "forbidden" },
--- pinned/tailscale_get_posture_integration +++ observed/tailscale_get_posture_integration { "annotations": { "destructiveHint": false, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_get_user +++ observed/tailscale_get_user { "annotations": { "destructiveHint": false, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_get_user_invite +++ observed/tailscale_get_user_invite { "annotations": { "destructiveHint": false, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_get_webhook +++ observed/tailscale_get_webhook { "annotations": { "destructiveHint": false, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_list_device_invites +++ observed/tailscale_list_device_invites { "annotations": { "destructiveHint": false, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_list_services +++ observed/tailscale_list_services { "annotations": { "destructiveHint": false, - "description": "List all Tailscale Services in your tailnet. Services provide stable MagicDNS names and virtual IPs, decoupled from individual devices. Note: services are created implicitly when a node first advertises one (`tailscale up --advertise-services=svc:name`); there is no API endpoint to create a service from this MCP. Use the update/delete/approval tools here once the service exists.", + "description": "List all Tailscale Services in your tailnet. Services provide stable MagicDNS names and virtual IPs, decoupled from individual devices. Note: this MCP has no create-service tool. The API documents its Service PUT as create-or-update, but tailscale_update_service sends only the fields you pass, so do not use it to create one. Use the update/delete/approval tools here once the service exists.", "execution": { "taskSupport": "forbidden" },
--- pinned/tailscale_resend_device_invite +++ observed/tailscale_resend_device_invite { "annotations": { "destructiveHint": false, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_resend_user_invite +++ observed/tailscale_resend_user_invite { "annotations": { "destructiveHint": false, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_restore_user +++ observed/tailscale_restore_user { "annotations": { "destructiveHint": false, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_rotate_webhook_secret +++ observed/tailscale_rotate_webhook_secret { "annotations": { "destructiveHint": false, - "description": "Rotate a webhook's secret. Returns the new secret — save it immediately, as it cannot be retrieved again. The old secret is immediately invalidated.\n\nSECURITY: the response body contains the secret verbatim. MCP clients commonly persist tool responses to logs and conversation transcripts; treat this response as sensitive.", + "description": "Rotate a webhook's secret. Returns the new secret -- save it immediately, as it cannot be retrieved again. The old secret is immediately invalidated.\n\nSECURITY: the response body contains the secret verbatim. MCP clients commonly persist tool responses to logs and conversation transcripts; treat this response as sensitive.", "execution": { "taskSupport": "forbidden" }, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_set_device_routes +++ observed/tailscale_set_device_routes { "annotations": { "destructiveHint": true, - "description": "Set the enabled subnet routes for a device. Replaces all currently enabled routes — pass the full list of routes you want enabled.", + "description": "Set the enabled subnet routes for a device. Replaces all currently enabled routes -- pass the full list of routes you want enabled.", "execution": { "taskSupport": "forbidden" },
--- pinned/tailscale_set_device_tags +++ observed/tailscale_set_device_tags { "annotations": { "destructiveHint": true, - "description": "Set ACL tags on a device. Replaces all existing tags — pass the full list of tags you want applied.", + "description": "Set ACL tags on a device. Replaces all existing tags -- pass the full list of tags you want applied.", "execution": { "taskSupport": "forbidden" },
--- pinned/tailscale_set_devices_authorized +++ observed/tailscale_set_devices_authorized { "annotations": { "destructiveHint": true, - "description": "Authorize or deauthorize multiple devices in one call. Each device's POST runs in parallel; per-device errors are returned alongside the successes so a partial failure doesn't lose the work that succeeded. On partial failure the call still returns success (ok) with data: { authorized, succeeded, failed } -- inspect data.failed for the per-device errors. Common use: authorize a batch of newly-enrolled CI hosts, or deauthorize a group of devices flagged by a security review.", + "description": "Authorize or deauthorize multiple devices in one call. Each device's POST runs in parallel; per-device errors are returned alongside the successes so a partial failure doesn't lose the work that succeeded. On partial failure the call still returns success (ok) with data: { authorized, succeeded, failed } -- inspect data.failed for the per-device errors. POSTs are never retried, so on a large batch a rate-limited device lands in data.failed with status 429: re-call with just those ids, or set TAILSCALE_MAX_CONCURRENT to cap requests in flight. Common use: authorize a batch of newly-enrolled CI hosts, or deauthorize a group of devices flagged by a security review.", "execution": { "taskSupport": "forbidden" },
--- pinned/tailscale_set_dns_configuration +++ observed/tailscale_set_dns_configuration { "annotations": { "destructiveHint": true, - "description": "Set the unified DNS configuration for your tailnet in a single call. Replaces all DNS settings (nameservers, search paths, split DNS, MagicDNS preference).", + "description": "Set the unified DNS configuration for your tailnet in a single call. Replaces all DNS settings (nameservers, search paths, split DNS, MagicDNS preference). This is a replace-all endpoint: pass every top-level field you want to keep, because a field you omit may be cleared rather than left alone.", "execution": { "taskSupport": "forbidden" }, - "items": { - "type": "string" - }, - "type": "array" + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ] }, - "description": "Map of domain to nameserver list for split DNS", + "description": "Map of domain to nameserver list for split DNS, or to null to clear that domain", "propertyNames": { "type": "string" },
--- pinned/tailscale_set_log_stream_config +++ observed/tailscale_set_log_stream_config { "annotations": { "destructiveHint": false, - "description": "Destination URL (required for non-s3 destinations)", + "description": "Destination URL (required for non-s3 destinations; for s3, an optional S3-compatible endpoint)", "type": "string" }, "user": {
--- pinned/tailscale_suspend_user +++ observed/tailscale_suspend_user { "annotations": { "destructiveHint": true, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_test_webhook +++ observed/tailscale_test_webhook { "annotations": { "destructiveHint": false, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_update_key +++ observed/tailscale_update_key { "annotations": { "destructiveHint": false, - "description": "Update an existing key. Supported fields depend on the key type: all key types accept 'description'; OAuth clients and federated identities additionally accept 'scopes' and 'tags'; federated identities additionally accept 'issuer', 'subject', 'audience', and 'customClaimRules'. For auth keys, pass only 'description' — the Tailscale API will reject other fields.", + "description": "Update an existing key. Supported fields depend on the key type: all key types accept 'description'; OAuth clients and federated identities additionally accept 'scopes' and 'tags'; federated identities additionally accept 'issuer', 'subject', 'audience', and 'customClaimRules'. For auth keys, pass only 'description' -- the Tailscale API will reject other fields.", "execution": { "taskSupport": "forbidden" }, + "minLength": 1, "type": "string" }, "scopes": {
--- pinned/tailscale_update_posture_integration +++ observed/tailscale_update_posture_integration { "annotations": { "destructiveHint": false, + "minLength": 1, "type": "string" }, "tenantId": {
--- pinned/tailscale_update_tailnet_settings +++ observed/tailscale_update_tailnet_settings { "annotations": { "destructiveHint": false, - "description": "Key expiry duration in days", - "type": "number" + "description": "Key expiry duration in days (whole days, 1-180)", + "maximum": 180, + "minimum": 1, + "type": "integer" }, "httpsEnabled": { "description": "Whether HTTPS certificates are enabled (for tailscale serve/funnel)",
--- pinned/tailscale_update_user_role +++ observed/tailscale_update_user_role { "annotations": { "destructiveHint": false, + "minLength": 1, "type": "string" } },
--- pinned/tailscale_update_webhook +++ observed/tailscale_update_webhook { "annotations": { "destructiveHint": false, + "minLength": 1, "type": "string" } },
Current tools
Show all 98 tool fingerprints
tailscale_accept_device_invite
tailscale_approve_user
tailscale_authorize_device
tailscale_batch_update_posture_attributes
tailscale_create_aws_external_id
tailscale_create_device_invite
tailscale_create_key
tailscale_create_oauth_app
tailscale_create_org_tailnet
tailscale_create_posture_integration
tailscale_create_user_invite
tailscale_create_webhook
tailscale_deauthorize_device
tailscale_delete_device
tailscale_delete_device_invite
tailscale_delete_device_posture_attribute
tailscale_delete_key
tailscale_delete_log_stream_config
tailscale_delete_oauth_app
tailscale_delete_posture_integration
tailscale_delete_service
tailscale_delete_tailnet
tailscale_delete_user
tailscale_delete_user_invite
tailscale_delete_webhook
tailscale_diff_acl_access
tailscale_expire_device
tailscale_get_acl
tailscale_get_audit_log
tailscale_get_contacts
tailscale_get_device
tailscale_get_device_invite
tailscale_get_device_posture_attributes
tailscale_get_device_routes
tailscale_get_dns_configuration
tailscale_get_dns_preferences
tailscale_get_key
tailscale_get_log_stream_config
tailscale_get_log_stream_status
tailscale_get_nameservers
tailscale_get_network_flow_logs
tailscale_get_oauth_app
tailscale_get_posture_integration
tailscale_get_search_paths
tailscale_get_service
tailscale_get_service_device_approval
tailscale_get_split_dns
tailscale_get_tailnet_settings
tailscale_get_user
tailscale_get_user_invite
tailscale_get_webhook
tailscale_list_device_invites
tailscale_list_devices
tailscale_list_keys
tailscale_list_log_stream_configs
tailscale_list_oauth_apps
tailscale_list_org_tailnets
tailscale_list_posture_integrations
tailscale_list_service_hosts
tailscale_list_services
tailscale_list_user_invites
tailscale_list_users
tailscale_list_webhooks
tailscale_preview_acl
tailscale_rename_device
tailscale_resend_contact_verification
tailscale_resend_device_invite
tailscale_resend_user_invite
tailscale_restore_user
tailscale_rotate_webhook_secret
tailscale_set_contacts
tailscale_set_device_ip
tailscale_set_device_posture_attribute
tailscale_set_device_routes
tailscale_set_device_tags
tailscale_set_devices_authorized
tailscale_set_dns_configuration
tailscale_set_dns_preferences
tailscale_set_log_stream_config
tailscale_set_nameservers
tailscale_set_search_paths
tailscale_set_service_device_approval
tailscale_set_split_dns
tailscale_status
tailscale_suspend_user
tailscale_test_webhook
tailscale_tool_groups
tailscale_update_acl
tailscale_update_device_key
tailscale_update_key
tailscale_update_posture_integration
tailscale_update_service
tailscale_update_split_dns
tailscale_update_tailnet_settings
tailscale_update_user_role
tailscale_update_webhook
tailscale_validate_acl
tailscale_validate_aws_trust_policy
Watch this server yourself
If you run this server, put the proxy in front of it. It pins these exact fingerprints on first connect and stops the session if they move.
npx -y mcp-pin@0.2.3 -- <your @yawlabs/tailscale-mcp command>
Or subscribe to this page's RSS feed to be told when it changes.
Badge
The badge states one fact about time and nothing else. It never claims a server is safe.
[](https://mcp-pin.gautamkhosla.com/servers/5ae0ee9fdedd72e3.html)