@yawlabs/tailscale-mcp 0.12.1 → 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 +58 -60
  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() {
@@ -31165,7 +31165,8 @@ async function apiRequest(method, path, body, options) {
31165
31165
  headers["Content-Type"] = "application/json";
31166
31166
  fetchBody = JSON.stringify(body);
31167
31167
  }
31168
- const url2 = path.startsWith("http") ? path : `${BASE_URL}${path}`;
31168
+ const isAbsolute = path.startsWith("http://") || path.startsWith("https://");
31169
+ const url2 = isAbsolute ? path : `${BASE_URL}${path}`;
31169
31170
  const startedAt = Date.now();
31170
31171
  debugLog(`${method} ${url2}`);
31171
31172
  const isRetryable = RETRYABLE_METHODS.has(method.toUpperCase());
@@ -31284,6 +31285,9 @@ var PROFILES = {
31284
31285
  full: []
31285
31286
  // empty = all groups
31286
31287
  };
31288
+ function parseReadonlyFlag(value) {
31289
+ return value === "1" || value === "true";
31290
+ }
31287
31291
  function filterTools(groups, options) {
31288
31292
  const validNames = new Set(Object.keys(groups));
31289
31293
  let profileGroups;
@@ -31304,7 +31308,7 @@ function filterTools(groups, options) {
31304
31308
  const effectiveGroups = explicitTools2 ?? profileGroups ?? null;
31305
31309
  const enabledGroups = effectiveGroups ? new Set(effectiveGroups) : null;
31306
31310
  const unknownGroups2 = enabledGroups ? [...enabledGroups].filter((g) => !validNames.has(g)) : [];
31307
- const readonly2 = options.readonly === "1" || options.readonly === "true";
31311
+ const readonly2 = parseReadonlyFlag(options.readonly);
31308
31312
  const out = [];
31309
31313
  for (const [name, tools] of Object.entries(groups)) {
31310
31314
  if (enabledGroups && !enabledGroups.has(name)) continue;
@@ -31321,6 +31325,49 @@ function filterTools(groups, options) {
31321
31325
  return result;
31322
31326
  }
31323
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
+
31324
31371
  // src/server-wiring.ts
31325
31372
  function isLocalCliEnabled(env) {
31326
31373
  return env.TAILSCALE_LOCAL_CLI === "1" || env.TAILSCALE_LOCAL_CLI === "true";
@@ -31370,19 +31417,7 @@ async function tailnetStatusResource(uri) {
31370
31417
  apiGet(`/tailnet/${getTailnet()}/devices?fields=id`),
31371
31418
  apiGet(`/tailnet/${getTailnet()}/settings`)
31372
31419
  ]);
31373
- const data = {
31374
- tailnet: getTailnet(),
31375
- // `?? null` (not `?? 0`): the request succeeded but the body was missing
31376
- // a `devices` array (204 / empty content-length / unexpected shape).
31377
- // Reporting `0` in that case would be confidently wrong; null signals
31378
- // "unknown" so the caller doesn't conflate it with an actually-empty tailnet.
31379
- deviceCount: devicesRes.ok ? devicesRes.data?.devices?.length ?? null : null,
31380
- settings: settingsRes.ok ? settingsRes.data : null
31381
- };
31382
- const errors = {};
31383
- if (!devicesRes.ok) errors.devices = devicesRes.error ?? `HTTP ${devicesRes.status}`;
31384
- if (!settingsRes.ok) errors.settings = settingsRes.error ?? `HTTP ${settingsRes.status}`;
31385
- if (Object.keys(errors).length > 0) data.errors = errors;
31420
+ const data = composeTailnetStatusData(devicesRes, settingsRes, { tailnet: getTailnet() });
31386
31421
  return { contents: [{ uri: uri.href, text: JSON.stringify(data, null, 2), mimeType: "application/json" }] };
31387
31422
  }
31388
31423
  async function tailnetDevicesResource(uri) {
@@ -31615,8 +31650,8 @@ function isCidr(s) {
31615
31650
  if (slash < 0) return false;
31616
31651
  const addr = s.slice(0, slash);
31617
31652
  const prefix = s.slice(slash + 1);
31653
+ if (!/^\d+$/.test(prefix)) return false;
31618
31654
  const prefixN = Number(prefix);
31619
- if (!Number.isInteger(prefixN) || prefixN < 0) return false;
31620
31655
  if (net.isIPv4(addr)) return prefixN <= 32;
31621
31656
  if (net.isIPv6(addr)) return prefixN <= 128;
31622
31657
  return false;
@@ -33196,46 +33231,6 @@ var serviceTools = [
33196
33231
  }
33197
33232
  ];
33198
33233
 
33199
- // src/tools/status.ts
33200
- var statusTools = [
33201
- {
33202
- name: "tailscale_status",
33203
- 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.",
33204
- annotations: {
33205
- title: "Check API status",
33206
- readOnlyHint: true,
33207
- destructiveHint: false,
33208
- idempotentHint: true,
33209
- openWorldHint: true
33210
- },
33211
- inputSchema: external_exports.object({}),
33212
- handler: async () => {
33213
- const [devicesRes, settingsRes] = await Promise.all([
33214
- apiGet(`/tailnet/${getTailnet()}/devices?fields=id`),
33215
- apiGet(`/tailnet/${getTailnet()}/settings`)
33216
- ]);
33217
- if (!devicesRes.ok && !settingsRes.ok) {
33218
- return devicesRes;
33219
- }
33220
- const data = {
33221
- connected: true,
33222
- tailnet: getTailnet(),
33223
- // `?? null` (not `?? 0`): the request succeeded but the body was missing
33224
- // a `devices` array (204 / empty content-length / unexpected shape).
33225
- // Reporting `0` in that case would be confidently wrong; null signals
33226
- // "unknown" so the caller doesn't conflate it with an actually-empty tailnet.
33227
- deviceCount: devicesRes.ok ? devicesRes.data?.devices?.length ?? null : null,
33228
- settings: settingsRes.ok ? settingsRes.data : null
33229
- };
33230
- const errors = {};
33231
- if (!devicesRes.ok) errors.devices = devicesRes.error ?? `HTTP ${devicesRes.status}`;
33232
- if (!settingsRes.ok) errors.settings = settingsRes.error ?? `HTTP ${settingsRes.status}`;
33233
- if (Object.keys(errors).length > 0) data.errors = errors;
33234
- return { ok: true, status: 200, data };
33235
- }
33236
- }
33237
- ];
33238
-
33239
33234
  // src/tools/tailnet.ts
33240
33235
  var tailnetTools = [
33241
33236
  {
@@ -33679,7 +33674,7 @@ var webhookTools = [
33679
33674
  ];
33680
33675
 
33681
33676
  // src/index.ts
33682
- var version2 = true ? "0.12.1" : (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;
33683
33678
  var subcommand = process.argv[2];
33684
33679
  if (subcommand === "deploy-acl") {
33685
33680
  const filePath = process.argv[3];
@@ -33773,7 +33768,7 @@ server.resource(
33773
33768
  );
33774
33769
  var transport = new StdioServerTransport();
33775
33770
  await server.connect(transport);
33776
- var readonlyMode = process.env.TAILSCALE_READONLY === "1" || process.env.TAILSCALE_READONLY === "true";
33771
+ var readonlyMode = parseReadonlyFlag(process.env.TAILSCALE_READONLY);
33777
33772
  var filterSuffix = formatBannerFilterSuffix({
33778
33773
  unknownProfile,
33779
33774
  explicitTools,
@@ -33787,8 +33782,11 @@ console.error(
33787
33782
  );
33788
33783
  var hasCreds = !!process.env.TAILSCALE_API_KEY || !!process.env.TAILSCALE_OAUTH_CLIENT_ID && !!process.env.TAILSCALE_OAUTH_CLIENT_SECRET;
33789
33784
  if (!filterSuffix && hasCreds) {
33785
+ const profileCount = (groups) => groups.reduce((n, g) => n + (toolGroups[g]?.length ?? 0), 0);
33786
+ const coreCount = profileCount(PROFILES.core);
33787
+ const minimalCount = profileCount(PROFILES.minimal);
33790
33788
  console.error(
33791
- "@yawlabs/tailscale-mcp: tip \u2014 set TAILSCALE_PROFILE=core (47 tools) or =minimal (20) to load a smaller tool surface. See README."
33789
+ `@yawlabs/tailscale-mcp: tip \u2014 set TAILSCALE_PROFILE=core (${coreCount} tools) or =minimal (${minimalCount}) to load a smaller tool surface. See README.`
33792
33790
  );
33793
33791
  }
33794
33792
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/tailscale-mcp",
3
- "version": "0.12.1",
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",