find_file
Locate non-gitignored files matching a specified file mask within a given directory. Returns a JSON list of matching files for efficient file discovery.
Instructions
Finds non-gitignored files matching the given file mask within the given relative path. Returns a JSON object with the list of matching files.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| file_mask | Yes | The filename or file mask (using the wildcards * or ?) to search for. | |
| relative_path | Yes | The relative path to the directory to search in; pass "." to scan the project root. |
Implementation Reference
- src/serena/tools/file_tools.py:124-157 (handler)The FindFileTool class defines the 'find_file' tool. Its 'apply' method implements the core logic: validates the path, scans the directory recursively using scan_directory with a custom ignore filter based on fnmatch matching the file_mask, and returns a JSON list of matching relative file paths.class FindFileTool(Tool): """ Finds files in the given relative paths """ def apply(self, file_mask: str, relative_path: str) -> str: """ Finds non-gitignored files matching the given file mask within the given relative path :param file_mask: the filename or file mask (using the wildcards * or ?) to search for :param relative_path: the relative path to the directory to search in; pass "." to scan the project root :return: a JSON object with the list of matching files """ self.project.validate_relative_path(relative_path, require_not_ignored=True) dir_to_scan = os.path.join(self.get_project_root(), relative_path) # find the files by ignoring everything that doesn't match def is_ignored_file(abs_path: str) -> bool: if self.project.is_ignored_path(abs_path): return True filename = os.path.basename(abs_path) return not fnmatch(filename, file_mask) _dirs, files = scan_directory( path=dir_to_scan, recursive=True, is_ignored_dir=self.project.is_ignored_path, is_ignored_file=is_ignored_file, relative_to=self.get_project_root(), ) result = self._to_json({"files": files}) return result
- Generates the JSON schema for the tool from the 'apply' method's signature, type annotations, and docstring using func_metadata. This schema is used for input validation in MCP.@classmethod def get_apply_fn_metadata_from_cls(cls) -> FuncMetadata: """Get the metadata for the apply method from the class (static metadata). Needed for creating MCP tools in a separate process without running into serialization issues. """ # First try to get from __dict__ to handle dynamic docstring changes if "apply" in cls.__dict__: apply_fn = cls.__dict__["apply"] else: # Fall back to getattr for inherited methods apply_fn = getattr(cls, "apply", None) if apply_fn is None: raise AttributeError(f"apply method not defined in {cls}. Did you forget to implement it?") return func_metadata(apply_fn, skip_names=["self", "cls"])
- src/serena/mcp.py:234-241 (registration)Registers all exposed Serena tools, including 'find_file', as MCP tools in the FastMCP server by name, using their schema and wrapping the apply method."""Update the tools in the MCP server""" if mcp is not None: mcp._tool_manager._tools = {} for tool in self._iter_tools(): mcp_tool = self.make_mcp_tool(tool, openai_tool_compatible=openai_tool_compatible) mcp._tool_manager._tools[tool.get_name()] = mcp_tool log.info(f"Starting MCP server with {len(mcp._tool_manager._tools)} tools: {list(mcp._tool_manager._tools.keys())}")
- src/serena/tools/tools_base.py:363-370 (registration)ToolRegistry singleton automatically discovers subclasses of Tool in serena.tools modules, computes their names (e.g., FindFileTool -> 'find_file'), and registers them.if not cls.__module__.startswith("serena.tools"): continue is_optional = issubclass(cls, ToolMarkerOptional) name = cls.get_name_from_cls() if name in self._tool_dict: raise ValueError(f"Duplicate tool name found: {name}. Tool classes must have unique names.") self._tool_dict[name] = RegisteredTool(tool_class=cls, is_optional=is_optional, tool_name=name)
- Derives the tool name 'find_file' from the class name 'FindFileTool' by removing 'Tool' suffix and converting CamelCase to snake_case.def get_name_from_cls(cls) -> str: name = cls.__name__ if name.endswith("Tool"): name = name[:-4] # convert to snake_case name = "".join(["_" + c.lower() if c.isupper() else c for c in name]).lstrip("_") return name