@yawlabs/tailscale-mcp 0.4.0 → 0.5.1
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 +396 -40
- 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
|
@@ -21156,7 +21156,9 @@ async function deployAcl(filePath) {
|
|
|
21156
21156
|
}
|
|
21157
21157
|
const validateRes = await apiPost(`/tailnet/${getTailnet()}/acl/validate`, void 0, {
|
|
21158
21158
|
rawBody: policy,
|
|
21159
|
-
contentType: "application/hujson"
|
|
21159
|
+
contentType: "application/hujson",
|
|
21160
|
+
acceptRaw: true,
|
|
21161
|
+
accept: "application/hujson"
|
|
21160
21162
|
});
|
|
21161
21163
|
if (!validateRes.ok) {
|
|
21162
21164
|
console.error(`ACL validation failed: ${validateRes.error}`);
|
|
@@ -21165,7 +21167,9 @@ async function deployAcl(filePath) {
|
|
|
21165
21167
|
const deployRes = await apiPost(`/tailnet/${getTailnet()}/acl`, void 0, {
|
|
21166
21168
|
rawBody: policy,
|
|
21167
21169
|
contentType: "application/hujson",
|
|
21168
|
-
ifMatch: getRes.etag
|
|
21170
|
+
ifMatch: getRes.etag,
|
|
21171
|
+
acceptRaw: true,
|
|
21172
|
+
accept: "application/hujson"
|
|
21169
21173
|
});
|
|
21170
21174
|
if (!deployRes.ok) {
|
|
21171
21175
|
console.error(`ACL deploy failed: ${deployRes.error}`);
|
|
@@ -21225,7 +21229,9 @@ Pass this ETag to tailscale_update_acl when updating the policy.`
|
|
|
21225
21229
|
return apiPost(`/tailnet/${getTailnet()}/acl`, void 0, {
|
|
21226
21230
|
rawBody: input.policy,
|
|
21227
21231
|
contentType: "application/hujson",
|
|
21228
|
-
ifMatch: input.etag
|
|
21232
|
+
ifMatch: input.etag,
|
|
21233
|
+
acceptRaw: true,
|
|
21234
|
+
accept: "application/hujson"
|
|
21229
21235
|
});
|
|
21230
21236
|
}
|
|
21231
21237
|
},
|
|
@@ -21245,10 +21251,12 @@ Pass this ETag to tailscale_update_acl when updating the policy.`
|
|
|
21245
21251
|
handler: async (input) => {
|
|
21246
21252
|
const res = await apiPost(`/tailnet/${getTailnet()}/acl/validate`, void 0, {
|
|
21247
21253
|
rawBody: input.policy,
|
|
21248
|
-
contentType: "application/hujson"
|
|
21254
|
+
contentType: "application/hujson",
|
|
21255
|
+
acceptRaw: true,
|
|
21256
|
+
accept: "application/hujson"
|
|
21249
21257
|
});
|
|
21250
|
-
if (res.ok && !res.
|
|
21251
|
-
return { ...res,
|
|
21258
|
+
if (res.ok && !res.rawBody) {
|
|
21259
|
+
return { ...res, rawBody: "ACL policy is valid." };
|
|
21252
21260
|
}
|
|
21253
21261
|
return res;
|
|
21254
21262
|
}
|
|
@@ -21272,7 +21280,9 @@ Pass this ETag to tailscale_update_acl when updating the policy.`
|
|
|
21272
21280
|
const params = new URLSearchParams({ type: input.type, previewFor: input.previewFor });
|
|
21273
21281
|
return apiPost(`/tailnet/${getTailnet()}/acl/preview?${params}`, void 0, {
|
|
21274
21282
|
rawBody: input.policy,
|
|
21275
|
-
contentType: "application/hujson"
|
|
21283
|
+
contentType: "application/hujson",
|
|
21284
|
+
acceptRaw: true,
|
|
21285
|
+
accept: "application/hujson"
|
|
21276
21286
|
});
|
|
21277
21287
|
}
|
|
21278
21288
|
}
|
|
@@ -21350,7 +21360,7 @@ var deviceTools = [
|
|
|
21350
21360
|
)
|
|
21351
21361
|
}),
|
|
21352
21362
|
handler: async (input) => {
|
|
21353
|
-
const params = input.fields ?
|
|
21363
|
+
const params = input.fields ? `?${new URLSearchParams({ fields: input.fields })}` : "";
|
|
21354
21364
|
return apiGet(`/tailnet/${getTailnet()}/devices${params}`);
|
|
21355
21365
|
}
|
|
21356
21366
|
},
|
|
@@ -21577,6 +21587,63 @@ var deviceTools = [
|
|
|
21577
21587
|
}
|
|
21578
21588
|
return apiPost(`/device/${encPath(input.deviceId)}/tags`, { tags: input.tags });
|
|
21579
21589
|
}
|
|
21590
|
+
},
|
|
21591
|
+
{
|
|
21592
|
+
name: "tailscale_set_device_ip",
|
|
21593
|
+
description: "Set the Tailscale IPv4 address for a device.",
|
|
21594
|
+
annotations: {
|
|
21595
|
+
title: "Set device IP",
|
|
21596
|
+
readOnlyHint: false,
|
|
21597
|
+
destructiveHint: false,
|
|
21598
|
+
idempotentHint: true,
|
|
21599
|
+
openWorldHint: true
|
|
21600
|
+
},
|
|
21601
|
+
inputSchema: external_exports.object({
|
|
21602
|
+
deviceId: external_exports.string().describe("The device ID"),
|
|
21603
|
+
ipv4: external_exports.string().describe("The new Tailscale IPv4 address for the device (e.g. '100.64.0.1')")
|
|
21604
|
+
}),
|
|
21605
|
+
handler: async (input) => {
|
|
21606
|
+
return apiPost(`/device/${encPath(input.deviceId)}/ip`, { ipv4: input.ipv4 });
|
|
21607
|
+
}
|
|
21608
|
+
},
|
|
21609
|
+
{
|
|
21610
|
+
name: "tailscale_update_device_key",
|
|
21611
|
+
description: "Update a device's key settings, such as disabling key expiry. Useful for servers that should never need to re-authenticate.",
|
|
21612
|
+
annotations: {
|
|
21613
|
+
title: "Update device key",
|
|
21614
|
+
readOnlyHint: false,
|
|
21615
|
+
destructiveHint: false,
|
|
21616
|
+
idempotentHint: true,
|
|
21617
|
+
openWorldHint: true
|
|
21618
|
+
},
|
|
21619
|
+
inputSchema: external_exports.object({
|
|
21620
|
+
deviceId: external_exports.string().describe("The device ID"),
|
|
21621
|
+
keyExpiryDisabled: external_exports.boolean().describe("Whether to disable key expiry for this device")
|
|
21622
|
+
}),
|
|
21623
|
+
handler: async (input) => {
|
|
21624
|
+
return apiPost(`/device/${encPath(input.deviceId)}/key`, {
|
|
21625
|
+
keyExpiryDisabled: input.keyExpiryDisabled
|
|
21626
|
+
});
|
|
21627
|
+
}
|
|
21628
|
+
},
|
|
21629
|
+
{
|
|
21630
|
+
name: "tailscale_batch_update_posture_attributes",
|
|
21631
|
+
description: "Batch update custom posture attributes across multiple devices. Each attribute key must start with 'custom:'.",
|
|
21632
|
+
annotations: {
|
|
21633
|
+
title: "Batch update posture attributes",
|
|
21634
|
+
readOnlyHint: false,
|
|
21635
|
+
destructiveHint: false,
|
|
21636
|
+
idempotentHint: true,
|
|
21637
|
+
openWorldHint: true
|
|
21638
|
+
},
|
|
21639
|
+
inputSchema: external_exports.object({
|
|
21640
|
+
attributes: external_exports.record(external_exports.string(), external_exports.record(external_exports.string(), external_exports.unknown())).describe(
|
|
21641
|
+
'Map of device ID to attribute map (e.g. { "12345": { "custom:compliant": "true" }, "67890": { "custom:compliant": "false" } })'
|
|
21642
|
+
)
|
|
21643
|
+
}),
|
|
21644
|
+
handler: async (input) => {
|
|
21645
|
+
return apiPatch(`/tailnet/${getTailnet()}/device-attributes`, input.attributes);
|
|
21646
|
+
}
|
|
21580
21647
|
}
|
|
21581
21648
|
];
|
|
21582
21649
|
|
|
@@ -21715,6 +21782,64 @@ var dnsTools = [
|
|
|
21715
21782
|
magicDNS: input.magicDNS
|
|
21716
21783
|
});
|
|
21717
21784
|
}
|
|
21785
|
+
},
|
|
21786
|
+
{
|
|
21787
|
+
name: "tailscale_update_split_dns",
|
|
21788
|
+
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.",
|
|
21789
|
+
annotations: {
|
|
21790
|
+
title: "Update split DNS (partial)",
|
|
21791
|
+
readOnlyHint: false,
|
|
21792
|
+
destructiveHint: false,
|
|
21793
|
+
idempotentHint: true,
|
|
21794
|
+
openWorldHint: true
|
|
21795
|
+
},
|
|
21796
|
+
inputSchema: external_exports.object({
|
|
21797
|
+
splitDns: external_exports.record(external_exports.string(), external_exports.array(external_exports.string())).describe(
|
|
21798
|
+
'Map of domain to nameserver list to merge (e.g. { "new.example.com": ["10.0.0.3"] }). Only specified domains are changed.'
|
|
21799
|
+
)
|
|
21800
|
+
}),
|
|
21801
|
+
handler: async (input) => {
|
|
21802
|
+
return apiPatch(`/tailnet/${getTailnet()}/dns/split-dns`, input.splitDns);
|
|
21803
|
+
}
|
|
21804
|
+
},
|
|
21805
|
+
{
|
|
21806
|
+
name: "tailscale_get_dns_configuration",
|
|
21807
|
+
description: "Get the unified DNS configuration for your tailnet, including nameservers, search paths, split DNS, and MagicDNS preference in a single call.",
|
|
21808
|
+
annotations: {
|
|
21809
|
+
title: "Get DNS configuration (unified)",
|
|
21810
|
+
readOnlyHint: true,
|
|
21811
|
+
destructiveHint: false,
|
|
21812
|
+
idempotentHint: true,
|
|
21813
|
+
openWorldHint: true
|
|
21814
|
+
},
|
|
21815
|
+
inputSchema: external_exports.object({}),
|
|
21816
|
+
handler: async () => {
|
|
21817
|
+
return apiGet(`/tailnet/${getTailnet()}/dns/configuration`);
|
|
21818
|
+
}
|
|
21819
|
+
},
|
|
21820
|
+
{
|
|
21821
|
+
name: "tailscale_set_dns_configuration",
|
|
21822
|
+
description: "Set the unified DNS configuration for your tailnet in a single call. Replaces all DNS settings (nameservers, search paths, split DNS, MagicDNS preference).",
|
|
21823
|
+
annotations: {
|
|
21824
|
+
title: "Set DNS configuration (unified)",
|
|
21825
|
+
readOnlyHint: false,
|
|
21826
|
+
destructiveHint: false,
|
|
21827
|
+
idempotentHint: true,
|
|
21828
|
+
openWorldHint: true
|
|
21829
|
+
},
|
|
21830
|
+
inputSchema: external_exports.object({
|
|
21831
|
+
dns: external_exports.array(external_exports.string()).optional().describe("List of DNS server IP addresses"),
|
|
21832
|
+
searchPaths: external_exports.array(external_exports.string()).optional().describe("List of DNS search domains"),
|
|
21833
|
+
splitDns: external_exports.record(external_exports.string(), external_exports.array(external_exports.string())).optional().describe("Map of domain to nameserver list for split DNS"),
|
|
21834
|
+
magicDNS: external_exports.boolean().optional().describe("Whether to enable MagicDNS")
|
|
21835
|
+
}),
|
|
21836
|
+
handler: async (input) => {
|
|
21837
|
+
const body = {};
|
|
21838
|
+
for (const [key, value] of Object.entries(input)) {
|
|
21839
|
+
if (value !== void 0) body[key] = value;
|
|
21840
|
+
}
|
|
21841
|
+
return apiPost(`/tailnet/${getTailnet()}/dns/configuration`, body);
|
|
21842
|
+
}
|
|
21718
21843
|
}
|
|
21719
21844
|
];
|
|
21720
21845
|
|
|
@@ -21723,7 +21848,7 @@ var inviteTools = [
|
|
|
21723
21848
|
// --- Device Invites ---
|
|
21724
21849
|
{
|
|
21725
21850
|
name: "tailscale_list_device_invites",
|
|
21726
|
-
description: "List all device invites for
|
|
21851
|
+
description: "List all device invites for a specific device.",
|
|
21727
21852
|
annotations: {
|
|
21728
21853
|
title: "List device invites",
|
|
21729
21854
|
readOnlyHint: true,
|
|
@@ -21731,14 +21856,16 @@ var inviteTools = [
|
|
|
21731
21856
|
idempotentHint: true,
|
|
21732
21857
|
openWorldHint: true
|
|
21733
21858
|
},
|
|
21734
|
-
inputSchema: external_exports.object({
|
|
21735
|
-
|
|
21736
|
-
|
|
21859
|
+
inputSchema: external_exports.object({
|
|
21860
|
+
deviceId: external_exports.string().describe("The device ID to list invites for")
|
|
21861
|
+
}),
|
|
21862
|
+
handler: async (input) => {
|
|
21863
|
+
return apiGet(`/device/${encPath(input.deviceId)}/device-invites`);
|
|
21737
21864
|
}
|
|
21738
21865
|
},
|
|
21739
21866
|
{
|
|
21740
21867
|
name: "tailscale_create_device_invite",
|
|
21741
|
-
description: "Create a new device invite that allows someone to add a device to your tailnet.",
|
|
21868
|
+
description: "Create a new device invite that allows someone to add a specific device to your tailnet.",
|
|
21742
21869
|
annotations: {
|
|
21743
21870
|
title: "Create device invite",
|
|
21744
21871
|
readOnlyHint: false,
|
|
@@ -21747,6 +21874,7 @@ var inviteTools = [
|
|
|
21747
21874
|
openWorldHint: true
|
|
21748
21875
|
},
|
|
21749
21876
|
inputSchema: external_exports.object({
|
|
21877
|
+
deviceId: external_exports.string().describe("The device ID to create an invite for"),
|
|
21750
21878
|
multiUse: external_exports.boolean().optional().describe("Whether the invite can be used more than once (default: false)"),
|
|
21751
21879
|
allowExitNode: external_exports.boolean().optional().describe("Whether the invited device can be used as an exit node (default: false)"),
|
|
21752
21880
|
email: external_exports.string().optional().describe("Email address to send the invite to")
|
|
@@ -21756,7 +21884,7 @@ var inviteTools = [
|
|
|
21756
21884
|
if (input.multiUse !== void 0) body.multiUse = input.multiUse;
|
|
21757
21885
|
if (input.allowExitNode !== void 0) body.allowExitNode = input.allowExitNode;
|
|
21758
21886
|
if (input.email !== void 0) body.email = input.email;
|
|
21759
|
-
return apiPost(`/
|
|
21887
|
+
return apiPost(`/device/${encPath(input.deviceId)}/device-invites`, body);
|
|
21760
21888
|
}
|
|
21761
21889
|
},
|
|
21762
21890
|
{
|
|
@@ -21863,6 +21991,40 @@ var inviteTools = [
|
|
|
21863
21991
|
handler: async (input) => {
|
|
21864
21992
|
return apiDelete(`/user-invites/${encPath(input.inviteId)}`);
|
|
21865
21993
|
}
|
|
21994
|
+
},
|
|
21995
|
+
{
|
|
21996
|
+
name: "tailscale_resend_device_invite",
|
|
21997
|
+
description: "Resend a device invite email.",
|
|
21998
|
+
annotations: {
|
|
21999
|
+
title: "Resend device invite",
|
|
22000
|
+
readOnlyHint: false,
|
|
22001
|
+
destructiveHint: false,
|
|
22002
|
+
idempotentHint: true,
|
|
22003
|
+
openWorldHint: true
|
|
22004
|
+
},
|
|
22005
|
+
inputSchema: external_exports.object({
|
|
22006
|
+
inviteId: external_exports.string().describe("The device invite ID to resend")
|
|
22007
|
+
}),
|
|
22008
|
+
handler: async (input) => {
|
|
22009
|
+
return apiPost(`/device-invites/${encPath(input.inviteId)}/resend`);
|
|
22010
|
+
}
|
|
22011
|
+
},
|
|
22012
|
+
{
|
|
22013
|
+
name: "tailscale_resend_user_invite",
|
|
22014
|
+
description: "Resend a user invite email.",
|
|
22015
|
+
annotations: {
|
|
22016
|
+
title: "Resend user invite",
|
|
22017
|
+
readOnlyHint: false,
|
|
22018
|
+
destructiveHint: false,
|
|
22019
|
+
idempotentHint: true,
|
|
22020
|
+
openWorldHint: true
|
|
22021
|
+
},
|
|
22022
|
+
inputSchema: external_exports.object({
|
|
22023
|
+
inviteId: external_exports.string().describe("The user invite ID to resend")
|
|
22024
|
+
}),
|
|
22025
|
+
handler: async (input) => {
|
|
22026
|
+
return apiPost(`/user-invites/${encPath(input.inviteId)}/resend`);
|
|
22027
|
+
}
|
|
21866
22028
|
}
|
|
21867
22029
|
];
|
|
21868
22030
|
|
|
@@ -21919,6 +22081,12 @@ var keyTools = [
|
|
|
21919
22081
|
description: external_exports.string().optional().describe("Description for this key")
|
|
21920
22082
|
}),
|
|
21921
22083
|
handler: async (input) => {
|
|
22084
|
+
if (input.tags && input.tags.length > 0) {
|
|
22085
|
+
const invalid = input.tags.filter((t) => !t.startsWith("tag:"));
|
|
22086
|
+
if (invalid.length > 0) {
|
|
22087
|
+
throw new Error(`All tags must start with 'tag:' prefix. Invalid tags: ${invalid.join(", ")}`);
|
|
22088
|
+
}
|
|
22089
|
+
}
|
|
21922
22090
|
const body = {
|
|
21923
22091
|
capabilities: {
|
|
21924
22092
|
devices: {
|
|
@@ -21952,6 +22120,26 @@ var keyTools = [
|
|
|
21952
22120
|
handler: async (input) => {
|
|
21953
22121
|
return apiDelete(`/tailnet/${getTailnet()}/keys/${encPath(input.keyId)}`);
|
|
21954
22122
|
}
|
|
22123
|
+
},
|
|
22124
|
+
{
|
|
22125
|
+
name: "tailscale_update_key",
|
|
22126
|
+
description: "Update an existing auth key's description or capabilities.",
|
|
22127
|
+
annotations: {
|
|
22128
|
+
title: "Update auth key",
|
|
22129
|
+
readOnlyHint: false,
|
|
22130
|
+
destructiveHint: false,
|
|
22131
|
+
idempotentHint: true,
|
|
22132
|
+
openWorldHint: true
|
|
22133
|
+
},
|
|
22134
|
+
inputSchema: external_exports.object({
|
|
22135
|
+
keyId: external_exports.string().describe("The auth key ID to update"),
|
|
22136
|
+
description: external_exports.string().optional().describe("Updated description for the key")
|
|
22137
|
+
}),
|
|
22138
|
+
handler: async (input) => {
|
|
22139
|
+
const body = {};
|
|
22140
|
+
if (input.description !== void 0) body.description = input.description;
|
|
22141
|
+
return apiPut(`/tailnet/${getTailnet()}/keys/${encPath(input.keyId)}`, body);
|
|
22142
|
+
}
|
|
21955
22143
|
}
|
|
21956
22144
|
];
|
|
21957
22145
|
|
|
@@ -21959,7 +22147,7 @@ var keyTools = [
|
|
|
21959
22147
|
var logStreamingTools = [
|
|
21960
22148
|
{
|
|
21961
22149
|
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.",
|
|
22150
|
+
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
22151
|
annotations: {
|
|
21964
22152
|
title: "List log stream configs",
|
|
21965
22153
|
readOnlyHint: true,
|
|
@@ -21969,7 +22157,18 @@ var logStreamingTools = [
|
|
|
21969
22157
|
},
|
|
21970
22158
|
inputSchema: external_exports.object({}),
|
|
21971
22159
|
handler: async () => {
|
|
21972
|
-
|
|
22160
|
+
const [configuration, network] = await Promise.all([
|
|
22161
|
+
apiGet(`/tailnet/${getTailnet()}/logging/configuration/stream`),
|
|
22162
|
+
apiGet(`/tailnet/${getTailnet()}/logging/network/stream`)
|
|
22163
|
+
]);
|
|
22164
|
+
return {
|
|
22165
|
+
ok: true,
|
|
22166
|
+
status: 200,
|
|
22167
|
+
data: {
|
|
22168
|
+
configuration: configuration.ok ? configuration.data : { error: configuration.error },
|
|
22169
|
+
network: network.ok ? network.data : { error: network.error }
|
|
22170
|
+
}
|
|
22171
|
+
};
|
|
21973
22172
|
}
|
|
21974
22173
|
},
|
|
21975
22174
|
{
|
|
@@ -21986,7 +22185,7 @@ var logStreamingTools = [
|
|
|
21986
22185
|
logType: external_exports.enum(["configuration", "network"]).describe("The log type: 'configuration' for audit logs, 'network' for network flow logs")
|
|
21987
22186
|
}),
|
|
21988
22187
|
handler: async (input) => {
|
|
21989
|
-
return apiGet(`/tailnet/${getTailnet()}/logging
|
|
22188
|
+
return apiGet(`/tailnet/${getTailnet()}/logging/${encPath(input.logType)}/stream`);
|
|
21990
22189
|
}
|
|
21991
22190
|
},
|
|
21992
22191
|
{
|
|
@@ -22014,7 +22213,7 @@ var logStreamingTools = [
|
|
|
22014
22213
|
for (const [key, value] of Object.entries(body)) {
|
|
22015
22214
|
if (value !== void 0) cleanBody[key] = value;
|
|
22016
22215
|
}
|
|
22017
|
-
return apiPut(`/tailnet/${getTailnet()}/logging
|
|
22216
|
+
return apiPut(`/tailnet/${getTailnet()}/logging/${encPath(logType)}/stream`, cleanBody);
|
|
22018
22217
|
}
|
|
22019
22218
|
},
|
|
22020
22219
|
{
|
|
@@ -22031,7 +22230,60 @@ var logStreamingTools = [
|
|
|
22031
22230
|
logType: external_exports.enum(["configuration", "network"]).describe("The log type to stop streaming: 'configuration' or 'network'")
|
|
22032
22231
|
}),
|
|
22033
22232
|
handler: async (input) => {
|
|
22034
|
-
return apiDelete(`/tailnet/${getTailnet()}/logging
|
|
22233
|
+
return apiDelete(`/tailnet/${getTailnet()}/logging/${encPath(input.logType)}/stream`);
|
|
22234
|
+
}
|
|
22235
|
+
},
|
|
22236
|
+
{
|
|
22237
|
+
name: "tailscale_get_log_stream_status",
|
|
22238
|
+
description: "Get the status of log streaming for a specific log type. Shows whether logs are being delivered successfully.",
|
|
22239
|
+
annotations: {
|
|
22240
|
+
title: "Get log stream status",
|
|
22241
|
+
readOnlyHint: true,
|
|
22242
|
+
destructiveHint: false,
|
|
22243
|
+
idempotentHint: true,
|
|
22244
|
+
openWorldHint: true
|
|
22245
|
+
},
|
|
22246
|
+
inputSchema: external_exports.object({
|
|
22247
|
+
logType: external_exports.enum(["configuration", "network"]).describe("The log type: 'configuration' for audit logs, 'network' for network flow logs")
|
|
22248
|
+
}),
|
|
22249
|
+
handler: async (input) => {
|
|
22250
|
+
return apiGet(`/tailnet/${getTailnet()}/logging/${encPath(input.logType)}/stream/status`);
|
|
22251
|
+
}
|
|
22252
|
+
},
|
|
22253
|
+
{
|
|
22254
|
+
name: "tailscale_create_aws_external_id",
|
|
22255
|
+
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.",
|
|
22256
|
+
annotations: {
|
|
22257
|
+
title: "Create AWS external ID",
|
|
22258
|
+
readOnlyHint: false,
|
|
22259
|
+
destructiveHint: false,
|
|
22260
|
+
idempotentHint: true,
|
|
22261
|
+
openWorldHint: true
|
|
22262
|
+
},
|
|
22263
|
+
inputSchema: external_exports.object({}),
|
|
22264
|
+
handler: async () => {
|
|
22265
|
+
return apiPost(`/tailnet/${getTailnet()}/aws-external-id`);
|
|
22266
|
+
}
|
|
22267
|
+
},
|
|
22268
|
+
{
|
|
22269
|
+
name: "tailscale_validate_aws_trust_policy",
|
|
22270
|
+
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.",
|
|
22271
|
+
annotations: {
|
|
22272
|
+
title: "Validate AWS trust policy",
|
|
22273
|
+
readOnlyHint: true,
|
|
22274
|
+
destructiveHint: false,
|
|
22275
|
+
idempotentHint: true,
|
|
22276
|
+
openWorldHint: true
|
|
22277
|
+
},
|
|
22278
|
+
inputSchema: external_exports.object({
|
|
22279
|
+
externalId: external_exports.string().describe("The AWS external ID to validate"),
|
|
22280
|
+
roleArn: external_exports.string().describe("The AWS IAM role ARN to validate against")
|
|
22281
|
+
}),
|
|
22282
|
+
handler: async (input) => {
|
|
22283
|
+
return apiPost(
|
|
22284
|
+
`/tailnet/${getTailnet()}/aws-external-id/${encPath(input.externalId)}/validate-aws-trust-policy`,
|
|
22285
|
+
{ roleArn: input.roleArn }
|
|
22286
|
+
);
|
|
22035
22287
|
}
|
|
22036
22288
|
}
|
|
22037
22289
|
];
|
|
@@ -22108,6 +22360,12 @@ var oauthClientTools = [
|
|
|
22108
22360
|
description: external_exports.string().optional().describe("Description for this OAuth client")
|
|
22109
22361
|
}),
|
|
22110
22362
|
handler: async (input) => {
|
|
22363
|
+
if (input.tags && input.tags.length > 0) {
|
|
22364
|
+
const invalid = input.tags.filter((t) => !t.startsWith("tag:"));
|
|
22365
|
+
if (invalid.length > 0) {
|
|
22366
|
+
throw new Error(`All tags must start with 'tag:' prefix. Invalid tags: ${invalid.join(", ")}`);
|
|
22367
|
+
}
|
|
22368
|
+
}
|
|
22111
22369
|
return apiPost(`/tailnet/${getTailnet()}/oauth-clients`, input);
|
|
22112
22370
|
}
|
|
22113
22371
|
},
|
|
@@ -22311,12 +22569,18 @@ var serviceTools = [
|
|
|
22311
22569
|
autoApproveHosts: external_exports.boolean().optional().describe("Whether to auto-approve devices that want to host this service")
|
|
22312
22570
|
}),
|
|
22313
22571
|
handler: async (input) => {
|
|
22572
|
+
if (input.tags && input.tags.length > 0) {
|
|
22573
|
+
const invalid = input.tags.filter((t) => !t.startsWith("tag:"));
|
|
22574
|
+
if (invalid.length > 0) {
|
|
22575
|
+
throw new Error(`All tags must start with 'tag:' prefix. Invalid tags: ${invalid.join(", ")}`);
|
|
22576
|
+
}
|
|
22577
|
+
}
|
|
22314
22578
|
const { serviceName, ...body } = input;
|
|
22315
22579
|
const cleanBody = {};
|
|
22316
22580
|
for (const [key, value] of Object.entries(body)) {
|
|
22317
22581
|
if (value !== void 0) cleanBody[key] = value;
|
|
22318
22582
|
}
|
|
22319
|
-
return
|
|
22583
|
+
return apiPut(`/tailnet/${getTailnet()}/services/${encPath(serviceName)}`, cleanBody);
|
|
22320
22584
|
}
|
|
22321
22585
|
},
|
|
22322
22586
|
{
|
|
@@ -22350,7 +22614,49 @@ var serviceTools = [
|
|
|
22350
22614
|
serviceName: external_exports.string().describe("The service name")
|
|
22351
22615
|
}),
|
|
22352
22616
|
handler: async (input) => {
|
|
22353
|
-
return apiGet(`/tailnet/${getTailnet()}/services/${encPath(input.serviceName)}/
|
|
22617
|
+
return apiGet(`/tailnet/${getTailnet()}/services/${encPath(input.serviceName)}/devices`);
|
|
22618
|
+
}
|
|
22619
|
+
},
|
|
22620
|
+
{
|
|
22621
|
+
name: "tailscale_get_service_device_approval",
|
|
22622
|
+
description: "Get the approval status of a specific device for a Tailscale Service.",
|
|
22623
|
+
annotations: {
|
|
22624
|
+
title: "Get service device approval",
|
|
22625
|
+
readOnlyHint: true,
|
|
22626
|
+
destructiveHint: false,
|
|
22627
|
+
idempotentHint: true,
|
|
22628
|
+
openWorldHint: true
|
|
22629
|
+
},
|
|
22630
|
+
inputSchema: external_exports.object({
|
|
22631
|
+
serviceName: external_exports.string().describe("The service name"),
|
|
22632
|
+
deviceId: external_exports.string().describe("The device ID")
|
|
22633
|
+
}),
|
|
22634
|
+
handler: async (input) => {
|
|
22635
|
+
return apiGet(
|
|
22636
|
+
`/tailnet/${getTailnet()}/services/${encPath(input.serviceName)}/device/${encPath(input.deviceId)}/approved`
|
|
22637
|
+
);
|
|
22638
|
+
}
|
|
22639
|
+
},
|
|
22640
|
+
{
|
|
22641
|
+
name: "tailscale_set_service_device_approval",
|
|
22642
|
+
description: "Approve or reject a device to host a Tailscale Service.",
|
|
22643
|
+
annotations: {
|
|
22644
|
+
title: "Set service device approval",
|
|
22645
|
+
readOnlyHint: false,
|
|
22646
|
+
destructiveHint: false,
|
|
22647
|
+
idempotentHint: true,
|
|
22648
|
+
openWorldHint: true
|
|
22649
|
+
},
|
|
22650
|
+
inputSchema: external_exports.object({
|
|
22651
|
+
serviceName: external_exports.string().describe("The service name"),
|
|
22652
|
+
deviceId: external_exports.string().describe("The device ID"),
|
|
22653
|
+
approved: external_exports.boolean().describe("Whether to approve (true) or reject (false) the device")
|
|
22654
|
+
}),
|
|
22655
|
+
handler: async (input) => {
|
|
22656
|
+
return apiPost(
|
|
22657
|
+
`/tailnet/${getTailnet()}/services/${encPath(input.serviceName)}/device/${encPath(input.deviceId)}/approved`,
|
|
22658
|
+
{ approved: input.approved }
|
|
22659
|
+
);
|
|
22354
22660
|
}
|
|
22355
22661
|
}
|
|
22356
22662
|
];
|
|
@@ -22428,21 +22734,15 @@ var tailnetTools = [
|
|
|
22428
22734
|
networkFlowLoggingOn: external_exports.boolean().optional().describe("Whether network flow logging is enabled"),
|
|
22429
22735
|
regionalRoutingOn: external_exports.boolean().optional().describe("Whether regional routing is enabled"),
|
|
22430
22736
|
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)")
|
|
22737
|
+
httpsEnabled: external_exports.boolean().optional().describe("Whether HTTPS certificates are enabled (for tailscale serve/funnel)"),
|
|
22738
|
+
aclsExternallyManagedOn: external_exports.boolean().optional().describe("Whether ACLs are externally managed (e.g. via GitOps)"),
|
|
22739
|
+
aclsExternalLink: external_exports.string().optional().describe("URL to the external ACL management system (shown in the admin console)")
|
|
22432
22740
|
}),
|
|
22433
22741
|
handler: async (input) => {
|
|
22434
22742
|
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;
|
|
22743
|
+
for (const [key, value] of Object.entries(input)) {
|
|
22744
|
+
if (value !== void 0) body[key] = value;
|
|
22745
|
+
}
|
|
22446
22746
|
return apiPatch(`/tailnet/${getTailnet()}/settings`, body);
|
|
22447
22747
|
}
|
|
22448
22748
|
},
|
|
@@ -22477,11 +22777,33 @@ var tailnetTools = [
|
|
|
22477
22777
|
security: external_exports.object({ email: external_exports.string() }).optional().describe("Security contact email")
|
|
22478
22778
|
}),
|
|
22479
22779
|
handler: async (input) => {
|
|
22480
|
-
const
|
|
22481
|
-
|
|
22482
|
-
|
|
22483
|
-
|
|
22484
|
-
|
|
22780
|
+
const results = {};
|
|
22781
|
+
for (const contactType of ["account", "support", "security"]) {
|
|
22782
|
+
const value = input[contactType];
|
|
22783
|
+
if (value !== void 0) {
|
|
22784
|
+
const res = await apiPatch(`/tailnet/${getTailnet()}/contacts/${encPath(contactType)}`, value);
|
|
22785
|
+
if (!res.ok) return res;
|
|
22786
|
+
results[contactType] = res.data;
|
|
22787
|
+
}
|
|
22788
|
+
}
|
|
22789
|
+
return { ok: true, status: 200, data: results };
|
|
22790
|
+
}
|
|
22791
|
+
},
|
|
22792
|
+
{
|
|
22793
|
+
name: "tailscale_resend_contact_verification",
|
|
22794
|
+
description: "Resend the verification email for a tailnet contact.",
|
|
22795
|
+
annotations: {
|
|
22796
|
+
title: "Resend contact verification",
|
|
22797
|
+
readOnlyHint: false,
|
|
22798
|
+
destructiveHint: false,
|
|
22799
|
+
idempotentHint: true,
|
|
22800
|
+
openWorldHint: true
|
|
22801
|
+
},
|
|
22802
|
+
inputSchema: external_exports.object({
|
|
22803
|
+
contactType: external_exports.enum(["account", "support", "security"]).describe("The contact type to resend verification for")
|
|
22804
|
+
}),
|
|
22805
|
+
handler: async (input) => {
|
|
22806
|
+
return apiPost(`/tailnet/${getTailnet()}/contacts/${encPath(input.contactType)}/resend-verification-email`);
|
|
22485
22807
|
}
|
|
22486
22808
|
}
|
|
22487
22809
|
];
|
|
@@ -22586,7 +22908,24 @@ var userTools = [
|
|
|
22586
22908
|
role: external_exports.enum(["owner", "admin", "it-admin", "network-admin", "billing-admin", "auditor", "member"]).describe("The new role to assign")
|
|
22587
22909
|
}),
|
|
22588
22910
|
handler: async (input) => {
|
|
22589
|
-
return
|
|
22911
|
+
return apiPost(`/users/${encPath(input.userId)}/role`, { role: input.role });
|
|
22912
|
+
}
|
|
22913
|
+
},
|
|
22914
|
+
{
|
|
22915
|
+
name: "tailscale_delete_user",
|
|
22916
|
+
description: "Delete a user from the tailnet. This is irreversible \u2014 the user and all their devices will be removed.",
|
|
22917
|
+
annotations: {
|
|
22918
|
+
title: "Delete user",
|
|
22919
|
+
readOnlyHint: false,
|
|
22920
|
+
destructiveHint: true,
|
|
22921
|
+
idempotentHint: false,
|
|
22922
|
+
openWorldHint: true
|
|
22923
|
+
},
|
|
22924
|
+
inputSchema: external_exports.object({
|
|
22925
|
+
userId: external_exports.string().describe("The user ID to delete")
|
|
22926
|
+
}),
|
|
22927
|
+
handler: async (input) => {
|
|
22928
|
+
return apiPost(`/users/${encPath(input.userId)}/delete`);
|
|
22590
22929
|
}
|
|
22591
22930
|
}
|
|
22592
22931
|
];
|
|
@@ -22705,6 +23044,23 @@ var webhookTools = [
|
|
|
22705
23044
|
handler: async (input) => {
|
|
22706
23045
|
return apiPost(`/webhooks/${encPath(input.webhookId)}/rotate`);
|
|
22707
23046
|
}
|
|
23047
|
+
},
|
|
23048
|
+
{
|
|
23049
|
+
name: "tailscale_test_webhook",
|
|
23050
|
+
description: "Send a test event to a webhook endpoint to verify it is configured correctly and receiving events.",
|
|
23051
|
+
annotations: {
|
|
23052
|
+
title: "Test webhook",
|
|
23053
|
+
readOnlyHint: false,
|
|
23054
|
+
destructiveHint: false,
|
|
23055
|
+
idempotentHint: true,
|
|
23056
|
+
openWorldHint: true
|
|
23057
|
+
},
|
|
23058
|
+
inputSchema: external_exports.object({
|
|
23059
|
+
webhookId: external_exports.string().describe("The webhook ID to test")
|
|
23060
|
+
}),
|
|
23061
|
+
handler: async (input) => {
|
|
23062
|
+
return apiPost(`/webhooks/${encPath(input.webhookId)}/test`);
|
|
23063
|
+
}
|
|
22708
23064
|
}
|
|
22709
23065
|
];
|
|
22710
23066
|
|
|
@@ -22807,7 +23163,7 @@ var workloadIdentityTools = [
|
|
|
22807
23163
|
];
|
|
22808
23164
|
|
|
22809
23165
|
// src/index.ts
|
|
22810
|
-
var version2 = true ? "0.
|
|
23166
|
+
var version2 = true ? "0.5.1" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
22811
23167
|
var subcommand = process.argv[2];
|
|
22812
23168
|
if (subcommand === "deploy-acl") {
|
|
22813
23169
|
const filePath = process.argv[3];
|