跳转至

文件搜索工具

FileSearch 工具提供 Agent 工作流中常用的文件发现和内容搜索功能:基于 glob 的文件查找、正则表达式内容搜索(grep)和目录树显示。

类概述

  • FileSearch - 三个静态方法,覆盖最常见的文件发现操作:
    • glob() - 按模式查找文件
    • grep() - 使用正则表达式搜索文件内容
    • tree() - 显示目录结构

使用方法

Glob - 按模式查找文件

from toolregistry_hub import FileSearch

# 递归查找所有 Python 文件
files = FileSearch.glob("**/*.py", root="/path/to/project")

# 仅在根目录中非递归搜索
files = FileSearch.glob("*.txt", root=".", recursive=False)

结果按修改时间排序(最近的在前),最多返回 1000 条。路径相对于 root

Grep - 搜索文件内容

# 在目录中搜索模式
results = FileSearch.grep("import os", path="/path/to/project")
# [{"file": "main.py", "line_number": 3, "content": "import os"}, ...]

# 使用文件过滤器搜索
results = FileSearch.grep(r"def\s+test_", path=".", file_pattern="*.py")

# 搜索单个文件
results = FileSearch.grep("TODO", path="src/main.py")

# 限制结果数
results = FileSearch.grep(".", path=".", max_results=10)

每个结果是包含 file(相对路径)、line_number(从 1 开始)和 content(去除首尾空白的行)的字典。

Tree - 目录结构

# 基本树形显示
print(FileSearch.tree("/path/to/project"))
# project/
# +-- src/
# |   +-- main.py
# |   +-- utils.py
# +-- tests/
# |   +-- test_main.py
# +-- README.md

# 限制深度并过滤文件
print(FileSearch.tree(".", max_depth=2, file_pattern="*.py"))

# 显示隐藏文件
print(FileSearch.tree(".", show_hidden=True))

参数

glob()

参数 类型 默认值 描述
pattern str 必填 Glob 模式(如 "**/*.py"
root str "." 搜索的根目录
recursive bool True ** 是否匹配子目录

grep()

参数 类型 默认值 描述
pattern str 必填 要搜索的正则表达式
path str "." 要搜索的文件或目录
recursive bool True 是否搜索子目录
file_pattern str \| None None 文件过滤 glob(如 "*.py"
max_results int 50 最大结果数(上限 200)

tree()

参数 类型 默认值 描述
path str "." 根目录
max_depth int 3 最大显示深度
show_hidden bool False 是否显示隐藏文件/目录
file_pattern str \| None None 文件过滤 glob

安全上限

  • Glob:最多 1000 条结果
  • Grep:最多 200 条结果
  • Tree:最多 2000 个条目

MCP 服务端点

POST /tools/fs/file_search/glob
POST /tools/fs/file_search/grep
POST /tools/fs/file_search/tree

API 参考

toolregistry_hub.file_search.FileSearch

File and content search tools.

glob staticmethod

glob(pattern: str, root: str = '.', recursive: bool = True) -> list[str]

Find files matching a glob pattern.

Parameters:

Name Type Description Default
pattern str

Glob pattern (e.g. "**/*.py", "src/**/*.ts").

required
root str

Root directory to search from. Defaults to current directory.

'.'
recursive bool

Whether ** matches subdirectories. Defaults to True.

True

Returns:

Type Description
list[str]

List of matching file paths relative to root, sorted by

list[str]

modification time (most recent first). Capped at 1000 results.

Raises:

Type Description
FileNotFoundError

If root does not exist or is not a directory.

Source code in toolregistry_hub/file_search.py
@staticmethod
def glob(
    pattern: str,
    root: str = ".",
    recursive: bool = True,
) -> list[str]:
    """Find files matching a glob pattern.

    Args:
        pattern: Glob pattern (e.g. ``"**/*.py"``, ``"src/**/*.ts"``).
        root: Root directory to search from. Defaults to current directory.
        recursive: Whether ``**`` matches subdirectories. Defaults to True.

    Returns:
        List of matching file paths relative to *root*, sorted by
        modification time (most recent first).  Capped at 1000 results.

    Raises:
        FileNotFoundError: If *root* does not exist or is not a directory.
    """
    root_path = Path(root).resolve()
    if not root_path.is_dir():
        raise FileNotFoundError(
            f"Root is not a directory or does not exist: {root}"
        )

    if recursive:
        matches = list(root_path.glob(pattern))
    else:
        # Non-recursive: only match in the root directory itself
        matches = [p for p in root_path.glob(pattern) if p.parent == root_path]

    # Sort by modification time, most recent first
    matches.sort(key=lambda p: p.stat().st_mtime, reverse=True)

    # Cap results
    matches = matches[:_MAX_GLOB_RESULTS]

    return [str(p.relative_to(root_path)) for p in matches]

grep staticmethod

grep(pattern: str, path: str = '.', recursive: bool = True, file_pattern: str | None = None, max_results: int = 50) -> list[dict]

Search file contents using regex.

Parameters:

Name Type Description Default
pattern str

Regex pattern to search for.

required
path str

File or directory to search in. Defaults to current directory.

'.'
recursive bool

Whether to search subdirectories. Defaults to True.

True
file_pattern str | None

Optional glob to filter files (e.g. "*.py").

None
max_results int

Maximum number of match results to return. Clamped to an internal cap of 200.

50

Returns:

Type Description
list[dict]

List of dicts, each with keys:

list[dict]
  • file (str): path relative to search root
list[dict]
  • line_number (int): 1-based line number
list[dict]
  • content (str): the matching line (stripped)

Raises:

Type Description
FileNotFoundError

If path does not exist.

error

If pattern is not a valid regex.

Source code in toolregistry_hub/file_search.py
@staticmethod
def grep(
    pattern: str,
    path: str = ".",
    recursive: bool = True,
    file_pattern: str | None = None,
    max_results: int = 50,
) -> list[dict]:
    """Search file contents using regex.

    Args:
        pattern: Regex pattern to search for.
        path: File or directory to search in. Defaults to current directory.
        recursive: Whether to search subdirectories. Defaults to True.
        file_pattern: Optional glob to filter files (e.g. ``"*.py"``).
        max_results: Maximum number of match results to return.
            Clamped to an internal cap of 200.

    Returns:
        List of dicts, each with keys:

        - ``file`` (str): path relative to search root
        - ``line_number`` (int): 1-based line number
        - ``content`` (str): the matching line (stripped)

    Raises:
        FileNotFoundError: If *path* does not exist.
        re.error: If *pattern* is not a valid regex.
    """
    target = Path(path).resolve()
    if not target.exists():
        raise FileNotFoundError(f"Path does not exist: {path}")

    regex = re.compile(pattern)
    effective_max = min(max_results, _MAX_GREP_RESULTS)
    results: list[dict] = []

    if target.is_file():
        FileSearch._grep_file(target, regex, target.parent, results, effective_max)
        return results

    # Directory search
    files = target.rglob("*") if recursive else target.iterdir()

    for filepath in sorted(files):
        if len(results) >= effective_max:
            break
        if not filepath.is_file():
            continue
        if file_pattern and not fnmatch.fnmatch(filepath.name, file_pattern):
            continue
        FileSearch._grep_file(filepath, regex, target, results, effective_max)

    return results

tree staticmethod

tree(path: str = '.', max_depth: int = 3, show_hidden: bool = False, file_pattern: str | None = None) -> str

Display directory tree structure.

Parameters:

Name Type Description Default
path str

Root directory. Defaults to current directory.

'.'
max_depth int

Maximum depth to display. Defaults to 3.

3
show_hidden bool

Whether to show hidden files/directories.

False
file_pattern str | None

Optional glob to filter displayed files.

None

Returns:

Type Description
str

Tree-formatted string representation of the directory.

Raises:

Type Description
FileNotFoundError

If path does not exist or is not a directory.

ValueError

If max_depth is less than 1.

Source code in toolregistry_hub/file_search.py
@staticmethod
def tree(
    path: str = ".",
    max_depth: int = 3,
    show_hidden: bool = False,
    file_pattern: str | None = None,
) -> str:
    """Display directory tree structure.

    Args:
        path: Root directory. Defaults to current directory.
        max_depth: Maximum depth to display. Defaults to 3.
        show_hidden: Whether to show hidden files/directories.
        file_pattern: Optional glob to filter displayed files.

    Returns:
        Tree-formatted string representation of the directory.

    Raises:
        FileNotFoundError: If *path* does not exist or is not a directory.
        ValueError: If *max_depth* is less than 1.
    """
    root = Path(path).resolve()
    if not root.is_dir():
        raise FileNotFoundError(
            f"Path is not a directory or does not exist: {path}"
        )
    if max_depth < 1:
        raise ValueError("max_depth must be 1 or greater.")

    lines: list[str] = [root.name + "/"]
    count = [0]  # mutable counter for the nested function

    FileSearch._build_tree(
        root, "", max_depth, 1, show_hidden, file_pattern, lines, count
    )

    if count[0] >= _MAX_TREE_ENTRIES:
        lines.append(f"\n... truncated at {_MAX_TREE_ENTRIES} entries")

    return "\n".join(lines)