From eff5007871b7c77ee500a724dcb940c698249bee Mon Sep 17 00:00:00 2001 From: Tavily PR Agent Date: Mon, 23 Mar 2026 21:58:39 +0000 Subject: [PATCH 1/2] feat: add Tavily search as configurable MCP tool in miroflow-agent --- apps/miroflow-agent/.env.example | 3 +++ apps/miroflow-agent/conf/agent/default.yaml | 1 + apps/miroflow-agent/src/config/settings.py | 29 +++++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/apps/miroflow-agent/.env.example b/apps/miroflow-agent/.env.example index 7b46f867..8a8fa69b 100644 --- a/apps/miroflow-agent/.env.example +++ b/apps/miroflow-agent/.env.example @@ -32,6 +32,9 @@ REASONING_BASE_URL="https://your_reasoning_base_url/v1/chat/completions" ANTHROPIC_API_KEY=your_anthropic_key ANTHROPIC_BASE_URL=https://api.anthropic.com +# API for Tavily Search (optional) +TAVILY_API_KEY=your_tavily_key + # API for Sogou Search (optional) TENCENTCLOUD_SECRET_ID=your_tencent_cloud_secret_id TENCENTCLOUD_SECRET_KEY=your_tencent_cloud_secret_key diff --git a/apps/miroflow-agent/conf/agent/default.yaml b/apps/miroflow-agent/conf/agent/default.yaml index 6d9ecca0..c478f2f7 100644 --- a/apps/miroflow-agent/conf/agent/default.yaml +++ b/apps/miroflow-agent/conf/agent/default.yaml @@ -14,6 +14,7 @@ sub_agents: agent-browsing: tools: - tool-google-search + # - tool-tavily-search # Alternative to tool-google-search (requires TAVILY_API_KEY) - tool-vqa - tool-reader - tool-python diff --git a/apps/miroflow-agent/src/config/settings.py b/apps/miroflow-agent/src/config/settings.py index d5489ddd..87f97193 100644 --- a/apps/miroflow-agent/src/config/settings.py +++ b/apps/miroflow-agent/src/config/settings.py @@ -55,6 +55,9 @@ OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") OPENAI_BASE_URL = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1") +# API for Tavily Search (optional) +TAVILY_API_KEY = os.environ.get("TAVILY_API_KEY") + # API for Sogou Search TENCENTCLOUD_SECRET_ID = os.environ.get("TENCENTCLOUD_SECRET_ID") TENCENTCLOUD_SECRET_KEY = os.environ.get("TENCENTCLOUD_SECRET_KEY") @@ -136,6 +139,31 @@ def create_mcp_server_parameters(cfg: DictConfig, agent_cfg: DictConfig): } ) + if ( + agent_cfg.get("tools", None) is not None + and "tool-tavily-search" in agent_cfg["tools"] + ): + if not TAVILY_API_KEY: + raise ValueError( + "TAVILY_API_KEY not set, tool-tavily-search will be unavailable." + ) + + configs.append( + { + "name": "tool-tavily-search", + "params": StdioServerParameters( + command=sys.executable, + args=[ + "-m", + "miroflow_tools.mcp_servers.tavily_mcp_server", + ], + env={ + "TAVILY_API_KEY": TAVILY_API_KEY, + }, + ), + } + ) + if agent_cfg.get("tools", None) is not None and "tool-python" in agent_cfg["tools"]: configs.append( { @@ -460,6 +488,7 @@ def get_env_info(cfg: DictConfig) -> dict: else {} ), # API Keys (masked for security) + "has_tavily_api_key": bool(TAVILY_API_KEY), "has_serper_api_key": bool(SERPER_API_KEY), "has_jina_api_key": bool(JINA_API_KEY), "has_anthropic_api_key": bool(ANTHROPIC_API_KEY), From 3bcf27ce4d81ba4196286f6976fb13b8921a9727 Mon Sep 17 00:00:00 2001 From: Tavily PR Agent Date: Mon, 23 Mar 2026 22:01:08 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20address=20review=20feedback=20(attem?= =?UTF-8?q?pt=202)=20=E2=80=94=20Wire=20tool-tavily-search=20into=20mirofl?= =?UTF-8?q?ow-agent=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/miroflow-agent/src/config/settings.py | 2 +- libs/miroflow-tools/pyproject.toml | 3 +- .../mcp_servers/tavily_mcp_server.py | 95 +++++++++++++++++++ 3 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 libs/miroflow-tools/src/miroflow_tools/mcp_servers/tavily_mcp_server.py diff --git a/apps/miroflow-agent/src/config/settings.py b/apps/miroflow-agent/src/config/settings.py index 87f97193..fe37977a 100644 --- a/apps/miroflow-agent/src/config/settings.py +++ b/apps/miroflow-agent/src/config/settings.py @@ -145,7 +145,7 @@ def create_mcp_server_parameters(cfg: DictConfig, agent_cfg: DictConfig): ): if not TAVILY_API_KEY: raise ValueError( - "TAVILY_API_KEY not set, tool-tavily-search will be unavailable." + "TAVILY_API_KEY is required when tool-tavily-search is configured but is not set." ) configs.append( diff --git a/libs/miroflow-tools/pyproject.toml b/libs/miroflow-tools/pyproject.toml index 4213ce39..ea281be1 100644 --- a/libs/miroflow-tools/pyproject.toml +++ b/libs/miroflow-tools/pyproject.toml @@ -18,7 +18,8 @@ dependencies = [ "markitdown-mcp>=0.0.1a3", "google-genai", "aiohttp", - "redis" + "redis", + "tavily-python>=0.5.0" ] [build-system] diff --git a/libs/miroflow-tools/src/miroflow_tools/mcp_servers/tavily_mcp_server.py b/libs/miroflow-tools/src/miroflow_tools/mcp_servers/tavily_mcp_server.py new file mode 100644 index 00000000..35d282d1 --- /dev/null +++ b/libs/miroflow-tools/src/miroflow_tools/mcp_servers/tavily_mcp_server.py @@ -0,0 +1,95 @@ +# Copyright (c) 2025 MiroMind +# This source code is licensed under the Apache 2.0 License. + +""" +MCP server for Tavily web search. + +Provides a tool-tavily-search MCP tool that uses the Tavily API +to perform web searches optimized for LLM consumption. +""" + +import json +import os + +from mcp.server.fastmcp import FastMCP +from tavily import TavilyClient + +TAVILY_API_KEY = os.getenv("TAVILY_API_KEY", "") + +# Initialize FastMCP server +mcp = FastMCP("tavily-mcp-server") + + +@mcp.tool() +def tavily_search( + query: str, + max_results: int = 10, + search_depth: str = "basic", + topic: str = "general", + include_domains: list[str] | None = None, + exclude_domains: list[str] | None = None, + time_range: str | None = None, +) -> str: + """Perform web searches via Tavily API and retrieve results optimized for LLMs. + + Args: + query: Search query string (keep under 400 characters for best results). + max_results: Maximum number of results to return (default: 10). + search_depth: Search depth - 'basic' (fast, 1 credit) or 'advanced' (thorough, 2 credits). Default is 'basic'. + topic: Search topic category - 'general', 'news', or 'finance'. Default is 'general'. + include_domains: Optional list of domains to restrict search to (e.g., ['wikipedia.org', 'arxiv.org']). + exclude_domains: Optional list of domains to exclude from search results. + time_range: Optional time filter - 'day', 'week', 'month', or 'year'. + + Returns: + JSON string containing search results with title, url, content, and relevance score. + """ + if not TAVILY_API_KEY: + return json.dumps( + { + "success": False, + "error": "TAVILY_API_KEY environment variable not set", + "results": [], + }, + ensure_ascii=False, + ) + + if not query or not query.strip(): + return json.dumps( + { + "success": False, + "error": "Search query is required and cannot be empty", + "results": [], + }, + ensure_ascii=False, + ) + + try: + client = TavilyClient(api_key=TAVILY_API_KEY) + + kwargs = { + "query": query.strip(), + "max_results": max_results, + "search_depth": search_depth, + "topic": topic, + } + if include_domains: + kwargs["include_domains"] = include_domains + if exclude_domains: + kwargs["exclude_domains"] = exclude_domains + if time_range: + kwargs["time_range"] = time_range + + response = client.search(**kwargs) + + return json.dumps(response, ensure_ascii=False) + + except Exception as e: + return json.dumps( + {"success": False, "error": f"Unexpected error: {str(e)}", "results": []}, + ensure_ascii=False, + ) + + +if __name__ == "__main__": + mcp.run()