What Purview is
Purview implements the MCP GUI Bridge Protocol v0.1.0. It sits between your real GUI and an MCP client, serving a projection of your application: a set of windows, each carrying semantic blocks (fields, tables, lists, notices, media…) and actions.
You never build protocol messages. You describe windows and keep them current; the library serves them.
Why "Purview"
A purview is the range of what someone is allowed to see and to decide — as in "within the purview of". That is precisely what a projection is: the agent's purview over your application. What you put in it, the agent can read; the actions you expose, the agent may invoke. Everything else is not hidden behind a permission check — it never reaches the wire at all.
The word also carries view inside it, which is the other half of the story: what the agent holds is a view of your GUI, never the GUI itself. It cannot click your buttons or reach past the projection into your widgets; it can only ask, through an action you defined, for something to happen.
The three ideas
Framework-agnostic core. The purview crate links no GUI toolkit. It reaches your main thread through one closure you register at startup.
Semantic, not pixels. An agent reads structured blocks — labelled fields, typed rows — not a screenshot it has to interpret.
The library owns the protocol. Dynamic tools/list, modal chains, optimistic concurrency, redaction, notifications: all derived from the projection you maintain.
Which crate do I use?
| Crate | Use when |
|---|---|
purview | Always. It is the core: projection, protocol, server. |
purview-gpui | Your UI is gpui. It removes the thread-marshaling boilerplate — see Using purview-gpui. |
Mental model
Two flows, and keeping them separate is the single most important habit:
Read side (GUI → library). Your UI's change events push values into the projection through the typed handles you kept. This is the only place blocks change.
Write side (agent → GUI). Each action becomes an MCP tool. Its handler triggers real business logic or GUI operations; it does not edit the projection directly — the change flows back through the read side.
Why it matters
If a handler wrote to the projection directly, an agent-driven change and a human-driven change would take different paths and eventually disagree. Funnel every mutation through one method — the examples all use a single sync() or bump() — and the projection cannot drift.
The three things you hold
| Handle | From | For |
|---|---|---|
AppHandle<Cx> | build() | Opening windows; reading the snapshot |
Window<Cx> | app.open(title) | Summary, blocks, actions, modals, close |
| Block handles | win.blocks().fields(..) etc. | Updating that block later |
All of them are cheap to clone, Send + Sync, and hold a weak reference to the shared core — so you can store them in GUI components and call them from any thread. Once the core is dropped, their operations become safe no-ops.