filesystem: search_files does not expand non-wildcard excludePatterns like directory_tree does
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 72/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- node.js, typescript
Research direction
Read searchFilesWithValidation in src/filesystem/lib.ts alongside buildTree in src/filesystem/server.ts to compare how they match excludePatterns. Add coverage in src/filesystem/__tests__/lib.test.ts and src/filesystem/__tests__/server-tools.test.ts for bare exclusions at top-level and nested directories. Done means search_files skips matching directory subtrees and its behavior matches directory_tree.
Written by the indexing model from the issue text.
Description
Summary
While auditing src/filesystem on v2.0.0 (origin/main) following the #4857 spec refactor, I found a behavioral mismatch in how excludePatterns is evaluated between directory_tree (src/filesystem/server.ts) and search_files (src/filesystem/lib.ts).
When an LLM or user passes a bare directory or file name in excludePatterns (for example, ["node_modules", ".git"]), directory_tree excludes both the directory and its entire subtree, whereas search_files still traverses into the excluded directory and returns all files inside it.
Server & Environment
- Server:
filesystem(@modelcontextprotocol/server-filesystemv2.0.0/origin/main) - Files:
src/filesystem/server.ts(buildTree) vs.src/filesystem/lib.ts(searchFilesWithValidation) - Protocol era / transport: Both
2026-07-28and2025-eraoverstdio/ in-memory client transport
Reproduction / Root Cause in v2/main
- In
directory_tree(src/filesystem/server.ts, lines 515–529),buildTreechecks whether an exclusion pattern contains*. If it does not, it automatically expands the pattern to match nested paths and subtrees (**/${pattern}and**/${pattern}/**), and skips recursing into the excluded directory:
const shouldExclude = excludePatterns.some((pattern) => {
if (pattern.includes("*")) {
return minimatch(relativePath, pattern, { dot: true });
}
// For files: match exact name or as part of path
// For directories: match as directory path
return (
minimatch(relativePath, pattern, { dot: true }) ||
minimatch(relativePath, `**/${pattern}`, { dot: true }) ||
minimatch(relativePath, `**/${pattern}/**`, { dot: true })
);
});
if (shouldExclude) continue;
- In
search_files(src/filesystem/lib.ts, lines 397–413),searchFilesWithValidationevaluatesminimatch(relativePath, excludePattern, { dot: true })directly without the non-wildcard expansion:
const relativePath = path.relative(rootPath, fullPath);
const shouldExclude = excludePatterns.some((excludePattern) =>
minimatch(relativePath, excludePattern, { dot: true }),
);
if (shouldExclude) continue;
What happens when a caller passes excludePatterns: ["node_modules"] or ["sub"]:
- At
node_modules(top-level):relativePathis"node_modules".minimatch("node_modules", "node_modules")istrue, soif (shouldExclude) continueskips recursing into top-levelnode_modules! - At
packages/app/node_modules(nested directory):relativePathis"packages/app/node_modules".minimatch("packages/app/node_modules", "node_modules")isfalse!search_filesenterspackages/app/node_modulesand traverses every file inside it. - When
excludePatterns: ["node_modules/*"]or["**/node_modules"](without trailing/**) is passed: Even if a pattern matches files inside a directory,search_filesstill wastes I/O recursing unless the directory entry itself matchesshouldExclude.
Expected vs. Actual Behavior
- Expected: Passing
excludePatterns: ["node_modules", ".git"](or a bare filename/directory name without*) tosearch_filesshould behave identically todirectory_tree: any nested directory or file matchingpattern,**/${pattern}, or**/${pattern}/**is excluded and its directory subtree is not traversed. - Actual:
directory_treeexcludes nestedpackages/app/node_modules, whilesearch_filestraverses intopackages/app/node_modulesand returns matches from inside it unless the caller explicitly writes**/node_modules/**.
Prompt to Reproduce Fix in the Agentic Workflow
Per CONTRIBUTING.md, here is the exact prompt to fix and test this in the factory workflow:
Unify `excludePatterns` matching between `directory_tree` (`src/filesystem/server.ts`) and `search_files` (`src/filesystem/lib.ts`):
1. Extract a shared helper (or update `searchFilesWithValidation` in `src/filesystem/lib.ts` to match `buildTree` in `src/filesystem/server.ts`) so that when an entry in `excludePatterns` does not contain `*`, it matches `minimatch(relativePath, pattern, { dot: true }) || minimatch(relativePath, `**/${pattern}`, { dot: true }) || minimatch(relativePath, `**/${pattern}/**`, { dot: true })`.
2. Add a protocol-level test in `src/filesystem/__tests__/server-tools.test.ts` (and unit test in `src/filesystem/__tests__/lib.test.ts`) verifying that `search_files` with a bare name in `excludePatterns` (e.g. `excludePatterns: ["ignored_dir"]`) excludes both top-level `ignored_dir/file.txt` and nested `nested/ignored_dir/file.txt`, matching `directory_tree`'s behavior.
3. Run `npm run format` and `npm run local:gate`.
- Dominant language
- TypeScript
- Stars
- 91.4k
- Forks
- 11.8k
- Avg merge
- 7h 36m
- Merged PRs (30d)
- 126
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from modelcontextprotocol/servers
-
bug v2
Difficulty 2/5 1-3 hours Newbie friendliness 79/100
modelcontextprotocol/servers#5215 ·
Maintainers usually reply within 1 day
-
documentation server-git server-time v2
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
modelcontextprotocol/servers#5213 · 2 comments ·
Maintainers usually reply within 1 day
-
documentation v2
Difficulty 1/5 1-3 hours Newbie friendliness 78/100
modelcontextprotocol/servers#5212 · 1 comment ·
Maintainers usually reply within 1 day
-
documentation v2
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
modelcontextprotocol/servers#5211 · 1 comment ·
Maintainers usually reply within 1 day
-
chore server-everything v2
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
modelcontextprotocol/servers#5210 · 1 comment ·
Maintainers usually reply within 1 day
All issues in modelcontextprotocol/servers
Similar issues
-
ready-for-agent
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
xavxyz/bei-shan-lin-shui#57 ·
Maintainers usually reply within 1 day
-
[BUG] rag: sentence content can cross the reported sentence boundariesPossibly taken @LJK-ikun claimed this today. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
bug needs-triage
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Maintainers usually reply within 1 day
-
README: the Resource URI Schemas example's file://documents/* pattern only matches that literal URIOpen
Difficulty 1/5 Under an hour Newbie friendliness 88/100
platformatic/mcp#361 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
siyuan-note/siyuan#20799 · 7 comments ·
Maintainers usually reply within 1 day