Context Course documentation
Gradio MCP Integration: Web UIs + MCP Servers
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:
- Converts your functions into MCP tools automatically
- Creates a web interface for humans
- Exposes an MCP endpoint for agents
- 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.0Type 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
- Go to huggingface.co/new-space
- Name your Space
- Select “Gradio” as the SDK
- 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.0Your 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 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:
- Check Space is running (not in error state)
- Use correct endpoint:
https://user-space.hf.space/gradio_api/mcp/ - Test with curl:
curl https://user-space.hf.space/gradio_api/mcp/ - 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
- Keep functions fast — Agents timeout after ~60 seconds
- Validate inputs — Check data size before processing
- Handle errors gracefully — Return error messages instead of crashing
- Cache expensive results — Avoid redundant computation
- 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