You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
v1.7.0 refreshRoots() clears allowedLocalDirs when host advertises roots capability but returns empty list — wipes CLI-arg directories #644
Version: 1.7.0 (pdf-server-mcp via Claude Desktop extension manager) OS: Windows 11 / Claude Desktop 1.4758.0 Severity: Functional regression — local PDFs cannot be opened via display_pdf
What happens
list_pdfs returns:
{"localFiles": [], "allowedDirectories": [], "truncated": false}
Even when:Directories are passed as positional args via the manifest's mcp_config.args(which parseArgs() should register at line ~34372 in dist/index.js)A cowork directory mount has been approved by the userdisplay_pdf against any local file path is rejected with "Local file not in allowed list".Root causeIn dist/server.js refreshRoots():async function refreshRoots(server) {if (!server.getClientCapabilities()?.roots)return;try {const { roots } = await server.listRoots();allowedLocalDirs.clear(); // <-- this linefor (const root of roots) { ... }}}When --stdio mode runs, useClientRoots: true is hardcoded, sorefreshRoots() always fires on init via oninitialized. Claude Desktop1.4758.0 advertises the roots capability but server.listRoots() returnsan empty array (cowork mounts not propagating as MCP roots). Theunconditional clear() then wipes any directories registered via CLI argsin main() lines 34372/34376.ReproductionInstall pdf-server-mcp 1.7.0 in Claude Desktop on Windows.In manifest.json (or via a launch wrapper) add positional directorypaths to mcp_config.args after --stdio, e.g.
["${__dirname}/dist/index.js", "--stdio", "C:\\some\\dir"].Restart Claude Desktop.Call list_pdfs. Expected: allowedDirectories includes C:\some\dir.Actual: allowedDirectories: [].Suggested fixIn refreshRoots(), either (a) only clear when the new roots list isnon-empty, or (b) drop the clear entirely and rely on Set.add()'s dedupbehavior. Option (b) is safer because cowork-side root removals wouldstill need a separate mechanism (e.g., a roots/list_changed notificationthat explicitly signals removal, not the current implicitclear-and-replace).Workaround applied locallyCommented out the allowedLocalDirs.clear() line. CLI-arg directoriesnow survive the refresh and display_pdf works on local paths again.This loses the ability for the host to revoke a previously-granted rootvia the empty-list path, but in practice cowork mounts are additive inour usage pattern.
The unconditional clear() looks risky here. If Claude advertises roots but returns an empty list, preserving the pre-approved CLI-arg directories as a fallback would avoid turning a capability mismatch into a regression. I’d gate the clear on roots.length > 0, or merge roots into the existing allowlist instead of replacing it wholesale. That keeps local-file access stable when the host and server disagree about root propagation, and it still lets explicit user-approved roots win when they are present.
Confirming this on pdf-server 2.0.0 (Claude Desktop extension, Claude Desktop 2.7032.0, Windows 10 22H2). Directories from the extension settings are registered at startup (Registered local directory: I:\, ... C:\00), but after initialization display_pdf fails with Local file not in allowed list and Allowed dirs: is empty. Commenting out allowedLocalDirs.clear(); in refreshRoots() fixes it, as described above.
There is also a second problem on Windows that makes the roots path fail even when the host sends non-empty roots. fileUrlToPath() only strips the scheme:
refreshRoots() catches the statSync error and skips the root without logging anything, so on Windows every file:// root is dropped. Together with the unconditional clear(), the allowed list always ends up empty. From the logs I couldn't tell whether the host sends an empty list or Windows file URLs, but the result is the same either way. The same helper is also used for file:// arguments elsewhere in server.ts (e.g. display_pdf and save paths), so going by the code those would resolve the same way on Windows.
Both issues are still present in examples/pdf-server/server.ts on main.
Possible fix: use Node's url.fileURLToPath() for file:// URLs (it's already imported in server.ts for SERVER_FILE). It handles drive letters, percent-encoding and UNC paths:
Also, only replace the CLI-arg directories when the new roots list yields at least one valid directory (option (a) in the issue), and log roots that are skipped.
Activity
sophymarine commentedon Apr 27, 2026
The unconditional clear() looks risky here. If Claude advertises roots but returns an empty list, preserving the pre-approved CLI-arg directories as a fallback would avoid turning a capability mismatch into a regression. I’d gate the clear on roots.length > 0, or merge roots into the existing allowlist instead of replacing it wholesale. That keeps local-file access stable when the host and server disagree about root propagation, and it still lets explicit user-approved roots win when they are present.
uheira commentedon Sep 24, 2026
Confirming this on pdf-server 2.0.0 (Claude Desktop extension, Claude Desktop 2.7032.0, Windows 10 22H2). Directories from the extension settings are registered at startup (
Registered local directory: I:\,... C:\00), but after initializationdisplay_pdffails withLocal file not in allowed listandAllowed dirs:is empty. Commenting outallowedLocalDirs.clear();inrefreshRoots()fixes it, as described above.There is also a second problem on Windows that makes the roots path fail even when the host sends non-empty roots.
fileUrlToPath()only strips the scheme:For a Windows file URL this leaves a leading slash before the drive letter, and
path.resolve()then prepends the current drive:refreshRoots()catches thestatSyncerror and skips the root without logging anything, so on Windows everyfile://root is dropped. Together with the unconditionalclear(), the allowed list always ends up empty. From the logs I couldn't tell whether the host sends an empty list or Windows file URLs, but the result is the same either way. The same helper is also used forfile://arguments elsewhere inserver.ts(e.g.display_pdfand save paths), so going by the code those would resolve the same way on Windows.Both issues are still present in
examples/pdf-server/server.tsonmain.Possible fix: use Node's
url.fileURLToPath()forfile://URLs (it's already imported inserver.tsforSERVER_FILE). It handles drive letters, percent-encoding and UNC paths:Also, only replace the CLI-arg directories when the new roots list yields at least one valid directory (option (a) in the issue), and log roots that are skipped.