get_file_tree
Generate a complete file tree structure for a project directory, with options to apply .gitignore rules, exclude .git folders, or add custom exclusions. Ideal for organizing and navigating code repositories.
Instructions
Retrieves the file tree structure of the project.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| custom_blacklist | No | Custom blacklist items. | |
| ignore_git | No | Whether to ignore the .git directory. | |
| path | No | The target directory path. | |
| use_gitignore | No | Whether to use .gitignore rules. |
Implementation Reference
- src/tools/get_file_tree.js:90-142 (handler)Main execution logic for the get_file_tree tool: validates input path, lists files recursively, builds and renders a visual tree structure.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 }; }
- src/mcp-server.js:121-145 (registration)Registers the get_file_tree tool with the MCP server, loading the handler and defining input schema.if (getFileTreeHandler) { server.tool( 'get_file_tree', 'Retrieves the file tree structure of the project.', { path: z.string().optional().describe('The target directory path.'), use_gitignore: z.boolean().optional().describe('Whether to use .gitignore rules.'), ignore_git: z.boolean().optional().describe('Whether to ignore the .git directory.'), custom_blacklist: z.array(z.string()).optional().describe('Custom blacklist items.') }, async (params) => { logInfo(`Executing get_file_tree tool with params: ${JSON.stringify(params)}`); try { const startTime = Date.now(); const result = await getFileTreeHandler(params); const executionTime = Date.now() - startTime; logDebug(`get_file_tree completed in ${executionTime}ms`); return adaptToolResult(result); } catch (error) { logError('Error in get_file_tree tool:', error); throw error; } } ); }
- src/mcp-server.js:125-130 (schema)Input schema using Zod for validating tool parameters.{ path: z.string().optional().describe('The target directory path.'), use_gitignore: z.boolean().optional().describe('Whether to use .gitignore rules.'), ignore_git: z.boolean().optional().describe('Whether to ignore the .git directory.'), custom_blacklist: z.array(z.string()).optional().describe('Custom blacklist items.') },
- src/tools/get_file_tree.js:11-47 (helper)Helper function to construct a nested object representing the directory tree from a list of file paths.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; }
- src/tools/get_file_tree.js:54-79 (helper)Helper function to render the tree object into an ASCII art string representation.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; }