From 3f35f6ec86c6266ab19a4543f1543b996538dd8f Mon Sep 17 00:00:00 2001 From: Emmaanuel Date: Mon, 1 Sep 2025 19:06:24 +0200 Subject: [PATCH 1/2] Enhance README and index.ts with date filtering options for Perplexity MCP Server --- README.md | 89 ++++++++++++++--- perplexity-ask/index.ts | 209 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 273 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index b2c854c..aea5bb0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Perplexity Ask MCP Server -An MCP server implementation that integrates the Sonar API to provide Claude with unparalleled real-time, web-wide research. +An MCP server implementation that integrates the Sonar API to provide Claude with unparalleled real-time, web-wide research capabilities, now enhanced with advanced date filtering options. Please refer to the official [DeepWiki page](https://deepwiki.com/ppl-ai/modelcontextprotocol) for assistance with implementation. @@ -10,22 +10,89 @@ Please refer to the official [DeepWiki page](https://deepwiki.com/ppl-ai/modelco ![System Architecture](perplexity-ask/assets/system_architecture.png) +![Demo](perplexity-ask/assets/demo_screenshot.png) +## Tools +### **perplexity_ask** +- Engage in a conversation with the Sonar API for live web searches. +- **Inputs:** + - `messages` (array): An array of conversation messages. + - Each message must include: + - `role` (string): The role of the message (e.g., `system`, `user`, `assistant`). + - `content` (string): The content of the message. + - **Date Filtering Options** (all optional): + - `search_after_date_filter` (string): Filter to content published after this date (format: M/D/YYYY) + - `search_before_date_filter` (string): Filter to content published before this date (format: M/D/YYYY) + - `last_updated_after_filter` (string): Filter to content updated after this date (format: M/D/YYYY) + - `last_updated_before_filter` (string): Filter to content updated before this date (format: M/D/YYYY) + - `search_recency_filter` (string): Filter by predefined periods ("day", "week", "month", "year") + +### **perplexity_research** +- Perform deep research queries using the Perplexity API with comprehensive analysis. +- **Inputs:** Same as `perplexity_ask` including all date filtering options. + +### **perplexity_reason** +- Execute advanced reasoning tasks with enhanced analytical capabilities. +- **Inputs:** Same as `perplexity_ask` including all date filtering options. + +## Date Filtering Features + +### Publication Date Filters +Filter results based on when content was originally created or published: +- `search_after_date_filter`: Include only content published after the specified date +- `search_before_date_filter`: Include only content published before the specified date + +### Last Updated Date Filters +Filter results based on when content was last modified: +- `last_updated_after_filter`: Include only content updated after the specified date +- `last_updated_before_filter`: Include only content updated before the specified date + +### Recency Filter +Quick filtering by predefined time periods relative to the current date: +- `search_recency_filter`: Use "day", "week", "month", or "year" for convenience + +### Filter Usage Examples + +1. **Find recent AI developments from the past week:** +```json +{ + "messages": [{"role": "user", "content": "What are the latest AI developments?"}], + "search_recency_filter": "week" +} +``` +2. **Research articles published in March 2025:** +```json +{ + "messages": [{"role": "user", "content": "Machine learning research trends"}], + "search_after_date_filter": "3/1/2025", + "search_before_date_filter": "3/31/2025" +} +``` -![Demo](perplexity-ask/assets/demo_screenshot.png) - +3. **Find recently updated documentation:** +```json +{ + "messages": [{"role": "user", "content": "React development best practices"}], + "last_updated_after_filter": "1/1/2025" +} +``` -## Tools +4. **Combine publication and update filters:** +```json +{ + "messages": [{"role": "user", "content": "Python tutorials"}], + "search_after_date_filter": "1/1/2024", + "search_before_date_filter": "12/31/2024", + "last_updated_after_filter": "2/1/2025" +} +``` -- **perplexity_ask** - - Engage in a conversation with the Sonar API for live web searches. - - **Inputs:** - - `messages` (array): An array of conversation messages. - - Each message must include: - - `role` (string): The role of the message (e.g., `system`, `user`, `assistant`). - - `content` (string): The content of the message. +### Important Notes +- Date format must be exactly M/D/YYYY (e.g., "3/1/2025" or "03/01/2025") +- `search_recency_filter` cannot be combined with other date filters +- All date filters are optional and can be used independently or together (except recency filter) ## Configuration diff --git a/perplexity-ask/index.ts b/perplexity-ask/index.ts index f5e5d6d..8c2c78f 100644 --- a/perplexity-ask/index.ts +++ b/perplexity-ask/index.ts @@ -1,5 +1,49 @@ #!/usr/bin/env node +/** + * Perplexity MCP Server with Date Filtering Support + * + * This server provides three tools for interacting with the Perplexity API: + * - perplexity_ask: Basic conversation tool + * - perplexity_research: Deep research queries + * - perplexity_reason: Advanced reasoning tasks + * + * All tools support optional date filtering parameters: + * + * PUBLICATION DATE FILTERS: + * - search_after_date_filter: Include only content published after this date (format: M/D/YYYY) + * - search_before_date_filter: Include only content published before this date (format: M/D/YYYY) + * + * LAST UPDATED FILTERS: + * - last_updated_after_filter: Include only content updated after this date (format: M/D/YYYY) + * - last_updated_before_filter: Include only content updated before this date (format: M/D/YYYY) + * + * RECENCY FILTER: + * - search_recency_filter: Quick filter by predefined periods ("day", "week", "month", "year") + * Note: Cannot be combined with other date filters + * + * EXAMPLES: + * + * 1. Find recent AI news from the past week: + * { + * "messages": [{"role": "user", "content": "What's new in AI?"}], + * "search_recency_filter": "week" + * } + * + * 2. Research articles published in March 2025: + * { + * "messages": [{"role": "user", "content": "Machine learning trends"}], + * "search_after_date_filter": "3/1/2025", + * "search_before_date_filter": "3/31/2025" + * } + * + * 3. Find recently updated documentation: + * { + * "messages": [{"role": "user", "content": "React best practices"}], + * "last_updated_after_filter": "1/1/2025" + * } + */ + import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { @@ -12,13 +56,15 @@ import { * Definition of the Perplexity Ask Tool. * This tool accepts an array of messages and returns a chat completion response * from the Perplexity API, with citations appended to the message if provided. + * Supports date and time filtering options. */ const PERPLEXITY_ASK_TOOL: Tool = { name: "perplexity_ask", description: "Engages in a conversation using the Sonar API. " + "Accepts an array of messages (each with a role and content) " + - "and returns a ask completion response from the Perplexity model.", + "and returns a ask completion response from the Perplexity model. " + + "Supports optional date filtering to constrain search results by publication date, last updated date, or recency.", inputSchema: { type: "object", properties: { @@ -40,6 +86,27 @@ const PERPLEXITY_ASK_TOOL: Tool = { }, description: "Array of conversation messages", }, + search_after_date_filter: { + type: "string", + description: "Filter results to content published after this date. Format: M/D/YYYY (e.g., '3/1/2025')", + }, + search_before_date_filter: { + type: "string", + description: "Filter results to content published before this date. Format: M/D/YYYY (e.g., '3/5/2025')", + }, + last_updated_after_filter: { + type: "string", + description: "Filter results to content last updated after this date. Format: M/D/YYYY (e.g., '3/1/2025')", + }, + last_updated_before_filter: { + type: "string", + description: "Filter results to content last updated before this date. Format: M/D/YYYY (e.g., '3/5/2025')", + }, + search_recency_filter: { + type: "string", + enum: ["day", "week", "month", "year"], + description: "Filter results by predefined time periods relative to current date. Cannot be combined with other date filters.", + }, }, required: ["messages"], }, @@ -48,13 +115,15 @@ const PERPLEXITY_ASK_TOOL: Tool = { /** * Definition of the Perplexity Research Tool. * This tool performs deep research queries using the Perplexity API. + * Supports date and time filtering options. */ const PERPLEXITY_RESEARCH_TOOL: Tool = { name: "perplexity_research", description: "Performs deep research using the Perplexity API. " + "Accepts an array of messages (each with a role and content) " + - "and returns a comprehensive research response with citations.", + "and returns a comprehensive research response with citations. " + + "Supports optional date filtering to constrain search results by publication date, last updated date, or recency.", inputSchema: { type: "object", properties: { @@ -76,6 +145,27 @@ const PERPLEXITY_RESEARCH_TOOL: Tool = { }, description: "Array of conversation messages", }, + search_after_date_filter: { + type: "string", + description: "Filter results to content published after this date. Format: M/D/YYYY (e.g., '3/1/2025')", + }, + search_before_date_filter: { + type: "string", + description: "Filter results to content published before this date. Format: M/D/YYYY (e.g., '3/5/2025')", + }, + last_updated_after_filter: { + type: "string", + description: "Filter results to content last updated after this date. Format: M/D/YYYY (e.g., '3/1/2025')", + }, + last_updated_before_filter: { + type: "string", + description: "Filter results to content last updated before this date. Format: M/D/YYYY (e.g., '3/5/2025')", + }, + search_recency_filter: { + type: "string", + enum: ["day", "week", "month", "year"], + description: "Filter results by predefined time periods relative to current date. Cannot be combined with other date filters.", + }, }, required: ["messages"], }, @@ -84,13 +174,15 @@ const PERPLEXITY_RESEARCH_TOOL: Tool = { /** * Definition of the Perplexity Reason Tool. * This tool performs reasoning queries using the Perplexity API. + * Supports date and time filtering options. */ const PERPLEXITY_REASON_TOOL: Tool = { name: "perplexity_reason", description: "Performs reasoning tasks using the Perplexity API. " + "Accepts an array of messages (each with a role and content) " + - "and returns a well-reasoned response using the sonar-reasoning-pro model.", + "and returns a well-reasoned response using the sonar-reasoning-pro model. " + + "Supports optional date filtering to constrain search results by publication date, last updated date, or recency.", inputSchema: { type: "object", properties: { @@ -112,6 +204,27 @@ const PERPLEXITY_REASON_TOOL: Tool = { }, description: "Array of conversation messages", }, + search_after_date_filter: { + type: "string", + description: "Filter results to content published after this date. Format: M/D/YYYY (e.g., '3/1/2025')", + }, + search_before_date_filter: { + type: "string", + description: "Filter results to content published before this date. Format: M/D/YYYY (e.g., '3/5/2025')", + }, + last_updated_after_filter: { + type: "string", + description: "Filter results to content last updated after this date. Format: M/D/YYYY (e.g., '3/1/2025')", + }, + last_updated_before_filter: { + type: "string", + description: "Filter results to content last updated before this date. Format: M/D/YYYY (e.g., '3/5/2025')", + }, + search_recency_filter: { + type: "string", + enum: ["day", "week", "month", "year"], + description: "Filter results by predefined time periods relative to current date. Cannot be combined with other date filters.", + }, }, required: ["messages"], }, @@ -124,28 +237,86 @@ if (!PERPLEXITY_API_KEY) { process.exit(1); } +/** + * Interface for date filtering options + */ +interface DateFilterOptions { + search_after_date_filter?: string; + search_before_date_filter?: string; + last_updated_after_filter?: string; + last_updated_before_filter?: string; + search_recency_filter?: "day" | "week" | "month" | "year"; +} + +/** + * Validates date filter options to ensure they follow the correct format and constraints + */ +function validateDateFilters(filters: DateFilterOptions): void { + const dateRegex = /^(0?[1-9]|1[0-2])\/(0?[1-9]|[12][0-9]|3[01])\/[0-9]{4}$/; + + // Validate date format for specific date filters + if (filters.search_after_date_filter && !dateRegex.test(filters.search_after_date_filter)) { + throw new Error("search_after_date_filter must be in M/D/YYYY format (e.g., '3/1/2025')"); + } + if (filters.search_before_date_filter && !dateRegex.test(filters.search_before_date_filter)) { + throw new Error("search_before_date_filter must be in M/D/YYYY format (e.g., '3/5/2025')"); + } + if (filters.last_updated_after_filter && !dateRegex.test(filters.last_updated_after_filter)) { + throw new Error("last_updated_after_filter must be in M/D/YYYY format (e.g., '3/1/2025')"); + } + if (filters.last_updated_before_filter && !dateRegex.test(filters.last_updated_before_filter)) { + throw new Error("last_updated_before_filter must be in M/D/YYYY format (e.g., '3/5/2025')"); + } + + // Validate that search_recency_filter is not combined with other date filters + if (filters.search_recency_filter && + (filters.search_after_date_filter || filters.search_before_date_filter || + filters.last_updated_after_filter || filters.last_updated_before_filter)) { + throw new Error("search_recency_filter cannot be combined with other date filters"); + } +} + /** * Performs a chat completion by sending a request to the Perplexity API. * Appends citations to the returned message content if they exist. * * @param {Array<{ role: string; content: string }>} messages - An array of message objects. * @param {string} model - The model to use for the completion. + * @param {DateFilterOptions} dateFilters - Optional date filtering parameters. * @returns {Promise} The chat completion result with appended citations. * @throws Will throw an error if the API request fails. */ async function performChatCompletion( messages: Array<{ role: string; content: string }>, - model: string = "sonar-pro" + model: string = "sonar-pro", + dateFilters: DateFilterOptions = {} ): Promise { + // Validate date filters + validateDateFilters(dateFilters); + // Construct the API endpoint URL and request body const url = new URL("https://api.perplexity.ai/chat/completions"); - const body = { + const body: any = { model: model, // Model identifier passed as parameter messages: messages, - // Additional parameters can be added here if required (e.g., max_tokens, temperature, etc.) - // See the Sonar API documentation for more details: - // https://docs.perplexity.ai/api-reference/chat-completions }; + + // Add date filter parameters if provided + if (dateFilters.search_after_date_filter) { + body.search_after_date_filter = dateFilters.search_after_date_filter; + } + if (dateFilters.search_before_date_filter) { + body.search_before_date_filter = dateFilters.search_before_date_filter; + } + if (dateFilters.last_updated_after_filter) { + body.last_updated_after_filter = dateFilters.last_updated_after_filter; + } + if (dateFilters.last_updated_before_filter) { + body.last_updated_before_filter = dateFilters.last_updated_before_filter; + } + if (dateFilters.search_recency_filter) { + body.search_recency_filter = dateFilters.search_recency_filter; + } let response; try { @@ -230,14 +401,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { if (!args) { throw new Error("No arguments provided"); } + + // Extract date filter parameters from arguments + const dateFilters: DateFilterOptions = { + search_after_date_filter: args.search_after_date_filter as string | undefined, + search_before_date_filter: args.search_before_date_filter as string | undefined, + last_updated_after_filter: args.last_updated_after_filter as string | undefined, + last_updated_before_filter: args.last_updated_before_filter as string | undefined, + search_recency_filter: args.search_recency_filter as "day" | "week" | "month" | "year" | undefined, + }; + switch (name) { case "perplexity_ask": { if (!Array.isArray(args.messages)) { throw new Error("Invalid arguments for perplexity_ask: 'messages' must be an array"); } - // Invoke the chat completion function with the provided messages + // Invoke the chat completion function with the provided messages and date filters const messages = args.messages; - const result = await performChatCompletion(messages, "sonar-pro"); + const result = await performChatCompletion(messages, "sonar-pro", dateFilters); return { content: [{ type: "text", text: result }], isError: false, @@ -247,9 +428,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { if (!Array.isArray(args.messages)) { throw new Error("Invalid arguments for perplexity_research: 'messages' must be an array"); } - // Invoke the chat completion function with the provided messages using the deep research model + // Invoke the chat completion function with the provided messages using the deep research model and date filters const messages = args.messages; - const result = await performChatCompletion(messages, "sonar-deep-research"); + const result = await performChatCompletion(messages, "sonar-deep-research", dateFilters); return { content: [{ type: "text", text: result }], isError: false, @@ -259,9 +440,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { if (!Array.isArray(args.messages)) { throw new Error("Invalid arguments for perplexity_reason: 'messages' must be an array"); } - // Invoke the chat completion function with the provided messages using the reasoning model + // Invoke the chat completion function with the provided messages using the reasoning model and date filters const messages = args.messages; - const result = await performChatCompletion(messages, "sonar-reasoning-pro"); + const result = await performChatCompletion(messages, "sonar-reasoning-pro", dateFilters); return { content: [{ type: "text", text: result }], isError: false, From 56d78e8fa2038e9c024a9f05cd84107348466361 Mon Sep 17 00:00:00 2001 From: Emmaanuel Date: Mon, 1 Sep 2025 19:17:57 +0200 Subject: [PATCH 2/2] Enhance README and index.ts with domain filtering options for Perplexity MCP Server --- README.md | 74 +++++++++++++++++++++-- perplexity-ask/index.ts | 129 ++++++++++++++++++++++++++++++---------- 2 files changed, 167 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index aea5bb0..1d322b5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Perplexity Ask MCP Server -An MCP server implementation that integrates the Sonar API to provide Claude with unparalleled real-time, web-wide research capabilities, now enhanced with advanced date filtering options. +An MCP server implementation that integrates the Sonar API to provide Claude with unparalleled real-time, web-wide research capabilities, now enhanced with advanced date filtering and domain filtering options. Please refer to the official [DeepWiki page](https://deepwiki.com/ppl-ai/modelcontextprotocol) for assistance with implementation. @@ -27,14 +27,19 @@ Please refer to the official [DeepWiki page](https://deepwiki.com/ppl-ai/modelco - `last_updated_after_filter` (string): Filter to content updated after this date (format: M/D/YYYY) - `last_updated_before_filter` (string): Filter to content updated before this date (format: M/D/YYYY) - `search_recency_filter` (string): Filter by predefined periods ("day", "week", "month", "year") + - **Domain Filtering Options** (optional): + - `search_domain_filter` (array): Filter search results by domain or URL + - Allowlist mode: `["wikipedia.org", "github.com"]` - Include only these domains + - Denylist mode: `["-reddit.com", "-quora.com"]` - Exclude these domains + - Supports up to 20 entries, can mix domains and specific URLs ### **perplexity_research** - Perform deep research queries using the Perplexity API with comprehensive analysis. -- **Inputs:** Same as `perplexity_ask` including all date filtering options. +- **Inputs:** Same as `perplexity_ask` including all date and domain filtering options. ### **perplexity_reason** - Execute advanced reasoning tasks with enhanced analytical capabilities. -- **Inputs:** Same as `perplexity_ask` including all date filtering options. +- **Inputs:** Same as `perplexity_ask` including all date and domain filtering options. ## Date Filtering Features @@ -52,6 +57,31 @@ Filter results based on when content was last modified: Quick filtering by predefined time periods relative to the current date: - `search_recency_filter`: Use "day", "week", "month", or "year" for convenience +## Domain Filtering Features + +Control which websites are included or excluded from search results: + +### Allowlist Mode +Include only specified domains/URLs: +```json +"search_domain_filter": ["wikipedia.org", "github.com", "stackoverflow.com"] +``` + +### Denylist Mode +Exclude specified domains/URLs (use `-` prefix): +```json +"search_domain_filter": ["-reddit.com", "-pinterest.com", "-quora.com"] +``` + +### URL-Level Filtering +Target specific pages for granular control: +```json +"search_domain_filter": [ + "https://docs.python.org/3/tutorial/", + "https://en.wikipedia.org/wiki/Machine_learning" +] +``` + ### Filter Usage Examples 1. **Find recent AI developments from the past week:** @@ -89,10 +119,42 @@ Quick filtering by predefined time periods relative to the current date: } ``` +5. **Search only trusted academic sources:** +```json +{ + "messages": [{"role": "user", "content": "AI research papers"}], + "search_domain_filter": ["arxiv.org", "nature.com", "science.org"] +} +``` + +6. **Exclude social media and forums:** +```json +{ + "messages": [{"role": "user", "content": "Programming tutorials"}], + "search_domain_filter": ["-reddit.com", "-quora.com", "-pinterest.com"] +} +``` + +7. **Target specific documentation with recent updates:** +```json +{ + "messages": [{"role": "user", "content": "React hooks documentation"}], + "search_domain_filter": ["https://react.dev/docs/hooks", "github.com/facebook/react"], + "last_updated_after_filter": "1/1/2025" +} +``` + ### Important Notes -- Date format must be exactly M/D/YYYY (e.g., "3/1/2025" or "03/01/2025") -- `search_recency_filter` cannot be combined with other date filters -- All date filters are optional and can be used independently or together (except recency filter) +- **Date filters:** + - Date format must be exactly M/D/YYYY (e.g., "3/1/2025" or "03/01/2025") + - `search_recency_filter` cannot be combined with other date filters + - All date filters are optional and can be used independently or together (except recency filter) +- **Domain filters:** + - Maximum 20 domains/URLs per filter + - Cannot mix allowlist and denylist modes in the same request + - Use simple domain names (`example.com`) for broad filtering + - Use complete URLs (`https://example.com/page`) for specific page targeting + - Domain filtering applies to all subdomains (e.g., `nytimes.com` includes all subdomains) ## Configuration diff --git a/perplexity-ask/index.ts b/perplexity-ask/index.ts index 8c2c78f..b1b99b7 100644 --- a/perplexity-ask/index.ts +++ b/perplexity-ask/index.ts @@ -22,6 +22,13 @@ * - search_recency_filter: Quick filter by predefined periods ("day", "week", "month", "year") * Note: Cannot be combined with other date filters * + * DOMAIN FILTERING: + * - search_domain_filter: Array of domains/URLs to include (allowlist) or exclude (denylist) + * - Allowlist mode: ["domain1.com", "domain2.com"] - Include only these domains + * - Denylist mode: ["-domain1.com", "-domain2.com"] - Exclude these domains + * - URL-level filtering: ["https://example.com/specific-page"] for granular control + * - Maximum 20 domains/URLs allowed + * * EXAMPLES: * * 1. Find recent AI news from the past week: @@ -42,6 +49,24 @@ * "messages": [{"role": "user", "content": "React best practices"}], * "last_updated_after_filter": "1/1/2025" * } + * + * 4. Search only trusted domains: + * { + * "messages": [{"role": "user", "content": "AI research papers"}], + * "search_domain_filter": ["arxiv.org", "nature.com", "science.org"] + * } + * + * 5. Exclude social media and forum sites: + * { + * "messages": [{"role": "user", "content": "Machine learning tutorials"}], + * "search_domain_filter": ["-reddit.com", "-quora.com", "-pinterest.com"] + * } + * + * 6. Target specific documentation pages: + * { + * "messages": [{"role": "user", "content": "Python async programming"}], + * "search_domain_filter": ["https://docs.python.org/3/library/asyncio.html", "stackoverflow.com"] + * } */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; @@ -64,7 +89,8 @@ const PERPLEXITY_ASK_TOOL: Tool = { "Engages in a conversation using the Sonar API. " + "Accepts an array of messages (each with a role and content) " + "and returns a ask completion response from the Perplexity model. " + - "Supports optional date filtering to constrain search results by publication date, last updated date, or recency.", + "Supports optional date filtering to constrain search results by publication date, last updated date, or recency, " + + "and domain filtering to include or exclude specific websites from search results.", inputSchema: { type: "object", properties: { @@ -107,6 +133,13 @@ const PERPLEXITY_ASK_TOOL: Tool = { enum: ["day", "week", "month", "year"], description: "Filter results by predefined time periods relative to current date. Cannot be combined with other date filters.", }, + search_domain_filter: { + type: "array", + items: { + type: "string", + }, + description: "Filter search results by domain or URL. Use allowlist mode (include only specified domains/URLs) or denylist mode (exclude domains/URLs with '-' prefix). Examples: ['wikipedia.org', 'github.com'] for allowlist, ['-reddit.com', '-quora.com'] for denylist. Supports up to 20 entries. Can mix domains ('example.com') and specific URLs ('https://example.com/page').", + }, }, required: ["messages"], }, @@ -123,7 +156,8 @@ const PERPLEXITY_RESEARCH_TOOL: Tool = { "Performs deep research using the Perplexity API. " + "Accepts an array of messages (each with a role and content) " + "and returns a comprehensive research response with citations. " + - "Supports optional date filtering to constrain search results by publication date, last updated date, or recency.", + "Supports optional date filtering to constrain search results by publication date, last updated date, or recency, " + + "and domain filtering to include or exclude specific websites from search results.", inputSchema: { type: "object", properties: { @@ -166,6 +200,13 @@ const PERPLEXITY_RESEARCH_TOOL: Tool = { enum: ["day", "week", "month", "year"], description: "Filter results by predefined time periods relative to current date. Cannot be combined with other date filters.", }, + search_domain_filter: { + type: "array", + items: { + type: "string", + }, + description: "Filter search results by domain or URL. Use allowlist mode (include only specified domains/URLs) or denylist mode (exclude domains/URLs with '-' prefix). Examples: ['wikipedia.org', 'github.com'] for allowlist, ['-reddit.com', '-quora.com'] for denylist. Supports up to 20 entries. Can mix domains ('example.com') and specific URLs ('https://example.com/page').", + }, }, required: ["messages"], }, @@ -182,7 +223,8 @@ const PERPLEXITY_REASON_TOOL: Tool = { "Performs reasoning tasks using the Perplexity API. " + "Accepts an array of messages (each with a role and content) " + "and returns a well-reasoned response using the sonar-reasoning-pro model. " + - "Supports optional date filtering to constrain search results by publication date, last updated date, or recency.", + "Supports optional date filtering to constrain search results by publication date, last updated date, or recency, " + + "and domain filtering to include or exclude specific websites from search results.", inputSchema: { type: "object", properties: { @@ -225,6 +267,13 @@ const PERPLEXITY_REASON_TOOL: Tool = { enum: ["day", "week", "month", "year"], description: "Filter results by predefined time periods relative to current date. Cannot be combined with other date filters.", }, + search_domain_filter: { + type: "array", + items: { + type: "string", + }, + description: "Filter search results by domain or URL. Use allowlist mode (include only specified domains/URLs) or denylist mode (exclude domains/URLs with '-' prefix). Examples: ['wikipedia.org', 'github.com'] for allowlist, ['-reddit.com', '-quora.com'] for denylist. Supports up to 20 entries. Can mix domains ('example.com') and specific URLs ('https://example.com/page').", + }, }, required: ["messages"], }, @@ -238,20 +287,21 @@ if (!PERPLEXITY_API_KEY) { } /** - * Interface for date filtering options + * Interface for filtering options */ -interface DateFilterOptions { +interface FilterOptions { search_after_date_filter?: string; search_before_date_filter?: string; last_updated_after_filter?: string; last_updated_before_filter?: string; search_recency_filter?: "day" | "week" | "month" | "year"; + search_domain_filter?: string[]; } /** - * Validates date filter options to ensure they follow the correct format and constraints + * Validates filter options to ensure they follow the correct format and constraints */ -function validateDateFilters(filters: DateFilterOptions): void { +function validateFilters(filters: FilterOptions): void { const dateRegex = /^(0?[1-9]|1[0-2])\/(0?[1-9]|[12][0-9]|3[01])\/[0-9]{4}$/; // Validate date format for specific date filters @@ -274,6 +324,21 @@ function validateDateFilters(filters: DateFilterOptions): void { filters.last_updated_after_filter || filters.last_updated_before_filter)) { throw new Error("search_recency_filter cannot be combined with other date filters"); } + + // Validate domain filter constraints + if (filters.search_domain_filter) { + if (filters.search_domain_filter.length > 20) { + throw new Error("search_domain_filter can contain a maximum of 20 domains/URLs"); + } + + // Check if it's mixing allowlist and denylist modes + const hasAllowlist = filters.search_domain_filter.some(domain => !domain.startsWith('-')); + const hasDenylist = filters.search_domain_filter.some(domain => domain.startsWith('-')); + + if (hasAllowlist && hasDenylist) { + throw new Error("search_domain_filter cannot mix allowlist and denylist modes. Use either domains without '-' prefix (allowlist) or domains with '-' prefix (denylist), but not both."); + } + } } /** @@ -282,17 +347,17 @@ function validateDateFilters(filters: DateFilterOptions): void { * * @param {Array<{ role: string; content: string }>} messages - An array of message objects. * @param {string} model - The model to use for the completion. - * @param {DateFilterOptions} dateFilters - Optional date filtering parameters. + * @param {FilterOptions} filters - Optional filtering parameters for dates and domains. * @returns {Promise} The chat completion result with appended citations. * @throws Will throw an error if the API request fails. */ async function performChatCompletion( messages: Array<{ role: string; content: string }>, model: string = "sonar-pro", - dateFilters: DateFilterOptions = {} + filters: FilterOptions = {} ): Promise { - // Validate date filters - validateDateFilters(dateFilters); + // Validate filters + validateFilters(filters); // Construct the API endpoint URL and request body const url = new URL("https://api.perplexity.ai/chat/completions"); @@ -301,21 +366,24 @@ async function performChatCompletion( messages: messages, }; - // Add date filter parameters if provided - if (dateFilters.search_after_date_filter) { - body.search_after_date_filter = dateFilters.search_after_date_filter; + // Add filter parameters if provided + if (filters.search_after_date_filter) { + body.search_after_date_filter = filters.search_after_date_filter; + } + if (filters.search_before_date_filter) { + body.search_before_date_filter = filters.search_before_date_filter; } - if (dateFilters.search_before_date_filter) { - body.search_before_date_filter = dateFilters.search_before_date_filter; + if (filters.last_updated_after_filter) { + body.last_updated_after_filter = filters.last_updated_after_filter; } - if (dateFilters.last_updated_after_filter) { - body.last_updated_after_filter = dateFilters.last_updated_after_filter; + if (filters.last_updated_before_filter) { + body.last_updated_before_filter = filters.last_updated_before_filter; } - if (dateFilters.last_updated_before_filter) { - body.last_updated_before_filter = dateFilters.last_updated_before_filter; + if (filters.search_recency_filter) { + body.search_recency_filter = filters.search_recency_filter; } - if (dateFilters.search_recency_filter) { - body.search_recency_filter = dateFilters.search_recency_filter; + if (filters.search_domain_filter && filters.search_domain_filter.length > 0) { + body.search_domain_filter = filters.search_domain_filter; } let response; @@ -402,13 +470,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { throw new Error("No arguments provided"); } - // Extract date filter parameters from arguments - const dateFilters: DateFilterOptions = { + // Extract filter parameters from arguments + const filters: FilterOptions = { search_after_date_filter: args.search_after_date_filter as string | undefined, search_before_date_filter: args.search_before_date_filter as string | undefined, last_updated_after_filter: args.last_updated_after_filter as string | undefined, last_updated_before_filter: args.last_updated_before_filter as string | undefined, search_recency_filter: args.search_recency_filter as "day" | "week" | "month" | "year" | undefined, + search_domain_filter: args.search_domain_filter as string[] | undefined, }; switch (name) { @@ -416,9 +485,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { if (!Array.isArray(args.messages)) { throw new Error("Invalid arguments for perplexity_ask: 'messages' must be an array"); } - // Invoke the chat completion function with the provided messages and date filters + // Invoke the chat completion function with the provided messages and filters const messages = args.messages; - const result = await performChatCompletion(messages, "sonar-pro", dateFilters); + const result = await performChatCompletion(messages, "sonar-pro", filters); return { content: [{ type: "text", text: result }], isError: false, @@ -428,9 +497,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { if (!Array.isArray(args.messages)) { throw new Error("Invalid arguments for perplexity_research: 'messages' must be an array"); } - // Invoke the chat completion function with the provided messages using the deep research model and date filters + // Invoke the chat completion function with the provided messages using the deep research model and filters const messages = args.messages; - const result = await performChatCompletion(messages, "sonar-deep-research", dateFilters); + const result = await performChatCompletion(messages, "sonar-deep-research", filters); return { content: [{ type: "text", text: result }], isError: false, @@ -440,9 +509,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { if (!Array.isArray(args.messages)) { throw new Error("Invalid arguments for perplexity_reason: 'messages' must be an array"); } - // Invoke the chat completion function with the provided messages using the reasoning model and date filters + // Invoke the chat completion function with the provided messages using the reasoning model and filters const messages = args.messages; - const result = await performChatCompletion(messages, "sonar-reasoning-pro", dateFilters); + const result = await performChatCompletion(messages, "sonar-reasoning-pro", filters); return { content: [{ type: "text", text: result }], isError: false,