unix-disk-mcp 0.1.0 → 0.3.0

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.
@@ -17,11 +17,13 @@ const MCP_CONFIGS = {
17
17
  ? join(homedir(), "Library", "Application Support", "Code", "User", "mcp.json")
18
18
  : join(homedir(), ".config", "Code", "User", "mcp.json"),
19
19
  },
20
+ cursor: {
21
+ name: "Cursor",
22
+ path: join(homedir(), ".cursor", "mcp.json"),
23
+ },
20
24
  claude: {
21
25
  name: "Claude Desktop",
22
- path: process.platform === 'darwin'
23
- ? join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json")
24
- : join(homedir(), ".config", "Claude", "claude_desktop_config.json"),
26
+ path: join(homedir(), ".claude.json"),
25
27
  },
26
28
  };
27
29
  /**
@@ -81,19 +83,22 @@ async function configureProtectedPaths(rl) {
81
83
  async function selectMCPClient(rl) {
82
84
  console.log("\nšŸ”§ MCP Client Configuration\n");
83
85
  console.log("Which MCP client would you like to configure?\n");
84
- console.log(" 1. VS Code (Roo Cline)");
85
- console.log(" 2. Claude Desktop");
86
- console.log(" 3. Both");
87
- console.log(" 4. None (manual configuration)\n");
88
- const answer = await ask(rl, "Select [1-4]: ");
86
+ console.log(" 1. VS Code");
87
+ console.log(" 2. Cursor");
88
+ console.log(" 3. Claude Desktop");
89
+ console.log(" 4. All of the above");
90
+ console.log(" 5. None (manual configuration)\n");
91
+ const answer = await ask(rl, "Select [1-5]: ");
89
92
  switch (answer.trim()) {
90
93
  case "1":
91
94
  return "vscode";
92
95
  case "2":
93
- return "claude";
96
+ return "cursor";
94
97
  case "3":
95
- return "both";
98
+ return "claude";
96
99
  case "4":
100
+ return "all";
101
+ case "5":
97
102
  default:
98
103
  return "none";
99
104
  }
@@ -110,24 +115,24 @@ function updateMCPConfig(client, configPath) {
110
115
  try {
111
116
  const raw = readFileSync(config.path, "utf-8");
112
117
  const data = JSON.parse(raw);
113
- if (client === "vscode") {
114
- // VS Code MCP config format
115
- if (!data.servers) {
116
- data.servers = {};
118
+ if (client === "cursor") {
119
+ // Cursor format: uses "mcpServers" without "type"
120
+ if (!data.mcpServers) {
121
+ data.mcpServers = {};
117
122
  }
118
- data.servers["unix-disk-mcp"] = {
119
- type: "stdio",
123
+ data.mcpServers["unix-disk-mcp"] = {
120
124
  command: "unix-disk-mcp",
125
+ args: [],
121
126
  };
122
127
  }
123
128
  else {
124
- // Claude Desktop config format
125
- if (!data.mcpServers) {
126
- data.mcpServers = {};
129
+ // VS Code & Claude format: use "servers" with "type"
130
+ if (!data.servers) {
131
+ data.servers = {};
127
132
  }
128
- data.mcpServers["unix-disk-mcp"] = {
133
+ data.servers["unix-disk-mcp"] = {
134
+ type: "stdio",
129
135
  command: "unix-disk-mcp",
130
- args: [],
131
136
  };
132
137
  }
133
138
  writeFileSync(config.path, JSON.stringify(data, null, 2));
@@ -166,8 +171,10 @@ function printManualInstructions() {
166
171
  args: [],
167
172
  },
168
173
  }, null, 2));
169
- console.log("\n\nVS Code (Roo Cline):");
174
+ console.log("\n\nVS Code:");
170
175
  console.log(` ${MCP_CONFIGS.vscode.path}\n`);
176
+ console.log("Cursor:");
177
+ console.log(` ${MCP_CONFIGS.cursor.path}\n`);
171
178
  console.log("Claude Desktop:");
172
179
  console.log(` ${MCP_CONFIGS.claude.path}\n`);
173
180
  }
@@ -191,10 +198,13 @@ export async function runSetup() {
191
198
  printManualInstructions();
192
199
  }
193
200
  else {
194
- if (client === "vscode" || client === "both") {
201
+ if (client === "vscode" || client === "all") {
195
202
  updateMCPConfig("vscode", getConfigPath());
196
203
  }
197
- if (client === "claude" || client === "both") {
204
+ if (client === "cursor" || client === "all") {
205
+ updateMCPConfig("cursor", getConfigPath());
206
+ }
207
+ if (client === "claude" || client === "all") {
198
208
  updateMCPConfig("claude", getConfigPath());
199
209
  }
200
210
  console.log("\nāœ… Setup complete! Restart your MCP client to use the server.\n");
@@ -71,48 +71,71 @@ export function registerExplorationTools(server, config) {
71
71
  try {
72
72
  let disk;
73
73
  if (process.platform === 'darwin') {
74
- // macOS: Use diskutil for accurate APFS container usage
75
- const diskutilOutput = execSync("diskutil info / | grep -E 'Volume Name|Container Total Space|Container Free Space'", {
76
- encoding: "utf-8"
74
+ // macOS: Use df to get relevant volumes, plus diskutil for container info
75
+ const dfOutput = execSync("df -k | awk '$9==\"/\" || $9==\"/System/Volumes/Data\" || $9~/^\\/Volumes\\// {print}'", { encoding: "utf-8" });
76
+ const dfLines = dfOutput.trim().split("\n");
77
+ const disks = dfLines.map((line) => {
78
+ const parts = line.trim().split(/\s+/);
79
+ const totalBytes = parseInt(parts[1]) * 1024 || 0;
80
+ const usedBytes = parseInt(parts[2]) * 1024 || 0;
81
+ const availableBytes = parseInt(parts[3]) * 1024 || 0;
82
+ const percentUsed = Math.round((usedBytes / totalBytes) * 100) || 0;
83
+ return {
84
+ filesystem: parts[0],
85
+ total_bytes: totalBytes,
86
+ used_bytes: usedBytes,
87
+ available_bytes: availableBytes,
88
+ percent_used: percentUsed,
89
+ mounted_on: parts[8],
90
+ };
77
91
  });
78
- const lines = diskutilOutput.trim().split("\n");
79
- const volumeName = lines[0]?.split(":")[1]?.trim() || "Unknown";
80
- // Parse container space - format: "494.4 GB (494384795648 Bytes) (exactly...)"
81
- const totalLine = lines[1]?.split(":")[1]?.trim() || "";
82
- const freeLine = lines[2]?.split(":")[1]?.trim() || "";
83
- // Extract human-readable values before the first parenthesis (e.g., "494.4 GB")
84
- const totalGB = totalLine.split("(")[0]?.trim() || "Unknown";
85
- const freeGB = freeLine.split("(")[0]?.trim() || "Unknown";
86
- // Extract bytes for calculations - inside first parenthesis
87
- const totalBytesMatch = totalLine.match(/\((\d+) Bytes\)/);
88
- const freeBytesMatch = freeLine.match(/\((\d+) Bytes\)/);
89
- const totalBytes = totalBytesMatch ? parseInt(totalBytesMatch[1]) : 0;
90
- const freeBytes = freeBytesMatch ? parseInt(freeBytesMatch[1]) : 0;
91
- const usedBytes = totalBytes - freeBytes;
92
- const percentUsed = totalBytes > 0 ? Math.round((usedBytes / totalBytes) * 100) : 0;
93
- // Format used space
94
- const usedGB = (usedBytes / 1e9).toFixed(1) + " GB";
95
- disk = {
96
- volume: volumeName,
97
- total: totalGB,
98
- used: usedGB,
99
- available: freeGB,
100
- percent_used: `${percentUsed}%`,
101
- note: "APFS container usage (accurate)",
102
- };
92
+ // Add container summary for context
93
+ try {
94
+ const diskutilOutput = execSync("diskutil info / | grep -E 'Container Total Space|Container Free Space'", { encoding: "utf-8" });
95
+ const lines = diskutilOutput.trim().split("\n");
96
+ const totalLine = lines[0]?.split(":")[1]?.trim() || "";
97
+ const freeLine = lines[1]?.split(":")[1]?.trim() || "";
98
+ const totalBytesMatch = totalLine.match(/\((\d+) Bytes\)/);
99
+ const freeBytesMatch = freeLine.match(/\((\d+) Bytes\)/);
100
+ const containerTotal = totalBytesMatch ? parseInt(totalBytesMatch[1]) : 0;
101
+ const containerFree = freeBytesMatch ? parseInt(freeBytesMatch[1]) : 0;
102
+ const containerUsed = containerTotal - containerFree;
103
+ disk = {
104
+ volumes: disks,
105
+ apfs_container: {
106
+ total_bytes: containerTotal,
107
+ used_bytes: containerUsed,
108
+ available_bytes: containerFree,
109
+ percent_used: Math.round((containerUsed / containerTotal) * 100),
110
+ note: "Shared APFS container space - volumes dynamically allocate from this",
111
+ },
112
+ };
113
+ }
114
+ catch {
115
+ // If diskutil fails, just return volumes
116
+ disk = disks;
117
+ }
103
118
  }
104
119
  else {
105
- // Linux: Use df
106
- const dfOutput = execSync("df -h / | tail -1", { encoding: "utf-8" });
107
- const parts = dfOutput.trim().split(/\s+/);
108
- disk = {
109
- filesystem: parts[0],
110
- total: parts[1],
111
- used: parts[2],
112
- available: parts[3],
113
- percent_used: parts[4],
114
- mounted: parts[5],
115
- };
120
+ // Linux: Use df to get all relevant filesystems
121
+ const dfOutput = execSync("df -B1 | awk 'NR==1 || $6==\"/\" || $6==\"/home\" || $6~/^\\/mnt\\// || $6~/^\\/media\\//'", { encoding: "utf-8" });
122
+ const lines = dfOutput.trim().split("\n");
123
+ const disks = lines.slice(1).map((line) => {
124
+ const parts = line.trim().split(/\s+/);
125
+ const totalBytes = parseInt(parts[1]) || 0;
126
+ const usedBytes = parseInt(parts[2]) || 0;
127
+ const availableBytes = parseInt(parts[3]) || 0;
128
+ const percentUsed = Math.round((usedBytes / totalBytes) * 100) || 0;
129
+ return {
130
+ filesystem: parts[0],
131
+ total_bytes: totalBytes,
132
+ used_bytes: usedBytes,
133
+ available_bytes: availableBytes,
134
+ percent_used: percentUsed,
135
+ mounted_on: parts[5],
136
+ };
137
+ });
138
+ disk = disks;
116
139
  }
117
140
  // Get home directory breakdown
118
141
  const home = homedir();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unix-disk-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server for AI-assisted disk cleanup on Unix systems (macOS and Linux)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",