Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/miroflow-agent/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions apps/miroflow-agent/conf/agent/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions apps/miroflow-agent/src/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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 is required when tool-tavily-search is configured but is not set."
)

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(
{
Expand Down Expand Up @@ -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),
Expand Down
3 changes: 2 additions & 1 deletion libs/miroflow-tools/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ dependencies = [
"markitdown-mcp>=0.0.1a3",
"google-genai",
"aiohttp",
"redis"
"redis",
"tavily-python>=0.5.0"
]

[build-system]
Expand Down
Original file line number Diff line number Diff line change
@@ -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()