@yawlabs/tailscale-mcp 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -16
- package/dist/index.js +360 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/YawLabs/tailscale-mcp/stargazers)
|
|
6
6
|
[](https://github.com/YawLabs/tailscale-mcp/actions/workflows/ci.yml) [](https://github.com/YawLabs/tailscale-mcp/actions/workflows/release.yml)
|
|
7
7
|
|
|
8
|
-
**Manage your Tailscale tailnet from Claude Code, Cursor, and any MCP client.**
|
|
8
|
+
**Manage your Tailscale tailnet from Claude Code, Cursor, and any MCP client.** 98 tools + 4 resources. One env var. Works on first try.
|
|
9
9
|
|
|
10
10
|
Built and maintained by [YawLabs](https://yaw.sh).
|
|
11
11
|
|
|
@@ -96,7 +96,7 @@ MCP Resources expose read-only data that clients can browse without tool calls.
|
|
|
96
96
|
| ACL Policy | `tailscale://tailnet/acl` | Full ACL policy (HuJSON preserved) |
|
|
97
97
|
| DNS Config | `tailscale://tailnet/dns` | Nameservers, search paths, split DNS, MagicDNS |
|
|
98
98
|
|
|
99
|
-
## Tools (
|
|
99
|
+
## Tools (98)
|
|
100
100
|
|
|
101
101
|
<details>
|
|
102
102
|
<summary><strong>Status</strong> (1 tool)</summary>
|
|
@@ -108,7 +108,7 @@ MCP Resources expose read-only data that clients can browse without tool calls.
|
|
|
108
108
|
</details>
|
|
109
109
|
|
|
110
110
|
<details>
|
|
111
|
-
<summary><strong>Devices</strong> (
|
|
111
|
+
<summary><strong>Devices</strong> (16 tools)</summary>
|
|
112
112
|
|
|
113
113
|
| Tool | Description |
|
|
114
114
|
|------|-------------|
|
|
@@ -125,6 +125,9 @@ MCP Resources expose read-only data that clients can browse without tool calls.
|
|
|
125
125
|
| `tailscale_set_device_posture_attribute` | Set a custom posture attribute (with optional expiry) |
|
|
126
126
|
| `tailscale_delete_device_posture_attribute` | Delete a custom posture attribute |
|
|
127
127
|
| `tailscale_set_device_tags` | Set ACL tags on a device |
|
|
128
|
+
| `tailscale_set_device_ip` | Set a device's Tailscale IPv4 address |
|
|
129
|
+
| `tailscale_update_device_key` | Update device key settings (e.g. disable key expiry) |
|
|
130
|
+
| `tailscale_batch_update_posture_attributes` | Batch update custom posture attributes across devices |
|
|
128
131
|
|
|
129
132
|
</details>
|
|
130
133
|
|
|
@@ -141,7 +144,7 @@ MCP Resources expose read-only data that clients can browse without tool calls.
|
|
|
141
144
|
</details>
|
|
142
145
|
|
|
143
146
|
<details>
|
|
144
|
-
<summary><strong>DNS</strong> (
|
|
147
|
+
<summary><strong>DNS</strong> (11 tools)</summary>
|
|
145
148
|
|
|
146
149
|
| Tool | Description |
|
|
147
150
|
|------|-------------|
|
|
@@ -150,14 +153,17 @@ MCP Resources expose read-only data that clients can browse without tool calls.
|
|
|
150
153
|
| `tailscale_get_search_paths` | Get DNS search paths |
|
|
151
154
|
| `tailscale_set_search_paths` | Set DNS search paths |
|
|
152
155
|
| `tailscale_get_split_dns` | Get split DNS configuration |
|
|
153
|
-
| `tailscale_set_split_dns` | Set split DNS configuration |
|
|
156
|
+
| `tailscale_set_split_dns` | Set split DNS configuration (full replace) |
|
|
157
|
+
| `tailscale_update_split_dns` | Update split DNS configuration (partial merge) |
|
|
154
158
|
| `tailscale_get_dns_preferences` | Get DNS preferences (MagicDNS) |
|
|
155
159
|
| `tailscale_set_dns_preferences` | Set DNS preferences (MagicDNS) |
|
|
160
|
+
| `tailscale_get_dns_configuration` | Get unified DNS configuration (all settings in one call) |
|
|
161
|
+
| `tailscale_set_dns_configuration` | Set unified DNS configuration (all settings in one call) |
|
|
156
162
|
|
|
157
163
|
</details>
|
|
158
164
|
|
|
159
165
|
<details>
|
|
160
|
-
<summary><strong>Auth Keys</strong> (
|
|
166
|
+
<summary><strong>Auth Keys</strong> (5 tools)</summary>
|
|
161
167
|
|
|
162
168
|
| Tool | Description |
|
|
163
169
|
|------|-------------|
|
|
@@ -165,11 +171,12 @@ MCP Resources expose read-only data that clients can browse without tool calls.
|
|
|
165
171
|
| `tailscale_get_key` | Get details for an auth key |
|
|
166
172
|
| `tailscale_create_key` | Create a new auth key |
|
|
167
173
|
| `tailscale_delete_key` | Delete an auth key |
|
|
174
|
+
| `tailscale_update_key` | Update an existing auth key |
|
|
168
175
|
|
|
169
176
|
</details>
|
|
170
177
|
|
|
171
178
|
<details>
|
|
172
|
-
<summary><strong>Users</strong> (
|
|
179
|
+
<summary><strong>Users</strong> (7 tools)</summary>
|
|
173
180
|
|
|
174
181
|
| Tool | Description |
|
|
175
182
|
|------|-------------|
|
|
@@ -179,18 +186,20 @@ MCP Resources expose read-only data that clients can browse without tool calls.
|
|
|
179
186
|
| `tailscale_suspend_user` | Suspend a user, revoking access |
|
|
180
187
|
| `tailscale_restore_user` | Restore a suspended user |
|
|
181
188
|
| `tailscale_update_user_role` | Update a user's role (owner, admin, member, etc.) |
|
|
189
|
+
| `tailscale_delete_user` | Delete a user and all their devices |
|
|
182
190
|
|
|
183
191
|
</details>
|
|
184
192
|
|
|
185
193
|
<details>
|
|
186
|
-
<summary><strong>Tailnet Settings</strong> (
|
|
194
|
+
<summary><strong>Tailnet Settings</strong> (5 tools)</summary>
|
|
187
195
|
|
|
188
196
|
| Tool | Description |
|
|
189
197
|
|------|-------------|
|
|
190
198
|
| `tailscale_get_tailnet_settings` | Get tailnet settings (HTTPS, device approval, key expiry, etc.) |
|
|
191
|
-
| `tailscale_update_tailnet_settings` | Update tailnet settings (HTTPS certificates, approval, auto-updates, key expiry, posture, regional routing, network flow logging) |
|
|
199
|
+
| `tailscale_update_tailnet_settings` | Update tailnet settings (HTTPS certificates, approval, auto-updates, key expiry, posture, regional routing, network flow logging, external ACL management) |
|
|
192
200
|
| `tailscale_get_contacts` | Get tailnet contacts |
|
|
193
201
|
| `tailscale_set_contacts` | Set tailnet contacts |
|
|
202
|
+
| `tailscale_resend_contact_verification` | Resend verification email for a contact |
|
|
194
203
|
|
|
195
204
|
</details>
|
|
196
205
|
|
|
@@ -204,7 +213,7 @@ MCP Resources expose read-only data that clients can browse without tool calls.
|
|
|
204
213
|
</details>
|
|
205
214
|
|
|
206
215
|
<details>
|
|
207
|
-
<summary><strong>Webhooks</strong> (
|
|
216
|
+
<summary><strong>Webhooks</strong> (7 tools)</summary>
|
|
208
217
|
|
|
209
218
|
| Tool | Description |
|
|
210
219
|
|------|-------------|
|
|
@@ -214,6 +223,7 @@ MCP Resources expose read-only data that clients can browse without tool calls.
|
|
|
214
223
|
| `tailscale_update_webhook` | Update a webhook's endpoint URL and/or subscriptions |
|
|
215
224
|
| `tailscale_delete_webhook` | Delete a webhook |
|
|
216
225
|
| `tailscale_rotate_webhook_secret` | Rotate a webhook's secret |
|
|
226
|
+
| `tailscale_test_webhook` | Send a test event to verify webhook delivery |
|
|
217
227
|
|
|
218
228
|
</details>
|
|
219
229
|
|
|
@@ -231,7 +241,7 @@ MCP Resources expose read-only data that clients can browse without tool calls.
|
|
|
231
241
|
</details>
|
|
232
242
|
|
|
233
243
|
<details>
|
|
234
|
-
<summary><strong>Tailscale Services</strong> (
|
|
244
|
+
<summary><strong>Tailscale Services</strong> (7 tools)</summary>
|
|
235
245
|
|
|
236
246
|
| Tool | Description |
|
|
237
247
|
|------|-------------|
|
|
@@ -240,18 +250,23 @@ MCP Resources expose read-only data that clients can browse without tool calls.
|
|
|
240
250
|
| `tailscale_update_service` | Update a service's configuration |
|
|
241
251
|
| `tailscale_delete_service` | Delete a service |
|
|
242
252
|
| `tailscale_list_service_hosts` | List devices hosting a service |
|
|
253
|
+
| `tailscale_get_service_device_approval` | Get approval status of a device for a service |
|
|
254
|
+
| `tailscale_set_service_device_approval` | Approve or reject a device to host a service |
|
|
243
255
|
|
|
244
256
|
</details>
|
|
245
257
|
|
|
246
258
|
<details>
|
|
247
|
-
<summary><strong>Log Streaming</strong> (
|
|
259
|
+
<summary><strong>Log Streaming</strong> (7 tools)</summary>
|
|
248
260
|
|
|
249
261
|
| Tool | Description |
|
|
250
262
|
|------|-------------|
|
|
251
|
-
| `tailscale_list_log_stream_configs` | List log streaming configurations |
|
|
263
|
+
| `tailscale_list_log_stream_configs` | List log streaming configurations (both audit and network) |
|
|
252
264
|
| `tailscale_get_log_stream_config` | Get log streaming config for a log type |
|
|
253
265
|
| `tailscale_set_log_stream_config` | Set where logs are sent (Axiom, Datadog, Splunk, etc.) |
|
|
254
266
|
| `tailscale_delete_log_stream_config` | Delete a log streaming configuration |
|
|
267
|
+
| `tailscale_get_log_stream_status` | Check if log streaming is delivering successfully |
|
|
268
|
+
| `tailscale_create_aws_external_id` | Create/get AWS external ID for S3 log streaming |
|
|
269
|
+
| `tailscale_validate_aws_trust_policy` | Validate AWS IAM role trust policy for S3 log streaming |
|
|
255
270
|
|
|
256
271
|
</details>
|
|
257
272
|
|
|
@@ -282,19 +297,20 @@ MCP Resources expose read-only data that clients can browse without tool calls.
|
|
|
282
297
|
</details>
|
|
283
298
|
|
|
284
299
|
<details>
|
|
285
|
-
<summary><strong>Device Invites</strong> (
|
|
300
|
+
<summary><strong>Device Invites</strong> (5 tools)</summary>
|
|
286
301
|
|
|
287
302
|
| Tool | Description |
|
|
288
303
|
|------|-------------|
|
|
289
|
-
| `tailscale_list_device_invites` | List device invites |
|
|
304
|
+
| `tailscale_list_device_invites` | List device invites for a specific device |
|
|
290
305
|
| `tailscale_create_device_invite` | Create a device invite |
|
|
291
306
|
| `tailscale_get_device_invite` | Get a device invite |
|
|
292
307
|
| `tailscale_delete_device_invite` | Delete a device invite |
|
|
308
|
+
| `tailscale_resend_device_invite` | Resend a device invite email |
|
|
293
309
|
|
|
294
310
|
</details>
|
|
295
311
|
|
|
296
312
|
<details>
|
|
297
|
-
<summary><strong>User Invites</strong> (
|
|
313
|
+
<summary><strong>User Invites</strong> (5 tools)</summary>
|
|
298
314
|
|
|
299
315
|
| Tool | Description |
|
|
300
316
|
|------|-------------|
|
|
@@ -302,6 +318,7 @@ MCP Resources expose read-only data that clients can browse without tool calls.
|
|
|
302
318
|
| `tailscale_create_user_invite` | Create a user invite |
|
|
303
319
|
| `tailscale_get_user_invite` | Get a user invite |
|
|
304
320
|
| `tailscale_delete_user_invite` | Delete a user invite |
|
|
321
|
+
| `tailscale_resend_user_invite` | Resend a user invite email |
|
|
305
322
|
|
|
306
323
|
</details>
|
|
307
324
|
|
package/dist/index.js
CHANGED
|
@@ -21577,6 +21577,63 @@ var deviceTools = [
|
|
|
21577
21577
|
}
|
|
21578
21578
|
return apiPost(`/device/${encPath(input.deviceId)}/tags`, { tags: input.tags });
|
|
21579
21579
|
}
|
|
21580
|
+
},
|
|
21581
|
+
{
|
|
21582
|
+
name: "tailscale_set_device_ip",
|
|
21583
|
+
description: "Set the Tailscale IPv4 address for a device.",
|
|
21584
|
+
annotations: {
|
|
21585
|
+
title: "Set device IP",
|
|
21586
|
+
readOnlyHint: false,
|
|
21587
|
+
destructiveHint: false,
|
|
21588
|
+
idempotentHint: true,
|
|
21589
|
+
openWorldHint: true
|
|
21590
|
+
},
|
|
21591
|
+
inputSchema: external_exports.object({
|
|
21592
|
+
deviceId: external_exports.string().describe("The device ID"),
|
|
21593
|
+
ipv4: external_exports.string().describe("The new Tailscale IPv4 address for the device (e.g. '100.64.0.1')")
|
|
21594
|
+
}),
|
|
21595
|
+
handler: async (input) => {
|
|
21596
|
+
return apiPost(`/device/${encPath(input.deviceId)}/ip`, { ipv4: input.ipv4 });
|
|
21597
|
+
}
|
|
21598
|
+
},
|
|
21599
|
+
{
|
|
21600
|
+
name: "tailscale_update_device_key",
|
|
21601
|
+
description: "Update a device's key settings, such as disabling key expiry. Useful for servers that should never need to re-authenticate.",
|
|
21602
|
+
annotations: {
|
|
21603
|
+
title: "Update device key",
|
|
21604
|
+
readOnlyHint: false,
|
|
21605
|
+
destructiveHint: false,
|
|
21606
|
+
idempotentHint: true,
|
|
21607
|
+
openWorldHint: true
|
|
21608
|
+
},
|
|
21609
|
+
inputSchema: external_exports.object({
|
|
21610
|
+
deviceId: external_exports.string().describe("The device ID"),
|
|
21611
|
+
keyExpiryDisabled: external_exports.boolean().describe("Whether to disable key expiry for this device")
|
|
21612
|
+
}),
|
|
21613
|
+
handler: async (input) => {
|
|
21614
|
+
return apiPost(`/device/${encPath(input.deviceId)}/key`, {
|
|
21615
|
+
keyExpiryDisabled: input.keyExpiryDisabled
|
|
21616
|
+
});
|
|
21617
|
+
}
|
|
21618
|
+
},
|
|
21619
|
+
{
|
|
21620
|
+
name: "tailscale_batch_update_posture_attributes",
|
|
21621
|
+
description: "Batch update custom posture attributes across multiple devices. Each attribute key must start with 'custom:'.",
|
|
21622
|
+
annotations: {
|
|
21623
|
+
title: "Batch update posture attributes",
|
|
21624
|
+
readOnlyHint: false,
|
|
21625
|
+
destructiveHint: false,
|
|
21626
|
+
idempotentHint: true,
|
|
21627
|
+
openWorldHint: true
|
|
21628
|
+
},
|
|
21629
|
+
inputSchema: external_exports.object({
|
|
21630
|
+
attributes: external_exports.record(external_exports.string(), external_exports.record(external_exports.string(), external_exports.unknown())).describe(
|
|
21631
|
+
'Map of device ID to attribute map (e.g. { "12345": { "custom:compliant": "true" }, "67890": { "custom:compliant": "false" } })'
|
|
21632
|
+
)
|
|
21633
|
+
}),
|
|
21634
|
+
handler: async (input) => {
|
|
21635
|
+
return apiPatch(`/tailnet/${getTailnet()}/device-attributes`, input.attributes);
|
|
21636
|
+
}
|
|
21580
21637
|
}
|
|
21581
21638
|
];
|
|
21582
21639
|
|
|
@@ -21715,6 +21772,64 @@ var dnsTools = [
|
|
|
21715
21772
|
magicDNS: input.magicDNS
|
|
21716
21773
|
});
|
|
21717
21774
|
}
|
|
21775
|
+
},
|
|
21776
|
+
{
|
|
21777
|
+
name: "tailscale_update_split_dns",
|
|
21778
|
+
description: "Partially update split DNS configuration. Merges the provided domains with the existing config \u2014 only the specified domains are changed, others are untouched. Set a domain's nameservers to an empty array to remove it.",
|
|
21779
|
+
annotations: {
|
|
21780
|
+
title: "Update split DNS (partial)",
|
|
21781
|
+
readOnlyHint: false,
|
|
21782
|
+
destructiveHint: false,
|
|
21783
|
+
idempotentHint: true,
|
|
21784
|
+
openWorldHint: true
|
|
21785
|
+
},
|
|
21786
|
+
inputSchema: external_exports.object({
|
|
21787
|
+
splitDns: external_exports.record(external_exports.string(), external_exports.array(external_exports.string())).describe(
|
|
21788
|
+
'Map of domain to nameserver list to merge (e.g. { "new.example.com": ["10.0.0.3"] }). Only specified domains are changed.'
|
|
21789
|
+
)
|
|
21790
|
+
}),
|
|
21791
|
+
handler: async (input) => {
|
|
21792
|
+
return apiPatch(`/tailnet/${getTailnet()}/dns/split-dns`, input.splitDns);
|
|
21793
|
+
}
|
|
21794
|
+
},
|
|
21795
|
+
{
|
|
21796
|
+
name: "tailscale_get_dns_configuration",
|
|
21797
|
+
description: "Get the unified DNS configuration for your tailnet, including nameservers, search paths, split DNS, and MagicDNS preference in a single call.",
|
|
21798
|
+
annotations: {
|
|
21799
|
+
title: "Get DNS configuration (unified)",
|
|
21800
|
+
readOnlyHint: true,
|
|
21801
|
+
destructiveHint: false,
|
|
21802
|
+
idempotentHint: true,
|
|
21803
|
+
openWorldHint: true
|
|
21804
|
+
},
|
|
21805
|
+
inputSchema: external_exports.object({}),
|
|
21806
|
+
handler: async () => {
|
|
21807
|
+
return apiGet(`/tailnet/${getTailnet()}/dns/configuration`);
|
|
21808
|
+
}
|
|
21809
|
+
},
|
|
21810
|
+
{
|
|
21811
|
+
name: "tailscale_set_dns_configuration",
|
|
21812
|
+
description: "Set the unified DNS configuration for your tailnet in a single call. Replaces all DNS settings (nameservers, search paths, split DNS, MagicDNS preference).",
|
|
21813
|
+
annotations: {
|
|
21814
|
+
title: "Set DNS configuration (unified)",
|
|
21815
|
+
readOnlyHint: false,
|
|
21816
|
+
destructiveHint: false,
|
|
21817
|
+
idempotentHint: true,
|
|
21818
|
+
openWorldHint: true
|
|
21819
|
+
},
|
|
21820
|
+
inputSchema: external_exports.object({
|
|
21821
|
+
dns: external_exports.array(external_exports.string()).optional().describe("List of DNS server IP addresses"),
|
|
21822
|
+
searchPaths: external_exports.array(external_exports.string()).optional().describe("List of DNS search domains"),
|
|
21823
|
+
splitDns: external_exports.record(external_exports.string(), external_exports.array(external_exports.string())).optional().describe("Map of domain to nameserver list for split DNS"),
|
|
21824
|
+
magicDNS: external_exports.boolean().optional().describe("Whether to enable MagicDNS")
|
|
21825
|
+
}),
|
|
21826
|
+
handler: async (input) => {
|
|
21827
|
+
const body = {};
|
|
21828
|
+
for (const [key, value] of Object.entries(input)) {
|
|
21829
|
+
if (value !== void 0) body[key] = value;
|
|
21830
|
+
}
|
|
21831
|
+
return apiPost(`/tailnet/${getTailnet()}/dns/configuration`, body);
|
|
21832
|
+
}
|
|
21718
21833
|
}
|
|
21719
21834
|
];
|
|
21720
21835
|
|
|
@@ -21723,7 +21838,7 @@ var inviteTools = [
|
|
|
21723
21838
|
// --- Device Invites ---
|
|
21724
21839
|
{
|
|
21725
21840
|
name: "tailscale_list_device_invites",
|
|
21726
|
-
description: "List all device invites for
|
|
21841
|
+
description: "List all device invites for a specific device.",
|
|
21727
21842
|
annotations: {
|
|
21728
21843
|
title: "List device invites",
|
|
21729
21844
|
readOnlyHint: true,
|
|
@@ -21731,14 +21846,16 @@ var inviteTools = [
|
|
|
21731
21846
|
idempotentHint: true,
|
|
21732
21847
|
openWorldHint: true
|
|
21733
21848
|
},
|
|
21734
|
-
inputSchema: external_exports.object({
|
|
21735
|
-
|
|
21736
|
-
|
|
21849
|
+
inputSchema: external_exports.object({
|
|
21850
|
+
deviceId: external_exports.string().describe("The device ID to list invites for")
|
|
21851
|
+
}),
|
|
21852
|
+
handler: async (input) => {
|
|
21853
|
+
return apiGet(`/device/${encPath(input.deviceId)}/device-invites`);
|
|
21737
21854
|
}
|
|
21738
21855
|
},
|
|
21739
21856
|
{
|
|
21740
21857
|
name: "tailscale_create_device_invite",
|
|
21741
|
-
description: "Create a new device invite that allows someone to add a device to your tailnet.",
|
|
21858
|
+
description: "Create a new device invite that allows someone to add a specific device to your tailnet.",
|
|
21742
21859
|
annotations: {
|
|
21743
21860
|
title: "Create device invite",
|
|
21744
21861
|
readOnlyHint: false,
|
|
@@ -21747,6 +21864,7 @@ var inviteTools = [
|
|
|
21747
21864
|
openWorldHint: true
|
|
21748
21865
|
},
|
|
21749
21866
|
inputSchema: external_exports.object({
|
|
21867
|
+
deviceId: external_exports.string().describe("The device ID to create an invite for"),
|
|
21750
21868
|
multiUse: external_exports.boolean().optional().describe("Whether the invite can be used more than once (default: false)"),
|
|
21751
21869
|
allowExitNode: external_exports.boolean().optional().describe("Whether the invited device can be used as an exit node (default: false)"),
|
|
21752
21870
|
email: external_exports.string().optional().describe("Email address to send the invite to")
|
|
@@ -21756,7 +21874,7 @@ var inviteTools = [
|
|
|
21756
21874
|
if (input.multiUse !== void 0) body.multiUse = input.multiUse;
|
|
21757
21875
|
if (input.allowExitNode !== void 0) body.allowExitNode = input.allowExitNode;
|
|
21758
21876
|
if (input.email !== void 0) body.email = input.email;
|
|
21759
|
-
return apiPost(`/
|
|
21877
|
+
return apiPost(`/device/${encPath(input.deviceId)}/device-invites`, body);
|
|
21760
21878
|
}
|
|
21761
21879
|
},
|
|
21762
21880
|
{
|
|
@@ -21863,6 +21981,40 @@ var inviteTools = [
|
|
|
21863
21981
|
handler: async (input) => {
|
|
21864
21982
|
return apiDelete(`/user-invites/${encPath(input.inviteId)}`);
|
|
21865
21983
|
}
|
|
21984
|
+
},
|
|
21985
|
+
{
|
|
21986
|
+
name: "tailscale_resend_device_invite",
|
|
21987
|
+
description: "Resend a device invite email.",
|
|
21988
|
+
annotations: {
|
|
21989
|
+
title: "Resend device invite",
|
|
21990
|
+
readOnlyHint: false,
|
|
21991
|
+
destructiveHint: false,
|
|
21992
|
+
idempotentHint: true,
|
|
21993
|
+
openWorldHint: true
|
|
21994
|
+
},
|
|
21995
|
+
inputSchema: external_exports.object({
|
|
21996
|
+
inviteId: external_exports.string().describe("The device invite ID to resend")
|
|
21997
|
+
}),
|
|
21998
|
+
handler: async (input) => {
|
|
21999
|
+
return apiPost(`/device-invites/${encPath(input.inviteId)}/resend`);
|
|
22000
|
+
}
|
|
22001
|
+
},
|
|
22002
|
+
{
|
|
22003
|
+
name: "tailscale_resend_user_invite",
|
|
22004
|
+
description: "Resend a user invite email.",
|
|
22005
|
+
annotations: {
|
|
22006
|
+
title: "Resend user invite",
|
|
22007
|
+
readOnlyHint: false,
|
|
22008
|
+
destructiveHint: false,
|
|
22009
|
+
idempotentHint: true,
|
|
22010
|
+
openWorldHint: true
|
|
22011
|
+
},
|
|
22012
|
+
inputSchema: external_exports.object({
|
|
22013
|
+
inviteId: external_exports.string().describe("The user invite ID to resend")
|
|
22014
|
+
}),
|
|
22015
|
+
handler: async (input) => {
|
|
22016
|
+
return apiPost(`/user-invites/${encPath(input.inviteId)}/resend`);
|
|
22017
|
+
}
|
|
21866
22018
|
}
|
|
21867
22019
|
];
|
|
21868
22020
|
|
|
@@ -21952,6 +22104,26 @@ var keyTools = [
|
|
|
21952
22104
|
handler: async (input) => {
|
|
21953
22105
|
return apiDelete(`/tailnet/${getTailnet()}/keys/${encPath(input.keyId)}`);
|
|
21954
22106
|
}
|
|
22107
|
+
},
|
|
22108
|
+
{
|
|
22109
|
+
name: "tailscale_update_key",
|
|
22110
|
+
description: "Update an existing auth key's description or capabilities.",
|
|
22111
|
+
annotations: {
|
|
22112
|
+
title: "Update auth key",
|
|
22113
|
+
readOnlyHint: false,
|
|
22114
|
+
destructiveHint: false,
|
|
22115
|
+
idempotentHint: true,
|
|
22116
|
+
openWorldHint: true
|
|
22117
|
+
},
|
|
22118
|
+
inputSchema: external_exports.object({
|
|
22119
|
+
keyId: external_exports.string().describe("The auth key ID to update"),
|
|
22120
|
+
description: external_exports.string().optional().describe("Updated description for the key")
|
|
22121
|
+
}),
|
|
22122
|
+
handler: async (input) => {
|
|
22123
|
+
const body = {};
|
|
22124
|
+
if (input.description !== void 0) body.description = input.description;
|
|
22125
|
+
return apiPut(`/tailnet/${getTailnet()}/keys/${encPath(input.keyId)}`, body);
|
|
22126
|
+
}
|
|
21955
22127
|
}
|
|
21956
22128
|
];
|
|
21957
22129
|
|
|
@@ -21959,7 +22131,7 @@ var keyTools = [
|
|
|
21959
22131
|
var logStreamingTools = [
|
|
21960
22132
|
{
|
|
21961
22133
|
name: "tailscale_list_log_stream_configs",
|
|
21962
|
-
description: "List all log streaming configurations for your tailnet. Log streaming sends logs to external destinations like Axiom, Datadog, Splunk, Elasticsearch, S3, or GCS.",
|
|
22134
|
+
description: "List all log streaming configurations for your tailnet. Fetches both 'configuration' (audit) and 'network' (flow) log stream configs. Log streaming sends logs to external destinations like Axiom, Datadog, Splunk, Elasticsearch, S3, or GCS.",
|
|
21963
22135
|
annotations: {
|
|
21964
22136
|
title: "List log stream configs",
|
|
21965
22137
|
readOnlyHint: true,
|
|
@@ -21969,7 +22141,18 @@ var logStreamingTools = [
|
|
|
21969
22141
|
},
|
|
21970
22142
|
inputSchema: external_exports.object({}),
|
|
21971
22143
|
handler: async () => {
|
|
21972
|
-
|
|
22144
|
+
const [configuration, network] = await Promise.all([
|
|
22145
|
+
apiGet(`/tailnet/${getTailnet()}/logging/configuration/stream`),
|
|
22146
|
+
apiGet(`/tailnet/${getTailnet()}/logging/network/stream`)
|
|
22147
|
+
]);
|
|
22148
|
+
return {
|
|
22149
|
+
ok: true,
|
|
22150
|
+
status: 200,
|
|
22151
|
+
data: {
|
|
22152
|
+
configuration: configuration.ok ? configuration.data : { error: configuration.error },
|
|
22153
|
+
network: network.ok ? network.data : { error: network.error }
|
|
22154
|
+
}
|
|
22155
|
+
};
|
|
21973
22156
|
}
|
|
21974
22157
|
},
|
|
21975
22158
|
{
|
|
@@ -21986,7 +22169,7 @@ var logStreamingTools = [
|
|
|
21986
22169
|
logType: external_exports.enum(["configuration", "network"]).describe("The log type: 'configuration' for audit logs, 'network' for network flow logs")
|
|
21987
22170
|
}),
|
|
21988
22171
|
handler: async (input) => {
|
|
21989
|
-
return apiGet(`/tailnet/${getTailnet()}/logging
|
|
22172
|
+
return apiGet(`/tailnet/${getTailnet()}/logging/${encPath(input.logType)}/stream`);
|
|
21990
22173
|
}
|
|
21991
22174
|
},
|
|
21992
22175
|
{
|
|
@@ -22014,7 +22197,7 @@ var logStreamingTools = [
|
|
|
22014
22197
|
for (const [key, value] of Object.entries(body)) {
|
|
22015
22198
|
if (value !== void 0) cleanBody[key] = value;
|
|
22016
22199
|
}
|
|
22017
|
-
return apiPut(`/tailnet/${getTailnet()}/logging
|
|
22200
|
+
return apiPut(`/tailnet/${getTailnet()}/logging/${encPath(logType)}/stream`, cleanBody);
|
|
22018
22201
|
}
|
|
22019
22202
|
},
|
|
22020
22203
|
{
|
|
@@ -22031,7 +22214,60 @@ var logStreamingTools = [
|
|
|
22031
22214
|
logType: external_exports.enum(["configuration", "network"]).describe("The log type to stop streaming: 'configuration' or 'network'")
|
|
22032
22215
|
}),
|
|
22033
22216
|
handler: async (input) => {
|
|
22034
|
-
return apiDelete(`/tailnet/${getTailnet()}/logging
|
|
22217
|
+
return apiDelete(`/tailnet/${getTailnet()}/logging/${encPath(input.logType)}/stream`);
|
|
22218
|
+
}
|
|
22219
|
+
},
|
|
22220
|
+
{
|
|
22221
|
+
name: "tailscale_get_log_stream_status",
|
|
22222
|
+
description: "Get the status of log streaming for a specific log type. Shows whether logs are being delivered successfully.",
|
|
22223
|
+
annotations: {
|
|
22224
|
+
title: "Get log stream status",
|
|
22225
|
+
readOnlyHint: true,
|
|
22226
|
+
destructiveHint: false,
|
|
22227
|
+
idempotentHint: true,
|
|
22228
|
+
openWorldHint: true
|
|
22229
|
+
},
|
|
22230
|
+
inputSchema: external_exports.object({
|
|
22231
|
+
logType: external_exports.enum(["configuration", "network"]).describe("The log type: 'configuration' for audit logs, 'network' for network flow logs")
|
|
22232
|
+
}),
|
|
22233
|
+
handler: async (input) => {
|
|
22234
|
+
return apiGet(`/tailnet/${getTailnet()}/logging/${encPath(input.logType)}/stream/status`);
|
|
22235
|
+
}
|
|
22236
|
+
},
|
|
22237
|
+
{
|
|
22238
|
+
name: "tailscale_create_aws_external_id",
|
|
22239
|
+
description: "Create or get an AWS external ID for your tailnet. Used when configuring log streaming to S3 \u2014 the external ID is included in the IAM role trust policy.",
|
|
22240
|
+
annotations: {
|
|
22241
|
+
title: "Create AWS external ID",
|
|
22242
|
+
readOnlyHint: false,
|
|
22243
|
+
destructiveHint: false,
|
|
22244
|
+
idempotentHint: true,
|
|
22245
|
+
openWorldHint: true
|
|
22246
|
+
},
|
|
22247
|
+
inputSchema: external_exports.object({}),
|
|
22248
|
+
handler: async () => {
|
|
22249
|
+
return apiPost(`/tailnet/${getTailnet()}/aws-external-id`);
|
|
22250
|
+
}
|
|
22251
|
+
},
|
|
22252
|
+
{
|
|
22253
|
+
name: "tailscale_validate_aws_trust_policy",
|
|
22254
|
+
description: "Validate that an AWS IAM role trust policy is correctly configured with the Tailscale external ID. Use this after setting up the IAM role for S3 log streaming.",
|
|
22255
|
+
annotations: {
|
|
22256
|
+
title: "Validate AWS trust policy",
|
|
22257
|
+
readOnlyHint: true,
|
|
22258
|
+
destructiveHint: false,
|
|
22259
|
+
idempotentHint: true,
|
|
22260
|
+
openWorldHint: true
|
|
22261
|
+
},
|
|
22262
|
+
inputSchema: external_exports.object({
|
|
22263
|
+
externalId: external_exports.string().describe("The AWS external ID to validate"),
|
|
22264
|
+
roleArn: external_exports.string().describe("The AWS IAM role ARN to validate against")
|
|
22265
|
+
}),
|
|
22266
|
+
handler: async (input) => {
|
|
22267
|
+
return apiPost(
|
|
22268
|
+
`/tailnet/${getTailnet()}/aws-external-id/${encPath(input.externalId)}/validate-aws-trust-policy`,
|
|
22269
|
+
{ roleArn: input.roleArn }
|
|
22270
|
+
);
|
|
22035
22271
|
}
|
|
22036
22272
|
}
|
|
22037
22273
|
];
|
|
@@ -22316,7 +22552,7 @@ var serviceTools = [
|
|
|
22316
22552
|
for (const [key, value] of Object.entries(body)) {
|
|
22317
22553
|
if (value !== void 0) cleanBody[key] = value;
|
|
22318
22554
|
}
|
|
22319
|
-
return
|
|
22555
|
+
return apiPut(`/tailnet/${getTailnet()}/services/${encPath(serviceName)}`, cleanBody);
|
|
22320
22556
|
}
|
|
22321
22557
|
},
|
|
22322
22558
|
{
|
|
@@ -22350,7 +22586,49 @@ var serviceTools = [
|
|
|
22350
22586
|
serviceName: external_exports.string().describe("The service name")
|
|
22351
22587
|
}),
|
|
22352
22588
|
handler: async (input) => {
|
|
22353
|
-
return apiGet(`/tailnet/${getTailnet()}/services/${encPath(input.serviceName)}/
|
|
22589
|
+
return apiGet(`/tailnet/${getTailnet()}/services/${encPath(input.serviceName)}/devices`);
|
|
22590
|
+
}
|
|
22591
|
+
},
|
|
22592
|
+
{
|
|
22593
|
+
name: "tailscale_get_service_device_approval",
|
|
22594
|
+
description: "Get the approval status of a specific device for a Tailscale Service.",
|
|
22595
|
+
annotations: {
|
|
22596
|
+
title: "Get service device approval",
|
|
22597
|
+
readOnlyHint: true,
|
|
22598
|
+
destructiveHint: false,
|
|
22599
|
+
idempotentHint: true,
|
|
22600
|
+
openWorldHint: true
|
|
22601
|
+
},
|
|
22602
|
+
inputSchema: external_exports.object({
|
|
22603
|
+
serviceName: external_exports.string().describe("The service name"),
|
|
22604
|
+
deviceId: external_exports.string().describe("The device ID")
|
|
22605
|
+
}),
|
|
22606
|
+
handler: async (input) => {
|
|
22607
|
+
return apiGet(
|
|
22608
|
+
`/tailnet/${getTailnet()}/services/${encPath(input.serviceName)}/device/${encPath(input.deviceId)}/approved`
|
|
22609
|
+
);
|
|
22610
|
+
}
|
|
22611
|
+
},
|
|
22612
|
+
{
|
|
22613
|
+
name: "tailscale_set_service_device_approval",
|
|
22614
|
+
description: "Approve or reject a device to host a Tailscale Service.",
|
|
22615
|
+
annotations: {
|
|
22616
|
+
title: "Set service device approval",
|
|
22617
|
+
readOnlyHint: false,
|
|
22618
|
+
destructiveHint: false,
|
|
22619
|
+
idempotentHint: true,
|
|
22620
|
+
openWorldHint: true
|
|
22621
|
+
},
|
|
22622
|
+
inputSchema: external_exports.object({
|
|
22623
|
+
serviceName: external_exports.string().describe("The service name"),
|
|
22624
|
+
deviceId: external_exports.string().describe("The device ID"),
|
|
22625
|
+
approved: external_exports.boolean().describe("Whether to approve (true) or reject (false) the device")
|
|
22626
|
+
}),
|
|
22627
|
+
handler: async (input) => {
|
|
22628
|
+
return apiPost(
|
|
22629
|
+
`/tailnet/${getTailnet()}/services/${encPath(input.serviceName)}/device/${encPath(input.deviceId)}/approved`,
|
|
22630
|
+
{ approved: input.approved }
|
|
22631
|
+
);
|
|
22354
22632
|
}
|
|
22355
22633
|
}
|
|
22356
22634
|
];
|
|
@@ -22428,21 +22706,15 @@ var tailnetTools = [
|
|
|
22428
22706
|
networkFlowLoggingOn: external_exports.boolean().optional().describe("Whether network flow logging is enabled"),
|
|
22429
22707
|
regionalRoutingOn: external_exports.boolean().optional().describe("Whether regional routing is enabled"),
|
|
22430
22708
|
postureIdentityCollectionOn: external_exports.boolean().optional().describe("Whether posture identity collection is enabled"),
|
|
22431
|
-
httpsEnabled: external_exports.boolean().optional().describe("Whether HTTPS certificates are enabled (for tailscale serve/funnel)")
|
|
22709
|
+
httpsEnabled: external_exports.boolean().optional().describe("Whether HTTPS certificates are enabled (for tailscale serve/funnel)"),
|
|
22710
|
+
aclsExternallyManagedOn: external_exports.boolean().optional().describe("Whether ACLs are externally managed (e.g. via GitOps)"),
|
|
22711
|
+
aclsExternalLink: external_exports.string().optional().describe("URL to the external ACL management system (shown in the admin console)")
|
|
22432
22712
|
}),
|
|
22433
22713
|
handler: async (input) => {
|
|
22434
22714
|
const body = {};
|
|
22435
|
-
|
|
22436
|
-
|
|
22437
|
-
|
|
22438
|
-
if (input.usersApprovalOn !== void 0) body.usersApprovalOn = input.usersApprovalOn;
|
|
22439
|
-
if (input.usersRoleAllowedToJoinExternalTailnets !== void 0)
|
|
22440
|
-
body.usersRoleAllowedToJoinExternalTailnets = input.usersRoleAllowedToJoinExternalTailnets;
|
|
22441
|
-
if (input.networkFlowLoggingOn !== void 0) body.networkFlowLoggingOn = input.networkFlowLoggingOn;
|
|
22442
|
-
if (input.regionalRoutingOn !== void 0) body.regionalRoutingOn = input.regionalRoutingOn;
|
|
22443
|
-
if (input.postureIdentityCollectionOn !== void 0)
|
|
22444
|
-
body.postureIdentityCollectionOn = input.postureIdentityCollectionOn;
|
|
22445
|
-
if (input.httpsEnabled !== void 0) body.httpsEnabled = input.httpsEnabled;
|
|
22715
|
+
for (const [key, value] of Object.entries(input)) {
|
|
22716
|
+
if (value !== void 0) body[key] = value;
|
|
22717
|
+
}
|
|
22446
22718
|
return apiPatch(`/tailnet/${getTailnet()}/settings`, body);
|
|
22447
22719
|
}
|
|
22448
22720
|
},
|
|
@@ -22477,11 +22749,33 @@ var tailnetTools = [
|
|
|
22477
22749
|
security: external_exports.object({ email: external_exports.string() }).optional().describe("Security contact email")
|
|
22478
22750
|
}),
|
|
22479
22751
|
handler: async (input) => {
|
|
22480
|
-
const
|
|
22481
|
-
|
|
22482
|
-
|
|
22483
|
-
|
|
22484
|
-
|
|
22752
|
+
const results = {};
|
|
22753
|
+
for (const contactType of ["account", "support", "security"]) {
|
|
22754
|
+
const value = input[contactType];
|
|
22755
|
+
if (value !== void 0) {
|
|
22756
|
+
const res = await apiPatch(`/tailnet/${getTailnet()}/contacts/${encPath(contactType)}`, value);
|
|
22757
|
+
if (!res.ok) return res;
|
|
22758
|
+
results[contactType] = res.data;
|
|
22759
|
+
}
|
|
22760
|
+
}
|
|
22761
|
+
return { ok: true, status: 200, data: results };
|
|
22762
|
+
}
|
|
22763
|
+
},
|
|
22764
|
+
{
|
|
22765
|
+
name: "tailscale_resend_contact_verification",
|
|
22766
|
+
description: "Resend the verification email for a tailnet contact.",
|
|
22767
|
+
annotations: {
|
|
22768
|
+
title: "Resend contact verification",
|
|
22769
|
+
readOnlyHint: false,
|
|
22770
|
+
destructiveHint: false,
|
|
22771
|
+
idempotentHint: true,
|
|
22772
|
+
openWorldHint: true
|
|
22773
|
+
},
|
|
22774
|
+
inputSchema: external_exports.object({
|
|
22775
|
+
contactType: external_exports.enum(["account", "support", "security"]).describe("The contact type to resend verification for")
|
|
22776
|
+
}),
|
|
22777
|
+
handler: async (input) => {
|
|
22778
|
+
return apiPost(`/tailnet/${getTailnet()}/contacts/${encPath(input.contactType)}/resend-verification-email`);
|
|
22485
22779
|
}
|
|
22486
22780
|
}
|
|
22487
22781
|
];
|
|
@@ -22586,7 +22880,24 @@ var userTools = [
|
|
|
22586
22880
|
role: external_exports.enum(["owner", "admin", "it-admin", "network-admin", "billing-admin", "auditor", "member"]).describe("The new role to assign")
|
|
22587
22881
|
}),
|
|
22588
22882
|
handler: async (input) => {
|
|
22589
|
-
return
|
|
22883
|
+
return apiPost(`/users/${encPath(input.userId)}/role`, { role: input.role });
|
|
22884
|
+
}
|
|
22885
|
+
},
|
|
22886
|
+
{
|
|
22887
|
+
name: "tailscale_delete_user",
|
|
22888
|
+
description: "Delete a user from the tailnet. This is irreversible \u2014 the user and all their devices will be removed.",
|
|
22889
|
+
annotations: {
|
|
22890
|
+
title: "Delete user",
|
|
22891
|
+
readOnlyHint: false,
|
|
22892
|
+
destructiveHint: true,
|
|
22893
|
+
idempotentHint: false,
|
|
22894
|
+
openWorldHint: true
|
|
22895
|
+
},
|
|
22896
|
+
inputSchema: external_exports.object({
|
|
22897
|
+
userId: external_exports.string().describe("The user ID to delete")
|
|
22898
|
+
}),
|
|
22899
|
+
handler: async (input) => {
|
|
22900
|
+
return apiPost(`/users/${encPath(input.userId)}/delete`);
|
|
22590
22901
|
}
|
|
22591
22902
|
}
|
|
22592
22903
|
];
|
|
@@ -22705,6 +23016,23 @@ var webhookTools = [
|
|
|
22705
23016
|
handler: async (input) => {
|
|
22706
23017
|
return apiPost(`/webhooks/${encPath(input.webhookId)}/rotate`);
|
|
22707
23018
|
}
|
|
23019
|
+
},
|
|
23020
|
+
{
|
|
23021
|
+
name: "tailscale_test_webhook",
|
|
23022
|
+
description: "Send a test event to a webhook endpoint to verify it is configured correctly and receiving events.",
|
|
23023
|
+
annotations: {
|
|
23024
|
+
title: "Test webhook",
|
|
23025
|
+
readOnlyHint: false,
|
|
23026
|
+
destructiveHint: false,
|
|
23027
|
+
idempotentHint: true,
|
|
23028
|
+
openWorldHint: true
|
|
23029
|
+
},
|
|
23030
|
+
inputSchema: external_exports.object({
|
|
23031
|
+
webhookId: external_exports.string().describe("The webhook ID to test")
|
|
23032
|
+
}),
|
|
23033
|
+
handler: async (input) => {
|
|
23034
|
+
return apiPost(`/webhooks/${encPath(input.webhookId)}/test`);
|
|
23035
|
+
}
|
|
22708
23036
|
}
|
|
22709
23037
|
];
|
|
22710
23038
|
|
|
@@ -22807,7 +23135,7 @@ var workloadIdentityTools = [
|
|
|
22807
23135
|
];
|
|
22808
23136
|
|
|
22809
23137
|
// src/index.ts
|
|
22810
|
-
var version2 = true ? "0.
|
|
23138
|
+
var version2 = true ? "0.5.0" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
22811
23139
|
var subcommand = process.argv[2];
|
|
22812
23140
|
if (subcommand === "deploy-acl") {
|
|
22813
23141
|
const filePath = process.argv[3];
|