@@ -9,11 +9,15 @@ import (
9
9
"os"
10
10
"os/exec"
11
11
"slices"
12
+ "strings"
12
13
"sync"
13
14
"testing"
14
15
"time"
15
16
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"
17
21
mcpClient "github.com/mark3labs/mcp-go/client"
18
22
"github.com/mark3labs/mcp-go/mcp"
19
23
"github.com/stretchr/testify/require"
@@ -56,68 +60,91 @@ func ensureDockerImageBuilt(t *testing.T) {
56
60
require .NoError (t , buildError , "expected to build Docker image successfully" )
57
61
}
58
62
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
63
67
}
64
68
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 )
67
71
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
72
77
}
73
78
}
74
79
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 {
78
81
// Get token and ensure Docker image is built
79
82
token := getE2EToken (t )
80
- ensureDockerImageBuilt (t )
81
83
82
84
// Create and configure options
83
- opts := & ClientOpts {
84
- EnvVars : make (map [string ]string ),
85
- }
85
+ opts := & clientOpts {}
86
86
87
87
// Apply all options to configure the opts struct
88
88
for _ , option := range options {
89
89
option (opts )
90
90
}
91
91
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
+ }
101
107
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
+ }
112
+
113
+ // Add the image name
114
+ args = append (args , "github/e2e-github-mcp-server" )
106
115
107
- // Add the image name
108
- args = append (args , "github/e2e-github-mcp-server" )
116
+ // Construct the env vars for the MCP Client to execute docker with
117
+ dockerEnvVars := []string {
118
+ fmt .Sprintf ("GITHUB_PERSONAL_ACCESS_TOKEN=%s" , token ),
119
+ fmt .Sprintf ("GITHUB_TOOLSETS=%s" , strings .Join (opts .enabledToolsets , "," )),
120
+ }
109
121
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 ))
122
+ // Create the client
123
+ t .Log ("Starting Stdio MCP client..." )
124
+ var err error
125
+ client , err = mcpClient .NewStdioMCPClient (args [0 ], dockerEnvVars , args [1 :]... )
126
+ require .NoError (t , err , "expected to create client successfully" )
127
+ } else {
128
+ // We need this because the fully compiled server has a default for the viper config, which is
129
+ // not in scope for using the MCP server directly. This probably indicates that we should refactor
130
+ // so that there is a shared setup mechanism, but let's wait till we feel more friction.
131
+ enabledToolsets := opts .enabledToolsets
132
+ if enabledToolsets == nil {
133
+ enabledToolsets = github .DefaultTools
134
+ }
135
+
136
+ ghServer , err := ghmcp .NewMCPServer (ghmcp.MCPServerConfig {
137
+ Token : token ,
138
+ EnabledToolsets : enabledToolsets ,
139
+ Translator : translations .NullTranslationHelper ,
140
+ })
141
+ require .NoError (t , err , "expected to construct MCP server successfully" )
142
+
143
+ t .Log ("Starting In Process MCP client..." )
144
+ client , err = mcpClient .NewInProcessClient (ghServer )
145
+ require .NoError (t , err , "expected to create in-process client successfully" )
115
146
}
116
147
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" )
121
148
t .Cleanup (func () {
122
149
require .NoError (t , client .Close (), "expected to close client successfully" )
123
150
})
@@ -169,7 +196,7 @@ func TestGetMe(t *testing.T) {
169
196
170
197
// Then the login in the response should match the login obtained via the same
171
198
// token using the GitHub API.
172
- ghClient := github .NewClient (nil ).WithAuthToken (getE2EToken (t ))
199
+ ghClient := gogithub .NewClient (nil ).WithAuthToken (getE2EToken (t ))
173
200
user , _ , err := ghClient .Users .Get (context .Background (), "" )
174
201
require .NoError (t , err , "expected to get user successfully" )
175
202
require .Equal (t , trimmedContent .Login , * user .Login , "expected login to match" )
@@ -181,9 +208,7 @@ func TestToolsets(t *testing.T) {
181
208
182
209
mcpClient := setupMCPClient (
183
210
t ,
184
- WithEnvVars (map [string ]string {
185
- "GITHUB_TOOLSETS" : "repos,issues" ,
186
- }),
211
+ withToolsets ([]string {"repos" , "issues" }),
187
212
)
188
213
189
214
ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Second )
@@ -208,6 +233,8 @@ func TestToolsets(t *testing.T) {
208
233
}
209
234
210
235
func TestTags (t * testing.T ) {
236
+ t .Parallel ()
237
+
211
238
mcpClient := setupMCPClient (t )
212
239
213
240
ctx := context .Background ()
@@ -253,32 +280,32 @@ func TestTags(t *testing.T) {
253
280
// Cleanup the repository after the test
254
281
t .Cleanup (func () {
255
282
// MCP Server doesn't support deletions, but we can use the GitHub Client
256
- ghClient := github .NewClient (nil ).WithAuthToken (getE2EToken (t ))
283
+ ghClient := gogithub .NewClient (nil ).WithAuthToken (getE2EToken (t ))
257
284
t .Logf ("Deleting repository %s/%s..." , currentOwner , repoName )
258
285
_ , err := ghClient .Repositories .Delete (context .Background (), currentOwner , repoName )
259
286
require .NoError (t , err , "expected to delete repository successfully" )
260
287
})
261
288
262
289
// Then create a tag
263
290
// MCP Server doesn't support tag creation, but we can use the GitHub Client
264
- ghClient := github .NewClient (nil ).WithAuthToken (getE2EToken (t ))
291
+ ghClient := gogithub .NewClient (nil ).WithAuthToken (getE2EToken (t ))
265
292
t .Logf ("Creating tag %s/%s:%s..." , currentOwner , repoName , "v0.0.1" )
266
293
ref , _ , err := ghClient .Git .GetRef (context .Background (), currentOwner , repoName , "refs/heads/main" )
267
294
require .NoError (t , err , "expected to get ref successfully" )
268
295
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 {
296
+ tagObj , _ , err := ghClient .Git .CreateTag (context .Background (), currentOwner , repoName , & gogithub .Tag {
297
+ Tag : gogithub .Ptr ("v0.0.1" ),
298
+ Message : gogithub .Ptr ("v0.0.1" ),
299
+ Object : & gogithub .GitObject {
273
300
SHA : ref .Object .SHA ,
274
- Type : github .Ptr ("commit" ),
301
+ Type : gogithub .Ptr ("commit" ),
275
302
},
276
303
})
277
304
require .NoError (t , err , "expected to create tag object successfully" )
278
305
279
- _ , _ , err = ghClient .Git .CreateRef (context .Background (), currentOwner , repoName , & github .Reference {
280
- Ref : github .Ptr ("refs/tags/v0.0.1" ),
281
- Object : & github .GitObject {
306
+ _ , _ , err = ghClient .Git .CreateRef (context .Background (), currentOwner , repoName , & gogithub .Reference {
307
+ Ref : gogithub .Ptr ("refs/tags/v0.0.1" ),
308
+ Object : & gogithub .GitObject {
282
309
SHA : tagObj .SHA ,
283
310
},
284
311
})
0 commit comments