@yawlabs/tailscale-mcp 0.10.3 → 0.10.5
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 +88 -83
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -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;
|
|
@@ -30381,7 +30384,6 @@ async function apiDelete(path, options) {
|
|
|
30381
30384
|
}
|
|
30382
30385
|
|
|
30383
30386
|
// src/cli.ts
|
|
30384
|
-
import { readFileSync } from "node:fs";
|
|
30385
30387
|
async function deployAcl(filePath) {
|
|
30386
30388
|
let policy;
|
|
30387
30389
|
try {
|
|
@@ -30463,6 +30465,85 @@ function filterTools(groups, options) {
|
|
|
30463
30465
|
return result;
|
|
30464
30466
|
}
|
|
30465
30467
|
|
|
30468
|
+
// src/server-wiring.ts
|
|
30469
|
+
function wrapToolHandler(tool) {
|
|
30470
|
+
return async (input) => {
|
|
30471
|
+
try {
|
|
30472
|
+
const result = await tool.handler(input);
|
|
30473
|
+
const response = result;
|
|
30474
|
+
if (!response.ok) {
|
|
30475
|
+
return {
|
|
30476
|
+
content: [
|
|
30477
|
+
{
|
|
30478
|
+
type: "text",
|
|
30479
|
+
text: `Error: ${response.error || "Unknown error"}`
|
|
30480
|
+
}
|
|
30481
|
+
],
|
|
30482
|
+
isError: true
|
|
30483
|
+
};
|
|
30484
|
+
}
|
|
30485
|
+
const text = response.rawBody ?? JSON.stringify(response.data ?? { success: true }, null, 2);
|
|
30486
|
+
return {
|
|
30487
|
+
content: [{ type: "text", text }]
|
|
30488
|
+
};
|
|
30489
|
+
} catch (err) {
|
|
30490
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30491
|
+
return {
|
|
30492
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
30493
|
+
isError: true
|
|
30494
|
+
};
|
|
30495
|
+
}
|
|
30496
|
+
};
|
|
30497
|
+
}
|
|
30498
|
+
async function tailnetStatusResource(uri) {
|
|
30499
|
+
const [devicesRes, settingsRes] = await Promise.all([
|
|
30500
|
+
apiGet(`/tailnet/${getTailnet()}/devices?fields=id`),
|
|
30501
|
+
apiGet(`/tailnet/${getTailnet()}/settings`)
|
|
30502
|
+
]);
|
|
30503
|
+
const data = {
|
|
30504
|
+
tailnet: getTailnet(),
|
|
30505
|
+
deviceCount: devicesRes.ok ? devicesRes.data?.devices?.length ?? 0 : null,
|
|
30506
|
+
settings: settingsRes.ok ? settingsRes.data : null
|
|
30507
|
+
};
|
|
30508
|
+
const errors = {};
|
|
30509
|
+
if (!devicesRes.ok) errors.devices = devicesRes.error ?? `HTTP ${devicesRes.status}`;
|
|
30510
|
+
if (!settingsRes.ok) errors.settings = settingsRes.error ?? `HTTP ${settingsRes.status}`;
|
|
30511
|
+
if (Object.keys(errors).length > 0) data.errors = errors;
|
|
30512
|
+
return { contents: [{ uri: uri.href, text: JSON.stringify(data, null, 2), mimeType: "application/json" }] };
|
|
30513
|
+
}
|
|
30514
|
+
async function tailnetDevicesResource(uri) {
|
|
30515
|
+
const res = await apiGet(`/tailnet/${getTailnet()}/devices`);
|
|
30516
|
+
const text = res.ok ? JSON.stringify(res.data, null, 2) : JSON.stringify({ error: res.error ?? `HTTP ${res.status}` }, null, 2);
|
|
30517
|
+
return { contents: [{ uri: uri.href, text, mimeType: "application/json" }] };
|
|
30518
|
+
}
|
|
30519
|
+
async function tailnetAclResource(uri) {
|
|
30520
|
+
const res = await apiGet(`/tailnet/${getTailnet()}/acl`, { acceptRaw: true, accept: "application/hujson" });
|
|
30521
|
+
const text = res.ok ? res.rawBody ?? "" : `// Error: ${res.error ?? `HTTP ${res.status}`}
|
|
30522
|
+
`;
|
|
30523
|
+
return { contents: [{ uri: uri.href, text, mimeType: "application/hujson" }] };
|
|
30524
|
+
}
|
|
30525
|
+
async function tailnetDnsResource(uri) {
|
|
30526
|
+
const [nameservers, searchPaths, splitDns, preferences] = await Promise.all([
|
|
30527
|
+
apiGet(`/tailnet/${getTailnet()}/dns/nameservers`),
|
|
30528
|
+
apiGet(`/tailnet/${getTailnet()}/dns/searchpaths`),
|
|
30529
|
+
apiGet(`/tailnet/${getTailnet()}/dns/split-dns`),
|
|
30530
|
+
apiGet(`/tailnet/${getTailnet()}/dns/preferences`)
|
|
30531
|
+
]);
|
|
30532
|
+
const data = {
|
|
30533
|
+
nameservers: nameservers.ok ? nameservers.data : null,
|
|
30534
|
+
searchPaths: searchPaths.ok ? searchPaths.data : null,
|
|
30535
|
+
splitDns: splitDns.ok ? splitDns.data : null,
|
|
30536
|
+
preferences: preferences.ok ? preferences.data : null
|
|
30537
|
+
};
|
|
30538
|
+
const errors = {};
|
|
30539
|
+
if (!nameservers.ok) errors.nameservers = nameservers.error ?? `HTTP ${nameservers.status}`;
|
|
30540
|
+
if (!searchPaths.ok) errors.searchPaths = searchPaths.error ?? `HTTP ${searchPaths.status}`;
|
|
30541
|
+
if (!splitDns.ok) errors.splitDns = splitDns.error ?? `HTTP ${splitDns.status}`;
|
|
30542
|
+
if (!preferences.ok) errors.preferences = preferences.error ?? `HTTP ${preferences.status}`;
|
|
30543
|
+
if (Object.keys(errors).length > 0) data.errors = errors;
|
|
30544
|
+
return { contents: [{ uri: uri.href, text: JSON.stringify(data, null, 2), mimeType: "application/json" }] };
|
|
30545
|
+
}
|
|
30546
|
+
|
|
30466
30547
|
// src/tools/acl.ts
|
|
30467
30548
|
var aclTools = [
|
|
30468
30549
|
{
|
|
@@ -32519,7 +32600,7 @@ var webhookTools = [
|
|
|
32519
32600
|
];
|
|
32520
32601
|
|
|
32521
32602
|
// src/index.ts
|
|
32522
|
-
var version2 = true ? "0.10.
|
|
32603
|
+
var version2 = true ? "0.10.5" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
32523
32604
|
var subcommand = process.argv[2];
|
|
32524
32605
|
if (subcommand === "deploy-acl") {
|
|
32525
32606
|
const filePath = process.argv[3];
|
|
@@ -32576,81 +32657,25 @@ var server = new McpServer({
|
|
|
32576
32657
|
version: version2
|
|
32577
32658
|
});
|
|
32578
32659
|
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
|
-
);
|
|
32660
|
+
server.tool(tool.name, tool.description, tool.inputSchema.shape, tool.annotations, wrapToolHandler(tool));
|
|
32612
32661
|
}
|
|
32613
32662
|
server.resource(
|
|
32614
32663
|
"tailnet-status",
|
|
32615
32664
|
"tailscale://tailnet/status",
|
|
32616
32665
|
{ 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
|
-
}
|
|
32666
|
+
tailnetStatusResource
|
|
32633
32667
|
);
|
|
32634
32668
|
server.resource(
|
|
32635
32669
|
"tailnet-devices",
|
|
32636
32670
|
"tailscale://tailnet/devices",
|
|
32637
32671
|
{ 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
|
-
}
|
|
32672
|
+
tailnetDevicesResource
|
|
32643
32673
|
);
|
|
32644
32674
|
server.resource(
|
|
32645
32675
|
"tailnet-acl",
|
|
32646
32676
|
"tailscale://tailnet/acl",
|
|
32647
32677
|
{ 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
|
-
}
|
|
32678
|
+
tailnetAclResource
|
|
32654
32679
|
);
|
|
32655
32680
|
server.resource(
|
|
32656
32681
|
"tailnet-dns",
|
|
@@ -32659,27 +32684,7 @@ server.resource(
|
|
|
32659
32684
|
description: "DNS configuration including nameservers, search paths, split DNS, and MagicDNS status",
|
|
32660
32685
|
mimeType: "application/json"
|
|
32661
32686
|
},
|
|
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
|
-
}
|
|
32687
|
+
tailnetDnsResource
|
|
32683
32688
|
);
|
|
32684
32689
|
var transport = new StdioServerTransport();
|
|
32685
32690
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yawlabs/tailscale-mcp",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.5",
|
|
4
4
|
"description": "Tailscale MCP server for managing your tailnet from AI assistants",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "YawLabs <contact@yaw.sh>",
|
|
@@ -51,6 +51,6 @@
|
|
|
51
51
|
"zod": "^4.3.6"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
|
-
"node": ">=
|
|
54
|
+
"node": ">=20"
|
|
55
55
|
}
|
|
56
56
|
}
|