Skip to content

feat: support ui/update-model-context for MCP Apps#8044

Open
aharvard wants to merge 5 commits intomainfrom
feat/mcp-app-update-model-context
Open

feat: support ui/update-model-context for MCP Apps#8044
aharvard wants to merge 5 commits intomainfrom
feat/mcp-app-update-model-context

Conversation

@aharvard
Copy link
Collaborator

@aharvard aharvard commented Mar 20, 2026

Summary

Implements ui/update-model-context from the MCP Apps specification, allowing MCP App views to push context updates to the host agent without triggering immediate action.

Closes #6472

Demo

goose-update-model-context.mov

How it works

When an MCP App sends ui/update-model-context:

  1. McpAppRenderer (desktop) catches the request and POSTs to /agent/update_model_context
  2. Agent stores the context in an in-memory HashMap, keyed by {extension}__{resourceUri} (last-write-wins)
  3. On the next agent turn, stored contexts are formatted as XML blocks and injected alongside MOIM as a synthetic user message
MCP App (iframe)
  → postMessage("ui/update-model-context", { content, structuredContent })
    → McpAppRenderer → POST /agent/update_model_context
      → Agent.mcp_app_contexts HashMap (in-memory, ephemeral)
        → Next LLM turn: collect → format as XML → inject with MOIM

The model sees context like:

<mcp-app-context source="mcp-app-bench__inspect-model-context">
The user is interacting with a counter demo. Current counter value: 5
{"counter": 5}
</mcp-app-context>

Design decisions

  • Last-write-wins: Each app resource gets a single slot. Subsequent updates overwrite the previous one — no accumulation.
  • Ephemeral: Context lives only in memory. Not persisted to session storage. Clears on new session.
  • Non-triggering: Unlike ui/message, updating context does not kick off an agent turn. It passively waits for the next natural turn.
  • MOIM integration: App context is combined with existing MOIM (working dir, tool usage, etc.) into a single synthetic user message.

Changes

File What
crates/goose/src/agents/agent.rs McpAppContext struct, mcp_app_contexts HashMap, update_mcp_app_context(), collect_mcp_app_contexts(), 4 unit tests
crates/goose/src/agents/moim.rs inject_moim() accepts mcp_app_context param, combines with MOIM text, 2 new tests
crates/goose/src/agents/mod.rs Re-exports McpAppContext
crates/goose-server/src/routes/agent.rs POST /agent/update_model_context endpoint
crates/goose-server/src/openapi.rs Registers endpoint + schema
ui/desktop/src/components/McpApps/McpAppRenderer.tsx Handles ui/update-model-context from MCP Apps
ui/desktop/openapi.json, api/* Auto-generated

Testing

  • 7 unit tests covering: empty state, store + collect, overwrite, structured content, MOIM + app context injection, app context only injection
  • Manual testing with mcp-app-bench Model Context Inspector (counter demo + manual send)

Implement the ui/update-model-context method from the MCP Apps
specification, allowing MCP App views to push context updates to the
host agent without triggering immediate action.

Context updates are stored in-memory (last-write-wins per app key) and
injected alongside MOIM as a synthetic user message on the next agent
turn, giving the model awareness of app state.

- Add McpAppContext store on Agent with update/collect methods
- Add /agent/update_model_context server endpoint
- Integrate with MOIM injection in moim.rs
- Handle ui/update-model-context in McpAppRenderer
- Add 7 unit tests covering store, overwrite, collect, and injection

Signed-off-by: Andrew Harvard <aharvard@squareup.com>
Co-authored-by: Goose <opensource@block.xyz>
Ai-assisted: true
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e5b1c8a2bc

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e5b1c8a2bc

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Escape <, >, &, and " in app-provided text and keys before wrapping
them in <mcp-app-context> XML blocks. Prevents malformed markup from
benign payloads (e.g. code snippets) and blocks malicious apps from
closing the tag to spoof extra context sections.

Adds a test covering special character escaping.

Signed-off-by: Andrew Harvard <aharvard@squareup.com>
Co-authored-by: Goose <opensource@block.xyz>
Ai-assisted: true
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8d191713b6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@aharvard aharvard changed the title feat: support ui/update-model-context for MCP Apps feat: support ui/update-model-context for MCP Apps Mar 20, 2026
Content blocks without a text field (e.g. image, resource) were
silently dropped during context collection. Now falls back to JSON
serialization so the model sees all block types.

Signed-off-by: Andrew Harvard <aharvard@squareup.com>
Co-authored-by: Goose <opensource@block.xyz>
Ai-assisted: true
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9fa95fbda7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@aharvard
Copy link
Collaborator Author

aharvard commented Mar 20, 2026

Regarding the per-view key comment — this is a known design decision. The current key scheme ({extension}__{resourceUri}) intentionally uses last-write-wins per resource. This is the correct default for the common case (one instance per resource).

For the multi-instance question (same resource rendered multiple times in a session), we've opened an issue upstream to get spec guidance: modelcontextprotocol/ext-apps#558

Instance-scoped keys can be added later without breaking the existing API if multi-instance support is needed.

When serializing non-text content blocks (e.g. images), remove the
`data` field before JSON-stringifying. This prevents base64 payloads
from bloating the synthetic MOIM message with thousands of wasted
tokens. The block metadata (type, mimeType, etc.) is preserved so the
model knows a non-text block exists without seeing the raw data.

Signed-off-by: Andrew Harvard <aharvard@squareup.com>
Co-authored-by: Goose <opensource@block.xyz>
Ai-assisted: true
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8b80b9fe7a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

structuredContent is view-only hydration data and should never be
injected into the model context. Remove it from collect_mcp_app_contexts
entirely.

Also strip resource.blob from embedded resource content blocks alongside
the existing data field stripping, preventing binary payloads from
leaking into the synthetic message.

Signed-off-by: Andrew Harvard <aharvard@squareup.com>
Co-authored-by: Goose <opensource@block.xyz>
Ai-assisted: true
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d5e2cba51c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +68 to +72
s.replace('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
.replace('"', "&quot;")
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop escaping quotes in app-context body text

collect_mcp_app_contexts() serializes non-text blocks to JSON and then runs them through xml_escape(). Because this helper also rewrites " to &quot;, the fallback you inject into Message::user().with_text(...) is no longer valid JSON—apps that send image/resource blocks will reach the model as {&quot;type&quot;:...} instead of something it can reliably inspect or copy. Escaping </& is enough for element text here; escaping quotes breaks the metadata this feature is trying to preserve.

Useful? React with 👍 / 👎.

Comment on lines +1141 to +1143
goose::agents::McpAppContext {
content: payload.content,
structured_content: payload.structured_content,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Drop structuredContent instead of retaining it unused

This request stores structuredContent on McpAppContext, but the new collection path explicitly never injects that field into the prompt. I checked the production references after this change: outside tests, structured_content is only written here and on the struct, never read. For MCP apps that follow our own guidance and put full hydration payloads in structuredContent, each update now pins that potentially large JSON blob in mcp_app_contexts until another update or session teardown with no effect on model behavior.

Useful? React with 👍 / 👎.

Copy link
Collaborator

@DOsinga DOsinga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left some comments, but maybe we should talk about this. is moim the right mechanism? if the agent gets reset due to some mechanism, you lose the value the way it is setup right now. should we consider a user invisible message instead? and also what's the role of the structured content here?

}

if let Some(moim) = extension_manager
let moim = extension_manager
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the way it works right now is that the extension manager does all the moim collection, moving part of that here but not everything makes this fragile. so I would either have the extension manager return a list of moim items and do the construction here (preferred) or send the mcp_app_context to the extension manager

);
}

#[tokio::test]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would drop these tests. all they do is confirm that if you add a string it makes it to the other end

}
}
// structuredContent is intentionally not injected — it is
// view-only hydration data and should never reach the model.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this gives me pause - shouldn't the structured content go back to the widget then again?

@DOsinga DOsinga added the needs_human label to set when a robot looks at a PR and can't handle it label Mar 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs_human label to set when a robot looks at a PR and can't handle it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Implement ui/update-model-context for MCP Apps

2 participants