feat: migrate MCP server search to support Tavily alongside DuckDuckGo#34
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces Tavily as an alternative search provider alongside DuckDuckGo. It updates the MCP server to use a new dispatcher function, search_web, and adds the necessary configuration and implementation for Tavily searches. Feedback was provided to improve the error handling in the Tavily search implementation by explicitly checking for the API key and ensuring the response processing is safely contained within a try-except block to avoid masking configuration or malformed response issues.
| try: | ||
| client = AsyncTavilyClient(api_key=os.environ.get("TAVILY_API_KEY", "")) | ||
| response = await client.search(query=q, max_results=max_results) | ||
| except Exception: | ||
| return [] | ||
| results = [] | ||
| for r in response.get("results", []): | ||
| results.append({ | ||
| "title": r.get("title", ""), | ||
| "href": r.get("url", ""), | ||
| "body": r.get("content", ""), | ||
| }) | ||
| return results |
There was a problem hiding this comment.
The current implementation uses a broad except Exception block that silently returns an empty list, which masks configuration issues (like a missing TAVILY_API_KEY) or API errors. Additionally, processing the response outside the try block could lead to unhandled exceptions if the response is malformed.
Consider checking for the API key explicitly and wrapping the entire search and normalization logic in the try block, ideally with logging for better observability.
| try: | |
| client = AsyncTavilyClient(api_key=os.environ.get("TAVILY_API_KEY", "")) | |
| response = await client.search(query=q, max_results=max_results) | |
| except Exception: | |
| return [] | |
| results = [] | |
| for r in response.get("results", []): | |
| results.append({ | |
| "title": r.get("title", ""), | |
| "href": r.get("url", ""), | |
| "body": r.get("content", ""), | |
| }) | |
| return results | |
| api_key = os.environ.get("TAVILY_API_KEY") | |
| if not api_key: | |
| return [] | |
| try: | |
| client = AsyncTavilyClient(api_key=api_key) | |
| response = await client.search(query=q, max_results=max_results) | |
| return [ | |
| { | |
| "title": r.get("title", ""), | |
| "href": r.get("url", ""), | |
| "body": r.get("content", ""), | |
| } | |
| for r in response.get("results", []) | |
| ] | |
| except Exception: | |
| # Consider adding logging here to capture API or network errors | |
| return [] |
Summary
Adds Tavily as a configurable search backend for the MCP server's
/searchand/researchendpoints, keeping DuckDuckGo as the default provider.What changed
mcp/tools/web.py: Addedsearch_tavily()function using the Tavily Python SDK, with result normalization to{title, href, body}format. Addedsearch_web()dispatcher that routes to Tavily or DuckDuckGo based on theSEARCH_PROVIDERenvironment variable.mcp/server.py: Replaced directwebtools.search_ddg()calls in/searchand/researchendpoints withwebtools.search_web()dispatcher.requirements.txt: Addedtavily-python>=0.3.0dependency..env.example: DocumentedSEARCH_PROVIDERandTAVILY_API_KEYenvironment variables.Dependency changes
tavily-python>=0.3.0torequirements.txtEnvironment variable changes
SEARCH_PROVIDER(optional, defaults toduckduckgo, set totavilyto use Tavily)TAVILY_API_KEY(required whenSEARCH_PROVIDER=tavily)Notes for reviewers
search_ddgfunction is unchanged, so existing tests that mock it directly continue to work.{title, href, body}for Tavily results, matching the DuckDuckGo output format.Automated Review
AsyncTavilyClientis now used withawait client.search(), eliminating the blocking-call-in-async-function problem; (2) client instantiation moved inside thetry/exceptblock andos.environ.get("TAVILY_API_KEY", "")used for graceful degradation when the key is absent. All relevant files are updated (mcp/tools/web.py,mcp/server.py,requirements.txt,.env.example). Existing tests remain valid — the DDGS mock path still works becauseSEARCH_PROVIDERdefaults toduckduckgo. No regressions detected. One minor doc staleness issue found.