diff --git a/examples/pdf-server/main.ts b/examples/pdf-server/main.ts index bb7b57f42..bc2e5bba7 100644 --- a/examples/pdf-server/main.ts +++ b/examples/pdf-server/main.ts @@ -23,6 +23,7 @@ import { cliLocalFiles, DEFAULT_PDF, allowedLocalDirs, + cliLocalDirs, writeFlags, } from "./server.js"; @@ -162,6 +163,7 @@ async function main() { console.error(`[pdf-server] Registered local file: ${filePath}`); } else if (s.isDirectory()) { allowedLocalDirs.add(filePath); + cliLocalDirs.add(filePath); console.error(`[pdf-server] Registered local directory: ${filePath}`); } } else { diff --git a/examples/pdf-server/server.test.ts b/examples/pdf-server/server.test.ts index 0b2a5eb4d..1fea9f773 100644 --- a/examples/pdf-server/server.test.ts +++ b/examples/pdf-server/server.test.ts @@ -20,6 +20,7 @@ import { startFileWatch, stopFileWatch, cliLocalFiles, + cliLocalDirs, isWritablePath, writeFlags, viewSourcePaths, @@ -786,6 +787,31 @@ describe("createServer useClientRoots option", () => { expect(server.server.oninitialized).toBeFunction(); server.close(); }); + + it("preserves CLI directories when the client returns no roots", async () => { + const cliDir = "/cli/documents"; + const previousClientDir = "/client/documents"; + cliLocalDirs.add(cliDir); + allowedLocalDirs.add(cliDir); + allowedLocalDirs.add(previousClientDir); + + const server = createServer({ useClientRoots: true }); + try { + spyOn(server.server, "getClientCapabilities").mockReturnValue({ + roots: {}, + }); + spyOn(server.server, "listRoots").mockResolvedValue({ roots: [] }); + + await server.server.oninitialized?.(); + + expect(allowedLocalDirs).toEqual(new Set([cliDir])); + } finally { + allowedLocalDirs.delete(cliDir); + allowedLocalDirs.delete(previousClientDir); + cliLocalDirs.delete(cliDir); + await server.close(); + } + }); }); describe("isWritablePath", () => { diff --git a/examples/pdf-server/server.ts b/examples/pdf-server/server.ts index e67cd4731..ca2f1816f 100644 --- a/examples/pdf-server/server.ts +++ b/examples/pdf-server/server.ts @@ -99,6 +99,9 @@ export const allowedLocalDirs = new Set(); */ export const cliLocalFiles = new Set(); +/** Directory paths explicitly passed as CLI args. */ +export const cliLocalDirs = new Set(); + /** * Write-permission flags. Object wrapper (not a bare `let`) so main.ts can * mutate via the exported binding without re-import gymnastics — same @@ -954,6 +957,7 @@ async function refreshRoots(server: Server): Promise { try { const { roots } = await server.listRoots(); allowedLocalDirs.clear(); + for (const dir of cliLocalDirs) allowedLocalDirs.add(dir); for (const root of roots) { if (isFileUrl(root.uri)) { const dir = fileUrlToPath(root.uri); @@ -1275,8 +1279,8 @@ export function createServer(options: CreateServerOptions = {}): McpServer { if (useClientRoots) { // Fetch roots on initialization and subscribe to changes - server.server.oninitialized = () => { - refreshRoots(server.server); + server.server.oninitialized = async () => { + await refreshRoots(server.server); }; server.server.setNotificationHandler( "notifications/roots/list_changed",