Context Course documentation

Gradio MCP Integration: Web UIs + MCP Servers

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Gradio MCP Integration: Web UIs + MCP Servers

This lesson picks up where the previous server-building lesson left off. You already saw the minimal mcp_server=True pattern; here the goal is to go deeper on when Gradio is a good fit, how to expose MCP-only surfaces, and how to deploy the result safely.

What Is Gradio MCP Integration?

Gradio is a Python library for building web UIs for machine learning models and data processing tools. When you enable MCP support, Gradio:

  1. Converts your functions into MCP tools automatically
  2. Creates a web interface for humans
  3. Exposes an MCP endpoint for agents
  4. Handles JSON-RPC serialization and protocol details

You get two interfaces for the price of one: humans use the web UI, agents use MCP.

Installing Gradio with MCP Support

Install Gradio with MCP support:

pip install "gradio[mcp]"

Create a requirements.txt for deployment:

gradio>=4.0.0

Type hints and docstrings are essential. Gradio uses them to generate the MCP tool schema. Always include:

  • Type hints on all parameters and return value
  • A docstring description
  • An Args: section documenting each parameter
  • A Returns: section describing the output

Building a Practical Gradio MCP App

Instead of another hello-world example, let’s build a small text toolkit that is useful both in a browser and through MCP:

import gradio as gr
import json

def analyze_text(text: str) -> str:
    """Analyze text and compute statistics.
    
    Args:
        text: The input text to analyze
    
    Returns:
        JSON with analysis results
    """
    words = text.split()
    chars = len(text)
    
    return json.dumps({
        "words": len(words),
        "characters": chars,
        "average_word_length": round(chars / len(words), 2) if words else 0
    })

def reverse_text(text: str) -> str:
    """Reverse a string.
    
    Args:
        text: Input text
    
    Returns:
        The reversed text
    """
    return text[::-1]

def count_vowels(text: str) -> int:
    """Count vowels in text.
    
    Args:
        text: Input text
    
    Returns:
        Number of vowels
    """
    vowels = "aeiouAEIOU"
    return sum(1 for char in text if char in vowels)

# Create interface
with gr.Blocks(title="Text Tools") as demo:
    gr.Markdown("# Text Processing Tools")
    
    with gr.Tab("Analyze Text"):
        text_input1 = gr.Textbox(label="Enter text", lines=5)
        analysis_output = gr.Textbox(label="Analysis", lines=5)
        gr.Button("Analyze").click(analyze_text, text_input1, analysis_output)
    
    with gr.Tab("Reverse Text"):
        text_input2 = gr.Textbox(label="Enter text", lines=5)
        reverse_output = gr.Textbox(label="Reversed", lines=5)
        gr.Button("Reverse").click(reverse_text, text_input2, reverse_output)
    
    with gr.Tab("Count Vowels"):
        text_input3 = gr.Textbox(label="Enter text")
        vowel_output = gr.Number(label="Vowel Count")
        gr.Button("Count").click(count_vowels, text_input3, vowel_output)

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

Each function becomes a separate MCP tool that agents can call independently.

Advanced Gradio MCP Features

Exposing Resources with @gr.mcp.resource()

Define read-only data resources that agents can access:

import gradio as gr

@gr.mcp.resource("config://api")
def api_documentation() -> str:
    """API documentation and endpoints."""
    return """# API Documentation
    
## Endpoints
- GET /users - List all users
- POST /users - Create user
- GET /users/{id} - Get specific user

## Authentication
Use Bearer token in Authorization header.
"""

# Your tools and UI here...

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

Resources are identified by URIs and provide static or semi-static content that agents can read.

MCP-Only Functions with @gr.api()

Define tools that exist only in MCP, not in the web UI:

import gradio as gr

@gr.api()
def query_database(sql: str) -> str:
    """Execute a database query (MCP only).
    
    Args:
        sql: SQL query string
    
    Returns:
        Query results
    """
    # Only accessible via MCP, not web UI
    return "Query results..."

@gr.api()
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email (MCP only).
    
    Args:
        to: Recipient email
        subject: Email subject
        body: Email body
    
    Returns:
        Confirmation message
    """
    # Only accessible via MCP
    return f"Email sent to {to}"

# Regular UI components here...

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

Authentication

Gradio’s auth argument on launch() gates the web UI with HTTP basic auth. It does not protect the /gradio_api/mcp/ MCP endpoint — by design, agents can reach it without logging in.

import gradio as gr

def authenticate(username: str, password: str) -> bool:
    return username == "admin" and password == "secret"

# `auth` protects the web UI only; MCP remains open.
if __name__ == "__main__":
    demo.launch(mcp_server=True, auth=authenticate)

For production, authenticate the MCP endpoint outside Gradio: put the app behind a reverse proxy that enforces auth on /gradio_api/mcp/, deploy it as a private Hugging Face Space, or use an MCP gateway that implements the MCP Authorization spec.

Deploying to Hugging Face Spaces

Hugging Face Spaces provides free hosting for Gradio apps with MCP support.

Step 1: Create a New Space

  1. Go to huggingface.co/new-space
  2. Name your Space
  3. Select “Gradio” as the SDK
  4. Create the Space

Step 2: Upload Your Code

Create app.py in your Space:

import gradio as gr

def my_tool(input_text: str) -> str:
    """Do something with input.
    
    Args:
        input_text: The input
    
    Returns:
        The result
    """
    return f"Processed: {input_text}"

demo = gr.Interface(fn=my_tool, inputs="text", outputs="text")

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

Step 3: Add Dependencies (if needed)

Create requirements.txt:

gradio>=4.0.0
requests>=2.28.0

Your Space automatically installs dependencies on each build.

Step 4: Access Your MCP Server

Once deployed, your MCP server is available at:

https://[your-username]-[space-name].hf.space/gradio_api/mcp/

Configure your agent to use it:

Claude Code
Codex
OpenCode
Pi
claude mcp add --transport http --scope user my-tools https://yourname-space-name.hf.space/gradio_api/mcp/

Your Space’s MCP endpoint uses Streamable HTTP transport. Configuration changes take effect immediately without agent restarts.

Function Signature Best Practices

For optimal MCP tool generation, follow these patterns:

# Good: Clear types, docstring, Args/Returns sections
def process_data(data: str, format: str = "json") -> str:
    """Process data in specified format.
    
    Args:
        data: Input data string
        format: Output format (default: json)
    
    Returns:
        Processed data as string
    """
    return data

# Good: Handles multiple parameters
def calculate(a: float, b: float, operation: str) -> float:
    """Perform mathematical operation.
    
    Args:
        a: First number
        b: Second number
        operation: Operation (add, subtract, multiply, divide)
    
    Returns:
        Result of operation
    """
    if operation == "add":
        return a + b
    # ... more operations

# Avoid: Missing type hints
def bad_function(data):  # No return type!
    return data

# Avoid: No docstring
def also_bad(text: str) -> str:
    return text.upper()

# Avoid: Complex types without clarification
def confusing(data: dict) -> list:  # What's in the dict/list?
    return []

Best practices:

  • Use simple, clear type hints (str, int, float, bool, list)
  • Include docstring with description
  • Document every parameter in Args section
  • Describe the return value in Returns section
  • Return strings, numbers, or lists (simple types)
  • Return JSON strings for complex data

Troubleshooting Gradio MCP

Functions Not Appearing as MCP Tools

Check your function signatures:

  • Must have type hints on all parameters
  • Must have a docstring
  • Docstring must include “Args:” section
  • Return type must be specified
# This will work
def my_tool(text: str) -> str:
    """Process text.
    
    Args:
        text: Input text
    
    Returns:
        Processed text
    """
    return text.upper()

# This won't work (no docstring)
def bad_tool(text: str) -> str:
    return text.upper()

MCP Endpoint Not Responding

If the endpoint doesn’t work:

  1. Check Space is running (not in error state)
  2. Use correct endpoint: https://user-space.hf.space/gradio_api/mcp/
  3. Test with curl: curl https://user-space.hf.space/gradio_api/mcp/
  4. Check Space logs for errors

Type Mismatch Errors

Agents may fail if return types don’t match specification:

# Correct: Returns string as declared
def correct(x: int) -> str:
    return str(x * 2)

# Wrong: Declares string but returns int
def wrong(x: int) -> str:
    return x * 2  # Returns int, not string!

Always ensure actual return types match declared types.

Performance Tips for Production Spaces

  1. Keep functions fast — Agents timeout after ~60 seconds
  2. Validate inputs — Check data size before processing
  3. Handle errors gracefully — Return error messages instead of crashing
  4. Cache expensive results — Avoid redundant computation
  5. Monitor resource usage — Spaces have limited CPU/memory
import time

def analyze(data: str) -> str:
    """Analyze large datasets.
    
    Args:
        data: Input data
    
    Returns:
        Analysis results
    """
    # Reject too-large inputs
    if len(data) > 1_000_000:
        return "Error: Input too large (max 1MB)"
    
    # Show progress
    start = time.time()
    # ... do analysis
    elapsed = time.time() - start
    
    if elapsed > 30:
        return "Error: Processing took too long"
    
    return "Results..."

Key Takeaways

mcp_server=True gives you a web UI and an MCP server from the same Gradio app. Gradio relies on type hints and docstrings to build the tool schemas, so keep both tight. Spaces is a quick way to host MCP servers publicly; @gr.api() hides tools from the UI, and @gr.mcp.resource() exposes read-only data.

Next we’ll put these pieces together in a hands-on project.

Update on GitHub