DEV Community

Cover image for Building a Lightweight Remote Gradio MCP Server with DuckDuckGo Search
0xkoji
0xkoji

Posted on

Building a Lightweight Remote Gradio MCP Server with DuckDuckGo Search

Gradio now lets you spin up an MCP (Machine Control Plane) server with almost no boilerplate, so I decided to give it a try.
The official guide is here: https://www.gradio.app/guides/building-mcp-server-with-gradio

Running everything locally worked great—but because Gradio lives in the Hugging Face ecosystem, I wondered if I could deploy the exact same code to a Space and treat it as a lightweight remote MCP server. Spoiler: you can!


1 Create a project

uv init gradio-mcp-server
cd gradio-mcp-server
Enter fullscreen mode Exit fullscreen mode

uv is the Rust-powered Python package/build tool from Astral. (Any env manager works; uv just makes everything reproducible.)


2 Install Gradio + DuckDuckGo Search

uv add "gradio[mcp]" duckduckgo-search
Enter fullscreen mode Exit fullscreen mode

The [mcp] extra pulls in the small Gradio extension that exposes the MCP interface.


3 Write the code

Building an MCP server is identical to making a regular Gradio app—the only difference is passing mcp_server=True to launch().

Below is the exact main.py generated by uv init, with a single helper function added for news search.

import gradio as gr
from duckduckgo_search import DDGS


def news(
    keywords: str,
    region: str = "wt-wt",
    safesearch: str = "moderate",
    timelimit: str | None = None,
    max_results: int | None = None,
) -> list[dict[str, str]]:
    """
    DuckDuckGo News search. Query-string reference:
    https://duckduckgo.com/params

    Args:
        keywords: Search query.
        region:  e.g. wt-wt, us-en, uk-en. Defaults to "wt-wt".
        safesearch: on | moderate | off. Defaults to "moderate".
        timelimit: d | w | m. If None, no date filter.
        max_results: Max rows to return. None = first batch only.

    Returns:
        List of news result dictionaries.
    """
    # Note: use the incoming `keywords`, not a hard-coded string
    results = DDGS().news(
        keywords=keywords,
        region=region,
        safesearch=safesearch,
        timelimit=timelimit,
        max_results=max_results,
    )
    return list(results)  # The generator → list


demo = gr.Interface(
    fn=news,
    inputs=[
        gr.Textbox(label="Keywords"),
        gr.Dropdown(["wt-wt", "us-en", "uk-en"], label="Region", value="wt-wt"),
        gr.Dropdown(["on", "moderate", "off"], label="Safe Search", value="moderate"),
        gr.Dropdown([None, "d", "w", "m"], label="Time Limit", value=None),
        gr.Number(label="Max Results", value=10),
    ],
    outputs=gr.JSON(),
    title="News Search",
    description="Search news via DuckDuckGo",
)

demo.launch(mcp_server=True)
Enter fullscreen mode Exit fullscreen mode

4 Run the server locally

uv run python main.py
Enter fullscreen mode Exit fullscreen mode

Typical output:

* Running on local URL: http://127.0.0.1:7860
🔨 MCP server (SSE) at:  http://127.0.0.1:7860/gradio_api/mcp/sse
Enter fullscreen mode Exit fullscreen mode

Open http://127.0.0.1:7860 in a browser—Gradio’s built-in UI acts as a handy debug dashboard.


5 Add the server to an MCP client

I’m using Cursor as the client. Copy–paste the following snippet into settings.json (adjust the URL if you changed ports):

"gradio": {
  "command": "npx",
  "args": [
    "mcp-remote",
    "http://127.0.0.1:7860/gradio_api/mcp/sse",
    "--transport",
    "sse-only"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Cursor immediately detected the server:

Cursor MCP target


6 Quick test

In Cursor’s chat pane, type:

Tell me the latest news about Real Madrid.
Enter fullscreen mode Exit fullscreen mode

cursor_test

Even with max_results=10, DuckDuckGo sometimes returns fewer hits (I got four). But the round-trip works, and the Gradio interface logs every request.


7 Deploy to Hugging Face Spaces

I initially created a fresh Space but the build kept failing.
Forking wjlgatech/mcp-demo1 and pushing my code worked out of the box:

https://huggingface.co/spaces/baxin/news-search-mcp

It’s noticeably slower than localhost, and occasionally the client fails to detect the endpoint on the first try. Still, it’s perfectly usable.

"gradio": {
  "command": "npx",
  "args": [
    "mcp-remote",
    "https://baxin-newssearch-mcp.hf.space/gradio_api/mcp/sse",
    "--transport",
    "sse-only"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Remote MCP server


Closing thoughts

Compared with the TypeScript version I wrote a while back, this Python + Gradio approach needs far less boilerplate, and the built-in debug UI is a pleasant bonus. For small one-off tools, turning any Gradio app into a portable MCP server feels almost too easy.

If you run into flaky connections on Spaces, try:

  • Enabling share=True in launch() (sometimes steadies SSE handshakes).
  • Switching from SSE-only to websocket transport.
  • Restarting the Space—you won’t lose your files.

Happy hacking!

DevCycle image

Fast, Flexible Releases with OpenFeature Built-in

Ship faster on the first feature management platform with OpenFeature built-in to all of our open source SDKs.

Start shipping

Top comments (0)

DevCycle image

Ship Faster, Stay Flexible.

DevCycle is the first feature flag platform with OpenFeature built-in to every open source SDK, designed to help developers ship faster while avoiding vendor-lock in.

Start shipping

👋 Kindness is contagious

Explore this insightful write-up, celebrated by our thriving DEV Community. Developers everywhere are invited to contribute and elevate our shared expertise.

A simple "thank you" can brighten someone’s day—leave your appreciation in the comments!

On DEV, knowledge-sharing fuels our progress and strengthens our community ties. Found this useful? A quick thank you to the author makes all the difference.

Okay