@@ -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
210234func 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