search
Recursively search for a text pattern in files within a specified directory. Define the pattern and path to locate matching files.
Instructions
Recursively search for pattern in files under path.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| pattern | Yes | ||
| path | No | . | |
| max_results | No |
Implementation Reference
- mcpforge/builtin/filesystem.py:62-90 (handler)The main handler for the 'search' tool. Uses os.walk to recursively search files under a sandboxed path for lines containing the given pattern, up to max_results.
@tool def search(self, pattern: str, path: str = ".", max_results: int = 100) -> list[dict]: """Recursively search for `pattern` in files under `path`.""" if not pattern: raise ValueError("pattern must be non-empty") root = self._safe_join(path) if not root.exists(): raise FileNotFoundError(f"no such path: {path}") if not root.is_dir(): raise NotADirectoryError(f"not a directory: {path}") results: list[dict] = [] for dirpath, _dirs, files in os.walk(root): for fname in files: fpath = Path(dirpath) / fname try: with fpath.open("r", encoding="utf-8", errors="ignore") as f: for lineno, line in enumerate(f, start=1): if pattern in line: results.append({ "file": str(fpath.relative_to(self.root)), "line": lineno, "text": line.rstrip("\n"), }) if len(results) >= max_results: return results except (OSError, UnicodeDecodeError): continue return results - mcpforge/builtin/filesystem.py:63-90 (schema)The function signature defines the input schema: pattern (str, required), path (str, default '.'), max_results (int, default 100). Return type list[dict].
def search(self, pattern: str, path: str = ".", max_results: int = 100) -> list[dict]: """Recursively search for `pattern` in files under `path`.""" if not pattern: raise ValueError("pattern must be non-empty") root = self._safe_join(path) if not root.exists(): raise FileNotFoundError(f"no such path: {path}") if not root.is_dir(): raise NotADirectoryError(f"not a directory: {path}") results: list[dict] = [] for dirpath, _dirs, files in os.walk(root): for fname in files: fpath = Path(dirpath) / fname try: with fpath.open("r", encoding="utf-8", errors="ignore") as f: for lineno, line in enumerate(f, start=1): if pattern in line: results.append({ "file": str(fpath.relative_to(self.root)), "line": lineno, "text": line.rstrip("\n"), }) if len(results) >= max_results: return results except (OSError, UnicodeDecodeError): continue return results - mcpforge/builtin/filesystem.py:62-64 (registration)The @tool decorator (from mcpforge.decorator) marks this method as an MCP tool, which is then collected by the @serve decorator on the class.
@tool def search(self, pattern: str, path: str = ".", max_results: int = 100) -> list[dict]: """Recursively search for `pattern` in files under `path`.""" - mcpforge/builtin/filesystem.py:10-14 (registration)The @serve decorator registers the FilesystemTools class as an MCP server, which collects all @tool-decorated methods (including 'search') during class creation.
@serve( name="filesystem", version="0.1.0", description="Sandboxed filesystem read/list/search tools", ) - mcpforge/builtin/filesystem.py:21-30 (helper)Helper used by search() to safely resolve paths within the sandbox root, preventing path traversal attacks.
def _safe_join(self, path: str) -> Path: """Resolve `path` under self.root and verify it stays inside.""" candidate = (self.root / path).resolve() try: candidate.relative_to(self.root) except ValueError: raise ValueError( f"path escapes sandbox root: {path!r} resolves outside {self.root}" ) return candidate