Skip to main content
Glama

Perplexity MCP Server

search.ts7.75 kB
/** * Tool implementation for web search functionality with real streaming support */ import type { PuppeteerContext } from "../types/index.js"; /** * Handles web search with configurable detail levels and optional streaming */ export default async function search( args: { query: string; detail_level?: "brief" | "normal" | "detailed"; stream?: boolean; }, ctx: PuppeteerContext, performSearch: (prompt: string, ctx: PuppeteerContext) => Promise<string>, ): Promise<string | AsyncGenerator<string, void, unknown>> { const { query, detail_level = "normal", stream = false } = args; let prompt = query; switch (detail_level) { case "brief": prompt = `Provide a brief, concise answer to: ${query}`; break; case "detailed": prompt = `Provide a comprehensive, detailed analysis of: ${query}. Include relevant examples, context, and supporting information where applicable.`; break; default: prompt = `Provide a clear, balanced answer to: ${query}. Include key points and relevant context.`; } // If streaming is not requested, return traditional response if (!stream) { return await performSearch(prompt, ctx); } // Return real streaming generator that monitors browser automation return realTimeStreamingSearch(prompt, ctx, performSearch); } // Helper functions for streaming search async function* streamBrowserSetup(ctx: PuppeteerContext): AsyncGenerator<string, void, unknown> { yield "🌐 Initializing browser connection...\n"; if (!ctx.browser || !ctx.page || ctx.page?.isClosed()) { yield "🔧 Setting up browser instance...\n"; } else { yield "✅ Browser ready, navigating to Perplexity...\n"; } } async function* streamSearchInitiation(prompt: string): AsyncGenerator<string, void, unknown> { yield "📡 Connecting to Perplexity AI...\n"; yield `⌨️ Submitting query: "${prompt.substring(0, 100)}${prompt.length > 100 ? "..." : ""}"\n\n`; } async function* streamSearchExecution( prompt: string, ctx: PuppeteerContext, performSearch: (prompt: string, ctx: PuppeteerContext) => Promise<string>, ): AsyncGenerator<string, void, unknown> { let searchCompleted = false; let finalResult = ""; // Monitor content while search is running const monitoringTask = monitorPageContent(ctx); // Start both search and monitoring const searchTask = performSearch(prompt, ctx).then((result) => { searchCompleted = true; finalResult = result; return result; }); // Stream monitoring updates while search runs for await (const contentUpdate of monitoringTask) { if (searchCompleted) break; yield contentUpdate; } // Ensure search is complete await searchTask; if (finalResult) { yield* streamSearchResults(finalResult); } } async function* streamSearchResults(result: string): AsyncGenerator<string, void, unknown> { yield "\n\n📋 **Search Results:**\n\n"; // Stream the final result in chunks for better UX const chunkSize = 300; for (let i = 0; i < result.length; i += chunkSize) { const chunk = result.slice(i, i + chunkSize); yield chunk; // Small delay to maintain streaming feel await new Promise<void>((resolve) => setTimeout(resolve, 50)); } } async function* streamFallbackSearch( prompt: string, ctx: PuppeteerContext, performSearch: (prompt: string, ctx: PuppeteerContext) => Promise<string>, ): AsyncGenerator<string, void, unknown> { yield "⚠️ Streaming unavailable, falling back to standard search...\n\n"; const result = await performSearch(prompt, ctx); yield result; } function formatStreamingError(error: unknown): string { const errorMessage = error instanceof Error && error.message ? error.message : "Unknown error"; return `\n\n❌ **Search failed:** ${errorMessage}\n💡 **Tip:** Try a more specific query or check your connection.\n`; } /** * Real-time streaming search implementation that monitors browser automation * and streams content as it arrives from Perplexity */ async function* realTimeStreamingSearch( prompt: string, ctx: PuppeteerContext, performSearch: (prompt: string, ctx: PuppeteerContext) => Promise<string>, ): AsyncGenerator<string, void, unknown> { yield "🔍 **Starting documentation search...**\n\n"; try { // Stream browser setup status yield* streamBrowserSetup(ctx); // Check if page is available for streaming if (ctx.page && !ctx.page.isClosed()) { yield* streamSearchInitiation(prompt); yield* streamSearchExecution(prompt, ctx, performSearch); } else { yield* streamFallbackSearch(prompt, ctx, performSearch); } yield "\n\n✅ **Search completed successfully!**"; } catch (error) { yield formatStreamingError(error); throw error; } } // Helper functions for content monitoring interface ContentCheckResult { hasContent: boolean; contentLength: number; hasInputField: boolean; pageState: string; } function createContentCheck() { return ` const proseElements = document.querySelectorAll( '.prose, [class*="prose"], [class*="answer"], [class*="result"]' ); let totalLength = 0; for (const element of proseElements) { totalLength += (element.innerText?.length || 0); } return { hasContent: totalLength > 0, contentLength: totalLength, hasInputField: !!document.querySelector('textarea[placeholder*="Ask"]'), pageState: document.readyState, }; `; } async function checkPageContent(ctx: PuppeteerContext): Promise<ContentCheckResult | null> { if (!ctx.page || ctx.page.isClosed()) return null; try { return (await ctx.page.evaluate(createContentCheck())) as ContentCheckResult; } catch { return null; } } function generateProgressUpdate( contentCheck: ContentCheckResult, lastContentLength: number, startTime: number, ): string | null { if (contentCheck.hasInputField && !contentCheck.hasContent) { if (Date.now() - startTime > 2000) { return "⏳ Waiting for AI response...\n"; } } else if (contentCheck.hasContent && contentCheck.contentLength > lastContentLength) { const status = lastContentLength === 0 ? " (response started)" : " (updating)"; return `📝 Content loading${status}...\n`; } return null; } function shouldBreakMonitoring(contentCheck: ContentCheckResult): boolean { return contentCheck.contentLength > 200 && contentCheck.pageState === "complete"; } /** * Monitor page content for real-time updates during search */ async function* monitorPageContent(ctx: PuppeteerContext): AsyncGenerator<string, void, unknown> { if (!ctx.page || ctx.page.isClosed()) return; try { let lastContentLength = 0; const maxMonitoringTime = 10000; // 10 seconds max monitoring const startTime = Date.now(); while (Date.now() - startTime < maxMonitoringTime) { const contentCheck = await checkPageContent(ctx); if (!contentCheck) { await new Promise<void>((resolve) => setTimeout(resolve, 1000)); continue; } // Generate progress update if needed const progressUpdate = generateProgressUpdate(contentCheck, lastContentLength, startTime); if (progressUpdate) { yield progressUpdate; lastContentLength = contentCheck.contentLength; } // Check if monitoring should break early if (shouldBreakMonitoring(contentCheck)) { yield "🎯 Response ready, finalizing...\n"; break; } await new Promise<void>((resolve) => setTimeout(resolve, 500)); // Check every 500ms } } catch (error) { // Monitoring failed, but don't break the main search yield "⚠️ Live monitoring unavailable, search continuing...\n"; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wysh3/perplexity-mcp-zerver'

If you have feedback or need assistance with the MCP directory API, please join our Discord server