def web_search(query: str) -> str:
    if not query:
        raise MissingOrEmpty(name="query")

    results = DDGS().text(query, backend="bing, duckduckgo", max_results=10)
    answer = format_search_results(query, results)

    return answer


def web_fetch(url: str) -> str:
   from httpx import AsyncClient, HTTPError

    async with AsyncClient(proxies=proxy_url) as client:
        headers = {
            "User-Agent": user_agent,
        }

        try:
            response = await client.get(url, follow_redirects=True, headers=headers, timeout=30)

        except HTTPError as e:
            raise McpError(ErrorData(code=INTERNAL_ERROR, message=f"Failed to fetch {url}: {e!r}"))

        if response.status_code >= 400:
            raise McpError(ErrorData(
                code=INTERNAL_ERROR,
                message=f"Failed to fetch {url} - status {response.status_code}",
            ))

        page_raw = response.text

    content_type = response.headers.get("content-type", "")
    is_page_html = (
        "<html" in page_raw[:100] or "text/html" in content_type or not content_type
    )

    if is_page_html and not force_raw:
        return extract_content_from_html(page_raw), ""

    return (
        page_raw,
        f"Content type {content_type} cannot be simplified to markdown, but here is the raw content:\n",
    )
    ret = readabilipy.simple_json.simple_json_from_html_string(
        html, use_readability=True
    )
    if not ret["content"]:
        return "<error>Page failed to be simplified from HTML</error>"
    content = markdownify.markdownify(
        ret["content"],
        heading_style=markdownify.ATX,
    )
    return content

async def fetch_url(
    url: str, user_agent: str, force_raw: bool = False, proxy_url: str | None = None
) -> Tuple[str, str]:
    """
    Fetch the URL and return the content in a form ready for the LLM, as well as a prefix string with status information.
    """
    from httpx import AsyncClient, HTTPError

    async with AsyncClient() as client:
        headers = {
            'User-Agent': user_agent,
        }

        try:
            response = await client.get( url, follow_redirects=True, headers=headers, timeout=30,)

        except HTTPError as e:
            raise McpError(ErrorData(code=INTERNAL_ERROR, message=f"Failed to fetch {url}: {e!r}"))

        if response.status_code >= 400:
            raise McpError(ErrorData(
                code=INTERNAL_ERROR,
                message=f"Failed to fetch {url} - status code {response.status_code}",
            ))

        page_raw = response.text

    content_type = response.headers.get("content-type", "")
    is_page_html = (
        "<html" in page_raw[:100] or "text/html" in content_type or not content_type
    )

    if is_page_html and not force_raw:
        return extract_content_from_html(page_raw), ""

    return (
        page_raw,
        f"Content type {content_type} cannot be simplified to markdown, but here is the raw content:\n",
    )

def format_search_results(query: str, results) -> str:
    template = textwrap.dedent("""
        index  : {number}
        title  : {title}
        href   : {href}
        summary: {body}
    """).strip()

    results_formatted = [
        template.format(number=i, **result)
        for i, result in enumerate(results)
    ]

    return "\n\n".join(results_formatted)
