Skip to content

MCP GUI Bridge Protocol

Version 0.1.0 · Status: Proposed · Built on MCP

An application-layer protocol that exposes the UI state and operations of a multi-window graphical application to AI agents via MCP. An agent can read the data of all windows, operate the windows that are currently operable (including modal dialogs), and navigate between windows and views.

Normative conventions

MUST = a mandatory requirement · SHOULD = strongly recommended; deviating requires justification · MAY = optional.

Code identifiers are always kept in their original English form. The // comments in the JSON examples are explanatory only and are not part of the message.

1 · Overview

1.1 Purpose

To establish a standard interface between a GUI application and any MCP agent, enabling the agent to "understand and operate the UI" like a human, while safely collaborating concurrently with the human users who are using that application.

1.2 Roles

RoleFulfilled byResponsibilities
ServerThe GUI application itself (or its embedded component)Exposes resources (read) and tools (write), and pushes notifications
ClientThe agent side (e.g. Claude)Reads data, invokes operations
UserA humanOperates the same UI concurrently with the agent (see §7.1, §10.1)

Interaction model: the user converses with the agent → the agent reads and writes the GUI through this protocol. The user does not invoke this protocol directly.

1.3 Design principles

  • Read globally, write at the top of the stack — resources expose all windows; tools expose only the operations of the currently operable windows (the top of each modal chain).
  • Decouple read and write granularity — the write side stops at the window level (no drilling down into controls); the read side may go as fine as semantic state, but only describes "what state something is in", not "how it can be operated". Reading in detail serves only the decisions of writing.
  • Centralize conclusions, keep topology in one place — operability is computed by the Server into a single authoritative conclusion, operableWindowIds; the topology stores only ownerId, with no redundant derived information.
  • Correctness backed by runtime enforcement — dynamic visibility is a "best-effort" optimization; ultimate correctness is guaranteed by the runtime validation of write operations (see §7).
  • Designed to be understandable by the agent — expose "semantics an LLM can understand" (summary + semantic blocks + human-readable title/label), not a control tree / coordinates / screenshots. The quality with which the server translates the UI into semantics directly determines whether the agent can understand it (see §4.3).

2 · Terminology & conventions

window — a top-level window or a modal dialog. It is untyped and can be opened multiple times; each instance has a unique id. The window is the smallest operable unit; this protocol does not drill down to controls.

The three window states (determined by modal + ownerId):

modalownerIdMeaning
falsenullOrdinary top-level window
truenon-nullWindow-level modal (attached under a parent window)
truenullApp-level modal (belongs to the entire application, has no parent window)

modal chain — the chain that window-level modals form along ownerId (e.g. B → D1 → D2). When needed, the Client MUST reconstruct it itself from windows[].ownerId.

top — the single currently operable window on a modal chain (the tail of the chain).

operable set — when there is no app-level modal, it is { top of each modal chain } ∪ { non-modal top-level windows }; when an app-level modal exists, it collapses to { top of the app-level modal } (see §10.2). The final conclusion is operableWindowIds.

Window A (no modal)               → operable
Window B → modal D1 → modal D2    → only D2 is operable (B, D1 are blocked)
Window C (no modal)               → operable

operable set = { A, D2, C }

3 · Transport & initialization

  • Transport: any MCP transport (local applications SHOULD use stdio).
  • In the initialize result, the Server MUST declare the following capabilities:
json
{ "capabilities": {
    "resources": { "subscribe": true },
    "tools":     { "listChanged": true } } }

The Server SHOULD provide an overview in instructions that explains the purpose of this single resource, that "operations are all window-level, and only the operations of operable windows appear in the list", and that "users may operate concurrently, so write operations should carry expectedVersion". See §8.1 for the full recommended text.

4 · Resources (state)

4.1 The single resource

This protocol has only one resource: app://windows (which can be subscribed). It inlines everything — the topology, the operability conclusion, and the content of each window (summary + blocks). A single read yields a complete snapshot; any change emits only its own updated, and the client re-reads everything. Under local IPC the data volume is not a bottleneck, so it is not split, not paginated, and not delivered incrementally (see §9 for handling very large data).

4.2 app://windows

It simultaneously provides the topology, the operability conclusion, and the content of each window; a single read yields a self-consistent snapshot.

FieldTypeRequiredDescription
operableWindowIdsstring[]YesThe single authoritative conclusion: the ids of the windows operable at this moment. Modal chains and app-level collapse are already accounted for
windowsWindow[]YesAll windows, each inlining its content (see the Window object below); the array order has no semantics (it does not represent stacking / z-order, do not rely on the order)

Window object

FieldTypeRequiredDescription
idstringYesUnique window identifier, never reused (§10.3)
titlestringYesWindow title (human-readable)
modalbooleanYesWhether it is modal
ownerIdstring | nullYesThe parent window id (a window-level modal is attached beneath it); null means there is no parent window, and together with modal it determines the three window states (see §2). Use: the agent can reconstruct the modal chain along each window's ownerId and understand the modal ownership hierarchy (which dialog belongs to which window); operability itself is given directly by operableWindowIds and does not need to be inferred from it
versionmonotonic int64YesThe optimistic-concurrency version number of this window; it increments whenever either blocks or summary changes (see §7.1)
summarystringShouldA one-sentence description of "what this window is currently showing"
blocksBlock[]YesThe window content — a sequence of semantic blocks (see §4.4); use [] for an empty window

The Client MUST treat operableWindowIds as the sole basis for operability.

json
{
  "operableWindowIds": ["w-A"],
  "windows": [
    { "id": "w-A", "title": "Order Editor", "modal": false, "ownerId": null,
      "version": 42, "summary": "Editing order #12345, recipient not yet filled in",
      "blocks": [
        { "type": "fields", "id": "b2", "items": [
            { "label": "Recipient", "value": "" }, { "label": "Amount", "value": "$100.00" } ] },
        { "type": "notice", "id": "b4", "severity": "error", "text": "Recipient cannot be empty" }
      ] }
  ]
}

4.3 Understandability (making it easy for the agent to read)

What this protocol exposes is "semantics an LLM can understand", not a control tree / coordinates / screenshots — this is what fundamentally distinguishes it from traditional GUI automation. Whether the agent can understand the UI depends half on the server's "translation quality". The Server SHOULD:

  • Write a truthful summary: a one-sentence, human-readable overview of "what this window is doing and what its key state is". Don't leave it blank, don't stuff it with machine codes — it is the agent's entry point to understanding the window.
  • Prefer semantic primitives: if something can be expressed with fields/table/notice/list, don't use custom; custom is a fallback, and overusing it leaves the agent facing an opaque payload.
  • Human-readable labels/titles: use words the user can understand for the label of fields, the window title, and a tool's title/description — don't use internal codes (such as btn_47).
  • Make state explicit: state errors/warnings explicitly with notice (carrying severity); don't expect the agent to guess between the lines.

A counterexample: an empty summary + blocks all stuffed into custom + labels that are codes → the agent is left in the dark. Semantic input is the LLM's "native language", far superior to an accessibility tree or a screenshot.

4.4 Semantic blocks (Block)

blocks is a flat array (not nested). Each Block contains at least type (required) and id (should be present, used for reference). blocks is assembled from business-agnostic general-purpose primitives, and does not restrict the kind of window; a Block only describes "what is displayed" and must not carry any operation.

Core primitives (implementations MUST support them):

typePurposeSpecific fields
textParagraph texttext: string
fieldsKey-value field groupitems: { label, value, sensitive? }[]
tableTablecolumns: string[], rows: any[][]
listListitems: any[], ordered?
noticeStatus noticeseverity: info|warn|error|success, text
mediaImage / screenshot / chartmimeType: string, url: string, alt?
customFallback for arbitrary structurespayload: any

text · paragraph text

FieldTypeRequiredDescription
textstringYesThe plain-text content to display.

fields · key-value field group (forms, property panels)

FieldTypeRequiredDescription
itemsarrayYesThe list of field items; the per-item fields are below.
items[].labelstringYesThe field name/label, such as "Recipient" or "Amount".
items[].valueanyYesThe current value of the field; an empty string or null means not filled in.
items[].sensitivebooleanNoMarking it true means the value has been masked (see §4.5).

table · table

FieldTypeRequiredDescription
columnsstring[]YesThe header. Each element is the title of one column, and the array order is the left-to-right order of the columns. Example: ["Product","Quantity"].
rowsany[][]YesThe row data. Each element is a row, and the array within a row corresponds positionally one-to-one with columnsrows[i][j] is the value at row i, column j (columns[j]). Example: [["A",2],["B",1]] together with the columns above means "product A quantity 2, product B quantity 1".

list · list

FieldTypeRequiredDescription
itemsany[]YesThe list items, one value per item (usually a string).
orderedbooleanNotrue = ordered (1. 2. 3.); omitted or false = unordered.

notice · status/notice

FieldTypeRequiredDescription
severityenumYesThe notice level, one of info/warn/error/success, which determines the tone and color.
textstringYesThe notice text, such as "Recipient cannot be empty".

media · image / screenshot / chart (read-side rich media)

FieldTypeRequiredDescription
mimeTypestringYesThe media MIME type, such as image/png.
urlstringYesWhere to fetch it: an https:// / file:// URL, or an inline data:<mime>;base64,… URL.
altstringShouldA text description — the semantic fallback so a non-visual agent still understands what the media shows.

custom · fallback

FieldTypeRequiredDescription
payloadanyYesAn arbitrary structure. Use it when the content cannot be expressed with the primitives above; the agent understands it in combination with the window's summary and context.
  • custom is a safety valve: put content that cannot be categorized here first; when a certain kind appears repeatedly it SHOULD be promoted to a formal primitive.
  • Large data goes through a tool: state holds only the overview/important data; a window's complete or very large data does not go into blocks, and is fetched on demand through that window's tool (see §9).

Candidate primitives (not required in this version): section (partition nesting), progress (progress).

json
{
  "version": 42,
  "summary": "Editing order #12345, 3 fields still to be filled in",
  "blocks": [
    { "type": "text",   "id": "b1", "text": "Editing order #12345" },
    { "type": "fields", "id": "b2", "items": [
        { "label": "Recipient", "value": "" },
        { "label": "Amount",   "value": "$100.00" },
        { "label": "Payment password", "value": "••••••", "sensitive": true } ] },
    { "type": "table",  "id": "b3", "columns": ["Product","Quantity"], "rows": [["A",2],["B",1]] },
    { "type": "notice", "id": "b4", "severity": "error", "text": "Recipient cannot be empty" }
  ]
}

4.5 Sensitive data

Any field or Block value MAY be marked sensitive: true. The Server MUST mask it before exposure (e.g. a password shown as ••••••), so that plaintext passwords/tokens do not enter the Client's context.

5 · Tools (actions)

5.1 Model

A tool is an action actively triggered by the agent — its effect may be a modification (submit) or navigation (focus), and it may also be read-only (e.g. a refresh triggering a reload); the criterion is not "read or write" but "whether it is an active action" (see §5.6).

  • Operation granularity = window. Each operable window exposes a group of independent tools, isolated from one another; identically-named operations of different windows MAY have completely different parameters and semantics.
  • Visible means available: at any moment tools/list MUST contain only the operations invocable at that moment. Two levels of hiding: (1) the window is blocked by a modal → the whole group is hidden; (2) a certain window-level operation is unavailable at that moment → that operation alone is hidden.
  • Any hiding/restoration MUST trigger tools/list_changed.

5.2 Naming

A tool name MUST have the form {winId}__{action}. The name is only a machine identifier; the Server SHOULD provide a human-readable title and description. Only [A-Za-z0-9_-] is allowed; otherwise maintain a stable handle mapping (§10.4).

json
{
  "name": "w-A__edit_field",
  "title": "Edit field · Order Editor (window w-A)",
  "description": "Edit a field in the \"Order Editor\" window",
  "inputSchema": {
    "type": "object",
    "properties": {
      "field": { "type": "string" },
      "value": {},
      "expectedVersion": { "type": "integer", "description": "The state.version it is based on" }
    },
    "required": ["field", "value"]
  }
}

5.3 No reserved operations

The protocol reserves no operation names. Every operation — including common ones like closing a window, bringing it to the front, switching views, or opening a new window — is application-specific and exposed as an ordinary window-level / app-level action. There are no special-cased actions; an implementation defines exactly the actions it wants, each with its own name, parameters, and semantics.

Common conventional actions (ordinary actions, named at the implementation's discretion): close a window w-A__close (closing a modal yields the top back to the previous level), bring a window to the front w-A__focus, switch view w-A__switch_tab, open a new window app__new_report — the last returns the new window id in its return value (see §5.4). A client MAY call a window's focus-style action before operating on it, to keep the human and the machine looking at the same place, when the implementation provides one.

5.4 Return values and failure

GUI operations often trigger an asynchronous timeline. A tool MUST return only after the operation has taken effect, and SHOULD include the final result and any subsequent UI hints. On success it returns the following fields — returning normally already indicates success, and there is no ok boolean flag:

FieldTypeRequiredDescription
messagestringShouldA human-readable description of the result, such as "Submitted; a confirmation dialog popped up"
openedWindowIdsstring[]NoThe ids of new windows opened/popped up by this operation (may be 0, 1, or several); whether they are modal is given by windows[].modal. Omit or use an empty array if none
resultobjectNoThe tool-specific structured business result (such as { balance, currency }); its shape is defined by that tool's outputSchema, and the protocol does not constrain its content

Failure always goes through isError: any failure (blocked by a modal, validation failure, version conflict, window not found, …) is returned via the MCP tool result's isError:true + { code, message, … } (see §7.4); a boolean flag in the return value is not used to express failure.

Direct results go in the return value; broad side effects (other windows change too) rely on resource notifications and are not stuffed into the return value.

How it is carried: both the success fields and the failure error object are returned via MCP's structuredContent (the server declares an outputSchema for the tool); for its decisions the agent reads only structuredContent and does not parse the natural language of content (whether content carries a text mirror is up to the implementation, see §12.3, and even if it does the agent does not rely on it). As for the division of rich data: rich media (such as a window screenshot) is a block of state (the media primitive, on the read side); very large structured data is fetched on demand through that window's tool (§9) — both consistent with "read the overview via a resource, fetch large data via a tool".

5.5 Optimistic concurrency

A write operation SHOULD accept the optional parameter expectedVersion — that is, the current state.version of the window the tool belongs to (the winId in the name). See §7.1 for conflict handling.

5.6 Tool semantic annotations

The Server SHOULD attach MCP's standard annotations to every tool, to help the agent judge "whether to be cautious, and whether a failure can be retried":

annotationMeaningAgent use
readOnlyHintRead-only, changes no stateCan be called freely, with no side-effect concerns
destructiveHintDestructive/irreversible (delete, charge-and-submit)Be cautious; confirmation may be added in the future (see §10.6)
idempotentHintRepeated calls are equivalent to oneCombined with stale_state to judge whether it can be safely retried (see §7.1)
openWorldHintInteracts with the outside worldThe result may be nondeterministic

Focus on correctly marking the two: destructiveHint (dangerous operations) and idempotentHint (retry-safe).

A resource is state, a tool is an action

The distinction is not "read or write" but "whether it is an action actively triggered by the agent". Use a resource for addressable, subscribable static state (which windows exist, what is shown in a window); use a tool for things that happen only when the agent actively triggers them — including read-only actions like refresh or re-fetching — and mark their nature with the annotations above.

6 · Notifications

The Server MUST send a notification only after the state has fully taken effect. The event-to-notification mapping:

EventNotification
Open/close a window, modal popped up/closedapp://windows updated; if the operable set changes → an additional tools/list_changed
Data change within a windowapp://windows updated (that window's version++)
Change in the availability of a window-level operationtools/list_changed

Just two notifications

resources/updated = the content of the single resource app://windows changed — topology / operability / window content, any change goes through it.

tools/list_changed = the set of operable windows, or the available operations of some window, changed.

On receiving either → re-read app://windows.

7 · Consistency & errors

7.1 Read-then-write consistency (optimistic concurrency)

The agent shares the same UI with the user (and other agents) and is a concurrent operating party. To prevent "writes based on stale understanding":

  • Each window carries a version, which increments whenever its blocks/summary changes.
  • When a write operation carries expectedVersion, the Server MUST reject it if the current version is unequal, and return stale_state (containing currentVersion).
  • The Client SHOULD re-read accordingly, re-decide, and then retry. The default conflict arbitration is user-first.

7.2 Runtime enforcement

Dynamic hiding is a "best-effort" optimization; because notifications are asynchronous, the Client MAY still invoke an operation that has just been blocked/invalidated. The Server MUST perform runtime validation on such calls and return a structured error — this is the last line of defense for correctness.

7.3 Field-level validation errors

When rejected due to input-validation failure, the Server MUST return errors per field, with field taking the corresponding tool parameter name:

json
{ "code": "validation_failed", "message": "Submission rejected: 2 fields are invalid",
  "fields": [ { "field": "recipient", "error": "Cannot be empty" },
              { "field": "amount", "error": "Must be greater than 0" } ] }

7.4 Error object

A tool error is returned via the MCP tool result's isError: true, with structure { code, message, ... }.

codeMeaningExtra fields
stale_stateexpectedVersion does not match the current onecurrentVersion
validation_failedInput validation failedfields[]
blocked_by_modalThe target window is blocked by a modal (window-level or app-level)
window_not_foundThe window is already closed / the id does not exist
action_not_availableThat window-level operation is currently unavailable
action_timeoutThe operation timed out without taking effect

8 · Session lifecycle

8.1 Initial handshake

The instructions the Server returns in initialize are the agent's entry point to understanding this protocol. It SHOULD directly adopt the recommended text below (application-specific notes may be appended at the end), so that all agents receive consistent, sufficient guidance:

Recommended instructions text

You are connected to a "multi-window desktop application" — read its UI and operate it on the user's behalf through the interface below.

Reading the UI: the single resource is app://windows, and one read gets everything. operableWindowIds = the windows you can operate at this moment (the sole authority; only operate the ones in this list); each of windows[] contains title/modal/ownerId, plus the content summary (a one-sentence overview) + blocks (semantic UI content: text, fields, tables, notices, media…). Read the summary first to understand the window, then the blocks for details.

Operating: everything is a window-level tool, named like {winId}__{action}; the list contains only the operations of "currently operable windows" — if you can see it, you can call it. If a window exposes a focus-style action, call it before acting so the user sees which one you are working on.

Staying in sync: when you receive an updated for app://windows or a tools-list-changed notification, re-read app://windows before deciding.

Concurrent writes: the user may be operating at the same time — you are not the only operating party. Attach expectedVersion (the window version you read) to write operations; if stale_state is returned, the UI has been changed, so re-read before deciding.

Failure: returned via isError, carrying a code (stale_state/validation_failed/blocked_by_modal…); validation_failed explains what is wrong per field, so correct accordingly and retry.

Large data: a window's state holds only the overview; when you need complete/large data, call the corresponding tool of that window to fetch it (the result is in the returned result).

① initializeread instructions② read app://windowswindows + operability conclusion③ subscribeapp://windows

8.2 Resynchronization after disconnection

The app://windows snapshot is self-contained, so resynchronization does not require event replay — re-reading this single resource is enough to realign.

① Reconnectrecover from disconnection② Re-readapp://windows③ Resubscribeapp://windows④ Compare versionlocate the changed windows

8.3 Notification ordering and atomicity

  • The Server MUST send a notification only after the state has fully taken effect.
  • Everything (topology / operability / window content) lives in the single resource app://windows, and one read yields a self-consistent snapshot.
  • After receiving a notification the agent re-reads app://windows to obtain a consistent view, without having to infer the type of change itself.

9 · Handling large data

Under local IPC, transmitting ordinary data volumes in full within app://windows poses no pressure at all (see §4), so this protocol is not paginated and not incremental.

What to do about truly very large data (such as a tens-of-thousands-of-rows table): do not stuff it into state. state holds only the overview/important data (such as the first few rows + a one-sentence note on the total count); the complete data is fetched on demand through that window's tool — when the agent needs it, it calls an action like {winId}__load_…, and the large data is in the returned result (that tool is marked readOnlyHint, see §5.6).

This way state stays permanently lightweight (full retransmission is no burden), and large data goes through a tool only once, when the agent actively asks for it, and does not enter subscription pushes. In a sentence: read the overview via a resource, pull large data via a tool.

10 · Multi-session & implementation conventions

10.1 Multiple clients / multiple sessions

  • UI state is globally shared — windows are real, globally unique desktop windows; there is no per-Client independent copy.
  • All agents and the user operate on the same set of windows concurrently. Multiple agents simply reuse the §7.1 optimistic concurrency, with no new mechanism added.
  • The Server MUST serialize the execution of operations (one takes effect at a time); notifications MUST be broadcast to all connections; subscription relationships are independent per connection.
  • One GUI process = one Server instance, which can serve multiple Clients simultaneously.

10.2 App-level modals

  • Definition: a modal that blocks the entire application and has no parent window, appearing in the index as modal:true + ownerId:null.
  • Effect: when one exists, operability collapses with priority — operableWindowIds retains only the top of the app modal, and everything else is not operable. They can be nested.
  • The Server is responsible for computing the collapse into operableWindowIds; the protocol does not need any additional field for it.

10.3–10.6 Other conventions

ConventionRequirement
Identifier stabilityWindow id is globally unique, and block.id is unique within a window; both MUST be stable and never reused (monotonically increasing / UUID)
name-safe charactersThe tool name {winId}__{action} allows only [A-Za-z0-9_-]; when winId/action contains illegal characters, a stable handle mapping MUST be maintained
Numeric widthEach window's version MUST be a 64-bit monotonic integer, with no wraparound handling
Explicitly out of scope for this versionPermission gates (operation confirmation/authorization), control-level operations (window-level only)

11 · Conformance

A conformant Server MUST:

  • declare the capabilities listed in §3;
  • provide the single resource app://windows (inlining window content), with fields satisfying §4;
  • make operableWindowIds the sole authoritative conclusion on operability, correctly reflecting window-level / app-level modals;
  • have tools/list contain, at any moment, only the available operations of operable windows, and emit tools/list_changed on change;
  • have tool names follow {winId}__{action} with compliant characters;
  • return stale_state for conflicting writes carrying expectedVersion; perform runtime validation for blocked/invalidated writes;
  • send notifications only after the state has taken effect, mapped per §6;
  • serialize the execution of operations and broadcast notifications to all connections;
  • mask sensitive values before exposing them;
  • ensure id/version satisfy §10.3 / §10.5.

A conformant Client SHOULD:

  • treat operableWindowIds as the sole basis for operability;
  • carry expectedVersion on write operations, and correctly handle the error codes of §7.4;
  • re-read app://windows before deciding after receiving a notification, rather than acting on a single mid-stream notification;
  • call a window's focus-style action before operating on it, when the implementation provides one.

12 · Implementation mapping (onto MCP methods)

This section grounds the preceding data model on the concrete methods of MCP to eliminate implementation ambiguity — only servers implemented accordingly can be mutually compatible.

12.1 Discovering the resource

MCP methodReturns
resources/listLists only one: app://windows (fixed, subscribeable). There is no dynamic addition/removal, so resources/list_changed is not needed

12.2 The return wrapping of resources/read

Each resources/read returns the corresponding JSON as a single application/json text content:

json
{
  "contents": [
    { "uri": "app://windows",
      "mimeType": "application/json",
      "text": "{ ...the app://windows JSON defined in §4.2, inlining all windows... }" }
  ]
}

Just this one resource, this one wrapping (the text holds the complete JSON defined in §4.2).

12.3 The return wrapping of tools/call

  • Success: structuredContent = the ToolResult object of §5.4; isError is omitted or false.
  • Failure: isError:true, and the error object of §7.4 is placed in structuredContent.
  • content: this protocol does not rely on it; a single text SHOULD be placed (repeating the message or the error message) to satisfy MCP's convention that "content provides at least some content", but for its decisions the agent reads only structuredContent.
  • Every tool that returns something MUST declare an outputSchema.

outputSchema example (the protocol's common base fields + the shape of that tool's result):

json
{
  "type": "object",
  "properties": {
    "message": { "type": "string" },
    "openedWindowIds": { "type": "array", "items": { "type": "string" } },
    "result": {
      "type": "object",
      "properties": { "balance": { "type": "number" }, "currency": { "type": "string" } }
    }
  }
}

12.4 winId consistent across all interfaces

winId appears in operableWindowIds, ownerId, openedWindowIds, and the tool name {winId}__{action} — these must be the same value, so that the agent can correlate across fields and tools (given an id → match a tool, look up a window).

  • The external winId MUST be name-safe ([A-Za-z0-9_-]) and consistent across all interfaces.
  • When the internal window id does not satisfy this (contains spaces / Chinese characters, etc.), the server MUST use a stable mapped handle throughout as the external winId — not only mapping it in the tool name; every id field and the tool name all use the same handle.

12.5 Miscellaneous conventions

  • Closing a window → only the content of app://windows changes (that window is removed from windows[]), emitting updated; it does not involve resource addition/removal.
  • Empty collections are legal: operableWindowIds being empty (all blocked by an app-level modal) and tools/list being empty are both normal states.

Appendix A · Full example

Scenario: windows A (Order Editor) and C (Report Details) are open and operable; after A submits, a confirmation modal D1 pops up.

AgentServer · GUI1read app://windows{ operableWindowIds, windows[…inlined content] }2call w-A__submit(expectedVersion=42){ openedWindowIds:[w-D1], message }tools/list_changed · w-A__* → w-D1__*app://windows updated · operable→[w-D1,w-C]3call w-D1__confirmD1 closedtools/list_changed · restoredapp://windows updated · operable→[w-A,w-C]RequestResponseNotification (async broadcast)

Appendix B · Type reference

Resources · state that is read

Windowsapp://windows

FieldTypeNotes
operableWindowIdsstring[]operable windows · the sole authoritative conclusion
windowsWindow[]all windows · inlined content · order has no semantics

Windowwindows[] element

FieldTypeNotes
idstringglobally unique · never reused
titlestringwindow title
modalbooleanwhether it is modal
ownerIdstring | nullparent window; null = top-level / app-level modal
versionint64content version · write consistency
summarystring?one-sentence description
blocksBlock[]inlined content · sequence of semantic blocks

Block — one of seven · all contain type + id?

typeShape
text{ text }
fields{ items: { label, value, sensitive? }[] }
table{ columns: string[], rows: any[][], offset?, total? }
list{ items: any[], ordered?, offset?, total? }
notice{ severity: "info"|"warn"|"error"|"success", text }
media{ mimeType: string, url: string, alt? }
custom{ payload: any }

For table / list: when truncated, total is required; offset defaults to 0.

Tools · actions that are initiated

Call parameters — in addition to each tool's own parameters

FieldTypeNotes
expectedVersionint?the owning window's state.version · optimistic concurrency

ToolResult — success · no ok flag

FieldTypeNotes
messagestring?human-readable result
openedWindowIdsstring[]new windows opened/popped up · may be several
resultobject?tool-specific business result · shape defined by outputSchema

ToolErrorisError = true

FieldTypeNotes
codestringerror code · see below
messagestringhuman-readable
currentVersionint?when code = stale_state
fields{field,error}[]?when code = validation_failed

codestale_state · validation_failed · blocked_by_modal · window_not_found · action_not_available · action_timeout