AI  

Building Gradio MCP Servers: A Practical Guide

Abstract / Overview

Gradio can expose MCP tools from standard Python apps with one flag. This article shows how to launch, test, and deploy a Gradio-backed MCP server; connect it to popular MCP clients; and ship production-grade tools. Scope includes resources, prompts, progress streaming, MCP-only tools, OpenAPI wrapping, token handling, and Spaces deployment.

ChatGPT Image Sep 5, 2025, 09_43_39 AM

Conceptual Background

Model Context Protocol (MCP) standardizes tool invocation, resources, and prompts so clients and servers interoperate. Gradio maps Python callables and annotated assets to MCP schemas, then serves them over a streaming endpoint. Result: one codebase provides a browser UI for humans and a protocol surface for agents and IDE copilots.

Key ideas:

  • A Gradio app can emit MCP metadata and an SSE stream for tool calls.

  • Functions become tools when exposed through Gradio interfaces or APIs.

  • Decorators surface resources and prompts as discoverable MCP artifacts.

  • Most MCP clients connect directly. A bridge converts remote SSE to stdio when needed.

  • Hosting on Hugging Face Spaces yields a public MCP endpoint with optional auth.

Step-by-Step Walkthrough

Install Gradio with MCP extras

Use pip.

pip install "gradio[mcp]"

Create a minimal tool

Define a typed function and launch the app with MCP enabled.

import gradio as gr

def letter_counter(word: str, letter: str) -> int:
    """Count occurrences of `letter` in `word`."""
    return word.lower().count(letter.lower())

demo = gr.Interface(
    fn=letter_counter,
    inputs=["text", "text"],
    outputs="number",
    title="Letter Counter",
    description="Count how many times a letter appears in a word."
)

if __name__ == "__main__":
    demo.launch(mcp_server=True)

Run the script. Note the local URL for UI and the MCP server information in logs.

Connect from an MCP client

If your client supports remote SSE, configure it to point at the server’s MCP SSE endpoint. If it requires stdio, run a bridge:

npx mcp-remote http://YOUR-HOST:PORT/gradio_api/mcp/sse

Keep the bridge process running and let your client talk to it locally.

Inspect tools and schema

Open the app footer’s API links or hit the MCP schema endpoint in the browser to view tool names, input schemas, and types. Confirm your function names, docstrings, and annotations are reflected.

Deploy on Hugging Face Spaces

Push your repo to a Space with a requirements.txt and a launch script. For private Spaces, include an Authorization header in the client or via your bridge. Free CPU hardware suffices for light workloads; upgrade when traffic grows or you need GPUs.

Add resources and prompts

Use decorators to expose read-only data and reusable prompt templates.

import gradio as gr

@gr.mcp.tool()
def add(a: int, b: int) -> int:
    """Return the sum of two integers."""
    return a + b

@gr.mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
    """Deterministic greeting text."""
    return f"Hello, {name}!"

@gr.mcp.prompt()
def email_prompt(name: str, tone: str = "formal") -> str:
    """Templated instruction usable by clients."""
    if tone == "formal":
        return f"Write a concise professional email to {name}."
    if tone == "friendly":
        return f"Write a warm, concise email to {name}."
    return f"Write a brief email to {name}."

Expose MCP-only tools

Hide implementation functions from the web UI while keeping them available to MCP clients.

import gradio as gr

def slice_list(items: list[int], start: int, end: int) -> list[int]:
    """Return a slice of the list."""
    return items[start:end]

with gr.Blocks() as demo:
    gr.Markdown("MCP-only tools are hidden from the UI.")
    gr.api(slice_list)  # exposed to MCP; not rendered as a UI component

demo.launch(mcp_server=True)

Handle tokens and headers

Use typed headers so clients see documented requirements and you receive validated inputs.

import gradio as gr

def call_backend(prompt: str, x_api_token: gr.Header) -> str:
    """Proxy to downstream service using a user token."""
    if not x_api_token:
        raise ValueError("Missing X-API-Token header.")
    # downstream call using x_api_token...
    return f"Processed: {prompt}"

gr.Interface(call_backend, gr.Textbox(), gr.Textbox()).launch(mcp_server=True)

Stream progress for long tasks

Emit periodic updates so clients show status instead of appearing idle.

import gradio as gr, time

def slow_reverse(text: str, progress=gr.Progress()):
    n = len(text)
    for i in range(n):
        progress(i / n, desc="Reversing")
        time.sleep(0.05)
    return text[::-1]

gr.Interface(slow_reverse, gr.Textbox(), gr.Textbox()).launch(mcp_server=True) 

Wrap existing REST APIs via OpenAPI

Turn an OpenAPI spec into callable tools with one construct.

import gradio as gr

demo = gr.load_openapi(
    openapi_spec="https://petstore3.swagger.io/api/v3/openapi.json",
    base_url="https://petstore3.swagger.io/api/v3",
    paths=["/pet.*"],
    methods=["get", "post"]
)
demo.launch(mcp_server=True)

Code Snippets

The following blocks are runnable and omit any JSON configuration.

Minimal letter counter (UI + MCP)

import gradio as gr

def letter_counter(word: str, letter: str) -> int:
    return word.lower().count(letter.lower())

gr.Interface(letter_counter, ["text", "text"], "number").launch(mcp_server=True)

Bridge for clients that lack remote SSE

npx mcp-remote https://YOUR-SPACE.hf.space/gradio_api/mcp/sse

Resources, prompts, and MCP-only APIs together

import gradio as gr

@gr.mcp.tool()
def concat(a: str, b: str) -> str:
    return a + b

@gr.mcp.resource("doc://intro")
def intro() -> str:
    return "Welcome to the tool surface."

@gr.mcp.prompt()
def rewriter(style: str = "short") -> str:
    return "Rewrite the input in a " + style + " style."

def hidden_math(x: int) -> int:
    return x * x

with gr.Blocks() as demo:
    gr.Markdown("Public UI components, internal MCP tools.")
    gr.api(hidden_math)  # MCP-only
    gr.Button("Ready")

demo.launch(mcp_server=True)

Token header example

import gradio as gr

def gated(name: str, authorization: gr.Header) -> str:
    if not authorization:
        raise ValueError("Missing Authorization header.")
    return f"Hello {name}, token accepted."

gr.Interface(gated, gr.Textbox(), gr.Textbox()).launch(mcp_server=True)

Progress reporting example

import gradio as gr, time

def work(x: int, progress=gr.Progress()):
    for i in range(x):
        progress((i + 1) / x, desc="Working")
        time.sleep(0.1)
    return "done"

gr.Interface(work, gr.Number(), gr.Textbox()).launch(mcp_server=True)

OpenAPI wrapper example

import gradio as gr
demo = gr.load_openapi(
    openapi_spec="https://petstore3.swagger.io/api/v3/openapi.json",
    base_url="https://petstore3.swagger.io/api/v3"
)
demo.launch(mcp_server=True)

Use Cases / Scenarios

  • IDE copilots that call your domain tools with type safety.

  • Analytics assistants that read resources and call internal REST endpoints.

  • Lightweight data catalogs exposed as deterministic MCP resources.

  • Public demo tools hosted on Spaces for easy evaluation.

  • Enterprise gateways that enforce per-user tokens and audit events.

gradio-mcp-server-flow-bridge-tools-resources-prompts

Conclusion

Gradio’s MCP integration lets one codebase serve both humans and agents. Launch with a single flag. Add typed tools, resources, and prompts. Stream progress for long tasks. Wrap legacy REST surfaces via OpenAPI. Deploy on Spaces for reach, and bridge where clients lack remote SSE. This yields a clean, discoverable tool surface that scales from prototype to production.
References: https://huggingface.co/blog/gradio-mcp