Protocol Extensions
MCP protocol extensions advertise additional, optional capabilities during the initialize handshake. A server opts in via Builder::enableExtension()
:
use Mcp\Schema\Extension\Apps\McpApps;
use Mcp\Server;
$server = Server::builder()
->setServerInfo('My Server', '1.0.0')
->enableExtension(new McpApps())
->build();
Pass one or more ServerExtensionInterface
instances; multiple extensions can be enabled in a single call. Enabling the same extension twice throws a LogicException
.
Note: extensions enabled via
enableExtension()are merged into theextensionscapability even when you supply your ownServerCapabilitiesviasetCapabilities(). An enabled extension overrides any entry under the same id already present in those capabilities.
MCP Apps (io.modelcontextprotocol/ui
)
The MCP Apps extension lets servers expose interactive HTML UIs as resources. Clients that support it render them in sandboxed iframes and bridge tool calls between the iframe (the View ) and the server via the host.
A UI consists of two pieces wired together by _meta.ui
:
- A resource
with URI scheme
ui://and MIME typetext/html;profile=mcp-app, returning the HTML body. - A tool
linked to that resource via
UiToolMeta, so the client knows to open the UI when the tool is invoked.
use Mcp\Schema\Content\TextResourceContents;
use Mcp\Schema\Extension\Apps\McpApps;
use Mcp\Schema\Extension\Apps\ToolVisibility;
use Mcp\Schema\Extension\Apps\UiResourceContentMeta;
use Mcp\Schema\Extension\Apps\UiResourceCsp;
use Mcp\Schema\Extension\Apps\UiResourcePermissions;
use Mcp\Schema\Extension\Apps\UiToolMeta;
$server = Server::builder()
->enableExtension(new McpApps())
->addResource(
fn () => new TextResourceContents(
uri: 'ui://my-app',
mimeType: McpApps::MIME_TYPE,
text: file_get_contents(__DIR__.'/app.html'),
meta: ['ui' => new UiResourceContentMeta(
csp: new UiResourceCsp(connectDomains: ['https://api.example.com']),
permissions: new UiResourcePermissions(geolocation: true),
prefersBorder: true,
)],
),
'ui://my-app',
mimeType: McpApps::MIME_TYPE,
meta: ['ui' => McpApps::resourceMarker()],
)
->addTool(
$myToolHandler,
'my_tool',
meta: ['ui' => new UiToolMeta(
resourceUri: 'ui://my-app',
visibility: [ToolVisibility::Model, ToolVisibility::App],
)],
)
->build();
Note the two distinct _meta.ui
shapes: the resource descriptor
(its resources/list
entry) carries only an empty marker — McpApps::resourceMarker()
— flagging it as an MCP App, while the resource content
returned by resources/read
carries the structured UiResourceContentMeta
with the actual CSP and permission configuration.
Server-side DTOs
| Class | Purpose |
|---|---|
McpApps
|
Extension marker; provides EXTENSION_ID
, MIME_TYPE
, URI_SCHEME
constants. |
UiToolMeta
|
Tool _meta.ui
payload: resourceUri
+ visibility
. |
ToolVisibility
|
Enum: Model
, App
. |
UiResourceContentMeta
|
Resource content _meta.ui
: csp
, permissions
, domain
, prefersBorder
. |
UiResourceCsp
|
CSP allow-lists: connectDomains
, resourceDomains
, frameDomains
, baseUriDomains
. |
UiResourcePermissions
|
Sandbox permissions: camera
, microphone
, geolocation
, clipboardWrite
. |
Writing the HTML view
The View and host exchange JSONRPCMessage
objects
(not JSON strings) via window.parent.postMessage
. Before the host forwards tools/call
, tool-input
, or tool-result
, the View must complete the spec-mandated handshake:
- View → Host:
ui/initializerequest - Host → View: response with
hostCapabilities,hostInfo,hostContext - View → Host:
ui/notifications/initialized - View → Host:
ui/notifications/size-changedwhenever the iframe wants to resize
See the ext-apps
repository for the full protocol, official TypeScript SDK (@modelcontextprotocol/ext-apps
), and view-side examples. A working minimal view is included in examples/server/mcp-apps/weather-app.html
.