Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungThe Model Got Better. Your Judgment Got Worse.(22.09.2026 um 03:02 Uhr)
Sichere ProgrammierungAIFeed - signed content permissions for AI web crawlers(22.09.2026 um 03:10 Uhr)
Sichere ProgrammierungThanks, glad you liked it!(22.09.2026 um 03:15 Uhr)
Sichere ProgrammierungA request for /.env shouldn't render your React app(22.09.2026 um 03:17 Uhr)
Sichere ProgrammierungAI-Agent Marketplaces Need Verifiable Delivery, Not More Listings(22.09.2026 um 03:20 Uhr)
AI & KI NachrichtenBeyond Bigger Models: Toward a Modular Cognitive Architecture(22.09.2026 um 03:21 Uhr)
Sichere ProgrammierungMasa Depan Manajemen Data: Mengenal Konsep Data Mesh yang Revolusioner(22.09.2026 um 03:22 Uhr)
Sichere ProgrammierungHow to Search Your Claude Code Conversation History(22.09.2026 um 03:22 Uhr)
Sichere ProgrammierungThe Model Got Better. Your Judgment Got Worse.(22.09.2026 um 03:02 Uhr)
Sichere ProgrammierungAIFeed - signed content permissions for AI web crawlers(22.09.2026 um 03:10 Uhr)
Sichere ProgrammierungThanks, glad you liked it!(22.09.2026 um 03:15 Uhr)
Sichere ProgrammierungA request for /.env shouldn't render your React app(22.09.2026 um 03:17 Uhr)
Sichere ProgrammierungAI-Agent Marketplaces Need Verifiable Delivery, Not More Listings(22.09.2026 um 03:20 Uhr)
AI & KI NachrichtenBeyond Bigger Models: Toward a Modular Cognitive Architecture(22.09.2026 um 03:21 Uhr)
Sichere ProgrammierungMasa Depan Manajemen Data: Mengenal Konsep Data Mesh yang Revolusioner(22.09.2026 um 03:22 Uhr)
Sichere ProgrammierungHow to Search Your Claude Code Conversation History(22.09.2026 um 03:22 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

MCP App CSP Explained: Why Your Widget Won't Render

You built an MCP App. The tool works. The server returns data. But the widget renders as a blank iframe. You've hit the #1 problem in MCP App development: Content Security Policy. This post explains exactly how CSP works in MCP Apps,…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

You built an MCP App. The tool works. The server returns data. But the widget renders as a blank iframe.



You've hit the #1 problem in MCP App development: Content Security Policy.



This post explains exactly how CSP works in MCP Apps, what the three domain arrays do, the mistakes that cause silent failures, and how to debug them. By the end, you'll never stare at a blank widget again.






The sandbox model



Every MCP App widget runs inside a sandboxed iframe. On ChatGPT, that iframe lives at a domain like yourapp.web-sandbox.oaiusercontent.com. On Claude, it's computed from a hash of your server URL. On VS Code, it's host-controlled.



The sandbox blocks everything by default. No external API calls. No CDN images. No Google Fonts. No WebSocket connections. Nothing leaves the iframe unless you explicitly declare it.



You declare allowed domains in _meta.ui.csp on your MCP resource. The host reads this and sets the iframe's Content Security Policy. If a domain isn't declared, the browser blocks the request before it even happens.



Here's what a declaration looks like:




_meta: {
ui: {
csp: {
connectDomains: ["https://api.example.com"],
resourceDomains: ["https://cdn.example.com"],
}
}
}






Simple enough. But the devil is in knowing which array to put each domain in.






The three domain arrays






connectDomains — runtime connections



Controls: fetch(), XMLHttpRequest, WebSocket, EventSource, navigator.sendBeacon()



Maps to the CSP connect-src directive.



Use when: your widget calls an API at runtime.




// This fetch will be BLOCKED unless api.stripe.com is in connectDomains
const charges = await fetch("https://api.stripe.com/v1/charges");

// Same for WebSockets
const ws = new WebSocket("wss://realtime.example.com/feed");









resourceDomains — static assets



Controls: <script src>, <link rel="stylesheet">, <img src>, <video>, <audio>, @font-face, CSS url(), @import



Maps to CSP script-src, style-src, img-src, font-src, media-src.



Use when: your widget loads assets from external CDNs.




<!-- These will be BLOCKED unless the domains are in resourceDomains -->
<script src="https://cdn.example.com/chart.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet">
<img src="https://res.cloudinary.com/demo/image/upload/sample.jpg">









frameDomains — nested iframes



Controls: <iframe src>



Maps to CSP frame-src.



Use when: your widget embeds third-party content like YouTube videos, Google Maps, or Spotify players.




<!-- BLOCKED unless youtube.com is in frameDomains -->
<iframe src="https://www.youtube.com/embed/abc123"></iframe>






Without frameDomains, nested iframes are blocked entirely. Note that ChatGPT reviews apps with frameDomains more strictly — only use it when you actually embed iframes.






The five mistakes that break your widget






1. Using resourceDomains for API calls



This is the most common mistake. Your widget calls fetch() to an API, and you put the domain in resourceDomains because "it's a resource." It isn't — fetch() is a runtime connection.




// Wrong: API domain in resourceDomains
csp: {
resourceDomains: ["https://api.example.com"]
}

// Correct: API domain in connectDomains
csp: {
connectDomains: ["https://api.example.com"]
}






The rule: if your JavaScript code calls it at runtime, it goes in connectDomains. If an HTML tag loads it as a static asset, it goes in resourceDomains.






2. Forgetting the font file domain



Google Fonts is a two-domain system. The CSS is served from fonts.googleapis.com, but the actual font files (.woff2) come from fonts.gstatic.com. If you only declare the first, the CSS loads but the fonts don't.




// Wrong: CSS loads, fonts don't
csp: {
resourceDomains: ["https://fonts.googleapis.com"]
}

// Correct: both domains declared
csp: {
resourceDomains: [
"https://fonts.googleapis.com",
"https://fonts.gstatic.com"
]
}






Your widget will render with fallback system fonts — a subtle visual bug that's easy to miss during development but obvious to users.






3. Missing the WebSocket protocol



WebSocket connections use wss://, not https://. If you declare the HTTPS version, the WebSocket connection still fails.




// Wrong: wss:// connections are still blocked
csp: {
connectDomains: ["https://realtime.example.com"]
}

// Correct: use the wss:// scheme
csp: {
connectDomains: ["wss://realtime.example.com"]
}

// Also correct: declare both if you use both
csp: {
connectDomains: [
"https://api.example.com",
"wss://realtime.example.com"
]
}









4. Services that need both arrays



Some services serve both static assets AND API responses from the same or related domains. Mapbox is a classic example — it serves API responses (tile coordinates) and image tiles (actual map pictures) from the same origins.




// Wrong: only connect, map tiles don't render
csp: {
connectDomains: ["https://api.mapbox.com"]
}

// Correct: both connect and resource
csp: {
connectDomains: ["https://api.mapbox.com"],
resourceDomains: ["https://api.mapbox.com"]
}






Other services that commonly need both: Cloudinary (API + image CDN), Firebase (API + hosting), Supabase (API + storage).






5. Works in dev, breaks when published



ChatGPT has a more relaxed CSP in developer mode. When you publish your app, stricter rules apply. Two things that catch people:



Missing _meta.ui.domain. Developer mode works without it. Published mode requires it — this is the domain ChatGPT uses to scope your widget's sandbox origin.




_meta: {
ui: {
domain: "https://myapp.example.com", // required for published apps
csp: { /* ... */ }
}
}






Missing openai/widgetCSP. Some published apps need the ChatGPT-specific CSP format alongside the standard _meta.ui.csp:




_meta: {
ui: {
csp: {
connectDomains: ["https://api.example.com"],
resourceDomains: ["https://cdn.example.com"]
}
},
// ChatGPT compatibility layer
"openai/widgetCSP": {
connect_domains: ["https://api.example.com"],
resource_domains: ["https://cdn.example.com"]
}
}






Note the naming difference: connectDomains (camelCase) in the standard spec vs connect_domains (snake_case) in the ChatGPT extension.






How to debug CSP violations



When CSP blocks a request, the browser logs it to the console. Here's how to find it:




  1. Open DevTools (F12 or Cmd+Opt+I)

  2. Go to the Console tab

  3. Look for red errors starting with Refused to



The error message tells you exactly what was blocked:




Refused to connect to 'https://api.example.com/data'
because it violates the following Content Security Policy directive:
"connect-src 'self'"






This tells you:





  • What was blocked: https://api.example.com/data


  • Which directive: connect-src — you need connectDomains


  • Current policy: only 'self' is allowed — the domain isn't declared



For font issues:




Refused to load the font 'https://fonts.gstatic.com/s/inter/...'
because it violates the following Content Security Policy directive:
"font-src 'self'"






This means fonts.gstatic.com needs to be in resourceDomains.






Debugging checklist



When your widget is blank or partially broken:




  1. Open DevTools Console — look for Refused to errors

  2. For each error, identify the directive (connect-src, font-src, script-src, etc.)

  3. Map the directive to the right array:



    • connect-srcconnectDomains


    • script-src, style-src, img-src, font-src, media-srcresourceDomains


    • frame-srcframeDomains



  4. Add the blocked domain to the correct array

  5. Restart your MCP server and test again






Copy-paste patterns



Here are CSP declarations for common use cases:



API calls only:




csp: {
connectDomains: ["https://api.yourbackend.com"]
}






CDN images:




csp: {
resourceDomains: ["https://cdn.yourbackend.com"]
}






Google Fonts:




csp: {
resourceDomains: [
"https://fonts.googleapis.com",
"https://fonts.gstatic.com"
]
}






Full stack — API + CDN + Fonts:




csp: {
connectDomains: ["https://api.yourbackend.com"],
resourceDomains: [
"https://cdn.yourbackend.com",
"https://fonts.googleapis.com",
"https://fonts.gstatic.com"
]
}






Mapbox maps:




csp: {
connectDomains: [
"https://api.mapbox.com",
"https://events.mapbox.com"
],
resourceDomains: [
"https://api.mapbox.com",
"https://cdn.mapbox.com"
]
}






Embedded YouTube:




csp: {
frameDomains: ["https://www.youtube.com"]
}









Other sandbox restrictions



CSP isn't the only thing the sandbox blocks. These browser APIs are also restricted inside MCP App iframes:





  • localStorage / sessionStorage — may throw SecurityError. Use in-memory state instead.


  • eval() / new Function() — blocked by default. Some charting libraries use eval() internally — check before picking a dependency.


  • window.open() — blocked. Use the MCP Apps bridge for navigation.


  • document.cookie — no cookies in sandboxed iframes.


  • navigator.clipboard — blocked.


  • alert() / confirm() / prompt() — blocked.



If your widget depends on any of these, it will fail silently even if your CSP is perfect.






Platform differences



The MCP Apps spec is standard, but each host implements it differently:
































Platform CSP source Widget domain Notes
ChatGPT
_meta.ui.csp + openai/widgetCSP
{domain}.web-sandbox.oaiusercontent.com Requires _meta.ui.domain for published apps
Claude _meta.ui.csp SHA-256 of MCP server URL Own sandbox model
VS Code _meta.ui.csp Host-controlled Had bugs with resourceDomains mapping in older versions


If you're building for multiple platforms, test on each. A widget that works on ChatGPT might fail on Claude or VS Code due to these subtle differences.






Skip the debugging entirely



Getting CSP right by hand is tedious. Every time you add a new external dependency — a font, an analytics script, an API endpoint — you need to update _meta.ui.csp and hope you picked the right array.



MCPR is an open-source MCP proxy that handles this for you. It sits between the AI client and your MCP server, reads your _meta.ui.csp declarations, and injects the correct CSP headers automatically — so you declare once and it works across ChatGPT, Claude, and VS Code.




cargo install mcpr
mcpr --config mcpr.toml






If you don't want to self-host, MCPR Cloud gives you a managed tunnel with a free subdomain. Claim yours at cloud.mcpr.app and start proxying in minutes — CSP handled, auth included, every tool call observable.








TL;DR:





  • connectDomains = fetch(), WebSocket, XHR (runtime connections)


  • resourceDomains = images, fonts, scripts, stylesheets (static assets)


  • frameDomains = nested iframes (use sparingly)

  • Debug with DevTools Console — look for "Refused to" errors

  • Google Fonts needs both fonts.googleapis.com AND fonts.gstatic.com

  • Test on every platform you ship to — they're all slightly different

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten MCP App CSP Explained: Why Your Widget Won't Render

Thematisch verwandte Begriffe: Explained, Your, Widget, Wont · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-49449 | Joplin is an open source note-taking and to-do application that organise…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick