Разработчикам
MCP — открытый стандарт для подключения ИИ к вашим системам: зарегистрируйте свой сервер, выберите, кто может использовать каждый инструмент, и опубликуйте. Развёртывайте ассистента в веб-версии, iOS, Android и на рабочем столе, и управляйте каждым рабочим пространством из любого MCP-клиента.
claude mcp add --transport http busymate-ai https://busymate.ai/mcpТочки входа
Один тег скрипта на любой разрешённой странице — лончер и гостевой чат работают сразу.
Установите версионированный пакет Swift для идентификации учётной записи, чата и запроса разрешения на микрофон при нажатии.
Установите версионированную библиотеку Android для идентификации учётной записи, чата и запроса разрешения на микрофон при нажатии.
Ваш собственный MCP-сервер становится действиями ассистента — от имени каждого клиента.
Управляйте платформой из Claude, Claude Code, Cursor или любого MCP-клиента через OAuth.
WebMCP: ваши страницы сами указывают свои действия как инструменты — бронирование, заказ, проверка статуса — со встроенным подтверждением.
01Быстрый старт
Ваше приложение остаётся источником истины. Ассистент получает короткоживущий подписанный токен, который говорит, кто присутствует, и вызывает ваши инструменты как отдельный актор с ограниченными правами.
01
Хранит личность и данные аккаунта; подписывает двухминутный токен о том, кто авторизован.
02
Применяет настройки вашего рабочего пространства: контент, модели, уровни доступа инструментов, шаги подтверждения, передачу оператору.
03
Отвечает инструментами для одного пользователя за раз, беря пользователя из bearer-токена входа, а не из аргументов инструмента.
Вариантов подключения: 7, ассистент один: Встраивание на сайт · Авторизованные клиенты · iOS · Android · Десктоп · REST API · Ваш MCP-сервер
02Авторизованные клиенты
Чтобы ассистент мог доверять тому, кто спрашивает, одна аутентифицированная точка на вашем бэкенде подписывает короткоживущий токен (JWT на ES256) с вашим постоянным идентификатором клиента и несколькими безопасными полями для отображения.
Токен живет не более 120 секунд, содержит одноразовый nonce и jti и указывает ваше рабочее пространство. Приватный ключ и сеанс вашего продукта никогда не попадают в браузер или чат.
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: process.env.BMAI_TENANT_ID, // the tenant id shown in Console → Identity
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(process.env.BMAI_ISSUER) // the Issuer you registered in Console → Identity
.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 });
});Токен говорит, кто пришёл, а не что ему позволено. Держите балансы, устройства, настройки и любые изменения за инструментами, которые отвечают только для одного пользователя.
03Веб и полная страница
SDK запрашивает личность у вашего бэкенда, когда она нужна, работает для гостей, обновляется после входа и выхода, открывает внешние ссылки вне фрейма и не требует сторонних cookie.
<!-- Floating "mate" 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, an access-token/session
// rotation, or an account switch — upgrades the live conversation in
// place, same thread. Do not send an identity postMessage directly.
window.assistantIdentityChanged = () =>
window.BusymateAI?.identityChanged?.() ?? Promise.resolve(false);
// Call after YOUR product completes logout. Without this the signed-out
// visitor keeps a verified chat and the previous transcript until the tab
// closes — a privacy defect, not a missing nicety.
window.assistantSignedOut = () =>
window.BusymateAI?.signedOut?.("signed_out");
</script>
<script
src="https://your-assistant.busymate.ai/embed/v1.js"
data-assistant="your-assistant"
data-label="Ask mate"
async></script>Когда авторизованный пользователь открывает адрес вашего ассистента или ваш собственный домен, передайте токен входа во фрагменте URL. Страница очищает его до обмена.
// 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
Зафиксируйте версию пакета Swift. Подключите ваш backend с аутентификацией и добавьте системное разрешение на микрофон, следуя руководству для платформы и примеру приложения.
iOS: установка, настройка и демонстрация
// Swift Package: https://github.com/serebano/busymate-ai-sdk-ios.git (exact 1.0.2)
// Full lifecycle + demo: https://busymate.ai/docs/guides/ios-sdk
import WebKit
import BusymateAI
@MainActor
func installAssistant(
on webView: WKWebView,
account: @escaping () -> String?,
mint: @escaping @MainActor (BusymateBridge.MintRequest) async throws -> BusymateBridge.MintToken?
) {
BusymateBridge.install(on: webView, config: .init(
assistant: "your-assistant",
origins: ["https://your-assistant.busymate.ai"],
account: account,
mint: mint
))
// mint calls YOUR authenticated backend with request.nonce; never cache tokens.
// Backend endpoint: https://YOUR-PRODUCT-DOMAIN/api/bmai/identity
webView.load(URLRequest(url: URL(string: "https://your-assistant.busymate.ai/?channel=ios")!))
}
// Microphone: declare NSMicrophoneUsageDescription; install and retain
// BusymateMicrophone before load, and forward WKUIDelegate media permission.
// Call BusymateBridge.accountChanged() on login/logout/account changes.05Android
Зафиксируйте версию библиотеки Android. Установите её до загрузки чата и следуйте руководству по жизненному циклу Activity, микрофону и восстановлению процесса рендеринга.
Android: установка, настройка и демонстрация
// Gradle installation + lifecycle: https://busymate.ai/docs/guides/android-sdk
// Source: https://github.com/serebano/busymate-ai-sdk-android/tree/1.0.2
import android.webkit.WebView
import ai.busymate.bridge.BusymateBridge
fun installAssistant(
webView: WebView,
account: () -> String?,
mint: (Map<String, Any?>, (BusymateBridge.MintResult) -> Unit) -> Unit
) {
BusymateBridge.install(webView, BusymateBridge.Config(
assistant = "your-assistant",
origins = listOf("https://your-assistant.busymate.ai"),
account = account,
mint = mint
))
// mint calls YOUR authenticated backend with request["nonce"], then done(result).
// Backend endpoint: https://YOUR-PRODUCT-DOMAIN/api/bmai/identity
webView.loadUrl("https://your-assistant.busymate.ai/?channel=android")
}
// Microphone: declare INTERNET + RECORD_AUDIO; construct and retain
// BusymateMicrophone in Activity.onCreate BEFORE STARTED/loadUrl; forward
// WebChromeClient.onPermissionRequest. See the guide for renderer recovery.
// Call BusymateBridge.accountChanged() on login/logout/account changes.06Десктоп
Официальные SDK для macOS, Windows, Electron, Linux и Tauri загружают размещённый чат в изолированном WebView, отвечают на запросы идентификации через привилегированный слой хоста и открывают безопасные ссылки во внешнем браузере. Зафиксируйте версию SDK и подключите свой аутентифицированный бэкенд так же, как на мобильных.
Настольные платформы: установка, настройка и примеры приложений
// Official desktop SDK for your platform — macOS, Windows, Electron, Linux or Tauri:
// https://github.com/serebano/busymate-ai-sdk-electron (and -macos, -windows, -linux, -tauri).
// Every SDK takes these settings and installs the identity bridge before the first load.
const chatOptions = {
url: "https://your-assistant.busymate.ai/?channel=desktop_sdk", // the window's top-level page
assistant: "your-assistant",
account: () => productAuth.currentUserId(), // null when signed out
// YOUR authenticated backend signs { token, nonce } for request.nonce.
mint: (request) => productAuth.mintAssistantIdentity(request),
};07Инструменты MCP
Публикуйте полные схемы при обнаружении. Присвойте каждому инструменту один из трёх уровней доступа, отметьте изменения, требующие карточки подтверждения, и заново проверяйте права при каждом вызове.
Может вызвать любой, кто пишет в чат. Цены, статус, возможности.
Требует авторизованного пользователя. Статус заказа, ответы по аккаунту.
Действует от имени авторизованного пользователя через актор-токен с ограниченными правами или собственное OAuth-разрешение пользователя.
Каждое изменение подтверждается на сервере. Инструменты, перечисленные в confirm_tools, останавливаются на карточке подтверждения перед выполнением; так же ведёт себя любое имя инструмента, которого ассистент не знает. Фреймы виджетов никогда не выполняют изменения.
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// POST https://YOUR-DOMAIN/mcp
// Authorization: Bearer <the per-user OAuth token mate 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-клиента.
REST API и вебхуки
Один ключ API, привязанный к рабочему пространству, аутентифицирует REST-точки доступа и MCP-сервер. Получайте список диалогов и читайте их, отправляйте ответ оператора или подпишитесь на вебхук вместо опроса.
Создайте ключ в Console → API keys, затем отправьте его как Bearer token.
curl https://busymate.ai/api/v1/conversations \
-H "Authorization: Bearer bmai_sk_..."Полная справка: руководство API, вебхуки, документ OpenAPI.
08Передача оператору и аналитика
Клиент просит человека, инструмент выдал ошибку, возврат превышает ваш лимит, встретилось ключевое слово или произошло любое событие, которое вы задали.
Одни Входящие; назначение вручную, по очереди или тому, у кого меньше всего открытых чатов; оповещения в приложении, через Web Push, APNs и личный Telegram.
Наблюдайте за диалогом, подключайтесь с полным контекстом, отвечайте в том же чате; пока человек активен, ассистент ничего не меняет.
Повторяющиеся вопросы, пробелы в знаниях и неудачные сценарии, сгруппированные из реальных диалогов, со ссылками на подтверждения.
09Вход рабочего пространства
Настройте URL входа и аккаунта рабочего пространства в Консоли. Гость, выбравший «Войти», покидает фрейм, проходит аутентификацию в вашем продукте и возвращается точно на исходный URL. Затем ваш бэкенд выдаёт одноразовый токен входа; Busymate AI никогда не получает ни пароль пользователя, ни ваш ключ подписи.
Изменение всегда затрагивает только собственные данные авторизованного пользователя. Инструменты аккаунта берут клиента из делегированного актора, заново проверяют права при каждом вызове и запрашивают подтверждение на тех изменениях, которые вы настроили.
10Чек-лист запуска
Готовы настроить своё рабочее пространство?
Страница интеграции в Консоли сама генерирует код и настройки для каждой площадки на основе ваших опубликованных настроек.
WebMCP
WebMCP — это черновик W3C Web Machine Learning Community Group для страниц, которые сами указывают свои действия как инструменты. Три способа внедрения: от минимума изменений до полного контроля.
book_appointment, check_order_status, start_return — один инструмент, одна задача.
«next Tuesday at ten», «Express» — никогда внутренний id, который должен угадать ассистент.
Предложите инструменты, которые может запустить эта страница; удалите их регистрацию, когда страница переходит дальше.
Бронирование, платёж, возврат — отметьте это, и клиент подтвердит перед выполнением.
Подтверждение остаётся у клиента. Инструменты работают в браузере посетителя на вашем домене. Инструмент, помеченный как consequential, останавливается на подтверждении, которое видит клиент; exposedTo ограничивает, какие домены смогут видеть инструмент.
Минимум изменений: имя и однострочное описание на форме, которая у вас уже есть. Браузер преобразует это в инструмент; форма остаётся видимой и продолжает работать для всех.
<form action="/book" toolname="book_appointment"
tooldescription="Book an appointment for a date, a time and a service."
toolautosubmit>
<input name="date" type="date" toolparamdescription="The day, e.g. next Tuesday">
<input name="time" type="time" toolparamdescription="The start time">
<select name="service" toolparamdescription="Which service to book">
<option>Consultation</option>
<option>Follow-up</option>
</select>
<button type="submit">Book</button>
</form>Полный контроль: API браузера. Проверяйте поддержку — браузеры без WebMCP получают страницу в прежнем виде. Chrome 149 и Edge 150 поддерживают это как origin trial; Firefox и Safari рассматривают предложение.
// The browser API (draft). Load the polyfill BEFORE this file so every browser has it
// (it stands down where the browser is native):
// <script src="https://busymate.ai/webmcp/polyfill-1.0.1.js" integrity="sha384-pLZ5lO3gwZIn8Nim+hDrUQY+bvpliRtZhrS1O1OGRFB3lvoW0sZ71DUFHIk3m+DG" crossorigin="anonymous"></script>
const ctx = document.modelContext ?? navigator.modelContext;
if (!ctx) throw new Error("no WebMCP registry: the polyfill did not load");
ctx?.registerTool({
name: "start_return",
description: "Start a return for a delivered order. Asks the customer to confirm first.",
inputSchema: { type: "object", properties: { orderNumber: { type: "string", description: "The order number, as printed on the receipt" } }, required: ["orderNumber"] },
annotations: { consequentialHint: true },
execute: async ({ orderNumber }) => startReturn(orderNumber),
}, { exposedTo: ["self"] });Там, где ассистент уже установлен, один вызов выдаст ваши инструменты для него и для каждого другого ассистента, который заходит — через API браузера, где он существует, и через собственный мост ассистента везде остального.
// One call, both transports: the browser's own WebMCP where it exists,
// and the assistant's bridge everywhere else (Safari, Firefox, app WebViews).
BusymateAI.registerPageTools([
{
name: "check_order_status",
description: "Look up an order by its number and say where it is.",
inputSchema: {
type: "object",
properties: { orderNumber: { type: "string", description: "The order number, as printed on the receipt" } },
required: ["orderNumber"],
additionalProperties: false,
},
annotations: { readOnlyHint: true },
execute: async ({ orderNumber }) => (await fetch(`/api/orders/${orderNumber}`)).json(),
},
]);Инструкция по внедрению и готовый к копированию промптСтраница на простом языке для вашей команды
MCP управления
Инструменты управления через MCP с OAuth 2.1. При первом подключении клиент открывает вход в браузер; ничего не вставляется.
https://busymate.ai/mcp| Клиент | Способ подключения |
|---|---|
| Claude | Добавьте коннектор из каталога коннекторов Claude и войдите — ничего вставлять не нужно. |
| ChatGPT and Codex | Приложение проходит проверку в OpenAI и пока не опубликовано в каталоге. |
| Claude Code (CLI) | Выполните команду ниже — браузер обработает вход через OAuth. |
| Cursor | Вставьте запись mcp.json ниже в настройки MCP Cursor. |
| Другие MCP-клиенты | Предоставьте клиенту эндпоинт ниже; OAuth 2.1 discovery и PKCE сделают остальное. |
Аккаунт, под которым вы вошли, определяет, какими рабочими пространствами клиент может управлять. Каждое изменение запрашивает подтверждение.
claude mcp add --transport http busymate-ai https://busymate.ai/mcpCursor, Claude Desktop и любой клиент, читающий mcp.json, принимают эту запись; клиенты, которым нужен удалённый URL, берут точку выше.
{
"mcpServers": {
"busymate-ai": {
"type": "http",
"url": "https://busymate.ai/mcp"
}
}
}Последние изменения
сборка 1630
Busymate AI views in ChatGPT load their own data when ChatGPT doesn't deliver it. When ChatGPT runs several Busymate AI lookups at once, for example the conversations of five workspaces, the iPhone app often passed the result to only one of the views and the rest said "Couldn't load this result". A view for a read-only lookup now fetches its own data after 3 seconds when nothing arrived, using the same request. If that fails, it shows the real reason.
сборка 1629
Signed-in customers now get one consistent answer about account tools. Each connector now has one "customer identity".
сборка 1628
Write confirmation (preview first, then Apply or yes in chat) now applies only in the ChatGPT and Claude apps. Automations and other connected clients write directly again, and every automated caller fails loudly if it ever gets a preview instead of a write .