angular/angular-cli · error · Error

Access denied: path '${path}' is outside allowed roots.

Error message

Access denied: path '${path}' is outside allowed roots.

What it means

The Angular CLI MCP server sandbox every filesystem access (stat, existsSync, readFile, glob) through checkPath, which resolves symlinks and requires the real path to fall under one of the configured roots. This error is thrown when the requested path, after canonicalization, is not inside any allowed root, preventing the MCP server from reading files outside the workspace.

Source

Thrown at packages/angular/cli/src/commands/mcp/host.ts:339

              // Reached filesystem root
              throw err;
            }
            current = parent;
          }
        }
      } else {
        throw e;
      }
    }

    const isAllowed = roots.some((root) => {
      const rel = relative(root, realPath);

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

    if (!isAllowed) {
      throw new Error(`Access denied: path '${path}' is outside allowed roots.`);
    }
  }

  return {
    ...baseHost,
    setRoots(newRoots: string[]) {
      roots = newRoots.length > 0 ? resolveRoots(newRoots) : defaultRoots;
    },
    stat(path: string) {
      checkPath(path);

      return baseHost.stat(path);
    },
    existsSync(path: string) {
      checkPath(path);

      return baseHost.existsSync(path);
    },

View on GitHub (pinned to bb72145f9a)

Solutions

  1. Move the requested path inside one of the allowed roots (workspace directory) and retry.
  2. Have the MCP client call setRoots with the directory containing the desired path before accessing it.
  3. Resolve or remove symlinks that point outside the allowed roots.
  4. Use MCP tools like list_projects to discover workspaces that are actually accessible.

Example fix

// before
await host.readFile('/etc/hosts', 'utf8');
// after
host.setRoots(['/home/dev/my-app']);
await host.readFile('/home/dev/my-app/src/main.ts', 'utf8');
Defensive patterns

Strategy: validation

Validate before calling

import { relative, isAbsolute, resolve, realpathSync } from 'node:path';
function isInsideRoots(path: string, roots: string[]): boolean {
  const realPath = realpathSync(resolve(path));
  return roots.some((root) => {
    const rel = relative(resolve(root), realPath);
    return !rel.startsWith('..') && !isAbsolute(rel);
  });
}
if (!isInsideRoots(targetPath, allowedRoots)) throw new Error('path outside roots');

Type guard

function isWithinRoot(p: string, roots: string[]): p is string {
  const rel = relative(resolve(roots[0] ?? '/'), resolve(p));
  return !rel.startsWith('..') && !isAbsolute(rel);
}

Try / catch

try {
  await host.readFile(path, 'utf8');
} catch (e) {
  if ((e as Error).message.includes('is outside allowed roots')) {
    host.setRoots([resolve(path).split('/').slice(0, 3).join('/')]);
    await host.readFile(path, 'utf8');
  } else throw e;
}

Prevention

When it happens

Trigger: Calling any MCP host operation (stat/existsSync/readFile/glob/executeNgCommand/startNgProcess) with an absolute path outside the roots set via setRoots, a path containing '..' segments that resolves outside the roots, or a symlink pointing outside the allowed roots.

Common situations: MCP clients hardcoding absolute paths to files in another project; referring to a shared directory outside the workspace; symlinked node_modules or home-directory configs resolving outside roots; stale roots after the user switched workspaces without calling setRoots.

Understand the failure class

Related errors


AI-assisted analysis of angular/angular-cli@bb72145f9a (2026-08-30). Data as JSON: /api/errors/77972a5f7f48c5f6. Report an issue: GitHub.