MCP Apps Stateless Design Pattern¶
MCP Apps lets tools return interactive UIs that render inside the AI conversation. The pattern is powerful, but host implementations differ. One rule makes the app work everywhere: design it stateless.
Why stateless¶
Two host behaviors break stateful apps:
- Metadata stripping. Some clients do not pass the full tool result
metadata to the app layer. If your app depends on data carried in
_meta, it silently breaks on those hosts. - No replay after refresh. When the user refreshes the app view, the host does not replay the original tool call. A widget that waits for a second round trip gets stuck loading forever.
A stateless app never depends on either behavior. It renders completely from the data already present in the tool result.
The pattern¶
- Return everything the UI needs in the tool result itself. Values, labels, deltas, and source references live in the result payload, not in a follow-up call.
- Render from the result. The app receives the result and renders. No state initialization call, no re-fetch, no host round trip.
- Treat refresh as re-render. A refresh just re-renders from the same result data. It cannot hang because there is nothing to wait for.
- Keep navigation internal. Drill-downs filter the data already in the result. Do not call back to the server for sub-views.
Example flow¶
User asks a business question.
- The tool executes and returns the answer: metrics, deltas, sources, and a reconciliation summary, all in one structured result.
- The app view renders the dashboard from that result.
- The user clicks a metric card. The app expands the detail from the same result payload. No server call.
- The user refreshes. The host re-renders the view from the last result. Nothing to replay, nothing to wait for.
Why this matters for business answers¶
For business intelligence the pattern is not just a compatibility trick. A stateless, result-driven app makes every answer reproducible. The rendered dashboard is a direct function of the verified tool result, so the same question always renders the same answer, on any host, after any refresh.