perplexity-mcp-server
Version:
A Perplexity API Model Context Protocol (MCP) server that unlocks Perplexity's search-augmented AI capabilities for LLM agents. Features robust error handling, secure input validation, and transparent reasoning with the showThinking parameter. Built with
105 lines (100 loc) • 7.42 kB
JavaScript
/**
* @fileoverview Defines the core logic, schemas, and types for the `perplexity_search` tool.
* This tool interfaces with the Perplexity API to provide search-augmented answers.
* @module src/mcp-server/tools/perplexitySearch/logic
*/
import { z } from 'zod';
import { config } from '../../../config/index.js';
import { perplexityApiService, PerplexityChatCompletionRequestSchema } from '../../../services/index.js';
import { BaseErrorCode, McpError } from '../../../types-global/errors.js';
import { logger } from '../../../utils/index.js';
// 1. DEFINE Zod input and output schemas.
export const PerplexitySearchInputSchema = z.object({
query: z.string().min(1).describe("The natural language query for Perplexity's search-augmented generation."),
return_related_questions: z.boolean().optional().default(false).describe("If true, the model will suggest related questions in its response. Defaults to false."),
search_recency_filter: z.string().optional().describe("Restricts the web search to a specific timeframe. Accepts 'day', 'week', 'month', 'year'."),
search_domain_filter: z.array(z.string()).optional().describe("A list of domains to restrict or exclude from the search. (e.g. ['wikipedia.org', 'arxiv.org'])."),
search_after_date_filter: z.string().optional().describe("Filters search results to content published after a specific date (MM/DD/YYYY)."),
search_before_date_filter: z.string().optional().describe("Filters search results to content published before a specific date (MM/DD/YYYY)."),
search_mode: z.enum(['web', 'academic']).optional().describe("Set to 'academic' to prioritize scholarly sources."),
showThinking: z.boolean().optional().default(false).describe("If true, includes the model's internal reasoning in the response. Defaults to false."),
}).describe("Performs a search-augmented query using the Perplexity Search API. `perplexity_search` takes a natural language query, performs a web search, and uses an LLM to synthesize an answer. Use concise, specific queries for best results; include version information if applicable. Supports filtering by recency, date, domain, and search mode (web or academic). '(Ex. 'What are the latest advancements in quantum computing?')");
const SearchResultSchema = z.object({
title: z.string().describe("The title of the search result."),
url: z.string().url().describe("The URL of the search result."),
date: z.string().nullable().optional().describe("The publication date of the search result. Can be null."),
});
export const PerplexitySearchResponseSchema = z.object({
rawResultText: z.string().describe("The synthesized answer from the Perplexity model."),
responseId: z.string().describe("The unique identifier for the Perplexity API response."),
modelUsed: z.string().describe("The model that was used to generate the response."),
usage: z.object({
prompt_tokens: z.number(),
completion_tokens: z.number(),
total_tokens: z.number(),
}).describe("Token usage details for the API call."),
searchResults: z.array(SearchResultSchema).optional().describe("An array of web search results used to generate the response."),
});
// --- System Prompt ---
const SYSTEM_PROMPT = `You are an advanced AI assistant using the Perplexity search engine. Your primary directive is to provide comprehensive, accurate, and trustworthy answers based on systematic web research.
**Core Directives:**
1. **Systematic Research:** Conduct a thorough and systematic search to gather a complete picture of the topic. Do not rely on a single source.
2. **Source Vetting:** Critically evaluate sources for reliability, authoritativeness, and timeliness. Prioritize modern, primary sources and reputable publications.
3. **Accurate & Robust Citations:** All claims and data points must be backed by accurate, inline citations. Ensure that the citation metadata (URL, title) is complete and correct.
**Final Answer Formatting Rules:**
1. **Synthesize:** Base your answer *only* on the provided web search results. Do not add external knowledge.
2. **Structure:**
* If the answer is best presented as a list: Write a brief, natural introductory sentence based on the original query, followed by the list items. Separate each list item with two newlines (\n\n).
* If the answer is a direct statement or explanation: Provide it clearly and concisely.
3. **Clarity:** The final answer should be direct and stand alone. Do NOT explain the search process or mention intermediate steps within the final answer itself.
4. **Focus:** Prioritize accuracy and relevance to the user's query, supported by strong evidence from your research.`;
/**
* 3. IMPLEMENT and export the core logic function.
* It must remain pure: its only concerns are its inputs and its return value or thrown error.
* @throws {McpError} If the logic encounters an unrecoverable issue.
*/
export async function perplexitySearchLogic(params, context) {
logger.debug("Executing perplexitySearchLogic...", { ...context, toolInput: params });
const modelValidation = PerplexityChatCompletionRequestSchema.shape.model.safeParse(config.perplexityDefaultModel);
if (!modelValidation.success) {
throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, `Invalid Perplexity default model configured: ${config.perplexityDefaultModel}`, { ...context, error: modelValidation.error });
}
const modelToUse = modelValidation.data;
const requestPayload = {
model: modelToUse,
messages: [
{ role: 'system', content: SYSTEM_PROMPT },
{ role: 'user', content: params.query },
],
stream: false,
...(params.return_related_questions && { return_related_questions: params.return_related_questions }),
...(params.search_recency_filter && { search_recency_filter: params.search_recency_filter }),
...(params.search_domain_filter && { search_domain_filter: params.search_domain_filter }),
...(params.search_after_date_filter && { search_after_date_filter: params.search_after_date_filter }),
...(params.search_before_date_filter && { search_before_date_filter: params.search_before_date_filter }),
...(params.search_mode && { search_mode: params.search_mode }),
};
logger.info("Calling Perplexity API", { ...context, model: modelToUse });
logger.debug("API Payload", { ...context, payload: requestPayload });
const response = await perplexityApiService.chatCompletion(requestPayload, context);
const rawResultText = response.choices?.[0]?.message?.content;
if (!rawResultText) {
logger.warning("Perplexity API returned empty content", { ...context, responseId: response.id });
throw new McpError(BaseErrorCode.SERVICE_UNAVAILABLE, 'Perplexity API returned an empty response.', { ...context, responseId: response.id });
}
const toolResponse = {
rawResultText,
responseId: response.id,
modelUsed: response.model,
usage: response.usage,
searchResults: response.search_results,
};
logger.info("Perplexity search logic completed successfully.", {
...context,
responseId: toolResponse.responseId,
model: toolResponse.modelUsed,
usage: toolResponse.usage,
searchResultCount: toolResponse.searchResults?.length ?? 0,
});
return toolResponse;
}