Busymate AI

开发者

用 MCP 给您的 AI 助手配上真正的工具

通过 MCP——连接 AI 与您系统的开放标准——为助手配上真正的工具。在 Web、iOS、Android 和桌面端上线,并从任意 MCP 客户端管理一切。

claude mcp add --transport http busymate-ai https://busymate.ai/mcp
TERMINAL$claude mcp add --transport http busymate-ai https://busymate.ai/mcp已连接JSON-RPC 2.0 · tools/call{ "jsonrpc": "2.0", "id": 12, "method": "tools/call", "params": { "name": "get_my_account", "arguments": {} } }{ "jsonrpc": "2.0", "id": 12, "result": { "content": [{ "type": "text", "text": "…" }] } }

接入方式

选一个起点

把助手加到您的网站上

在您允许的任意页面加一个 script 标签——悬浮入口和访客聊天立刻可用。

把 AI 客服放进您的 iOS 应用

在 WKWebView 里加载聊天;一座三条消息的小桥完成用户登录。

把 AI 客服放进您的 Android 应用

在 WebView 里加载聊天;一个 JavaScript 接口完成用户登录。

把您的 MCP 服务器接成工具

您自己的 MCP 服务器成为助手的操作能力,并代表每一位顾客执行。

在终端里管理每一个工作区

通过 OAuth,从 Claude Code、Cursor 或任意 MCP 客户端驱动平台。

让 AI 智能体读懂您的站点

在 /llms.txt 提供一份纯文本站点地图,专为 AI 智能体而写。

01快速上手

各部分如何衔接

您的应用始终是事实来源。助手收到一个短时效的签名令牌,说明当前是谁在场,并以一个独立且受限的执行者身份调用您的工具。

您的顾客您的网站或应用助手您的系统您的设置自动应用

01

您的应用

保管身份和账户数据;签发一个两分钟有效的令牌,说明是谁登录了。

02

助手

应用您工作区的设置:内容、模型、工具访问级别、确认步骤、转接。

03

您的 MCP 服务器

每次只为一位用户作答,用户身份取自已登录的 bearer 令牌——绝不取自工具参数。

7 种接入方式,一个助手: Web 嵌入 · 已登录顾客 · iOS · Android · 桌面端 · REST API · 您的 MCP 服务器

YOUR HELP SITErobots-aware1–50 pagesIndexRetrieve for an answerwith a source

02已登录顾客

识别已经登录的顾客

为了让助手确信提问者的身份,您后端上一个需要认证的端点会用您稳定的顾客 id 和少量可安全展示的字段,签发一个短时效令牌(一个 ES256 JWT)。

该令牌最长存活 120 秒,携带一次性 nonce 与 jti,并写明您的租户。私钥和您产品的会话永远不会进入浏览器或聊天。

Your backendThe browserThe assistantsigns the tokenthe key never leavescarries itchecks your JWKS120s maxchecked
server/bmai-identity.tstypescript
import { SignJWT, importJWK } from "jose";

// POST https://YOUR-PRODUCT-DOMAIN/api/bmai/identity — requires YOUR OWN logged-in product session.
app.post("/api/bmai/identity", requireSession, async (req, res) => {
  // Fresh on EVERY mint. Never persist the launch token/nonce in localStorage,
  // sessionStorage, cookies, React state, or module state: every assistant
  // launch consumes this pair exactly once.
  const nonce = typeof req.body?.nonce === "string" ? req.body.nonce : "";
  if (!/^[A-Za-z0-9_-]{32,200}$/.test(nonce)) return res.status(400).json({ error: "invalid_nonce" });
  const key = await importJWK(JSON.parse(process.env.AI_LAUNCH_PRIVATE_JWK), "ES256");
  const token = await new SignJWT({
      "tenant_id": "00000000-0000-0000-0000-000000000000", // Your product's registered tenant claim
      nonce,                               // equals the sibling field below
      name: req.user.displayName,          // optional low-sensitivity display claim
    })
    .setProtectedHeader({ alg: "ES256", kid: process.env.AI_LAUNCH_KEY_ID })
    .setIssuer("https://YOUR-PRODUCT-DOMAIN (set when you register the provider)")
    .setAudience("busymate-ai")
    .setSubject(req.user.id)               // IMMUTABLE internal account id; never email/phone/session id
    .setJti(crypto.randomUUID())           // one-time (replay-protected)
    .setIssuedAt()
    .setExpirationTime("120s")               // <= registered max age (120s)
    .sign(key);
  res.set("Cache-Control", "no-store");
  res.status(201).json({ token, nonce, expiresIn: 120 });
});

令牌说明的是谁在场,而不是他们可以做什么。 把余额、设备、设置和每一次变更,都放在只为单个用户作答的工具背后。

03Web 与整页

一段脚本,一个嵌入式悬浮入口

SDK 在需要身份时向您的后端索取,对访客同样可用,在登录和退出后自动刷新,把外部链接放到框架外打开,且完全不需要第三方 cookie。

index.htmlhtml
<!-- Floating "bro" launcher for Your product.
     Anonymous chat works immediately on the allowed origins. -->
<script>
  function newLaunchNonce() {
    const bytes = new Uint8Array(32);
    crypto.getRandomValues(bytes);
    return btoa(String.fromCharCode(...bytes))
      .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
  }

  window.BusymateAI = {
    getIdentity: async () => {
      const accessToken = await getProductAccessToken(); // YOUR existing auth helper
      if (!accessToken) return null;
      const nonce = newLaunchNonce();
      const returnTo = new URL(location.href); returnTo.search = ""; returnTo.hash = "";
      const r = await fetch("https://YOUR-PRODUCT-DOMAIN/api/bmai/identity", {
        method: "POST", credentials: "include", cache: "no-store",
        headers: { "content-type": "application/json", authorization: "Bearer " + accessToken },
        body: JSON.stringify({ nonce, returnTo: returnTo.href }),
      });
      if (r.status === 401) return null; // signed out -> anonymous chat
      if (!r.ok) throw new Error("AI identity mint failed (" + r.status + ")");
      // MUST be a newly minted { token, nonce } pair on every call. Never
      // persist either value in localStorage, sessionStorage, cookies, React
      // state, or module state.
      const identity = await r.json();
      if (identity.nonce !== nonce) throw new Error("AI identity nonce mismatch");
      return { token: identity.token, nonce: identity.nonce };
    },
  };

  // Call after YOUR product completes login, logout, access-token/session
  // rotation, or account switch. Do not send an identity postMessage directly.
  window.refreshAssistantIdentity = () =>
    window.BusymateAI?.refreshIdentity?.() ?? Promise.resolve();
</script>
<script
  src="https://your-assistant.busymate.ai/embed/v1.js"
  data-assistant="your-assistant"
  data-label="Ask bro"
  async></script>

在您的地址上打开整页聊天

当已登录用户打开您助手的地址或您的自定义域名时,把登录令牌放在 URL 片段中传入。页面会在交换之前把它清除。

open-assistant.tstypescript
// Full-page open with identity in the URL FRAGMENT — never sent in the
// request line, referrer, or logs; the destination strips it before exchange.
function newLaunchNonce() {
  const bytes = new Uint8Array(32);
  crypto.getRandomValues(bytes);
  return btoa(String.fromCharCode(...bytes))
    .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
}

const nonce = newLaunchNonce();
const accessToken = await getProductAccessToken(); // YOUR existing auth helper
if (!accessToken) throw new Error("Sign in before opening an identified assistant");
const returnTo = new URL(location.href); returnTo.search = ""; returnTo.hash = "";
const response = await fetch("https://YOUR-PRODUCT-DOMAIN/api/bmai/identity", {
  method: "POST", credentials: "include", cache: "no-store",
  headers: { "content-type": "application/json", authorization: "Bearer " + accessToken },
  body: JSON.stringify({ nonce, returnTo: returnTo.href }),
});
if (response.status === 401) throw new Error("Sign in before opening an identified assistant");
if (!response.ok) throw new Error("AI identity mint failed (" + response.status + ")");
const identity = await response.json();
if (typeof identity.token !== "string" || typeof identity.nonce !== "string") {
  throw new Error("AI identity mint returned an invalid response");
}
if (identity.nonce !== nonce) throw new Error("AI identity nonce mismatch");

const url = new URL("https://your-assistant.busymate.ai/");
url.hash = new URLSearchParams({
  bmai_token: identity.token,
  bmai_nonce: identity.nonce,
}).toString();
location.assign(url);

04iPhone

WKWebView 加一座窄口径的消息桥

只允许三条带版本号的消息:身份请求、身份响应和外部 URL。身份令牌通过应用已有的认证 API 客户端签发。

AssistantBridge.swiftswift
// SDK source: https://busymate.ai/sdk/v1/ios/BusymateAI.swift
// Load https://your-assistant.busymate.ai/?channel=ios in a WKWebView.
final class AssistantBridge: NSObject, WKScriptMessageHandler {
  let webView: WKWebView

  func userContentController(_ controller: WKUserContentController,
                             didReceive message: WKScriptMessage) {
    guard message.name == "BusymateAI",
          let body = message.body as? [String: Any],
          body["type"] as? String == "busymate.ai.v1.identity_request"
    else { return }

    Task { // mint through YOUR authenticated API — never a key in the app
      let identity = try await api.mintLaunchIdentity()
      let payload: [String: Any] = [
        "type": "busymate.ai.v1.identity",
        "token": identity.token,
        "nonce": identity.nonce,
      ]
      let data = try JSONSerialization.data(withJSONObject: payload)
      let json = String(decoding: data, as: UTF8.self)
      await webView.evaluateJavaScript("window.postMessage(\(json), '*')")
    }
  }
}

05Android

WebView 加一个白名单接口

将导航限制在租户 AI 源站,把外部链接交给系统浏览器,并且不要暴露通用原生方法。

AssistantBridge.ktkotlin
// SDK source: https://busymate.ai/sdk/v1/android/BusymateAI.kt
// Load https://your-assistant.busymate.ai/?channel=android in a WebView.
class AssistantBridge(private val webView: WebView) {
  @JavascriptInterface
  fun postMessage(raw: String) {
    val message = JSONObject(raw)
    if (message.optString("type") != "busymate.ai.v1.identity_request") return

    lifecycleScope.launch { // mint through YOUR authenticated API client
      val identity = api.mintLaunchIdentity()
      val response = JSONObject()
        .put("type", "busymate.ai.v1.identity")
        .put("token", identity.token)
        .put("nonce", identity.nonce)
      webView.evaluateJavascript(
        "window.postMessage(${JSONObject.quote(response.toString())}, '*')", null
      )
    }
  }
}

webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(AssistantBridge(webView), "BusymateAINative")
// SupportChatNative + support.chat.v1.* remain accepted for shipped apps.

06桌面端

同一套协议同样适用于 Electron、Tauri 和原生外壳

使用隔离的 WebView,禁止在视图内新开窗口,把安全的 URL 交给外部打开,并通过特权宿主层来响应身份请求。

assistant-renderer.tstypescript
import { mountBusymateAI } from "https://busymate.ai/sdk/v1/index.js";

// productAuth is a narrow preload/Tauri command bridge. It calls YOUR
// authenticated backend; no cookie, signing key, or refresh token is exposed
// to the renderer.
const assistant = await mountBusymateAI({
  assistant: "your-assistant",
  origin: "https://your-assistant.busymate.ai",
  label: "Ask bro",
  getIdentity: () => window.productAuth.mintAssistantIdentity(),
});

window.productAuth.onSessionChanged(() => assistant.refreshIdentity());
assistant.open();

// Electron main process (Tauri: use the equivalent shell/open allowlist):
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
  const target = new URL(url);
  if (target.protocol === "https:" || target.protocol === "http:") {
    void shell.openExternal(target.href);
  }
  return { action: "deny" };
});

07MCP 工具

给助手工具,而不是把顾客数据库交出去

在发现阶段发布完整的 schema。为每个工具指定三种访问级别之一,标出需要确认卡片的变更,并在每次调用时重新校验授权。

公开

任何聊天的人都可以调用。价格、状态、功能。

已识别

需要用户已登录。订单状态、账户类问题。

受委派

通过受限的执行者令牌或用户本人的 OAuth 授权,代表已登录用户行事。

每一次变更都在服务器端确认。 您列入 confirm_tools 的工具在执行前会停在确认卡片处;助手不认识的工具名同样如此。组件框架永远不会执行变更。

oauth-requirements.txttext
MCP endpoint ............ https://YOUR-DOMAIN/mcp
RFC 8707 resource ....... https://YOUR-DOMAIN/mcp   (the token is audience-bound to this)
AS metadata (RFC 8414) .. https://YOUR-DOMAIN/.well-known/oauth-authorization-server
Resource meta (RFC 9728)  https://YOUR-DOMAIN/.well-known/oauth-protected-resource
Client registration ..... Dynamic (RFC 7591) — public client, no secret
PKCE .................... S256 required (RFC 7636)
Authorization response .. iss parameter checked (RFC 9207)
Grants .................. authorization_code + refresh_token
Delegated tools/call .... no bearer -> 401; user derived from the bearer,
                          NEVER from an account id in tool arguments
Customer experience ..... one separate Authorize account tools action is expected;
                          use signed_actor_token instead for automatic SSO
delegated-call.jsonjson
// POST https://YOUR-DOMAIN/mcp
// Authorization: Bearer <the per-user OAuth token bro obtained>
{
  "jsonrpc": "2.0",
  "id": 12,
  "method": "tools/call",
  "params": { "name": "get_my_account", "arguments": {} }
}
// Your server verifies the bearer, derives the user from its signed subject,
// and returns ONLY that user's data.

平台支持的协议

无论平台是您服务器的客户端,还是您 MCP 客户端的服务器,适用的都是同一套协议。

  • OAuth 2.1 + PKCE(S256);公共客户端动态注册(RFC 7591)。
  • 通过授权服务器元数据(RFC 8414)和受保护资源元数据(RFC 9728)进行发现。
  • 授权类型:authorization_code 与 refresh_token;授权响应中带有签发方(RFC 9207)。
  • 当平台作为 OAuth 客户端访问您的 MCP 服务器时,使用资源指示符(RFC 8707)。
  • 在已配置的场景下使用令牌交换(RFC 8693)。

08转接与洞察

设定真人何时介入的规则

何时转接

顾客要求找真人、工具调用失败、退款超出您的限额、出现某个关键词,或任何由您定义的事件。

您的团队如何收到通知

一个收件箱;可手动指派、轮流接单,或分给未结对话最少的人;提醒方式包括应用内、Web Push、APNs 和个人 Telegram。

您的团队接手

观察对话、带着上下文加入、在同一段聊天里回复;真人在场期间,助手不会做任何变更。

洞察

重复出现的问题、缺失的知识和走不通的路径,从真实对话中归类整理,并附上证据链接。

09工作区登录

保留您自己的用户体系和认证方式

在 Console 中配置工作区的登录和账户 URL。访客点击登录后离开聊天框架,在您的产品上完成认证,再回到原来那个确切的 URL。随后您的后端提供一次性登录令牌;Busymate AI 永远拿不到用户的密码或您的签名密钥。

变更只会触及已登录用户本人的数据。 账户类工具从受委派的执行者那里取得顾客身份,在每次调用时重新校验授权,并对您配置的变更请求确认。

10上线检查清单

上线前检查清单

  1. 01设置品牌形象、SEO、您的网址和推荐问题。
  2. 02注册您的登录体系:签发方、JWKS、受众、claims 和令牌时效。
  3. 03接入 MCP,为每个工具设定访问级别,验证读取只限本人范围、写入必须确认。
  4. 04设定转接规则,为收件箱安排人力,设置工作时间和响应目标。
  5. 05测试访客、已登录用户、退出登录与切换账户、重放的令牌,以及被替换的工作区 id。
  6. 06检查整页聊天、嵌入、iOS、Android、桌面端、无障碍和外部链接。
  7. 07只发布您已检查过的版本;持续关注连接和域名的健康状况。

准备好配置您的工作区了吗?

Console 的集成页面会根据您已发布的设置,为每个入口生成对应的代码和配置。

打开 Console

管理用 MCP

从任意 MCP 客户端管理每一个工作区

218 个管理工具,基于 MCP 与 OAuth 2.1。客户端首次连接时会打开浏览器登录;无需粘贴任何东西。

发现文档

您登录所用的账户决定了客户端可以管理哪些工作区。每一次变更都会请求确认。

Your MCP clientcreate_artifactJSON-RPCcreated: truehttps://your-assistant.busymate.ai/artifact/…VISIBILITYSitemapGallery

Claude Code

claude-code.shbash
claude mcp add --transport http busymate-ai https://busymate.ai/mcp

从任意 MCP 客户端管理平台

Cursor、Claude Desktop 以及任何会读取 mcp.json 的客户端都接受这段配置;支持远程 URL 的客户端直接填上面那个端点。

mcp.jsonjson
{
  "mcpServers": {
    "platform-management": {
      "type": "http",
      "url": "https://busymate.ai/mcp"
    }
  }
}

今天就接入第一个工具

注册您的 MCP 服务器,选定每个工具允许谁使用,然后发布。助手同一分钟就能用上。