From e4d92c216da4be452b9d6f79c43e7480545b9159 Mon Sep 17 00:00:00 2001 From: Anton Pidkuiko MacBook Date: Fri, 30 Jan 2026 12:50:02 +0000 Subject: [PATCH 1/2] docs(skill): add CSP location and CORS pitfalls to common mistakes Add three new items to the 'Common Mistakes to Avoid' section: - CSP _meta must be in contents array, not registerResource config - Localhost/private IPs blocked in CSP validation - CORS vs CSP confusion with ui.domain guidance --- .../mcp-apps/skills/create-mcp-app/SKILL.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/plugins/mcp-apps/skills/create-mcp-app/SKILL.md b/plugins/mcp-apps/skills/create-mcp-app/SKILL.md index b497105e..cd6d75e3 100644 --- a/plugins/mcp-apps/skills/create-mcp-app/SKILL.md +++ b/plugins/mcp-apps/skills/create-mcp-app/SKILL.md @@ -312,6 +312,25 @@ See `examples/shadertoy-server/` for complete implementation. 6. **No text fallback** - Always provide `content` array for non-UI hosts 7. **Hardcoded styles** - Use host CSS variables for theme integration 8. **No streaming for large inputs** - Use `ontoolinputpartial` to show progress during generation +9. **CSP `_meta` in wrong location** - Put `_meta.ui.csp` in the `contents` array (readCallback), NOT in the `registerResource` config. Only the contents location is read by hosts: + ```typescript + // WRONG - config param + registerAppResource(server, "View", "ui://app", { _meta: { ui: { csp: {...} } } }, callback); + + // CORRECT - in contents returned by callback + async () => ({ + contents: [{ + uri, mimeType, text: html, + _meta: { ui: { csp: { connectDomains: ["https://api.example.com"] } } } + }] + }) + ``` +10. **Localhost in CSP blocked** - CSP validation blocks `localhost`, `127.0.0.1`, and private IPs to prevent security issues. Use a tunnel (Cloudflare, ngrok) for local development. +11. **CORS vs CSP confusion** - CSP controls what *your app can request*. CORS controls what *external servers will accept*. If requests fail with "No Access-Control-Allow-Origin header", that's a CORS issue on the target server, not CSP. Use `ui.domain` to get a stable origin for CORS allowlists: + ```bash + # Compute your stable origin for CORS allowlist + node -e "console.log(require('crypto').createHash('sha256').update('https://your-server.com/mcp').digest('hex').slice(0,32)+'.claudemcpcontent.com')" + ``` ## Testing From 5aa4c6cc30dd70125ab4e878726a788726b513ab Mon Sep 17 00:00:00 2001 From: Anton Pidkuiko MacBook Date: Fri, 30 Jan 2026 12:57:15 +0000 Subject: [PATCH 2/2] docs(skill): simplify CSP example --- plugins/mcp-apps/skills/create-mcp-app/SKILL.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/mcp-apps/skills/create-mcp-app/SKILL.md b/plugins/mcp-apps/skills/create-mcp-app/SKILL.md index cd6d75e3..007f7f3e 100644 --- a/plugins/mcp-apps/skills/create-mcp-app/SKILL.md +++ b/plugins/mcp-apps/skills/create-mcp-app/SKILL.md @@ -314,10 +314,6 @@ See `examples/shadertoy-server/` for complete implementation. 8. **No streaming for large inputs** - Use `ontoolinputpartial` to show progress during generation 9. **CSP `_meta` in wrong location** - Put `_meta.ui.csp` in the `contents` array (readCallback), NOT in the `registerResource` config. Only the contents location is read by hosts: ```typescript - // WRONG - config param - registerAppResource(server, "View", "ui://app", { _meta: { ui: { csp: {...} } } }, callback); - - // CORRECT - in contents returned by callback async () => ({ contents: [{ uri, mimeType, text: html,