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