Unla MCP 网关代理配置教程

Unla 是一个轻量级的 MCP 网关服务,它的核心优势在于"零代码侵入"。通过配置,可以将传统的 HTTP API 转换为标准 MCP 协议,或对现有的 MCP Server 进行反向代理。

一、 准备工作

  1. 部署 Unla:通过 Docker 或源码启动了 Unla。
  2. 管理后台 :访问 Unla 的 Web UI(默认地址通常为 http://localhost:5234)。

二、 配置网关代理 (Gateways)

在 Unla 中,网关配置通常分为两种场景:代理现有 MCP Server转换 HTTP API

1. 代理现有的 MCP Server

如果你已经有一个运行中的 MCP Server(例如 Python/Node.js 编写的),可以通过 Unla 进行中转。

  • 步骤
    1. 登录 Web UI,点击 "网关配置" > "创建" > "MCP服务"
    2. 选择 "YAML模式" 或者 "表单模式" 填写配置信息。
    3. 点击 "创建" 按钮。
2. 将 HTTP API 转换为 MCP 工具
  • 步骤
    1. 登录 Web UI,点击 "网关配置" > "创建" > "HTTP服务"
    2. 选择 "YAML模式" 或者 "表单模式" 填写配置信息。
    3. 点击 "创建" 按钮。

这是 Unla 最强大的功能,只需编写 YAML 即可将普通的HTTP接口变成 AI 代理工具。

  • YAML配置示例
yaml 复制代码
name: "mock-server"              # 代理服务名称,全局唯一
tenant: "default"                # 租户标识,用于多租户场景

# 路由配置
routers:
  - server: "mock-server"       # 服务名称
    prefix: "/gateway/user"     # 路由前缀,全局唯一,不可重复(必须以租户ID开头)

    # CORS 配置
    cors:
      allowOrigins:             # 开发测试环境可全部开放,线上最好按需开放。(大部分MCP Client是不需要开放跨域的)
        - "*"
      allowMethods:             # 允许的请求方法,按需开放,对于MCP(SSE和Streamable)来说通常只需要这3个方法即可
        - "GET"
        - "POST"
        - "PUT"
        - "OPTIONS"
      allowHeaders:
        - "Content-Type"        # 必须允许的
        - "Authorization"       # 有鉴权需求的需要支持请求里携带此Key
        - "Mcp-Session-Id"      # 对于MCP来说,必须支持请求里携带这个Key,否则Streamable HTTP无法正常使用
        - "mcp-protocol-version" # MCP协议版本头,用于协议版本协商
      exposeHeaders:
        - "Mcp-Session-Id"      # 对于MCP来说,开启跨域的时候必须要暴露这个Key,否则Streamable HTTP无法正常使用
        - "mcp-protocol-version" # MCP协议版本头
      allowCredentials: true    # 是否增加 Access-Control-Allow-Credentials: true 这个Header

# 服务配置
servers:
  - name: "mock-server"               # 服务名称,需要与routers中的server保持一致
    description: "Mock User Service"  # 服务描述
    allowedTools:                     # 允许使用的工具列表(为tools的子集)
      - "register_user"
      - "get_user_by_email"
      - "update_user_preferences"
      - "update_user_avatar"
    config:                                           # 服务级别的配置,可以在tools中通过{{.Config}}引用
      Cookie: 123                                     # 写死的配置
      Authorization: 'Bearer {{ env "AUTH_TOKEN" }}'  # 从环境变量中获取的配置,用法是'{{ env "ENV_VAR_NAME" }}'

# 工具配置
tools:
  - name: "register_user"                                   # 工具名称
    description: "Register a new user"                      # 工具描述
    method: "POST"                                          # 请求目标(上游、后端)服务的HTTP方法
    endpoint: "http://localhost:5236/users"                 # 目标服务地址
    headers:                                                # 请求头配置,用于在请求目标服务时携带的请求头
      Content-Type: "application/json"                      # 写死的请求头
      Authorization: "{{.Config.Authorization}}"            # 使用服务配置中的值
      Cookie: "{{.Config.Cookie}}"                          # 使用服务配置中的值
    args:                         # 参数配置
      - name: "username"          # 参数名称
        position: "body"          # 参数位置:header, query, path, body, form-data
        required: true            # 参数是否必填
        type: "string"            # 参数类型
        description: "Username"   # 参数描述
        default: ""               # 默认值
      - name: "email"
        position: "body"
        required: true
        type: "string"
        description: "Email"
        default: ""
    requestBody: |-               # 请求体模板,用于动态生成请求体,如:从参数(MCP的请求arguments)中提取的值
      {
        "username": "{{.Args.username}}",
        "email": "{{.Args.email}}"
      }
    responseBody: |-              # 响应体模板,用于动态生成响应体,如:从响应中提取的值
      {
        "id": "{{.Response.Data.id}}",
        "username": "{{.Response.Data.username}}",
        "email": "{{.Response.Data.email}}",
        "createdAt": "{{.Response.Data.createdAt}}"
      }

  - name: "get_user_by_email"
    description: "Get user by email"
    method: "GET"
    endpoint: "http://localhost:5236/users/email/{{.Args.email}}"
    args:
      - name: "email"
        position: "path"
        required: true
        type: "string"
        description: "Email"
        default: ""
    responseBody: |-
      {
        "id": "{{.Response.Data.id}}",
        "username": "{{.Response.Data.username}}",
        "email": "{{.Response.Data.email}}",
        "createdAt": "{{.Response.Data.createdAt}}"
      }

  - name: "update_user_preferences"
    description: "Update user preferences"
    method: "PUT"
    endpoint: "http://localhost:5236/users/{{.Args.email}}/preferences"
    headers:
      Content-Type: "application/json"
      Authorization: "{{.Request.Headers.Authorization}}"
      Cookie: "{{.Config.Cookie}}"
    args:
      - name: "email"
        position: "path"
        required: true
        type: "string"
        description: "Email"
        default: ""
      - name: "isPublic"
        position: "body"
        required: true
        type: "boolean"
        description: "Whether the user profile is public"
        default: "false"
      - name: "showEmail"
        position: "body"
        required: true
        type: "boolean"
        description: "Whether to show email in profile"
        default: "true"
      - name: "theme"
        position: "body"
        required: true
        type: "string"
        description: "User interface theme"
        default: "light"
      - name: "tags"
        position: "body"
        required: true
        type: "array"
        items:
          type: "string"
          enum: ["developer", "designer", "manager", "tester"]
        description: "User role tags"
        default: "[]"
      - name: "settings"
        position: "body"
        required: false
        type: "object"
        description: "User custom settings as key-value pairs"
        default: "{}"
      - name: "notifications"
        position: "body"
        required: false
        type: "array"
        description: "User notification preferences"
        items:
          type: "object"
          properties:
            type:
              type: "string"
              description: "Notification type (email, push, sms)"
            channel:
              type: "string"
              description: "Notification channel (marketing, system, security)"
            enabled:
              type: "boolean"
              description: "Whether this notification is enabled"
            frequency:
              type: "number"
              description: "Notification frequency (0: realtime, 1: daily, 2: weekly, 3: monthly)"
        default: "[]"
    requestBody: |-                   # toJSON:对非JSON原生支持的类型转为JSON格式
      {
        "isPublic": {{.Args.isPublic}},
        "showEmail": {{.Args.showEmail}},
        "theme": "{{.Args.theme}}",
        "tags": {{.Args.tags}},
        "settings": {{ toJSON .Args.settings }},
        "notifications": {{ .Args.notifications }}
      }
    responseBody: |-
      {
        "id": "{{.Response.Data.id}}",
        "username": "{{.Response.Data.username}}",
        "email": "{{.Response.Data.email}}",
        "createdAt": "{{.Response.Data.createdAt}}",
        "preferences": {
          "isPublic": {{.Response.Data.preferences.isPublic}},
          "showEmail": {{.Response.Data.preferences.showEmail}},
          "theme": "{{.Response.Data.preferences.theme}}",
          "tags": {{.Response.Data.preferences.tags}},
          "settings": {{ toJSON .Response.Data.preferences.settings }},
          "notifications": {{ toJSON .Response.Data.preferences.notifications }}
        }
      }

  - name: "update_user_avatar"
    description: "Update user avatar using a URL via multipart form"
    method: "POST"
    endpoint: "http://localhost:5236/users/{{.Args.email}}/avatar"
    headers:
      Authorization: "{{.Request.Headers.Authorization}}"
      Cookie: "{{.Config.Cookie}}"
    args:
      - name: "email"
        position: "path"
        required: true
        type: "string"
        description: "Email of the user"
        default: ""
      - name: "url"
        position: "form-data"
        required: true
        type: "string"
        description: "The avatar image URL"
        default: ""
    responseBody: |-
      {
        "message": "{{.Response.Data.message}}",
        "avatarUrl": "{{.Response.Data.avatarUrl}}"
      }
  • 网关处理逻辑: 当 AI 客户端请求该工具时,Unla 网关会自动构造 HTTP 请求发往后端,并将结果封装为 MCP 响应返回。

三、 客户端接入

配置完成后,你需要让你的 AI 客户端(如 Dify, Cursor, Claude Desktop)连接到 Unla 网关。

  1. 获取接入点 URL : 在 Unla 控制面板查看生成 SSE 和 HTTP 两种类型的端点地址,通常格式为:http://<your-gateway-ip>:5235/{tenant_id}/<server-name>/ssehttp://<your-gateway-ip>:5235/{tenant_id}/<server-name>/mcp
  2. 在客户端配置 (以 Dify 为例):
    • 工具 -> MCP -> 添加MCP服务。
    • 添加"服务端点,名称,服务器标识符,请求头"等信息

四、租户管理

1. 租户的作用

在 Unla 中,租户(Tenant)是一个逻辑隔离单位。其主要用途包括:

  • 资源隔离:不同项目组或部门可以拥有独立的 MCP Server 集合,互不干扰。
  • 配置复用 :可以在不同租户下配置同名的 Server(如 prod-dbdev-db),通过租户 ID 进行区分。
  • 安全合规 :为每个租户分配独立的 API KeyJWT Secret,确保 A 租户无法调用 B 租户的工具。
2. 租户在网关前缀(Gateway Prefix)中的匹配逻辑

Unla 网关通过 URL 路径中的前缀来识别请求所属的租户和服务。

标准的路由匹配格式通常如下: http://<gateway-host>:<port>/{tenant_id}/{server_name}/ssehttp://<gateway-host>:<port>/{tenant_id}/{server_name}/mcp


五、操作建议

  1. 先创建租户 :在 Web UI 的 "租户管理" 模块创建租户,并记录其 ID
  2. 绑定 Server :在创建网关代理配置时,务必选择所属的 "租户"
  3. 路由配置 :路由前缀必须以 "租户ID" 开头,否则失败。
  4. 正确YAML配置文件格式:通过YAML模式进行网关配置时,确保正确的缩进,否则会失败。
  5. 正确的JSON格式数据 :在配置请求或响应Body时,对非JSON原生支持的数据类型通过toJSON进行转换
相关推荐
猿小羽13 小时前
基于 Spring AI 与 Streamable HTTP 构建 MCP Server 实践
java·llm·spring ai·mcp·streamable http
猿小羽14 小时前
MCP Server 运行模式入门(Streamable HTTP / stdio)
http·ai·ai实战·mcp·mcp server
花酒锄作田1 天前
MCP官方Go SDK尝鲜
golang·mcp
猿小羽1 天前
深入理解 Microservice Control Proxy(MCP) 的 AI 实战指南
微服务·ai·推荐系统·service mesh·microservice·mcp·ai 实战
名字不好奇1 天前
一文拆解MCP协议
人工智能·mcp
ahxdyz2 天前
.NET平台MCP
ai·.net·mcp
绿荫阿广2 天前
将SignalR移植到Esp32—让小智设备无缝连接.NET功能拓展MCP服务
.net·asp.net core·mcp
青火coding3 天前
java本地实现mcp的服务器调用
java·服务器·spring boot·mcp
Bruk.Liu4 天前
AI中的Agent、Prompt、MCP与Function Calling:从简单对话到智能执行
人工智能·prompt·mcp
领航猿1号4 天前
Langchain 1.0.2 从入门到精通(含基础、RAG、Milvus、Ollama、MCP、Agents)
langchain·agent·milvus·rag·mcp·langchain 1.0