Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 35 additions & 5 deletions crates/goose-server/src/routes/mcp_app_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ struct ProxyQuery {
connect_domains: Option<String>,
/// Comma-separated list of domains for resource loading (scripts, styles, images, fonts, media)
resource_domains: Option<String>,
/// Comma-separated list of origins for nested iframes (frame-src)
frame_domains: Option<String>,
/// Comma-separated list of allowed base URIs (base-uri)
base_uri_domains: Option<String>,
}

const MCP_APP_PROXY_HTML: &str = include_str!("templates/mcp_app_proxy.html");
Expand All @@ -26,7 +30,12 @@ const MCP_APP_PROXY_HTML: &str = include_str!("templates/mcp_app_proxy.html");
///
/// Based on the MCP Apps specification (ext-apps SEP):
/// <https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx>
fn build_outer_csp(connect_domains: &[String], resource_domains: &[String]) -> String {
fn build_outer_csp(
connect_domains: &[String],
resource_domains: &[String],
frame_domains: &[String],
base_uri_domains: &[String],
) -> String {
let resources = if resource_domains.is_empty() {
String::new()
} else {
Expand All @@ -39,6 +48,18 @@ fn build_outer_csp(connect_domains: &[String], resource_domains: &[String]) -> S
format!(" {}", connect_domains.join(" "))
};

let frame_src = if frame_domains.is_empty() {
"frame-src 'none'".to_string()
} else {
format!("frame-src {}", frame_domains.join(" "))
};

let base_uris = if base_uri_domains.is_empty() {
String::new()
} else {
format!(" {}", base_uri_domains.join(" "))
};

format!(
"default-src 'none'; \
script-src 'self' 'unsafe-inline'{resources}; \
Expand All @@ -49,9 +70,9 @@ fn build_outer_csp(connect_domains: &[String], resource_domains: &[String]) -> S
img-src 'self' data: blob:{resources}; \
font-src 'self'{resources}; \
media-src 'self' data: blob:{resources}; \
frame-src blob: data:; \
{frame_src}; \
object-src 'none'; \
base-uri 'self'"
base-uri 'self'{base_uris}"
)
}

Expand All @@ -73,7 +94,9 @@ fn parse_domains(domains: Option<&String>) -> Vec<String> {
params(
("secret" = String, Query, description = "Secret key for authentication"),
("connect_domains" = Option<String>, Query, description = "Comma-separated domains for connect-src"),
("resource_domains" = Option<String>, Query, description = "Comma-separated domains for resource loading")
("resource_domains" = Option<String>, Query, description = "Comma-separated domains for resource loading"),
("frame_domains" = Option<String>, Query, description = "Comma-separated origins for nested iframes (frame-src)"),
("base_uri_domains" = Option<String>, Query, description = "Comma-separated allowed base URIs (base-uri)")
),
responses(
(status = 200, description = "MCP App proxy HTML page", content_type = "text/html"),
Expand All @@ -91,9 +114,16 @@ async fn mcp_app_proxy(
// Parse domains from query params
let connect_domains = parse_domains(params.connect_domains.as_ref());
let resource_domains = parse_domains(params.resource_domains.as_ref());
let frame_domains = parse_domains(params.frame_domains.as_ref());
let base_uri_domains = parse_domains(params.base_uri_domains.as_ref());

// Build the outer CSP based on declared domains
let csp = build_outer_csp(&connect_domains, &resource_domains);
let csp = build_outer_csp(
&connect_domains,
&resource_domains,
&frame_domains,
&base_uri_domains,
);

// Replace the CSP placeholder in the HTML template
let html = MCP_APP_PROXY_HTML.replace("{{OUTER_CSP}}", &csp);
Expand Down
8 changes: 8 additions & 0 deletions ui/desktop/src/components/McpApps/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export async function fetchMcpAppProxyUrl(
csp?: {
connectDomains?: string[] | null;
resourceDomains?: string[] | null;
frameDomains?: string[] | null;
baseUriDomains?: string[] | null;
} | null
): Promise<string | null> {
try {
Expand All @@ -28,6 +30,12 @@ export async function fetchMcpAppProxyUrl(
if (csp?.resourceDomains?.length) {
params.set('resource_domains', csp.resourceDomains.join(','));
}
if (csp?.frameDomains?.length) {
params.set('frame_domains', csp.frameDomains.join(','));
}
if (csp?.baseUriDomains?.length) {
params.set('base_uri_domains', csp.baseUriDomains.join(','));
}

return `${baseUrl}/mcp-app-proxy?${params.toString()}`;
} catch (error) {
Expand Down
Loading