Skip to content

Commit 8660acb

Browse files
committed
add response formatter and feature flags for easy testing
1 parent e903346 commit 8660acb

File tree

13 files changed

+535
-37
lines changed

13 files changed

+535
-37
lines changed

cmd/github-mcp-server/main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ var (
6262
LogFilePath: viper.GetString("log-file"),
6363
ContentWindowSize: viper.GetInt("content-window-size"),
6464
LockdownMode: viper.GetBool("lockdown-mode"),
65+
JSONFormat: viper.GetBool("json-format"),
66+
TOONFormat: viper.GetBool("toon-format"),
6567
}
6668
return ghmcp.RunStdioServer(stdioServerConfig)
6769
},
@@ -84,6 +86,8 @@ func init() {
8486
rootCmd.PersistentFlags().String("gh-host", "", "Specify the GitHub hostname (for GitHub Enterprise etc.)")
8587
rootCmd.PersistentFlags().Int("content-window-size", 5000, "Specify the content window size")
8688
rootCmd.PersistentFlags().Bool("lockdown-mode", false, "Enable lockdown mode")
89+
rootCmd.PersistentFlags().Bool("json-format", false, "Use JSON output format instead of default CSV")
90+
rootCmd.PersistentFlags().Bool("toon-format", false, "Enable TOON output format")
8791

8892
// Bind flag to viper
8993
_ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets"))
@@ -95,6 +99,8 @@ func init() {
9599
_ = viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("gh-host"))
96100
_ = viper.BindPFlag("content-window-size", rootCmd.PersistentFlags().Lookup("content-window-size"))
97101
_ = viper.BindPFlag("lockdown-mode", rootCmd.PersistentFlags().Lookup("lockdown-mode"))
102+
_ = viper.BindPFlag("json-format", rootCmd.PersistentFlags().Lookup("json-format"))
103+
_ = viper.BindPFlag("toon-format", rootCmd.PersistentFlags().Lookup("toon-format"))
98104

99105
// Add subcommands
100106
rootCmd.AddCommand(stdioCmd)

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/github/github-mcp-server
33
go 1.24.0
44

55
require (
6+
github.com/alpkeskin/gotoon v0.1.1
67
github.com/google/go-github/v77 v77.0.0
78
github.com/josephburnett/jd v1.9.2
89
github.com/mark3labs/mcp-go v0.36.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/alpkeskin/gotoon v0.1.1 h1:GQOVwMfWKINnfEA6slrXHJaJYDwnUFmrPlXOtnuja1w=
2+
github.com/alpkeskin/gotoon v0.1.1/go.mod h1:XRTz8RM4tz8M2nB37MNRN8rHF4YgeYd8nIXmoU0B0+M=
13
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
24
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
35
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=

internal/ghmcp/server.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ type MCPServerConfig struct {
5454

5555
// LockdownMode indicates if we should enable lockdown mode
5656
LockdownMode bool
57+
58+
// JSONFormat controls whether tools return a JSON response
59+
JSONFormat bool
60+
61+
// TOONFormat controls whether tools return a TOON response
62+
TOONFormat bool
5763
}
5864

5965
const stdioServerLogPrefix = "stdioserver"
@@ -164,7 +170,7 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
164170
getRawClient,
165171
cfg.Translator,
166172
cfg.ContentWindowSize,
167-
github.FeatureFlags{LockdownMode: cfg.LockdownMode},
173+
github.FeatureFlags{LockdownMode: cfg.LockdownMode, JSONFormat: cfg.JSONFormat, TOONFormat: cfg.TOONFormat},
168174
)
169175
err = tsg.EnableToolsets(enabledToolsets, nil)
170176

@@ -219,6 +225,12 @@ type StdioServerConfig struct {
219225

220226
// LockdownMode indicates if we should enable lockdown mode
221227
LockdownMode bool
228+
229+
// JSONFormat controls whether tools return a JSON response
230+
JSONFormat bool
231+
232+
// TOONFormat controls whether tools return a TOON response
233+
TOONFormat bool
222234
}
223235

224236
// RunStdioServer is not concurrent safe.
@@ -239,6 +251,8 @@ func RunStdioServer(cfg StdioServerConfig) error {
239251
Translator: t,
240252
ContentWindowSize: cfg.ContentWindowSize,
241253
LockdownMode: cfg.LockdownMode,
254+
JSONFormat: cfg.JSONFormat,
255+
TOONFormat: cfg.TOONFormat,
242256
})
243257
if err != nil {
244258
return fmt.Errorf("failed to create MCP server: %w", err)

pkg/github/feature_flags.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@ package github
33
// FeatureFlags defines runtime feature toggles that adjust tool behavior.
44
type FeatureFlags struct {
55
LockdownMode bool
6+
// JSONFormat controls whether tools return a JSON response
7+
JSONFormat bool
8+
// TOONFormat controls whether tools return a TOON response
9+
TOONFormat bool
610
}

pkg/github/issues.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,7 +1192,7 @@ func UpdateIssue(ctx context.Context, client *github.Client, gqlClient *githubv4
11921192
}
11931193

11941194
// ListIssues creates a tool to list and filter repository issues
1195-
func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
1195+
func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc, flags FeatureFlags) (tool mcp.Tool, handler server.ToolHandlerFunc) {
11961196
return mcp.NewTool("list_issues",
11971197
mcp.WithDescription(t("TOOL_LIST_ISSUES_DESCRIPTION", "List issues in a GitHub repository. For pagination, use the 'endCursor' from the previous response's 'pageInfo' in the 'after' parameter.")),
11981198
mcp.WithToolAnnotation(mcp.ToolAnnotation{
@@ -1384,9 +1384,8 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
13841384
totalCount = fragment.TotalCount
13851385
}
13861386

1387-
// Create response with issues
1388-
response := map[string]interface{}{
1389-
"issues": issues,
1387+
// Create metadata for pagination
1388+
metadata := map[string]interface{}{
13901389
"pageInfo": map[string]interface{}{
13911390
"hasNextPage": pageInfo.HasNextPage,
13921391
"hasPreviousPage": pageInfo.HasPreviousPage,
@@ -1395,11 +1394,8 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
13951394
},
13961395
"totalCount": totalCount,
13971396
}
1398-
out, err := json.Marshal(response)
1399-
if err != nil {
1400-
return nil, fmt.Errorf("failed to marshal issues: %w", err)
1401-
}
1402-
return mcp.NewToolResultText(string(out)), nil
1397+
1398+
return FormatResponse(issues, flags, "issues", metadata)
14031399
}
14041400
}
14051401

pkg/github/issues_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,7 @@ func Test_CreateIssue(t *testing.T) {
857857
func Test_ListIssues(t *testing.T) {
858858
// Verify tool definition
859859
mockClient := githubv4.NewClient(nil)
860-
tool, _ := ListIssues(stubGetGQLClientFn(mockClient), translations.NullTranslationHelper)
860+
tool, _ := ListIssues(stubGetGQLClientFn(mockClient), translations.NullTranslationHelper, FeatureFlags{JSONFormat: true})
861861
require.NoError(t, toolsnaps.Test(tool.Name, tool))
862862

863863
assert.Equal(t, "list_issues", tool.Name)
@@ -1118,7 +1118,7 @@ func Test_ListIssues(t *testing.T) {
11181118
}
11191119

11201120
gqlClient := githubv4.NewClient(httpClient)
1121-
_, handler := ListIssues(stubGetGQLClientFn(gqlClient), translations.NullTranslationHelper)
1121+
_, handler := ListIssues(stubGetGQLClientFn(gqlClient), translations.NullTranslationHelper, FeatureFlags{JSONFormat: true})
11221122

11231123
req := createMCPRequest(tc.reqParams)
11241124
res, err := handler(context.Background(), req)

pkg/github/pullrequests.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ func UpdatePullRequest(getClient GetClientFn, getGQLClient GetGQLClientFn, t tra
713713
}
714714

715715
// ListPullRequests creates a tool to list and filter repository pull requests.
716-
func ListPullRequests(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc) {
716+
func ListPullRequests(getClient GetClientFn, t translations.TranslationHelperFunc, flags FeatureFlags) (mcp.Tool, server.ToolHandlerFunc) {
717717
return mcp.NewTool("list_pull_requests",
718718
mcp.WithDescription(t("TOOL_LIST_PULL_REQUESTS_DESCRIPTION", "List pull requests in a GitHub repository. If the user specifies an author, then DO NOT use this tool and use the search_pull_requests tool instead.")),
719719
mcp.WithToolAnnotation(mcp.ToolAnnotation{
@@ -828,12 +828,7 @@ func ListPullRequests(getClient GetClientFn, t translations.TranslationHelperFun
828828
}
829829
}
830830

831-
r, err := json.Marshal(prs)
832-
if err != nil {
833-
return nil, fmt.Errorf("failed to marshal response: %w", err)
834-
}
835-
836-
return mcp.NewToolResultText(string(r)), nil
831+
return FormatResponse(prs, flags, "")
837832
}
838833
}
839834

pkg/github/pullrequests_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,7 @@ func Test_UpdatePullRequest_Draft(t *testing.T) {
578578
func Test_ListPullRequests(t *testing.T) {
579579
// Verify tool definition once
580580
mockClient := github.NewClient(nil)
581-
tool, _ := ListPullRequests(stubGetClientFn(mockClient), translations.NullTranslationHelper)
581+
tool, _ := ListPullRequests(stubGetClientFn(mockClient), translations.NullTranslationHelper, FeatureFlags{JSONFormat: true})
582582
require.NoError(t, toolsnaps.Test(tool.Name, tool))
583583

584584
assert.Equal(t, "list_pull_requests", tool.Name)
@@ -671,7 +671,7 @@ func Test_ListPullRequests(t *testing.T) {
671671
t.Run(tc.name, func(t *testing.T) {
672672
// Setup client with mock
673673
client := github.NewClient(tc.mockedClient)
674-
_, handler := ListPullRequests(stubGetClientFn(client), translations.NullTranslationHelper)
674+
_, handler := ListPullRequests(stubGetClientFn(client), translations.NullTranslationHelper, FeatureFlags{JSONFormat: true})
675675

676676
// Create call request
677677
request := createMCPRequest(tc.requestArgs)

pkg/github/repositories.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
"github.com/mark3labs/mcp-go/server"
1919
)
2020

21-
func GetCommit(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
21+
func GetCommit(getClient GetClientFn, t translations.TranslationHelperFunc, flags FeatureFlags) (tool mcp.Tool, handler server.ToolHandlerFunc) {
2222
return mcp.NewTool("get_commit",
2323
mcp.WithDescription(t("TOOL_GET_COMMITS_DESCRIPTION", "Get details for a commit from a GitHub repository")),
2424
mcp.WithToolAnnotation(mcp.ToolAnnotation{
@@ -101,11 +101,12 @@ func GetCommit(getClient GetClientFn, t translations.TranslationHelperFunc) (too
101101
}
102102

103103
return mcp.NewToolResultText(string(r)), nil
104+
// return FormatResponse(minimalCommit, flags, "")
104105
}
105106
}
106107

107108
// ListCommits creates a tool to get commits of a branch in a repository.
108-
func ListCommits(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
109+
func ListCommits(getClient GetClientFn, t translations.TranslationHelperFunc, flags FeatureFlags) (tool mcp.Tool, handler server.ToolHandlerFunc) {
109110
return mcp.NewTool("list_commits",
110111
mcp.WithDescription(t("TOOL_LIST_COMMITS_DESCRIPTION", "Get list of commits of a branch in a GitHub repository. Returns at least 30 results per page by default, but can return more if specified using the perPage parameter (up to 100).")),
111112
mcp.WithToolAnnotation(mcp.ToolAnnotation{
@@ -191,12 +192,7 @@ func ListCommits(getClient GetClientFn, t translations.TranslationHelperFunc) (t
191192
minimalCommits[i] = convertToMinimalCommit(commit, false)
192193
}
193194

194-
r, err := json.Marshal(minimalCommits)
195-
if err != nil {
196-
return nil, fmt.Errorf("failed to marshal response: %w", err)
197-
}
198-
199-
return mcp.NewToolResultText(string(r)), nil
195+
return FormatResponse(minimalCommits, flags, "")
200196
}
201197
}
202198

0 commit comments

Comments
 (0)