(options: CreateServerOptions = {})
| 1271 | let cachedAppHtml: string | undefined; |
| 1272 | |
| 1273 | export function createServer(options: CreateServerOptions = {}): McpServer { |
| 1274 | const { enableInteract = false, useClientRoots = false } = options; |
| 1275 | const debug = options.debug ?? false; |
| 1276 | const disableInteract = !enableInteract; |
| 1277 | const server = new McpServer({ name: "PDF Server", version: "2.0.0" }); |
| 1278 | |
| 1279 | if (useClientRoots) { |
| 1280 | // Fetch roots on initialization and subscribe to changes |
| 1281 | server.server.oninitialized = () => { |
| 1282 | refreshRoots(server.server); |
| 1283 | }; |
| 1284 | server.server.setNotificationHandler( |
| 1285 | RootsListChangedNotificationSchema, |
| 1286 | async () => { |
| 1287 | await refreshRoots(server.server); |
| 1288 | }, |
| 1289 | ); |
| 1290 | } |
| 1291 | |
| 1292 | const { readPdfRange } = sharedPdfCache; |
| 1293 | |
| 1294 | // Tool: list_pdfs - List available PDFs |
| 1295 | server.tool( |
| 1296 | "list_pdfs", |
| 1297 | "List available PDFs that can be displayed", |
| 1298 | {}, |
| 1299 | async (): Promise<CallToolResult> => { |
| 1300 | const seen = new Set<string>(); |
| 1301 | const localFiles: string[] = []; |
| 1302 | const addLocal = (filePath: string) => { |
| 1303 | const url = pathToFileUrl(filePath); |
| 1304 | if (seen.has(url)) return; |
| 1305 | seen.add(url); |
| 1306 | localFiles.push(url); |
| 1307 | }; |
| 1308 | |
| 1309 | // Explicitly registered files (CLI args + file roots) |
| 1310 | for (const filePath of allowedLocalFiles) addLocal(filePath); |
| 1311 | |
| 1312 | // Walk directory roots for *.pdf files |
| 1313 | const WALK_MAX_DEPTH = 8; |
| 1314 | const WALK_MAX_FILES = 500; |
| 1315 | let truncated = false; |
| 1316 | const walk = async (dir: string, depth: number): Promise<void> => { |
| 1317 | if (depth > WALK_MAX_DEPTH || localFiles.length >= WALK_MAX_FILES) { |
| 1318 | truncated ||= localFiles.length >= WALK_MAX_FILES; |
| 1319 | return; |
| 1320 | } |
| 1321 | let entries; |
| 1322 | try { |
| 1323 | entries = await fs.promises.readdir(dir, { withFileTypes: true }); |
| 1324 | } catch { |
| 1325 | return; // unreadable — skip silently |
| 1326 | } |
| 1327 | for (const e of entries) { |
| 1328 | if (localFiles.length >= WALK_MAX_FILES) { |
| 1329 | truncated = true; |
| 1330 | return; |
searching dependent graphs…