Skip to content

Commit f3f34ea

Browse files
committed
enhance client support checks for MCP Apps UI rendering
1 parent dc3ee11 commit f3f34ea

File tree

5 files changed

+64
-15
lines changed

5 files changed

+64
-15
lines changed

pkg/context/request.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,19 @@ func GetHeaderFeatures(ctx context.Context) []string {
113113
}
114114
return nil
115115
}
116+
117+
// clientNameCtxKey is a context key for the MCP client name
118+
type clientNameCtxKey struct{}
119+
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)
125+
}
126+
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 != ""
131+
}

pkg/github/issues.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1103,7 +1103,7 @@ Options are:
11031103
// to distinguish form submissions from LLM calls.
11041104
uiSubmitted, _ := OptionalParam[bool](args, "_ui_submitted")
11051105

1106-
if deps.GetFlags(ctx).InsidersMode && clientSupportsUI(req) && !uiSubmitted {
1106+
if deps.GetFlags(ctx).InsidersMode && clientSupportsUI(ctx, req) && !uiSubmitted {
11071107
if method == "update" {
11081108
issueNumber, numErr := RequiredInt(args, "issue_number")
11091109
if numErr != nil {

pkg/github/pullrequests.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ func CreatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo
557557
// to distinguish form submissions from LLM calls.
558558
uiSubmitted, _ := OptionalParam[bool](args, "_ui_submitted")
559559

560-
if deps.GetFlags(ctx).InsidersMode && clientSupportsUI(req) && !uiSubmitted {
560+
if deps.GetFlags(ctx).InsidersMode && clientSupportsUI(ctx, req) && !uiSubmitted {
561561
return utils.NewToolResultText(fmt.Sprintf("Ready to create a pull request in %s/%s. The user will review and confirm via the interactive form.", owner, repo)), nil, nil
562562
}
563563

pkg/github/ui_capability.go

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package github
22

3-
import "github.com/modelcontextprotocol/go-sdk/mcp"
3+
import (
4+
"context"
5+
6+
ghcontext "github.com/github/github-mcp-server/pkg/context"
7+
"github.com/modelcontextprotocol/go-sdk/mcp"
8+
)
49

510
// uiSupportedClients lists client names (from ClientInfo.Name) known to
611
// support MCP Apps UI rendering.
@@ -14,14 +19,21 @@ var uiSupportedClients = map[string]bool{
1419
}
1520

1621
// clientSupportsUI reports whether the MCP client that sent this request
17-
// supports MCP Apps UI rendering, based on its ClientInfo.Name.
18-
func clientSupportsUI(req *mcp.CallToolRequest) bool {
19-
if req == nil || req.Session == nil {
20-
return false
22+
// 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).
26+
func clientSupportsUI(ctx context.Context, req *mcp.CallToolRequest) bool {
27+
// Try go-sdk session first (works for stdio/stateful servers)
28+
if req != nil && req.Session != nil {
29+
params := req.Session.InitializeParams()
30+
if params != nil && params.ClientInfo != nil {
31+
return uiSupportedClients[params.ClientInfo.Name]
32+
}
2133
}
22-
params := req.Session.InitializeParams()
23-
if params == nil || params.ClientInfo == nil {
24-
return false
34+
// Fall back to context (works for HTTP/stateless servers)
35+
if name, ok := ghcontext.GetClientName(ctx); ok {
36+
return uiSupportedClients[name]
2537
}
26-
return uiSupportedClients[params.ClientInfo.Name]
38+
return false
2739
}

pkg/github/ui_capability_test.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import (
44
"context"
55
"testing"
66

7+
ghcontext "github.com/github/github-mcp-server/pkg/context"
78
"github.com/modelcontextprotocol/go-sdk/mcp"
89
"github.com/stretchr/testify/assert"
910
)
1011

1112
func Test_clientSupportsUI(t *testing.T) {
1213
t.Parallel()
14+
ctx := context.Background()
1315

1416
tests := []struct {
1517
name string
@@ -25,22 +27,23 @@ func Test_clientSupportsUI(t *testing.T) {
2527
for _, tt := range tests {
2628
t.Run(tt.name, func(t *testing.T) {
2729
req := createMCPRequestWithSession(t, tt.clientName, nil)
28-
assert.Equal(t, tt.want, clientSupportsUI(&req))
30+
assert.Equal(t, tt.want, clientSupportsUI(ctx, &req))
2931
})
3032
}
3133

3234
t.Run("nil request", func(t *testing.T) {
33-
assert.False(t, clientSupportsUI(nil))
35+
assert.False(t, clientSupportsUI(ctx, nil))
3436
})
3537

3638
t.Run("nil session", func(t *testing.T) {
3739
req := createMCPRequest(nil)
38-
assert.False(t, clientSupportsUI(&req))
40+
assert.False(t, clientSupportsUI(ctx, &req))
3941
})
4042
}
4143

4244
func Test_clientSupportsUI_nilClientInfo(t *testing.T) {
4345
t.Parallel()
46+
ctx := context.Background()
4447

4548
srv := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil)
4649
st, _ := mcp.NewInMemoryTransports()
@@ -57,5 +60,23 @@ func Test_clientSupportsUI_nilClientInfo(t *testing.T) {
5760
t.Cleanup(func() { _ = session.Close() })
5861

5962
req := mcp.CallToolRequest{Session: session}
60-
assert.False(t, clientSupportsUI(&req))
63+
assert.False(t, clientSupportsUI(ctx, &req))
64+
}
65+
66+
func Test_clientSupportsUI_fromContext(t *testing.T) {
67+
t.Parallel()
68+
69+
t.Run("supported client in context", func(t *testing.T) {
70+
ctx := ghcontext.WithClientName(context.Background(), "Visual Studio Code - Insiders")
71+
assert.True(t, clientSupportsUI(ctx, nil))
72+
})
73+
74+
t.Run("unsupported client in context", func(t *testing.T) {
75+
ctx := ghcontext.WithClientName(context.Background(), "some-other-client")
76+
assert.False(t, clientSupportsUI(ctx, nil))
77+
})
78+
79+
t.Run("no client in context or session", func(t *testing.T) {
80+
assert.False(t, clientSupportsUI(context.Background(), nil))
81+
})
6182
}

0 commit comments

Comments
 (0)