Source code for tavily_fastmcp.tools.search

"""Search tool registration.

Purpose:
    Register the namespaced Tavily search MCP tool.

Design:
    - Convert flat MCP arguments into the canonical :class:`SearchRequest`.
    - Keep request validation inside the shared model layer.
"""

from __future__ import annotations

from typing import Annotated, Any, cast

from pydantic import Field

from tavily_fastmcp._typing import ToolRegistrar
from tavily_fastmcp.models import SearchRequest, SearchResponse
from tavily_fastmcp.service import TavilyServiceProtocol
from tavily_fastmcp.settings import Settings


[docs] def register_search_tool( mcp: Any, *, backend: TavilyServiceProtocol, settings: Settings, ) -> None: """Register the ``tavily.search`` MCP tool. Args: mcp: FastMCP server instance. backend: Tavily service backend. settings: Package settings. Returns: ``None``. """ tool_server = cast(ToolRegistrar, mcp) @tool_server.tool( name="tavily.search", title="Tavily Search", description="Search the web when relevant URLs are not yet known.", tags={"search", "web", "readonly"}, annotations={ "title": "Tavily Search", "readOnlyHint": True, "openWorldHint": True, "idempotentHint": True, }, meta={"profile_hints": ["router", "quick-search"]}, ) async def tavily_search( query: Annotated[str, Field(description="Natural-language web search query.")], max_results: Annotated[ int, Field(ge=1, le=20, description="Maximum number of results.") ] = 5, topic: Annotated[ str, Field(description="Search topic mode.") ] = settings.default_search_topic, include_answer: Annotated[ bool, Field(description="Include Tavily's answer field.") ] = False, include_raw_content: Annotated[ bool, Field(description="Include cleaned page content.") ] = False, include_images: Annotated[bool, Field(description="Include image URLs.")] = False, include_image_descriptions: Annotated[ bool, Field(description="Include image descriptions.") ] = False, search_depth: Annotated[ str, Field(description="Tavily search depth.") ] = settings.default_search_depth, time_range: Annotated[ str | None, Field(description="Relative publish-date filter.") ] = None, start_date: Annotated[ str | None, Field(description="Inclusive start date in YYYY-MM-DD format.") ] = None, end_date: Annotated[ str | None, Field(description="Inclusive end date in YYYY-MM-DD format.") ] = None, include_domains: Annotated[ list[str] | None, Field(description="Domains to include.") ] = None, exclude_domains: Annotated[ list[str] | None, Field(description="Domains to exclude.") ] = None, include_usage: Annotated[bool, Field(description="Include usage metadata.")] = False, ctx: Any | None = None, ) -> SearchResponse: """Search the public web using Tavily. Args: query: Natural-language search query. max_results: Maximum number of results. topic: Tavily topic mode. include_answer: Whether to include Tavily's answer summary. include_raw_content: Whether to include raw page content. include_images: Whether to include image URLs. include_image_descriptions: Whether to include image descriptions. search_depth: Tavily search depth. time_range: Relative time filter. start_date: Inclusive start date. end_date: Inclusive end date. include_domains: Domains to include. exclude_domains: Domains to exclude. include_usage: Whether to include usage metadata. ctx: Optional FastMCP context. Returns: A normalized Tavily search response. """ request = SearchRequest.model_validate( { "query": query, "max_results": max_results, "topic": topic, "include_answer": include_answer, "include_raw_content": include_raw_content, "include_images": include_images, "include_image_descriptions": include_image_descriptions, "search_depth": search_depth, "time_range": time_range, "start_date": start_date, "end_date": end_date, "include_domains": include_domains, "exclude_domains": exclude_domains, "include_usage": include_usage, } ) if ctx is not None: await ctx.info(f"Executing Tavily search for query: {query}") return backend.search_from_model(request)