@@ -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"
1721mcpClient"github.com/mark3labs/mcp-go/client"
1822"github.com/mark3labs/mcp-go/mcp"
1923"github.com/stretchr/testify/require"
@@ -56,68 +60,91 @@ func ensureDockerImageBuilt(t *testing.T) {
5660require .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 toset before starting theclient
62- EnvVars map [ string ]string
63+ //clientOpts holds configuration options for the MCP client setup
64+ type clientOpts struct {
65+ //Toolsets toenable in theMCP 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
7982token := 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
8888for _ ,option := range options {
8989option (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+ }
112+
113+ // Add the image name
114+ args = append (args ,"github/e2e-github-mcp-server" )
106115
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+ }
109121
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" )
115146}
116147
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" )
121148t .Cleanup (func () {
122149require .NoError (t ,client .Close (),"expected to close client successfully" )
123150})
@@ -169,7 +196,7 @@ func TestGetMe(t *testing.T) {
169196
170197// Then the login in the response should match the login obtained via the same
171198// token using the GitHub API.
172- ghClient := github .NewClient (nil ).WithAuthToken (getE2EToken (t ))
199+ ghClient := gogithub .NewClient (nil ).WithAuthToken (getE2EToken (t ))
173200user ,_ ,err := ghClient .Users .Get (context .Background (),"" )
174201require .NoError (t ,err ,"expected to get user successfully" )
175202require .Equal (t ,trimmedContent .Login ,* user .Login ,"expected login to match" )
@@ -181,9 +208,7 @@ func TestToolsets(t *testing.T) {
181208
182209mcpClient := setupMCPClient (
183210t ,
184- WithEnvVars (map [string ]string {
185- "GITHUB_TOOLSETS" :"repos,issues" ,
186- }),
211+ withToolsets ([]string {"repos" ,"issues" }),
187212)
188213
189214ctx ,cancel := context .WithTimeout (context .Background (),5 * time .Second )
@@ -208,6 +233,8 @@ func TestToolsets(t *testing.T) {
208233}
209234
210235func TestTags (t * testing.T ) {
236+ t .Parallel ()
237+
211238mcpClient := setupMCPClient (t )
212239
213240ctx := context .Background ()
@@ -253,32 +280,32 @@ func TestTags(t *testing.T) {
253280// Cleanup the repository after the test
254281t .Cleanup (func () {
255282// 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 ))
257284t .Logf ("Deleting repository %s/%s..." ,currentOwner ,repoName )
258285_ ,err := ghClient .Repositories .Delete (context .Background (),currentOwner ,repoName )
259286require .NoError (t ,err ,"expected to delete repository successfully" )
260287})
261288
262289// Then create a tag
263290// 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 ))
265292t .Logf ("Creating tag %s/%s:%s..." ,currentOwner ,repoName ,"v0.0.1" )
266293ref ,_ ,err := ghClient .Git .GetRef (context .Background (),currentOwner ,repoName ,"refs/heads/main" )
267294require .NoError (t ,err ,"expected to get ref successfully" )
268295
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 {
273300SHA :ref .Object .SHA ,
274- Type :github .Ptr ("commit" ),
301+ Type :gogithub .Ptr ("commit" ),
275302},
276303})
277304require .NoError (t ,err ,"expected to create tag object successfully" )
278305
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 {
282309SHA :tagObj .SHA ,
283310},
284311})