Skip to content

Conversation

@Kvadratni
Copy link
Contributor

@Kvadratni Kvadratni commented Aug 22, 2025

Add MCP UI sidecar panel for interactive tool interfaces

This PR introduces a collapsible sidecar panel that displays MCP UI resources alongside the main chat interface. When tools return UI resources (like forms, visualizations, or interactive components), users can open them in a dedicated side panel for better interaction.

Key Features:

  • Toggle button appears on tool responses with UI resources
  • Sidecar panel slides in from the right with smooth animations
  • Window automatically resizes to accommodate the panel
  • Context shared between main chat and sidecar for seamless interaction
  • Clean separation of concerns with dedicated SidecarContext

Technical Implementation:

  • New SidecarContext manages panel state and UI resource handling
  • MainPanelLayout updated to support flexible width with sidecar
  • IPC communication for window resizing when panel opens/closes
  • Responsive design maintains usability across different screen sizes
mcp-ui-sidecar-compressed.mp4

@Kvadratni Kvadratni marked this pull request as ready for review August 22, 2025 17:26
@michaelneale michaelneale self-assigned this Aug 25, 2025
@michaelneale
Copy link
Collaborator

ok looks interesting, testing it out:

image

I wasn't able to close it (top right) and think need to think about the flow for if you want things always in sidecar, or not, vs case by case. If a broke it out once - then it would't show the next visualisation until I clicked the breckout arrow again (which did work, but was confusing).

@Kvadratni if you pull this, turn on autoviz and you can have an easy way to try this self contained that we should ensure it works coherently with cc @aharvard

@Kvadratni Kvadratni force-pushed the mnovich/mcp-ui-sidecar branch from 9cb8e97 to c055607 Compare August 28, 2025 16:58
@thisispete
Copy link

thisispete commented Aug 29, 2025

864b61d
applies the following logic:

  • when tool completes with UI context the sidecar atuomatically opens, if it's already open it replaces the contents
  • the close button closes the sidecar
  • clicking the popout button opens replacing the current contents of the sidecar (so you can scroll back and manually open older results)

working on upgrading the resizing logic when the sidecar opens to reduce the visual glitching

@michaelneale
Copy link
Collaborator

getting there! - at the moment it for me defaulted to showing it in the size car with autovisualiser - is that intended, and has a header that says "MCP-sidecar" which I assume we don't need? (and when I closed sidecar, it didn't put the visualisation back in the chat)

@michaelneale
Copy link
Collaborator

@thisispete want to resolve that conflict so it is updated? (or I can if you like)

@michaelneale
Copy link
Collaborator

(updated to main, hopefully is still ok)

Copy link
Collaborator

@aharvard aharvard left a comment

Choose a reason for hiding this comment

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

I'm wondering if this PR suggests that all MCP-UI should be shown in a sidecar. I just want to make sure we're not assuming too much about what the MCP server author intended. Please let me know if I'm mistaken!

I would like to suggest that we consider using a configuration object that allows the MCP server author to decide where and how rendering occurs.

The MCP UI server SDK gives us a solution via uiMetadata. I pulled this metadata configuration example from mcpui.dev as an example:

// Example 8: Using uiMetadata for client-side configuration
const resourceWithUIMetadata = createUIResource({
  uri: 'ui://chart/interactive',
  content: { type: 'externalUrl', iframeUrl: 'https://charts.example.com/widget' },
  encoding: 'text',
  uiMetadata: {
    'preferred-frame-size': ['800px', '600px'],
    'initial-render-data': { 
      theme: 'dark', 
      chartType: 'bar',
      dataSet: 'quarterly-sales' 
    },
  }
});
console.log('Resource with UI metadata:', JSON.stringify(resourceWithUIMetadata, null, 2));

/* Output includes:
{
  "type": "resource",
  "resource": {
    "uri": "ui://chart/interactive",
    "mimeType": "text/uri-list",
    "text": "https://charts.example.com/widget",
    "_meta": {
      "mcpui.dev/ui-preferred-frame-size": ["800px", "600px"],
      "mcpui.dev/ui-initial-render-data": { 
        "theme": "dark", 
        "chartType": "bar",
        "dataSet": "quarterly-sales" 
      },
    }
  }
}
*/

Stuff that gets added to uiMetadata will get processed by the SDK. It will get prefixed with mcpui.dev/ui- and placed in the embedded resources _meta field.

So I think the best plan for this work is for us to look up the value of some resource._meta.mcpui.dev/ui-<WE-NEED-TO-LAND-A-PROP-NAME-FOR-GOOSE> and then conditionally render the MCP UI inline or in a sidecar.

Now, what do we want MCP server authors to provide under uiMetadata?

  • uiMetadata.preferred-frame-location: "sidecard" | "inline"
  • uiMetadata.initial-render-data.location: "sidecard" | "inline"

FYI - The Rust SDK (RMCP) fails to return _meta currently. We will need to fix that upstream before this PR can land, leveraging a _meta approach.

@aharvard
Copy link
Collaborator

aharvard commented Sep 2, 2025

getting there! - at the moment it for me defaulted to showing it in the size car with autovisualiser - is that intended, and has a header that says "MCP-sidecar" which I assume we don't need? (and when I closed sidecar, it didn't put the visualisation back in the chat)

@michaelneale, as the author of the autovisualiser tool, I assume that you get to own where the MCP UI should render. What's your take? Only show inline? Only show in sidecar? Show inline but allow user to "cast" into sidecar and back to inline?

(Generally speaking, I think Goose needs to defer to tool authors and not assume too much.)

@liady
Copy link

liady commented Sep 2, 2025

@aharvard about the prop name -

  1. initial-render-data is automatically being passed back to the iframe itself, so in this case it's not suitable (since the host needs to know this data, not the UI)
  2. uiMetadata reserved props are automatically prefized and are automatically handled by the SDK (i.e preferred-frame-size and initial-render-data). In this case it's location information that the embedding environment needs to know (the Goose code that renders UIResourceRenderer), so perhaps the general metadata is a more suitable option here (unsure, just suggesting).

So maybe

createUIResource({
  ...
  metadata: {
    'preferred-flow-location': 'inline' | 'persistent' 
  } 
})

?

@michaelneale michaelneale removed their assignment Sep 3, 2025
@michaelneale
Copy link
Collaborator

@aharvard sidecar! I would pick that.

@aharvard
Copy link
Collaborator

aharvard commented Sep 3, 2025

modelcontextprotocol/rust-sdk#386 was merged yesterday and should fix the issue where Goose would not pass _meta from a server response to the client.

After the next RMCP release, we should be able to update Goose and get inline/sidecar positioning preference from servers.

In the meantime, we need to land the property name we want to look for and document for server authors to manage rendering position.

@aharvard
Copy link
Collaborator

aharvard commented Sep 3, 2025

per @liady, let's assume servers should

createUIResource({
  ...
  metadata: {
    'some-goose-property-name': 'some-value'  
  } 
})

We'll then be able to pull from _meta

@aharvard
Copy link
Collaborator

aharvard commented Sep 5, 2025

As soon as #4523 merges, _meta will start to flow thru #4523 (comment)

@michaelneale and I discussed strategy around property naming as it relates to whether MCP UI should appear inline or in sidecar. He brought up a really good point about how if we namespace under a goose object that's not really all that aligned with the spirit of MCP. We think other MCP UI clients could take advantage of this key for other string values. He floated the idea of this:

{
  "type": "resource",
  "resource": {
    "uri": "ui://mcp-aharvard/weather-card",
    "mimeType": "text/html",
    "text": "<style>body { background: black; color: white; }</style><div>UI metadata demo</div>",
    "_meta": {
      "target": "sidecar"
    }
  }
}

Proposal

resource._meta.target could accept sidecar or inline.

Goose can default to inline when resource._meta.target is not provided.

Alternative Idea

@liady @idosal — we could add a new target property to https://mcpui.dev/guide/server/typescript/overview#uimetadata — is it too soon for MCP-UI to commit to a first-class config option like this?

@Kvadratni Kvadratni force-pushed the mnovich/mcp-ui-sidecar branch from 7a44668 to 1bfff0f Compare September 5, 2025 20:10
@idosal
Copy link

idosal commented Sep 6, 2025

As soon as #4523 merges, _meta will start to flow thru #4523 (comment)

@michaelneale and I discussed strategy around property naming as it relates to whether MCP UI should appear inline or in sidecar. He brought up a really good point about how if we namespace under a goose object that's not really all that aligned with the spirit of MCP. We think other MCP UI clients could take advantage of this key for other string values. He floated the idea of this:

{
  "type": "resource",
  "resource": {
    "uri": "ui://mcp-aharvard/weather-card",
    "mimeType": "text/html",
    "text": "<style>body { background: black; color: white; }</style><div>UI metadata demo</div>",
    "_meta": {
      "target": "sidecar"
    }
  }
}

Proposal

resource._meta.target could accept sidecar or inline.

Goose can default to inline when resource._meta.target is not provided.

Alternative Idea

@liady @idosal — we could add a new target property to https://mcpui.dev/guide/server/typescript/overview#uimetadata — is it too soon for MCP-UI to commit to a first-class config option like this?

I agree, Goose namespacing sounds too limiting. The proposal to place a generic layout hint prop under _meta seems like the right approach at this time, although the term target may be a little ambiguous. It might make sense to go with a map under a key like layout, that can indicate container, persistence, etc. WDYT?

Regarding the alternative - we need to give more thought to what the mcpui.dev namespace will signal going forward.

@liady
Copy link

liady commented Sep 7, 2025

@aharvard in addition - recall that originally we had a preferred-context hint in uiMetadata, and we removed it not only because "context" was unclear but because its values weren't standardized.
A layout (or locationHints?) object indeed makes more sense if we want to treat position and persistence as separate keys.
As for the frame location values - I think inline is generic enough and an understandable default. However we need to think on whether sidecar is a generic term, and if maybe at first we'd want to only recommend two location hint values (inline and "not inline"), and let each client decide what "not inline" means.

In a 3rd party scenario (which can be the common one) - the server doesn't have any information about the client, so we'd probably want to keep the terms as generic as possible.
Also in this scenario the server doesn't know where the client is aiming to render the tool response, so we'd might want to allow servers to provide multiple resources options for the same UI (imagine a tool response with two resources objects - one for inline rendering and one for a sidebar).

So we need to find a way to answer these two slightly different needs -

  • the server saying I recommend the client to render this resource in a sidecar, and -
  • the server saying if you need to render in a sidecar - this is the resource you should use.

@michaelneale michaelneale marked this pull request as draft September 8, 2025 04:48
@michaelneale
Copy link
Collaborator

housekeeping: converting to draft just a signal to know when I should take a harder run at it (when you think it is ready please to change status)

@michaelneale
Copy link
Collaborator

hey @Kvadratni @aharvard might close this for now - can we move this to a discussion pointing to this branch until ready? Open again when ready to go or have time (I don't at the moment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants