@yawlabs/tailscale-mcp 0.10.4 → 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.
Files changed (2) hide show
  1. package/dist/index.js +88 -83
  2. package/package.json +1 -1
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.4" : (await null).createRequire(import.meta.url)("../package.json").version;
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
- async (uri) => {
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
- async (uri) => {
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
- async (uri) => {
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
- async (uri) => {
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.4",
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>",