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进行转换
相关推荐
kagg8864 小时前
mcp-gateway —— 隐藏mcp上下文以避免不必要的tokens开销
llm·mcp
AAA阿giao5 小时前
qoder-cli:下一代命令行 AI 编程代理——全面解析与深度实践指南
开发语言·前端·人工智能·ai编程·mcp·context7·qoder-cli
饭勺oO1 天前
AI 编程配置太头疼?ACP 帮你一键搞定,再也不用反复折腾!
ai·prompt·agent·acp·mcp·skills·agent skill
AlienZHOU1 天前
MCP 是最大骗局?Skills 才是救星?
agent·mcp·vibecoding
Linux内核拾遗1 天前
人人都在聊 MCP,它到底解决了什么?
aigc·ai编程·mcp
谷哥的小弟1 天前
SQLite MCP服务器安装以及客户端连接配置
服务器·数据库·人工智能·sqlite·大模型·源码·mcp
tyw151 天前
解决 Trae MySQL MCP 连接失败(Fail to start)
mcp·trae·fail to start·mysql mcp·mcp兼容
谷哥的小弟1 天前
File System MCP服务器安装以及客户端连接配置
服务器·人工智能·大模型·file system·mcp·ai项目
啊湘2 天前
vscode 使用 github (适用CURSOR等使用)
ide·vscode·github·cursor·mcp