Skip to main content
Glama
KSroido

kagi-session2api-mcp

by KSroido

kagi_search_fetch

Fetch web search results from Kagi for one or more queries. Results are numbered continuously so users can refer to specific results.

Instructions

Fetch web results based on one or more queries using Kagi Search.

Use for general search and when the user explicitly tells you to 'fetch' results/information. Results are from all queries given. They are numbered continuously, so that a user may be able to refer to a result by a specific number.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queriesYesOne or more concise, keyword-focused search queries. Include essential context within each query for standalone use. Supports Kagi operators: site:, -site:, filetype:/ext:, intitle:, inurl:, lang:, loc:, before:, after:, "exact phrase", +term, -term
limitNoMaximum number of results per query. Default: all available.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The main handler function for the 'kagi_search_fetch' MCP tool. Decorated with @mcp.tool(), it accepts a list of search queries and an optional limit, executes them concurrently via asyncio.gather, and formats results using format_search_results.
    @mcp.tool()
    async def kagi_search_fetch(
        queries: list[str] = Field(
            description=(
                "One or more concise, keyword-focused search queries. "
                "Include essential context within each query for standalone use. "
                "Supports Kagi operators: site:, -site:, filetype:/ext:, intitle:, "
                'inurl:, lang:, loc:, before:, after:, "exact phrase", +term, -term'
            )
        ),
        limit: int | None = Field(
            default=None,
            description="Maximum number of results per query. Default: all available.",
        ),
    ) -> str:
        """Fetch web results based on one or more queries using Kagi Search.
    
        Use for general search and when the user explicitly tells you to
        'fetch' results/information. Results are from all queries given.
        They are numbered continuously, so that a user may be able to refer
        to a result by a specific number.
        """
        if not queries:
            raise ValueError("Search called with no queries.")
    
        if client is None:
            raise RuntimeError("Server not initialized. Session client is missing.")
    
        # Execute all queries concurrently using asyncio.gather for parallel I/O
        query_response_pairs = await asyncio.gather(
            *[_execute_single_query(query, limit) for query in queries]
        )
    
        # Preserve original query ordering for consistent result numbering
        responses = [response for _, response in query_response_pairs]
    
        return format_search_results(queries, responses)
  • Pydantic Field definitions for the 'kagi_search_fetch' tool parameters: 'queries' (list of keyword search strings with Kagi operators) and 'limit' (max results per query).
    @mcp.tool()
    async def kagi_search_fetch(
        queries: list[str] = Field(
            description=(
                "One or more concise, keyword-focused search queries. "
                "Include essential context within each query for standalone use. "
                "Supports Kagi operators: site:, -site:, filetype:/ext:, intitle:, "
                'inurl:, lang:, loc:, before:, after:, "exact phrase", +term, -term'
            )
        ),
        limit: int | None = Field(
            default=None,
            description="Maximum number of results per query. Default: all available.",
        ),
  • Tool registration via the @mcp.tool() decorator on the kagi_search_fetch async function, which registers it with the FastMCP server instance.
    @mcp.tool()
  • Helper function _execute_single_query that executes a single search query: fetches HTML via client.search_html(), parses it via parse_search_html(), and applies the limit. Used by kagi_search_fetch in the asyncio.gather call.
    async def _execute_single_query(
        query: str, limit: int | None
    ) -> tuple[str, "SearchResponse"]:
        """Execute a single search query and return (query, response) tuple.
    
        Runs the HTML fetch and parse pipeline with timing for elapsed_ms.
    
        Args:
            query: Search query string
            limit: Maximum number of results to return, or None for all
    
        Returns:
            Tuple of (original query, SearchResponse)
        """
        start = time.monotonic()
        html = await client.search_html(query)
        elapsed_ms = int((time.monotonic() - start) * 1000)
        response = parse_search_html(html, query, elapsed_ms)
    
        # Apply limit if specified
        if limit is not None and limit > 0:
            response.data = response.data[:limit]
    
        return (query, response)
  • format_search_results helper that formats SearchResponse objects into numbered text output, used by kagi_search_fetch to produce the final string result.
    def format_search_results(
        queries: list[str], responses: list[SearchResponse]
    ) -> str:
        """Format search results as numbered text, matching official kagimcp output format.
    
        Results from all queries are numbered continuously so that users can
        refer to a specific result by its number.
    
        Args:
            queries: List of search queries
            responses: List of SearchResponse objects (one per query)
    
        Returns:
            Formatted text string with numbered results
        """
        if not queries or not responses:
            return "No search results found."
    
        parts: list[str] = []
        result_number = 0
    
        for query_idx, (query, response) in enumerate(zip(queries, responses)):
            # Add query header
            if len(queries) > 1:
                parts.append(f"## Query: {query}\n")
            else:
                parts.append(f"## Search results for: {query}\n")
    
            if not response.data:
                parts.append("No results found for this query.\n")
                continue
    
            for item in response.data:
                if isinstance(item, SearchResult):
                    result_number += 1
                    parts.append(_format_search_result(item, result_number))
                elif isinstance(item, RelatedSearches):
                    parts.append(_format_related_searches(item))
    
            # Add separator between queries
            if query_idx < len(queries) - 1:
                parts.append("---\n")
    
        return "\n".join(parts)
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It mentions numbering and that results are from all queries, but fails to disclose other behavioral traits such as pagination, caching, or auth requirements.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise, with three sentences that are front-loaded with purpose. Every sentence adds value without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the presence of an output schema, return values need not be explained. The description is adequate for basic usage, though it could mention rate limits or error handling for completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, and the description adds meaning by specifying that queries should be concise and keyword-focused, with essential context. The limit parameter's default behavior is explained.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it fetches web results using Kagi Search, for general search and when the user explicitly asks to 'fetch'. It distinguishes from the sibling tool kagi_summarizer by focusing on search results rather than summaries.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains when to use (general search, explicit fetch) and notes that results from all queries are numbered continuously. While it doesn't explicitly state when not to use, the sibling context implies summarization is separate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/KSroido/Kagi-Session2API-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server