普通视图

发现新文章,点击刷新页面。
昨天 — 2026年3月25日首页

WebMCP + WebSkills:企业级智能化页面操控方案,兼顾隐私安全与高效落地!

2026年3月25日 17:28

本文由云软件体验技术团队郑志超原创。

前言

🌟 情景再现:小明的“职场救赎”

这是小明入职这家大型电商平台公司的第一天。屁股还没坐热,老板就走过来丢下一个紧急任务:“小明,有个大客户叫王五,因为百亿补贴活动,我们需要给他补发一个 1000 元的价保申请单。你赶紧操作一下,客户等着呢。”

小明愣住了。作为刚入职不到两小时的新人,他甚至连后台系统的入口、各级菜单的功能都还没摸清,更别提复杂的价保审核流程和财务对账逻辑了。看着老板匆忙离去的背影,小明坐在工位上对着密密麻麻的业务后台菜单发呆,心里焦虑万分,又不敢在这时候去打扰忙碌的老板请教这种“基础操作”。

这时,坐在旁边的小红看出了小明的窘迫,笑着指了指屏幕右下角的图标:“别愁啦,咱们公司的管理后台集成了 WebMCP + WebSkills 智能系统。你直接跟它说话就行。”

小明半信半疑地打开助手,试着输入了一句:  “帮我给用户王五创建一个价保申请单,金额 1000 元,原因为百亿补贴。”

奇迹发生了!系统立刻自动定位到了用户管理模块,识别了王五的身份,并调取了相关的订单信息。几秒钟后,屏幕上直接弹出了一个预填好的申请单确认框,上面清晰地列出了所有申请细节,并提示:“已为您准备好价保申请单,请确认无误后点击‘提交’。”

小明屏住呼吸,轻轻一点确认按钮,任务圆满完成。

原本以为要折腾一上午的复杂业务,竟然在一句话之间就解决了。这个“神操作”不仅让小明保住了入职第一天的体面,更让他真实感受到了智能化应用带来的效率革命。

以下是模拟小明操作的视频演示(欢迎访问 在线演示地址 亲自体验):

外部1.gif

内容摘要:本文深度解析了 WebMCP + WebSkills 这套专为前端页面驱动设计的“组合拳”方案。通过解决现有自动化方案(无障碍适配、视觉模型)在安全性、成本及适配难度上的核心痛点,提供了 Vue、Angular 及 React 三大主流技术栈的工程级最佳实践,助力开发者在不改变现有业务系统的架构下,实现极简、高效、安全的 AI 驱动页面操作。同时,借助 WebAgent 远程遥控,用户只需手机扫码或输入识别码,即可通过移动端直接遥控桌面页面——这是 WebMCP 在交互体验上的重大突破。

1. 背景与痛点

1.1 场景引子:为什么页面自动化这么难?

做前端工程、AI业务接入的小伙伴,是不是都有过这样的崩溃时刻?想实现页面自动化操作,要么被各种方案的坑绊住脚,要么配置复杂到让人头大,好不容易跑通还面临安全隐患……别慌!这篇文档要介绍的“组合拳”——WebMCP+WebSkills,就是帮你在不大改现有系统的前提下,把页面操作做得又稳又安全。

2.11.JPG

1.2 业界主流方案与痛点

先吐个槽:业界现有方案,坑是真的多!

在 WebMCP 出现之前,咱们做页面操作自动化,主流就两种方案,但说句实在话,用起来都让人一言难尽,痛点直接拉满:

方案一:基于无障碍信息(如 chrome-devtools-mcp)

听着挺专业,但实际用起来全是“门槛”:首先得要求业务系统页面做好完善的无障碍信息适配,可现实里很多老项目、复杂业务页面,根本达不到这个要求;其次,业务逻辑一旦复杂,基于无障碍信息的操作就会出现各种不确定性,时而正常时而报错,排查起来比找 bug 还难;更麻烦的是,想用它还得额外装浏览器扩展插件,或者依赖 playwright 等工具,步骤繁琐,兼容性还参齐不齐。

方案二:基于视觉模型截图操作

这个方案看似不用适配页面,实则“费钱又费时间”:视觉模型运行起来特别消耗 token,长期用下来成本蹭蹭涨;而且执行速度慢得让人着急,复杂业务操作能卡到你怀疑人生;最关键的是,它根本扛不住复杂业务系统的考验,稍微多几个交互步骤就直接“罢工”。

共同致命伤:安全不可控

不管是无障碍信息方案,还是视觉模型方案,都存在一个核心隐患——安全性。两种方案都需要一定程度上获取页面敏感信息,且缺乏有效的安全管控机制,一不小心就可能造成数据泄露,给业务带来不可挽回的损失。

3.11.JPG

1.3 WebMCP + WebSkills 的定位

就在大家被这些痛点折磨得焦头烂额时,WebMCP+WebSkills 横空出世,直接精准戳中所有痛点,给前端页面操作自动化带来了新希望!

WebMCP 不是“替代者”,而是“最强补充”

很多小伙伴会误以为 WebMCP 是要取代业界现有的 MCP 协议,其实不然!WebMCP 是基于业界 MCP 协议打造的前端优化方案,核心定位是“补充和增强”——它保留了 MCP 协议的核心优势,同时针对前端页面操作的痛点做了针对性优化,让页面操作更简单、更高效、更安全。

WebSkills:让 AI 真的“懂你的业务”

而 WebSkills 则是 WebMCP 的“神助攻”,它能进一步增强 AI 对业务的理解能力,让页面操作自动化更智能,哪怕是复杂的业务场景,也能轻松应对,两者搭配使用,直接实现“1+1>2”的效果。

WebAgent 远程遥控:移动端直接操控桌面页面

WebMCP + WebSkills 还有一个杀手级亮点——远程遥控。通过 useWebAgentServer 将本地 MCP Server 桥接到远端 Agent 平台,用户扫描二维码或输入 6 位识别码,即可在手机上通过自然语言指令遥控桌面浏览器页面。真正实现"移动端说一句话,桌面页面帮你干活"。

4.11.JPG

2. 三大技术栈最佳实践总览

干货来袭:三大技术栈最佳实践,直接抄作业!

不管你是用 Vue、React 还是 Angular,WebMCP+WebSkills 都能完美适配,而且实现方式高度统一:核心是通过前端路由 + 页面工具(Page Tool Bridge)把业务页面和 MCP 工具打通,再通过 WebSkills 和 TinyRemoter 做“知识与对话入口”  。下面分别给出 Vue / Angular 的摘要示例,并附上工程级最佳实践链接。

2.1 Vue 工程最佳实践(摘要)

源码工程:packages/doc-ai
完整工程路径:packages/doc-ai
详细文档:docs/guide/vue-webmcp-best-practice.md

步骤 1:安装依赖

pnpm add @opentiny/next-sdk @opentiny/next-remoter

说明:这里直接引入 WebMCP 核心 SDK 与 TinyRemoter 组件包,为后续“页面工具 + 对话框 UI”打基础。

步骤 2:在 main.ts 中注册路由导航器

// src/main.ts
import { createApp } from 'vue'
import router from './router'
import App from './App.vue'
import { setNavigator } from '@opentiny/next-sdk'

const app = createApp(App)
app.use(router)
app.mount('#app')

// 告诉 SDK:需要跳转页面时统一走 router.push
setNavigator((route) => router.push(route))

中文小结:setNavigator 是 Page Tool Bridge 的前提,只需在入口调用一次,之后所有“与页面绑定的工具”在执行时都会通过这里完成路由跳转。

步骤 3:配置业务路由

// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  historycreateWebHistory(),
  routes: [
    { path'/'component() => import('../views/home/index.vue') },
    { path'/product-list'component() => import('../views/product-list/index.vue') },
    { path'/price-protection'component() => import('../views/price-protection/index.vue') }
  ]
})

export default router

中文小结:后面在 registerTool 和 registerPageTool 里会引用这些 path,请保持一致,避免因为路由不对导致工具调用超时。

步骤 4:创建 MCP Server,并通过 withPageTools 绑定路由

// src/mcp-servers/index.ts
import {
  WebMcpServer,
  createMessageChannelPairTransport,
  withPageTools,
  registerNavigateTool
} from '@opentiny/next-sdk'
import registerProductGuideTools from './product-guide/tools'
import registerPriceProtectionTools from './price-protection/tools'

const rawServer = new WebMcpServer()
const [serverTransport, clientTransport] = createMessageChannelPairTransport()

export const server = withPageTools(rawServer)
export { clientTransport }

export const createMcpServer = async () => {
  registerNavigateTool(rawServer)
  registerProductGuideTools(server)
  registerPriceProtectionTools(server)
  await rawServer.connect(serverTransport)
}

中文小结:withPageTools 让工具可以和路由产生映射;registerNavigateTool 注册了一个通用的 navigate_to_page 工具,供大模型主动发起“先跳转再用页面工具”的链路。

步骤 5:注册与页面绑定的业务工具

// src/mcp-servers/product-guide/tools.ts
import { z } from '@opentiny/next-sdk'
import type { PageAwareServer } from '@opentiny/next-sdk'

const registerProductGuideTools = (server: PageAwareServer) => {
  server.registerTool(
    'product-guide',
    {
      title'产品指南',
      description'根据产品 ID 获取产品详细信息',
      inputSchema: {
        productId: z.string().describe('产品 ID')
      }
    },
    { route'/product-list' } // 工具执行时自动导航到该路由
  )
}

export default registerProductGuideTools

中文小结:第三个参数 { route: '/product-list' } 是关键,它告诉 SDK“这个工具需要在哪个页面内执行”,从而触发 Page Tool Bridge 的自动跳转与消息投递。

步骤 6:在页面内通过 registerPageTool 注册工具处理器

<!-- src/views/product-list/index.vue -->
<template>
  <div class="products-page">
    <div v-for="product in products" :key="product.id">{{ product.name }} - ¥{{ product.price }}</div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { registerPageTool } from '@opentiny/next-sdk'
import productsData from './products.json'

type Product = {
  id: number
  name: string
  price: number
  stock: number
  status'on' | 'off' | string
}

const products = ref<Product[]>(productsData as Product[])
let cleanupPageTool() => void

onMounted(() => {
  cleanupPageTool = registerPageTool({
    handlers: {
      'product-guide'async ({ productId }: { productId: string }) => {
        const product = products.value.find((p) => String(p.id) === productId)
        const text = product ? `产品信息:${JSON.stringify(product, null2)}` : `未找到产品 ID 为 ${productId} 的商品`
        return { content: [{ type'text', text }] }
      }
    }
  })
})

onUnmounted(() => cleanupPageTool?.())
</script>

中文小结:页面挂载时把 handler 注册进去,卸载时清理;handler 中可以直接访问 Vue 响应式数据,实现“AI 调工具 → 工具调页面逻辑”的完整闭环。

步骤 7:在 App.vue 中挂载 TinyRemoter + Skills,并接入远程遥控(可选)

<!-- src/App.vue -->
<template>
  <div class="app-container">
    <router-view />
    <TinyRemoter
      :show="true"
      :skills="skillMdModules"
      :mcpServers="mcpServers"
      :menuItems="menuItems"
      title="智能助手"
      :llmConfig="llmConfig"
    />
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { TinyRemoter } from '@opentiny/next-remoter'
import { createMcpServer, clientTransport } from './mcp-servers'
import { useWebAgentServer } from './mcp-servers/useWebAgentServer'

const llmConfig = {
  apiKey: import.meta.env.VITE_LLM_API_KEY || 'your-api-key-placeholder',
  baseURL: import.meta.env.VITE_LLM_BASE_URL || 'https://api.openai.com/v1',
  providerType: 'openai',
  model: 'gpt-4o',
  maxSteps: 10
}

const skillMdModules = import.meta.glob('./skills/**/*', {
  query: '?raw',
  import: 'default',
  eager: true
}) as Record<string, string>

const mcpServers = {
  'my-mcp-server': {
    type: 'local' as const,
    transport: clientTransport
  }
}

const menuItems = ref<any[]>([])

onMounted(async () => {
  // 本地 MCP 核心功能:失败直接抛出
  await createMcpServer()

  // 远程遥控增强功能:失败只打印警告,不影响本地对话
  try {
    const result = await useWebAgentServer()
    if (result?.sessionId) {
      const remoteUrl = `https://agent.opentiny.design/mcp?sessionId=${result.sessionId}`
      menuItems.value = [
        { action: 'remote-url', text: '遥控器链接', desc: remoteUrl, tip: remoteUrl, active: true, showCopyIcon: true },
        { action: 'remote-control', text: '识别码', desc: result.sessionId.slice(-6), know: true, showCopyIcon: true }
      ]
    }
  } catch (err) {
    console.warn('[WebAgent] 远程遥控初始化失败,本地功能不受影响:', err)
  }
})
</script>

中文小结:menuItems 在 WebAgent 连接成功后填充,TinyRemoter 会自动在悬浮菜单中显示"遥控器链接"和"识别码"。本地 MCP 与远程遥控必须分开 try/catch 处理,避免网络问题导致本地对话功能也一起失效。详细接入方式见 远程遥控亮点章节。

2.2 Angular 工程最佳实践(摘要)

源码工程:packages/doc-ai-angular
完整工程路径:packages/doc-ai-angular
详细文档:docs/guide/angular-webmcp-best-practice.md

Angular 与 Vue 最大的差异在于:TinyRemoter 是 Vue 组件,Angular 不能直接引入,需要通过 iframe + MessageChannel 与主应用通讯

整体架构:

  • • Angular 主应用:负责路由、业务页面、WebMCP Server、registerPageTool
  • • Vue Remoter 子应用(iframe 内):负责 TinyRemoter UI + Skills,使用 createMessageChannelClientTransport 连接主应用

步骤 1:在根组件中注册 setNavigator 并启动 MCP Server

// src/app/app.component.ts
import { ComponentOnInit, inject } from '@angular/core'
import { RouterRouterOutlet } from '@angular/router'
import { setNavigator } from '@opentiny/next-sdk'
import { createMcpServer } from '../mcp-servers'

@Component({
  selector'app-root',
  standalonetrue,
  imports: [RouterOutlet],
  templateUrl'./app.component.html',
  styleUrl'./app.component.scss'
})
export class AppComponent implements OnInit {
  private router = inject(Router)

  async ngOnInit(): Promise<void> {
    setNavigator(async (route) => {
      const navigated = await this.router.navigateByUrl(route)
      if (!navigated) {
        throw new Error(`页面跳转失败:导航至 "${route}" 被取消或拦截`)
      }
    })

    await createMcpServer()
  }
}

中文小结:和 Vue 版类似,这里统一封装“页面跳转策略”,同时在应用入口启动 MCP Server,确保后续 iframe 连接时已有可用的工具服务。

步骤 2:在根模板中通过 iframe 嵌入 Remoter

<!-- src/app/app.component.html -->
<div class="app-container">
  <div class="main-content">
    <router-outlet />
  </div>
  <aside class="remoter-sidebar">
    <iframe class="remoter-frame" src="/remoter.html" frameborder="0" allow="clipboard-write" title="AI 助手"></iframe>
  </aside>
</div>

中文小结:/remoter.html 会通过代理指向 Remoter 子应用入口(例如 Vite dev server 的 /remoter/),两端同源后即可使用 MessageChannel 互通。

步骤 3:在主窗口创建 MCP Server,并暴露 MessageChannel 服务端

// src/mcp-servers/index.ts
import {
  WebMcpServer,
  createMessageChannelServerTransport,
  withPageTools,
  registerNavigateTool
} from '@opentiny/next-sdk'
import registerProductGuideTools from './product-guide/tools'
import registerPriceProtectionTools from './price-protection/tools'

const rawServer = new WebMcpServer()
export const server = withPageTools(rawServer)

export const createMcpServer = async () => {
  registerNavigateTool(rawServer)
  registerProductGuideTools(server)
  registerPriceProtectionTools(server)

  const serverTransport = createMessageChannelServerTransport('local-mcp')
  await serverTransport.listen()
  await rawServer.connect(serverTransport)
}

中文小结:这里不再使用“同窗口内存对”的 createMessageChannelPairTransport,而是用 createMessageChannelServerTransport('local-mcp') 等待 iframe 侧主动连入。

步骤 4:在 Angular 页面中注册页面工具处理器

// src/app/pages/comprehensive/comprehensive.component.ts(节选)
import { ComponentOnInitOnDestroy } from '@angular/core'
import { registerPageTool } from '@opentiny/next-sdk'

@Component({
  /* 模板与样式省略 */
})
export class ComprehensiveComponent implements OnInitOnDestroy {
  productsProduct[] = productsData as Product[]
  private cleanupPageTool!: () => void

  ngOnInit(): void {
    this.cleanupPageTool = registerPageTool({
      handlers: {
        'product-guide'async ({ productId }: { productId: string }) => {
          const product = this.products.find((p) => String(p.id) === productId)
          const text = product
            ? `产品信息:${JSON.stringify(product, null2)}`
            : `未找到产品 ID 为 ${productId} 的商品`
          return { content: [{ type'text', text }] }
        }
      }
    })
  }

  ngOnDestroy(): void {
    this.cleanupPageTool?.()
  }
}

中文小结:写法和 Vue 版高度类似,只是生命周期钩子由 onMounted/onUnmounted 换成了 ngOnInit/ngOnDestroy,其余 Page Tool Bridge 行为完全一致。

步骤 5:在 Remoter 子应用中,通过 createMessageChannelClientTransport 连接主窗口

<!-- remoter/src/App.vue(节选) -->
<template>
  <tiny-remoter :skills="skillMdModules" :show="true" :fullscreen="true" :mcpServers="mcpServers" />
</template>

<script setup lang="ts">
import { TinyRemoter } from '@opentiny/next-remoter'
import { createMessageChannelClientTransport } from '@opentiny/next-sdk'

const skillMdModules = import.meta.glob('./skills/**/*', {
  query'?raw',
  import'default',
  eagertrue
}) as Record<string, string>

const clientTransport = createMessageChannelClientTransport('local-mcp'window.parent)

const mcpServers = {
  'local-mcp-server': {
    type'local',
    transport: clientTransport
  }
}
</script>

中文小结:endpoint 'local-mcp' 和主窗口必须一致,通过这一对 Transport,TinyRemoter 就可以把所有工具调用发送到 Angular 主应用,再由 Page Tool Bridge 转发到具体页面。

2.3 React 工程最佳实践(工程入口)

源码工程:packages/doc-ai-react
完整工程路径:packages/doc-ai-react

React 工程的整体架构与 Angular 工程高度一致,同样是:

  •  主应用(React SPA)  :直接对接 @opentiny/next-sdk,在浏览器中创建 WebMCP Server、注册业务工具,结合路由和 registerPageTool 在各业务页面内挂载页面工具处理器;
  •  Remoter 子应用(Vue)  :作为一个独立的前端子工程,通过 iframe 嵌入到 React 主应用中,内部渲染 TinyRemoter 组件并加载 WebSkills 文档;
  •  通信方式:主应用和 iframe 之间通过 MessageChannel 建立连接,主应用侧暴露服务端 Transport,Remoter 侧创建客户端 Transport,最终由 TinyRemoter 将对话中的工具调用透传到 React 主应用,再由 Page Tool Bridge 负责路由跳转和页面内业务逻辑执行。

简单理解:React 主应用负责“工具和页面”,Remoter 子应用负责“对话 UI 和技能文档”,两者通过 iframe + MessageChannel 打通,整体模式与 Angular 版本完全一致。示例工程可参考 packages/doc-ai-react,根据你的 React 路由和对话组件做适配即可。

2.4 远程遥控:跨设备遥控桌面的杀手级亮点 🎮

这是 WebMCP 区别于所有现有方案的独家能力:无需任何额外硬件或客户端,用手机扫一扫,就能用自然语言遥控桌面浏览器上的业务系统。

原理一句话

桌面浏览器(WebMCP Server)
    ↕ WebSocket 长连接
远端 Agent 平台(sessionId 路由)
    ↕
手机浏览器(遥控端 UI)
    ↓ 用户语音/文字指令
AI 解析意图 → 调用 MCP 工具 → 桌面页面执行 → 结果回显到手机

本地 MCP Server 通过 useWebAgentServer 向远端 Agent 平台注册,获得唯一 sessionId。手机端打开遥控页面并输入识别码(sessionId 后 6 位)或扫描二维码,即与桌面建立会话。

核心 API:useWebAgentServer

// src/mcp-servers/useWebAgentServer.ts
import { WebMcpServerWebMcpClient, createMessageChannelPairTransport, withPageTools } from '@opentiny/next-sdk'
import { registerAllTools } from './common'

const rawServer = new WebMcpServer()
const client = new WebMcpClient()
const [serverTransport, clientTransport] = createMessageChannelPairTransport()
export const server = withPageTools(rawServer)

const SESSION_ID_KEY = 'web-agent-session-id'

export const useWebAgentServer = async () => {
  registerAllTools(server)
  await rawServer.connect(serverTransport)
  await client.connect(clientTransport)

  const cachedSessionId = localStorage.getItem(SESSION_ID_KEY) ?? undefined
  const { sessionId } = await client.connect({
    sessionId: cachedSessionId,
    agenttrue,
    url'https://agent.opentiny.design/api/v1/webmcp-trial/mcp'
  })

  if (sessionId) localStorage.setItem(SESSION_ID_KEY, sessionId)
  return { sessionId }
}

三步快速接入

① 创建 useWebAgentServer.ts(如上)

② 在 onMounted 中分离调用(错误隔离是关键!)

onMounted(async () => {
  await createMcpServer() // 本地 MCP:失败直接抛出(核心功能)

  try {
    const result = await useWebAgentServer() // 远程遥控:失败只警告(增强功能)
    if (result?.sessionId) {
      const remoteUrl = `https://agent.opentiny.design/mcp?sessionId=${result.sessionId}`
      menuItems.value = [
        { action'remote-url'text'遥控器链接'desc: remoteUrl, tip: remoteUrl, activetrueshowCopyIcontrue },
        { action'remote-control'text'识别码'desc: result.sessionId.slice(-6), knowtrueshowCopyIcontrue }
      ]
    }
  } catch (err) {
    console.warn('[WebAgent] 远程遥控初始化失败,不影响本地功能:', err)
  }
})

⚠️ 为什么必须分开 try/catch?   若合并在同一 await 链,网络抖动导致 useWebAgentServer 失败时,整个 onMounted 会 reject,本地对话也随之失效。分开后,远程功能降级,本地始终可用。

③ 将 menuItems 传给 TinyRemoter

<TinyRemoter :menuItems="menuItems" :mcpServers="mcpServers" :skills="skillMdModules" />

⚠️ 关键细节:desc 必须存完整 URL

// ✅ 正确:desc 存带 sessionId 的完整链接
{ action: 'remote-url', desc: `${AGENT_ROOT}/mcp?sessionId=${result.sessionId}`, ... }

// ❌ 错误:desc 只存裸域名,复制后无法建立遥控会话
{ action: 'remote-url', desc: AGENT_ROOT, ... }

TinyRemoter 的复制按钮优先读取 desc 字段,若只是裸域名则复制内容缺少 sessionId,手机端无法建立遥控会话。

完整交互时序

① 桌面打开页面
    → createMcpServer():本地 MCP 启动完毕
    → useWebAgentServer():向 Agent 平台注册,获得 sessionId
    → TinyRemoter 菜单显示「遥控器链接」和「识别码」

② 用户扫码 / 复制链接 → 手机打开遥控端
    → 输入 6 位识别码(或链接自动携带 sessionId)
    → 与桌面建立 WebSocket 长连接(通过 Agent 平台路由)

③ 用户输入「帮我把库存里的 MacBook 下架」
    → AI 调用桌面的 MCP 工具 → Page Tool Bridge 自动跳转页面
    → 页面内处理器执行业务逻辑 → 结果返回给 AI → 回复用户

sessionId 持久化,刷新不丢会话

useWebAgentServer 内部将 sessionId 存入 localStorage(key:web-agent-session-id),刷新页面后自动复用,无需重新扫码。若 session 过期,Agent 平台会分配新 sessionId 并写回。

3. 总结

WebMCP + WebSkills + WebAgent 远程遥控,前端页面操作的"最优解"

对比业界现有方案,这套组合拳的优势一目了然:

能力亮点 说明
🚫 无需复杂工具 不用装浏览器插件,不用额外部署 playwright,轻量化接入
🔌 适配性更强 不要求业务页面做复杂无障碍适配,新老系统都能稳定运行
💰 高效又省钱 摆脱视觉模型的 token 消耗,执行速度快,长期成本低
🔒 安全可控 从底层保障数据安全,避免敏感信息泄露
🌐 多技术栈 Vue / React / Angular 全覆盖,实现方式统一
🎮 远程遥控(独家) 手机扫码 / 输入识别码,即可跨设备遥控桌面页面,零门槛移动端 AI 操控

远程遥控:最值得期待的杀手级亮点 🚀

远程遥控是 WebMCP 区别于所有现有方案的独家能力,也是当前最值得优先体验的功能:

  • 用户无需安装任何 App,打开手机浏览器,扫描二维码或输入 6 位识别码即可;
  • 在手机上用自然语言下达指令,AI 实时调用桌面页面注册的 MCP 工具;
  •  sessionId 自动持久化到 localStorage,刷新页面后无需重新扫码;
  • 本地对话与远程遥控完全解耦——即使远程初始化失败,本地 AI 对话功能照样可用。

未来,WebMCP + WebSkills + WebAgent 还会持续迭代优化,进一步简化接入流程、增强功能适配,覆盖更多复杂业务场景。

如果你也正在被页面操作自动化的痛点困扰,不妨直接去 GitHub 下载对应技术栈的最佳实践代码,跟着操作,分分钟解锁前端高效新姿势!

关于OpenTiny

欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~
参与 next-sdk 共建 → github.com/opentiny/ne… (欢迎star)
使用 next-sdk → opentiny.design/next-sdk
关于我们:opentiny.design/opentiny-de…

如果你有任何问题,欢迎在评论区留言交流!

昨天以前首页

MiniMax MCP 原理解读:从初始化到工具调用的完整链路分析

作者 sorryhc
2026年3月23日 11:42

引言

MiniMax MCP JS 是 MiniMax 官方提供的 Model Context Protocol (MCP) JavaScript 实现,它将 MiniMax 强大的 AI 能力(图像生成、视频生成、语音合成、声音克隆等)封装为标准的 MCP 工具,供 Claude Desktop、Cursor 等 MCP 客户端调用。本文将深入源码,剖析其从初始化到工具调用的完整技术链路。

一、MCP 服务初始化流程

1.1 入口点与启动逻辑

MiniMax MCP 的入口文件是 src/index.ts,它通过 startMiniMaxMCP 函数启动服务:

**

export async function startMiniMaxMCP(customConfig?: Partial<Config>): Promise<void> {
  try {
    const config = ConfigManager.getConfig(customConfig);
    const mode = config.server?.mode || DEFAULT_TRANSPORT_MODE;

    switch (mode) {
      case TRANSPORT_MODE_REST:
        return new MCPRestServer(config).start();
      case TRANSPORT_MODE_SSE:
        return new MCPSSEServer(config).start();
      case TRANSPORT_MODE_STDIO:
      default:
        return new MCPServer(config).start();
    }
  } catch (err) {
    process.exit(1);
  }
}

核心设计要点:

  • 支持三种传输模式:STDIO(默认)、REST、SSE
  • 通过 ConfigManager 实现多层级配置管理
  • 根据不同模式实例化对应的服务器类

1.2 配置管理机制

ConfigManager 实现了一套完善的配置优先级系统(从高到低):

**

// 配置优先级:
// 1. 请求级配置(meta.auth)- 最高优先级
// 2. 命令行参数
// 3. 环境变量
// 4. 配置文件
// 5. 默认值 - 最低优先级

static getConfig(requestConfig: Partial<Config> = {}, defaultConfig: Partial<Config> = {}): Config {
  const config: Config = {
    apiKey: '',
    apiHost: DEFAULT_API_HOST,
    basePath: DesktopPath,
    resourceMode: RESOURCE_MODE_URL,
    server: {
      port: DEFAULT_SERVER_PORT,
      endpoint: DEFAULT_SERVER_ENDPOINT,
      mode: TRANSPORT_MODE_STDIO,
    },
  };

  // 按优先级依次应用配置
  this.applyConfigFile(config);      // 优先级 4
  this.applyEnvVars(config);         // 优先级 3
  this.applyCliArgs(config);         // 优先级 2
  Object.assign(config, requestConfig); // 优先级 1

  return config;
}

设计亮点:

  • 支持多租户场景:每个请求可通过 meta.auth 使用独立配置
  • 灵活的配置方式:适配本地开发、云端部署等多种场景
  • 命令行参数解析支持 kebab-case 和 camelCase 两种风格

1.3 服务器实例化

以 STDIO 模式的 MCPServer 为例,其构造函数完成了核心组件的初始化:

**

constructor(config: Config) {
  this.config = config;
  
  // 创建 MCP 服务器实例
  this.server = new McpServer({
    name: 'minimax-mcp-js',
    version: '0.0.17',
  });

  // 初始化 API 客户端
  this.api = new MiniMaxAPI(this.config);
  
  // 初始化各个功能 API
  this.ttsApi = new TTSAPI(this.api);
  this.imageApi = new ImageAPI(this.api);
  this.videoApi = new VideoAPI(this.api);
  this.voiceCloneApi = new VoiceCloneAPI(this.api);
  this.musicApi = new MusicAPI(this.api);
  this.voiceDesignApi = new VoiceDesignAPI(this.api);

  // 注册所有工具
  this.registerTools();
}

架构特点:

  • 使用 @modelcontextprotocol/sdk 提供的 McpServer 作为底层框架
  • 采用分层设计:API 层(MiniMaxAPI)→ 功能层(TTSAPI、ImageAPI 等)→ 工具层
  • 通过依赖注入方式传递配置和 API 实例

二、对外暴露的三种连接方式

2.1 STDIO 模式(本地集成)

适用场景:  Claude Desktop、Cursor 等本地 MCP 客户端

实现原理:

**

public async startStdioServer(): Promise<void> {
  const transport = new StdioServerTransport();
  await this.server.connect(transport);
}

STDIO 模式通过标准输入输出流进行通信,是最简单直接的方式:

  • 优势:  零配置、低延迟、适合本地开发
  • 限制:  仅支持本地进程间通信,无法跨网络访问

配置示例(Claude Desktop):

**

{
  "mcpServers": {
    "minimax-mcp-js": {
      "command": "npx",
      "args": ["-y", "minimax-mcp-js"],
      "env": {
        "MINIMAX_API_KEY": "<your-api-key>",
        "MINIMAX_API_HOST": "https://api.minimaxi.chat"
      }
    }
  }
}

2.2 REST 模式(HTTP API)

适用场景:  云端部署、跨语言调用、API 服务

实现原理:

**

public async start(): Promise<void> {
  const port = this.config.server?.port || 3000;
  const endpoint = this.config.server?.endpoint || '/mcp';

  // 创建 REST 传输层
  this.transport = new RestServerTransport({
    endpoint: endpoint,
    port: port
  });

  await this.server.connect(this.transport);
  await this.transport.startServer();
}

REST 模式提供标准的 HTTP 接口:

  • 请求处理:  通过 Express 框架处理 HTTP 请求
  • 工具调用:  客户端通过 POST 请求调用工具
  • 配置提取:  支持从请求参数中提取 meta.auth 配置

核心请求处理逻辑:

**

this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const toolName = request.params.tool;
  const toolParams = request.params.params || {};

  // 从请求中提取配置
  const requestConfig = this.getRequestConfig(request);
  const requestApi = new MiniMaxAPI(requestConfig);
  
  // 根据工具名称分发到对应的处理函数
  switch (toolName) {
    case 'text_to_audio':
      return await this.handleTextToAudio(toolParams, requestApi, mediaService);
    case 'text_to_image':
      return await this.handleTextToImage(toolParams, requestApi, mediaService);
    // ... 其他工具
  }
});

2.3 SSE 模式(服务器推送)

适用场景:  需要服务器主动推送、长连接场景

实现原理:

SSE 模式是最复杂的实现,包含连接管理、心跳机制、会话管理等:

**

// 1. SSE 连接建立
app.get('/sse', async (req, res) => {
  const transport = new SSEServerTransport(endpoint, res);
  const sessionId = transport.sessionId || `session-${Date.now()}-${Math.random()}`;

  // 设置 SSE 响应头
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  // 启动心跳机制
  const heartbeatInterval = setInterval(() => {
    res.write(`event: heartbeat\ndata: ${Date.now()}\n\n`);
  }, HEARTBEAT_INTERVAL);

  // 保存连接信息
  this.connections.set(sessionId, {
    transport,
    heartbeatInterval,
    lastActivityTime: Date.now()
  });

  await this.mcpServer.connect(transport);
});

// 2. 消息处理端点
app.post(endpoint, async (req, res) => {
  const sessionId = req.query.sessionId as string;
  const connectionInfo = this.connections.get(sessionId);
  
  if (connectionInfo) {
    connectionInfo.lastActivityTime = Date.now();
    await this.handleClientMessage(connectionInfo.transport, req, res);
  }
});

SSE 模式的关键特性:

  • 连接管理:  维护 connections Map 存储所有活跃连接
  • 心跳机制:  每 30 秒发送心跳,保持连接活跃
  • 超时清理:  定期检查并清理 5 分钟无活动的连接
  • 重试机制:  失败请求自动重试,指数退避策略

三、工具调用完整链路

3.1 工具注册机制

所有工具通过 registerTools() 方法统一注册:

**

TypeScript
private registerTools(): void {
  this.registerTextToAudioTool();
  this.registerListVoicesTool();
  this.registerPlayAudioTool();
  this.registerVoiceCloneTool();
  this.registerTextToImageTool();
  this.registerGenerateVideoTool();
  this.registerImageToVideoTool();
  this.registerQueryVideoGenerationTool();
  this.registerMusicGenerationTool();
  this.registerVoiceDesignTool();
}

以 text_to_audio 工具为例,注册过程包含三个核心部分:

**

private registerTextToAudioTool(): void {
  this.server.tool(
    // 1. 工具名称
    'text_to_audio',
    
    // 2. 工具描述
    'Convert text to speech audio file...',
    
    // 3. 参数 Schema(使用 Zod 进行类型验证)
    {
      text: z.string().describe('Text to convert'),
      model: z.string().optional().default('speech-02-hd'),
      voiceId: z.string().optional().default('male-qn-qingse'),
      speed: z.number().optional().default(1.0),
      // ... 更多参数
    },
    
    // 4. 工具处理函数
    async (params) => {
      try {
        const result = await this.ttsApi.generateSpeech(params);
        return {
          content: [{
            type: 'text',
            text: `Audio saved: ${result.audio}`
          }]
        };
      } catch (error) {
        return {
          content: [{
            type: 'text',
            text: `Failed: ${error.message}`
          }]
        };
      }
    }
  );
}

设计要点:

  • 使用 Zod 进行参数验证,确保类型安全
  • 统一的错误处理和响应格式
  • 支持可选参数和默认值

3.2 API 调用层

MiniMaxAPI 类封装了与 MiniMax API 的所有交互:

**

export class MiniMaxAPI {
  private config: Config;
  private baseURL: string;
  private session: ReturnType<typeof axios.create>;

  constructor(config: Config) {
    this.config = config;
    this.baseURL = config.apiHost || 'https://api.minimax.chat';
    this.session = axios.create({
      headers: {
        'Authorization': `Bearer ${this.config.apiKey}`,
        'MM-API-Source': 'Minimax-MCP-JS'
      }
    });
  }

  async makeRequest<T>(endpoint: string, data: any, method: string = 'POST'): Promise<T> {
    const url = `${this.baseURL}${endpoint}`;
    const hasFiles = !!data.files;
    
    const config: AxiosRequestConfig = {
      method,
      url,
      headers: this.getHeaders(hasFiles),
    };

    // 处理文件上传
    if (hasFiles) {
      const formData = new FormData();
      // ... FormData 构建逻辑
      config.data = formData;
    } else {
      config.data = data;
    }

    try {
      const response = await this.session.request(config);
      
      // 检查业务错误码
      const baseResp = response.data?.base_resp;
      if (baseResp && baseResp.status_code !== 0) {
        if (baseResp.status_code === 1004) {
          throw new MinimaxAuthError(`API Error: ${baseResp.status_msg}`);
        } else {
          throw new MinimaxRequestError(`API Error: ${baseResp.status_msg}`);
        }
      }

      return response.data;
    } catch (error) {
      // 统一错误处理
      // ...
    }
  }
}

关键特性:

  • 认证管理:  自动添加 Bearer Token
  • 错误分类:  区分认证错误(1004)和其他业务错误
  • 文件上传:  支持 FormData 方式上传文件
  • 请求追踪:  通过 Trace ID 追踪请求

3.3 功能 API 层

以 TTSAPI 为例,展示功能层的实现:

**

export class TTSAPI {
  private api: MiniMaxAPI;

  constructor(api: MiniMaxAPI) {
    this.api = api;
  }

  async generateSpeech(request: TTSRequest): Promise<any> {
    // 1. 参数验证
    if (!request.text || request.text.trim() === '') {
      throw new MinimaxRequestError(ERROR_TEXT_REQUIRED);
    }

    // 2. 构建请求数据(嵌套结构)
    const requestData: Record<string, any> = {
      model: this.ensureValidModel(request.model),
      text: request.text,
      voice_setting: {
        voice_id: request.voiceId || 'male-qn-qingse',
        speed: request.speed || 1.0,
        vol: request.vol || 1.0,
        pitch: request.pitch || 0,
        emotion: this.ensureValidEmotion(request.emotion, request.model)
      },
      audio_setting: {
        sample_rate: this.ensureValidSampleRate(request.sampleRate),
        bitrate: this.ensureValidBitrate(request.bitrate),
        format: this.ensureValidFormat(request.format),
        channel: this.ensureValidChannel(request.channel)
      }
    };

    // 3. 过滤 undefined 字段
    const filteredData = this.removeUndefinedFields(requestData);

    // 4. 发送请求
    const response = await this.api.post<any>('/v1/t2a_v2', filteredData);

    // 5. 处理响应
    const audioData = response?.data?.audio;
    
    if (request.outputFormat === RESOURCE_MODE_URL) {
      return { audio: audioData };
    } else {
      // 解码并保存文件
      const audioBuffer = Buffer.from(audioData, 'hex');
      fs.writeFileSync(outputFile, audioBuffer);
      return { audio: outputFile };
    }
  }

  // 参数验证辅助方法
  private ensureValidModel(model?: string): string {
    const validModels = ['speech-02-hd', 'speech-02-turbo', ...];
    return validModels.includes(model) ? model : 'speech-02-hd';
  }
}

设计模式:

  • 参数规范化:  确保所有参数在有效范围内
  • 嵌套数据结构:  符合 MiniMax API 的请求格式
  • 资源模式切换:  支持 URL 模式和本地文件模式
  • 错误传播:  将底层错误向上传递

3.4 完整调用链路示例

以用户调用 text_to_audio 工具为例,完整链路如下:

**

1. 客户端(Claude Desktop)
   ↓ 通过 STDIO 发送 MCP 请求
   
2. MCPServer.registerTextToAudioTool()
   ↓ 接收请求,提取参数
   
3. TTSAPI.generateSpeech()
   ↓ 参数验证和规范化
   
4. MiniMaxAPI.post('/v1/t2a_v2', data)
   ↓ 构建 HTTP 请求
   
5. MiniMax API 服务器
   ↓ 处理请求,返回音频数据
   
6. TTSAPI
   ↓ 解码音频数据,保存文件
   
7. MCPServer
   ↓ 构建 MCP 响应
   
8. 客户端
   ↓ 接收结果

四、核心技术亮点

4.1 多租户支持

通过请求级配置实现多租户:

**

// REST 模式下,每个请求可以携带独立配置
private getRequestConfig(request: any): Config {
  const metaAuth = request.params?._meta?.auth;
  const requestConfig = ConfigManager.extractConfigFromMetaAuth(metaAuth);
  return ConfigManager.getConfig(requestConfig, this.config);
}

4.2 资源模式切换

支持两种资源返回模式:

**

// URL 模式:返回资源 URL(适合云端部署)
if (this.config.resourceMode === RESOURCE_MODE_URL) {
  return { content: [{ type: 'text', text: `Video URL: ${result.video_url}` }] };
}

// Local 模式:下载并保存到本地(适合本地使用)
else {
  await this.api.downloadFile(result.video_url, outputPath);
  return { content: [{ type: 'text', text: `Video saved: ${outputPath}` }] };
}

4.3 异步任务支持

视频生成等耗时任务支持异步模式:

**

if (params.asyncMode) {
  const result = await this.videoApi.generateVideo(params);
  return {
    content: [{
      type: 'text',
      text: `Task submitted: ${result.task_id}. Use query_video_generation to check status.`
    }]
  };
}

4.4 重试机制

REST 和 SSE 模式实现了自动重试:

**

private async handleTextToAudio(args: any, api: MiniMaxAPI, mediaService: MediaService, attempt = 1): Promise<any> {
  try {
    return await mediaService.generateSpeech(args);
  } catch (error) {
    if (attempt < MAX_RETRY_ATTEMPTS) {
      await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * Math.pow(2, attempt - 1)));
      return this.handleTextToAudio(args, api, mediaService, attempt + 1);
    }
    throw error;
  }
}

五、总结

MiniMax MCP JS 通过精心设计的架构,实现了:

  1. 灵活的部署方式:支持 STDIO、REST、SSE 三种传输模式
  2. 完善的配置管理:五层优先级配置系统,适配多种场景
  3. 清晰的分层架构:传输层 → 服务层 → API 层 → 功能层
  4. 健壮的错误处理:统一的错误分类和重试机制
  5. 多租户支持:请求级配置实现资源隔离

这套架构不仅为 MiniMax AI 能力提供了标准化的 MCP 接口,也为其他 AI 服务的 MCP 集成提供了优秀的参考实现。

❌
❌