@yawlabs/tailscale-mcp 0.10.5 → 0.10.6
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/dist/index.js +52 -39
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3104,7 +3104,7 @@ var require_utils = __commonJS({
|
|
|
3104
3104
|
"node_modules/fast-uri/lib/utils.js"(exports, module) {
|
|
3105
3105
|
"use strict";
|
|
3106
3106
|
var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
3107
|
-
var
|
|
3107
|
+
var isIPv42 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
|
|
3108
3108
|
function stringArrayToHexStripped(input) {
|
|
3109
3109
|
let acc = "";
|
|
3110
3110
|
let code = 0;
|
|
@@ -3327,7 +3327,7 @@ var require_utils = __commonJS({
|
|
|
3327
3327
|
}
|
|
3328
3328
|
if (component.host !== void 0) {
|
|
3329
3329
|
let host = unescape(component.host);
|
|
3330
|
-
if (!
|
|
3330
|
+
if (!isIPv42(host)) {
|
|
3331
3331
|
const ipV6res = normalizeIPv6(host);
|
|
3332
3332
|
if (ipV6res.isIPV6 === true) {
|
|
3333
3333
|
host = `[${ipV6res.escapedHost}]`;
|
|
@@ -3348,7 +3348,7 @@ var require_utils = __commonJS({
|
|
|
3348
3348
|
recomposeAuthority,
|
|
3349
3349
|
normalizeComponentEncoding,
|
|
3350
3350
|
removeDotSegments,
|
|
3351
|
-
isIPv4,
|
|
3351
|
+
isIPv4: isIPv42,
|
|
3352
3352
|
isUUID,
|
|
3353
3353
|
normalizeIPv6,
|
|
3354
3354
|
stringArrayToHexStripped
|
|
@@ -3570,7 +3570,7 @@ var require_schemes = __commonJS({
|
|
|
3570
3570
|
var require_fast_uri = __commonJS({
|
|
3571
3571
|
"node_modules/fast-uri/index.js"(exports, module) {
|
|
3572
3572
|
"use strict";
|
|
3573
|
-
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
|
|
3573
|
+
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4: isIPv42, nonSimpleDomain } = require_utils();
|
|
3574
3574
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
3575
3575
|
function normalize(uri, options) {
|
|
3576
3576
|
if (typeof uri === "string") {
|
|
@@ -3751,7 +3751,7 @@ var require_fast_uri = __commonJS({
|
|
|
3751
3751
|
parsed.port = matches[5];
|
|
3752
3752
|
}
|
|
3753
3753
|
if (parsed.host) {
|
|
3754
|
-
const ipv4result =
|
|
3754
|
+
const ipv4result = isIPv42(parsed.host);
|
|
3755
3755
|
if (ipv4result === false) {
|
|
3756
3756
|
const ipv6result = normalizeIPv6(parsed.host);
|
|
3757
3757
|
parsed.host = ipv6result.host.toLowerCase();
|
|
@@ -30210,15 +30210,20 @@ function validateTags(tags) {
|
|
|
30210
30210
|
function sanitizeDescription(value) {
|
|
30211
30211
|
return value.replace(/[/_]/g, "-").replace(/[^a-zA-Z0-9 -]/g, "").replace(/ {2,}/g, " ").trim().slice(0, 50);
|
|
30212
30212
|
}
|
|
30213
|
-
function
|
|
30214
|
-
const
|
|
30215
|
-
|
|
30216
|
-
|
|
30217
|
-
|
|
30218
|
-
|
|
30219
|
-
|
|
30220
|
-
|
|
30221
|
-
|
|
30213
|
+
function validateAndSanitizeDescription(value) {
|
|
30214
|
+
const sanitized = sanitizeDescription(value);
|
|
30215
|
+
if (sanitized.length > 0) return sanitized;
|
|
30216
|
+
if (value.trim().length === 0) return void 0;
|
|
30217
|
+
throw new Error(
|
|
30218
|
+
`description ${JSON.stringify(value)} contains no valid characters after sanitization. Allowed characters: alphanumeric, spaces, and hyphens (max 50 chars).`
|
|
30219
|
+
);
|
|
30220
|
+
}
|
|
30221
|
+
function formatAuthError(status, apiBody) {
|
|
30222
|
+
const usingOAuth = !process.env.TAILSCALE_API_KEY && !!process.env.TAILSCALE_OAUTH_CLIENT_ID;
|
|
30223
|
+
const headline = status === 401 ? "Authentication failed (HTTP 401)." : "Authorization failed (HTTP 403): the request was authenticated but not permitted for this resource.";
|
|
30224
|
+
const cause = status === 401 ? usingOAuth ? " - OAuth client credentials are invalid or lack required scopes" : " - API key has expired or been revoked" : usingOAuth ? " - OAuth client is missing a scope required for this endpoint" : " - API key lacks the permission required for this endpoint";
|
|
30225
|
+
const lines = [headline, "", "Possible causes:", cause];
|
|
30226
|
+
if (status === 401 && process.platform === "win32" && !usingOAuth) {
|
|
30222
30227
|
lines.push(
|
|
30223
30228
|
" - On Windows, env vars set in bash/WSL profiles are not visible to MCP servers launched via cmd",
|
|
30224
30229
|
"",
|
|
@@ -30227,7 +30232,8 @@ function formatAuthError(apiBody) {
|
|
|
30227
30232
|
" 2. Set TAILSCALE_API_KEY as a Windows user environment variable (System Properties > Environment Variables)"
|
|
30228
30233
|
);
|
|
30229
30234
|
}
|
|
30230
|
-
|
|
30235
|
+
const link = status === 401 ? "Generate a new key at: https://login.tailscale.com/admin/settings/keys" : usingOAuth ? "Adjust the OAuth client scopes at: https://login.tailscale.com/admin/settings/oauth" : "Adjust the API key permissions at: https://login.tailscale.com/admin/settings/keys";
|
|
30236
|
+
lines.push("", link);
|
|
30231
30237
|
if (apiBody) {
|
|
30232
30238
|
lines.push("", `API response: ${apiBody}`);
|
|
30233
30239
|
}
|
|
@@ -30253,28 +30259,32 @@ var concurrencyQueue = [];
|
|
|
30253
30259
|
function getConcurrencyLimit() {
|
|
30254
30260
|
const raw = process.env.TAILSCALE_MAX_CONCURRENT;
|
|
30255
30261
|
if (!raw) return 0;
|
|
30256
|
-
const n = Number
|
|
30257
|
-
return Number.
|
|
30262
|
+
const n = Number(raw);
|
|
30263
|
+
return Number.isInteger(n) && n > 0 ? n : 0;
|
|
30258
30264
|
}
|
|
30259
30265
|
function getRequestBudgetMs() {
|
|
30260
30266
|
const raw = process.env.TAILSCALE_REQUEST_BUDGET_MS;
|
|
30261
30267
|
if (!raw) return MAX_REQUEST_BUDGET_MS;
|
|
30262
|
-
const n = Number
|
|
30263
|
-
return Number.
|
|
30268
|
+
const n = Number(raw);
|
|
30269
|
+
return Number.isInteger(n) && n > 0 ? n : MAX_REQUEST_BUDGET_MS;
|
|
30264
30270
|
}
|
|
30265
30271
|
async function withConcurrencyLimit(fn) {
|
|
30266
30272
|
const limit = getConcurrencyLimit();
|
|
30267
30273
|
if (limit === 0) return fn();
|
|
30268
30274
|
if (inFlight >= limit) {
|
|
30269
30275
|
await new Promise((resolve) => concurrencyQueue.push(resolve));
|
|
30276
|
+
} else {
|
|
30277
|
+
inFlight++;
|
|
30270
30278
|
}
|
|
30271
|
-
inFlight++;
|
|
30272
30279
|
try {
|
|
30273
30280
|
return await fn();
|
|
30274
30281
|
} finally {
|
|
30275
|
-
inFlight--;
|
|
30276
30282
|
const next = concurrencyQueue.shift();
|
|
30277
|
-
if (next)
|
|
30283
|
+
if (next) {
|
|
30284
|
+
next();
|
|
30285
|
+
} else {
|
|
30286
|
+
inFlight--;
|
|
30287
|
+
}
|
|
30278
30288
|
}
|
|
30279
30289
|
}
|
|
30280
30290
|
function debugLog(...parts) {
|
|
@@ -30350,14 +30360,14 @@ async function apiRequest(method, path, body, options) {
|
|
|
30350
30360
|
if (options?.acceptRaw) {
|
|
30351
30361
|
const rawBody = await response.text();
|
|
30352
30362
|
if (!response.ok) {
|
|
30353
|
-
const error48 = response.status === 401 ? formatAuthError(rawBody) : extractErrorMessage(rawBody);
|
|
30363
|
+
const error48 = response.status === 401 || response.status === 403 ? formatAuthError(response.status, rawBody) : extractErrorMessage(rawBody);
|
|
30354
30364
|
return { ok: false, status: response.status, error: error48, rawBody, etag };
|
|
30355
30365
|
}
|
|
30356
30366
|
return { ok: true, status: response.status, rawBody, etag };
|
|
30357
30367
|
}
|
|
30358
30368
|
if (!response.ok) {
|
|
30359
30369
|
const errorBody = await response.text();
|
|
30360
|
-
const error48 = response.status === 401 ? formatAuthError(errorBody) : extractErrorMessage(errorBody);
|
|
30370
|
+
const error48 = response.status === 401 || response.status === 403 ? formatAuthError(response.status, errorBody) : extractErrorMessage(errorBody);
|
|
30361
30371
|
return { ok: false, status: response.status, error: error48, etag };
|
|
30362
30372
|
}
|
|
30363
30373
|
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
@@ -30731,6 +30741,18 @@ var auditTools = [
|
|
|
30731
30741
|
];
|
|
30732
30742
|
|
|
30733
30743
|
// src/tools/devices.ts
|
|
30744
|
+
import * as net from "node:net";
|
|
30745
|
+
function isCidr(s) {
|
|
30746
|
+
const slash = s.indexOf("/");
|
|
30747
|
+
if (slash < 0) return false;
|
|
30748
|
+
const addr = s.slice(0, slash);
|
|
30749
|
+
const prefix = s.slice(slash + 1);
|
|
30750
|
+
const prefixN = Number(prefix);
|
|
30751
|
+
if (!Number.isInteger(prefixN) || prefixN < 0) return false;
|
|
30752
|
+
if (net.isIPv4(addr)) return prefixN <= 32;
|
|
30753
|
+
if (net.isIPv6(addr)) return prefixN <= 128;
|
|
30754
|
+
return false;
|
|
30755
|
+
}
|
|
30734
30756
|
var deviceTools = [
|
|
30735
30757
|
{
|
|
30736
30758
|
name: "tailscale_list_devices",
|
|
@@ -30894,16 +30916,7 @@ var deviceTools = [
|
|
|
30894
30916
|
},
|
|
30895
30917
|
inputSchema: external_exports3.object({
|
|
30896
30918
|
deviceId: external_exports3.string().describe("The device ID"),
|
|
30897
|
-
routes: external_exports3.array(
|
|
30898
|
-
external_exports3.string().refine(
|
|
30899
|
-
// Accept v4 (10.0.0.0/24) or v6 (fd7a:115c::/48) CIDRs. Routes can be either.
|
|
30900
|
-
// Loose check: must contain a '/' followed by 1-3 digits, and the address part
|
|
30901
|
-
// must look like an IPv4 quad-dotted or an IPv6 colon-form. The Tailscale API
|
|
30902
|
-
// is the authoritative validator; this just rejects obvious typos client-side.
|
|
30903
|
-
(s) => /^([\d.]+|[\da-fA-F:]+)\/\d{1,3}$/.test(s),
|
|
30904
|
-
{ message: "must be a CIDR (e.g. '10.0.0.0/24' or 'fd7a:115c::/48')" }
|
|
30905
|
-
)
|
|
30906
|
-
).describe(
|
|
30919
|
+
routes: external_exports3.array(external_exports3.string().refine(isCidr, { message: "must be a CIDR (e.g. '10.0.0.0/24' or 'fd7a:115c::/48')" })).describe(
|
|
30907
30920
|
"Full list of CIDR routes to enable (e.g. ['10.0.0.0/24', '192.168.1.0/24']). Replaces existing enabled routes."
|
|
30908
30921
|
)
|
|
30909
30922
|
}),
|
|
@@ -31607,8 +31620,8 @@ var keyTools = [
|
|
|
31607
31620
|
const body = {};
|
|
31608
31621
|
if (keyType !== "auth") body.keyType = keyType;
|
|
31609
31622
|
if (input.description !== void 0) {
|
|
31610
|
-
const sanitized =
|
|
31611
|
-
if (sanitized
|
|
31623
|
+
const sanitized = validateAndSanitizeDescription(input.description);
|
|
31624
|
+
if (sanitized !== void 0) body.description = sanitized;
|
|
31612
31625
|
}
|
|
31613
31626
|
if (keyType === "auth") {
|
|
31614
31627
|
body.capabilities = {
|
|
@@ -31681,8 +31694,8 @@ var keyTools = [
|
|
|
31681
31694
|
validateTags(input.tags);
|
|
31682
31695
|
const body = {};
|
|
31683
31696
|
if (input.description !== void 0) {
|
|
31684
|
-
const sanitized =
|
|
31685
|
-
if (sanitized
|
|
31697
|
+
const sanitized = validateAndSanitizeDescription(input.description);
|
|
31698
|
+
if (sanitized !== void 0) body.description = sanitized;
|
|
31686
31699
|
}
|
|
31687
31700
|
if (input.scopes !== void 0) body.scopes = input.scopes;
|
|
31688
31701
|
if (input.tags !== void 0) body.tags = input.tags;
|
|
@@ -32600,7 +32613,7 @@ var webhookTools = [
|
|
|
32600
32613
|
];
|
|
32601
32614
|
|
|
32602
32615
|
// src/index.ts
|
|
32603
|
-
var version2 = true ? "0.10.
|
|
32616
|
+
var version2 = true ? "0.10.6" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
32604
32617
|
var subcommand = process.argv[2];
|
|
32605
32618
|
if (subcommand === "deploy-acl") {
|
|
32606
32619
|
const filePath = process.argv[3];
|