Files
Connor Peet a217a7f43a Add MCP App form deferral opt-out
Allow clients to keep MCP App views enabled while making form-backed write tools execute directly when explicitly configured.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-22 19:02:50 +02:00

133 lines
3.7 KiB
Go

package github
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()
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"},
})
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(ctx, nil))
})
t.Run("nil session", func(t *testing.T) {
req := createMCPRequest(nil)
assert.False(t, clientSupportsUI(ctx, &req))
})
}
func Test_clientSupportsUI_fromContext(t *testing.T) {
t.Parallel()
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))
})
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))
})
}
func Test_shouldDeferToForm_featureFlags(t *testing.T) {
t.Parallel()
ctx := ghcontext.WithUISupport(context.Background(), true)
args := map[string]any{"owner": "octocat"}
formParams := map[string]struct{}{"owner": {}}
tests := []struct {
name string
enabledFlags []string
want bool
}{
{
name: "MCP Apps enabled defers to form",
enabledFlags: []string{MCPAppsFeatureFlag},
want: true,
},
{
name: "form deferral disabled executes directly",
enabledFlags: []string{
MCPAppsFeatureFlag,
MCPAppsDisableFormDeferralFeatureFlag,
},
want: false,
},
{
name: "form deferral opt-out does not enable MCP Apps",
enabledFlags: []string{MCPAppsDisableFormDeferralFeatureFlag},
want: false,
},
{
name: "MCP Apps disabled executes directly",
want: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
deps := BaseDeps{featureChecker: featureCheckerFor(tc.enabledFlags...)}
assert.Equal(t, tc.want, shouldDeferToForm(ctx, deps, nil, args, formParams))
})
}
}