From c01d4015c85fec137dd899b2a2aac0dc03f3183b Mon Sep 17 00:00:00 2001 From: Tavily PR Agent Date: Thu, 21 May 2026 14:05:42 +0000 Subject: [PATCH 1/2] feat: add Tavily as configurable search provider --- README.md | 8 ++- pyproject.toml | 1 + src/perplexity_mcp/server.py | 100 +++++++++++++++++++++++++---------- 3 files changed, 78 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 6c3f160..bf6736c 100644 --- a/README.md +++ b/README.md @@ -69,10 +69,12 @@ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | ie The following environment variable is required in your claude_desktop_config.json. You can obtain an API key from [Perplexity](https://perplexity.ai) -- `PERPLEXITY_API_KEY`: Your Perplexity AI API key +- `PERPLEXITY_API_KEY`: Your Perplexity AI API key (required when using Perplexity provider) Optional environment variables: +- `SEARCH_PROVIDER`: Select the search backend — `perplexity` (default) or `tavily` +- `TAVILY_API_KEY`: Your Tavily API key (required when `SEARCH_PROVIDER=tavily`). Get one at [Tavily](https://app.tavily.com) - `PERPLEXITY_MODEL`: The Perplexity model to use (defaults to "sonar" if not specified) Available models: @@ -94,7 +96,9 @@ Add this tool as a mcp server by editing the Cursor/Claude config file. "perplexity-mcp": { "env": { "PERPLEXITY_API_KEY": "XXXXXXXXXXXXXXXXXXXX", - "PERPLEXITY_MODEL": "sonar" + "PERPLEXITY_MODEL": "sonar", + "SEARCH_PROVIDER": "perplexity", + "TAVILY_API_KEY": "tvly-XXXXXXXXXXXXXXXXXXXX" }, "command": "uvx", "args": [ diff --git a/pyproject.toml b/pyproject.toml index cf5c0c8..8007a00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ dependencies = [ "aiohttp>=3.8.0", "pydantic>=2.0.0", "mcp>=1.0.2", + "tavily-python>=0.5", ] [project.scripts] diff --git a/src/perplexity_mcp/server.py b/src/perplexity_mcp/server.py index c739188..f5bb535 100644 --- a/src/perplexity_mcp/server.py +++ b/src/perplexity_mcp/server.py @@ -6,6 +6,8 @@ from datetime import datetime import os +from tavily import TavilyClient + from mcp.server.models import InitializationOptions import mcp.types as types from mcp.server import NotificationOptions, Server @@ -104,7 +106,11 @@ async def call_tool( if name == "perplexity_search_web": query = arguments["query"] recency = arguments.get("recency", "month") - result = await call_perplexity(query, recency) + provider = os.getenv("SEARCH_PROVIDER", "perplexity") + if provider == "tavily": + result = await call_tavily(query, recency) + else: + result = await call_perplexity(query, recency) return [types.TextContent(type="text", text=str(result))] raise ValueError(f"Tool not found: {name}") @@ -156,10 +162,34 @@ async def call_perplexity(query: str, recency: str) -> str: return content +async def call_tavily(query: str, recency: str) -> str: + client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY")) + response = client.search( + query=query, + max_results=5, + search_depth="advanced", + time_range=recency, + ) + results = response.get("results", []) + if not results: + return "No results found." + formatted = [] + for i, r in enumerate(results, 1): + title = r.get("title", "No title") + url = r.get("url", "") + content = r.get("content", "") + formatted.append(f"[{i}] {title}\n {url}\n {content}") + return "\n\n".join(formatted) + + async def main_async(): - API_KEY = os.getenv("PERPLEXITY_API_KEY") - if not API_KEY: - raise ValueError("PERPLEXITY_API_KEY environment variable is required") + provider = os.getenv("SEARCH_PROVIDER", "perplexity") + if provider == "tavily": + if not os.getenv("TAVILY_API_KEY"): + raise ValueError("TAVILY_API_KEY environment variable is required when SEARCH_PROVIDER=tavily") + else: + if not os.getenv("PERPLEXITY_API_KEY"): + raise ValueError("PERPLEXITY_API_KEY environment variable is required") async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): await server.run( @@ -180,32 +210,44 @@ def main(): """CLI entry point for perplexity-mcp""" logging.basicConfig(level=logging.INFO) - API_KEY = os.getenv("PERPLEXITY_API_KEY") - if not API_KEY: - print( - "Error: PERPLEXITY_API_KEY environment variable is required", - file=sys.stderr, - ) - sys.exit(1) + provider = os.getenv("SEARCH_PROVIDER", "perplexity") + logging.info(f"Search provider: {provider}") - # Log which model is being used (helpful for debug) - model = os.getenv("PERPLEXITY_MODEL", "sonar") - logging.info(f"Using Perplexity AI model: {model}") - - # List available models - available_models = { - "sonar-deep-research": "128k context - Enhanced research capabilities", - "sonar-reasoning-pro": "128k context - Advanced reasoning with professional focus", - "sonar-reasoning": "128k context - Enhanced reasoning capabilities", - "sonar-pro": "200k context - Professional grade model", - "sonar": "128k context - Default model", - "r1-1776": "128k context - Alternative architecture" - } - - logging.info("Available Perplexity models (set with PERPLEXITY_MODEL environment variable):") - for model_name, description in available_models.items(): - marker = "→" if model_name == model else " " - logging.info(f" {marker} {model_name}: {description}") + if provider == "tavily": + if not os.getenv("TAVILY_API_KEY"): + print( + "Error: TAVILY_API_KEY environment variable is required when SEARCH_PROVIDER=tavily", + file=sys.stderr, + ) + sys.exit(1) + logging.info("Using Tavily search API") + else: + API_KEY = os.getenv("PERPLEXITY_API_KEY") + if not API_KEY: + print( + "Error: PERPLEXITY_API_KEY environment variable is required", + file=sys.stderr, + ) + sys.exit(1) + + # Log which model is being used (helpful for debug) + model = os.getenv("PERPLEXITY_MODEL", "sonar") + logging.info(f"Using Perplexity AI model: {model}") + + # List available models + available_models = { + "sonar-deep-research": "128k context - Enhanced research capabilities", + "sonar-reasoning-pro": "128k context - Advanced reasoning with professional focus", + "sonar-reasoning": "128k context - Enhanced reasoning capabilities", + "sonar-pro": "200k context - Professional grade model", + "sonar": "128k context - Default model", + "r1-1776": "128k context - Alternative architecture" + } + + logging.info("Available Perplexity models (set with PERPLEXITY_MODEL environment variable):") + for model_name, description in available_models.items(): + marker = "→" if model_name == model else " " + logging.info(f" {marker} {model_name}: {description}") asyncio.run(main_async()) From f78cb95529189cc751af194187ff1ff16cab7de7 Mon Sep 17 00:00:00 2001 From: Tavily PR Agent Date: Thu, 21 May 2026 14:08:19 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20address=20review=20feedback=20(attem?= =?UTF-8?q?pt=202)=20=E2=80=94=20Add=20Tavily=20as=20a=20configurable=20se?= =?UTF-8?q?arch=20provider=20in=20perplexity-mcp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/perplexity_mcp/server.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/perplexity_mcp/server.py b/src/perplexity_mcp/server.py index f5bb535..fbc39bd 100644 --- a/src/perplexity_mcp/server.py +++ b/src/perplexity_mcp/server.py @@ -6,7 +6,7 @@ from datetime import datetime import os -from tavily import TavilyClient +from tavily import AsyncTavilyClient from mcp.server.models import InitializationOptions import mcp.types as types @@ -27,7 +27,7 @@ async def handle_list_prompts() -> list[types.Prompt]: return [ types.Prompt( name="perplexity_search_web", - description="Search the web using Perplexity AI and filter results by recency", + description="Search the web with recency filtering", arguments=[ types.PromptArgument( name="query", @@ -82,7 +82,7 @@ async def list_tools() -> list[types.Tool]: return [ types.Tool( name="perplexity_search_web", - description="Search the web using Perplexity AI with recency filtering", + description="Search the web with recency filtering", inputSchema={ "type": "object", "properties": { @@ -163,13 +163,16 @@ async def call_perplexity(query: str, recency: str) -> str: async def call_tavily(query: str, recency: str) -> str: - client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY")) - response = client.search( - query=query, - max_results=5, - search_depth="advanced", - time_range=recency, - ) + try: + client = AsyncTavilyClient(api_key=os.getenv("TAVILY_API_KEY")) + response = await client.search( + query=query, + max_results=5, + search_depth="advanced", + time_range=recency, + ) + except Exception as e: + return f"Tavily search error: {e}" results = response.get("results", []) if not results: return "No results found."