diff --git a/pkg/github/repository_resource_completions.go b/pkg/github/repository_resource_completions.go index 9f4edc7b40..54c6e9c364 100644 --- a/pkg/github/repository_resource_completions.go +++ b/pkg/github/repository_resource_completions.go @@ -28,7 +28,9 @@ var RepositoryResourceArgumentResolvers = map[string]CompleteHandler{ func RepositoryResourceCompletionHandler(getClient GetClientFn) func(ctx context.Context, req *mcp.CompleteRequest) (*mcp.CompleteResult, error) { return func(ctx context.Context, req *mcp.CompleteRequest) (*mcp.CompleteResult, error) { if req.Params.Ref.Type != "ref/resource" { - return nil, nil // Not a resource completion + return &mcp.CompleteResult{ + Completion: mcp.CompletionResultDetails{Values: []string{}}, + }, nil } argName := req.Params.Argument.Name @@ -57,6 +59,9 @@ func RepositoryResourceCompletionHandler(getClient GetClientFn) func(ctx context if err != nil { return nil, err } + if values == nil { + values = []string{} + } if len(values) > 100 { values = values[:100] } diff --git a/pkg/github/repository_resource_completions_test.go b/pkg/github/repository_resource_completions_test.go index 7d20d1e1db..c7936efd98 100644 --- a/pkg/github/repository_resource_completions_test.go +++ b/pkg/github/repository_resource_completions_test.go @@ -2,6 +2,7 @@ package github import ( "context" + "encoding/json" "errors" "fmt" "testing" @@ -20,16 +21,18 @@ func TestRepositoryResourceCompletionHandler(t *testing.T) { wantErr bool }{ { - name: "non-resource completion request", + name: "prompt completion request", request: &mcp.CompleteRequest{ Params: &mcp.CompleteParams{ Ref: &mcp.CompleteReference{ - Type: "something-else", + Type: "ref/prompt", }, }, }, - expected: nil, - wantErr: false, + expected: &mcp.CompleteResult{ + Completion: mcp.CompletionResultDetails{Values: []string{}}, + }, + wantErr: false, }, { name: "invalid ref type", @@ -40,8 +43,10 @@ func TestRepositoryResourceCompletionHandler(t *testing.T) { }, }, }, - expected: nil, - wantErr: false, + expected: &mcp.CompleteResult{ + Completion: mcp.CompletionResultDetails{Values: []string{}}, + }, + wantErr: false, }, { name: "unknown argument", @@ -65,6 +70,7 @@ func TestRepositoryResourceCompletionHandler(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { getClient := func(_ context.Context) (*github.Client, error) { + assert.Equal(t, "ref/resource", tt.request.Params.Ref.Type, "non-resource completions must not fetch a client") return &github.Client{}, nil } @@ -77,10 +83,45 @@ func TestRepositoryResourceCompletionHandler(t *testing.T) { require.NoError(t, err) } assert.Equal(t, tt.expected, result) + if !tt.wantErr { + require.NotNil(t, result) + data, err := json.Marshal(result) + require.NoError(t, err) + assert.JSONEq(t, `{"completion":{"values":[]}}`, string(data)) + } }) } } +func TestRepositoryResourceCompletionHandler_EmptyResults(t *testing.T) { + const argumentName = "owner" + originalResolver := RepositoryResourceArgumentResolvers[argumentName] + RepositoryResourceArgumentResolvers[argumentName] = func(context.Context, *github.Client, map[string]string, string) ([]string, error) { + return nil, nil + } + t.Cleanup(func() { + RepositoryResourceArgumentResolvers[argumentName] = originalResolver + }) + + handler := RepositoryResourceCompletionHandler(func(context.Context) (*github.Client, error) { + return &github.Client{}, nil + }) + result, err := handler(t.Context(), &mcp.CompleteRequest{ + Params: &mcp.CompleteParams{ + Ref: &mcp.CompleteReference{Type: "ref/resource"}, + Argument: mcp.CompleteParamsArgument{Name: argumentName}, + }, + }) + + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, []string{}, result.Completion.Values) + assert.Equal(t, 0, result.Completion.Total) + data, err := json.Marshal(result) + require.NoError(t, err) + assert.JSONEq(t, `{"completion":{"values":[]}}`, string(data)) +} + func TestRepositoryResourceCompletionHandler_GetClientError(t *testing.T) { getClient := func(_ context.Context) (*github.Client, error) { return nil, errors.New("client error") diff --git a/pkg/github/server.go b/pkg/github/server.go index de82c8be3f..78a66f2582 100644 --- a/pkg/github/server.go +++ b/pkg/github/server.go @@ -212,7 +212,9 @@ func CompletionsHandler(getClient GetClientFn) func(ctx context.Context, req *mc } return nil, fmt.Errorf("unsupported resource URI: %s", req.Params.Ref.URI) case "ref/prompt": - return nil, nil + return &mcp.CompleteResult{ + Completion: mcp.CompletionResultDetails{Values: []string{}}, + }, nil default: return nil, fmt.Errorf("unsupported ref type: %s", req.Params.Ref.Type) } diff --git a/pkg/github/server_test.go b/pkg/github/server_test.go index 8fe76965b0..df51fcd802 100644 --- a/pkg/github/server_test.go +++ b/pkg/github/server_test.go @@ -528,3 +528,58 @@ func TestCompletionsHandler_RejectsMissingRef(t *testing.T) { }) } } + +func TestCompletionsHandler(t *testing.T) { + clientErr := errors.New("client error") + tests := []struct { + name string + ref *mcp.CompleteReference + wantErr string + wantClient bool + }{ + { + name: "prompt completion", + ref: &mcp.CompleteReference{Type: "ref/prompt"}, + }, + { + name: "repository resource completion", + ref: &mcp.CompleteReference{Type: "ref/resource", URI: "repo://{owner}/{repo}/contents/{path}"}, + wantErr: clientErr.Error(), + wantClient: true, + }, + { + name: "unsupported resource URI", + ref: &mcp.CompleteReference{Type: "ref/resource", URI: "unsupported://resource"}, + wantErr: "unsupported resource URI: unsupported://resource", + }, + { + name: "unsupported ref type", + ref: &mcp.CompleteReference{Type: "unsupported"}, + wantErr: "unsupported ref type: unsupported", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + clientCalled := false + handler := CompletionsHandler(func(context.Context) (*gogithub.Client, error) { + clientCalled = true + return nil, clientErr + }) + result, err := handler(t.Context(), &mcp.CompleteRequest{ + Params: &mcp.CompleteParams{Ref: tt.ref}, + }) + assert.Equal(t, tt.wantClient, clientCalled) + if tt.wantErr != "" { + require.EqualError(t, err, tt.wantErr) + assert.Nil(t, result) + return + } + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, []string{}, result.Completion.Values) + data, err := json.Marshal(result) + require.NoError(t, err) + assert.JSONEq(t, `{"completion":{"values":[]}}`, string(data)) + }) + } +}