Skip to content

Commit a8e88d8

Browse files
committed
pdf-server: list_pdfs walks directory roots for *.pdf files
Recurse into allowedLocalDirs (depth ≤ 8, ≤ 500 files) and enumerate actual .pdf files as file:// URLs alongside the explicitly-registered ones. Skip dotfiles/dirs and node_modules. Dedup (a file may be both explicitly registered and under a walked directory). Report truncation.
1 parent 30207bd commit a8e88d8

1 file changed

Lines changed: 50 additions & 11 deletions

File tree

examples/pdf-server/server.ts

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,23 +1174,61 @@ export function createServer(options: CreateServerOptions = {}): McpServer {
11741174
"List available PDFs that can be displayed",
11751175
{},
11761176
async (): Promise<CallToolResult> => {
1177-
const pdfs: Array<{ url: string; type: "local" | "remote" }> = [];
1177+
const seen = new Set<string>();
1178+
const localFiles: string[] = [];
1179+
const addLocal = (filePath: string) => {
1180+
const url = pathToFileUrl(filePath);
1181+
if (seen.has(url)) return;
1182+
seen.add(url);
1183+
localFiles.push(url);
1184+
};
11781185

1179-
// Add local files
1180-
for (const filePath of allowedLocalFiles) {
1181-
pdfs.push({ url: pathToFileUrl(filePath), type: "local" });
1182-
}
1186+
// Explicitly registered files (CLI args + file roots)
1187+
for (const filePath of allowedLocalFiles) addLocal(filePath);
1188+
1189+
// Walk directory roots for *.pdf files
1190+
const WALK_MAX_DEPTH = 8;
1191+
const WALK_MAX_FILES = 500;
1192+
let truncated = false;
1193+
const walk = async (dir: string, depth: number): Promise<void> => {
1194+
if (depth > WALK_MAX_DEPTH || localFiles.length >= WALK_MAX_FILES) {
1195+
truncated ||= localFiles.length >= WALK_MAX_FILES;
1196+
return;
1197+
}
1198+
let entries;
1199+
try {
1200+
entries = await fs.promises.readdir(dir, { withFileTypes: true });
1201+
} catch {
1202+
return; // unreadable — skip silently
1203+
}
1204+
for (const e of entries) {
1205+
if (localFiles.length >= WALK_MAX_FILES) {
1206+
truncated = true;
1207+
return;
1208+
}
1209+
// Skip dotfiles/dirs and common noise
1210+
if (e.name.startsWith(".") || e.name === "node_modules") continue;
1211+
const full = path.join(dir, e.name);
1212+
if (e.isDirectory()) {
1213+
await walk(full, depth + 1);
1214+
} else if (e.isFile() && /\.pdf$/i.test(e.name)) {
1215+
addLocal(full);
1216+
}
1217+
}
1218+
};
1219+
for (const dir of allowedLocalDirs) await walk(dir, 0);
11831220

11841221
// Build text
11851222
const parts: string[] = [];
1186-
if (pdfs.length > 0) {
1187-
parts.push(
1188-
`Available PDFs:\n${pdfs.map((p) => `- ${p.url} (${p.type})`).join("\n")}`,
1189-
);
1223+
if (localFiles.length > 0) {
1224+
const header = truncated
1225+
? `Available PDFs (showing first ${WALK_MAX_FILES}):`
1226+
: `Available PDFs:`;
1227+
parts.push(`${header}\n${localFiles.map((u) => `- ${u}`).join("\n")}`);
11901228
}
11911229
if (allowedLocalDirs.size > 0) {
11921230
parts.push(
1193-
`Allowed local directories (from client roots):\n${[...allowedLocalDirs].map((d) => `- ${d}`).join("\n")}\nAny PDF file under these directories can be displayed.`,
1231+
`Allowed local directories:\n${[...allowedLocalDirs].map((d) => `- ${d}`).join("\n")}\nAny PDF file under these directories can be displayed.`,
11941232
);
11951233
}
11961234
parts.push(
@@ -1200,8 +1238,9 @@ export function createServer(options: CreateServerOptions = {}): McpServer {
12001238
return {
12011239
content: [{ type: "text", text: parts.join("\n\n") }],
12021240
structuredContent: {
1203-
localFiles: pdfs.filter((p) => p.type === "local").map((p) => p.url),
1241+
localFiles,
12041242
allowedDirectories: [...allowedLocalDirs],
1243+
truncated,
12051244
},
12061245
};
12071246
},

0 commit comments

Comments
 (0)