Typed Go SDK for the MCP UI / MCP Apps extension.
This module is the dedicated extension layer for UI-specific MCP behavior. It exists to keep the boundaries clean between:
github.com/viant/mcp-protocol— official MCP schema mirrorgithub.com/viant/mcp— transport/runtimegithub.com/viant/mcp-ui— UI extension capability, metadata, resources, browser envelope contracts, and compatibility helpers
UI support for MCP needs its own layer.
This repo owns:
- typed
_meta.uihelpers - typed UI extension capability helpers
ui://...resource builders- browser host/guest
postMessageenvelope types - exact compatibility switching between negotiated UI resources and explicit embedded fallback
This repo intentionally does not own:
- core MCP schema definitions
- MCP transport implementations
- Agently-specific server behavior
- Forge-specific rendering behavior
Today this module focuses on the extension contract itself.
That means:
- typed capability negotiation
- typed
_meta.uimetadata - canonical
ui://...resource construction - browser host/guest envelope types
- exact compatibility switching between negotiated UI resources and explicit embedded fallback
It does not try to be:
- a browser host implementation
- an MCP server framework
- a product-specific UI toolkit
- a replacement for
mcp-protocolormcp
The SDK surface is active and usable.
Implemented in the current worktree:
- capability helpers in
capabilities - typed metadata helpers in
meta - resource builders in
resource - browser envelope types in
appproto - compatibility switching in
compat
The current compatibility rule is exact:
- if UI extension capability is negotiated, use
_meta.ui.resourceUriplusresources/read - if UI extension capability is not negotiated, embedded fallback is
allowed only when
_meta.ui.fallback = "embedded"is explicitly present
Current maturity in this repo:
- the SDK contract is implemented and test-backed
- compatibility switching is implemented and test-backed
- browser-host rollout, production review, and product-specific policy remain downstream concerns for consuming projects
In other words: this repo is ready to be used as an extension SDK, while deployment posture is decided by downstream hosts.
This SDK targets the MCP Apps extension proposed by SEP-1865.
- Repository:
modelcontextprotocol/ext-apps - Commit SHA:
9a37ad71827d076af06978fa7f7f510449687061 - Pinned source: apps.mdx
- SEP discussion: modelcontextprotocol/discussions/1865
All extension identifiers, MIME types, and _meta.ui.* key spellings in this
repo are pinned to that SHA.
Pinned identifiers:
- extension capability key:
io.modelcontextprotocol/ui - UI resource MIME type:
text/html;profile=mcp-app - canonical tool metadata field:
_meta.ui.resourceUri
Current metadata surface includes:
resourceUriallowedToolsallowedToolBundlescontentHashprotocolVersionrendererUrlsandbox- legacy raw
csp - structured
cspPolicy fallback
Capability helpers for the UI extension.
Use it to:
- advertise UI support on client/server capabilities
- detect whether the peer negotiated UI support
Main exports:
ExtensionNameResourceMimeTypeCapabilitySetClientCapabilitySetServerCapabilityGetClientCapabilityGetServerCapability
Typed _meta.ui helpers for tools and resources.
Use it to:
- advertise
ui://...resources on tools - attach render/security hints to resources
- carry structured CSP policy and explicit embedded-fallback intent
Main exports:
ToolUIResourceUICSPPolicySetToolUIGetToolUISetResourceUIGetResourceUISetReadResultContentsUIGetReadResultContentsUI
Builders for ui://... resources and resource contents.
Use it to:
- create MCP resource declarations
- create
resources/readHTML contents - create embedded HTML fallback resources
- compute canonical content hashes
Main exports:
ValidateUIURIContentHashNewHTMLResourceNewHTMLContentsNewReadResultHTMLContentsNewEmbeddedHTMLResource
Browser host/guest postMessage contract.
This is explicitly not MCP JSON-RPC.
Use it to type and validate:
mcpui:tools-callmcpui:messagemcpui:open-linkmcpui:host-readymcpui:tool-inputmcpui:tool-input-partialmcpui:tool-resultmcpui:teardown
Exact compatibility switching helpers.
Use it to:
- decide whether a tool/resource should use negotiated UI-resource flow
- decide whether explicit embedded fallback is allowed
- build embedded fallback from canonical
resources/readoutput
The example below shows a full minimal flow:
- declare UI capability on the server
- advertise a
ui://...resource on a tool - publish the matching resource
- choose negotiated UI-resource flow vs embedded fallback
package main
import (
"context"
"fmt"
"github.com/viant/mcp-protocol/schema"
"github.com/viant/mcp-ui/capabilities"
"github.com/viant/mcp-ui/compat"
"github.com/viant/mcp-ui/meta"
"github.com/viant/mcp-ui/resource"
)
func main() {
const (
uri = "ui://example.demo/widget/show_status"
html = "<!DOCTYPE html><html><body><h1>Status OK</h1></body></html>"
)
// 1. Server advertises UI capability.
serverCaps := &schema.ServerCapabilities{}
capabilities.SetServerCapability(serverCaps, capabilities.Capability{
ProtocolVersion: "1.0.0",
MimeTypes: []string{capabilities.ResourceMimeType},
})
// 2. Tool advertises a UI resource and explicit embedded fallback.
tool := &schema.Tool{Name: "show_status"}
meta.SetToolUI(tool, meta.ToolUI{
ResourceUri: uri,
Fallback: meta.FallbackEmbedded,
})
// 3. Resource carries canonical UI metadata.
uiMeta := meta.ResourceUI{
ContentHash: resource.ContentHash(html),
ProtocolVersion: "1.0.0",
CSPPolicy: &meta.CSPPolicy{
ScriptSrc: []string{"https://cdn.example"},
},
}
res, err := resource.NewHTMLResource(
uri,
"show_status",
ptr("Demo status widget"),
ptr("Demo Status"),
nil,
uiMeta,
)
if err != nil {
panic(err)
}
readContents, err := resource.NewReadResultHTMLContents(uri, html, uiMeta)
if err != nil {
panic(err)
}
// 4. Client decides whether to use negotiated UI-resource flow
// or explicit embedded fallback.
clientCaps := &schema.ClientCapabilities{}
mode := compat.SelectToolMode(clientCaps, meta.ToolUI{
ResourceUri: uri,
Fallback: meta.FallbackEmbedded,
})
fmt.Println("tool:", tool.Name)
fmt.Println("resource:", res.Uri)
fmt.Println("mode:", mode.String())
fmt.Println("read mime:", *readContents.MimeType)
fmt.Println("read hash:", uiMeta.ContentHash)
// If the peer had no UI capability and you needed fallback:
reader := compatReader{contents: *readContents}
embedded, err := compat.BuildEmbeddedFallback(context.Background(), reader, uri)
if err != nil {
panic(err)
}
fmt.Println("embedded type:", embedded.Type)
}
type compatReader struct {
contents schema.ReadResourceResultContentsElem
}
func (r compatReader) ReadResource(context.Context, string) (*schema.ReadResourceResult, error) {
return &schema.ReadResourceResult{
Contents: []schema.ReadResourceResultContentsElem{r.contents},
}, nil
}
func ptr[T any](v T) *T { return &v }Local replace in a consumer repo:
replace github.com/viant/mcp-ui => ../mcp-uiThis repo itself uses:
replace github.com/viant/mcp-protocol => ../mcp-protocolRun the package tests from this repo root:
go test ./...For the package-level suites used most often during development:
go test ./capabilities ./meta ./resource ./compat ./appprotoThis repo:
- depends on
github.com/viant/mcp-protocol - must not depend on:
github.com/viant/agently-coregithub.com/viant/agentlygithub.com/viant/forge
That separation is intentional: this SDK defines the extension contract, while hosts and products consume it downstream.
Apache 2.0. See LICENSE.