@yawlabs/tailscale-mcp 0.8.8 → 0.9.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 +17 -52
- package/dist/index.js +101 -297
- 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
|
-
**Ask your agent questions about your tailnet and have it act on the answers.**
|
|
8
|
+
**Ask your agent questions about your tailnet and have it act on the answers.** 88 tools + 4 resources covering the full [Tailscale v2 API](https://tailscale.com/api). Backed by 700+ unit tests and an opt-in live-tailnet integration suite.
|
|
9
9
|
|
|
10
10
|
Built and maintained by [Yaw Labs](https://yaw.sh).
|
|
11
11
|
|
|
@@ -20,8 +20,8 @@ You could `curl` the Tailscale API. The point isn't replacing `curl` — it's le
|
|
|
20
20
|
- **"Which devices haven't checked in for 30 days and have key expiry disabled?"** — lists devices, filters by `lastSeen`, filters by `keyExpiryDisabled`, returns a table. Three endpoints, one question.
|
|
21
21
|
- **"Someone broke DNS at 2am — who changed what in the last 24 hours?"** — pulls the audit log, filters by DNS-related actors and endpoints, reads each change's before/after, summarizes in English.
|
|
22
22
|
- **"Draft an ACL change that lets `tag:mobile` reach `tag:dashboard` but not `tag:db`, preserving my comments"** — reads the current HuJSON, proposes a minimal diff, validates it against the API, returns the diff for you to apply.
|
|
23
|
-
- **"Show me the OIDC workload identity for our GitHub Actions and confirm its allowed subjects still match `repo:Acme/*`"** — fetches the workload identity, parses the JWT claim patterns, tells you whether the claim still matches your repo naming.
|
|
24
23
|
- **"Rotate every auth key older than 90 days and print the new ones"** — iterates, creates new keys with matching tags, revokes the old ones.
|
|
24
|
+
- **"Create an OAuth client for our CI pipeline scoped to `devices:read` and `dns`"** — creates a trust credential via `tailscale_create_key` with `keyType=client`, returns the credentials once (save them immediately).
|
|
25
25
|
|
|
26
26
|
A curl can do each step. The agent composes them. That's where the lift is, and that's what the tool surface is designed for — every read endpoint is first-class so the agent can synthesize, and every write endpoint is tagged `destructiveHint` or `idempotentHint` so your MCP client can gate mutations the way you configured it.
|
|
27
27
|
|
|
@@ -31,11 +31,11 @@ If all you need is one endpoint in a CI job, use `curl` — we even have a [CLI
|
|
|
31
31
|
|
|
32
32
|
Reasonable question. Both have their place. Where this MCP is better:
|
|
33
33
|
|
|
34
|
-
- **Full admin API coverage.** The `tailscale` CLI is scoped to the node it runs on. Admin concerns — ACLs, users, invites, webhooks, log streaming,
|
|
34
|
+
- **Full admin API coverage.** The `tailscale` CLI is scoped to the node it runs on. Admin concerns — ACLs, users, invites, webhooks, log streaming, posture integrations, auth keys, OAuth clients, and federated identities — live in the v2 HTTP API. You'd be shelling out to `curl` anyway.
|
|
35
35
|
- **Typed tool surface, not string parsing.** Every tool has a Zod-validated input schema and a structured response. No brittle `tailscale status --json | jq` pipelines that break when the schema evolves.
|
|
36
36
|
- **Cross-client, no user rewriting.** A Claude Code skill only loads in Claude Code. An MCP server works in Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, and anything else that speaks MCP. Version bumps ship through `npx` — users don't re-author their skill when Tailscale adds an endpoint.
|
|
37
37
|
- **Safe-by-default writes.** Every tool declares `readOnlyHint` / `destructiveHint` / `idempotentHint` so clients can skip confirmation on reads and require it on mutations. A skill that shells out to the CLI can't express that.
|
|
38
|
-
- **Real tests.**
|
|
38
|
+
- **Real tests.** 700+ unit tests covering every tool's input validation, API shape, and error handling. Plus an opt-in live-tailnet integration suite (`RUN_INTEGRATION_TESTS=1` + a tailnet API key) for shape-drift detection. Most skills are short markdown prompts without their own test layer — if the vendor changes output format, nothing catches it for you.
|
|
39
39
|
|
|
40
40
|
If you already have a skill that covers your 10% of Tailscale workflows, great — keep it. The MCP is for the other 90%.
|
|
41
41
|
|
|
@@ -43,7 +43,7 @@ If you already have a skill that covers your 10% of Tailscale workflows, great
|
|
|
43
43
|
|
|
44
44
|
Fair critique from Reddit: a new repo claiming "actively maintained" with no visible tests is worth exactly zero trust. Here's what's actually verifiable:
|
|
45
45
|
|
|
46
|
-
- **
|
|
46
|
+
- **700+ tests** (`node --test`) covering every tool's input validation, API shape, and error handling. Run `npm test` to see them pass locally.
|
|
47
47
|
- **3 CI workflows** on GitHub Actions:
|
|
48
48
|
- [`ci.yml`](.github/workflows/ci.yml) — lint + typecheck + build + unit tests on every push and PR.
|
|
49
49
|
- [`integration.yml`](.github/workflows/integration.yml) — read-only live-API smoke tests against a real tailnet. Wired up with three triggers (nightly schedule, every tag push via `release.yml`, manual dispatch); skips gracefully when no test-tailnet secret is configured, so forks aren't blocked.
|
|
@@ -107,7 +107,7 @@ That's it. Now ask your agent:
|
|
|
107
107
|
|
|
108
108
|
## Too many tools? Subset them.
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
88 tools is a lot. If you've already got a dozen MCP servers and your client is feeling heavy, trim what this one exposes. Three knobs, combinable:
|
|
111
111
|
|
|
112
112
|
### Option 1: `TAILSCALE_PROFILE` (preset, easiest)
|
|
113
113
|
|
|
@@ -122,7 +122,7 @@ That's it. Now ask your agent:
|
|
|
122
122
|
|
|
123
123
|
- **`minimal`** (19 tools) — `status`, `devices`, `audit`. Observe the tailnet, read the audit log.
|
|
124
124
|
- **`core`** (46 tools) — adds `acl`, `dns`, `keys`, `users`. The day-to-day admin surface.
|
|
125
|
-
- **`full`** (
|
|
125
|
+
- **`full`** (88 tools, default) — everything. Same as omitting the env var.
|
|
126
126
|
|
|
127
127
|
### Option 2: `TAILSCALE_TOOLS` (explicit group list)
|
|
128
128
|
|
|
@@ -137,7 +137,7 @@ That's it. Now ask your agent:
|
|
|
137
137
|
|
|
138
138
|
Comma-separated group names. Overrides `TAILSCALE_PROFILE` when both are set — use this when the presets aren't quite right.
|
|
139
139
|
|
|
140
|
-
Valid group names: `status`, `devices`, `acl`, `dns`, `keys`, `users`, `tailnet`, `webhooks`, `
|
|
140
|
+
Valid group names: `status`, `devices`, `acl`, `dns`, `keys`, `users`, `tailnet`, `webhooks`, `posture`, `audit`, `invites`, `services`, `log-streaming`.
|
|
141
141
|
|
|
142
142
|
### Option 3: `TAILSCALE_READONLY` (drop mutations)
|
|
143
143
|
|
|
@@ -158,7 +158,7 @@ Set to `1` or `true` to drop every tool without `readOnlyHint: true`. Stacks wit
|
|
|
158
158
|
The server logs the active filter to stderr on startup:
|
|
159
159
|
|
|
160
160
|
```
|
|
161
|
-
@yawlabs/tailscale-mcp v0.
|
|
161
|
+
@yawlabs/tailscale-mcp v0.9.1 ready (19 tools, profile=core, readonly)
|
|
162
162
|
```
|
|
163
163
|
|
|
164
164
|
If you don't set any filter, startup prints a tip pointing you at the profiles.
|
|
@@ -193,7 +193,7 @@ MCP Resources expose read-only data clients can browse without a tool call.
|
|
|
193
193
|
| ACL Policy | `tailscale://tailnet/acl` | Full ACL policy (HuJSON preserved) |
|
|
194
194
|
| DNS Config | `tailscale://tailnet/dns` | Nameservers, search paths, split DNS, MagicDNS |
|
|
195
195
|
|
|
196
|
-
## Tools (
|
|
196
|
+
## Tools (88)
|
|
197
197
|
|
|
198
198
|
<details>
|
|
199
199
|
<summary><strong>Status</strong> (1 tool)</summary>
|
|
@@ -260,15 +260,15 @@ MCP Resources expose read-only data clients can browse without a tool call.
|
|
|
260
260
|
</details>
|
|
261
261
|
|
|
262
262
|
<details>
|
|
263
|
-
<summary><strong>
|
|
263
|
+
<summary><strong>Keys / Trust Credentials</strong> (5 tools) — covers auth keys, OAuth clients, and federated identities</summary>
|
|
264
264
|
|
|
265
265
|
| Tool | Description |
|
|
266
266
|
|------|-------------|
|
|
267
|
-
| `tailscale_list_keys` | List auth keys |
|
|
268
|
-
| `tailscale_get_key` | Get details for
|
|
269
|
-
| `tailscale_create_key` | Create
|
|
270
|
-
| `tailscale_delete_key` | Delete
|
|
271
|
-
| `tailscale_update_key` | Update
|
|
267
|
+
| `tailscale_list_keys` | List keys (auth keys; pass `all=true` to include OAuth clients and federated identities) |
|
|
268
|
+
| `tailscale_get_key` | Get details for a key |
|
|
269
|
+
| `tailscale_create_key` | Create an auth key, OAuth client (`keyType=client`), or federated identity (`keyType=federated`) |
|
|
270
|
+
| `tailscale_delete_key` | Delete a key |
|
|
271
|
+
| `tailscale_update_key` | Update a key's description, scopes, tags, or federated claim settings |
|
|
272
272
|
|
|
273
273
|
</details>
|
|
274
274
|
|
|
@@ -300,15 +300,6 @@ MCP Resources expose read-only data clients can browse without a tool call.
|
|
|
300
300
|
|
|
301
301
|
</details>
|
|
302
302
|
|
|
303
|
-
<details>
|
|
304
|
-
<summary><strong>Network Lock</strong> (1 tool)</summary>
|
|
305
|
-
|
|
306
|
-
| Tool | Description |
|
|
307
|
-
|------|-------------|
|
|
308
|
-
| `tailscale_get_network_lock_status` | Get tailnet lock status and trusted signing keys |
|
|
309
|
-
|
|
310
|
-
</details>
|
|
311
|
-
|
|
312
303
|
<details>
|
|
313
304
|
<summary><strong>Webhooks</strong> (7 tools)</summary>
|
|
314
305
|
|
|
@@ -367,32 +358,6 @@ MCP Resources expose read-only data clients can browse without a tool call.
|
|
|
367
358
|
|
|
368
359
|
</details>
|
|
369
360
|
|
|
370
|
-
<details>
|
|
371
|
-
<summary><strong>Workload Identity</strong> (5 tools)</summary>
|
|
372
|
-
|
|
373
|
-
| Tool | Description |
|
|
374
|
-
|------|-------------|
|
|
375
|
-
| `tailscale_list_workload_identities` | List federated workload identity providers |
|
|
376
|
-
| `tailscale_get_workload_identity` | Get a workload identity provider |
|
|
377
|
-
| `tailscale_create_workload_identity` | Create an OIDC federation provider (GitHub Actions, GitLab CI, etc.) |
|
|
378
|
-
| `tailscale_update_workload_identity` | Update a workload identity provider |
|
|
379
|
-
| `tailscale_delete_workload_identity` | Delete a workload identity provider |
|
|
380
|
-
|
|
381
|
-
</details>
|
|
382
|
-
|
|
383
|
-
<details>
|
|
384
|
-
<summary><strong>OAuth Clients</strong> (5 tools)</summary>
|
|
385
|
-
|
|
386
|
-
| Tool | Description |
|
|
387
|
-
|------|-------------|
|
|
388
|
-
| `tailscale_list_oauth_clients` | List OAuth clients |
|
|
389
|
-
| `tailscale_get_oauth_client` | Get an OAuth client |
|
|
390
|
-
| `tailscale_create_oauth_client` | Create an OAuth client for programmatic API access |
|
|
391
|
-
| `tailscale_update_oauth_client` | Update an OAuth client |
|
|
392
|
-
| `tailscale_delete_oauth_client` | Delete an OAuth client |
|
|
393
|
-
|
|
394
|
-
</details>
|
|
395
|
-
|
|
396
361
|
<details>
|
|
397
362
|
<summary><strong>Device Invites</strong> (6 tools)</summary>
|
|
398
363
|
|
|
@@ -462,7 +427,7 @@ npm install
|
|
|
462
427
|
npm run lint # Biome check
|
|
463
428
|
npm run lint:fix # Auto-fix
|
|
464
429
|
npm run build # tsc + esbuild bundle
|
|
465
|
-
npm test # node --test (
|
|
430
|
+
npm test # node --test (full suite)
|
|
466
431
|
```
|
|
467
432
|
|
|
468
433
|
For integration testing against your own tailnet: set `TAILSCALE_API_KEY` and run `node dist/index.js`.
|
package/dist/index.js
CHANGED
|
@@ -30339,7 +30339,8 @@ function filterTools(groups, options) {
|
|
|
30339
30339
|
unknownProfile2 = profileKey;
|
|
30340
30340
|
}
|
|
30341
30341
|
}
|
|
30342
|
-
const
|
|
30342
|
+
const parsedTools = options.tools ? options.tools.split(",").map((s) => s.trim()).filter(Boolean) : null;
|
|
30343
|
+
const explicitTools = parsedTools && parsedTools.length > 0 ? parsedTools : null;
|
|
30343
30344
|
const effectiveGroups = explicitTools ?? profileGroups ?? null;
|
|
30344
30345
|
const enabledGroups = effectiveGroups ? new Set(effectiveGroups) : null;
|
|
30345
30346
|
const unknownGroups2 = enabledGroups ? [...enabledGroups].filter((g) => !validNames.has(g)) : [];
|
|
@@ -30470,7 +30471,8 @@ Pass this ETag to tailscale_update_acl when updating the policy.`
|
|
|
30470
30471
|
|
|
30471
30472
|
// src/tools/audit.ts
|
|
30472
30473
|
function assertRFC3339(value, label) {
|
|
30473
|
-
|
|
30474
|
+
const rfc3339 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
|
|
30475
|
+
if (!rfc3339.test(value) || Number.isNaN(Date.parse(value))) {
|
|
30474
30476
|
throw new Error(`${label} must be a valid RFC3339 date-time (e.g. '2026-04-01T00:00:00Z'), got: '${value}'`);
|
|
30475
30477
|
}
|
|
30476
30478
|
}
|
|
@@ -30611,7 +30613,7 @@ var deviceTools = [
|
|
|
30611
30613
|
title: "Delete device",
|
|
30612
30614
|
readOnlyHint: false,
|
|
30613
30615
|
destructiveHint: true,
|
|
30614
|
-
idempotentHint:
|
|
30616
|
+
idempotentHint: true,
|
|
30615
30617
|
openWorldHint: true
|
|
30616
30618
|
},
|
|
30617
30619
|
inputSchema: external_exports3.object({
|
|
@@ -30817,7 +30819,7 @@ var deviceTools = [
|
|
|
30817
30819
|
},
|
|
30818
30820
|
{
|
|
30819
30821
|
name: "tailscale_batch_update_posture_attributes",
|
|
30820
|
-
description: "Batch update custom posture attributes across multiple devices. Each attribute key must start with 'custom:'.",
|
|
30822
|
+
description: "Batch update custom posture attributes across multiple devices. Each attribute key must start with 'custom:'. Uses JSON Merge Patch semantics \u2014 pass null as the attribute config to delete.",
|
|
30821
30823
|
annotations: {
|
|
30822
30824
|
title: "Batch update posture attributes",
|
|
30823
30825
|
readOnlyHint: false,
|
|
@@ -30826,13 +30828,26 @@ var deviceTools = [
|
|
|
30826
30828
|
openWorldHint: true
|
|
30827
30829
|
},
|
|
30828
30830
|
inputSchema: external_exports3.object({
|
|
30829
|
-
|
|
30830
|
-
|
|
30831
|
-
|
|
30831
|
+
nodes: external_exports3.record(
|
|
30832
|
+
external_exports3.string(),
|
|
30833
|
+
external_exports3.record(
|
|
30834
|
+
external_exports3.string(),
|
|
30835
|
+
external_exports3.union([
|
|
30836
|
+
external_exports3.object({
|
|
30837
|
+
value: external_exports3.union([external_exports3.string(), external_exports3.number(), external_exports3.boolean()]),
|
|
30838
|
+
expiry: external_exports3.string().optional()
|
|
30839
|
+
}),
|
|
30840
|
+
external_exports3.null()
|
|
30841
|
+
])
|
|
30842
|
+
)
|
|
30843
|
+
).describe(
|
|
30844
|
+
'Map of device ID to attribute config map (e.g. { "12345": { "custom:compliant": { "value": "true" } }, "67890": { "custom:compliant": { "value": false, "expiry": "2026-12-01T00:00:00Z" } } }). Pass null as the config to delete an attribute.'
|
|
30845
|
+
),
|
|
30846
|
+
comment: external_exports3.string().optional().describe("Optional comment added to the audit log explaining why attributes are being set (max 200 chars)")
|
|
30832
30847
|
}),
|
|
30833
30848
|
handler: async (input) => {
|
|
30834
30849
|
const invalidKeys = [];
|
|
30835
|
-
for (const attrs of Object.values(input.
|
|
30850
|
+
for (const attrs of Object.values(input.nodes)) {
|
|
30836
30851
|
for (const key of Object.keys(attrs)) {
|
|
30837
30852
|
if (!key.startsWith("custom:")) invalidKeys.push(key);
|
|
30838
30853
|
}
|
|
@@ -30842,7 +30857,9 @@ var deviceTools = [
|
|
|
30842
30857
|
`All attribute keys must start with 'custom:' prefix. Invalid keys: ${[...new Set(invalidKeys)].join(", ")}`
|
|
30843
30858
|
);
|
|
30844
30859
|
}
|
|
30845
|
-
|
|
30860
|
+
const body = { nodes: input.nodes };
|
|
30861
|
+
if (input.comment !== void 0) body.comment = input.comment;
|
|
30862
|
+
return apiPatch(`/tailnet/${getTailnet()}/device-attributes`, body);
|
|
30846
30863
|
}
|
|
30847
30864
|
}
|
|
30848
30865
|
];
|
|
@@ -31267,16 +31284,16 @@ var keyTools = [
|
|
|
31267
31284
|
},
|
|
31268
31285
|
{
|
|
31269
31286
|
name: "tailscale_get_key",
|
|
31270
|
-
description: "Get details for a specific auth key.",
|
|
31287
|
+
description: "Get details for a specific key (auth key, OAuth client, or federated identity).",
|
|
31271
31288
|
annotations: {
|
|
31272
|
-
title: "Get
|
|
31289
|
+
title: "Get key",
|
|
31273
31290
|
readOnlyHint: true,
|
|
31274
31291
|
destructiveHint: false,
|
|
31275
31292
|
idempotentHint: true,
|
|
31276
31293
|
openWorldHint: true
|
|
31277
31294
|
},
|
|
31278
31295
|
inputSchema: external_exports3.object({
|
|
31279
|
-
keyId: external_exports3.string().describe("The auth key
|
|
31296
|
+
keyId: external_exports3.string().describe("The key ID (auth key, OAuth client, or federated identity)")
|
|
31280
31297
|
}),
|
|
31281
31298
|
handler: async (input) => {
|
|
31282
31299
|
return apiGet(`/tailnet/${getTailnet()}/keys/${encPath(input.keyId)}`);
|
|
@@ -31359,16 +31376,16 @@ var keyTools = [
|
|
|
31359
31376
|
},
|
|
31360
31377
|
{
|
|
31361
31378
|
name: "tailscale_delete_key",
|
|
31362
|
-
description: "Delete
|
|
31379
|
+
description: "Delete a key (auth key, OAuth client, or federated identity). This is irreversible. For auth keys, devices already authenticated are unaffected but no new devices can use it. For OAuth clients and federated identities, any integrations using them lose access immediately.",
|
|
31363
31380
|
annotations: {
|
|
31364
|
-
title: "Delete
|
|
31381
|
+
title: "Delete key",
|
|
31365
31382
|
readOnlyHint: false,
|
|
31366
31383
|
destructiveHint: true,
|
|
31367
31384
|
idempotentHint: true,
|
|
31368
31385
|
openWorldHint: true
|
|
31369
31386
|
},
|
|
31370
31387
|
inputSchema: external_exports3.object({
|
|
31371
|
-
keyId: external_exports3.string().describe("The
|
|
31388
|
+
keyId: external_exports3.string().describe("The key ID to delete (auth key, OAuth client, or federated identity)")
|
|
31372
31389
|
}),
|
|
31373
31390
|
handler: async (input) => {
|
|
31374
31391
|
return apiDelete(`/tailnet/${getTailnet()}/keys/${encPath(input.keyId)}`);
|
|
@@ -31416,7 +31433,7 @@ var keyTools = [
|
|
|
31416
31433
|
var logStreamingTools = [
|
|
31417
31434
|
{
|
|
31418
31435
|
name: "tailscale_list_log_stream_configs",
|
|
31419
|
-
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,
|
|
31436
|
+
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, or S3.",
|
|
31420
31437
|
annotations: {
|
|
31421
31438
|
title: "List log stream configs",
|
|
31422
31439
|
readOnlyHint: true,
|
|
@@ -31430,14 +31447,22 @@ var logStreamingTools = [
|
|
|
31430
31447
|
apiGet(`/tailnet/${getTailnet()}/logging/configuration/stream`),
|
|
31431
31448
|
apiGet(`/tailnet/${getTailnet()}/logging/network/stream`)
|
|
31432
31449
|
]);
|
|
31433
|
-
|
|
31434
|
-
|
|
31435
|
-
|
|
31436
|
-
|
|
31437
|
-
|
|
31438
|
-
|
|
31439
|
-
|
|
31450
|
+
const errors = {};
|
|
31451
|
+
if (!configuration.ok) errors.configuration = configuration.error ?? `HTTP ${configuration.status}`;
|
|
31452
|
+
if (!network.ok) errors.network = network.error ?? `HTTP ${network.status}`;
|
|
31453
|
+
if (!configuration.ok && !network.ok) {
|
|
31454
|
+
return {
|
|
31455
|
+
ok: false,
|
|
31456
|
+
status: configuration.status || network.status || 500,
|
|
31457
|
+
error: `Both log streams failed: ${JSON.stringify(errors)}`
|
|
31458
|
+
};
|
|
31459
|
+
}
|
|
31460
|
+
const data = {
|
|
31461
|
+
configuration: configuration.ok ? configuration.data : null,
|
|
31462
|
+
network: network.ok ? network.data : null
|
|
31440
31463
|
};
|
|
31464
|
+
if (Object.keys(errors).length > 0) data.errors = errors;
|
|
31465
|
+
return { ok: true, status: 200, data };
|
|
31441
31466
|
}
|
|
31442
31467
|
},
|
|
31443
31468
|
{
|
|
@@ -31459,7 +31484,7 @@ var logStreamingTools = [
|
|
|
31459
31484
|
},
|
|
31460
31485
|
{
|
|
31461
31486
|
name: "tailscale_set_log_stream_config",
|
|
31462
|
-
description: "Set the log streaming configuration for a specific log type. Configures where logs are sent (e.g. Axiom, Datadog, Splunk, Elasticsearch, S3,
|
|
31487
|
+
description: "Set the log streaming configuration for a specific log type. Configures where logs are sent (e.g. Axiom, Datadog, Splunk, Elasticsearch, S3).\n\nPer-destination required fields:\n- splunk / elastic / panther / cribl / datadog / axiom: url + token (user optional)\n- s3: s3Bucket + s3Region + s3AuthenticationType, plus either (s3AccessKeyId + s3SecretAccessKey) for 'accesskey' auth or s3RoleArn for 'rolearn' auth. Call tailscale_create_aws_external_id first when using 'rolearn'.",
|
|
31463
31488
|
annotations: {
|
|
31464
31489
|
title: "Set log stream config",
|
|
31465
31490
|
readOnlyHint: false,
|
|
@@ -31470,9 +31495,22 @@ var logStreamingTools = [
|
|
|
31470
31495
|
inputSchema: external_exports3.object({
|
|
31471
31496
|
logType: external_exports3.enum(["configuration", "network"]).describe("The log type: 'configuration' for audit logs, 'network' for network flow logs"),
|
|
31472
31497
|
destinationType: external_exports3.enum(["splunk", "elastic", "panther", "cribl", "datadog", "axiom", "s3"]).describe("The log streaming destination type"),
|
|
31473
|
-
url: external_exports3.string().optional().describe("Destination URL (required for
|
|
31498
|
+
url: external_exports3.string().optional().describe("Destination URL (required for non-s3 destinations)"),
|
|
31474
31499
|
token: external_exports3.string().optional().describe("Authentication token or API key for the destination"),
|
|
31475
|
-
user: external_exports3.string().optional().describe("Username for the destination (if required)")
|
|
31500
|
+
user: external_exports3.string().optional().describe("Username for the destination (if required)"),
|
|
31501
|
+
uploadPeriodMinutes: external_exports3.number().optional().describe("Minutes to wait between uploads (max 1440). Optional."),
|
|
31502
|
+
compressionFormat: external_exports3.enum(["zstd", "gzip", "none"]).optional().describe("Compression algorithm for log uploads. Defaults to 'none'."),
|
|
31503
|
+
s3Bucket: external_exports3.string().optional().describe("(s3 only) S3 bucket name. Required when destinationType is 's3'."),
|
|
31504
|
+
s3Region: external_exports3.string().optional().describe("(s3 only) AWS region of the S3 bucket. Required when destinationType is 's3'."),
|
|
31505
|
+
s3KeyPrefix: external_exports3.string().optional().describe("(s3 only) Optional prefix prepended to the auto-generated S3 object key."),
|
|
31506
|
+
s3AuthenticationType: external_exports3.enum(["accesskey", "rolearn"]).optional().describe(
|
|
31507
|
+
"(s3 only) Authentication mode. Required when destinationType is 's3'. Tailscale recommends 'rolearn'."
|
|
31508
|
+
),
|
|
31509
|
+
s3AccessKeyId: external_exports3.string().optional().describe("(s3 only) AWS access key id. Required when s3AuthenticationType is 'accesskey'."),
|
|
31510
|
+
s3SecretAccessKey: external_exports3.string().optional().describe("(s3 only) AWS secret access key. Required when s3AuthenticationType is 'accesskey'."),
|
|
31511
|
+
s3RoleArn: external_exports3.string().optional().describe(
|
|
31512
|
+
"(s3 only) IAM role ARN that Tailscale will assume. Required when s3AuthenticationType is 'rolearn'."
|
|
31513
|
+
)
|
|
31476
31514
|
}),
|
|
31477
31515
|
handler: async (input) => {
|
|
31478
31516
|
const { logType, ...body } = input;
|
|
@@ -31555,135 +31593,6 @@ var logStreamingTools = [
|
|
|
31555
31593
|
}
|
|
31556
31594
|
];
|
|
31557
31595
|
|
|
31558
|
-
// src/tools/network-lock.ts
|
|
31559
|
-
var networkLockTools = [
|
|
31560
|
-
{
|
|
31561
|
-
name: "tailscale_get_network_lock_status",
|
|
31562
|
-
description: "Get the tailnet lock (network lock) status, including whether it is enabled and the list of trusted signing keys.",
|
|
31563
|
-
annotations: {
|
|
31564
|
-
title: "Get network lock status",
|
|
31565
|
-
readOnlyHint: true,
|
|
31566
|
-
destructiveHint: false,
|
|
31567
|
-
idempotentHint: true,
|
|
31568
|
-
openWorldHint: true
|
|
31569
|
-
},
|
|
31570
|
-
inputSchema: external_exports3.object({}),
|
|
31571
|
-
handler: async () => {
|
|
31572
|
-
return apiGet(`/tailnet/${getTailnet()}/network-lock/status`);
|
|
31573
|
-
}
|
|
31574
|
-
}
|
|
31575
|
-
];
|
|
31576
|
-
|
|
31577
|
-
// src/tools/oauth-clients.ts
|
|
31578
|
-
var oauthClientTools = [
|
|
31579
|
-
{
|
|
31580
|
-
name: "tailscale_list_oauth_clients",
|
|
31581
|
-
description: "List all OAuth clients configured for your tailnet.",
|
|
31582
|
-
annotations: {
|
|
31583
|
-
title: "List OAuth clients",
|
|
31584
|
-
readOnlyHint: true,
|
|
31585
|
-
destructiveHint: false,
|
|
31586
|
-
idempotentHint: true,
|
|
31587
|
-
openWorldHint: true
|
|
31588
|
-
},
|
|
31589
|
-
inputSchema: external_exports3.object({}),
|
|
31590
|
-
handler: async () => {
|
|
31591
|
-
return apiGet(`/tailnet/${getTailnet()}/oauth-clients`);
|
|
31592
|
-
}
|
|
31593
|
-
},
|
|
31594
|
-
{
|
|
31595
|
-
name: "tailscale_get_oauth_client",
|
|
31596
|
-
description: "Get details for a specific OAuth client.",
|
|
31597
|
-
annotations: {
|
|
31598
|
-
title: "Get OAuth client",
|
|
31599
|
-
readOnlyHint: true,
|
|
31600
|
-
destructiveHint: false,
|
|
31601
|
-
idempotentHint: true,
|
|
31602
|
-
openWorldHint: true
|
|
31603
|
-
},
|
|
31604
|
-
inputSchema: external_exports3.object({
|
|
31605
|
-
clientId: external_exports3.string().describe("The OAuth client ID")
|
|
31606
|
-
}),
|
|
31607
|
-
handler: async (input) => {
|
|
31608
|
-
return apiGet(`/tailnet/${getTailnet()}/oauth-clients/${encPath(input.clientId)}`);
|
|
31609
|
-
}
|
|
31610
|
-
},
|
|
31611
|
-
{
|
|
31612
|
-
name: "tailscale_create_oauth_client",
|
|
31613
|
-
description: "Create a new OAuth client for programmatic API access. Returns the client secret \u2014 save it immediately, as it cannot be retrieved again.",
|
|
31614
|
-
annotations: {
|
|
31615
|
-
title: "Create OAuth client",
|
|
31616
|
-
readOnlyHint: false,
|
|
31617
|
-
destructiveHint: false,
|
|
31618
|
-
idempotentHint: false,
|
|
31619
|
-
openWorldHint: true
|
|
31620
|
-
},
|
|
31621
|
-
inputSchema: external_exports3.object({
|
|
31622
|
-
name: external_exports3.string().describe("A human-readable name for this OAuth client (max 50 chars, alphanumeric/hyphens/spaces)"),
|
|
31623
|
-
scopes: external_exports3.array(external_exports3.string()).describe(
|
|
31624
|
-
"OAuth scopes to grant (e.g. ['devices:read', 'dns', 'acl']). See Tailscale docs for available scopes."
|
|
31625
|
-
),
|
|
31626
|
-
tags: external_exports3.array(external_exports3.string()).optional().describe("ACL tags to assign to the OAuth client"),
|
|
31627
|
-
description: external_exports3.string().optional().describe("Description for this OAuth client (max 50 chars, alphanumeric/hyphens/spaces)")
|
|
31628
|
-
}),
|
|
31629
|
-
handler: async (input) => {
|
|
31630
|
-
validateTags(input.tags);
|
|
31631
|
-
const body = { ...input };
|
|
31632
|
-
body.name = sanitizeDescription(input.name);
|
|
31633
|
-
if (input.description !== void 0) body.description = sanitizeDescription(input.description);
|
|
31634
|
-
return apiPost(`/tailnet/${getTailnet()}/oauth-clients`, body);
|
|
31635
|
-
}
|
|
31636
|
-
},
|
|
31637
|
-
{
|
|
31638
|
-
name: "tailscale_update_oauth_client",
|
|
31639
|
-
description: "Update an OAuth client's name, description, or scopes.",
|
|
31640
|
-
annotations: {
|
|
31641
|
-
title: "Update OAuth client",
|
|
31642
|
-
readOnlyHint: false,
|
|
31643
|
-
destructiveHint: false,
|
|
31644
|
-
idempotentHint: true,
|
|
31645
|
-
openWorldHint: true
|
|
31646
|
-
},
|
|
31647
|
-
inputSchema: external_exports3.object({
|
|
31648
|
-
clientId: external_exports3.string().describe("The OAuth client ID to update"),
|
|
31649
|
-
name: external_exports3.string().optional().describe("Updated name"),
|
|
31650
|
-
scopes: external_exports3.array(external_exports3.string()).optional().describe("Updated OAuth scopes"),
|
|
31651
|
-
description: external_exports3.string().optional().describe("Updated description")
|
|
31652
|
-
}),
|
|
31653
|
-
handler: async (input) => {
|
|
31654
|
-
const { clientId, ...body } = input;
|
|
31655
|
-
const cleanBody = {};
|
|
31656
|
-
for (const [key, value] of Object.entries(body)) {
|
|
31657
|
-
if (value !== void 0) cleanBody[key] = value;
|
|
31658
|
-
}
|
|
31659
|
-
if (cleanBody.name !== void 0) cleanBody.name = sanitizeDescription(cleanBody.name);
|
|
31660
|
-
if (cleanBody.description !== void 0)
|
|
31661
|
-
cleanBody.description = sanitizeDescription(cleanBody.description);
|
|
31662
|
-
if (Object.keys(cleanBody).length === 0) {
|
|
31663
|
-
throw new Error("No fields to update. Provide at least one of: name, scopes, description.");
|
|
31664
|
-
}
|
|
31665
|
-
return apiPatch(`/tailnet/${getTailnet()}/oauth-clients/${encPath(clientId)}`, cleanBody);
|
|
31666
|
-
}
|
|
31667
|
-
},
|
|
31668
|
-
{
|
|
31669
|
-
name: "tailscale_delete_oauth_client",
|
|
31670
|
-
description: "Delete an OAuth client. This is irreversible \u2014 any integrations using this client will lose access immediately.",
|
|
31671
|
-
annotations: {
|
|
31672
|
-
title: "Delete OAuth client",
|
|
31673
|
-
readOnlyHint: false,
|
|
31674
|
-
destructiveHint: true,
|
|
31675
|
-
idempotentHint: true,
|
|
31676
|
-
openWorldHint: true
|
|
31677
|
-
},
|
|
31678
|
-
inputSchema: external_exports3.object({
|
|
31679
|
-
clientId: external_exports3.string().describe("The OAuth client ID to delete")
|
|
31680
|
-
}),
|
|
31681
|
-
handler: async (input) => {
|
|
31682
|
-
return apiDelete(`/tailnet/${getTailnet()}/oauth-clients/${encPath(input.clientId)}`);
|
|
31683
|
-
}
|
|
31684
|
-
}
|
|
31685
|
-
];
|
|
31686
|
-
|
|
31687
31596
|
// src/tools/posture.ts
|
|
31688
31597
|
var postureTools = [
|
|
31689
31598
|
{
|
|
@@ -31715,7 +31624,7 @@ var postureTools = [
|
|
|
31715
31624
|
integrationId: external_exports3.string().describe("The posture integration ID")
|
|
31716
31625
|
}),
|
|
31717
31626
|
handler: async (input) => {
|
|
31718
|
-
return apiGet(`/
|
|
31627
|
+
return apiGet(`/posture/integrations/${encPath(input.integrationId)}`);
|
|
31719
31628
|
}
|
|
31720
31629
|
},
|
|
31721
31630
|
{
|
|
@@ -31729,20 +31638,24 @@ var postureTools = [
|
|
|
31729
31638
|
openWorldHint: true
|
|
31730
31639
|
},
|
|
31731
31640
|
inputSchema: external_exports3.object({
|
|
31732
|
-
provider: external_exports3.
|
|
31733
|
-
clientId: external_exports3.string().describe(
|
|
31734
|
-
|
|
31735
|
-
|
|
31736
|
-
|
|
31641
|
+
provider: external_exports3.enum(["falcon", "intune", "jamfpro", "kandji", "kolide", "sentinelone"]).describe("The posture provider"),
|
|
31642
|
+
clientId: external_exports3.string().optional().describe(
|
|
31643
|
+
"Client ID for the provider (Intune: application UUID; Falcon/Jamf Pro: client id; Kandji/Kolide/Sentinel One: leave blank)"
|
|
31644
|
+
),
|
|
31645
|
+
clientSecret: external_exports3.string().describe("The secret (auth key, token, etc.) used to authenticate with the provider"),
|
|
31646
|
+
tenantId: external_exports3.string().optional().describe("Microsoft Intune directory (tenant) ID. Other providers leave blank."),
|
|
31647
|
+
cloudId: external_exports3.string().optional().describe(
|
|
31648
|
+
"Identifies which of the provider's clouds to integrate with. Falcon: us-1|us-2|eu-1|us-gov; Intune: global|us-gov; Jamf Pro/Kandji/Sentinel One: FQDN of your subdomain; Kolide: leave blank."
|
|
31649
|
+
)
|
|
31737
31650
|
}),
|
|
31738
31651
|
handler: async (input) => {
|
|
31739
31652
|
const body = {
|
|
31740
31653
|
provider: input.provider,
|
|
31741
|
-
clientId: input.clientId,
|
|
31742
31654
|
clientSecret: input.clientSecret
|
|
31743
31655
|
};
|
|
31656
|
+
if (input.clientId !== void 0) body.clientId = input.clientId;
|
|
31744
31657
|
if (input.tenantId !== void 0) body.tenantId = input.tenantId;
|
|
31745
|
-
if (input.
|
|
31658
|
+
if (input.cloudId !== void 0) body.cloudId = input.cloudId;
|
|
31746
31659
|
return apiPost(`/tailnet/${getTailnet()}/posture/integrations`, body);
|
|
31747
31660
|
}
|
|
31748
31661
|
},
|
|
@@ -31758,10 +31671,10 @@ var postureTools = [
|
|
|
31758
31671
|
},
|
|
31759
31672
|
inputSchema: external_exports3.object({
|
|
31760
31673
|
integrationId: external_exports3.string().describe("The posture integration ID to update"),
|
|
31761
|
-
clientId: external_exports3.string().optional().describe("Updated
|
|
31762
|
-
clientSecret: external_exports3.string().optional().describe("Updated
|
|
31674
|
+
clientId: external_exports3.string().optional().describe("Updated client ID for the provider"),
|
|
31675
|
+
clientSecret: external_exports3.string().optional().describe("Updated client secret for the provider (omit to retain the existing secret)"),
|
|
31763
31676
|
tenantId: external_exports3.string().optional().describe("Updated tenant ID"),
|
|
31764
|
-
|
|
31677
|
+
cloudId: external_exports3.string().optional().describe("Updated cloud identifier (e.g. 'us-1', 'global', or provider FQDN)")
|
|
31765
31678
|
}),
|
|
31766
31679
|
handler: async (input) => {
|
|
31767
31680
|
const { integrationId, ...body } = input;
|
|
@@ -31770,11 +31683,9 @@ var postureTools = [
|
|
|
31770
31683
|
if (value !== void 0) cleanBody[key] = value;
|
|
31771
31684
|
}
|
|
31772
31685
|
if (Object.keys(cleanBody).length === 0) {
|
|
31773
|
-
throw new Error(
|
|
31774
|
-
"No fields to update. Provide at least one of: clientId, clientSecret, tenantId, cloudEnvironment."
|
|
31775
|
-
);
|
|
31686
|
+
throw new Error("No fields to update. Provide at least one of: clientId, clientSecret, tenantId, cloudId.");
|
|
31776
31687
|
}
|
|
31777
|
-
return apiPatch(`/
|
|
31688
|
+
return apiPatch(`/posture/integrations/${encPath(integrationId)}`, cleanBody);
|
|
31778
31689
|
}
|
|
31779
31690
|
},
|
|
31780
31691
|
{
|
|
@@ -31791,7 +31702,7 @@ var postureTools = [
|
|
|
31791
31702
|
integrationId: external_exports3.string().describe("The posture integration ID to delete")
|
|
31792
31703
|
}),
|
|
31793
31704
|
handler: async (input) => {
|
|
31794
|
-
return apiDelete(`/
|
|
31705
|
+
return apiDelete(`/posture/integrations/${encPath(input.integrationId)}`);
|
|
31795
31706
|
}
|
|
31796
31707
|
}
|
|
31797
31708
|
];
|
|
@@ -31871,7 +31782,7 @@ var serviceTools = [
|
|
|
31871
31782
|
title: "Delete service",
|
|
31872
31783
|
readOnlyHint: false,
|
|
31873
31784
|
destructiveHint: true,
|
|
31874
|
-
idempotentHint:
|
|
31785
|
+
idempotentHint: true,
|
|
31875
31786
|
openWorldHint: true
|
|
31876
31787
|
},
|
|
31877
31788
|
inputSchema: external_exports3.object({
|
|
@@ -31960,21 +31871,20 @@ var statusTools = [
|
|
|
31960
31871
|
apiGet(`/tailnet/${getTailnet()}/devices?fields=id`),
|
|
31961
31872
|
apiGet(`/tailnet/${getTailnet()}/settings`)
|
|
31962
31873
|
]);
|
|
31963
|
-
if (!devicesRes.ok) {
|
|
31874
|
+
if (!devicesRes.ok && !settingsRes.ok) {
|
|
31964
31875
|
return devicesRes;
|
|
31965
31876
|
}
|
|
31966
|
-
const
|
|
31967
|
-
|
|
31968
|
-
|
|
31969
|
-
|
|
31970
|
-
data:
|
|
31971
|
-
connected: true,
|
|
31972
|
-
tailnet: getTailnet(),
|
|
31973
|
-
deviceCount,
|
|
31974
|
-
settings: settingsRes.ok ? settingsRes.data : void 0,
|
|
31975
|
-
...settingsRes.ok ? {} : { settingsError: settingsRes.error || "Failed to fetch tailnet settings" }
|
|
31976
|
-
}
|
|
31877
|
+
const data = {
|
|
31878
|
+
connected: true,
|
|
31879
|
+
tailnet: getTailnet(),
|
|
31880
|
+
deviceCount: devicesRes.ok ? devicesRes.data?.devices?.length ?? 0 : null,
|
|
31881
|
+
settings: settingsRes.ok ? settingsRes.data : null
|
|
31977
31882
|
};
|
|
31883
|
+
const errors = {};
|
|
31884
|
+
if (!devicesRes.ok) errors.devices = devicesRes.error ?? `HTTP ${devicesRes.status}`;
|
|
31885
|
+
if (!settingsRes.ok) errors.settings = settingsRes.error ?? `HTTP ${settingsRes.status}`;
|
|
31886
|
+
if (Object.keys(errors).length > 0) data.errors = errors;
|
|
31887
|
+
return { ok: true, status: 200, data };
|
|
31978
31888
|
}
|
|
31979
31889
|
}
|
|
31980
31890
|
];
|
|
@@ -32068,15 +31978,16 @@ var tailnetTools = [
|
|
|
32068
31978
|
if (value === void 0) continue;
|
|
32069
31979
|
const res = await apiPatch(`/tailnet/${getTailnet()}/contacts/${encPath(contactType)}`, value);
|
|
32070
31980
|
if (res.ok) applied[contactType] = res.data;
|
|
32071
|
-
else failed[contactType] = res.error ?? `HTTP ${res.status}
|
|
31981
|
+
else failed[contactType] = { status: res.status, error: res.error ?? `HTTP ${res.status}` };
|
|
32072
31982
|
}
|
|
32073
31983
|
const hasFailed = Object.keys(failed).length > 0;
|
|
32074
31984
|
const hasApplied = Object.keys(applied).length > 0;
|
|
32075
31985
|
if (hasFailed && !hasApplied) {
|
|
32076
|
-
|
|
31986
|
+
const first = Object.values(failed)[0];
|
|
31987
|
+
return { ok: false, status: first.status, error: `Contact update failed: ${JSON.stringify(failed)}` };
|
|
32077
31988
|
}
|
|
32078
31989
|
if (hasFailed) {
|
|
32079
|
-
return { ok: true, status:
|
|
31990
|
+
return { ok: true, status: 200, data: { applied, failed } };
|
|
32080
31991
|
}
|
|
32081
31992
|
return { ok: true, status: 200, data: applied };
|
|
32082
31993
|
}
|
|
@@ -32217,7 +32128,7 @@ var userTools = [
|
|
|
32217
32128
|
title: "Delete user",
|
|
32218
32129
|
readOnlyHint: false,
|
|
32219
32130
|
destructiveHint: true,
|
|
32220
|
-
idempotentHint:
|
|
32131
|
+
idempotentHint: true,
|
|
32221
32132
|
openWorldHint: true
|
|
32222
32133
|
},
|
|
32223
32134
|
inputSchema: external_exports3.object({
|
|
@@ -32382,112 +32293,8 @@ var webhookTools = [
|
|
|
32382
32293
|
}
|
|
32383
32294
|
];
|
|
32384
32295
|
|
|
32385
|
-
// src/tools/workload-identity.ts
|
|
32386
|
-
var workloadIdentityTools = [
|
|
32387
|
-
{
|
|
32388
|
-
name: "tailscale_list_workload_identities",
|
|
32389
|
-
description: "List all federated workload identity providers configured for your tailnet. Workload identities allow CI/CD pipelines and automated systems to authenticate using OIDC federation.",
|
|
32390
|
-
annotations: {
|
|
32391
|
-
title: "List workload identities",
|
|
32392
|
-
readOnlyHint: true,
|
|
32393
|
-
destructiveHint: false,
|
|
32394
|
-
idempotentHint: true,
|
|
32395
|
-
openWorldHint: true
|
|
32396
|
-
},
|
|
32397
|
-
inputSchema: external_exports3.object({}),
|
|
32398
|
-
handler: async () => {
|
|
32399
|
-
return apiGet(`/tailnet/${getTailnet()}/workload-identity/providers`);
|
|
32400
|
-
}
|
|
32401
|
-
},
|
|
32402
|
-
{
|
|
32403
|
-
name: "tailscale_get_workload_identity",
|
|
32404
|
-
description: "Get details for a specific federated workload identity provider, including issuer URL, audience, and the subject patterns it accepts for OIDC token exchange.",
|
|
32405
|
-
annotations: {
|
|
32406
|
-
title: "Get workload identity",
|
|
32407
|
-
readOnlyHint: true,
|
|
32408
|
-
destructiveHint: false,
|
|
32409
|
-
idempotentHint: true,
|
|
32410
|
-
openWorldHint: true
|
|
32411
|
-
},
|
|
32412
|
-
inputSchema: external_exports3.object({
|
|
32413
|
-
providerId: external_exports3.string().describe("The workload identity provider ID")
|
|
32414
|
-
}),
|
|
32415
|
-
handler: async (input) => {
|
|
32416
|
-
return apiGet(`/tailnet/${getTailnet()}/workload-identity/providers/${encPath(input.providerId)}`);
|
|
32417
|
-
}
|
|
32418
|
-
},
|
|
32419
|
-
{
|
|
32420
|
-
name: "tailscale_create_workload_identity",
|
|
32421
|
-
description: "Create a new workload identity provider for OIDC federation. Enables CI/CD systems (GitHub Actions, GitLab CI, etc.) to authenticate to your tailnet without static credentials.",
|
|
32422
|
-
annotations: {
|
|
32423
|
-
title: "Create workload identity",
|
|
32424
|
-
readOnlyHint: false,
|
|
32425
|
-
destructiveHint: false,
|
|
32426
|
-
idempotentHint: false,
|
|
32427
|
-
openWorldHint: true
|
|
32428
|
-
},
|
|
32429
|
-
inputSchema: external_exports3.object({
|
|
32430
|
-
name: external_exports3.string().describe("A human-readable name for this provider (max 50 chars, alphanumeric/hyphens/spaces)"),
|
|
32431
|
-
issuerUrl: external_exports3.string().describe("The OIDC issuer URL (e.g. 'https://token.actions.githubusercontent.com' for GitHub Actions)"),
|
|
32432
|
-
audience: external_exports3.string().optional().describe("Expected audience claim in the OIDC token"),
|
|
32433
|
-
claimMappings: external_exports3.record(external_exports3.string(), external_exports3.string()).optional().describe("Map of Tailscale attributes to OIDC token claims (e.g. { 'tag': 'repository' })")
|
|
32434
|
-
}),
|
|
32435
|
-
handler: async (input) => {
|
|
32436
|
-
const body = { ...input };
|
|
32437
|
-
body.name = sanitizeDescription(input.name);
|
|
32438
|
-
return apiPost(`/tailnet/${getTailnet()}/workload-identity/providers`, body);
|
|
32439
|
-
}
|
|
32440
|
-
},
|
|
32441
|
-
{
|
|
32442
|
-
name: "tailscale_update_workload_identity",
|
|
32443
|
-
description: "Update an existing workload identity provider's configuration.",
|
|
32444
|
-
annotations: {
|
|
32445
|
-
title: "Update workload identity",
|
|
32446
|
-
readOnlyHint: false,
|
|
32447
|
-
destructiveHint: false,
|
|
32448
|
-
idempotentHint: true,
|
|
32449
|
-
openWorldHint: true
|
|
32450
|
-
},
|
|
32451
|
-
inputSchema: external_exports3.object({
|
|
32452
|
-
providerId: external_exports3.string().describe("The workload identity provider ID to update"),
|
|
32453
|
-
name: external_exports3.string().optional().describe("Updated human-readable name"),
|
|
32454
|
-
audience: external_exports3.string().optional().describe("Updated expected audience claim"),
|
|
32455
|
-
claimMappings: external_exports3.record(external_exports3.string(), external_exports3.string()).optional().describe("Updated claim mappings")
|
|
32456
|
-
}),
|
|
32457
|
-
handler: async (input) => {
|
|
32458
|
-
const { providerId, ...body } = input;
|
|
32459
|
-
const cleanBody = {};
|
|
32460
|
-
for (const [key, value] of Object.entries(body)) {
|
|
32461
|
-
if (value !== void 0) cleanBody[key] = value;
|
|
32462
|
-
}
|
|
32463
|
-
if (cleanBody.name !== void 0) cleanBody.name = sanitizeDescription(cleanBody.name);
|
|
32464
|
-
if (Object.keys(cleanBody).length === 0) {
|
|
32465
|
-
throw new Error("No fields to update. Provide at least one of: name, audience, claimMappings.");
|
|
32466
|
-
}
|
|
32467
|
-
return apiPatch(`/tailnet/${getTailnet()}/workload-identity/providers/${encPath(providerId)}`, cleanBody);
|
|
32468
|
-
}
|
|
32469
|
-
},
|
|
32470
|
-
{
|
|
32471
|
-
name: "tailscale_delete_workload_identity",
|
|
32472
|
-
description: "Delete a workload identity provider. This is irreversible \u2014 any CI/CD pipelines using this provider will lose access.",
|
|
32473
|
-
annotations: {
|
|
32474
|
-
title: "Delete workload identity",
|
|
32475
|
-
readOnlyHint: false,
|
|
32476
|
-
destructiveHint: true,
|
|
32477
|
-
idempotentHint: true,
|
|
32478
|
-
openWorldHint: true
|
|
32479
|
-
},
|
|
32480
|
-
inputSchema: external_exports3.object({
|
|
32481
|
-
providerId: external_exports3.string().describe("The workload identity provider ID to delete")
|
|
32482
|
-
}),
|
|
32483
|
-
handler: async (input) => {
|
|
32484
|
-
return apiDelete(`/tailnet/${getTailnet()}/workload-identity/providers/${encPath(input.providerId)}`);
|
|
32485
|
-
}
|
|
32486
|
-
}
|
|
32487
|
-
];
|
|
32488
|
-
|
|
32489
32296
|
// src/index.ts
|
|
32490
|
-
var version2 = true ? "0.
|
|
32297
|
+
var version2 = true ? "0.9.1" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
32491
32298
|
var subcommand = process.argv[2];
|
|
32492
32299
|
if (subcommand === "deploy-acl") {
|
|
32493
32300
|
const filePath = process.argv[3];
|
|
@@ -32513,14 +32320,11 @@ var toolGroups = {
|
|
|
32513
32320
|
users: userTools,
|
|
32514
32321
|
tailnet: tailnetTools,
|
|
32515
32322
|
webhooks: webhookTools,
|
|
32516
|
-
"network-lock": networkLockTools,
|
|
32517
32323
|
posture: postureTools,
|
|
32518
32324
|
audit: auditTools,
|
|
32519
32325
|
invites: inviteTools,
|
|
32520
32326
|
services: serviceTools,
|
|
32521
|
-
"log-streaming": logStreamingTools
|
|
32522
|
-
"workload-identity": workloadIdentityTools,
|
|
32523
|
-
"oauth-clients": oauthClientTools
|
|
32327
|
+
"log-streaming": logStreamingTools
|
|
32524
32328
|
};
|
|
32525
32329
|
var {
|
|
32526
32330
|
tools: allTools,
|