Desarrolladores
MCP es el estándar abierto para conectar la IA a sus sistemas: registre su servidor, elija quién puede usar cada herramienta, publique. Implemente el asistente en web, iOS, Android y escritorio, y gestione cada espacio de trabajo desde cualquier cliente MCP.
claude mcp add --transport http busymate-ai https://busymate.ai/mcpVías de entrada
Una etiqueta script en cualquier página que permitas: el lanzador y el chat para invitados funcionan al momento.
Instala el paquete Swift versionado para la identidad de cuenta, el chat y el permiso del micrófono solicitado al tocar el control.
Instala la biblioteca Android versionada para la identidad de cuenta, el chat y el permiso del micrófono solicitado al tocar el control.
Su propio servidor MCP se convierte en las acciones del asistente, en nombre de cada cliente.
Maneja la plataforma desde Claude, Claude Code, Cursor o cualquier cliente MCP mediante OAuth.
WebMCP: sus páginas enumeran sus propias acciones como herramientas —reservar, pedir, consultar un estado— con confirmación integrada.
01Inicio rápido
Su aplicación sigue siendo la fuente de la verdad. El asistente recibe un token firmado de corta duración que dice quién está presente y llama a sus herramientas como un actor aparte y acotado.
01
Conserva la identidad y los datos de cuenta; firma un token de dos minutos que dice quién ha iniciado sesión.
02
Aplica los ajustes de su espacio de trabajo: contenido, modelos, niveles de acceso de las herramientas, pasos de confirmación y traspaso.
03
Responde a las herramientas para un usuario cada vez, tomando al usuario del bearer token de la sesión, nunca de los argumentos de la herramienta.
7 formas de conectar, un solo asistente: Embed web · Clientes con sesión iniciada · iOS · Android · Escritorio · API REST · Su servidor MCP
02Clientes con sesión iniciada
Para que el asistente pueda fiarse de quién pregunta, un endpoint autenticado de su backend firma un token de corta duración (un JWT ES256) con su id de cliente estable y unos pocos campos seguros para mostrar.
El token vive como máximo 120 segundos, lleva un nonce único y jti, e identifica su espacio de trabajo. La clave privada y su sesión de producto nunca llegan al navegador o al chat.
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 });
});El token dice quién está, no qué puede hacer. Mantenga saldos, dispositivos, ajustes y cada cambio detrás de herramientas que responden para un solo usuario.
03Web y página completa
El SDK pide la identidad a su backend cuando la necesita, funciona con invitados, se actualiza tras el inicio y el cierre de sesión, abre los enlaces externos fuera del marco y no necesita cookies de terceros.
<!-- 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>Cuando un usuario con sesión iniciada abre la dirección de su asistente o su dominio propio, pase el token de inicio de sesión en el fragmento de la URL. La página lo borra antes del intercambio.
// 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
Fija la versión del paquete Swift. Conecta tu backend autenticado y añade el permiso nativo del micrófono con la guía de la plataforma y la app de ejemplo.
Instalación, configuración y demo de 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
Fija la versión de la biblioteca Android. Instálala antes de cargar el chat y sigue la guía sobre el ciclo de vida de Activity, el micrófono y la recuperación del renderizador.
Instalación, configuración y demo de 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.06Escritorio
Los SDKs oficiales para macOS, Windows, Electron, Linux y Tauri cargan el chat alojado en un WebView aislado, responden a las solicitudes de identidad a través de la capa privilegiada del host y abren las URL seguras fuera de la app. Fija la versión del SDK y conecta tu backend autenticado igual que en móvil.
Instalación, configuración y apps de ejemplo para escritorio
// 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),
};07Herramientas MCP
Publique esquemas completos en el descubrimiento. Dé a cada herramienta uno de los tres niveles de acceso, marque los cambios que necesitan una tarjeta de confirmación y vuelva a comprobar la autorización en cada llamada.
Cualquiera que esté chateando puede llamarla. Precios, estado, funciones.
Requiere un usuario con sesión iniciada. Estado del pedido, respuestas sobre la cuenta.
Actúa en nombre del usuario con sesión iniciada mediante un actor token acotado o la propia concesión OAuth del usuario.
Cada cambio se confirma en el servidor. Las herramientas que incluya en confirm_tools se detienen en una tarjeta de confirmación antes de ejecutarse; también lo hace cualquier nombre de herramienta que el asistente no reconozca. Los marcos de widget nunca ejecutan un cambio.
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.El mismo conjunto de protocolos se aplica tanto si la plataforma es el cliente de su servidor como si es el servidor de su cliente MCP.
API REST y webhooks
Una clave de API con alcance de espacio de trabajo autentica tanto los puntos finales REST como el servidor MCP. Enumere y lea conversaciones, publique una respuesta del operador o suscríbase a un webhook en lugar de hacer sondeos.
Cree una clave en Consola → Claves de API, luego envíela como token Bearer.
curl https://busymate.ai/api/v1/conversations \
-H "Authorization: Bearer bmai_sk_..."Referencia completa: la guía de API, webhooks, el documento OpenAPI.
08Traspaso y análisis
Un cliente pide hablar con una persona, una herramienta falla, un reembolso supera tu límite, aparece una palabra clave o cualquier evento que definas.
Una sola bandeja de entrada; asignación a mano, por turnos o a quien tenga menos chats abiertos; avisos en la aplicación, por Web Push, APNs y Telegram personal.
Siga la conversación, entre con el contexto y responda en el mismo chat; el asistente no hace ningún cambio mientras hay una persona activa.
Preguntas repetidas, conocimiento que falta y caminos fallidos, agrupados a partir de conversaciones reales y con enlaces a la evidencia.
09Inicio de sesión del espacio de trabajo
Configure en la Consola las URL de inicio de sesión y de cuenta del espacio de trabajo. Un invitado que elige Iniciar sesión sale del marco, se autentica en su producto y vuelve exactamente a la URL de origen. Su backend entrega entonces un token de inicio de sesión de un solo uso; Busymate AI nunca recibe la contraseña del usuario ni su clave de firma.
Un cambio solo toca los datos del propio usuario con sesión iniciada. Las herramientas de cuenta toman al cliente del actor delegado, vuelven a comprobar la autorización en cada llamada y piden confirmación en los cambios que configures.
10Lista de lanzamiento
¿Listo para configurar su espacio de trabajo?
La página de Integración de la Consola genera el código y los ajustes de cada superficie a partir de sus ajustes publicados.
WebMCP
WebMCP es el borrador del W3C Web Machine Learning Community Group para páginas que enumeran sus propias acciones como herramientas. Tres formas de empezar, desde el cambio más pequeño hasta el control total.
book_appointment, check_order_status, start_return: una herramienta, una tarea.
«el próximo martes a las diez», «Express»: nunca un id interno que el asistente tenga que adivinar.
Ofrezca las herramientas que esta página puede ejecutar; anúlelas cuando la página cambie.
Una reserva, un pago, una devolución: márquelo, y el cliente confirma antes de que se ejecute.
La confirmación queda en manos del cliente. Las herramientas se ejecutan en el propio navegador del visitante, en su origen. Una herramienta que marque como consecuente se detiene en una confirmación que ve el cliente; exposedTo limita qué orígenes pueden ver una herramienta.
El cambio más pequeño: un nombre y una descripción de una línea en un formulario que ya tiene. El navegador lo convierte en una herramienta; el formulario sigue visible y funcionando para todos.
<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>Control total: la API del navegador. Detecte la función disponible —los navegadores sin WebMCP simplemente obtienen la página tal como es hoy—. Chrome 149 y Edge 150 la incluyen como prueba de origen; Firefox y Safari están revisando la propuesta.
// 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"] });Donde el asistente ya está instalado, una sola llamada expone sus herramientas para él y para cualquier otro asistente que visite —mediante la API del navegador donde exista, y a través del propio puente del asistente en cualquier otro caso—.
// 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(),
},
]);La guía de adopción y el mensaje listo para copiar y pegarLa página en lenguaje sencillo para su equipo
MCP de gestión
Herramientas de gestión sobre MCP con OAuth 2.1. El cliente abre un inicio de sesión en navegador en la primera conexión; nada se pega.
https://busymate.ai/mcp| Cliente | Cómo se conecta |
|---|---|
| Claude | Añade el conector desde el directorio de conectores de Claude e inicia sesión: nada que pegar. |
| ChatGPT and Codex | La app está en revisión por OpenAI y todavía no aparece en el directorio. |
| Claude Code (CLI) | Ejecute el comando siguiente: el navegador gestiona el inicio de sesión OAuth. |
| Cursor | Pegue la entrada mcp.json siguiente en la configuración MCP de Cursor. |
| Otros clientes MCP | Proporciónele al cliente el siguiente punto de conexión; el descubrimiento OAuth 2.1 y PKCE se encargan del resto. |
La cuenta con la que inicia sesión decide qué espacios de trabajo puede gestionar el cliente. Cada cambio pide confirmación.
claude mcp add --transport http busymate-ai https://busymate.ai/mcpCursor, Claude Desktop y cualquier cliente que lea un mcp.json aceptan esta entrada; los clientes que admiten una URL remota toman el endpoint de arriba.
{
"mcpServers": {
"busymate-ai": {
"type": "http",
"url": "https://busymate.ai/mcp"
}
}
}Últimos cambios
compilación 1632
Console: one Customer identity control per connector, the token check and the outcome sentence (auto-auth P3b). Connections › Account tools, per MCP connector.
compilación 1631
When a store's connector is updated, Busymate AI re-checks its tools. That check no longer hides public product tools (search, product details, collections) on stores whose shoppers are identified by Busymate AI's signed token. Publishing a store's assistant works again after such an update. Busymate AI now checks, by itself, that a connector your workspace declares as its own actually accepts the signed customer token. A connector set to "your own product" (customer identity Busymate AI) runs that way only after its server has accepted Busymate AI's signed customer token. The platform now runs that live check on its own every 6 hours and after the connector's address changes, and admins can run it on demand and read every connector's resolved mode.
compilación 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.