For AI agents: the complete documentation index is at llms.txt. Markdown versions are available by appending .md or sending Accept: text/markdown.
Reflex Logo
Docs Logo
Enterprise

/

Mcp

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

PathPurpose
POST /_reflex/mcpThe MCP server (streamable HTTP). Tools, resources, and prompts are served from this one path.
POST /_reflex/auth/tokenAnonymous session token grant, rate limited per client IP. Shared with EventHandlerAPIPlugin.
POST /_reflex/mcp/uploadTicket-authenticated file-upload endpoint used by the upload directions queue_event returns.
/agent-consentThe consent page shown during the OAuth flow (only with an AuthPlugin; route configurable).
/.well-known/oauth-protected-resource/_reflex/mcp, /.well-known/oauth-authorization-server, /authorize, /token, /register-oidc-client, /revokeThe OAuth 2.1 authorization server, when it is enabled. The four endpoint paths are configurable. See Authentication.

Change the mount with MCPPlugin(path="/mcp"); the upload endpoint and the protected-resource metadata follow it.

Tools

ToolWhat it does
search_events(query, limit)Free-text search over the app's event handlers. Returns each match's name, summary/description, and the JSON schema of its payload parameters. An empty query lists everything. This is the server-side-filtered entry point — the one to use on a large app.
queue_event(event_name, payload, query)Apply an event to the caller's session and return the resulting state delta.
get_pending_updates()Only registered with pending_updates="queue" — returns and clears followup deltas buffered for the session.

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:

ResourceContents
reflex://stateThe exposed state names (fully-qualified; the root is reflex___state____state).
reflex://eventEvery exposed handler: name, state, summary. The flat-enumeration alternative to search_events.
reflex://event/<event_name>One handler's full description, including its payload schema.
reflex://state/events/<state_name>The handlers defined on one state.
reflex://state/vars/<state_name>The session's live state rooted at a state — the resolved .dict() of frontend + computed var values. Read reflex___state____state for the whole app.
reflex://state/vars/<state_name>/<var_name>A single var's live value. A computed var is marked dirty and recomputed rather than served from the cache.

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

OptionDefaultPurpose
path/_reflex/mcpWhere the MCP endpoint is mounted.
server_name"<app_name> MCP"Name advertised by the server.
instructionsauto-generatedOverride the generated server instructions.
api_version"1.0.0"Version reported in the instructions.
contact / license_infoNoneContact / license objects rendered into the instructions.
max_search_results20Cap on search_events results.
expose_eventsTrueWhether the event surface is published at all.
configureNoneHook receiving the FastMCP server before it is mounted — see Extending the server.
pending_updates"drop""drop" or "queue" — how followup deltas for the browserless session are handled.

Authentication

OptionDefaultPurpose
authNoneNone = OAuth on exactly when an AuthPlugin is configured; True forces it (and fails fast without one); False keeps the endpoint anonymous-only.
anonymous_sessionsTrueServe the anonymous token endpoint and accept its tokens at the MCP endpoint — alongside OAuth, not instead of it. Always effectively on when OAuth is off.
anonymous_session_ttl3600Anonymous token lifetime in seconds (no refresh — a new token is a new session).
app_scopesNone{name: description} scopes offered as individually grantable checkboxes on the consent screen.
required_scopesNoneScopes a token must carry to reach the endpoint at all.
valid_scopes / default_scopesNoneScopes dynamic registrations may request / are granted when they request none.
issuer_urlconfig deploy_url / api_urlPublic origin serving the OAuth endpoints. Must be https in production.
consent_path/agent-consentFrontend route of the consent page.
consent_pagedefault pageCustom consent page builder, or a "module.function" import path.
access_token_ttl / refresh_token_ttl3600 / 30 daysIssued-token lifetimes (refresh rotates on use).
client_registration_ttl90 daysLifetime of a dynamic client registration; None keeps them forever.
pending_authorization_ttl / authorization_code_ttl600 / 60How long a started authorization may wait for login + consent, and the code lifetime (codes are single-use regardless).
upload_ticket_ttl300Lifetime of a pre-signed upload ticket.
enable_dynamic_client_registrationTrueServe RFC 7591 dynamic client registration.
enable_token_revocationTrueServe RFC 7009 token revocation.
registration_path/register-oidc-clientOrigin-root path of the registration endpoint — deliberately not the SDK's bare /register, which would shadow an app's own sign-up page.
authorization_path / token_path / revocation_path/authorize / /token / /revokeOrigin-root paths of the other OAuth endpoints. Override any that collide with your own routes; each must be distinct and outside the MCP mount.
auth_storeautoToken/consent storage. Defaults to Redis when the app's state manager is Redis, otherwise in-process.

Rate limiting

OptionDefaultPurpose
call_rate_limit / call_rate_window60 / 60.0Per-session-token cap on MCP calls. Per-handler override via rxe.event(rate_limit=...).
token_rate_limit / token_rate_window10 / 60.0Per-client-IP cap on anonymous token grants.
registration_rate_limit / registration_rate_window10 / 60.0Per-client-IP cap on dynamic client registration.
registration_trusted_proxy_hops0Number of trusted reverse proxies, for resolving the real client IP of the per-IP limiters.

Setting a limit to 0 disables it, which is not recommended in production — see deploying to production.

Built with Reflex