Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/ghmcp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func createGitHubClients(cfg github.MCPServerConfig, apiHost utils.APIHostResolv
if cfg.RepoAccessTTL != nil {
opts = append(opts, lockdown.WithTTL(*cfg.RepoAccessTTL))
}
repoAccessCache = lockdown.GetInstance(gqlClient, opts...)
repoAccessCache = lockdown.GetInstance(gqlClient, restClient, opts...)
}

return &githubClients{
Expand Down
7 changes: 6 additions & 1 deletion pkg/github/dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,8 +386,13 @@ func (d *RequestDeps) GetRepoAccessCache(ctx context.Context) (*lockdown.RepoAcc
return nil, err
}

restClient, err := d.GetClient(ctx)
if err != nil {
return nil, err
}

// Create repo access cache
instance := lockdown.GetInstance(gqlClient, d.RepoAccessOpts...)
instance := lockdown.GetInstance(gqlClient, restClient, d.RepoAccessOpts...)
return instance, nil
}

Expand Down
140 changes: 28 additions & 112 deletions pkg/github/issues_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (

"github.com/github/github-mcp-server/internal/githubv4mock"
"github.com/github/github-mcp-server/internal/toolsnaps"
"github.com/github/github-mcp-server/pkg/lockdown"
"github.com/github/github-mcp-server/pkg/translations"
"github.com/google/go-github/v82/github"
"github.com/google/jsonschema-go/jsonschema"
Expand All @@ -23,17 +22,14 @@ import (
)

var defaultGQLClient *githubv4.Client = githubv4.NewClient(newRepoAccessHTTPClient())
var repoAccessCache *lockdown.RepoAccessCache = stubRepoAccessCache(defaultGQLClient, 15*time.Minute)

type repoAccessKey struct {
owner string
repo string
username string
owner string
repo string
}

type repoAccessValue struct {
isPrivate bool
permission string
isPrivate bool
}

type repoAccessMockTransport struct {
Expand All @@ -42,8 +38,8 @@ type repoAccessMockTransport struct {

func newRepoAccessHTTPClient() *http.Client {
responses := map[repoAccessKey]repoAccessValue{
{owner: "owner2", repo: "repo2", username: "testuser2"}: {isPrivate: true},
{owner: "owner", repo: "repo", username: "testuser"}: {isPrivate: false, permission: "READ"},
{owner: "owner2", repo: "repo2"}: {isPrivate: true},
{owner: "owner", repo: "repo"}: {isPrivate: false},
}

return &http.Client{Transport: &repoAccessMockTransport{responses: responses}}
Expand All @@ -66,30 +62,19 @@ func (rt *repoAccessMockTransport) RoundTrip(req *http.Request) (*http.Response,

owner := toString(payload.Variables["owner"])
repo := toString(payload.Variables["name"])
username := toString(payload.Variables["username"])

value, ok := rt.responses[repoAccessKey{owner: owner, repo: repo, username: username}]
value, ok := rt.responses[repoAccessKey{owner: owner, repo: repo}]
if !ok {
value = repoAccessValue{isPrivate: false, permission: "WRITE"}
}

edges := []any{}
if value.permission != "" {
edges = append(edges, map[string]any{
"permission": value.permission,
"node": map[string]any{
"login": username,
},
})
value = repoAccessValue{isPrivate: false}
}

responseBody, err := json.Marshal(map[string]any{
"data": map[string]any{
"viewer": map[string]any{
"login": "test-viewer",
},
"repository": map[string]any{
"isPrivate": value.isPrivate,
"collaborators": map[string]any{
"edges": edges,
},
},
},
})
Expand Down Expand Up @@ -170,13 +155,13 @@ func Test_GetIssue(t *testing.T) {
tests := []struct {
name string
mockedClient *http.Client
gqlHTTPClient *http.Client
requestArgs map[string]any
expectHandlerError bool
expectResultError bool
expectedIssue *github.Issue
expectedErrMsg string
lockdownEnabled bool
restPermission string
}{
{
name: "successful issue retrieval",
Expand Down Expand Up @@ -210,36 +195,6 @@ func Test_GetIssue(t *testing.T) {
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
GetReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssue2),
}),
gqlHTTPClient: githubv4mock.NewMockedHTTPClient(
githubv4mock.NewQueryMatcher(
struct {
Repository struct {
IsPrivate githubv4.Boolean
Collaborators struct {
Edges []struct {
Permission githubv4.String
Node struct {
Login githubv4.String
}
}
} `graphql:"collaborators(query: $username, first: 1)"`
} `graphql:"repository(owner: $owner, name: $name)"`
}{},
map[string]any{
"owner": githubv4.String("owner2"),
"name": githubv4.String("repo2"),
"username": githubv4.String("testuser2"),
},
githubv4mock.DataResponse(map[string]any{
"repository": map[string]any{
"isPrivate": true,
"collaborators": map[string]any{
"edges": []any{},
},
},
}),
),
),
requestArgs: map[string]any{
"method": "get",
"owner": "owner2",
Expand All @@ -248,49 +203,13 @@ func Test_GetIssue(t *testing.T) {
},
expectedIssue: mockIssue2,
lockdownEnabled: true,
restPermission: "none",
},
{
name: "lockdown enabled - user lacks push access",
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
GetReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssue),
}),
gqlHTTPClient: githubv4mock.NewMockedHTTPClient(
githubv4mock.NewQueryMatcher(
struct {
Repository struct {
IsPrivate githubv4.Boolean
Collaborators struct {
Edges []struct {
Permission githubv4.String
Node struct {
Login githubv4.String
}
}
} `graphql:"collaborators(query: $username, first: 1)"`
} `graphql:"repository(owner: $owner, name: $name)"`
}{},
map[string]any{
"owner": githubv4.String("owner"),
"name": githubv4.String("repo"),
"username": githubv4.String("testuser"),
},
githubv4mock.DataResponse(map[string]any{
"repository": map[string]any{
"isPrivate": false,
"collaborators": map[string]any{
"edges": []any{
map[string]any{
"permission": "READ",
"node": map[string]any{
"login": "testuser",
},
},
},
},
},
}),
),
),
requestArgs: map[string]any{
"method": "get",
"owner": "owner",
Expand All @@ -300,26 +219,24 @@ func Test_GetIssue(t *testing.T) {
expectResultError: true,
expectedErrMsg: "access to issue details is restricted by lockdown mode",
lockdownEnabled: true,
restPermission: "read",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
client := github.NewClient(tc.mockedClient)

var gqlClient *githubv4.Client
cache := repoAccessCache
if tc.gqlHTTPClient != nil {
gqlClient = githubv4.NewClient(tc.gqlHTTPClient)
cache = stubRepoAccessCache(gqlClient, 15*time.Minute)
} else {
gqlClient = githubv4.NewClient(nil)
var restClient *github.Client
if tc.restPermission != "" {
restClient = mockRESTPermissionServer(t, tc.restPermission, nil)
}
cache := stubRepoAccessCache(restClient, 15*time.Minute)

flags := stubFeatureFlags(map[string]bool{"lockdown-mode": tc.lockdownEnabled})
deps := BaseDeps{
Client: client,
GQLClient: gqlClient,
GQLClient: defaultGQLClient,
RepoAccessCache: cache,
Flags: flags,
}
Expand Down Expand Up @@ -1997,7 +1914,6 @@ func Test_GetIssueComments(t *testing.T) {
tests := []struct {
name string
mockedClient *http.Client
gqlHTTPClient *http.Client
requestArgs map[string]any
expectError bool
expectedComments []*github.IssueComment
Expand Down Expand Up @@ -2069,7 +1985,6 @@ func Test_GetIssueComments(t *testing.T) {
},
}),
}),
gqlHTTPClient: newRepoAccessHTTPClient(),
requestArgs: map[string]any{
"method": "get_comments",
"owner": "owner",
Expand All @@ -2092,17 +2007,18 @@ func Test_GetIssueComments(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
// Setup client with mock
client := github.NewClient(tc.mockedClient)
var gqlClient *githubv4.Client
if tc.gqlHTTPClient != nil {
gqlClient = githubv4.NewClient(tc.gqlHTTPClient)
} else {
gqlClient = githubv4.NewClient(nil)
var restClient *github.Client
if tc.lockdownEnabled {
restClient = mockRESTPermissionServer(t, "read", map[string]string{
"maintainer": "write",
Comment thread
kerobbi marked this conversation as resolved.
"testuser": "read",
})
}
cache := stubRepoAccessCache(gqlClient, 15*time.Minute)
cache := stubRepoAccessCache(restClient, 15*time.Minute)
flags := stubFeatureFlags(map[string]bool{"lockdown-mode": tc.lockdownEnabled})
deps := BaseDeps{
Client: client,
GQLClient: gqlClient,
GQLClient: defaultGQLClient,
RepoAccessCache: cache,
Flags: flags,
}
Expand Down Expand Up @@ -2223,7 +2139,7 @@ func Test_GetIssueLabels(t *testing.T) {
deps := BaseDeps{
Client: client,
GQLClient: gqlClient,
RepoAccessCache: stubRepoAccessCache(gqlClient, 15*time.Minute),
RepoAccessCache: stubRepoAccessCache(nil, 15*time.Minute),
Flags: stubFeatureFlags(map[string]bool{"lockdown-mode": false}),
}
handler := serverTool.Handler(deps)
Expand Down Expand Up @@ -2652,7 +2568,7 @@ func Test_GetSubIssues(t *testing.T) {
deps := BaseDeps{
Client: client,
GQLClient: gqlClient,
RepoAccessCache: stubRepoAccessCache(gqlClient, 15*time.Minute),
RepoAccessCache: stubRepoAccessCache(nil, 15*time.Minute),
Flags: stubFeatureFlags(map[string]bool{"lockdown-mode": false}),
}
handler := serverTool.Handler(deps)
Expand Down
36 changes: 19 additions & 17 deletions pkg/github/pullrequests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (

"github.com/github/github-mcp-server/internal/githubv4mock"
"github.com/github/github-mcp-server/internal/toolsnaps"
"github.com/github/github-mcp-server/pkg/lockdown"
"github.com/github/github-mcp-server/pkg/translations"
"github.com/google/go-github/v82/github"
"github.com/google/jsonschema-go/jsonschema"
Expand Down Expand Up @@ -101,7 +100,7 @@ func Test_GetPullRequest(t *testing.T) {
deps := BaseDeps{
Client: client,
GQLClient: gqlClient,
RepoAccessCache: stubRepoAccessCache(gqlClient, 5*time.Minute),
RepoAccessCache: stubRepoAccessCache(nil, 5*time.Minute),
Flags: stubFeatureFlags(map[string]bool{"lockdown-mode": false}),
}
handler := serverTool.Handler(deps)
Expand Down Expand Up @@ -1202,7 +1201,7 @@ func Test_GetPullRequestFiles(t *testing.T) {
serverTool := PullRequestRead(translations.NullTranslationHelper)
deps := BaseDeps{
Client: client,
RepoAccessCache: stubRepoAccessCache(githubv4.NewClient(githubv4mock.NewMockedHTTPClient()), 5*time.Minute),
RepoAccessCache: stubRepoAccessCache(nil, 5*time.Minute),
Flags: stubFeatureFlags(map[string]bool{"lockdown-mode": false}),
}
handler := serverTool.Handler(deps)
Expand Down Expand Up @@ -1362,7 +1361,7 @@ func Test_GetPullRequestStatus(t *testing.T) {
serverTool := PullRequestRead(translations.NullTranslationHelper)
deps := BaseDeps{
Client: client,
RepoAccessCache: stubRepoAccessCache(githubv4.NewClient(nil), 5*time.Minute),
RepoAccessCache: stubRepoAccessCache(nil, 5*time.Minute),
Flags: stubFeatureFlags(map[string]bool{"lockdown-mode": false}),
}
handler := serverTool.Handler(deps)
Expand Down Expand Up @@ -1518,7 +1517,7 @@ func Test_GetPullRequestCheckRuns(t *testing.T) {
serverTool := PullRequestRead(translations.NullTranslationHelper)
deps := BaseDeps{
Client: client,
RepoAccessCache: stubRepoAccessCache(githubv4.NewClient(nil), 5*time.Minute),
RepoAccessCache: stubRepoAccessCache(nil, 5*time.Minute),
Flags: stubFeatureFlags(map[string]bool{"lockdown-mode": false}),
}
handler := serverTool.Handler(deps)
Expand Down Expand Up @@ -1937,12 +1936,15 @@ func Test_GetPullRequestComments(t *testing.T) {
}

// Setup cache for lockdown mode
var cache *lockdown.RepoAccessCache
var restClient *github.Client
if tc.lockdownEnabled {
cache = stubRepoAccessCache(githubv4.NewClient(newRepoAccessHTTPClient()), 5*time.Minute)
} else {
cache = stubRepoAccessCache(gqlClient, 5*time.Minute)
restClient = mockRESTPermissionServer(t, "read", map[string]string{
"maintainer": "write",
"external-user": "read",
"testuser": "read",
})
}
cache := stubRepoAccessCache(restClient, 5*time.Minute)

flags := stubFeatureFlags(map[string]bool{"lockdown-mode": tc.lockdownEnabled})
serverTool := PullRequestRead(translations.NullTranslationHelper)
Expand Down Expand Up @@ -2083,7 +2085,6 @@ func Test_GetPullRequestReviews(t *testing.T) {
},
}),
}),
gqlHTTPClient: newRepoAccessHTTPClient(),
requestArgs: map[string]any{
"method": "get_reviews",
"owner": "owner",
Expand All @@ -2107,13 +2108,14 @@ func Test_GetPullRequestReviews(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
// Setup client with mock
client := github.NewClient(tc.mockedClient)
var gqlClient *githubv4.Client
if tc.gqlHTTPClient != nil {
gqlClient = githubv4.NewClient(tc.gqlHTTPClient)
} else {
gqlClient = githubv4.NewClient(nil)
var restClient *github.Client
if tc.lockdownEnabled {
restClient = mockRESTPermissionServer(t, "read", map[string]string{
"maintainer": "write",
Comment thread
kerobbi marked this conversation as resolved.
"testuser": "read",
})
}
cache := stubRepoAccessCache(gqlClient, 5*time.Minute)
cache := stubRepoAccessCache(restClient, 5*time.Minute)
flags := stubFeatureFlags(map[string]bool{"lockdown-mode": tc.lockdownEnabled})
serverTool := PullRequestRead(translations.NullTranslationHelper)
deps := BaseDeps{
Expand Down Expand Up @@ -3348,7 +3350,7 @@ index 5d6e7b2..8a4f5c3 100644
serverTool := PullRequestRead(translations.NullTranslationHelper)
deps := BaseDeps{
Client: client,
RepoAccessCache: stubRepoAccessCache(githubv4.NewClient(nil), 5*time.Minute),
RepoAccessCache: stubRepoAccessCache(nil, 5*time.Minute),
Flags: stubFeatureFlags(map[string]bool{"lockdown-mode": false}),
}
handler := serverTool.Handler(deps)
Expand Down
Loading
Loading