1. MCP
  2. 使用 Gradio 构建 MCP 服务器

构建MCP服务器与Gradio

本指南将介绍如何启动您的Gradio应用程序,使其能够作为MCP服务器运行。

要点:只需在.launch()中设置mcp_server=True即可。

先决条件

如果您尚未安装,请使用MCP扩展名安装Gradio

pip install "gradio[mcp]"

这将安装必要的依赖项,包括mcp包。此外,您还需要一个支持使用MCP协议进行工具调用的LLM应用程序,例如Claude Desktop、Cursor或Cline(这些被称为“MCP客户端”)。

什么是MCP服务器?

MCP(模型控制协议)服务器是一种标准化的方式来暴露工具,以便大型语言模型(LLM)可以使用它们。工具可以提供LLM本身不具备的本地功能,例如生成图像或计算数字的素数因子。

示例:计算单词中的字母数量

LLMs在计算单词中的字母数量方面(例如,“strawberry”中有多少个“r”)是出了名的不擅长。但是,如果我们为它们配备一个工具来帮助它们呢?让我们开始编写一个简单的Gradio应用程序来计算单词或短语中的字母数量。

import gradio as gr

def letter_counter(word, letter):
    """
    Count the number of occurrences of a letter in a word or text.

    Args:
        word (str): The input text to search through
        letter (str): The letter to search for

    Returns:
        str: A message indicating how many times the letter appears
    """
    word = word.lower()
    letter = letter.lower()
    count = word.count(letter)
    return count

demo = gr.Interface(
    fn=letter_counter,
    inputs=[gr.Textbox("strawberry"), gr.Textbox("r")],
    outputs=[gr.Number()],
    title="Letter Counter",
    description="Enter text and a letter to count how many times the letter appears in the text.",
    api_name="predict"
)

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

请注意,我们做了两件事:(1)为我们的函数包含了一个详细的文档字符串,以及(2)在.launch()中设置了mcp_server=True。这就是让您的Gradio应用程序充当MCP服务器所需的一切!现在,当您运行此应用程序时,它将:

  1. 启动常规的Gradio Web界面
  2. 启动MCP服务器
  3. 在控制台打印MCP服务器URL

MCP服务器将可通过以下方式访问:

http://your-server:port/gradio_api/mcp/

Gradio会自动将letter_counter函数转换为MCP工具,LLM可以调用该工具。函数的文档字符串和参数的类型提示将用于生成工具及其参数的描述。函数名将用作工具名。您为输入组件提供的任何初始值(例如,上面gr.Textbox组件中的“strawberry”和“r”)如果LLM未为特定输入参数指定值,将用作默认值。

现在,您需要做的就是将此URL端点添加到您的MCP客户端(例如,Claude Desktop、Cursor或Cline)中,这通常意味着将此配置粘贴到设置中。

{
  "mcpServers": {
    "gradio": {
      "url": "http://your-server:port/gradio_api/mcp/"
    }
  }
}

(顺便说一句,您可以在Gradio应用程序页脚的“查看API”链接中找到要复制粘贴的确切配置,然后点击“MCP”。)

Gradio <> MCP集成的主要特性

  1. 工具转换:您的Gradio应用程序中的每个API端点都会自动转换为一个MCP工具,具有相应的名称、描述和输入模式。要查看工具和模式,请访问http://your-server:port/gradio_api/mcp/schema,或转到Gradio应用程序页脚的“查看API”链接,然后点击“MCP”。
  1. 环境变量支持。有两种方法可以启用MCP服务器功能
  • 使用mcp_server参数,如上所示

    demo.launch(mcp_server=True)
  • 使用环境变量

    export GRADIO_MCP_SERVER=True
  1. 文件处理:Gradio MCP服务器自动处理文件数据转换,包括

    • 处理图像文件并以正确的格式返回

    • 管理临时文件存储

      默认情况下,Gradio MCP服务器接受URL格式的输入图像和文件(“http://...”或“https:/...”)。为了方便起见,还生成了一个额外的基于STDIO的MCP服务器,可用于将文件上传到任何远程Gradio应用程序,并返回一个可用于后续工具调用的URL。

  2. 托管在󠀠🤗 Spaces上的MCP服务器:您可以免费在Hugging Face Spaces上发布您的Gradio应用程序,这将使您能够拥有一个免费的托管MCP服务器。这是一个此类Spaces的示例:https://hugging-face.cn/spaces/abidlabs/mcp-tools。请注意,您可以将此配置添加到您的MCP客户端中,以便立即开始使用此Spaces中的工具。

{
  "mcpServers": {
    "gradio": {
      "url": "https://abidlabs-mcp-tools.hf.space/gradio_api/mcp/"
    }
  }
}

转换现有Spaces

如果您想将现有的Spaces用作MCP服务器,您需要做三件事:

  1. 首先,复制该Spaces(如果它不是您自己的)。这将允许您对应用程序进行更改。如果Spaces需要GPU,请将复制的Spaces的硬件设置为与原始Spaces相同。您可以将其设为公共Spaces或私有Spaces,因为它们都可以用作MCP服务器,如下所述。
  2. 然后,为希望LLM能够作为工具调用的函数添加文档字符串。文档字符串应与上面示例代码的格式相同。
  3. 最后,在.launch()中添加mcp_server=True

就是这样!

私有Spaces

您可以使用公共Spaces或私有Spaces作为MCP服务器。如果您想使用私有Spaces作为MCP服务器(或具有您自己配额的ZeroGPU Spaces),则在发起请求时需要提供您的Hugging Face令牌。为此,只需在配置中添加它作为标头,如下所示:

{
  "mcpServers": {
    "gradio": {
      "url": "https://abidlabs-mcp-tools.hf.space/gradio_api/mcp/",
      "headers": {
        "Authorization": "Bearer <YOUR-HUGGING-FACE-TOKEN>"
      }
    }
  }
}

身份验证和凭据

您可能希望更精确地验证用户身份,或允许他们提供其他类型的凭据或令牌,以便为不同用户提供自定义体验。

Gradio允许您访问发起工具调用的底层starlette.Request,这意味着您可以访问标头、原始IP地址或网络请求的任何其他信息。为此,只需在函数中添加一个类型为gr.Request的参数,Gradio将自动将请求对象作为参数注入。

这是一个例子

import gradio as gr

def echo_headers(x, request: gr.Request):
    return str(dict(request.headers))

gr.Interface(echo_headers, "textbox", "textbox").launch(mcp_server=True)

此MCP服务器将简单地忽略用户的输入,并回显用户请求的所有标头。可以使用相同的思路构建更复杂的应用程序。有关更多信息,请参阅有关gr.Request文档(请注意,只有gr.Request对象的核心Starlette属性将存在,而诸如Gradio的.session_hash之类的属性将不存在)。

使用gr.Header类

MCP服务器开发中的一个常见模式是使用身份验证标头来代表用户调用服务。而不是像上面的示例那样使用gr.Request对象,您可以使用gr.Header参数。Gradio将自动从传入的请求中提取该标头(如果存在),并将其传递给您的函数。

在下面的示例中,X-API-Token标头从传入的请求中提取,并作为x_api_token参数传递给make_api_request_on_behalf_of_user

使用gr.Header的好处是,MCP连接文档将自动显示连接服务器时需要提供的标头!请参见下图。

import gradio as gr

def make_api_request_on_behalf_of_user(prompt: str, x_api_token: gr.Header):
    """Make a request to everyone's favorite API.
    Args:
        prompt: The prompt to send to the API.
    Returns:
        The response from the API.
    Raises:
        AssertionError: If the API token is not valid.
    """
    return "Hello from the API" if not x_api_token else "Hello from the API with token!"


demo = gr.Interface(
    make_api_request_on_behalf_of_user,
    [
        gr.Textbox(label="Prompt"),
    ],
    gr.Textbox(label="Response"),
)

demo.launch(mcp_server=True)

MCP Header Connection Page

发送进度更新

Gradio MCP服务器会根据Gradio应用程序的队列自动向您的MCP客户端发送进度更新。如果您想发送自定义进度更新,您可以使用与在Gradio应用程序UI中显示进度更新相同的方法:使用gr.Progress类!

以下是如何做到这一点的一个例子

import gradio as gr
import time

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


demo = gr.Interface(slow_text_reverser, gr.Textbox("Hello, world!"), gr.Textbox(), api_name="predict")

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

这是gr.Progress类的文档,它还可以自动跟踪tqdm调用。

注意:默认情况下,进度通知为所有MCP工具启用,即使相应的Gradio函数不包含gr.Progress。但是,这可能会给MCP工具带来一些开销(通常约为500毫秒)。要禁用进度通知,您可以将Gradio事件处理程序中的queue设置为False,以跳过与订阅队列进度更新相关的开销。

修改工具描述

Gradio会自动根据您的函数名称设置工具名称,并根据函数的文档字符串设置描述。但是,您可能希望更改描述在LLM中显示的方式。您可以通过在InterfaceChatInterface或任何事件监听器中使用api_description参数来实现。此参数接受三种不同类型的值:

  • None(默认):工具描述会从函数的文档字符串(或者如果函数本身没有文档字符串但继承了具有文档字符串的父类方法,则从父类文档字符串)自动生成。
  • False:LLM不会看到任何工具描述。
  • str:一个任意字符串,用作工具描述。

除了修改工具描述外,您还可以切换哪些工具显示给LLM。您可以通过设置show_api参数来实现,该参数默认为True。将其设置为False会隐藏API文档和MCP服务器中的端点。如果您公开了多个工具,应用程序用户还可以通过在“查看MCP或API”面板中勾选框来切换他们想添加到MCP客户端的工具。

以下是一个示例,展示了api_descriptionshow_api参数的用法

import numpy as np
import gradio as gr
from pathlib import Path
import os
from PIL import Image

def prime_factors(n: str):
    """
    Compute the prime factorization of a positive integer.

    Args:
        n (str): The integer to factorize. Must be greater than 1.
    """
    n_int = int(n)
    if n_int <= 1:
        raise ValueError("Input must be an integer greater than 1.")

    factors = []
    while n_int % 2 == 0:
        factors.append(2)
        n_int //= 2

    divisor = 3
    while divisor * divisor <= n_int:
        while n_int % divisor == 0:
            factors.append(divisor)
            n_int //= divisor
        divisor += 2

    if n_int > 1:
        factors.append(n_int)

    return factors


def generate_cheetah_image():
    """
    Generate a cheetah image.

    Returns:
        The generated cheetah image.
    """
    return Path(os.path.dirname(__file__)) / "cheetah.jpg"


def image_orientation(image: Image.Image) -> str:
    """
    Returns whether image is portrait or landscape.

    Args:
        image (Image.Image): The image to check.

    Returns:
        str: "Portrait" if image is portrait, "Landscape" if image is landscape.
    """
    return "Portrait" if image.height > image.width else "Landscape"


def sepia(input_img):
    """
    Apply a sepia filter to the input image.

    Args:
        input_img (np.array): The input image to apply the sepia filter to.

    Returns:
        The sepia filtered image.
    """
    sepia_filter = np.array([
        [0.393, 0.769, 0.189],
        [0.349, 0.686, 0.168],
        [0.272, 0.534, 0.131]
    ])
    sepia_img = input_img.dot(sepia_filter.T)
    sepia_img /= sepia_img.max()
    return sepia_img



demo = gr.TabbedInterface(
    [
        gr.Interface(prime_factors, gr.Textbox("1001"), gr.Textbox()),
        gr.Interface(generate_cheetah_image, None, gr.Image(), api_description="Generates a cheetah image. No arguments are required."),
        gr.Interface(image_orientation, gr.Image(type="pil"), gr.Textbox(), api_visibility="private"),
        gr.Interface(sepia, gr.Image(), gr.Image(), api_description=False),
    ],
    [
        "Prime Factors",
        "Cheetah Image",
        "Image Orientation Checker",
        "Sepia Filter",
    ]
)

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

MCP资源和提示

除了工具(它们通常执行函数,并且是Gradio MCP集成的任何函数公开的默认选项)之外,MCP还支持另外两种重要的基本类型:**资源**(用于公开数据)和**提示**(用于定义可重用模板)。Gradio提供了装饰器来轻松创建具有这三种功能的MCP服务器。

创建MCP资源

使用@gr.mcp.resource装饰器装饰任何函数,以通过您的Gradio应用程序公开数据。资源可以是静态的(始终可在固定URI访问)或模板化的(URI中包含参数)。

"""
Adapts the FastMCP quickstart example to work with Gradio's MCP integration.
"""
import gradio as gr


@gr.mcp.tool()  # Not needed as functions are registered as tools by default
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b


@gr.mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
    """Get a personalized greeting"""
    return f"Hello, {name}!"


@gr.mcp.prompt()
def greet_user(name: str, style: str = "friendly") -> str:
    """Generate a greeting prompt"""
    styles = {
        "friendly": "Please write a warm, friendly greeting",
        "formal": "Please write a formal, professional greeting", 
        "casual": "Please write a casual, relaxed greeting",
    }

    return f"{styles.get(style, styles['friendly'])} for someone named {name}."


demo = gr.TabbedInterface(
    [
        gr.Interface(add, [gr.Number(value=1), gr.Number(value=2)], gr.Number()),
        gr.Interface(get_greeting, gr.Textbox("Abubakar"), gr.Textbox()),
        gr.Interface(greet_user, [gr.Textbox("Abubakar"), gr.Dropdown(choices=["friendly", "formal", "casual"])], gr.Textbox()),
    ],
    [
        "Add",
        "Get Greeting",
        "Greet User",
    ]
)


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

在这个例子中

  • get_greeting函数被公开为一个资源,其URI模板为greeting://{name}
  • 当MCP客户端请求greeting://Alice时,它会收到“Hello, Alice!”。
  • 资源也可以返回图像和其他类型的文件或二进制数据。为了返回非文本数据,您应该在@gr.mcp.resource()中指定mime_type参数,并从函数返回Base64字符串。

创建MCP提示

提示有助于标准化用户与您的工具的交互方式。它们对于需要特定格式或多个步骤的复杂工作流尤其有用。

上面示例中的greet_user函数用@gr.mcp.prompt()装饰,该装饰器:

  • 使其在MCP客户端中可用作提示模板
  • 接受参数(namestyle)来自定义输出
  • 返回结构化的提示,以指导LLM的行为

添加仅限MCP的函数

到目前为止,我们所有的MCP工具、资源或提示都对应于UI中的事件监听器。这对于直接更新UI的函数来说效果很好,但如果您想公开一个“纯逻辑”函数,它应该返回原始数据(例如,JSON对象)而不直接导致UI更新,那么它可能不起作用。

为了公开这样的MCP工具,您可以使用gr.api创建纯Gradio API端点(请参阅完整的文档)。这是一个创建MCP工具的示例,该工具可以切片列表。

import gradio as gr

def slice_list(lst: list, start: int, end: int) -> list:
    """
    A tool that slices a list given a start and end index.
    Args:
        lst: The list to slice.
        start: The start index.
        end: The end index.
    Returns:
        The sliced list.
    """
    return lst[start:end]

with gr.Blocks() as demo:
    gr.Markdown(
        """
        This is a demo of a MCP-only tool.
        This tool slices a list.
        This tool is MCP-only, so it does not have a UI.
        """
    )
    gr.api(
        slice_list
    )

_, url, _ = demo.launch(mcp_server=True)

请注意,如果您使用此方法,您的函数签名必须是完全类型的,包括返回值,因为这些签名用于确定MCP工具的类型信息。

Gradio与FastMCP

在某些情况下,您可能决定不使用Gradio的内置集成,而是手动创建一个FastMCP服务器来调用Gradio应用程序。这种方法适用于您想:

  • 在调用之间存储状态/识别用户,而不是将每次工具调用完全视为独立的。
  • 在调用工具时启动Gradio应用程序MCP服务器(如果您在本地运行多个Gradio应用程序,并希望节省内存/GPU)。

这在很大程度上是可行的,得益于Gradio Python客户端MCP Python SDK的FastMCP类。以下是一个创建自定义MCP服务器的示例,该服务器使用stdio协议连接到托管在HuggingFace Spaces上的各种Gradio应用程序。

from mcp.server.fastmcp import FastMCP
from gradio_client import Client
import sys
import io
import json 

mcp = FastMCP("gradio-spaces")

clients = {}

def get_client(space_id: str) -> Client:
    """Get or create a Gradio client for the specified space."""
    if space_id not in clients:
        clients[space_id] = Client(space_id)
    return clients[space_id]


@mcp.tool()
async def generate_image(prompt: str, space_id: str = "ysharma/SanaSprint") -> str:
    """Generate an image using Flux.
    
    Args:
        prompt: Text prompt describing the image to generate
        space_id: HuggingFace Space ID to use 
    """
    client = get_client(space_id)
    result = client.predict(
            prompt=prompt,
            model_size="1.6B",
            seed=0,
            randomize_seed=True,
            width=1024,
            height=1024,
            guidance_scale=4.5,
            num_inference_steps=2,
            api_name="/infer"
    )
    return result


@mcp.tool()
async def run_dia_tts(prompt: str, space_id: str = "ysharma/Dia-1.6B") -> str:
    """Text-to-Speech Synthesis.
    
    Args:
        prompt: Text prompt describing the conversation between speakers S1, S2
        space_id: HuggingFace Space ID to use 
    """
    client = get_client(space_id)
    result = client.predict(
            text_input=f"""{prompt}""",
            audio_prompt_input=None, 
            max_new_tokens=3072,
            cfg_scale=3,
            temperature=1.3,
            top_p=0.95,
            cfg_filter_top_k=30,
            speed_factor=0.94,
            api_name="/generate_audio"
    )
    return result


if __name__ == "__main__":
    import sys
    import io
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
    
    mcp.run(transport='stdio')

此服务器公开两个工具:

  1. run_dia_tts- 以[S1]first-sentence. [S2]second-sentence. [S1]...的形式为给定文本生成对话。
  2. generate_image- 使用快速的文本到图像模型生成图像。

要将此MCP服务器与Claude Desktop(作为MCP客户端)一起使用:

  1. 将代码保存到文件(例如,gradio_mcp_server.py)。
  2. 安装所需的依赖项:pip install mcp gradio-client
  3. 通过编辑~/Library/Application Support/Claude/claude_desktop_config.json(macOS)或%APPDATA%\Claude\claude_desktop_config.json(Windows)中的配置文件,将Claude Desktop配置为使用您的服务器。
{
    "mcpServers": {
        "gradio-spaces": {
            "command": "python",
            "args": [
                "/absolute/path/to/gradio_mcp_server.py"
            ]
        }
    }
}
  1. 重新启动Claude Desktop。

现在,当您询问Claude关于生成图像或转录音频时,它可以利用您基于Gradio的工具来完成这些任务。

排查MCP服务器问题

MCP协议仍处于初级阶段,您可能会遇到连接您构建的MCP服务器时出现问题。我们通常建议使用MCP Inspector Tool来尝试连接和调试您的MCP服务器。

以下是一些可能有帮助的建议:

1.确保您为函数提供了类型提示和有效的文档字符串。

如前所述,Gradio读取函数的文档字符串和输入参数的类型提示,以生成工具和参数的描述。有效的函数和文档字符串看起来像这样(注意“Args:”块下面缩进的参数名)。

def image_orientation(image: Image.Image) -> str:
    """
    Returns whether image is portrait or landscape.

    Args:
        image (Image.Image): The image to check.
    """
    return "Portrait" if image.height > image.width else "Landscape"

注意:您可以通过访问http://your-server:port/gradio_api/mcp/schema URL来预览为您的MCP服务器创建的模式。

2.尝试将输入参数接受为str

一些MCP客户端不识别数字或其他复杂类型的参数,但我们测试过的所有MCP客户端都接受str输入参数。如有疑问,请将输入参数更改为str,然后在函数中将其转换为特定类型,如下例所示。

def prime_factors(n: str):
    """
    Compute the prime factorization of a positive integer.

    Args:
        n (str): The integer to factorize. Must be greater than 1.
    """
    n_int = int(n)
    if n_int <= 1:
        raise ValueError("Input must be an integer greater than 1.")

    factors = []
    while n_int % 2 == 0:
        factors.append(2)
        n_int //= 2

    divisor = 3
    while divisor * divisor <= n_int:
        while n_int % divisor == 0:
            factors.append(divisor)
            n_int //= divisor
        divisor += 2

    if n_int > 1:
        factors.append(n_int)

    return factors

3.确保您的MCP客户端支持流式HTTP。

一些MCP客户端尚不支持基于流式HTTP的MCP服务器。在这种情况下,您可以使用mcp-remote等工具。首先安装Node.js。然后,将以下内容添加到您自己的MCP客户端配置中:

{
  "mcpServers": {
    "gradio": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "http://your-server:port/gradio_api/mcp/"
      ]
    }
  }
}

4.重启您的MCP客户端和MCP服务器。

一些MCP客户端要求您每次更新MCP配置时都重新启动它们。其他时候,如果MCP客户端和服务器之间的连接中断,您可能需要重新启动MCP服务器。如果所有其他方法都失败了,请尝试同时重启您的MCP客户端和MCP服务器!

gradio