@tgomareli/macos-tools-mcp
Version:
MCP server for advanced macOS system monitoring and file search capabilities
152 lines • 5.56 kB
JavaScript
import { homedir } from "os";
import { resolve } from "path";
import { searchWithSpotlight, searchFileContent, searchByTags, searchFilenames, getFileTags, setFileTags, } from "../utils/file-search.js";
import { stat } from "fs/promises";
import { glob } from "glob";
import { fileTagsCache, createCacheKey } from "../utils/cache.js";
async function performSearch(params) {
const startTime = Date.now();
try {
const searchPath = params.path
? resolve(params.path.replace("~", homedir()))
: homedir();
let results = [];
switch (params.searchType) {
case "content": {
if (!params.query) {
return { status: "error", error: "Query required for content search" };
}
results = await searchFileContent(params.query, searchPath, params.fileTypes, false);
break;
}
case "regex": {
if (!params.query) {
return { status: "error", error: "Query required for regex search" };
}
results = await searchFileContent(params.query, searchPath, params.fileTypes, true);
break;
}
case "filename": {
if (!params.query) {
return { status: "error", error: "Query required for filename search" };
}
results = await searchFilenames(params.query, searchPath, params.fileTypes, params.maxResults || 100);
break;
}
case "tags": {
if (!params.tags || params.tags.length === 0) {
return { status: "error", error: "Tags required for tag search" };
}
results = await searchByTags(params.tags, searchPath);
break;
}
default: {
// Default to Spotlight search
if (!params.query) {
return { status: "error", error: "Query required for search" };
}
results = await searchWithSpotlight(params.query, searchPath, params.fileTypes);
}
}
// Apply max results limit
if (params.maxResults && results.length > params.maxResults) {
results = results.slice(0, params.maxResults);
}
// Add tags to results if not already present
for (const result of results) {
if (!result.tags) {
const tagCacheKey = createCacheKey("tags", { path: result.path });
result.tags = await fileTagsCache.get(tagCacheKey, async () => {
return await getFileTags(result.path);
});
}
}
const searchTime = Date.now() - startTime;
return {
status: "success",
results,
totalFound: results.length,
searchTime,
};
}
catch (error) {
return {
status: "error",
error: error instanceof Error ? error.message : String(error),
};
}
}
async function performTagOperation(params) {
try {
if (!params.path || !params.tags || params.tags.length === 0) {
return {
status: "error",
error: "Path and tags required for tag operations",
};
}
const targetPath = resolve(params.path.replace("~", homedir()));
// Check if path is a directory or file
const stats = await stat(targetPath);
let filesToTag = [];
if (stats.isDirectory()) {
// Tag all files in directory
const pattern = params.fileTypes
? `**/*.{${params.fileTypes.join(",")}}`
: "**/*";
filesToTag = await glob(pattern, {
cwd: targetPath,
absolute: true,
nodir: true,
maxDepth: params.action === "tag" ? 3 : undefined, // Limit depth for tagging
});
}
else {
filesToTag = [targetPath];
}
let filesTagged = 0;
for (const file of filesToTag) {
try {
if (params.action === "tag") {
const existingTags = await getFileTags(file);
const newTags = [...new Set([...existingTags, ...params.tags])];
await setFileTags(file, newTags);
}
else {
// untag
const existingTags = await getFileTags(file);
const newTags = existingTags.filter(t => !params.tags.includes(t));
await setFileTags(file, newTags);
}
filesTagged++;
}
catch {
// Skip files we can't tag
}
}
return {
status: "success",
filesTagged,
};
}
catch (error) {
return {
status: "error",
error: error instanceof Error ? error.message : String(error),
};
}
}
export async function spotlightEnhanced(params) {
switch (params.action) {
case "search":
return performSearch(params);
case "tag":
case "untag":
return performTagOperation(params);
default:
return {
status: "error",
error: "Invalid action",
};
}
}
//# sourceMappingURL=spotlight-enhanced.js.map