Skip to content

Commit dfbd526

Browse files
committed
return to prev non ui check
1 parent baf3331 commit dfbd526

File tree

6 files changed

+93
-92
lines changed

6 files changed

+93
-92
lines changed

internal/ghmcp/server.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,15 @@ func addUserAgentsMiddleware(cfg github.MCPServerConfig, restClient *gogithub.Cl
347347
}
348348

349349
message := initializeRequest
350+
351+
// Debug log client capabilities for MCP Apps support diagnosis
352+
if message.Params.Capabilities != nil {
353+
slog.Info("client capabilities",
354+
"experimental", message.Params.Capabilities.Experimental,
355+
"client", message.Params.ClientInfo.Name,
356+
)
357+
}
358+
350359
userAgent := fmt.Sprintf(
351360
"github-mcp-server/%s (%s/%s)",
352361
cfg.Version,

pkg/github/issues.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,7 +1086,7 @@ Options are:
10861086
},
10871087
},
10881088
[]scopes.Scope{scopes.Repo},
1089-
func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
1089+
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
10901090
method, err := RequiredParam[string](args, "method")
10911091
if err != nil {
10921092
return utils.NewToolResultError(err.Error()), nil, nil
@@ -1101,22 +1101,21 @@ Options are:
11011101
return utils.NewToolResultError(err.Error()), nil, nil
11021102
}
11031103

1104-
// When insiders mode is enabled and the client supports MCP Apps UI,
1105-
// check if this is a UI form submission. The UI sends _ui_submitted=true
1106-
// to distinguish form submissions from LLM calls. Without this flag,
1107-
// show the UI so the user can review/edit before submitting.
1108-
// Clients without MCP Apps support skip this gate and execute directly.
1104+
// When insiders mode is enabled, check if this is a UI form submission.
1105+
// The UI sends _ui_submitted=true to distinguish form submissions from LLM calls.
1106+
// Without this flag, return a message so the client can show a form.
1107+
// If no form is displayed, the model should call again with _ui_submitted=true.
11091108
uiSubmitted, _ := OptionalParam[bool](args, "_ui_submitted")
11101109

1111-
if deps.GetFlags(ctx).InsidersMode && clientSupportsUI(req) && !uiSubmitted {
1110+
if deps.GetFlags(ctx).InsidersMode && !uiSubmitted {
11121111
if method == "update" {
11131112
issueNumber, numErr := RequiredInt(args, "issue_number")
11141113
if numErr != nil {
11151114
return utils.NewToolResultError("issue_number is required for update method"), nil, nil
11161115
}
1117-
return utils.NewToolResultText(fmt.Sprintf("Ready to update issue #%d in %s/%s. The interactive form will be displayed.", issueNumber, owner, repo)), nil, nil
1116+
return utils.NewToolResultText(fmt.Sprintf("Ready to update issue #%d in %s/%s. The interactive form will be displayed. If no form appears, call this tool again with _ui_submitted set to true to proceed.", issueNumber, owner, repo)), nil, nil
11181117
}
1119-
return utils.NewToolResultText(fmt.Sprintf("Ready to create an issue in %s/%s. The interactive form will be displayed.", owner, repo)), nil, nil
1118+
return utils.NewToolResultText(fmt.Sprintf("Ready to create an issue in %s/%s. The interactive form will be displayed. If no form appears, call this tool again with _ui_submitted set to true to proceed.", owner, repo)), nil, nil
11201119
}
11211120

11221121
title, err := OptionalParam[string](args, "title")

pkg/github/issues_test.go

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -933,11 +933,10 @@ func Test_CreateIssue(t *testing.T) {
933933
}
934934
}
935935

936-
// Test_IssueWrite_InsidersMode_NoUISupport verifies that when insiders mode is
937-
// enabled but the client does not support MCP Apps UI (no session or no UI
938-
// capability), the tool executes directly instead of returning a "Ready to..."
939-
// form message.
940-
func Test_IssueWrite_InsidersMode_NoUISupport(t *testing.T) {
936+
// Test_IssueWrite_InsidersMode_UIGate verifies the insiders mode UI gate
937+
// behavior: the first call returns a form-ready message, and a subsequent
938+
// call with _ui_submitted=true executes the action.
939+
func Test_IssueWrite_InsidersMode_UIGate(t *testing.T) {
941940
t.Parallel()
942941

943942
mockIssue := &github.Issue{
@@ -959,22 +958,37 @@ func Test_IssueWrite_InsidersMode_NoUISupport(t *testing.T) {
959958
}
960959
handler := serverTool.Handler(deps)
961960

962-
// Request has no session — simulates a client without MCP Apps support
963-
request := createMCPRequest(map[string]interface{}{
964-
"method": "create",
965-
"owner": "owner",
966-
"repo": "repo",
967-
"title": "Test",
961+
t.Run("without _ui_submitted returns form message", func(t *testing.T) {
962+
request := createMCPRequest(map[string]interface{}{
963+
"method": "create",
964+
"owner": "owner",
965+
"repo": "repo",
966+
"title": "Test",
967+
})
968+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
969+
require.NoError(t, err)
970+
971+
textContent := getTextResult(t, result)
972+
assert.Contains(t, textContent.Text, "Ready to create an issue")
973+
assert.Contains(t, textContent.Text, "_ui_submitted",
974+
"message should instruct model to retry with _ui_submitted")
975+
})
976+
977+
t.Run("with _ui_submitted executes directly", func(t *testing.T) {
978+
request := createMCPRequest(map[string]interface{}{
979+
"method": "create",
980+
"owner": "owner",
981+
"repo": "repo",
982+
"title": "Test",
983+
"_ui_submitted": true,
984+
})
985+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
986+
require.NoError(t, err)
987+
988+
textContent := getTextResult(t, result)
989+
assert.Contains(t, textContent.Text, "https://github.com/owner/repo/issues/1",
990+
"tool should return the created issue URL")
968991
})
969-
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
970-
require.NoError(t, err)
971-
require.NotNil(t, result)
972-
973-
textContent := getTextResult(t, result)
974-
assert.NotContains(t, textContent.Text, "interactive form will be displayed",
975-
"tool should execute directly when client has no MCP Apps support")
976-
assert.Contains(t, textContent.Text, "https://github.com/owner/repo/issues/1",
977-
"tool should return the created issue URL")
978992
}
979993

980994
func Test_ListIssues(t *testing.T) {

pkg/github/pullrequests.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ func CreatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo
545545
},
546546
},
547547
[]scopes.Scope{scopes.Repo},
548-
func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
548+
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
549549
owner, err := RequiredParam[string](args, "owner")
550550
if err != nil {
551551
return utils.NewToolResultError(err.Error()), nil, nil
@@ -555,14 +555,13 @@ func CreatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo
555555
return utils.NewToolResultError(err.Error()), nil, nil
556556
}
557557

558-
// When insiders mode is enabled and the client supports MCP Apps UI,
559-
// check if this is a UI form submission. The UI sends _ui_submitted=true
560-
// to distinguish form submissions from LLM calls.
561-
// Clients without MCP Apps support skip this gate and execute directly.
558+
// When insiders mode is enabled, check if this is a UI form submission.
559+
// The UI sends _ui_submitted=true to distinguish form submissions from LLM calls.
560+
// If no form is displayed, the model should call again with _ui_submitted=true.
562561
uiSubmitted, _ := OptionalParam[bool](args, "_ui_submitted")
563562

564-
if deps.GetFlags(ctx).InsidersMode && clientSupportsUI(req) && !uiSubmitted {
565-
return utils.NewToolResultText(fmt.Sprintf("Ready to create a pull request in %s/%s. The interactive form will be displayed.", owner, repo)), nil, nil
563+
if deps.GetFlags(ctx).InsidersMode && !uiSubmitted {
564+
return utils.NewToolResultText(fmt.Sprintf("Ready to create a pull request in %s/%s. The interactive form will be displayed. If no form appears, call this tool again with _ui_submitted set to true to proceed.", owner, repo)), nil, nil
566565
}
567566

568567
// When creating PR, title/head/base are required

pkg/github/pullrequests_test.go

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2172,10 +2172,10 @@ func Test_CreatePullRequest(t *testing.T) {
21722172
}
21732173
}
21742174

2175-
// Test_CreatePullRequest_InsidersMode_NoUISupport verifies that when insiders
2176-
// mode is enabled but the client does not support MCP Apps UI, the tool
2177-
// executes directly instead of returning a "Ready to..." form message.
2178-
func Test_CreatePullRequest_InsidersMode_NoUISupport(t *testing.T) {
2175+
// Test_CreatePullRequest_InsidersMode_UIGate verifies the insiders mode UI gate
2176+
// behavior: the first call returns a form-ready message, and a subsequent
2177+
// call with _ui_submitted=true executes the action.
2178+
func Test_CreatePullRequest_InsidersMode_UIGate(t *testing.T) {
21792179
t.Parallel()
21802180

21812181
mockPR := &github.PullRequest{
@@ -2200,23 +2200,39 @@ func Test_CreatePullRequest_InsidersMode_NoUISupport(t *testing.T) {
22002200
}
22012201
handler := serverTool.Handler(deps)
22022202

2203-
// Request has no session — simulates a client without MCP Apps support
2204-
request := createMCPRequest(map[string]interface{}{
2205-
"owner": "owner",
2206-
"repo": "repo",
2207-
"title": "Test PR",
2208-
"head": "feature",
2209-
"base": "main",
2203+
t.Run("without _ui_submitted returns form message", func(t *testing.T) {
2204+
request := createMCPRequest(map[string]interface{}{
2205+
"owner": "owner",
2206+
"repo": "repo",
2207+
"title": "Test PR",
2208+
"head": "feature",
2209+
"base": "main",
2210+
})
2211+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
2212+
require.NoError(t, err)
2213+
2214+
textContent := getTextResult(t, result)
2215+
assert.Contains(t, textContent.Text, "Ready to create a pull request")
2216+
assert.Contains(t, textContent.Text, "_ui_submitted",
2217+
"message should instruct model to retry with _ui_submitted")
2218+
})
2219+
2220+
t.Run("with _ui_submitted executes directly", func(t *testing.T) {
2221+
request := createMCPRequest(map[string]interface{}{
2222+
"owner": "owner",
2223+
"repo": "repo",
2224+
"title": "Test PR",
2225+
"head": "feature",
2226+
"base": "main",
2227+
"_ui_submitted": true,
2228+
})
2229+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
2230+
require.NoError(t, err)
2231+
2232+
textContent := getTextResult(t, result)
2233+
assert.Contains(t, textContent.Text, "https://github.com/owner/repo/pull/42",
2234+
"tool should return the created PR URL")
22102235
})
2211-
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
2212-
require.NoError(t, err)
2213-
require.NotNil(t, result)
2214-
2215-
textContent := getTextResult(t, result)
2216-
assert.NotContains(t, textContent.Text, "interactive form will be displayed",
2217-
"tool should execute directly when client has no MCP Apps support")
2218-
assert.Contains(t, textContent.Text, "https://github.com/owner/repo/pull/42",
2219-
"tool should return the created PR URL")
22202236
}
22212237

22222238
func TestCreateAndSubmitPullRequestReview(t *testing.T) {

pkg/github/ui_capability_test.go

Lines changed: 0 additions & 36 deletions
This file was deleted.

0 commit comments

Comments
 (0)