DEV Community

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

Posted on

1

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!

Sentry image

Tutorial: Debugging with Cursor + Sentry MCP

No more copying and pasting error messages, logs, or trying to describe your distributed tracing setup or stack traces in chat. MCP can investigate real issues, understand their impact, and suggest fixes based on the actual production context.

👀 See how →

Top comments (0)

Heroku

Build AI apps faster with Heroku.

Heroku makes it easy to build with AI, without the complexity of managing your own AI services. Access leading AI models and build faster with Managed Inference and Agents, and extend your AI with MCP.

Get Started

👋 Kindness is contagious

Sign in to DEV to enjoy its full potential.

Unlock a customized interface with dark mode, personal reading preferences, and more.

Okay