@auth0/agent-components
Allows agents to render and interact with Auth0 Forms as MCP Apps, enabling sign-up, consent, profile, and payment flows, with session-aware forms and completion status reporting.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@auth0/agent-componentsopen the sign-up form for new users"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
@auth0/agent-components — Auth0 Universal Components for Agents
Turn an Auth0 Form into an MCP App — an interactive UI a Model Context Protocol server serves to an MCP client, rendered in a sandboxed iframe. This is Auth0 Universal Components for Agents: any Auth0 Form becomes a drop-in, agent-invokable UI, no Auth0 Action required.
Status: proof of concept. Works end-to-end against the official MCP Apps capability (SEP-1865), the MCP Inspector, and CopilotKit (see
examples/copilotkit-poc).
Why
Auth0 Forms normally render only inside an Auth0 Action during Universal Login. The dx-flows-sdk decoupled that: a form can be embedded via the browser bundle outside a login redirect. MCP Apps let a server hand a client interactive HTML to render in a sandboxed iframe. Put them together and an agent can surface a real, fully-functional Auth0 Form — sign-up, consent, profile, payment — as one of its tools.
How it works
MCP host (e.g. MCP Inspector, CopilotKit)
└─ sandboxed iframe ← our ui:// HTML resource (text/html;profile=mcp-app)
├─ <script src="https://<tenant>/forms/sdk/forms.js">
├─ Auth0Forms.embed(formId, "#root", { fields: { session_token } })
│ └─ form submits to its OWN Auth0 backend (/forms/api/...) ← we never see the data
└─ bridge: on af-submitForm-success → app.updateModelContext({ status: "completed" })
→ app.requestTeardown() (close the app view)
on af-redirect → app.openLink(url) (never navigates the iframe)The form owns its data. The Auth0 Form submits natively to its Auth0 backend.
The MCP layer never reads or transmits field values — it reports only a
completion status (completed / cancelled / errored, plus an optional
redirect target) so the agent knows the user finished.
Packages
Package | What it is |
| |
| |
Runnable MCP server POC; drive it from the MCP Inspector. Includes | |
CopilotKit as the MCP client — real Auth0 login ( |
Usage
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerAuth0Forms } from "@auth0/agent-components";
const server = new McpServer({ name: "my-server", version: "1.0.0" });
await registerAuth0Forms(
server,
[
{
formId: "your-form-id",
tenantOrigin: "https://your-tenant.us.auth0.com",
// inputMode: "prefill", // expose form fields as optional agent inputs
// ui: { csp: { frameDomains: ["https://js.stripe.com"] } }, // for payment/social steps
onComplete: (r) => console.log(r.formId, r.status), // status only — no field data
},
],
{ assumeUiSupport: true }, // register before connect; see "Registration timing"
);
// ...connect your transport (StreamableHTTP / stdio / SSE)Each form registers:
a tool
open_form_<slug>whose_meta.ui.resourceUripoints at …a
ui://agent-components/<formId>resource returning the form-app HTML + a CSP block.
CSP
The host renders the resource under a strict default policy (default-src 'none').
@auth0/agent-components automatically allowlists your tenantOrigin in both:
csp.resourceDomains— soforms.js, styles, fonts, images load, andcsp.connectDomains— so the bundle'sfetchto/forms/api/...works.
If a form step nests another origin's iframe (Stripe, a social provider, a captcha), declare it:
ui: { csp: { frameDomains: ["https://js.stripe.com", "https://hooks.stripe.com"] } }Watch the browser console the first time you render a new form: any CSP violation tells you exactly which origin to add.
Session-backed forms (flow/router forms)
Forms with a FLOW/ROUTER node need an authenticated session — otherwise the
step after the router fails with ERR_INVALID_FORM_SESSION. The mechanism
(confirmed with the Forms team, verified end-to-end):
Declare a hidden field on the form (e.g.
session_token).The form's flow reads it — e.g. an
Update Useraction withuser_id: {{fields.session_token}}.The MCP server, acting as an OAuth resource server, verifies the caller's Auth0 token, resolves the user's
sub, mints a short-lived trusted JWT for it (signed with a shared secret the Forms backend trusts), and injects that into the hidden field per request.
Mark the form session-aware:
{
formId: "ap_...",
session: { field: "session_token" }, // must match the form's hidden field
}…and initialize the client with the server-scoped trust config + a resolver that
reads the verified identity (verifier + subFromExtra come from the /auth
subpath, backed by @auth0/auth0-api-js):
import { createAgentComponents } from "@auth0/agent-components";
import { subFromExtra } from "@auth0/agent-components/auth";
const agentComponents = createAgentComponents({
tenantOrigin: "https://your-tenant.auth0.com",
assumeUiSupport: true,
sessionTrust: { secret: process.env.FORMS_TRUST_SECRET! }, // shared with the Forms backend
resolveUserSub: subFromExtra, // returns the caller's `sub`, or undefined
});
await agentComponents.register(server, forms);The POC server (examples/poc-server) wires the full flow: createAuth0Verifier
(→ @auth0/auth0-api-js), a ProtectedResourceMetadataBuilder metadata endpoint,
and requireBearerAuth — so a 401 + WWW-Authenticate challenge lets the client
run the Auth0 OAuth flow. The token/sub/minted-JWT never enter MCP model context —
the minted token rides embed().fields to the Auth0 Forms backend only.
Note: a completed Auth0 Forms journey is single-use today (re-submitting a spent journey returns
ERR_INVALID_FORM_SESSION). Universal Portals EPIC 7 adds re-completable journeys. The MCP App completes once and reports status, so a fresh tool invocation gets a fresh journey — re-submission isn't part of the flow.
Registration timing
The MCP SDK forbids adding a capability after server.connect(transport).
Two supported patterns:
Register before connect (simplest) with
assumeUiSupport: true. Correct for UI-capable clients (MCP Inspector, Claude). This is what the POC does.Dynamic per-client gating (
assumeUiSupport: "auto", the default): callregisterAuth0Formsfromserver.server.oninitializedso the client's capabilities are known — and construct the server with{ capabilities: { tools: {}, resources: {} } }, or register one tool + one resource before connecting, so the post-connect registration doesn't try to add a new capability.
Develop
npm install
npm run build # builds all packages (agent-components builds its bridge first)
npm test # unit tests (form→tool, ui-template CSP, bridge status-only contract)License
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
An AI concierge that turns static forms into adaptive AI conversations. From any MCP client.
Create and wire up contact forms from your coding agent. Forms, snippets, and submissions.
Phone, SMS & email for AI agents — one remote MCP endpoint, OAuth login, zero install.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/mustafadeel/universal-components-agents'
If you have feedback or need assistance with the MCP directory API, please join our Discord server