本地 Python 与魔搭社区 Gradio 创空间 MCP 通讯教程

魔搭社区上的 Gradio 创空间是支持 API 或 MCP 使用的,也就是说,我们无需将线上已经部署好并运行了的创空间拉到本地部署后再使用,而是直接本地写一个通信脚本就可以直接与云端的 MCP Server 连接并通过其支持的输入与输出交互,同样,这也是目前各个联网 Agent 调用外部工具的绝佳方式。这样不仅可以将大量业务逻辑从本地解耦出去,又能将算力压力转嫁出去,特别是对于那些有嵌入式 AI 开发需求的开发者,这点非常重要:由于嵌入式开发受内存、算力等架构上的限制,上面能使用的 Python SDK 有限,将复杂业务逻辑及算力解耦出去,以通信的形式在线调用 AI 功能是实现具身智能的低成本方式之一。虽然创空间界面上给出了 MCP 方式使用的 API 描述,但缺少实际可跑通的成功案例。

 

因此本文章以线上简单的支持 MCP 的翻译器创空间为例,给出本地上用 Python 以纯通信方式调用其功能的实例。之所以选择这个创空间,是因为其输入输出均为纯文本,适合适配对话类型的大语言模型,各位可以在调通后,将 MCP Server 链接替换成部署好的 LLM 创空间的,即可实现纯通信 AI 对话。我们首先点击一下创空间底端的“通过 API 或 MCP 使用” 链接并切换至 MCP 的面板,获取当前 Gradio 创空间的 MCP Server 地址:

 

当前案例是:https://studio-genius-society-translator.api-inference.modelscope.net/gradio_api/mcp/,并且下面给出了其可用的工具名为 infer 。其实它对应的是创空间源码中 app.py 里直接对接 Gradio UI 交互的主函数。将其展开还能进一步看到 infer 函数两个纯文本输入的键名(对应其两个入参,入参名与键名可能会不一样,但肯定一一对应),在当前案例中 source 对应界面上要翻译的文本,direction 则对应为翻译模式(如:自动检测语言到英文就是 auto2en,英译汉就是 en2zh 等),当然不同创空间这里的键名可能会不一样,具体要点击该创空间的 “通过 API 或 MCP 使用” 查看细节。上述提到的元素都用在下面的实例代码中:

import json
import requests

MCP_URL = "https://studio-genius-society-translator.api-inference.modelscope.net/gradio_api/mcp/"


class MCPClient:
    def __init__(self, url):
        self.url = url
        self.session = requests.Session()
        self.request_id = 0
        self.session_id = None
        self.token = "ms-*" # 为你的魔搭访问令牌,登陆后从 https://modelscope.cn/my/access/token 获得
        self.headers = {
            "Authorization": f"Bearer {self.token}",
            "Content-Type": "application/json",
            "Accept": "application/json, text/event-stream",
        }

    def next_id(self):
        self.request_id += 1
        return self.request_id

    def request(self, method, params=None):
        request_id = self.next_id()
        payload = {
            "jsonrpc": "2.0",
            "id": request_id,
            "method": method,
        }
        if params is not None:
            payload["params"] = params

        response = self.session.post(
            self.url,
            headers=self.headers,
            json=payload,
            timeout=120,
        )
        response.encoding = "utf-8"
        response.raise_for_status()
        # MCP session
        session_id = response.headers.get("Mcp-Session-Id")
        if session_id:
            self.session_id = session_id
            self.headers["Mcp-Session-Id"] = session_id

        content_type = response.headers.get("content-type", "")
        if "application/json" in content_type:
            return response.json()

        if "text/event-stream" in content_type:
            result = []
            for line in response.iter_lines(decode_unicode=True):
                if not line:
                    continue

                if line.startswith("data:"):
                    data = line[5:].strip()
                    try:
                        result.append(json.loads(data))
                    except json.JSONDecodeError:
                        result.append(data)

            return result

        return response.text

    def initialize(self):
        result = self.request(
            "initialize",
            {
                "protocolVersion": "2025-06-18",
                "capabilities": {},
                "clientInfo": {
                    "name": "python-requests",
                    "version": "1.0",
                },
            },
        )
        # initialized notification
        self.session.post(
            self.url,
            headers=self.headers,
            json={
                "jsonrpc": "2.0",
                "method": "notifications/initialized",
            },
            timeout=120,
        )

        return result

    def list_tools(self):
        return self.request(
            "tools/list",
            {},
        )

    def call_tool(self, name, arguments):
        return self.request(
            "tools/call",
            {
                "name": name,
                "arguments": arguments,
            },
        )


def en2zh(text: str):
    result = client.call_tool(
        "infer",
        {
            "source": text,
            "direction": "en2zh",
        },
    )

    return result


if __name__ == "__main__":
    client = MCPClient(MCP_URL)
    print("\n========== INITIALIZE ==========")
    result = client.initialize()
    print(
        json.dumps(
            result,
            indent=2,
            ensure_ascii=False,
        )
    )
    print("\n========== TOOLS ==========")
    tools = client.list_tools()
    print(
        json.dumps(
            tools,
            indent=2,
            ensure_ascii=False,
        )
    )
    print("\n========== EXAMPLE ==========")
    print(en2zh("Hello world"))

 

以上便是本地用 Python 以纯通信的方式调用翻译器的实例源码。由于与 MCP 通讯是需要先握手再调用的,源码主要是以下 5 段逻辑步骤实现:

┌─────────────────────────────────────────────────────────────────────────────┐
│                        MCP Client (Python)                                  │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                                      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 步骤 1: 握手初始化 (initialize)                                              │
│   - 发送 JSON-RPC 请求:                                                      │
│     { "jsonrpc": "2.0", "method": "initialize", ... }                       │
│   - 服务端响应:                                                              │
│     { "protocolVersion": "...", "serverInfo": { ... } }                     │
│   - 解析响应头中的 "Mcp-Session-Id" 并保存为会话标识(用于后续请求)            │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                                      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 步骤 2: 发送 initialized 通知 (notification)                                 │
│   - 客户端通知服务器: 已经完成初始化                                          │
│   - 请求格式:                                                               │
│     { "jsonrpc": "2.0", "method": "notifications/initialized" }            │
│   - 服务器通常不返回结果(仅为通知)                                          │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                                      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 步骤 3: 列出可用工具 (list_tools)                                             │
│   - 发送 JSON-RPC 请求:                                                      │
│     { "jsonrpc": "2.0", "method": "tools/list", ... }                       │
│   - 服务器响应:                                                              │
│     { "tools": [ { "name": "infer", ... }, ... ] }                          │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                                      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 步骤 4: 调用具体工具 (call_tool)                                             │
│   - 发送 JSON-RPC 请求:                                                     │
│     {                                                                      │
│       "jsonrpc": "2.0",                                                    │
│       "method": "tools/call",                                              │
│       "params": {                                                          │
│         "name": "infer",                                                   │
│         "arguments": { "source": "Hello world", "direction": "en2zh" }     │
│       }                                                                    │
│     }                                                                      │
│   - 服务器响应:                                                             │
│     { "result": { "content": "翻译结果" } }                                 │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                                      ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 步骤 5: 获取并展示结果                                                        │
│   - 客户端从响应中提取 "result.content" 字段                                  │
│   - 打印或返回最终结果                                                        │
└─────────────────────────────────────────────────────────────────────────────┘

 

整体可以简化为:握手->列出可用工具->调用工具测试 3个步骤。而由于可用工具可以在前述的创空间面板中查看,若已经得知,甚至可简化为握手后直接调用2个步骤,实例中如下调用工具部分代码可根据其描述自定义:

    result = client.call_tool(
        "infer",
        {
            "source": text,
            "direction": "en2zh",
        },
    )

 

另外注意实例代码中的 self.token = "ms-*" 要替换成自己的魔搭访问令牌。除此之外,我还给出了支持嵌入式版本的,CPython / MicroPython 兼容版本实例代码,由于 MicroPython 的 mip 中是有 json 和 requests 这些 PyPi 的平替的,纯通信方式适配如连了 WIFI 后的 ESP32 板子是可行的:

import json

try:
    import urequests as requests  # type: ignore[import-not-found]  # MicroPython
except ImportError:
    import requests  # CPython

MCP_URL = "https://studio-genius-society-translator.api-inference.modelscope.net/gradio_api/mcp/"


class MCPClient:
    def __init__(self, url):
        self.url = url  # urequests 没有 Session,CPython requests 才有。
        self.session = requests.Session() if hasattr(requests, "Session") else None
        self.request_id = 0
        self.session_id = None
        self.token = "ms-*" # 为你的魔搭访问令牌,登陆后从 https://modelscope.cn/my/access/token 获得
        self.headers = {
            "Authorization": "Bearer {}".format(self.token or ""),
            "Content-Type": "application/json",
            "Accept": "application/json, text/event-stream",
        }

    def next_id(self):
        self.request_id += 1
        return self.request_id

    def _decode_response_text(self, response, content_type):
        # CPython requests 支持 encoding,显式设为 utf-8 避免中文乱码。
        if hasattr(response, "encoding"):
            response.encoding = "utf-8"
            return getattr(response, "text", "")

        content = getattr(response, "content", None)
        if isinstance(content, bytes):
            try:
                return content.decode("utf-8")
            except Exception:
                return content.decode("utf-8", "replace")

        text = getattr(response, "text", "")
        if isinstance(text, bytes):
            try:
                return text.decode("utf-8")
            except Exception:
                return text.decode("utf-8", "replace")

        return text

    def request(self, method, params=None):
        request_id = self.next_id()
        payload = {
            "jsonrpc": "2.0",
            "id": request_id,
            "method": method,
        }
        if params is not None:
            payload["params"] = params

        data = json.dumps(payload)
        post_func = self.session.post if self.session else requests.post
        response = post_func(
            self.url,
            headers=self.headers,
            data=data,
            timeout=120,
        )
        try:
            if hasattr(response, "raise_for_status"):
                response.raise_for_status()
            # MCP session
            response_headers = getattr(response, "headers", {}) or {}
            session_id = response_headers.get("Mcp-Session-Id")
            if session_id:
                self.session_id = session_id
                self.headers["Mcp-Session-Id"] = session_id

            content_type = response_headers.get("content-type", "")
            text = self._decode_response_text(response, content_type)
            if "application/json" in content_type:
                if hasattr(response, "json"):
                    return response.json()

                return json.loads(text)

            if "text/event-stream" in content_type:
                result = []
                # urequests 没有 iter_lines,退化为按文本分行。
                if hasattr(response, "iter_lines"):
                    lines = response.iter_lines(decode_unicode=True)
                else:
                    lines = text.split("\n")

                for line in lines:
                    if not line:
                        continue

                    if isinstance(line, bytes):
                        line = line.decode("utf-8")

                    if line.startswith("data:"):
                        item = line[5:].strip()
                        try:
                            result.append(json.loads(item))
                        except Exception:
                            result.append(item)

                return result

            return text

        finally:
            # MicroPython 下及时 close,避免 socket 泄漏。
            if hasattr(response, "close"):
                response.close()

    def initialize(self):
        result = self.request(
            "initialize",
            {
                "protocolVersion": "2025-06-18",
                "capabilities": {},
                "clientInfo": {
                    "name": "python-requests",
                    "version": "1.0",
                },
            },
        )
        # initialized notification
        data = json.dumps(
            {
                "jsonrpc": "2.0",
                "method": "notifications/initialized",
            }
        )
        post_func = self.session.post if self.session else requests.post
        response = post_func(
            self.url,
            headers=self.headers,
            data=data,
            timeout=120,
        )
        if hasattr(response, "close"):
            response.close()

        return result

    def list_tools(self):
        return self.request(
            "tools/list",
            {},
        )

    def call_tool(self, name, arguments):
        return self.request(
            "tools/call",
            {
                "name": name,
                "arguments": arguments,
            },
        )


def en2zh(text: str):
    result = client.call_tool(
        "infer",
        {
            "source": text,
            "direction": "en2zh",
        },
    )

    return result


if __name__ == "__main__":
    client = MCPClient(MCP_URL)
    print("\n========== INITIALIZE ==========")
    result = client.initialize()
    print(
        json.dumps(
            result,
            indent=2,
            ensure_ascii=False,
        )
    )
    print("\n========== TOOLS ==========")
    tools = client.list_tools()
    print(
        json.dumps(
            tools,
            indent=2,
            ensure_ascii=False,
        )
    )
    print("\n========== EXAMPLE ==========")
    print(en2zh("Hello world"))

 

上述 MicroPython 版本的代码相较于 PC 端代码,除了在 import 部分做了适配,还将每个涉及 urequests 的负载序列化成了字符串。上述两套代码运行结果如下:

========== INITIALIZE ==========
[
  {
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
      "protocolVersion": "2025-06-18",
      "capabilities": {
        "experimental": {},
        "prompts": {
          "listChanged": false
        },
        "resources": {
          "subscribe": false,
          "listChanged": false
        },
        "tools": {
          "listChanged": false
        }
      },
      "serverInfo": {
        "name": "翻译器",
        "version": "1.29.0"
      }
    }
  }
]

========== TOOLS ==========
[
  {
    "jsonrpc": "2.0",
    "id": 2,
    "result": {
      "tools": [
        {
          "name": "infer",
          "description": "",
          "inputSchema": {
            "type": "object",
            "properties": {
              "source": {
                "type": "string",
                "description": ""
              },
              "direction": {
                "type": "string",
                "description": "",
                "default": "auto2en"
              }
            }
          }
        }
      ]
    }
  }
]

========== EXAMPLE ==========
[{'jsonrpc': '2.0', 'id': 3, 'result': {'content': [{'type': 'text', 'text': "['Success', '你好,世界']"}], 'isError': False}}]

案例的输入是翻译 "Hello World!",输出结果有 “你好,世界” ,可见通信成功。可以将当前的返回结果 dict 按键值进一步解析(如获取纯目标翻译结果),以适配自己的下游任务。

posted @ 2026-08-17 19:21  天才俱乐部  阅读(11)  评论(0)    收藏  举报