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
| Role | Fulfilled by | Responsibilities |
|---|---|---|
| Server | The GUI application itself (or its embedded component) | Exposes resources (read) and tools (write), and pushes notifications |
| Client | The agent side (e.g. Claude) | Reads data, invokes operations |
| User | A human | Operates 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 onlyownerId, 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+ semanticblocks+ human-readabletitle/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):
modal | ownerId | Meaning |
|---|---|---|
false | null | Ordinary top-level window |
true | non-null | Window-level modal (attached under a parent window) |
true | null | App-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
initializeresult, the Server MUST declare the following capabilities:
{ "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.
| Field | Type | Required | Description |
|---|---|---|---|
operableWindowIds | string[] | Yes | The single authoritative conclusion: the ids of the windows operable at this moment. Modal chains and app-level collapse are already accounted for |
windows | Window[] | Yes | All 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
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique window identifier, never reused (§10.3) |
title | string | Yes | Window title (human-readable) |
modal | boolean | Yes | Whether it is modal |
ownerId | string | null | Yes | The 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 |
version | monotonic int64 | Yes | The optimistic-concurrency version number of this window; it increments whenever either blocks or summary changes (see §7.1) |
summary | string | Should | A one-sentence description of "what this window is currently showing" |
blocks | Block[] | Yes | The 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.
{
"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 usecustom;customis a fallback, and overusing it leaves the agent facing an opaque payload. - Human-readable labels/titles: use words the user can understand for the
labeloffields, the windowtitle, and a tool'stitle/description— don't use internal codes (such asbtn_47). - Make state explicit: state errors/warnings explicitly with
notice(carryingseverity); 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):
| type | Purpose | Specific fields |
|---|---|---|
text | Paragraph text | text: string |
fields | Key-value field group | items: { label, value, sensitive? }[] |
table | Table | columns: string[], rows: any[][] |
list | List | items: any[], ordered? |
notice | Status notice | severity: info|warn|error|success, text |
media | Image / screenshot / chart | mimeType: string, url: string, alt? |
custom | Fallback for arbitrary structures | payload: any |
text · paragraph text
| Field | Type | Required | Description |
|---|---|---|---|
text | string | Yes | The plain-text content to display. |
fields · key-value field group (forms, property panels)
| Field | Type | Required | Description |
|---|---|---|---|
items | array | Yes | The list of field items; the per-item fields are below. |
items[].label | string | Yes | The field name/label, such as "Recipient" or "Amount". |
items[].value | any | Yes | The current value of the field; an empty string or null means not filled in. |
items[].sensitive | boolean | No | Marking it true means the value has been masked (see §4.5). |
table · table
| Field | Type | Required | Description |
|---|---|---|---|
columns | string[] | Yes | The 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"]. |
rows | any[][] | Yes | The row data. Each element is a row, and the array within a row corresponds positionally one-to-one with columns — rows[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
| Field | Type | Required | Description |
|---|---|---|---|
items | any[] | Yes | The list items, one value per item (usually a string). |
ordered | boolean | No | true = ordered (1. 2. 3.); omitted or false = unordered. |
notice · status/notice
| Field | Type | Required | Description |
|---|---|---|---|
severity | enum | Yes | The notice level, one of info/warn/error/success, which determines the tone and color. |
text | string | Yes | The notice text, such as "Recipient cannot be empty". |
media · image / screenshot / chart (read-side rich media)
| Field | Type | Required | Description |
|---|---|---|---|
mimeType | string | Yes | The media MIME type, such as image/png. |
url | string | Yes | Where to fetch it: an https:// / file:// URL, or an inline data:<mime>;base64,… URL. |
alt | string | Should | A text description — the semantic fallback so a non-visual agent still understands what the media shows. |
custom · fallback
| Field | Type | Required | Description |
|---|---|---|---|
payload | any | Yes | An 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. |
customis 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:
stateholds only the overview/important data; a window's complete or very large data does not go intoblocks, and is fetched on demand through that window's tool (see §9).
Candidate primitives (not required in this version): section (partition nesting), progress (progress).
{
"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/listMUST 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).
{
"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:
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Should | A human-readable description of the result, such as "Submitted; a confirmation dialog popped up" |
openedWindowIds | string[] | No | The 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 |
result | object | No | The 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":
| annotation | Meaning | Agent use |
|---|---|---|
readOnlyHint | Read-only, changes no state | Can be called freely, with no side-effect concerns |
destructiveHint | Destructive/irreversible (delete, charge-and-submit) | Be cautious; confirmation may be added in the future (see §10.6) |
idempotentHint | Repeated calls are equivalent to one | Combined with stale_state to judge whether it can be safely retried (see §7.1) |
openWorldHint | Interacts with the outside world | The 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:
| Event | Notification |
|---|---|
| Open/close a window, modal popped up/closed | app://windows updated; if the operable set changes → an additional tools/list_changed |
| Data change within a window | app://windows updated (that window's version++) |
| Change in the availability of a window-level operation | tools/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 itsblocks/summarychanges. - When a write operation carries
expectedVersion, the Server MUST reject it if the currentversionis unequal, and returnstale_state(containingcurrentVersion). - 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:
{ "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, ... }.
| code | Meaning | Extra fields |
|---|---|---|
stale_state | expectedVersion does not match the current one | currentVersion |
validation_failed | Input validation failed | fields[] |
blocked_by_modal | The target window is blocked by a modal (window-level or app-level) | — |
window_not_found | The window is already closed / the id does not exist | — |
action_not_available | That window-level operation is currently unavailable | — |
action_timeout | The 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).
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.
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://windowsto 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 —
operableWindowIdsretains 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
| Convention | Requirement |
|---|---|
| Identifier stability | Window id is globally unique, and block.id is unique within a window; both MUST be stable and never reused (monotonically increasing / UUID) |
| name-safe characters | The tool name {winId}__{action} allows only [A-Za-z0-9_-]; when winId/action contains illegal characters, a stable handle mapping MUST be maintained |
| Numeric width | Each window's version MUST be a 64-bit monotonic integer, with no wraparound handling |
| Explicitly out of scope for this version | Permission 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
operableWindowIdsthe sole authoritative conclusion on operability, correctly reflecting window-level / app-level modals; - have
tools/listcontain, at any moment, only the available operations of operable windows, and emittools/list_changedon change; - have tool names follow
{winId}__{action}with compliant characters; - return
stale_statefor conflicting writes carryingexpectedVersion; 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
sensitivevalues before exposing them; - ensure
id/versionsatisfy §10.3 / §10.5.
A conformant Client SHOULD:
- treat
operableWindowIdsas the sole basis for operability; - carry
expectedVersionon write operations, and correctly handle the error codes of §7.4; - re-read
app://windowsbefore 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 method | Returns |
|---|---|
resources/list | Lists 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:
{
"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;isErroris omitted or false. - Failure:
isError:true, and the error object of §7.4 is placed instructuredContent. content: this protocol does not rely on it; a single text SHOULD be placed (repeating themessageor the error message) to satisfy MCP's convention that "content provides at least some content", but for its decisions the agent reads onlystructuredContent.- Every tool that returns something MUST declare an
outputSchema.
outputSchema example (the protocol's common base fields + the shape of that tool's result):
{
"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
winIdMUST 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://windowschanges (that window is removed fromwindows[]), emittingupdated; it does not involve resource addition/removal. - Empty collections are legal:
operableWindowIdsbeing empty (all blocked by an app-level modal) andtools/listbeing 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.
Appendix B · Type reference
Resources · state that is read
Windows — app://windows
| Field | Type | Notes |
|---|---|---|
operableWindowIds | string[] | operable windows · the sole authoritative conclusion |
windows | Window[] | all windows · inlined content · order has no semantics |
Window — windows[] element
| Field | Type | Notes |
|---|---|---|
id | string | globally unique · never reused |
title | string | window title |
modal | boolean | whether it is modal |
ownerId | string | null | parent window; null = top-level / app-level modal |
version | int64 | content version · write consistency |
summary | string? | one-sentence description |
blocks | Block[] | inlined content · sequence of semantic blocks |
Block — one of seven · all contain type + id?
| type | Shape |
|---|---|
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
| Field | Type | Notes |
|---|---|---|
expectedVersion | int? | the owning window's state.version · optimistic concurrency |
ToolResult — success · no ok flag
| Field | Type | Notes |
|---|---|---|
message | string? | human-readable result |
openedWindowIds | string[] | new windows opened/popped up · may be several |
result | object? | tool-specific business result · shape defined by outputSchema |
ToolError — isError = true
| Field | Type | Notes |
|---|---|---|
code | string | error code · see below |
message | string | human-readable |
currentVersion | int? | when code = stale_state |
fields | {field,error}[]? | when code = validation_failed |
code ∈ stale_state · validation_failed · blocked_by_modal · window_not_found · action_not_available · action_timeout