Public log

@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
c34990d0bd367d17
tailscale_approve_user
98b500f94d40255d
tailscale_authorize_device
1c53ef9d966822e3
tailscale_batch_update_posture_attributes
f75c982998924273
tailscale_create_aws_external_id
5768d6c348fef109
tailscale_create_device_invite
bb30580d63a1e299
tailscale_create_key
d522d657b3d48d41
tailscale_create_oauth_app
be766c8c6fe84bdf
tailscale_create_org_tailnet
c7dbe45ae7e566c2
tailscale_create_posture_integration
e766fb75c475d90e
tailscale_create_user_invite
c0d9399449e50cd0
tailscale_create_webhook
1713943dbd353b92
tailscale_deauthorize_device
47ed0753bc7de16f
tailscale_delete_device
d42a1ab2926be29b
tailscale_delete_device_invite
5776647b7aa56128
tailscale_delete_device_posture_attribute
617ceb2a48ad86a5
tailscale_delete_key
243935a0f49585cf
tailscale_delete_log_stream_config
41f80b17f3e35d89
tailscale_delete_oauth_app
0b319a32c3c6829a
tailscale_delete_posture_integration
24f4ba33a87cc38e
tailscale_delete_service
1043d51cad26a614
tailscale_delete_tailnet
af2266cb08709bc7
tailscale_delete_user
15004d2706c084c3
tailscale_delete_user_invite
c08a8654fbf0df23
tailscale_delete_webhook
84fa187d31cd7158
tailscale_diff_acl_access
bdec5064c51b9464
tailscale_expire_device
87a9a0c9b96810a5
tailscale_get_acl
a01b9844f2ba6b23
tailscale_get_audit_log
88f101db10c8d09d
tailscale_get_contacts
fd33e27353b0c20d
tailscale_get_device
7d4659728f4f6f31
tailscale_get_device_invite
14f4dec52ddde849
tailscale_get_device_posture_attributes
301e867330c78ec9
tailscale_get_device_routes
7db799326d5570f3
tailscale_get_dns_configuration
4f2088e2e97f59cc
tailscale_get_dns_preferences
e9a1302e77ac4e7e
tailscale_get_key
26953e4f1233d078
tailscale_get_log_stream_config
50baf1abf2132648
tailscale_get_log_stream_status
f00e3986043e7a5c
tailscale_get_nameservers
294141853b242b2c
tailscale_get_network_flow_logs
6004dea3e65a1f49
tailscale_get_oauth_app
59822b4a2178a997
tailscale_get_posture_integration
7357574628d05af9
tailscale_get_search_paths
c623cf9b04d7b1f9
tailscale_get_service
0c128ff8441f254d
tailscale_get_service_device_approval
31a6ae1c0d7fe600
tailscale_get_split_dns
9c888357b27d81c8
tailscale_get_tailnet_settings
125ebf66224c0ab7
tailscale_get_user
715e9540eca3c4d5
tailscale_get_user_invite
94c49c9d67d9365b
tailscale_get_webhook
3c84af63e56676cb
tailscale_list_device_invites
a5371f13f2446f0a
tailscale_list_devices
fe3d680066ca2abe
tailscale_list_keys
e510182cce1de72b
tailscale_list_log_stream_configs
783f73b3a1fa16f3
tailscale_list_oauth_apps
3e0d34b685886f6b
tailscale_list_org_tailnets
cb6735c201ffd8d1
tailscale_list_posture_integrations
348700664afae118
tailscale_list_service_hosts
84055930df4a9b8a
tailscale_list_services
ba9bda057325eb70
tailscale_list_user_invites
ab4fd2274014473e
tailscale_list_users
6b8d7606802fa6cc
tailscale_list_webhooks
ffd0deac4107f21a
tailscale_preview_acl
927b804cd11de987
tailscale_rename_device
c867b6ebbdf89e7c
tailscale_resend_contact_verification
7dd7bc67357d4f04
tailscale_resend_device_invite
3a6629a659e33f9d
tailscale_resend_user_invite
b5f21d62f35cca75
tailscale_restore_user
eb71e88fc1ea7233
tailscale_rotate_webhook_secret
52fa1424a98c396b
tailscale_set_contacts
037c95cda9b12e1c
tailscale_set_device_ip
e152557d7e070921
tailscale_set_device_posture_attribute
047e79e74a66efb4
tailscale_set_device_routes
21abe5708a6d945f
tailscale_set_device_tags
6afa5aa22ee0bb2f
tailscale_set_devices_authorized
a29612a531a2ca5d
tailscale_set_dns_configuration
80a19c2875e48742
tailscale_set_dns_preferences
38ca12e227ec9acb
tailscale_set_log_stream_config
730980eaaa8b3059
tailscale_set_nameservers
3220186d52de6360
tailscale_set_search_paths
976e1b4fdb05c2ea
tailscale_set_service_device_approval
3a7c7f31a019180d
tailscale_set_split_dns
74bcf63ae6fcbec3
tailscale_status
4c4339bca1066245
tailscale_suspend_user
9a9e582053400597
tailscale_test_webhook
028d1d6ee94040e9
tailscale_tool_groups
1fd1fb515df9c547
tailscale_update_acl
cc9b2b41a3816b16
tailscale_update_device_key
7c60c1196c878173
tailscale_update_key
f6c72c016f4834e3
tailscale_update_posture_integration
96fa4f1c6c0f1d3e
tailscale_update_service
aa8425e0a10bc39a
tailscale_update_split_dns
061979a59af2f794
tailscale_update_tailnet_settings
dcae24fe2eb1d907
tailscale_update_user_role
3c99163a96ca8750
tailscale_update_webhook
64e06e35f7a71f68
tailscale_validate_acl
22da54f0f5a29b41
tailscale_validate_aws_trust_policy
89b94a7b2a5bf390

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

mcp-pin status badge for @yawlabs/tailscale-mcp

The badge states one fact about time and nothing else. It never claims a server is safe.

[![mcp-pin](https://mcp-pin.gautamkhosla.com/badge/5ae0ee9fdedd72e3.svg)](https://mcp-pin.gautamkhosla.com/servers/5ae0ee9fdedd72e3.html)