Skip to content

Commit 4031c58

Browse files
committed
update dependencies and enhance MCP Apps UI support handling
1 parent f3f34ea commit 4031c58

File tree

5 files changed

+91
-87
lines changed

5 files changed

+91
-87
lines changed

go.mod

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ require (
1010
github.com/josephburnett/jd/v2 v2.4.0
1111
github.com/lithammer/fuzzysearch v1.1.8
1212
github.com/microcosm-cc/bluemonday v1.0.27
13-
github.com/modelcontextprotocol/go-sdk v1.3.0
13+
github.com/modelcontextprotocol/go-sdk v1.3.1-0.20260220105450-b17143f71798
1414
github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021
1515
github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7
1616
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466
@@ -35,6 +35,8 @@ require (
3535
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
3636
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
3737
github.com/sagikazarmark/locafero v0.11.0 // indirect
38+
github.com/segmentio/asm v1.1.3 // indirect
39+
github.com/segmentio/encoding v0.5.3 // indirect
3840
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
3941
github.com/spf13/afero v1.15.0 // indirect
4042
github.com/spf13/cast v1.10.0 // indirect
@@ -43,8 +45,8 @@ require (
4345
go.yaml.in/yaml/v3 v3.0.4 // indirect
4446
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
4547
golang.org/x/net v0.38.0 // indirect
46-
golang.org/x/oauth2 v0.30.0 // indirect
47-
golang.org/x/sys v0.31.0 // indirect
48+
golang.org/x/oauth2 v0.34.0 // indirect
49+
golang.org/x/sys v0.40.0 // indirect
4850
golang.org/x/text v0.28.0 // indirect
4951
gopkg.in/yaml.v3 v3.0.1 // indirect
5052
)

go.sum

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr
1515
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
1616
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
1717
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
18-
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
19-
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
18+
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
19+
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
2020
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
2121
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
2222
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
@@ -44,8 +44,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
4444
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
4545
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
4646
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
47-
github.com/modelcontextprotocol/go-sdk v1.3.0 h1:gMfZkv3DzQF5q/DcQePo5rahEY+sguyPfXDfNBcT0Zs=
48-
github.com/modelcontextprotocol/go-sdk v1.3.0/go.mod h1:AnQ//Qc6+4nIyyrB4cxBU7UW9VibK4iOZBeyP/rF1IE=
47+
github.com/modelcontextprotocol/go-sdk v1.3.1-0.20260220105450-b17143f71798 h1:ogb5ErmcnxZgfaTeVZnKEMrwdHDpJ3yln5EhCIPcTlY=
48+
github.com/modelcontextprotocol/go-sdk v1.3.1-0.20260220105450-b17143f71798/go.mod h1:Nxc2n+n/GdCebUaqCOhTetptS17SXXNu9IfNTaLDi1E=
4949
github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021 h1:31Y+Yu373ymebRdJN1cWLLooHH8xAr0MhKTEJGV/87g=
5050
github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021/go.mod h1:WERUkUryfUWlrHnFSO/BEUZ+7Ns8aZy7iVOGewxKzcc=
5151
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
@@ -57,6 +57,10 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7
5757
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
5858
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
5959
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
60+
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
61+
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
62+
github.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQccx6w=
63+
github.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
6064
github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 h1:cYCy18SHPKRkvclm+pWm1Lk4YrREb4IOIb/YdFO0p2M=
6165
github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8=
6266
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0=
@@ -97,8 +101,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
97101
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
98102
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
99103
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
100-
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
101-
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
104+
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
105+
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
102106
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
103107
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
104108
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -108,8 +112,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
108112
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
109113
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
110114
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
111-
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
112-
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
115+
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
116+
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
113117
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
114118
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
115119
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -124,8 +128,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
124128
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
125129
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
126130
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
127-
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
128-
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
131+
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
132+
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
129133
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
130134
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
131135
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

pkg/context/request.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,18 @@ func GetHeaderFeatures(ctx context.Context) []string {
114114
return nil
115115
}
116116

117-
// clientNameCtxKey is a context key for the MCP client name
118-
type clientNameCtxKey struct{}
117+
// uiSupportCtxKey is a context key for MCP Apps UI support
118+
type uiSupportCtxKey struct{}
119119

120-
// WithClientName stores the MCP client name in the context.
121-
// This is used by HTTP/stateless servers where req.Session may not have
122-
// InitializeParams available on every request.
123-
func WithClientName(ctx context.Context, name string) context.Context {
124-
return context.WithValue(ctx, clientNameCtxKey{}, name)
120+
// WithUISupport stores whether the client supports MCP Apps UI in the context.
121+
// This is used by HTTP/stateless servers where the go-sdk session may not
122+
// persist client capabilities across requests.
123+
func WithUISupport(ctx context.Context, supported bool) context.Context {
124+
return context.WithValue(ctx, uiSupportCtxKey{}, supported)
125125
}
126126

127-
// GetClientName retrieves the MCP client name from the context.
128-
func GetClientName(ctx context.Context) (string, bool) {
129-
name, ok := ctx.Value(clientNameCtxKey{}).(string)
130-
return name, ok && name != ""
127+
// HasUISupport retrieves the MCP Apps UI support flag from context.
128+
func HasUISupport(ctx context.Context) (supported bool, ok bool) {
129+
v, ok := ctx.Value(uiSupportCtxKey{}).(bool)
130+
return v, ok
131131
}

pkg/github/ui_capability.go

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,26 @@ import (
77
"github.com/modelcontextprotocol/go-sdk/mcp"
88
)
99

10-
// uiSupportedClients lists client names (from ClientInfo.Name) known to
11-
// support MCP Apps UI rendering.
12-
//
13-
// This is a temporary workaround until the Go SDK adds an Extensions field
14-
// to ClientCapabilities (see https://github.com/modelcontextprotocol/go-sdk/issues/777).
15-
// Once that lands, detection should use capabilities.extensions instead.
16-
var uiSupportedClients = map[string]bool{
17-
"Visual Studio Code - Insiders": true,
18-
"Visual Studio Code": true,
19-
}
10+
// mcpAppsExtensionKey is the capability extension key that clients use to
11+
// advertise MCP Apps UI support.
12+
const mcpAppsExtensionKey = "io.modelcontextprotocol/ui"
2013

2114
// clientSupportsUI reports whether the MCP client that sent this request
2215
// supports MCP Apps UI rendering.
23-
// It checks the go-sdk Session first (for stdio/stateful servers), then
24-
// falls back to the request context (for HTTP/stateless servers where
25-
// the session may not persist InitializeParams across requests).
16+
// It checks the context first (set by HTTP/stateless servers from stored
17+
// session capabilities), then falls back to the go-sdk Session (for stdio).
2618
func clientSupportsUI(ctx context.Context, req *mcp.CallToolRequest) bool {
27-
// Try go-sdk session first (works for stdio/stateful servers)
19+
// Check context first (works for HTTP/stateless servers)
20+
if supported, ok := ghcontext.HasUISupport(ctx); ok {
21+
return supported
22+
}
23+
// Fall back to go-sdk session (works for stdio/stateful servers)
2824
if req != nil && req.Session != nil {
2925
params := req.Session.InitializeParams()
30-
if params != nil && params.ClientInfo != nil {
31-
return uiSupportedClients[params.ClientInfo.Name]
26+
if params != nil && params.Capabilities != nil {
27+
_, hasUI := params.Capabilities.Extensions[mcpAppsExtensionKey]
28+
return hasUI
3229
}
3330
}
34-
// Fall back to context (works for HTTP/stateless servers)
35-
if name, ok := ghcontext.GetClientName(ctx); ok {
36-
return uiSupportedClients[name]
37-
}
3831
return false
3932
}

pkg/github/ui_capability_test.go

Lines changed: 48 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,48 @@ import (
77
ghcontext "github.com/github/github-mcp-server/pkg/context"
88
"github.com/modelcontextprotocol/go-sdk/mcp"
99
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
1011
)
1112

13+
func createMCPRequestWithCapabilities(t *testing.T, caps *mcp.ClientCapabilities) mcp.CallToolRequest {
14+
t.Helper()
15+
srv := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil)
16+
st, _ := mcp.NewInMemoryTransports()
17+
session, err := srv.Connect(context.Background(), st, &mcp.ServerSessionOptions{
18+
State: &mcp.ServerSessionState{
19+
InitializeParams: &mcp.InitializeParams{
20+
ClientInfo: &mcp.Implementation{Name: "test-client"},
21+
Capabilities: caps,
22+
},
23+
},
24+
})
25+
require.NoError(t, err)
26+
t.Cleanup(func() { _ = session.Close() })
27+
return mcp.CallToolRequest{Session: session}
28+
}
29+
1230
func Test_clientSupportsUI(t *testing.T) {
1331
t.Parallel()
1432
ctx := context.Background()
1533

16-
tests := []struct {
17-
name string
18-
clientName string
19-
want bool
20-
}{
21-
{name: "VS Code Insiders", clientName: "Visual Studio Code - Insiders", want: true},
22-
{name: "VS Code Stable", clientName: "Visual Studio Code", want: true},
23-
{name: "unknown client", clientName: "some-other-client", want: false},
24-
{name: "empty client name", clientName: "", want: false},
25-
}
26-
27-
for _, tt := range tests {
28-
t.Run(tt.name, func(t *testing.T) {
29-
req := createMCPRequestWithSession(t, tt.clientName, nil)
30-
assert.Equal(t, tt.want, clientSupportsUI(ctx, &req))
34+
t.Run("client with UI extension", func(t *testing.T) {
35+
caps := &mcp.ClientCapabilities{}
36+
caps.AddExtension("io.modelcontextprotocol/ui", map[string]any{
37+
"mimeTypes": []string{"text/html;profile=mcp-app"},
3138
})
32-
}
39+
req := createMCPRequestWithCapabilities(t, caps)
40+
assert.True(t, clientSupportsUI(ctx, &req))
41+
})
42+
43+
t.Run("client without UI extension", func(t *testing.T) {
44+
req := createMCPRequestWithCapabilities(t, &mcp.ClientCapabilities{})
45+
assert.False(t, clientSupportsUI(ctx, &req))
46+
})
47+
48+
t.Run("client with nil capabilities", func(t *testing.T) {
49+
req := createMCPRequestWithCapabilities(t, nil)
50+
assert.False(t, clientSupportsUI(ctx, &req))
51+
})
3352

3453
t.Run("nil request", func(t *testing.T) {
3554
assert.False(t, clientSupportsUI(ctx, nil))
@@ -41,42 +60,28 @@ func Test_clientSupportsUI(t *testing.T) {
4160
})
4261
}
4362

44-
func Test_clientSupportsUI_nilClientInfo(t *testing.T) {
45-
t.Parallel()
46-
ctx := context.Background()
47-
48-
srv := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil)
49-
st, _ := mcp.NewInMemoryTransports()
50-
session, err := srv.Connect(context.Background(), st, &mcp.ServerSessionOptions{
51-
State: &mcp.ServerSessionState{
52-
InitializeParams: &mcp.InitializeParams{
53-
ClientInfo: nil,
54-
},
55-
},
56-
})
57-
if err != nil {
58-
t.Fatal(err)
59-
}
60-
t.Cleanup(func() { _ = session.Close() })
61-
62-
req := mcp.CallToolRequest{Session: session}
63-
assert.False(t, clientSupportsUI(ctx, &req))
64-
}
65-
6663
func Test_clientSupportsUI_fromContext(t *testing.T) {
6764
t.Parallel()
6865

69-
t.Run("supported client in context", func(t *testing.T) {
70-
ctx := ghcontext.WithClientName(context.Background(), "Visual Studio Code - Insiders")
66+
t.Run("UI supported in context", func(t *testing.T) {
67+
ctx := ghcontext.WithUISupport(context.Background(), true)
7168
assert.True(t, clientSupportsUI(ctx, nil))
7269
})
7370

74-
t.Run("unsupported client in context", func(t *testing.T) {
75-
ctx := ghcontext.WithClientName(context.Background(), "some-other-client")
71+
t.Run("UI not supported in context", func(t *testing.T) {
72+
ctx := ghcontext.WithUISupport(context.Background(), false)
7673
assert.False(t, clientSupportsUI(ctx, nil))
7774
})
7875

79-
t.Run("no client in context or session", func(t *testing.T) {
76+
t.Run("context takes precedence over session", func(t *testing.T) {
77+
ctx := ghcontext.WithUISupport(context.Background(), false)
78+
caps := &mcp.ClientCapabilities{}
79+
caps.AddExtension("io.modelcontextprotocol/ui", map[string]any{})
80+
req := createMCPRequestWithCapabilities(t, caps)
81+
assert.False(t, clientSupportsUI(ctx, &req))
82+
})
83+
84+
t.Run("no context or session", func(t *testing.T) {
8085
assert.False(t, clientSupportsUI(context.Background(), nil))
8186
})
8287
}

0 commit comments

Comments
 (0)