Skip to content

feat: enhance sandbox capability negotiation - #158

Merged
ochafik merged 9 commits into
mainfrom
feat/sandbox-capabilities
Jan 12, 2026
Merged

feat: enhance sandbox capability negotiation#158
ochafik merged 9 commits into
mainfrom
feat/sandbox-capabilities

Conversation

@idosal

Copy link
Copy Markdown
Contributor

Proposal to tackle #58 -

  1. Add frameDomains and baseUriDomains overrides to Resource Metadata's ui/csp attribute
  2. Add a permissions attribute(ui/permissions) to the Resource Metadata. Currently, it supports camera, microphone, and geolocation. There are many other permissions, but I think we should only add fields as they're needed. @alexi-openai could you please share what OpenAI allows?
  3. Add csp to Host<>App capability negotiation (since it's non-trivial for the app to detect at runtime)

If it's acceptable, I'll add an E2E test before merging.

Thoughts?

@pkg-pr-new

pkg-pr-newBot commented Dec 15, 2025

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/ext-apps

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/ext-apps@158

@modelcontextprotocol/server-basic-react

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-basic-react@158

@modelcontextprotocol/server-basic-vanillajs

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-basic-vanillajs@158

@modelcontextprotocol/server-budget-allocator

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-budget-allocator@158

@modelcontextprotocol/server-cohort-heatmap

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-cohort-heatmap@158

@modelcontextprotocol/server-customer-segmentation

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-customer-segmentation@158

@modelcontextprotocol/server-scenario-modeler

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-scenario-modeler@158

@modelcontextprotocol/server-system-monitor

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-system-monitor@158

@modelcontextprotocol/server-threejs

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-threejs@158

@modelcontextprotocol/server-wiki-explorer

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-wiki-explorer@158

commit: 88c9ed4

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the MCP Apps sandbox capability negotiation by adding support for additional CSP directives and permissions policies. The changes enable apps to request nested iframe support, custom base URI configuration, and browser permissions (camera, microphone, geolocation) through resource metadata.

Key changes:

  • Added frameDomains and baseUriDomains fields to CSP configuration for controlling nested iframes and base URI directives
  • Introduced permissions metadata for requesting camera, microphone, and geolocation access via Permission Policy
  • Extended host capability negotiation to include CSP support indicators (frameDomains and baseUriDomains)

Reviewed changes

Copilot reviewed 6 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
FileDescription
src/types.tsAdded exports for new McpUiResourcePermissions type and schema
src/spec.types.tsDefined new interfaces for permissions and extended CSP configuration with frame/baseUri domains
src/generated/schema.tsAdded Zod schemas for permissions and extended CSP schemas with new domain fields
src/generated/schema.test.tsUpdated type inference tests to include new permissions schema
src/generated/schema.jsonGenerated JSON schemas for new permissions and CSP extensions
specification/draft/apps.mdxDocumented new CSP fields and permissions with examples and security guidance
examples/simple-host/sandbox.htmlAdded new sandbox proxy HTML implementation (missing CSP/permissions handling)
examples/basic-host/src/sandbox.tsImplemented CSP meta tag building and iframe allow attribute for permissions
examples/basic-host/src/implementation.tsExtended resource data extraction to include new CSP and permissions metadata

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadspecification/draft/apps.mdx Outdated
Comment threadexamples/basic-host/src/sandbox.ts Outdated
Comment threadexamples/simple-host/sandbox.html Outdated
Comment threadexamples/basic-host/src/sandbox.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

@ochafikochafik left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Ido!

baseUriDomains?: string[];
};
permissions?: {
camera?: boolean;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's make these objects (as in capabilities) for future extensions?
e.g. what if one day there's fine-grained vers. coarse geolocation, or front vs. back camera permission, etc.

@idosalidosalDec 18, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like CSP, Permissions are coupled to the browser spec (Permissions Policy). I don't think we should diverge at this point. WDYT?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 40+ Permission Policies you can stuff in an iFrame. Consider keeping permissions very simple and flexible, just have it be a string[].

Benefits:

  1. Client can set all permission policies with a single string. Flexible for future extensions.
  2. Enforcing this on the client side is simple. We just stuff the string into an iFrame's allow. This is safe because invalid strings are silently ignored.
  3. Still easily parseable on the server side. Server developer only has to do permissions.contains("camera");.

@matteo8pmatteo8pDec 22, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively create some interface and have permission be an array of that interface if type safety and enforcement is of high importance.

interface Permissions {
camera: "camera", ....
}

permissions?: Permissions[]

Comment threadexamples/simple-host/sandbox.html Outdated

@ochafikochafik left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should tell app what permissions were granted (and potentially which domains were allowed)

@idosal
idosal requested a review from ochafikDecember 18, 2025 20:31
@matteo8p

Copy link
Copy Markdown
Contributor

Is there an iFrame in the ext-apps project, or that out of the scope of this extension project? I'm wondering where the permissions are getting enforced into the iFrame.

@matteo8p

matteo8p commented Dec 22, 2025

Copy link
Copy Markdown
Contributor

I can see this being useful if the MCP server needs to know what the browser capabilities / permissions are. However, I'm pretty sure you can also fetch the browser capability within the widget too using document.featurePolicy.allowedFeatures().

https://developer.mozilla.org/en-US/docs/Web/API/FeaturePolicy/allowedFeatures

I perhaps React widgets should be using this instead as a source of truth.

@matteo8p

Copy link
Copy Markdown
Contributor

@idosal I think OpenAI currently allows local-network-access *; microphone *; midi *

Screenshot 2025-12-22 at 3 57 47 PM

I have no clue why midi is on there. That's for music instruments haha

@idosal

Copy link
Copy Markdown
ContributorAuthor

I can see this being useful if the MCP server needs to know what the browser capabilities / permissions are. However, I'm pretty sure you can also fetch the browser capability within the widget too using document.featurePolicy.allowedFeatures().

https://developer.mozilla.org/en-US/docs/Web/API/FeaturePolicy/allowedFeatures

I perhaps React widgets should be using this instead as a source of truth.

Thanks @matteo8p ! Permissions were originally excluded from negotiation because they could be detected directly. It was considered that the negotiation assumes less about the app runtime and the developer's awareness of the browser APIs. We should explore this further with the community to come to a decision.

Comment threadexamples/simple-host/sandbox.html Outdated
if (event.data && event.data.method === 'ui/notifications/sandbox-resource-ready') {
const { html, sandbox, permissions } = event.data.params || {};
// Note: csp is not extracted here - CSP is set via HTTP response headers in serve.ts
if (typeof sandbox === 'string') {

@ochafikochafikJan 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we probably need to set the sandbox attribute in the host itself (and let them be inherited transparently here), cf. comments from @domfarolino during today's meeting.

(or else, the child, being same origin, will be able to change its own sandbox attributes by poking at its parent dom ; or would be able to create other iframes w/o these limitations)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(same way that CSP settings are defined in the headers serving this page, and not injected by this page)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think whatever features are enabled inside of sandbox.html will be inherited by the same-origin iframe inner. So I think what really needs to happen is the explicit capability delegation to the iframe whose source is sandbox.html, and we should get delegation to the same-origin app contents for free. This is important because any changes made to sandbox and permissions policies here, after inner has been inserted into the DOM, will not apply to content (the html string, in this case) that has been synchronously written into the frame after the attributes change. For those attributes to take effect, the inner iframe will have to re-navigate to apply the attribute changes.

Comment threadexamples/simple-host/sandbox.html Outdated
if (event.data && event.data.method === 'ui/notifications/sandbox-resource-ready') {
const { html, sandbox, permissions } = event.data.params || {};
// Note: csp is not extracted here - CSP is set via HTTP response headers in serve.ts
if (typeof sandbox === 'string') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think whatever features are enabled inside of sandbox.html will be inherited by the same-origin iframe inner. So I think what really needs to happen is the explicit capability delegation to the iframe whose source is sandbox.html, and we should get delegation to the same-origin app contents for free. This is important because any changes made to sandbox and permissions policies here, after inner has been inserted into the DOM, will not apply to content (the html string, in this case) that has been synchronously written into the frame after the attributes change. For those attributes to take effect, the inner iframe will have to re-navigate to apply the attribute changes.

Comment threadexamples/simple-host/sandbox.html Outdated
try {
if (inner.contentDocument) {
inner.contentDocument.open();
inner.contentDocument.write(html);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that as mentioned above, I don't think any changes made to inner's allow or sandbox attributes will take effect after inner has been inserted to the DOM. You'll need to re-navigate the iframe for those attributes to stick.

Probably what you want to do is hold of on appending inner to the DOM until after you parse and set the intended attribute values. Or, given the comment further above, maybe we don't need to send the attribute values all the way down here, since inner should inherit most things from sandbox.html, since it's a same-origin iframe.

aharvard added a commit to aaif-goose/goose that referenced this pull request Jan 8, 2026
Implements early support for proposed CSP extensions to the MCP Apps spec:
- frameDomains: Controls frame-src directive for nested iframes
- baseUriDomains: Controls base-uri directive
This anticipates changes proposed in:
modelcontextprotocol/ext-apps#158
Note: The upstream spec PR is not yet merged, but we're implementing
early to validate the approach.
@aharvard

Copy link
Copy Markdown
Contributor

Getting ahead of this and goose and implementing frameDomains and baseUriDoamins: aaif-goose/goose#6399

PTAL if interested.

ochafik added a commit that referenced this pull request Jan 11, 2026
…Domains, permissions
Security improvements:
- CSP is now set via HTTP headers in serve.ts instead of meta tags
(meta tag CSP can be tampered with by same-origin content)
- CSP passed as query param to sandbox.html for header-based enforcement
New CSP/permissions features (borrowed from PR #158):
- frameDomains: control frame-src directive for nested iframes
- baseUriDomains: control base-uri directive
- permissions: camera, microphone, geolocation via iframe allow attribute
WebGL fix:
- Use document.write() instead of srcdoc for inner iframe content
(srcdoc creates opaque origin that breaks WebGL canvas updates)
- Add worker-src directive with blob: support (critical for WebGL apps
like CesiumJS/Three.js that use workers for tile decoding, terrain
processing, image processing)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ochafik added a commit that referenced this pull request Jan 11, 2026
Keep this PR focused on CSP security fixes only.
Permissions (camera, microphone, geolocation) will be
handled in #158.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ochafik added a commit that referenced this pull request Jan 11, 2026
Keep this PR focused on CSP security fixes only.
Permissions (camera, microphone, geolocation) will be
handled in #158.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@ochafik

Copy link
Copy Markdown
Contributor

Note: I've created #234 to implement a reference csp handling in the sandbox's server, I've sneaked the frameDomains and baseUriDomains there. Maybe this PR can focus on the sandbox permissions? @idosal wdyt?

ochafik added a commit that referenced this pull request Jan 11, 2026
…er-src, document.write) (#234)
* fix: Move CSP to HTTP headers + add worker-src, frameDomains, baseUriDomains, permissions
Security improvements:
- CSP is now set via HTTP headers in serve.ts instead of meta tags
(meta tag CSP can be tampered with by same-origin content)
- CSP passed as query param to sandbox.html for header-based enforcement
New CSP/permissions features (borrowed from PR #158):
- frameDomains: control frame-src directive for nested iframes
- baseUriDomains: control base-uri directive
- permissions: camera, microphone, geolocation via iframe allow attribute
WebGL fix:
- Use document.write() instead of srcdoc for inner iframe content
(srcdoc creates opaque origin that breaks WebGL canvas updates)
- Add worker-src directive with blob: support (critical for WebGL apps
like CesiumJS/Three.js that use workers for tile decoding, terrain
processing, image processing)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: Remove permissions support (defer to PR #158)
Keep this PR focused on CSP security fixes only.
Permissions (camera, microphone, geolocation) will be
handled in #158.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* cleanup McpUiResourceCsp type usage / duplicate defs
* fix: guard against CSP injection in domain parameters
Validate CSP domain entries to reject characters that could:
- Break out of CSP directives (semicolons, newlines)
- Inject CSP keywords like 'unsafe-eval' (quotes)
- Inject multiple sources in one entry (spaces)
This prevents injection attacks where malicious domains could
override the security policy.
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
@ochafik

Copy link
Copy Markdown
Contributor

I've created an speech example that relies on microphone and clipboard-write: #240

@idosal mind if i help refresh this PR / push to this branch?

Merges the latest main branch changes including:
- HTTP header-based CSP enforcement (more secure than meta tags)
- frameDomains and baseUriDomains support
Added from this branch:
- McpUiResourcePermissions type for camera/microphone/geolocation
- Permission Policy support via iframe allow attribute
- Host capabilities sandbox section for permissions negotiation
Adds clipboardWrite to McpUiResourcePermissions for clipboard access.
Maps to Permission Policy 'clipboard-write' feature.
@ochafik

Copy link
Copy Markdown
Contributor

Merged main + pushed a few changes; Suggested updates for this PR (@idosal does this sg?):

Title: feat: add sandbox permissions support (camera, microphone, geolocation, clipboard-write)

Body:

Closes #58

Summary

This PR adds Permission Policy support for MCP Apps sandboxes, allowing apps to request browser capabilities like
camera, microphone, geolocation, and clipboard access.

Note: The CSP enhancements (frameDomains, baseUriDomains) and HTTP header-based CSP enforcement were
implemented separately in #234 and have been merged into this branch.

What's New

Permissions Support

Apps can now declare which browser permissions they need via resource metadata:

_meta: {ui: {permissions: {camera: {},microphone: {},geolocation: {},clipboardWrite: {},}}}Thesemaptothe https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Permissions_Policy allow attribute on the innersandboxiframe.HostCapabilityNegotiationHostsreportgrantedsandboxcapabilitiesbacktoappsviahostCapabilities.sandbox: hostCapabilities: {sandbox: {permissions: {camera: {},microphone: {}, ... },csp: {connectDomains: [...],resourceDomains: [...], ... }}}Thisallowsappstodetectatinitializationtimewhichpermissionsweregranted,ratherthanrelyingsolelyonruntimefeaturedetection.SupportedPermissions┌────────────────┬───────────────────┬───────────────────────────────────┐PermissionPermissionPolicyUseCase├────────────────┼───────────────────┼───────────────────────────────────┤cameracameraVideocapture├────────────────┼───────────────────┼───────────────────────────────────┤microphonemicrophoneAudiocapture,speechrecognition├────────────────┼───────────────────┼───────────────────────────────────┤geolocationgeolocationLocationservices├────────────────┼───────────────────┼───────────────────────────────────┤clipboardWriteclipboard-writeCopytoclipboard└────────────────┴───────────────────┴───────────────────────────────────┘Additionalpermissionscanbeaddedasneeded(e.g.,midi,fullscreen).Implementation-Types: McpUiResourcePermissionsinspec.types.ts-Schemas: Auto-generatedZodschemas-Sandbox: buildAllowAttribute()insandbox.tsappliespermissionstoinneriframe-Host: Passespermissionsviaui/notifications/sandbox-resource-ready-Spec: Updatedapps.mdxwithpermissionsdocumentationTesting-54unittestspass-51e2etestspass(Docker)DesignDiscussionAsnotedby @matteo8p,appscanalsodetectpermissionsatruntimeviadocument.featurePolicy.allowedFeatures().The negotiation approachwaschosenbecause: 1.Itdoesn't assume app developers know about browser APIs 2.Itallowscapabilitydetectionatinitializationtime3.ItfollowstheexistingpatternforCSPnegotiation

@antonpk1antonpk1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is great!

@ochafik
ochafik merged commit 9acc52c into mainJan 12, 2026
19 checks passed
connor4312 added a commit to microsoft/vscode that referenced this pull request Jan 12, 2026
Additional sandbox negotiation options
Refs modelcontextprotocol/ext-apps#158
connor4312 added a commit to microsoft/vscode that referenced this pull request Jan 12, 2026
@ochafikochafik mentioned this pull request Jan 12, 2026
eli-w-king pushed a commit to microsoft/vscode that referenced this pull request Jan 14, 2026
@ochafikochafik mentioned this pull request Jan 21, 2026
pedrumj2 pushed a commit to pedrumj2/Agentic that referenced this pull request Jun 30, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@idosal@matteo8p@aharvard@ochafik@domfarolino@antonpk1