Skip to main content
Glama
get_file_tree.js6.37 kB
// src/tools/get_file_tree.js const path = require('path'); const fs = require('fs').promises; const { listFiles } = require('../core/file-lister'); // --- Tree Rendering Logic (adapted from frontend logic) --- /** * Builds a hierarchical tree object from a flat list of file paths. * @param {string[]} filePaths Array of relative file paths. * @returns {object} A nested object representing the directory structure. */ function buildTreeObject(filePaths) { const tree = {}; // Sort paths alphabetically for consistent tree structure const sortedPaths = [...filePaths].sort((a, b) => a.localeCompare(b)); sortedPaths.forEach(filePath => { // Normalize path separators just in case const parts = filePath.replace(/\\/g, '/').split('/'); let currentLevel = tree; for (let i = 0; i < parts.length; i++) { const part = parts[i]; if (!part) continue; // Skip empty parts potentially caused by leading/trailing slashes if (i === parts.length - 1) { // It's a file if (!currentLevel._files) { currentLevel._files = []; } // Avoid adding duplicates if path normalization leads to same entry if (!currentLevel._files.includes(part)) { currentLevel._files.push(part); } } else { // It's a directory if (!currentLevel[part]) { currentLevel[part] = {}; // Create directory node if it doesn't exist } // Ensure we don't try to traverse into a file node mistakenly marked earlier if (typeof currentLevel[part] === 'object' && currentLevel[part] !== null) { currentLevel = currentLevel[part]; } else { // Handle potential path conflict (e.g., 'a/b' file and 'a/b/c' directory) // This basic builder assumes valid, non-conflicting paths from listFiles console.warn(`Path conflict or unexpected structure processing: ${filePath}`); break; // Stop processing this conflicting path } } } }); return tree; } /** * Renders the tree object into a string format. * @param {object} node The current node in the tree object. * @param {string} [prefix=''] The prefix string for the current level. * @returns {string} The string representation of the tree branch. */ function renderTree(node, prefix = '') { let result = ''; // Get directory names, sort them const folders = Object.keys(node).filter(key => key !== '_files').sort((a, b) => a.localeCompare(b)); // Get file names, sort them const files = node._files ? [...node._files].sort((a, b) => a.localeCompare(b)) : []; const totalItems = folders.length + files.length; let itemCount = 0; // Render folders first folders.forEach((folder) => { itemCount++; const isLast = itemCount === totalItems; const connector = isLast ? '└── ' : '├── '; const childPrefix = isLast ? ' ' : '│ '; // Connector for children result += prefix + connector + folder + '/\n'; result += renderTree(node[folder], prefix + childPrefix); // Recurse into subdirectory }); // Render files files.forEach((file) => { itemCount++; const isLast = itemCount === totalItems; const connector = isLast ? '└── ' : '├── '; result += prefix + connector + file + '\n'; }); return result; } // --- Tool Handler --- /** * Handles the 'get_file_tree' MCP request. * @param {object} parameters Request parameters. * @param {string} parameters.path The target directory path. * @param {boolean} [parameters.use_gitignore=true] * @param {boolean} [parameters.ignore_git=true] * @param {string[]} [parameters.custom_blacklist=[]] * @returns {Promise<object>} The result object containing the file tree string. */ async function handleRequest(parameters) { console.error('get_file_tree: Starting execution'); const startTime = Date.now(); const { path: targetPath, use_gitignore, ignore_git, custom_blacklist } = parameters; if (!targetPath) { throw new Error("Missing required parameter: 'path'."); } // Resolve to absolute path - assuming the input path might be relative to CWD const absolutePath = path.resolve(targetPath); console.error(`get_file_tree: Resolved path to ${absolutePath}`); // Validate path existence and type try { const stats = await fs.stat(absolutePath); if (!stats.isDirectory()) { throw new Error(`Path '${targetPath}' is not a directory.`); } console.error(`get_file_tree: Validated path exists and is a directory`); } catch (error) { if (error.code === 'ENOENT') { throw new Error(`Path '${targetPath}' not found.`); } // Rethrow other stat errors (like permission issues) throw new Error(`Error accessing path '${targetPath}': ${error.message}`); } // List files using the core lister and provided filter options console.error(`get_file_tree: Listing files in ${absolutePath}`); const fileList = await listFiles(absolutePath, { useGitignore: use_gitignore || false, // Pass through params correctly, default to false for speed ignoreGit: ignore_git || true, customBlacklist: custom_blacklist || [] }); console.error(`get_file_tree: Found ${fileList.length} files`); // Build and render the tree structure console.error(`get_file_tree: Building tree object`); const treeObject = buildTreeObject(fileList); const rootDirName = path.basename(absolutePath); // Render the tree starting from the root object, prefix indicates level console.error(`get_file_tree: Rendering tree`); const treeString = rootDirName + '/\n' + renderTree(treeObject); const executionTime = Date.now() - startTime; console.error(`get_file_tree: Execution completed in ${executionTime}ms`); return { file_tree: treeString // Return the result in the expected format }; } module.exports = { handleRequest, handler: handleRequest // Export as handler for compatibility with stdio-handler.js };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yy1588133/code-merge-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server