Add frameDomains and baseUriDomains CSP support for MCP Apps (#6399)
This commit is contained in:
@@ -14,6 +14,10 @@ struct ProxyQuery {
|
|||||||
connect_domains: Option<String>,
|
connect_domains: Option<String>,
|
||||||
/// Comma-separated list of domains for resource loading (scripts, styles, images, fonts, media)
|
/// Comma-separated list of domains for resource loading (scripts, styles, images, fonts, media)
|
||||||
resource_domains: Option<String>,
|
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");
|
const MCP_APP_PROXY_HTML: &str = include_str!("templates/mcp_app_proxy.html");
|
||||||
@@ -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):
|
/// Based on the MCP Apps specification (ext-apps SEP):
|
||||||
/// <https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx>
|
/// <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() {
|
let resources = if resource_domains.is_empty() {
|
||||||
String::new()
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
@@ -39,6 +48,18 @@ fn build_outer_csp(connect_domains: &[String], resource_domains: &[String]) -> S
|
|||||||
format!(" {}", connect_domains.join(" "))
|
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!(
|
format!(
|
||||||
"default-src 'none'; \
|
"default-src 'none'; \
|
||||||
script-src 'self' 'unsafe-inline'{resources}; \
|
script-src 'self' 'unsafe-inline'{resources}; \
|
||||||
@@ -49,9 +70,9 @@ fn build_outer_csp(connect_domains: &[String], resource_domains: &[String]) -> S
|
|||||||
img-src 'self' data: blob:{resources}; \
|
img-src 'self' data: blob:{resources}; \
|
||||||
font-src 'self'{resources}; \
|
font-src 'self'{resources}; \
|
||||||
media-src 'self' data: blob:{resources}; \
|
media-src 'self' data: blob:{resources}; \
|
||||||
frame-src blob: data:; \
|
{frame_src}; \
|
||||||
object-src 'none'; \
|
object-src 'none'; \
|
||||||
base-uri 'self'"
|
base-uri 'self'{base_uris}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +94,9 @@ fn parse_domains(domains: Option<&String>) -> Vec<String> {
|
|||||||
params(
|
params(
|
||||||
("secret" = String, Query, description = "Secret key for authentication"),
|
("secret" = String, Query, description = "Secret key for authentication"),
|
||||||
("connect_domains" = Option<String>, Query, description = "Comma-separated domains for connect-src"),
|
("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(
|
responses(
|
||||||
(status = 200, description = "MCP App proxy HTML page", content_type = "text/html"),
|
(status = 200, description = "MCP App proxy HTML page", content_type = "text/html"),
|
||||||
@@ -91,9 +114,16 @@ async fn mcp_app_proxy(
|
|||||||
// Parse domains from query params
|
// Parse domains from query params
|
||||||
let connect_domains = parse_domains(params.connect_domains.as_ref());
|
let connect_domains = parse_domains(params.connect_domains.as_ref());
|
||||||
let resource_domains = parse_domains(params.resource_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
|
// 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
|
// Replace the CSP placeholder in the HTML template
|
||||||
let html = MCP_APP_PROXY_HTML.replace("{{OUTER_CSP}}", &csp);
|
let html = MCP_APP_PROXY_HTML.replace("{{OUTER_CSP}}", &csp);
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export async function fetchMcpAppProxyUrl(
|
|||||||
csp?: {
|
csp?: {
|
||||||
connectDomains?: string[] | null;
|
connectDomains?: string[] | null;
|
||||||
resourceDomains?: string[] | null;
|
resourceDomains?: string[] | null;
|
||||||
|
frameDomains?: string[] | null;
|
||||||
|
baseUriDomains?: string[] | null;
|
||||||
} | null
|
} | null
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
@@ -28,6 +30,12 @@ export async function fetchMcpAppProxyUrl(
|
|||||||
if (csp?.resourceDomains?.length) {
|
if (csp?.resourceDomains?.length) {
|
||||||
params.set('resource_domains', csp.resourceDomains.join(','));
|
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()}`;
|
return `${baseUrl}/mcp-app-proxy?${params.toString()}`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user