@m8tes/react
v0.1.0-alpha.13
Published
Embed m8tes AI agents in your React app — headless client + hooks + a drop-in <MateChat> with a locked-down server proxy.
Maintainers
Readme
@m8tes/react
Alpha. Install with
npm i @m8tes/react. Component and hook APIs may change before 1.0; the streaming wire protocol itself is versioned separately (m8tes.stream.v2) and stays semver-stable. Found a rough edge? Tell us.
Embed m8tes AI agents in your React app: a headless client + hooks and a drop-in
<MateChat> that streams a live agent run — text, tool calls, and inline human
approvals — themed to your app.
Full guide: m8tes.ai/docs/embed-a-ui. Deeper
references ship inside this package (node_modules/@m8tes/react/docs/) and are readable
without installing:
customizing.md (theme tokens,
slots, dark mode) and
auth-adapters.md
(Clerk, Auth.js, Supabase, custom JWT, Express).
Architecture
The secret m8_ key lives only on your server. The browser talks to a thin proxy
you mount; the proxy injects the end-user user_id and pipes the SSE stream.
- Client entry
@m8tes/react(browser-safe):M8tesProvider,useMate,<MateChat>, the SSE normalizer, and the typed V2 client. Never touches the key. - Server entry
@m8tes/react/server(server-only):createM8tesHandler— the locked-down proxy. Mount it at/api/m8tes/[...path].
Quick start (Next.js App Router)
Install with npm i @m8tes/react in your React web app (React 18 or newer), then add two files.
1. The proxy route — app/api/m8tes/[...path]/route.ts:
import { auth } from "@clerk/nextjs/server"; // or use your existing server auth
import { createM8tesHandler } from "@m8tes/react/server";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
export const maxDuration = 300;
export const { GET, POST } = createM8tesHandler({
apiKey: process.env.M8TES_API_KEY!,
// map the request to YOUR end-user id (Clerk shown; Auth.js/Supabase/custom also work)
resolveUserId: async () => (await auth()).userId,
// recommended: pin embed runs to ONE mate so the browser can't target others
agentId: process.env.M8TES_AGENT_ID,
});2. The UI — any client component:
"use client";
import { M8tesProvider, MateChat } from "@m8tes/react";
import "@m8tes/react/styles.css";
export function Assistant() {
return (
<M8tesProvider>
<MateChat
style={{ height: "min(640px, 80dvh)" }}
greeting="What can I help you with?"
/>
</M8tesProvider>
);
}3. Set M8TES_API_KEY in .env.local on your server, and optionally
M8TES_AGENT_ID to pin your configured agent. Sign in through your app's auth and
send a message. The provider defaults to /api/m8tes; use its endpoint prop if
you mount the proxy elsewhere. Give the panel a height so the thread can scroll. The first message can take up to a minute while the agent's sandbox starts.
When a run fails, the handler logs the original error ([m8tes] upstream …) to your
server log; your end-user sees a neutral message.
No Tailwind installation or CSS reset is required.
The Clerk import assumes Clerk is already configured. For Auth.js, Supabase, or Express, use the complete auth adapters. Missing a signed-in user returns 401 by design.
Working in this repository? From packages/react/examples/next-chat, run
npm run setup, copy
.env.local.example to .env.local, then npm run dev.
Chat widget (launcher + panel)
The familiar support-bubble layout: a round launcher opens a floating panel (full screen on phones) with a home view, quick links, starter prompts and the chat.
Use the same server proxy as above, then add this component to your layout:
"use client";
import { M8tesProvider, MateWidget } from "@m8tes/react";
import "@m8tes/react/styles.css";
export function AssistantWidget() {
return (
<M8tesProvider>
<MateWidget
title="Ask Acme"
heading="How can we help?"
links={[{ label: "Read the docs", href: "/docs" }]}
starters={[{ label: "Set up billing", message: "How do I set up billing?" }]}
/>
</M8tesProvider>
);
}Closing the widget or returning home keeps the conversation, live stream, and
unsent draft. Reopen and choose Continue conversation. Starter prompts appear
before a conversation is opened. Unmount it (or change its React key) to reset.
Pass runId to restore a conversation and onRunIdChange to save new run IDs.
loading / error cover your server's session setup; onOpenChange and onHome
let you refresh that state. To send a first message from your own UI, use
<MateChat initialMessage="…" />.
Save and restore a conversation
Both components accept onRunIdChange. Save the id in your app's state or backend,
then pass it as runId when reopening the thread:
<MateChat runId={savedRunId} onRunIdChange={saveRunId} />Scope saved IDs to the signed-in user. The proxy checks ownership on every request. Use your backend to list a user's threads; the browser proxy does not expose run listing. A completed run whose stream is unavailable restores its final output; this fallback is not a full historical transcript.
Connection recovery
Temporary stream drops reconnect automatically. If those attempts are exhausted, Retry rejoins the existing run without submitting another message — the server may still be executing it, so resending the prompt would run it twice. A failed initial request, where no run was created, offers the same button and re-sends. Pending approvals and questions keep waiting for the user's decision; a temporary submission failure can be retried.
Headless (bring your own UI)
const { messages, status, pendingApproval, canSend, isDisconnected, send, approve, retry } = useMate({ agentId });Use canSend to enable your composer. When automatic reconnects run out the hook
sets isDisconnected, puts the reason in connectionError, and keeps sending
disabled until the run is reconciled — local status reads incomplete, but the
server may still be running. retry() (or resume()) rejoins that same run
without resending its prompt. An API refusal during a rejoin stays visible and can
be retried once access is restored.
Theming
Styled to match the m8tes app out of the box (warm-white surface, onyx agent avatar,
markdown replies, calm human-in-the-loop gates). It's chameleon: every --m8-*
token maps to the matching shadcn / Tailwind-v4 token (--background, --primary,
--border, --ring, --radius, --accent-blue, …) via var(--token, <m8tes fallback>),
so dropped into a shadcn app it automatically adopts that app's theme — no mapping
step. With no theme present it falls back to the m8tes palette. Override --m8-* on .m8tes-chat (or .m8tes-widget) in CSS loaded after the package stylesheet to fine-tune. (Assumes full-color tokens, i.e. shadcn on Tailwind v4 /
OKLCH; older HSL-channel themes need the Tailwind v3 token bridge.)
Platform parity
<MateChat> targets the m8tes platform run page: what a run looks like there is
what it looks like embedded. Current status:
Supported — streamed markdown replies with speaker grouping, readable long responses, live tool rows with verb labels, TodoWrite plan checklists, computer-use screenshots (inline + lightbox), approval gates (Allow / Always allow / Deny), AskUserQuestion cards (stepper, multi-select, free-text), plan approval (Approve / Revise), run file outputs with proxied downloads, sandbox status pills, notices/warnings, cancelled/failed states with retry, reconnect with exact-once replay, per-end-user limit cards.
Thinking and settled tool-history rows stay off the conversation, matching the Platform.
Not yet (tracked, on the beta path) — attachment upload through the proxy, the computer-use live desktop view, a shareable read-only run view.
Intentionally out of scope — permission-mode/model/tools pickers: the proxy strips execution-policy fields by design so the browser can never escalate what an embedded end-user may run. Configure policy on the Mate or the server proxy.
Security
The proxy keeps the key server-side, forwards only run-scoped routes, isolates end-users, and blocks policy escalation — but rate limiting, spend caps, and connection scoping are yours. Read the security model before going to production with real end-users.
Own the code
Prefer the component source in your repo, shadcn-style? Copy it and edit freely — the headless client stays a dependency:
npx shadcn@latest add https://www.m8tes.ai/r/mate-chat.jsonContributing
Bug reports and feature requests are welcome — open an issue; we review weekly. We don't currently accept external pull requests: this package is developed in our internal monorepo and synced out. If something blocks you, an issue (or [email protected]) is the fastest path to a fix.
License
MIT — see LICENSE. The m8tes name and logo are trademarks of m8tes; the MIT license does not grant trademark rights.
