@yawlabs/tailscale-mcp 0.10.4 → 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 +139 -121
- 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();
|
|
@@ -30113,6 +30113,9 @@ var StdioServerTransport = class {
|
|
|
30113
30113
|
}
|
|
30114
30114
|
};
|
|
30115
30115
|
|
|
30116
|
+
// src/cli.ts
|
|
30117
|
+
import { readFileSync } from "node:fs";
|
|
30118
|
+
|
|
30116
30119
|
// src/api.ts
|
|
30117
30120
|
var BASE_URL = "https://api.tailscale.com/api/v2";
|
|
30118
30121
|
var REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -30207,15 +30210,20 @@ function validateTags(tags) {
|
|
|
30207
30210
|
function sanitizeDescription(value) {
|
|
30208
30211
|
return value.replace(/[/_]/g, "-").replace(/[^a-zA-Z0-9 -]/g, "").replace(/ {2,}/g, " ").trim().slice(0, 50);
|
|
30209
30212
|
}
|
|
30210
|
-
function
|
|
30211
|
-
const
|
|
30212
|
-
|
|
30213
|
-
|
|
30214
|
-
|
|
30215
|
-
|
|
30216
|
-
|
|
30217
|
-
|
|
30218
|
-
|
|
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) {
|
|
30219
30227
|
lines.push(
|
|
30220
30228
|
" - On Windows, env vars set in bash/WSL profiles are not visible to MCP servers launched via cmd",
|
|
30221
30229
|
"",
|
|
@@ -30224,7 +30232,8 @@ function formatAuthError(apiBody) {
|
|
|
30224
30232
|
" 2. Set TAILSCALE_API_KEY as a Windows user environment variable (System Properties > Environment Variables)"
|
|
30225
30233
|
);
|
|
30226
30234
|
}
|
|
30227
|
-
|
|
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);
|
|
30228
30237
|
if (apiBody) {
|
|
30229
30238
|
lines.push("", `API response: ${apiBody}`);
|
|
30230
30239
|
}
|
|
@@ -30250,28 +30259,32 @@ var concurrencyQueue = [];
|
|
|
30250
30259
|
function getConcurrencyLimit() {
|
|
30251
30260
|
const raw = process.env.TAILSCALE_MAX_CONCURRENT;
|
|
30252
30261
|
if (!raw) return 0;
|
|
30253
|
-
const n = Number
|
|
30254
|
-
return Number.
|
|
30262
|
+
const n = Number(raw);
|
|
30263
|
+
return Number.isInteger(n) && n > 0 ? n : 0;
|
|
30255
30264
|
}
|
|
30256
30265
|
function getRequestBudgetMs() {
|
|
30257
30266
|
const raw = process.env.TAILSCALE_REQUEST_BUDGET_MS;
|
|
30258
30267
|
if (!raw) return MAX_REQUEST_BUDGET_MS;
|
|
30259
|
-
const n = Number
|
|
30260
|
-
return Number.
|
|
30268
|
+
const n = Number(raw);
|
|
30269
|
+
return Number.isInteger(n) && n > 0 ? n : MAX_REQUEST_BUDGET_MS;
|
|
30261
30270
|
}
|
|
30262
30271
|
async function withConcurrencyLimit(fn) {
|
|
30263
30272
|
const limit = getConcurrencyLimit();
|
|
30264
30273
|
if (limit === 0) return fn();
|
|
30265
30274
|
if (inFlight >= limit) {
|
|
30266
30275
|
await new Promise((resolve) => concurrencyQueue.push(resolve));
|
|
30276
|
+
} else {
|
|
30277
|
+
inFlight++;
|
|
30267
30278
|
}
|
|
30268
|
-
inFlight++;
|
|
30269
30279
|
try {
|
|
30270
30280
|
return await fn();
|
|
30271
30281
|
} finally {
|
|
30272
|
-
inFlight--;
|
|
30273
30282
|
const next = concurrencyQueue.shift();
|
|
30274
|
-
if (next)
|
|
30283
|
+
if (next) {
|
|
30284
|
+
next();
|
|
30285
|
+
} else {
|
|
30286
|
+
inFlight--;
|
|
30287
|
+
}
|
|
30275
30288
|
}
|
|
30276
30289
|
}
|
|
30277
30290
|
function debugLog(...parts) {
|
|
@@ -30347,14 +30360,14 @@ async function apiRequest(method, path, body, options) {
|
|
|
30347
30360
|
if (options?.acceptRaw) {
|
|
30348
30361
|
const rawBody = await response.text();
|
|
30349
30362
|
if (!response.ok) {
|
|
30350
|
-
const error48 = response.status === 401 ? formatAuthError(rawBody) : extractErrorMessage(rawBody);
|
|
30363
|
+
const error48 = response.status === 401 || response.status === 403 ? formatAuthError(response.status, rawBody) : extractErrorMessage(rawBody);
|
|
30351
30364
|
return { ok: false, status: response.status, error: error48, rawBody, etag };
|
|
30352
30365
|
}
|
|
30353
30366
|
return { ok: true, status: response.status, rawBody, etag };
|
|
30354
30367
|
}
|
|
30355
30368
|
if (!response.ok) {
|
|
30356
30369
|
const errorBody = await response.text();
|
|
30357
|
-
const error48 = response.status === 401 ? formatAuthError(errorBody) : extractErrorMessage(errorBody);
|
|
30370
|
+
const error48 = response.status === 401 || response.status === 403 ? formatAuthError(response.status, errorBody) : extractErrorMessage(errorBody);
|
|
30358
30371
|
return { ok: false, status: response.status, error: error48, etag };
|
|
30359
30372
|
}
|
|
30360
30373
|
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
@@ -30381,7 +30394,6 @@ async function apiDelete(path, options) {
|
|
|
30381
30394
|
}
|
|
30382
30395
|
|
|
30383
30396
|
// src/cli.ts
|
|
30384
|
-
import { readFileSync } from "node:fs";
|
|
30385
30397
|
async function deployAcl(filePath) {
|
|
30386
30398
|
let policy;
|
|
30387
30399
|
try {
|
|
@@ -30463,6 +30475,85 @@ function filterTools(groups, options) {
|
|
|
30463
30475
|
return result;
|
|
30464
30476
|
}
|
|
30465
30477
|
|
|
30478
|
+
// src/server-wiring.ts
|
|
30479
|
+
function wrapToolHandler(tool) {
|
|
30480
|
+
return async (input) => {
|
|
30481
|
+
try {
|
|
30482
|
+
const result = await tool.handler(input);
|
|
30483
|
+
const response = result;
|
|
30484
|
+
if (!response.ok) {
|
|
30485
|
+
return {
|
|
30486
|
+
content: [
|
|
30487
|
+
{
|
|
30488
|
+
type: "text",
|
|
30489
|
+
text: `Error: ${response.error || "Unknown error"}`
|
|
30490
|
+
}
|
|
30491
|
+
],
|
|
30492
|
+
isError: true
|
|
30493
|
+
};
|
|
30494
|
+
}
|
|
30495
|
+
const text = response.rawBody ?? JSON.stringify(response.data ?? { success: true }, null, 2);
|
|
30496
|
+
return {
|
|
30497
|
+
content: [{ type: "text", text }]
|
|
30498
|
+
};
|
|
30499
|
+
} catch (err) {
|
|
30500
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30501
|
+
return {
|
|
30502
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
30503
|
+
isError: true
|
|
30504
|
+
};
|
|
30505
|
+
}
|
|
30506
|
+
};
|
|
30507
|
+
}
|
|
30508
|
+
async function tailnetStatusResource(uri) {
|
|
30509
|
+
const [devicesRes, settingsRes] = await Promise.all([
|
|
30510
|
+
apiGet(`/tailnet/${getTailnet()}/devices?fields=id`),
|
|
30511
|
+
apiGet(`/tailnet/${getTailnet()}/settings`)
|
|
30512
|
+
]);
|
|
30513
|
+
const data = {
|
|
30514
|
+
tailnet: getTailnet(),
|
|
30515
|
+
deviceCount: devicesRes.ok ? devicesRes.data?.devices?.length ?? 0 : null,
|
|
30516
|
+
settings: settingsRes.ok ? settingsRes.data : null
|
|
30517
|
+
};
|
|
30518
|
+
const errors = {};
|
|
30519
|
+
if (!devicesRes.ok) errors.devices = devicesRes.error ?? `HTTP ${devicesRes.status}`;
|
|
30520
|
+
if (!settingsRes.ok) errors.settings = settingsRes.error ?? `HTTP ${settingsRes.status}`;
|
|
30521
|
+
if (Object.keys(errors).length > 0) data.errors = errors;
|
|
30522
|
+
return { contents: [{ uri: uri.href, text: JSON.stringify(data, null, 2), mimeType: "application/json" }] };
|
|
30523
|
+
}
|
|
30524
|
+
async function tailnetDevicesResource(uri) {
|
|
30525
|
+
const res = await apiGet(`/tailnet/${getTailnet()}/devices`);
|
|
30526
|
+
const text = res.ok ? JSON.stringify(res.data, null, 2) : JSON.stringify({ error: res.error ?? `HTTP ${res.status}` }, null, 2);
|
|
30527
|
+
return { contents: [{ uri: uri.href, text, mimeType: "application/json" }] };
|
|
30528
|
+
}
|
|
30529
|
+
async function tailnetAclResource(uri) {
|
|
30530
|
+
const res = await apiGet(`/tailnet/${getTailnet()}/acl`, { acceptRaw: true, accept: "application/hujson" });
|
|
30531
|
+
const text = res.ok ? res.rawBody ?? "" : `// Error: ${res.error ?? `HTTP ${res.status}`}
|
|
30532
|
+
`;
|
|
30533
|
+
return { contents: [{ uri: uri.href, text, mimeType: "application/hujson" }] };
|
|
30534
|
+
}
|
|
30535
|
+
async function tailnetDnsResource(uri) {
|
|
30536
|
+
const [nameservers, searchPaths, splitDns, preferences] = await Promise.all([
|
|
30537
|
+
apiGet(`/tailnet/${getTailnet()}/dns/nameservers`),
|
|
30538
|
+
apiGet(`/tailnet/${getTailnet()}/dns/searchpaths`),
|
|
30539
|
+
apiGet(`/tailnet/${getTailnet()}/dns/split-dns`),
|
|
30540
|
+
apiGet(`/tailnet/${getTailnet()}/dns/preferences`)
|
|
30541
|
+
]);
|
|
30542
|
+
const data = {
|
|
30543
|
+
nameservers: nameservers.ok ? nameservers.data : null,
|
|
30544
|
+
searchPaths: searchPaths.ok ? searchPaths.data : null,
|
|
30545
|
+
splitDns: splitDns.ok ? splitDns.data : null,
|
|
30546
|
+
preferences: preferences.ok ? preferences.data : null
|
|
30547
|
+
};
|
|
30548
|
+
const errors = {};
|
|
30549
|
+
if (!nameservers.ok) errors.nameservers = nameservers.error ?? `HTTP ${nameservers.status}`;
|
|
30550
|
+
if (!searchPaths.ok) errors.searchPaths = searchPaths.error ?? `HTTP ${searchPaths.status}`;
|
|
30551
|
+
if (!splitDns.ok) errors.splitDns = splitDns.error ?? `HTTP ${splitDns.status}`;
|
|
30552
|
+
if (!preferences.ok) errors.preferences = preferences.error ?? `HTTP ${preferences.status}`;
|
|
30553
|
+
if (Object.keys(errors).length > 0) data.errors = errors;
|
|
30554
|
+
return { contents: [{ uri: uri.href, text: JSON.stringify(data, null, 2), mimeType: "application/json" }] };
|
|
30555
|
+
}
|
|
30556
|
+
|
|
30466
30557
|
// src/tools/acl.ts
|
|
30467
30558
|
var aclTools = [
|
|
30468
30559
|
{
|
|
@@ -30650,6 +30741,18 @@ var auditTools = [
|
|
|
30650
30741
|
];
|
|
30651
30742
|
|
|
30652
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
|
+
}
|
|
30653
30756
|
var deviceTools = [
|
|
30654
30757
|
{
|
|
30655
30758
|
name: "tailscale_list_devices",
|
|
@@ -30813,16 +30916,7 @@ var deviceTools = [
|
|
|
30813
30916
|
},
|
|
30814
30917
|
inputSchema: external_exports3.object({
|
|
30815
30918
|
deviceId: external_exports3.string().describe("The device ID"),
|
|
30816
|
-
routes: external_exports3.array(
|
|
30817
|
-
external_exports3.string().refine(
|
|
30818
|
-
// Accept v4 (10.0.0.0/24) or v6 (fd7a:115c::/48) CIDRs. Routes can be either.
|
|
30819
|
-
// Loose check: must contain a '/' followed by 1-3 digits, and the address part
|
|
30820
|
-
// must look like an IPv4 quad-dotted or an IPv6 colon-form. The Tailscale API
|
|
30821
|
-
// is the authoritative validator; this just rejects obvious typos client-side.
|
|
30822
|
-
(s) => /^([\d.]+|[\da-fA-F:]+)\/\d{1,3}$/.test(s),
|
|
30823
|
-
{ message: "must be a CIDR (e.g. '10.0.0.0/24' or 'fd7a:115c::/48')" }
|
|
30824
|
-
)
|
|
30825
|
-
).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(
|
|
30826
30920
|
"Full list of CIDR routes to enable (e.g. ['10.0.0.0/24', '192.168.1.0/24']). Replaces existing enabled routes."
|
|
30827
30921
|
)
|
|
30828
30922
|
}),
|
|
@@ -31526,8 +31620,8 @@ var keyTools = [
|
|
|
31526
31620
|
const body = {};
|
|
31527
31621
|
if (keyType !== "auth") body.keyType = keyType;
|
|
31528
31622
|
if (input.description !== void 0) {
|
|
31529
|
-
const sanitized =
|
|
31530
|
-
if (sanitized
|
|
31623
|
+
const sanitized = validateAndSanitizeDescription(input.description);
|
|
31624
|
+
if (sanitized !== void 0) body.description = sanitized;
|
|
31531
31625
|
}
|
|
31532
31626
|
if (keyType === "auth") {
|
|
31533
31627
|
body.capabilities = {
|
|
@@ -31600,8 +31694,8 @@ var keyTools = [
|
|
|
31600
31694
|
validateTags(input.tags);
|
|
31601
31695
|
const body = {};
|
|
31602
31696
|
if (input.description !== void 0) {
|
|
31603
|
-
const sanitized =
|
|
31604
|
-
if (sanitized
|
|
31697
|
+
const sanitized = validateAndSanitizeDescription(input.description);
|
|
31698
|
+
if (sanitized !== void 0) body.description = sanitized;
|
|
31605
31699
|
}
|
|
31606
31700
|
if (input.scopes !== void 0) body.scopes = input.scopes;
|
|
31607
31701
|
if (input.tags !== void 0) body.tags = input.tags;
|
|
@@ -32519,7 +32613,7 @@ var webhookTools = [
|
|
|
32519
32613
|
];
|
|
32520
32614
|
|
|
32521
32615
|
// src/index.ts
|
|
32522
|
-
var version2 = true ? "0.10.
|
|
32616
|
+
var version2 = true ? "0.10.6" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
32523
32617
|
var subcommand = process.argv[2];
|
|
32524
32618
|
if (subcommand === "deploy-acl") {
|
|
32525
32619
|
const filePath = process.argv[3];
|
|
@@ -32576,81 +32670,25 @@ var server = new McpServer({
|
|
|
32576
32670
|
version: version2
|
|
32577
32671
|
});
|
|
32578
32672
|
for (const tool of allTools) {
|
|
32579
|
-
server.tool(
|
|
32580
|
-
tool.name,
|
|
32581
|
-
tool.description,
|
|
32582
|
-
tool.inputSchema.shape,
|
|
32583
|
-
tool.annotations,
|
|
32584
|
-
async (input) => {
|
|
32585
|
-
try {
|
|
32586
|
-
const result = await tool.handler(input);
|
|
32587
|
-
const response = result;
|
|
32588
|
-
if (!response.ok) {
|
|
32589
|
-
return {
|
|
32590
|
-
content: [
|
|
32591
|
-
{
|
|
32592
|
-
type: "text",
|
|
32593
|
-
text: `Error: ${response.error || "Unknown error"}`
|
|
32594
|
-
}
|
|
32595
|
-
],
|
|
32596
|
-
isError: true
|
|
32597
|
-
};
|
|
32598
|
-
}
|
|
32599
|
-
const text = response.rawBody ?? JSON.stringify(response.data ?? { success: true }, null, 2);
|
|
32600
|
-
return {
|
|
32601
|
-
content: [{ type: "text", text }]
|
|
32602
|
-
};
|
|
32603
|
-
} catch (err) {
|
|
32604
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
32605
|
-
return {
|
|
32606
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
32607
|
-
isError: true
|
|
32608
|
-
};
|
|
32609
|
-
}
|
|
32610
|
-
}
|
|
32611
|
-
);
|
|
32673
|
+
server.tool(tool.name, tool.description, tool.inputSchema.shape, tool.annotations, wrapToolHandler(tool));
|
|
32612
32674
|
}
|
|
32613
32675
|
server.resource(
|
|
32614
32676
|
"tailnet-status",
|
|
32615
32677
|
"tailscale://tailnet/status",
|
|
32616
32678
|
{ description: "Current tailnet status including device count and settings", mimeType: "application/json" },
|
|
32617
|
-
|
|
32618
|
-
const [devicesRes, settingsRes] = await Promise.all([
|
|
32619
|
-
apiGet(`/tailnet/${getTailnet()}/devices?fields=id`),
|
|
32620
|
-
apiGet(`/tailnet/${getTailnet()}/settings`)
|
|
32621
|
-
]);
|
|
32622
|
-
const data = {
|
|
32623
|
-
tailnet: getTailnet(),
|
|
32624
|
-
deviceCount: devicesRes.ok ? devicesRes.data?.devices?.length ?? 0 : null,
|
|
32625
|
-
settings: settingsRes.ok ? settingsRes.data : null
|
|
32626
|
-
};
|
|
32627
|
-
const errors = {};
|
|
32628
|
-
if (!devicesRes.ok) errors.devices = devicesRes.error ?? `HTTP ${devicesRes.status}`;
|
|
32629
|
-
if (!settingsRes.ok) errors.settings = settingsRes.error ?? `HTTP ${settingsRes.status}`;
|
|
32630
|
-
if (Object.keys(errors).length > 0) data.errors = errors;
|
|
32631
|
-
return { contents: [{ uri: uri.href, text: JSON.stringify(data, null, 2), mimeType: "application/json" }] };
|
|
32632
|
-
}
|
|
32679
|
+
tailnetStatusResource
|
|
32633
32680
|
);
|
|
32634
32681
|
server.resource(
|
|
32635
32682
|
"tailnet-devices",
|
|
32636
32683
|
"tailscale://tailnet/devices",
|
|
32637
32684
|
{ description: "List of all devices in the tailnet with their status", mimeType: "application/json" },
|
|
32638
|
-
|
|
32639
|
-
const res = await apiGet(`/tailnet/${getTailnet()}/devices`);
|
|
32640
|
-
const text = res.ok ? JSON.stringify(res.data, null, 2) : JSON.stringify({ error: res.error ?? `HTTP ${res.status}` }, null, 2);
|
|
32641
|
-
return { contents: [{ uri: uri.href, text, mimeType: "application/json" }] };
|
|
32642
|
-
}
|
|
32685
|
+
tailnetDevicesResource
|
|
32643
32686
|
);
|
|
32644
32687
|
server.resource(
|
|
32645
32688
|
"tailnet-acl",
|
|
32646
32689
|
"tailscale://tailnet/acl",
|
|
32647
32690
|
{ description: "Current ACL policy (HuJSON with comments preserved)", mimeType: "application/hujson" },
|
|
32648
|
-
|
|
32649
|
-
const res = await apiGet(`/tailnet/${getTailnet()}/acl`, { acceptRaw: true, accept: "application/hujson" });
|
|
32650
|
-
const text = res.ok ? res.rawBody ?? "" : `// Error: ${res.error ?? `HTTP ${res.status}`}
|
|
32651
|
-
`;
|
|
32652
|
-
return { contents: [{ uri: uri.href, text, mimeType: "application/hujson" }] };
|
|
32653
|
-
}
|
|
32691
|
+
tailnetAclResource
|
|
32654
32692
|
);
|
|
32655
32693
|
server.resource(
|
|
32656
32694
|
"tailnet-dns",
|
|
@@ -32659,27 +32697,7 @@ server.resource(
|
|
|
32659
32697
|
description: "DNS configuration including nameservers, search paths, split DNS, and MagicDNS status",
|
|
32660
32698
|
mimeType: "application/json"
|
|
32661
32699
|
},
|
|
32662
|
-
|
|
32663
|
-
const [nameservers, searchPaths, splitDns, preferences] = await Promise.all([
|
|
32664
|
-
apiGet(`/tailnet/${getTailnet()}/dns/nameservers`),
|
|
32665
|
-
apiGet(`/tailnet/${getTailnet()}/dns/searchpaths`),
|
|
32666
|
-
apiGet(`/tailnet/${getTailnet()}/dns/split-dns`),
|
|
32667
|
-
apiGet(`/tailnet/${getTailnet()}/dns/preferences`)
|
|
32668
|
-
]);
|
|
32669
|
-
const data = {
|
|
32670
|
-
nameservers: nameservers.ok ? nameservers.data : null,
|
|
32671
|
-
searchPaths: searchPaths.ok ? searchPaths.data : null,
|
|
32672
|
-
splitDns: splitDns.ok ? splitDns.data : null,
|
|
32673
|
-
preferences: preferences.ok ? preferences.data : null
|
|
32674
|
-
};
|
|
32675
|
-
const errors = {};
|
|
32676
|
-
if (!nameservers.ok) errors.nameservers = nameservers.error ?? `HTTP ${nameservers.status}`;
|
|
32677
|
-
if (!searchPaths.ok) errors.searchPaths = searchPaths.error ?? `HTTP ${searchPaths.status}`;
|
|
32678
|
-
if (!splitDns.ok) errors.splitDns = splitDns.error ?? `HTTP ${splitDns.status}`;
|
|
32679
|
-
if (!preferences.ok) errors.preferences = preferences.error ?? `HTTP ${preferences.status}`;
|
|
32680
|
-
if (Object.keys(errors).length > 0) data.errors = errors;
|
|
32681
|
-
return { contents: [{ uri: uri.href, text: JSON.stringify(data, null, 2), mimeType: "application/json" }] };
|
|
32682
|
-
}
|
|
32700
|
+
tailnetDnsResource
|
|
32683
32701
|
);
|
|
32684
32702
|
var transport = new StdioServerTransport();
|
|
32685
32703
|
await server.connect(transport);
|