Skip to content

feat: Integrate MCP Apps into A2UI#748

Open
dmandar wants to merge 3 commits intomainfrom
md-mcpuinew
Open

feat: Integrate MCP Apps into A2UI#748
dmandar wants to merge 3 commits intomainfrom
md-mcpuinew

Conversation

@dmandar
Copy link
Collaborator

@dmandar dmandar commented Mar 2, 2026

  • Adds McpAppsCustomComponent using the official @modelcontextprotocol/ext-apps SDK.
  • Implements double-iframe sandbox isolation (sandbox.html, sandbox.ts).
  • Upgrades the backend MCP server (floor_plan_server.py) to a persistent SSE architecture using Starlette.
  • Configures agent.py to connect via mcp.client.sse.
  • Refines Vite configuration for seamless dual-origin local development.

Description

Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.

List which issues are fixed by this PR. For larger changes, raising an issue first helps reduce redundant work.

Pre-launch Checklist

If you need help, consider asking for advice on the discussion board.

- Adds McpAppsCustomComponent using the official @modelcontextprotocol/ext-apps SDK.
- Implements double-iframe sandbox isolation (sandbox.html, sandbox.ts).
- Upgrades the backend MCP server (floor_plan_server.py) to a persistent SSE architecture using Starlette.
- Configures agent.py to connect via mcp.client.sse.
- Refines Vite configuration for seamless dual-origin local development.
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request integrates MCP Apps into A2UI by adding a new McpAppsCustomComponent, a double-iframe sandbox for security, and a persistent SSE backend. The changes are significant and well-structured. My review focuses on improving security and configuration management. I've identified critical security issues related to postMessage usage that should be addressed. Additionally, there are opportunities to improve maintainability by removing hardcoded URLs and making the code more robust. The repository's style guide requires tests for new code (line 17), which seem to be missing for the new components and server logic. Please consider adding tests to ensure the stability and correctness of these new features.

Comment on lines +209 to +264
el.onclick = () => {
// Protocol translation to MCP App standard
window.parent.postMessage({
jsonrpc: "2.0",
id: Date.now(),
method: "tools/call",
params: {
name: "chart_node_click",
arguments: {
clickedNodeName: mapping.contactName,
contactId: mapping.contactId,
deskId: desk.id,
source: 'modal'
}
}
}, '*');
};
} else {
el.style.borderColor = 'rgba(0,0,0,0.1)';
el.style.backgroundColor = 'transparent';
el.style.cursor = 'default';
el.title = "Empty Desk";
}

container.appendChild(el);
});
}

document.body.appendChild(tooltip);

function updateTooltipPos(e) {
if (tooltip.style.display === 'none') return;
tooltip.style.left = (e.clientX + 15) + 'px';
tooltip.style.top = (e.clientY + 15) + 'px';
}

window.addEventListener('message', (event) => {
const data = event.data;
if (data.type === 'zoom') {
zoom(data.payload.factor);
}
});

createHotspots();

// MCP Handshake
window.parent.postMessage({
jsonrpc: "2.0",
id: Date.now(),
method: "ui/initialize",
params: {
appCapabilities: {},
clientInfo: { name: "Floor Plan App", version: "1.0.0" },
protocolVersion: "2026-01-26"
}
}, '*');
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The postMessage calls on lines 224 and 264 use a wildcard * for the target origin, which is a significant security vulnerability. This allows any website to embed this content and intercept the messages. You should restrict the target origin to the specific, expected parent origin. For example, the parent frame could send its origin in an initial message, which this script could then store and use for all subsequent postMessage calls.

}
} else {
if (inner && inner.contentWindow) {
inner.contentWindow.postMessage(event.data, "*");
Copy link
Contributor

Choose a reason for hiding this comment

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

high

When forwarding messages to the inner iframe, you are using a wildcard * as the target origin. While the inner iframe is same-origin in this setup, it is a security best practice to always specify the exact target origin. You should use OWN_ORIGIN here to ensure the message is only delivered if the inner iframe's origin matches.

Suggested change
inner.contentWindow.postMessage(event.data, "*");
inner.contentWindow.postMessage(event.data, OWN_ORIGIN);

raise ValueError("No content returned from floor plan server")

html_content = result.contents[0].text
except Exception as e:
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Catching a broad Exception can hide unexpected errors and make debugging more difficult. It's better to catch more specific exceptions that you expect from the network request (e.g., connection errors) and from the logic within the try block (like the ValueError you're raising). This allows for more granular error handling and logging.

<body>
<div id="viewport">
<div id="container">
<img id="floorplan" src="http://localhost:10004/static/floorplan.png" alt="Office Floor Plan">
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The image URL http://localhost:10004/static/floorplan.png is hardcoded within the HTML string. This will cause issues when deploying to environments other than local development. This URL should be made configurable, for example by passing it into the HTML template from the Python server, which could in turn read it from an environment variable or configuration file.

Comment on lines +182 to +188
if (typeof value === "string") {
context.push({ key, value: { literalString: value } });
} else if (typeof value === "number") {
context.push({ key, value: { literalNumber: value } });
} else if (typeof value === "boolean") {
context.push({ key, value: { literalBoolean: value } });
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The #dispatchAgentAction method currently only handles primitive types (string, number, boolean) for action parameters. If an action parameter is a complex object or an array, it will be skipped without an error. To make this more robust, you should consider handling these cases, for example by serializing complex values to a JSON string.

        if (typeof value === "string") {
          context.push({ key, value: { literalString: value } });
        } else if (typeof value === "number") {
          context.push({ key, value: { literalNumber: value } });
        } else if (typeof value === "boolean") {
          context.push({ key, value: { literalBoolean: value } });
        } else if (value !== null && typeof value === 'object') {
          // Handle nested objects/arrays by stringifying them.
          context.push({ key, value: { literalString: JSON.stringify(value) } });
        }

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

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

1 participant