Skip to content

Filesystem MCP: Server should wait inital roots to be loaded before handling tool calls #3204

@Shellishack

Description

@Shellishack

Is your feature request related to a problem? Please describe.
When intial roots are used in MCP client to connect to a file system server, tool calls can happen before initial roots are loaded. This causes the server to complain about:

Updated allowed directories from MCP roots: 1 valid directories
Access denied - path outside allowed directories: C:\temp not in

Steps to replicate:
(I also documented how I discovered this issue here: #3174 (comment))

Client code:

  async function list() {
    const transport = new StdioClientTransport({
      command: "node",
      args: ["../mcp-servers/src/filesystem/dist/index.js"],
    });

    if (!transport) {
      throw new Error("Transport not available");
    }

    // Initialize the client
    const client = new MCPClient(
      {
        name: "test-fs-client",
        version: "1.0.0",
      },
      {
        capabilities: {
          roots: {
            listChanged: true,
          },
        },
      }
    );

    client.setRequestHandler(ListRootsRequestSchema, async (notification) => {
      console.log("Received roots/list_roots notification:", notification);
      return {
        roots: [
          {
            uri: "file:///C:/temp",
            name: "My Project",
          },
        ],
      };
    });

    await client.connect(transport, {});

    const result = await client?.callTool({
      name: "list_directory",
      arguments: {
        path: "C:/temp",
      },
    });

    await client.close();

    // @ts-expect-error ignore type
    const stdout = result?.content[0]?.text;
    // @ts-expect-error ignore type
    const stderr = result?.content[1]?.text;
    let output = `\
stdout:
\`\`\`
${stdout}
\`\`\`
`;
    if (stderr) {
      output += `\
stderr:
\`\`\`
${stderr}
\`\`\`
`;
    }
    return output;
  }


const result = await list();
console.log("Command output:", result);

Terminal output

Usage: mcp-server-filesystem [allowed-directory] [additional-directories...]
Note: Allowed directories can be provided via:
  1. Command-line arguments (shown above)
  2. MCP roots protocol (if client supports it)
At least one directory must be provided by EITHER method for the server to operate.
Secure MCP Filesystem Server running on stdio
Started without allowed directories - waiting for client to provide roots via MCP protocol
Received roots/list_roots notification: { method: 'roots/list' }
Updated allowed directories from MCP roots: 1 valid directories
Command output: stdout:
Access denied - path outside allowed directories: C:\temp not in

Describe the solution you'd like

  • Option 1: The Roots Protocal should include a notification event after initial roots are loaded/updated, then the client can know when to handle subsequent operations.
    async function updateAllowedDirectoriesFromRoots(requestedRoots: Root[]) {
    const validatedRootDirs = await getValidRootDirectories(requestedRoots);
    if (validatedRootDirs.length > 0) {
    allowedDirectories = [...validatedRootDirs];
    setAllowedDirectories(allowedDirectories); // Update the global state in lib.ts
    console.error(`Updated allowed directories from MCP roots: ${validatedRootDirs.length} valid directories`);
    } else {
    console.error("No valid root directories provided by client");
    }
    }
  • Option 2: The file server can queue up incoming requests until initial roots are loaded, so that client developers do need to handle this explicitly.
  • Option 3: Or, it should mention in https://modelcontextprotocol.io/specification/2025-11-25/client/roots#implementation-guidelines that the client might need to wait a little for server to load initial roots.

Describe alternatives you've considered
This is a temporary workaround that I am not a fan of. But it works for now by waiting a little before client calls the server.

    client.setRequestHandler(ListRootsRequestSchema, async (notification) => {
      console.log("Received roots/list_roots notification:", notification);
      return {
        roots: [
          {
            uri: "file:///C:/temp",
            name: "My Project",
          },
        ],
      };
    });

    await client.connect(transport, {});

    // wait 1 second 
    await new Promise((resolve) => setTimeout(resolve, 1000));

    const result = await client?.callTool({
      name: "list_directory",
      arguments: {
        path: "C:/temp",
      },
    });

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions