New in reflex-enterprise v0.9.4.
Auto MCP
rxe.MCPPlugin publishes your Reflex app over the
Model Context Protocol — no server code, no
tool definitions, no glue. Every event handler the app registers becomes
something an agent can search and apply, and the session's live state becomes
something it can read back.
Where Event Handler API exposes the same
surface as REST + OpenAPI for scripts and HTTP clients, MCPPlugin exposes it
as a streamable-HTTP MCP server for LLM agents and MCP-aware editors. Both
plugins share one implementation, so an agent and a curl script drive exactly
the same handlers with the same auth and the same rate limits.
Installation
The MCP SDK is an optional dependency, so it is only pulled in when you ask for it:
Quickstart
1. Add the plugin to rxconfig.py:
2. Use rxe.App() in your app module:
3. Point an MCP client at the endpoint. With a dev server running, the
server is mounted at /_reflex/mcp:
or, in a client that takes a JSON config:
Every request needs an app-issued bearer token. A client that speaks OAuth 2.1 obtains one by itself when an is configured; otherwise, grab an anonymous session token and send it as a header (see Authentication):
The transport is stateless streamable HTTP with JSON responses, so a single
JSON-RPC POST is a complete call — there is no separate initialize
round-trip to manage.
Endpoints
Change the mount with MCPPlugin(path="/mcp"); the upload endpoint and the
protected-resource metadata follow it.
Tools
search_events returns a results list along with total_available,
total_matched, and returned, so an agent can tell when its query was
capped (max_search_results, default 20; callers may request fewer, never
more).
queue_event takes the name from a search result (the canonical dotted form
foo.bar; the slash form foo/bar and the fully-qualified registry name also
resolve), invokes the handler with payload as keyword arguments, and returns:
Var names come back clean: the framework's internal _rx_state_ field-marker
suffix is stripped from every agent-facing delta and state read, so an agent
sees total_count, not total_count_rx_state_.
The optional query object becomes the request's query parameters, which is
how an agent supplies dynamic route variables —
handlers read them through self.router exactly as they would for a page URL's
query string.
Resources
State and event metadata are exposed as reflex:// resources rather than
tools, so a client can browse them without spending a tool call:
The reflex://state/vars/... resources read the session bound to the caller's
bearer token; the metadata resources are session-independent.
You can also publish your own read-only, parameterized views of session state — see custom MCP resources.
Server instructions
On connect, the server advertises auto-generated instructions — the MCP
analog of the OpenAPI spec's info preamble. They describe the app and where
it is served, the authentication model, the available tools and resources, the
exposed state names, the app's pages (with the handlers their on_load
triggers), and the dynamic route variables. Most agents need nothing beyond
this to start driving the app.
The page listing covers your app's pages only. The auth machinery's own routes
— /login, /callback, /logout, /forbidden, each OIDC provider's popup
pages, and the MCP consent page — are omitted, along with their dynamic route
variables: they are human-interactive sign-in plumbing an agent cannot drive,
so advertising them would only invite it to "visit" a page it can't use.
Override the generated text with MCPPlugin(instructions="..."), and add
contact / license_info / api_version to have them rendered into it:
Dynamic route variables
If any page uses dynamic route segments (e.g. /tickets/[ticket_id]), those
names are listed in the server instructions and can be passed in the
queue_event query object:
The handler reads them through self.router, just as it does for a browser
navigation.
File uploads
A file-upload handler (one taking list[rx.UploadFile] or an
rx.UploadChunkIterator) cannot be invoked inline — its files arrive as a
multipart body. queue_event detects those handlers and, instead of running
them, returns directions for making the upload out of band:
The URL is pre-signed with a single-use ticket bound to the caller's session
and to that exact handler, so the agent POSTs the files with no credential
of its own — the endpoint resolves the ticket and injects the session token
server-side. Non-file handler arguments ride in a __reflex_event_args JSON
form field that must precede the file parts. Tickets expire after
upload_ticket_ttl (default 5 minutes).
The upload endpoint re-checks the allowlist rather than trusting the request: a
POST with no valid ticket is a 401, and one whose Reflex-Event-Handler
doesn't match the handler the ticket was minted for — or names a handler that
isn't public and upload-shaped — is a 403. A raw bearer token is not accepted
there at all, since only the ticket path carries the scope gate and rate limit
applied when the ticket was minted.
Sessions and followup deltas
Each bearer token is bound to its own server-side Reflex session. That session
is real state — chain a few queue_event calls and each one sees the effects
of the last — but it has no browser websocket.
The delta an event produces is returned inline by queue_event, and the full
picture is available from reflex://state/vars/.... Reflex still routes
followup deltas to a session by token, though: chained events,
background-task results, cookie syncs, per-event auth bookkeeping. For an MCP
session there is no socket to push those to, so by default they are dropped
(pending_updates="drop") rather than logged as emissions to a disconnected
client. The session is also pre-seeded as hydrated, so the page
hydrate/on_load chain does not re-run on every tool call.
Set pending_updates="queue" to buffer them instead. A get_pending_updates
tool is then exposed, returning and clearing whatever accumulated since the
last call — the way for an agent to collect a background task's result:
Limiting what is exposed
Only your application's own event handlers are published. Framework and auth
handlers — every OIDC provider (including your own OIDCAuthState
subclasses), the login/logout/callback dispatchers, the page guard, and other
reflex / reflex_enterprise internals — are withheld, so an agent cannot
drive the login flow by queueing its events.
There is deliberately no per-handler "hide from MCP" flag. To restrict what an
agent may do, use the auth= checks described in
Authentication,
which can require an OAuth scope or reject a surface outright.
To publish no action surface at all — only state reading, your own
rxe.mcp.resource methods, and anything you add through configure= — pass
expose_events=False:
Configuration reference
All arguments are keyword-only and optional.
Server
Authentication
Rate limiting
Setting a limit to 0 disables it, which is not recommended in production —
see deploying to production.
Related
- Authentication: anonymous tokens, the OAuth 2.1 flow, consent, app scopes, and surface-aware auth checks.
- Custom MCP resources:
rxe.mcp.resourcefor read-only, parameterized views of session state. - Extending the server: add your own tools, resources, and prompts.
- Deploying to production: TLS, storage, reverse proxies, and rate-limit tuning.
- Event Handler API: the same surface over REST + OpenAPI.