Skip to content

feat: migrate MCP server search to support Tavily alongside DuckDuckGo#34

Open
tavily-integrations wants to merge 2 commits into
hoangsonww:masterfrom
Tavily-FDE:feat/tavily-migration/mcp-server-duckduckgo
Open

feat: migrate MCP server search to support Tavily alongside DuckDuckGo#34
tavily-integrations wants to merge 2 commits into
hoangsonww:masterfrom
Tavily-FDE:feat/tavily-migration/mcp-server-duckduckgo

Conversation

@tavily-integrations

Copy link
Copy Markdown

Summary

Adds Tavily as a configurable search backend for the MCP server's /search and /research endpoints, keeping DuckDuckGo as the default provider.

What changed

  • mcp/tools/web.py: Added search_tavily() function using the Tavily Python SDK, with result normalization to {title, href, body} format. Added search_web() dispatcher that routes to Tavily or DuckDuckGo based on the SEARCH_PROVIDER environment variable.
  • mcp/server.py: Replaced direct webtools.search_ddg() calls in /search and /research endpoints with webtools.search_web() dispatcher.
  • requirements.txt: Added tavily-python>=0.3.0 dependency.
  • .env.example: Documented SEARCH_PROVIDER and TAVILY_API_KEY environment variables.

Dependency changes

  • Added tavily-python>=0.3.0 to requirements.txt

Environment variable changes

  • SEARCH_PROVIDER (optional, defaults to duckduckgo, set to tavily to use Tavily)
  • TAVILY_API_KEY (required when SEARCH_PROVIDER=tavily)

Notes for reviewers

  • This is an additive change — DuckDuckGo remains the default and is fully preserved.
  • The existing search_ddg function is unchanged, so existing tests that mock it directly continue to work.
  • Result shape is normalized to {title, href, body} for Tavily results, matching the DuckDuckGo output format.

Automated Review

  • Passed after 2 attempt(s)
  • Final review: This is attempt 2 addressing two prior review issues. Both fixes are correctly implemented: (1) AsyncTavilyClient is now used with await client.search(), eliminating the blocking-call-in-async-function problem; (2) client instantiation moved inside the try/except block and os.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 because SEARCH_PROVIDER defaults to duckduckgo. No regressions detected. One minor doc staleness issue found.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread mcp/tools/web.py
Comment on lines +23 to +35
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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 []

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant