Tracking a deliberately-deferred design decision from the MCP Apps work (#4407).
Context
When a Shiny app is served as an MCP App, HTTP side channels (file upload/download, server-side selectize/DataTables, dynamically inserted dependencies) need to reach the app from the sandboxed iframe. Two transports exist:
- Tunnel — requests go through the app-only
_shiny_http tool (postMessage → tools/call), same channel as the websocket tunnel.
- Direct-connect — where the host honors the declared CSP, the iframe opens a real WebSocket straight to the app for reactivity.
Today, even in direct-connect mode the HTTP side channels stay tunneled (_shiny_http accepts calls without a connectionId), and no CORS headers are added to the app.
The trade-off (why it's deferred)
Adding permissive CORS to the app so the iframe could make direct cross-origin XHRs would mean any website could read a localhost/deployed Shiny app's responses. That's an unacceptable default, so we kept side channels tunneled. The cost is extra round-trips for side-channel traffic in direct mode (reactivity itself is still native-speed over the direct WebSocket).
What to revisit
- Is there a safe, scoped CORS story (e.g. echoing only the host's sandbox origin, credentialed vs. not, per-deployment opt-in) that would let direct-mode side channels go direct without opening the app to arbitrary origins?
- Or is tunneled-side-channels the right permanent design, in which case close this and document the rationale?
References: mcp/QUESTIONS.md §3 and mcp/architecture.md ("Security decisions") on the branch.
Experimental feature; not blocking the initial merge.
Tracking a deliberately-deferred design decision from the MCP Apps work (#4407).
Context
When a Shiny app is served as an MCP App, HTTP side channels (file upload/download, server-side selectize/DataTables, dynamically inserted dependencies) need to reach the app from the sandboxed iframe. Two transports exist:
_shiny_httptool (postMessage →tools/call), same channel as the websocket tunnel.Today, even in direct-connect mode the HTTP side channels stay tunneled (
_shiny_httpaccepts calls without aconnectionId), and no CORS headers are added to the app.The trade-off (why it's deferred)
Adding permissive CORS to the app so the iframe could make direct cross-origin XHRs would mean any website could read a localhost/deployed Shiny app's responses. That's an unacceptable default, so we kept side channels tunneled. The cost is extra round-trips for side-channel traffic in direct mode (reactivity itself is still native-speed over the direct WebSocket).
What to revisit
References:
mcp/QUESTIONS.md§3 andmcp/architecture.md("Security decisions") on the branch.Experimental feature; not blocking the initial merge.