Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/angular/cli/src/commands/mcp/tools/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,31 @@ async function processConfigFile(
}
}

/**
* Deduplicates overlapping search roots (e.g., if one is a child of another).
* Sorting by length ensures parent directories are processed before children.
* @param roots A list of normalized absolute paths used as search roots.
* @returns A deduplicated list of search roots.
*/
function deduplicateSearchRoots(roots: string[]): string[] {
const sortedRoots = [...roots].sort((a, b) => a.length - b.length);
const deduplicated: string[] = [];

for (const root of sortedRoots) {
const isSubdirectory = deduplicated.some((existing) => {
const rel = relative(existing, root);

return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
});

if (!isSubdirectory) {
deduplicated.push(root);
}
}

return deduplicated;
}

async function createListProjectsHandler({ server }: McpToolContext) {
return async () => {
const workspaces: WorkspaceData[] = [];
Expand All @@ -562,6 +587,8 @@ async function createListProjectsHandler({ server }: McpToolContext) {
searchRoots = [process.cwd()];
}

searchRoots = deduplicateSearchRoots(searchRoots);

// Pre-resolve allowed roots to handle their own symlinks or normalizations.
// We ignore failures here; if a root is broken, we simply won't match against it.
const realAllowedRoots = searchRoots
Expand Down
Loading