Skip to content

Commit 280890d

Browse files
committed
Support breakpoint debugging e2e tests
1 parent ed0a458 commit 280890d

File tree

1 file changed

+82
-56
lines changed

1 file changed

+82
-56
lines changed

e2e/e2e_test.go

Lines changed: 82 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@ import (
99
"os"
1010
"os/exec"
1111
"slices"
12+
"strings"
1213
"sync"
1314
"testing"
1415
"time"
1516

16-
"github.com/google/go-github/v69/github"
17+
"github.com/github/github-mcp-server/internal/ghmcp"
18+
"github.com/github/github-mcp-server/pkg/github"
19+
"github.com/github/github-mcp-server/pkg/translations"
20+
gogithub "github.com/google/go-github/v69/github"
1721
mcpClient "github.com/mark3labs/mcp-go/client"
1822
"github.com/mark3labs/mcp-go/mcp"
1923
"github.com/stretchr/testify/require"
@@ -56,68 +60,90 @@ func ensureDockerImageBuilt(t *testing.T) {
5660
require.NoError(t, buildError, "expected to build Docker image successfully")
5761
}
5862

59-
// ClientOpts holds configuration options for the MCP client setup
60-
type ClientOpts struct {
61-
// Environment variables to set before starting the client
62-
EnvVars map[string]string
63+
// clientOpts holds configuration options for the MCP client setup
64+
type clientOpts struct {
65+
// Toolsets to enable in the MCP server
66+
enabledToolsets []string
6367
}
6468

65-
// ClientOption defines a function type for configuring ClientOpts
66-
type ClientOption func(*ClientOpts)
69+
// clientOption defines a function type for configuring ClientOpts
70+
type clientOption func(*clientOpts)
6771

68-
// WithEnvVars returns an option that adds environment variables to the client options
69-
func WithEnvVars(envVars map[string]string) ClientOption {
70-
return func(opts *ClientOpts) {
71-
opts.EnvVars = envVars
72+
// withToolsets returns an option that either sets an Env Var when executing in docker,
73+
// or sets the toolsets in the MCP server when running in-process.
74+
func withToolsets(toolsets []string) clientOption {
75+
return func(opts *clientOpts) {
76+
opts.enabledToolsets = toolsets
7277
}
7378
}
7479

75-
// setupMCPClient sets up the test environment and returns an initialized MCP client
76-
// It handles token retrieval, Docker image building, and applying the provided options
77-
func setupMCPClient(t *testing.T, options ...ClientOption) *mcpClient.Client {
80+
func setupMCPClient(t *testing.T, options ...clientOption) *mcpClient.Client {
7881
// Get token and ensure Docker image is built
7982
token := getE2EToken(t)
80-
ensureDockerImageBuilt(t)
8183

8284
// Create and configure options
83-
opts := &ClientOpts{
84-
EnvVars: make(map[string]string),
85-
}
85+
opts := &clientOpts{}
8686

8787
// Apply all options to configure the opts struct
8888
for _, option := range options {
8989
option(opts)
9090
}
9191

92-
// Prepare Docker arguments
93-
args := []string{
94-
"docker",
95-
"run",
96-
"-i",
97-
"--rm",
98-
"-e",
99-
"GITHUB_PERSONAL_ACCESS_TOKEN", // Personal access token is all required
100-
}
92+
// By default, we run the tests including the Docker image, but with DEBUG
93+
// enabled, we run the server in-process, allowing for easier debugging.
94+
var client *mcpClient.Client
95+
if os.Getenv("GITHUB_MCP_SERVER_E2E_DEBUG") == "" {
96+
ensureDockerImageBuilt(t)
97+
98+
// Prepare Docker arguments
99+
args := []string{
100+
"docker",
101+
"run",
102+
"-i",
103+
"--rm",
104+
"-e",
105+
"GITHUB_PERSONAL_ACCESS_TOKEN", // Personal access token is all required
106+
}
101107

102-
// Add all environment variables to the Docker arguments
103-
for key := range opts.EnvVars {
104-
args = append(args, "-e", key)
105-
}
108+
// Add toolsets environment variable to the Docker arguments
109+
if len(opts.enabledToolsets) > 0 {
110+
args = append(args, "-e", "GITHUB_TOOLSETS")
111+
}
106112

107-
// Add the image name
108-
args = append(args, "github/e2e-github-mcp-server")
113+
// Add the image name
114+
args = append(args, "github/e2e-github-mcp-server")
115+
116+
// Construct the env vars for the MCP Client to execute docker with
117+
dockerEnvVars := make([]string, 0, len(opts.enabledToolsets)+1)
118+
dockerEnvVars = append(dockerEnvVars, fmt.Sprintf("GITHUB_PERSONAL_ACCESS_TOKEN=%s", token))
119+
dockerEnvVars = append(dockerEnvVars, fmt.Sprintf("GITHUB_TOOLSETS=%s", strings.Join(opts.enabledToolsets, ",")))
120+
121+
// Create the client
122+
t.Log("Starting Stdio MCP client...")
123+
var err error
124+
client, err = mcpClient.NewStdioMCPClient(args[0], dockerEnvVars, args[1:]...)
125+
require.NoError(t, err, "expected to create client successfully")
126+
} else {
127+
// We need this because the fully compiled server has a default for the viper config, which is
128+
// not in scope for using the MCP server directly. This probably indicates that we should refactor
129+
// so that there is a shared setup mechanism, but let's wait till we feel more friction.
130+
enabledToolsets := opts.enabledToolsets
131+
if enabledToolsets == nil {
132+
enabledToolsets = github.DefaultTools
133+
}
134+
135+
ghServer, err := ghmcp.NewMCPServer(ghmcp.MCPServerConfig{
136+
Token: token,
137+
EnabledToolsets: enabledToolsets,
138+
Translator: translations.NullTranslationHelper,
139+
})
140+
require.NoError(t, err, "expected to construct MCP server successfully")
109141

110-
// Construct the env vars for the MCP Client to execute docker with
111-
dockerEnvVars := make([]string, 0, len(opts.EnvVars)+1)
112-
dockerEnvVars = append(dockerEnvVars, fmt.Sprintf("GITHUB_PERSONAL_ACCESS_TOKEN=%s", token))
113-
for key, value := range opts.EnvVars {
114-
dockerEnvVars = append(dockerEnvVars, fmt.Sprintf("%s=%s", key, value))
142+
t.Log("Starting In Process MCP client...")
143+
client, err = mcpClient.NewInProcessClient(ghServer)
144+
require.NoError(t, err, "expected to create in-process client successfully")
115145
}
116146

117-
// Create the client
118-
t.Log("Starting Stdio MCP client...")
119-
client, err := mcpClient.NewStdioMCPClient(args[0], dockerEnvVars, args[1:]...)
120-
require.NoError(t, err, "expected to create client successfully")
121147
t.Cleanup(func() {
122148
require.NoError(t, client.Close(), "expected to close client successfully")
123149
})
@@ -169,7 +195,7 @@ func TestGetMe(t *testing.T) {
169195

170196
// Then the login in the response should match the login obtained via the same
171197
// token using the GitHub API.
172-
ghClient := github.NewClient(nil).WithAuthToken(getE2EToken(t))
198+
ghClient := gogithub.NewClient(nil).WithAuthToken(getE2EToken(t))
173199
user, _, err := ghClient.Users.Get(context.Background(), "")
174200
require.NoError(t, err, "expected to get user successfully")
175201
require.Equal(t, trimmedContent.Login, *user.Login, "expected login to match")
@@ -181,9 +207,7 @@ func TestToolsets(t *testing.T) {
181207

182208
mcpClient := setupMCPClient(
183209
t,
184-
WithEnvVars(map[string]string{
185-
"GITHUB_TOOLSETS": "repos,issues",
186-
}),
210+
withToolsets([]string{"repos", "issues"}),
187211
)
188212

189213
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
@@ -208,6 +232,8 @@ func TestToolsets(t *testing.T) {
208232
}
209233

210234
func TestTags(t *testing.T) {
235+
t.Parallel()
236+
211237
mcpClient := setupMCPClient(t)
212238

213239
ctx := context.Background()
@@ -253,32 +279,32 @@ func TestTags(t *testing.T) {
253279
// Cleanup the repository after the test
254280
t.Cleanup(func() {
255281
// MCP Server doesn't support deletions, but we can use the GitHub Client
256-
ghClient := github.NewClient(nil).WithAuthToken(getE2EToken(t))
282+
ghClient := gogithub.NewClient(nil).WithAuthToken(getE2EToken(t))
257283
t.Logf("Deleting repository %s/%s...", currentOwner, repoName)
258284
_, err := ghClient.Repositories.Delete(context.Background(), currentOwner, repoName)
259285
require.NoError(t, err, "expected to delete repository successfully")
260286
})
261287

262288
// Then create a tag
263289
// MCP Server doesn't support tag creation, but we can use the GitHub Client
264-
ghClient := github.NewClient(nil).WithAuthToken(getE2EToken(t))
290+
ghClient := gogithub.NewClient(nil).WithAuthToken(getE2EToken(t))
265291
t.Logf("Creating tag %s/%s:%s...", currentOwner, repoName, "v0.0.1")
266292
ref, _, err := ghClient.Git.GetRef(context.Background(), currentOwner, repoName, "refs/heads/main")
267293
require.NoError(t, err, "expected to get ref successfully")
268294

269-
tagObj, _, err := ghClient.Git.CreateTag(context.Background(), currentOwner, repoName, &github.Tag{
270-
Tag: github.Ptr("v0.0.1"),
271-
Message: github.Ptr("v0.0.1"),
272-
Object: &github.GitObject{
295+
tagObj, _, err := ghClient.Git.CreateTag(context.Background(), currentOwner, repoName, &gogithub.Tag{
296+
Tag: gogithub.Ptr("v0.0.1"),
297+
Message: gogithub.Ptr("v0.0.1"),
298+
Object: &gogithub.GitObject{
273299
SHA: ref.Object.SHA,
274-
Type: github.Ptr("commit"),
300+
Type: gogithub.Ptr("commit"),
275301
},
276302
})
277303
require.NoError(t, err, "expected to create tag object successfully")
278304

279-
_, _, err = ghClient.Git.CreateRef(context.Background(), currentOwner, repoName, &github.Reference{
280-
Ref: github.Ptr("refs/tags/v0.0.1"),
281-
Object: &github.GitObject{
305+
_, _, err = ghClient.Git.CreateRef(context.Background(), currentOwner, repoName, &gogithub.Reference{
306+
Ref: gogithub.Ptr("refs/tags/v0.0.1"),
307+
Object: &gogithub.GitObject{
282308
SHA: tagObj.SHA,
283309
},
284310
})

0 commit comments

Comments
 (0)