@yawlabs/tailscale-mcp 0.12.2 → 0.12.3

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.
Files changed (2) hide show
  1. package/dist/index.js +52 -58
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -30958,7 +30958,7 @@ var MAX_429_RETRIES = 3;
30958
30958
  var DEFAULT_429_DELAY_MS = 1e3;
30959
30959
  var MAX_429_DELAY_MS = 3e4;
30960
30960
  var MAX_REQUEST_BUDGET_MS = 9e4;
30961
- var RETRYABLE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE"]);
30961
+ var RETRYABLE_METHODS = /* @__PURE__ */ new Set(["GET", "PUT", "DELETE"]);
30962
30962
  var oauthToken = null;
30963
30963
  var oauthRefreshPromise = null;
30964
30964
  function getAuthConfig() {
@@ -31285,6 +31285,9 @@ var PROFILES = {
31285
31285
  full: []
31286
31286
  // empty = all groups
31287
31287
  };
31288
+ function parseReadonlyFlag(value) {
31289
+ return value === "1" || value === "true";
31290
+ }
31288
31291
  function filterTools(groups, options) {
31289
31292
  const validNames = new Set(Object.keys(groups));
31290
31293
  let profileGroups;
@@ -31305,7 +31308,7 @@ function filterTools(groups, options) {
31305
31308
  const effectiveGroups = explicitTools2 ?? profileGroups ?? null;
31306
31309
  const enabledGroups = effectiveGroups ? new Set(effectiveGroups) : null;
31307
31310
  const unknownGroups2 = enabledGroups ? [...enabledGroups].filter((g) => !validNames.has(g)) : [];
31308
- const readonly2 = options.readonly === "1" || options.readonly === "true";
31311
+ const readonly2 = parseReadonlyFlag(options.readonly);
31309
31312
  const out = [];
31310
31313
  for (const [name, tools] of Object.entries(groups)) {
31311
31314
  if (enabledGroups && !enabledGroups.has(name)) continue;
@@ -31322,6 +31325,49 @@ function filterTools(groups, options) {
31322
31325
  return result;
31323
31326
  }
31324
31327
 
31328
+ // src/tools/status.ts
31329
+ function composeTailnetStatusData(devicesRes, settingsRes, extras = {}) {
31330
+ const { deviceCount: _deviceCount, settings: _settings, errors: _errors, ...safeExtras } = extras;
31331
+ const data = {
31332
+ ...safeExtras,
31333
+ deviceCount: devicesRes.ok ? devicesRes.data?.devices?.length ?? null : null,
31334
+ settings: settingsRes.ok ? settingsRes.data : null
31335
+ };
31336
+ const errors = {};
31337
+ if (!devicesRes.ok) errors.devices = devicesRes.error ?? `HTTP ${devicesRes.status}`;
31338
+ if (!settingsRes.ok) errors.settings = settingsRes.error ?? `HTTP ${settingsRes.status}`;
31339
+ if (Object.keys(errors).length > 0) data.errors = errors;
31340
+ return data;
31341
+ }
31342
+ var statusTools = [
31343
+ {
31344
+ name: "tailscale_status",
31345
+ description: "Check that the Tailscale API connection is working. Returns your tailnet name, device count, and confirms authentication is valid. Use this to verify setup.",
31346
+ annotations: {
31347
+ title: "Check API status",
31348
+ readOnlyHint: true,
31349
+ destructiveHint: false,
31350
+ idempotentHint: true,
31351
+ openWorldHint: true
31352
+ },
31353
+ inputSchema: external_exports.object({}),
31354
+ handler: async () => {
31355
+ const [devicesRes, settingsRes] = await Promise.all([
31356
+ apiGet(`/tailnet/${getTailnet()}/devices?fields=id`),
31357
+ apiGet(`/tailnet/${getTailnet()}/settings`)
31358
+ ]);
31359
+ if (!devicesRes.ok && !settingsRes.ok) {
31360
+ return devicesRes;
31361
+ }
31362
+ const data = composeTailnetStatusData(devicesRes, settingsRes, {
31363
+ connected: true,
31364
+ tailnet: getTailnet()
31365
+ });
31366
+ return { ok: true, status: 200, data };
31367
+ }
31368
+ }
31369
+ ];
31370
+
31325
31371
  // src/server-wiring.ts
31326
31372
  function isLocalCliEnabled(env) {
31327
31373
  return env.TAILSCALE_LOCAL_CLI === "1" || env.TAILSCALE_LOCAL_CLI === "true";
@@ -31371,19 +31417,7 @@ async function tailnetStatusResource(uri) {
31371
31417
  apiGet(`/tailnet/${getTailnet()}/devices?fields=id`),
31372
31418
  apiGet(`/tailnet/${getTailnet()}/settings`)
31373
31419
  ]);
31374
- const data = {
31375
- tailnet: getTailnet(),
31376
- // `?? null` (not `?? 0`): the request succeeded but the body was missing
31377
- // a `devices` array (204 / empty content-length / unexpected shape).
31378
- // Reporting `0` in that case would be confidently wrong; null signals
31379
- // "unknown" so the caller doesn't conflate it with an actually-empty tailnet.
31380
- deviceCount: devicesRes.ok ? devicesRes.data?.devices?.length ?? null : null,
31381
- settings: settingsRes.ok ? settingsRes.data : null
31382
- };
31383
- const errors = {};
31384
- if (!devicesRes.ok) errors.devices = devicesRes.error ?? `HTTP ${devicesRes.status}`;
31385
- if (!settingsRes.ok) errors.settings = settingsRes.error ?? `HTTP ${settingsRes.status}`;
31386
- if (Object.keys(errors).length > 0) data.errors = errors;
31420
+ const data = composeTailnetStatusData(devicesRes, settingsRes, { tailnet: getTailnet() });
31387
31421
  return { contents: [{ uri: uri.href, text: JSON.stringify(data, null, 2), mimeType: "application/json" }] };
31388
31422
  }
31389
31423
  async function tailnetDevicesResource(uri) {
@@ -31616,8 +31650,8 @@ function isCidr(s) {
31616
31650
  if (slash < 0) return false;
31617
31651
  const addr = s.slice(0, slash);
31618
31652
  const prefix = s.slice(slash + 1);
31653
+ if (!/^\d+$/.test(prefix)) return false;
31619
31654
  const prefixN = Number(prefix);
31620
- if (!Number.isInteger(prefixN) || prefixN < 0) return false;
31621
31655
  if (net.isIPv4(addr)) return prefixN <= 32;
31622
31656
  if (net.isIPv6(addr)) return prefixN <= 128;
31623
31657
  return false;
@@ -33197,46 +33231,6 @@ var serviceTools = [
33197
33231
  }
33198
33232
  ];
33199
33233
 
33200
- // src/tools/status.ts
33201
- var statusTools = [
33202
- {
33203
- name: "tailscale_status",
33204
- description: "Check that the Tailscale API connection is working. Returns your tailnet name, device count, and confirms authentication is valid. Use this to verify setup.",
33205
- annotations: {
33206
- title: "Check API status",
33207
- readOnlyHint: true,
33208
- destructiveHint: false,
33209
- idempotentHint: true,
33210
- openWorldHint: true
33211
- },
33212
- inputSchema: external_exports.object({}),
33213
- handler: async () => {
33214
- const [devicesRes, settingsRes] = await Promise.all([
33215
- apiGet(`/tailnet/${getTailnet()}/devices?fields=id`),
33216
- apiGet(`/tailnet/${getTailnet()}/settings`)
33217
- ]);
33218
- if (!devicesRes.ok && !settingsRes.ok) {
33219
- return devicesRes;
33220
- }
33221
- const data = {
33222
- connected: true,
33223
- tailnet: getTailnet(),
33224
- // `?? null` (not `?? 0`): the request succeeded but the body was missing
33225
- // a `devices` array (204 / empty content-length / unexpected shape).
33226
- // Reporting `0` in that case would be confidently wrong; null signals
33227
- // "unknown" so the caller doesn't conflate it with an actually-empty tailnet.
33228
- deviceCount: devicesRes.ok ? devicesRes.data?.devices?.length ?? null : null,
33229
- settings: settingsRes.ok ? settingsRes.data : null
33230
- };
33231
- const errors = {};
33232
- if (!devicesRes.ok) errors.devices = devicesRes.error ?? `HTTP ${devicesRes.status}`;
33233
- if (!settingsRes.ok) errors.settings = settingsRes.error ?? `HTTP ${settingsRes.status}`;
33234
- if (Object.keys(errors).length > 0) data.errors = errors;
33235
- return { ok: true, status: 200, data };
33236
- }
33237
- }
33238
- ];
33239
-
33240
33234
  // src/tools/tailnet.ts
33241
33235
  var tailnetTools = [
33242
33236
  {
@@ -33680,7 +33674,7 @@ var webhookTools = [
33680
33674
  ];
33681
33675
 
33682
33676
  // src/index.ts
33683
- var version2 = true ? "0.12.2" : (await null).createRequire(import.meta.url)("../package.json").version;
33677
+ var version2 = true ? "0.12.3" : (await null).createRequire(import.meta.url)("../package.json").version;
33684
33678
  var subcommand = process.argv[2];
33685
33679
  if (subcommand === "deploy-acl") {
33686
33680
  const filePath = process.argv[3];
@@ -33774,7 +33768,7 @@ server.resource(
33774
33768
  );
33775
33769
  var transport = new StdioServerTransport();
33776
33770
  await server.connect(transport);
33777
- var readonlyMode = process.env.TAILSCALE_READONLY === "1" || process.env.TAILSCALE_READONLY === "true";
33771
+ var readonlyMode = parseReadonlyFlag(process.env.TAILSCALE_READONLY);
33778
33772
  var filterSuffix = formatBannerFilterSuffix({
33779
33773
  unknownProfile,
33780
33774
  explicitTools,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/tailscale-mcp",
3
- "version": "0.12.2",
3
+ "version": "0.12.3",
4
4
  "mcpName": "io.github.YawLabs/tailscale-mcp",
5
5
  "description": "Tailscale MCP server for managing your tailnet from AI assistants",
6
6
  "license": "MIT",