search_advanced
Perform targeted searches on Perplexity.ai with control over source types—web, academic, social—and combine them for precise results.
Instructions
Search Perplexity.ai with specific source selection. Lets you combine multiple sources (e.g. web + academic). Use this when source control matters; prefer search for general queries.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | The search query | |
| sources | Yes | Sources to search: 'web' (general web), 'academic' (scholarly articles), 'social' (Reddit & forums). Can combine multiple. |
Implementation Reference
- src/index.ts:53-57 (handler)The execute handler for the search_advanced tool. It ensures the browser is available, then calls searchWithSources with the query, timeout, and chosen sources, formatting the result.
execute: async ({ query, sources }) => { await ensureBrowser(); const result = await searchWithSources(query, TIMEOUT_MS, sources); return formatResult(result); }, - src/index.ts:46-52 (schema)The Zod schema for search_advanced's parameters: 'query' (string) and 'sources' (array of 'web', 'academic', or 'social' with at least 1 element).
parameters: z.object({ query: z.string().describe("The search query"), sources: z .array(z.enum(["web", "academic", "social"])) .min(1) .describe("Sources to search: 'web' (general web), 'academic' (scholarly articles), 'social' (Reddit & forums). Can combine multiple."), }), - src/index.ts:42-58 (registration)Registration of the 'search_advanced' tool via mcp.addTool(), with its name, description, parameter schema, and execute handler.
mcp.addTool({ name: "search_advanced", description: "Search Perplexity.ai with specific source selection. Lets you combine multiple sources (e.g. web + academic). Use this when source control matters; prefer `search` for general queries.", parameters: z.object({ query: z.string().describe("The search query"), sources: z .array(z.enum(["web", "academic", "social"])) .min(1) .describe("Sources to search: 'web' (general web), 'academic' (scholarly articles), 'social' (Reddit & forums). Can combine multiple."), }), execute: async ({ query, sources }) => { await ensureBrowser(); const result = await searchWithSources(query, TIMEOUT_MS, sources); return formatResult(result); }, }); - src/search.ts:31-34 (handler)The searchWithSources function that performs the actual advanced search by running runSearch(query, timeoutMs, sources).
export async function searchWithSources(query: string, timeoutMs: number, sources: string[]): Promise<SearchResult> { log(`Search: "${query}" sources=[${sources.join(",")}] (timeout: ${timeoutMs}ms)`); return runSearch(query, timeoutMs, sources); } - src/search.ts:88-140 (helper)The selectSources helper that opens the Perplexity source menu and checks/unchecks the requested source toggles (web, academic, social).
async function selectSources(page: Page, sources: string[]): Promise<void> { const targetIcons = sources.map(s => SOURCE_ICON[s]).filter(Boolean); if (targetIcons.length === 0) return; // Open the "+" menu — located by its icon #pplx-icon-plus const addBtnLabel = await page.evaluate(() => { const btn = Array.from(document.querySelectorAll('button[aria-haspopup="menu"]')).find(b => { const use = b.querySelector('use'); return use && (use.getAttribute('xlink:href') === '#pplx-icon-plus' || use.getAttribute('href') === '#pplx-icon-plus'); }); return btn?.getAttribute('aria-label') ?? null; }); if (!addBtnLabel) throw new Error("Could not find the + (add) button on Perplexity"); await page.locator(`button[aria-label="${addBtnLabel}"]`).click(); await page.waitForTimeout(300); // Open "Connecteurs et sources" submenu — located by its icon #pplx-icon-plug const connLabel = await page.evaluate(() => { const item = Array.from(document.querySelectorAll('[role="menuitem"]')).find(el => { const use = el.querySelector('use'); return use && (use.getAttribute('xlink:href') === '#pplx-icon-plug' || use.getAttribute('href') === '#pplx-icon-plug'); }); return item?.getAttribute('aria-label') ?? item?.textContent?.trim() ?? null; }); if (!connLabel) throw new Error("Could not find 'Connecteurs et sources' menuitem"); await page.locator('[role="menuitem"]').filter({ hasText: connLabel.slice(0, 10) }).click(); await page.locator('[role="menuitemcheckbox"]').first().waitFor({ state: "visible", timeout: 3_000 }); // Read current state of all checkboxes const getCheckboxInfo = (iconId: string) => page.evaluate((id) => { const item = Array.from(document.querySelectorAll('[role="menuitemcheckbox"]')).find(el => { const use = el.querySelector('use'); return use && (use.getAttribute('xlink:href') === id || use.getAttribute('href') === id); }); return item ? { label: item.getAttribute('aria-label') ?? item.textContent?.trim() ?? "", checked: item.getAttribute('aria-checked') === 'true' } : null; }, iconId); // Build the desired state: check targets, uncheck everything else const allIcons = Object.values(SOURCE_ICON); for (const icon of allIcons) { const info = await getCheckboxInfo(icon); if (!info || !info.label) continue; const shouldBeChecked = targetIcons.includes(icon); if (info.checked !== shouldBeChecked) { await page.locator('[role="menuitemcheckbox"]').filter({ hasText: info.label }).click(); await page.waitForTimeout(200); } } // Close menus await page.keyboard.press('Escape'); await page.waitForTimeout(300); }