-
Notifications
You must be signed in to change notification settings - Fork 2.3k
[MCP-UI] Proxy and Better Message Handling #5487
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
55 commits
Select commit
Hold shift + click to select a range
a828eac
upgrade @mcp-ui/client
aharvard 02b3d7c
declare supported content types
aharvard 8da9d52
simplify ui action handling
aharvard be436f7
set up self-host proxy for UIResourceRenderer
aharvard b49fd9c
add media-src CSP to mcp-ui-proxy
aharvard 8f877fa
relax script-src CSP on MCP-UI Proxy
aharvard 0ce5a8a
Enhance MCP-UI proxy server security with token validation and origin…
aharvard 94d2162
update console errors for clarity
aharvard 745f2eb
Revert "Enhance MCP-UI proxy server security with token validation an…
aharvard 715c09a
fix static path issue for builds
aharvard c42f12b
bring back MCP-UI proxy security
aharvard 72f1ef1
remove troubleshooting text in UI
aharvard 7677e76
Update ui/desktop/src/components/MCPUIResourceRenderer.tsx
aharvard 5af7c66
fix: use consistent lowercase casing for mcp-ui-proxy-token header
aharvard 949f9b4
Update ui/desktop/src/components/MCPUIResourceRenderer.tsx
aharvard fec432d
refactor: remove excessive debug logging from MCP-UI proxy
aharvard 093bf6c
fix: await MCP-UI proxy server close on app quit
aharvard 9084e51
fix: update Content-Security-Policy in MCP-UI proxy HTML
aharvard 8f2bd7e
Update ui/desktop/src/components/MCPUIResourceRenderer.tsx
aharvard 4f38d64
Update ui/desktop/src/main.ts
aharvard 2f9c187
Update ui/desktop/src/main.ts
aharvard ed320d2
Update ui/desktop/src/main.ts
aharvard 5af994a
security: add origin validation to MCP-UI proxy postMessage calls
aharvard 4d04991
Update ui/desktop/src/main.ts
aharvard 972f95f
Update ui/desktop/src/main.ts
aharvard 6a748ec
Update ui/desktop/src/main.ts
aharvard 6522b1b
Update ui/desktop/src/main.ts
aharvard 927c969
Update ui/desktop/static/mcp-ui-proxy.html
aharvard 127ba09
security: use 'null' origin instead of wildcard for srcdoc iframe
aharvard 80b2c21
refactor: consolidate webRequest handlers and fix origin validation l…
aharvard b1e8628
Update ui/desktop/src/main.ts
aharvard 5eb9bf3
Update ui/desktop/src/components/MCPUIResourceRenderer.tsx
aharvard 52304d7
Update ui/desktop/src/main.ts
aharvard 10468bb
fix issues that co-pilot code review introduced
aharvard d3f5360
fix spelling in comment
aharvard 72e52e8
fix: add null check for iframe contentWindow in MCP-UI proxy
aharvard bf42719
optimize ALLOWED_ORIGIN value
aharvard 8c3988d
refactor: move MCP-UI proxy logic to a dedicated module
aharvard 881eaa5
refactor and document proxy code for maintainability
aharvard ee9959a
Merge remote-tracking branch 'origin/main' into feat/mcp-ui-improvements
aharvard c444706
address copilot feedback
aharvard 913b0bf
update pnpm lock post-merge from main
aharvard c170e4c
remove accidental pnpm-lock
aharvard f2c66e1
security: validate IPC caller origin for MCP-UI proxy URL
aharvard 356b409
security: restrict proxy token injection to trusted webContents
aharvard dfe457e
security: add IPv6 loopback support to proxy allowed hostnames
aharvard e1590f0
security: replace blanket static serving with explicit route
aharvard 92d7c3c
Merge main and resolve conflict in preload.ts
aharvard 8433401
Update ui/desktop/src/components/MCPUIResourceRenderer.tsx
aharvard 40276bf
fix: remove orphaned setSchedulingEngine from preload.ts
aharvard a6daeed
fix: prefix unused req parameters with underscore in proxy.ts
aharvard b6740fb
feat: MCP UI proxy to goose-server (#5749)
alexhancock 273f4de
Merge remote-tracking branch 'origin/main' into feat/mcp-ui-improvements
aharvard 7aef5a2
remove old proxy files from client
aharvard e9e18d7
[MCP-UI] proxy add Referrer-Policy HTTP response header (#5797)
zanesq File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| use axum::{ | ||
| extract::Query, | ||
| http::{header, StatusCode}, | ||
| response::{Html, IntoResponse, Response}, | ||
| routing::get, | ||
| Router, | ||
| }; | ||
| use serde::Deserialize; | ||
|
|
||
| #[derive(Deserialize)] | ||
| struct ProxyQuery { | ||
| secret: String, | ||
| } | ||
|
|
||
| const MCP_UI_PROXY_HTML: &str = include_str!("templates/mcp_ui_proxy.html"); | ||
|
|
||
| #[utoipa::path( | ||
| get, | ||
| path = "/mcp-ui-proxy", | ||
| params( | ||
| ("secret" = String, Query, description = "Secret key for authentication") | ||
| ), | ||
| responses( | ||
| (status = 200, description = "MCP UI proxy HTML page", content_type = "text/html"), | ||
| (status = 401, description = "Unauthorized - invalid or missing secret"), | ||
| ) | ||
| )] | ||
| async fn mcp_ui_proxy( | ||
| axum::extract::State(secret_key): axum::extract::State<String>, | ||
| Query(params): Query<ProxyQuery>, | ||
| ) -> Response { | ||
| if params.secret != secret_key { | ||
| return (StatusCode::UNAUTHORIZED, "Unauthorized").into_response(); | ||
| } | ||
|
|
||
| ( | ||
| [ | ||
| (header::CONTENT_TYPE, "text/html; charset=utf-8"), | ||
| ( | ||
| header::HeaderName::from_static("referrer-policy"), | ||
| "no-referrer", | ||
| ), | ||
| ], | ||
| Html(MCP_UI_PROXY_HTML), | ||
| ) | ||
| .into_response() | ||
| } | ||
|
|
||
| pub fn routes(secret_key: String) -> Router { | ||
| Router::new() | ||
| .route("/mcp-ui-proxy", get(mcp_ui_proxy)) | ||
| .with_state(secret_key) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
142 changes: 142 additions & 0 deletions
142
crates/goose-server/src/routes/templates/mcp_ui_proxy.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| <!doctype html> | ||
| <html> | ||
| <head> | ||
| <meta charset="utf-8"/> | ||
| <meta name="referrer" content="no-referrer"/> | ||
| <!-- | ||
| Permissive CSP so nested content is not constrained by top-level Goose Desktop CSP | ||
| - default-src: Fallback for other directives (allows same-origin) | ||
| - script-src: Allow scripts from any origin, inline, eval, wasm, and blob URLs | ||
| - style-src: Allow styles from any origin and inline styles | ||
| - font-src: Allow fonts from any origin | ||
| - connect-src: Allow network requests to any origin | ||
| - frame-src: Allow embedding iframes from any origin (required for proxy functionality) | ||
| - media-src: Allow audio/video media from any origin | ||
| - base-uri: Restrict <base> tag to same-origin only | ||
| - upgrade-insecure-requests: Automatically upgrade HTTP to HTTPS | ||
| --> | ||
| <meta | ||
| http-equiv="Content-Security-Policy" | ||
| content="default-src 'self'; script-src * 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval' blob:; style-src * 'unsafe-inline'; font-src *; connect-src *; frame-src *; media-src *; base-uri 'self'; upgrade-insecure-requests"/> | ||
| <title>MCP-UI Proxy</title> | ||
| <style> | ||
| body, | ||
| html { | ||
| margin: 0; | ||
| height: 100vh; | ||
| width: 100vw; | ||
| } | ||
| body { | ||
| display: flex; | ||
| flex-direction: column; | ||
| } | ||
| * { | ||
| box-sizing: border-box; | ||
| } | ||
| iframe { | ||
| background-color: transparent; | ||
| border: 0 none transparent; | ||
| padding: 0; | ||
| overflow: hidden; | ||
| flex-grow: 1; | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <script> | ||
| const params = new URLSearchParams(location.search); | ||
| const contentType = params.get('contentType'); | ||
| const target = params.get('url'); | ||
|
|
||
| // Validate that the URL is a valid HTTP or HTTPS URL | ||
| function isValidHttpUrl(string) { | ||
| try { | ||
| const url = new URL(string); | ||
| return url.protocol === 'http:' || url.protocol === 'https:'; | ||
| } catch (error) { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| if (contentType === 'rawhtml') { | ||
| // Double-iframe raw HTML mode (HTML sent via postMessage) | ||
| const inner = document.createElement('iframe'); | ||
| inner.style = 'width:100%; height:100%; border:none;'; | ||
| // sandbox will be set from postMessage payload; default minimal before html arrives | ||
| inner.setAttribute('sandbox', 'allow-scripts'); | ||
| document | ||
| .body | ||
| .appendChild(inner); | ||
|
|
||
| // Wait for HTML content from parent | ||
| window.addEventListener('message', (event) => { | ||
| if (event.source === window.parent) { | ||
| if (event.data && event.data.type === 'ui-html-content') { | ||
| const payload = event.data.payload || {}; | ||
| const html = payload.html; | ||
| const sandbox = payload.sandbox; | ||
| if (typeof sandbox === 'string') { | ||
| inner.setAttribute('sandbox', sandbox); | ||
| } | ||
| if (typeof html === 'string') { | ||
| inner.srcdoc = html; | ||
| } | ||
| } else { | ||
| if (inner && inner.contentWindow) { | ||
| inner | ||
| .contentWindow | ||
| .postMessage(event.data, '*'); | ||
| } | ||
| } | ||
| } else if (event.source === inner.contentWindow) { | ||
| // Relay messages from inner to parent | ||
| window | ||
| .parent | ||
| .postMessage(event.data, '*'); | ||
| } | ||
| }); | ||
|
|
||
| // Notify parent that proxy is ready to receive HTML (distinct event) | ||
| window | ||
| .parent | ||
| .postMessage({ | ||
| type: 'ui-proxy-iframe-ready' | ||
| }, '*'); | ||
| } else if (target) { | ||
| if (!isValidHttpUrl(target)) { | ||
| document.body.textContent = 'Error: invalid URL. Only HTTP and HTTPS URLs are allowed.'; | ||
| } else { | ||
| const inner = document.createElement('iframe'); | ||
| inner.src = target; | ||
| inner.style = 'width:100%; height:100%; border:none;'; | ||
| // Default external URL sandbox; can be adjusted later by protocol if needed | ||
| inner.setAttribute('sandbox', 'allow-same-origin allow-scripts'); | ||
| document | ||
| .body | ||
| .appendChild(inner); | ||
| const urlOrigin = new URL(target).origin; | ||
|
|
||
| window.addEventListener('message', (event) => { | ||
| if (event.source === window.parent) { | ||
| // listen for messages from the parent and send them to the iframe | ||
| if (inner.contentWindow) { | ||
| inner | ||
| .contentWindow | ||
| .postMessage(event.data, urlOrigin); | ||
| } else { | ||
| console.warn('[MCP-UI Proxy] iframe contentWindow is not available; message not sent'); | ||
| } | ||
| } else if (event.source === inner.contentWindow) { | ||
| // listen for messages from the iframe and send them to the parent | ||
| window | ||
| .parent | ||
| .postMessage(event.data, '*'); | ||
| } | ||
| }); | ||
| } | ||
| } else { | ||
| document.body.textContent = 'Error: missing url or html parameter'; | ||
| } | ||
| </script> | ||
| </body> | ||
| </html> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.