Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 4.8k
Enhance client support checks for MCP Apps UI rendering#2051
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
f3f34ea4031c58c7e2ecd36603de24d648e350d97ac2d8087066aebaa8a28ccb6bdc318e169baFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,27 +1,32 @@ | ||||||||||||||||||||
| package github | ||||||||||||||||||||
| import "github.com/modelcontextprotocol/go-sdk/mcp" | ||||||||||||||||||||
| import ( | ||||||||||||||||||||
| "context" | ||||||||||||||||||||
| // uiSupportedClients lists client names (from ClientInfo.Name) known to | ||||||||||||||||||||
| // support MCP Apps UI rendering. | ||||||||||||||||||||
| // | ||||||||||||||||||||
| // This is a temporary workaround until the Go SDK adds an Extensions field | ||||||||||||||||||||
| // to ClientCapabilities (see https://github.com/modelcontextprotocol/go-sdk/issues/777). | ||||||||||||||||||||
| // Once that lands, detection should use capabilities.extensions instead. | ||||||||||||||||||||
| var uiSupportedClients = map[string]bool{ | ||||||||||||||||||||
| "Visual Studio Code - Insiders": true, | ||||||||||||||||||||
| "Visual Studio Code": true, | ||||||||||||||||||||
| } | ||||||||||||||||||||
| ghcontext "github.com/github/github-mcp-server/pkg/context" | ||||||||||||||||||||
| "github.com/modelcontextprotocol/go-sdk/mcp" | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| // mcpAppsExtensionKey is the capability extension key that clients use to | ||||||||||||||||||||
| // advertise MCP Apps UI support. | ||||||||||||||||||||
| const mcpAppsExtensionKey = "io.modelcontextprotocol/ui" | ||||||||||||||||||||
| // clientSupportsUI reports whether the MCP client that sent this request | ||||||||||||||||||||
| // supports MCP Apps UI rendering, based on its ClientInfo.Name. | ||||||||||||||||||||
| func clientSupportsUI(req *mcp.CallToolRequest) bool { | ||||||||||||||||||||
| if req == nil || req.Session == nil { | ||||||||||||||||||||
| return false | ||||||||||||||||||||
| // supports MCP Apps UI rendering. | ||||||||||||||||||||
| // It checks the context first (set by HTTP/stateless servers from stored | ||||||||||||||||||||
| // session capabilities), then falls back to the go-sdk Session (for stdio). | ||||||||||||||||||||
| func clientSupportsUI(ctx context.Context, req *mcp.CallToolRequest) bool { | ||||||||||||||||||||
| // Check context first (works for HTTP/stateless servers) | ||||||||||||||||||||
Comment on lines
+16
to
+19
CopilotAI | ||||||||||||||||||||
| // It checks the context first (set by HTTP/stateless servers from stored | |
| // session capabilities), then falls back to the go-sdk Session (for stdio). | |
| funcclientSupportsUI(ctx context.Context, req*mcp.CallToolRequest) bool { | |
| // Check context first (works for HTTP/stateless servers) | |
| // It first checks for a UI-support flag on the context (when explicitly set | |
| // by the caller, e.g., HTTP/stateless servers that persist capabilities), | |
| // then falls back to the go-sdk Session (for stdio/stateful servers). | |
| funcclientSupportsUI(ctx context.Context, req*mcp.CallToolRequest) bool { | |
| // Prefer explicit context flag when provided (e.g., HTTP/stateless servers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a legit issue or not @mattdholloway? Will we only support this in remote and set the context on the request there, or should this also be supported in STDIO?
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -4,58 +4,84 @@ import ( | ||
| "context" | ||
| "testing" | ||
| ghcontext "github.com/github/github-mcp-server/pkg/context" | ||
| "github.com/modelcontextprotocol/go-sdk/mcp" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
| func createMCPRequestWithCapabilities(t *testing.T, caps *mcp.ClientCapabilities) mcp.CallToolRequest { | ||
| t.Helper() | ||
| srv := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil) | ||
| st, _ := mcp.NewInMemoryTransports() | ||
| session, err := srv.Connect(context.Background(), st, &mcp.ServerSessionOptions{ | ||
| State: &mcp.ServerSessionState{ | ||
| InitializeParams: &mcp.InitializeParams{ | ||
| ClientInfo: &mcp.Implementation{Name: "test-client"}, | ||
| Capabilities: caps, | ||
| }, | ||
| }, | ||
| }) | ||
| require.NoError(t, err) | ||
| t.Cleanup(func() { _ = session.Close() }) | ||
| return mcp.CallToolRequest{Session: session} | ||
| } | ||
| func Test_clientSupportsUI(t *testing.T) { | ||
| t.Parallel() | ||
| ctx := context.Background() | ||
| tests := []struct { | ||
| name string | ||
| clientName string | ||
| want bool | ||
| }{ | ||
| {name: "VS Code Insiders", clientName: "Visual Studio Code - Insiders", want: true}, | ||
| {name: "VS Code Stable", clientName: "Visual Studio Code", want: true}, | ||
| {name: "unknown client", clientName: "some-other-client", want: false}, | ||
| {name: "empty client name", clientName: "", want: false}, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| req := createMCPRequestWithSession(t, tt.clientName, nil) | ||
| assert.Equal(t, tt.want, clientSupportsUI(&req)) | ||
| t.Run("client with UI extension", func(t *testing.T) { | ||
| caps := &mcp.ClientCapabilities{} | ||
| caps.AddExtension("io.modelcontextprotocol/ui", map[string]any{ | ||
| "mimeTypes": []string{"text/html;profile=mcp-app"}, | ||
| }) | ||
mattdholloway marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| req := createMCPRequestWithCapabilities(t, caps) | ||
| assert.True(t, clientSupportsUI(ctx, &req)) | ||
| }) | ||
| t.Run("client without UI extension", func(t *testing.T) { | ||
| req := createMCPRequestWithCapabilities(t, &mcp.ClientCapabilities{}) | ||
| assert.False(t, clientSupportsUI(ctx, &req)) | ||
| }) | ||
| t.Run("client with nil capabilities", func(t *testing.T) { | ||
| req := createMCPRequestWithCapabilities(t, nil) | ||
| assert.False(t, clientSupportsUI(ctx, &req)) | ||
| }) | ||
| t.Run("nil request", func(t *testing.T) { | ||
| assert.False(t, clientSupportsUI(nil)) | ||
| assert.False(t, clientSupportsUI(ctx, nil)) | ||
| }) | ||
| t.Run("nil session", func(t *testing.T) { | ||
| req := createMCPRequest(nil) | ||
| assert.False(t, clientSupportsUI(&req)) | ||
| assert.False(t, clientSupportsUI(ctx, &req)) | ||
| }) | ||
| } | ||
| func Test_clientSupportsUI_nilClientInfo(t *testing.T) { | ||
| func Test_clientSupportsUI_fromContext(t *testing.T) { | ||
| t.Parallel() | ||
| srv := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil) | ||
| st, _ := mcp.NewInMemoryTransports() | ||
| session, err := srv.Connect(context.Background(), st, &mcp.ServerSessionOptions{ | ||
| State: &mcp.ServerSessionState{ | ||
| InitializeParams: &mcp.InitializeParams{ | ||
| ClientInfo: nil, | ||
| }, | ||
| }, | ||
| t.Run("UI supported in context", func(t *testing.T) { | ||
| ctx := ghcontext.WithUISupport(context.Background(), true) | ||
| assert.True(t, clientSupportsUI(ctx, nil)) | ||
| }) | ||
| t.Run("UI not supported in context", func(t *testing.T) { | ||
| ctx := ghcontext.WithUISupport(context.Background(), false) | ||
| assert.False(t, clientSupportsUI(ctx, nil)) | ||
| }) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| t.Cleanup(func() { _ = session.Close() }) | ||
| req := mcp.CallToolRequest{Session: session} | ||
| assert.False(t, clientSupportsUI(&req)) | ||
| t.Run("context takes precedence over session", func(t *testing.T) { | ||
| ctx := ghcontext.WithUISupport(context.Background(), false) | ||
| caps := &mcp.ClientCapabilities{} | ||
| caps.AddExtension("io.modelcontextprotocol/ui", map[string]any{}) | ||
| req := createMCPRequestWithCapabilities(t, caps) | ||
| assert.False(t, clientSupportsUI(ctx, &req)) | ||
| }) | ||
| t.Run("no context or session", func(t *testing.T) { | ||
| assert.False(t, clientSupportsUI(context.Background(), nil)) | ||
| }) | ||
mattdholloway marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
CopilotAIFeb 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This helper now decides whether to add the UI extension by matching on
clientName(VS Code names). Since UI support detection has been refactored away from client-name matching, consider makingcreateMCPRequestWithSessionaccept an explicit*mcp.ClientCapabilities(or auiSupportedbool) so tests don’t implicitly rely on client-name heuristics and can set capabilities directly.