Skip to content

Commit 4f508c0

Browse files
committed
Go-SDK
1 parent 42e5ce9 commit 4f508c0

File tree

93 files changed

+5093
-4279
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+5093
-4279
lines changed

cmd/github-mcp-server/generate_docs.go

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"github.com/github/github-mcp-server/pkg/toolsets"
1515
"github.com/github/github-mcp-server/pkg/translations"
1616
gogithub "github.com/google/go-github/v72/github"
17-
"github.com/mark3labs/mcp-go/mcp"
17+
"github.com/modelcontextprotocol/go-sdk/mcp"
1818
"github.com/shurcooL/githubv4"
1919
"github.com/spf13/cobra"
2020
)
@@ -217,7 +217,7 @@ func formatToolsetName(name string) string {
217217
}
218218
}
219219

220-
func generateToolDoc(tool mcp.Tool) string {
220+
func generateToolDoc(tool *mcp.Tool) string {
221221
var lines []string
222222

223223
// Tool name only (using annotation name instead of verbose description)
@@ -245,26 +245,15 @@ func generateToolDoc(tool mcp.Tool) string {
245245
typeStr := "unknown"
246246
description := ""
247247

248-
if propMap, ok := prop.(map[string]interface{}); ok {
249-
if typeVal, ok := propMap["type"].(string); ok {
250-
if typeVal == "array" {
251-
if items, ok := propMap["items"].(map[string]interface{}); ok {
252-
if itemType, ok := items["type"].(string); ok {
253-
typeStr = itemType + "[]"
254-
}
255-
} else {
256-
typeStr = "array"
257-
}
258-
} else {
259-
typeStr = typeVal
260-
}
261-
}
262-
263-
if desc, ok := propMap["description"].(string); ok {
264-
description = desc
265-
}
248+
if prop.Type == "array" {
249+
items := prop.Items
250+
typeStr = items.Type + "[]" // Default to array type
251+
} else {
252+
typeStr = prop.Type
266253
}
267254

255+
description = prop.Description
256+
268257
paramLine := fmt.Sprintf(" - `%s`: %s (%s, %s)", propName, description, typeStr, requiredStr)
269258
lines = append(lines, paramLine)
270259
}

docs/error-handling.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ type GitHubGraphQLError struct {
4040

4141
### For GitHub REST API Errors
4242

43-
Instead of directly returning `mcp.NewToolResultError()`, use:
43+
Instead of directly returning `utils.NewToolResultError()`, use:
4444

4545
```go
4646
return ghErrors.NewGitHubAPIErrorResponse(ctx, message, response, err), nil
@@ -100,7 +100,7 @@ func GetIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (tool
100100
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
101101
owner, err := RequiredParam[string](request, "owner")
102102
if err != nil {
103-
return mcp.NewToolResultError(err.Error()), nil
103+
return utils.NewToolResultError(err.Error()), nil
104104
}
105105

106106
client, err := getClient(ctx)

e2e/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ index 1c91d70..ac4ef2b 100644
2828
--- a/pkg/github/context_tools.go
2929
+++ b/pkg/github/context_tools.go
3030
@@ -39,6 +39,8 @@ func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mc
31-
return mcp.NewToolResultError(fmt.Sprintf("failed to get user: %s", string(body))), nil
31+
return utils.NewToolResultError(fmt.Sprintf("failed to get user: %s", string(body))), nil
3232
}
3333

3434
+ user.Login = sPtr("foobar")
@@ -37,7 +37,7 @@ index 1c91d70..ac4ef2b 100644
3737
if err != nil {
3838
return nil, fmt.Errorf("failed to marshal user: %w", err)
3939
@@ -47,3 +49,7 @@ func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mc
40-
return mcp.NewToolResultText(string(r)), nil
40+
return utils.NewToolResultText(string(r)), nil
4141
}
4242
}
4343
+

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ require (
3232
github.com/google/uuid v1.6.0 // indirect
3333
github.com/gorilla/mux v1.8.0 // indirect
3434
github.com/inconshreveable/mousetrap v1.1.0 // indirect
35+
github.com/modelcontextprotocol/go-sdk v0.1.1-0.20250711120814-78a66a438a4e
3536
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
3637
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
3738
github.com/rogpeppe/go-internal v1.13.1 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ github.com/mark3labs/mcp-go v0.32.0 h1:fgwmbfL2gbd67obg57OfV2Dnrhs1HtSdlY/i5fn7M
5151
github.com/mark3labs/mcp-go v0.32.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
5252
github.com/migueleliasweb/go-github-mock v1.3.0 h1:2sVP9JEMB2ubQw1IKto3/fzF51oFC6eVWOOFDgQoq88=
5353
github.com/migueleliasweb/go-github-mock v1.3.0/go.mod h1:ipQhV8fTcj/G6m7BKzin08GaJ/3B5/SonRAkgrk0zCY=
54+
github.com/modelcontextprotocol/go-sdk v0.1.0 h1:ItzbFWYNt4EHcUrScX7P8JPASn1FVYb29G773Xkl+IU=
55+
github.com/modelcontextprotocol/go-sdk v0.1.0/go.mod h1:DcXfbr7yl7e35oMpzHfKw2nUYRjhIGS2uou/6tdsTB0=
56+
github.com/modelcontextprotocol/go-sdk v0.1.1-0.20250711120814-78a66a438a4e h1:BRsPs8vuvvMr8vWg0ME2WEQspw81V3q6xCZwWt6Fdfg=
57+
github.com/modelcontextprotocol/go-sdk v0.1.1-0.20250711120814-78a66a438a4e/go.mod h1:0sL9zUKKs2FTTkeCCVnKqbLJTw5TScefPAzojjU459E=
5458
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
5559
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
5660
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=

internal/ghmcp/server.go

Lines changed: 64 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package ghmcp
33
import (
44
"context"
55
"fmt"
6-
"io"
76
"log"
87
"net/http"
98
"net/url"
@@ -14,12 +13,10 @@ import (
1413

1514
"github.com/github/github-mcp-server/pkg/errors"
1615
"github.com/github/github-mcp-server/pkg/github"
17-
mcplog "github.com/github/github-mcp-server/pkg/log"
1816
"github.com/github/github-mcp-server/pkg/raw"
1917
"github.com/github/github-mcp-server/pkg/translations"
2018
gogithub "github.com/google/go-github/v72/github"
21-
"github.com/mark3labs/mcp-go/mcp"
22-
"github.com/mark3labs/mcp-go/server"
19+
"github.com/modelcontextprotocol/go-sdk/mcp"
2320
"github.com/shurcooL/githubv4"
2421
"github.com/sirupsen/logrus"
2522
)
@@ -49,7 +46,7 @@ type MCPServerConfig struct {
4946
Translator translations.TranslationHelperFunc
5047
}
5148

52-
func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
49+
func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) {
5350
apiHost, err := parseAPIHost(cfg.Host)
5451
if err != nil {
5552
return nil, fmt.Errorf("failed to parse API host: %w", err)
@@ -72,35 +69,14 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
7269
} // We're going to wrap the Transport later in beforeInit
7370
gqlClient := githubv4.NewEnterpriseClient(apiHost.graphqlURL.String(), gqlHTTPClient)
7471

75-
// When a client send an initialize request, update the user agent to include the client info.
76-
beforeInit := func(_ context.Context, _ any, message *mcp.InitializeRequest) {
77-
userAgent := fmt.Sprintf(
78-
"github-mcp-server/%s (%s/%s)",
79-
cfg.Version,
80-
message.Params.ClientInfo.Name,
81-
message.Params.ClientInfo.Version,
82-
)
72+
ghServer := mcp.NewServer(&mcp.Implementation{
73+
Name: "gh-mcp-server",
74+
Title: "GitHub MCP Server",
75+
Version: cfg.Version,
76+
}, &mcp.ServerOptions{})
8377

84-
restClient.UserAgent = userAgent
85-
86-
gqlHTTPClient.Transport = &userAgentTransport{
87-
transport: gqlHTTPClient.Transport,
88-
agent: userAgent,
89-
}
90-
}
91-
92-
hooks := &server.Hooks{
93-
OnBeforeInitialize: []server.OnBeforeInitializeFunc{beforeInit},
94-
OnBeforeAny: []server.BeforeAnyHookFunc{
95-
func(ctx context.Context, _ any, _ mcp.MCPMethod, _ any) {
96-
// Ensure the context is cleared of any previous errors
97-
// as context isn't propagated through middleware
98-
errors.ContextWithGitHubErrors(ctx)
99-
},
100-
},
101-
}
102-
103-
ghServer := github.NewServer(cfg.Version, server.WithHooks(hooks))
78+
ghServer.AddReceivingMiddleware(InitializeUserAgentMiddleware[*mcp.ServerSession](cfg, restClient, gqlHTTPClient))
79+
ghServer.AddReceivingMiddleware(ApiErrorMiddleware[*mcp.ServerSession]())
10480

10581
enabledToolsets := cfg.EnabledToolsets
10682
if cfg.DynamicToolsets {
@@ -140,14 +116,56 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
140116
// Register all mcp functionality with the server
141117
tsg.RegisterAll(ghServer)
142118

143-
if cfg.DynamicToolsets {
144-
dynamic := github.InitDynamicToolset(ghServer, tsg, cfg.Translator)
145-
dynamic.RegisterTools(ghServer)
119+
return ghServer, nil
120+
}
121+
122+
func InitializeUserAgentMiddleware[S mcp.Session](cfg MCPServerConfig, restClient *gogithub.Client, gqlHTTPClient *http.Client) mcp.Middleware[S] {
123+
return func(next mcp.MethodHandler[S]) mcp.MethodHandler[S] {
124+
return func(ctx context.Context, session S, method string, params mcp.Params) (mcp.Result, error) {
125+
// If this is an initialize request, we need to set the user agent
126+
if method == "initialize" {
127+
intializeParams, ok := params.(*mcp.InitializeParams)
128+
if !ok {
129+
return nil, fmt.Errorf("expected params to be of type *mcp.InitializeParams, got %T", params)
130+
}
131+
132+
userAgent := fmt.Sprintf(
133+
"github-mcp-server/%s (%s/%s)",
134+
cfg.Version,
135+
intializeParams.ClientInfo.Name,
136+
intializeParams.ClientInfo.Version,
137+
)
138+
139+
restClient.UserAgent = userAgent
140+
141+
gqlHTTPClient.Transport = &userAgentTransport{
142+
transport: gqlHTTPClient.Transport,
143+
agent: userAgent,
144+
}
145+
}
146+
return next(ctx, session, method, params)
147+
}
146148
}
149+
}
147150

148-
return ghServer, nil
151+
func ApiErrorMiddleware[S mcp.Session]() mcp.Middleware[S] {
152+
return func(next mcp.MethodHandler[S]) mcp.MethodHandler[S] {
153+
return func(ctx context.Context, session S, method string, params mcp.Params) (mcp.Result, error) {
154+
// Ensure the context is cleared of any previous errors
155+
// as context isn't propagated through middleware
156+
ctx = errors.ContextWithGitHubErrors(ctx)
157+
158+
// Call the next handler
159+
return next(ctx, session, method, params)
160+
}
161+
}
149162
}
150163

164+
// ghServer := github.NewServer(cfg.Version, server.WithHooks(hooks))
165+
166+
// return ghServer, nil
167+
// }
168+
151169
type StdioServerConfig struct {
152170
// Version of the server
153171
Version string
@@ -201,8 +219,6 @@ func RunStdioServer(cfg StdioServerConfig) error {
201219
return fmt.Errorf("failed to create MCP server: %w", err)
202220
}
203221

204-
stdioServer := server.NewStdioServer(ghServer)
205-
206222
logrusLogger := logrus.New()
207223
if cfg.LogFilePath != "" {
208224
file, err := os.OpenFile(cfg.LogFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
@@ -214,7 +230,6 @@ func RunStdioServer(cfg StdioServerConfig) error {
214230
logrusLogger.SetOutput(file)
215231
}
216232
stdLogger := log.New(logrusLogger.Writer(), "stdioserver", 0)
217-
stdioServer.SetErrorLogger(stdLogger)
218233

219234
if cfg.ExportTranslations {
220235
// Once server is initialized, all translations are loaded
@@ -224,15 +239,18 @@ func RunStdioServer(cfg StdioServerConfig) error {
224239
// Start listening for messages
225240
errC := make(chan error, 1)
226241
go func() {
227-
in, out := io.Reader(os.Stdin), io.Writer(os.Stdout)
242+
stdioTransport := mcp.NewStdioTransport()
243+
loggingTransport := mcp.NewLoggingTransport(stdioTransport, stdLogger.Writer())
228244

229-
if cfg.EnableCommandLogging {
230-
loggedIO := mcplog.NewIOLogger(in, out, logrusLogger)
231-
in, out = loggedIO, loggedIO
232-
}
245+
// in, out := io.Reader(os.Stdin), io.Writer(os.Stdout)
246+
247+
// if cfg.EnableCommandLogging {
248+
// loggedIO := mcplog.NewIOLogger(in, out, logrusLogger)
249+
// in, out = loggedIO, loggedIO
250+
// }
233251
// enable GitHub errors in the context
234252
ctx := errors.ContextWithGitHubErrors(ctx)
235-
errC <- stdioServer.Listen(ctx, in, out)
253+
errC <- ghServer.Run(ctx, loggingTransport)
236254
}()
237255

238256
// Output github-mcp-server string

main

16.4 MB
Binary file not shown.

pkg/errors/error.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import (
44
"context"
55
"fmt"
66

7+
"github.com/github/github-mcp-server/pkg/utils"
78
"github.com/google/go-github/v72/github"
8-
"github.com/mark3labs/mcp-go/mcp"
9+
"github.com/modelcontextprotocol/go-sdk/mcp"
910
)
1011

1112
type GitHubAPIError struct {
@@ -106,20 +107,20 @@ func addGitHubGraphQLErrorToContext(ctx context.Context, err *GitHubGraphQLError
106107
return nil, fmt.Errorf("context does not contain GitHubCtxErrors")
107108
}
108109

109-
// NewGitHubAPIErrorResponse returns an mcp.NewToolResultError and retains the error in the context for access via middleware
110+
// NewGitHubAPIErrorResponse returns an utils.NewToolResultError( and retains the error in the context for access via middleware
110111
func NewGitHubAPIErrorResponse(ctx context.Context, message string, resp *github.Response, err error) *mcp.CallToolResult {
111112
apiErr := newGitHubAPIError(message, resp, err)
112113
if ctx != nil {
113114
_, _ = addGitHubAPIErrorToContext(ctx, apiErr) // Explicitly ignore error for graceful handling
114115
}
115-
return mcp.NewToolResultErrorFromErr(message, err)
116+
return utils.NewToolResultErrorFromErr(message, err)
116117
}
117118

118-
// NewGitHubGraphQLErrorResponse returns an mcp.NewToolResultError and retains the error in the context for access via middleware
119+
// NewGitHubGraphQLErrorResponse returns an utils.NewToolResultError( and retains the error in the context for access via middleware
119120
func NewGitHubGraphQLErrorResponse(ctx context.Context, message string, err error) *mcp.CallToolResult {
120121
graphQLErr := newGitHubGraphQLError(message, err)
121122
if ctx != nil {
122123
_, _ = addGitHubGraphQLErrorToContext(ctx, graphQLErr) // Explicitly ignore error for graceful handling
123124
}
124-
return mcp.NewToolResultErrorFromErr(message, err)
125+
return utils.NewToolResultErrorFromErr(message, err)
125126
}
Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
11
{
22
"annotations": {
3-
"title": "Add comment to issue",
4-
"readOnlyHint": false
3+
"title": "Add comment to issue"
54
},
65
"description": "Add a comment to a specific issue in a GitHub repository.",
76
"inputSchema": {
7+
"required": [
8+
"owner",
9+
"repo",
10+
"issue_number",
11+
"body"
12+
],
813
"properties": {
914
"body": {
10-
"description": "Comment content",
11-
"type": "string"
15+
"type": "string",
16+
"description": "Comment content"
1217
},
1318
"issue_number": {
14-
"description": "Issue number to comment on",
15-
"type": "number"
19+
"type": "number",
20+
"description": "Issue number to comment on"
1621
},
1722
"owner": {
18-
"description": "Repository owner",
19-
"type": "string"
23+
"type": "string",
24+
"description": "Repository owner"
2025
},
2126
"repo": {
22-
"description": "Repository name",
23-
"type": "string"
27+
"type": "string",
28+
"description": "Repository name"
2429
}
25-
},
26-
"required": [
27-
"owner",
28-
"repo",
29-
"issue_number",
30-
"body"
31-
],
32-
"type": "object"
30+
}
3331
},
3432
"name": "add_issue_comment"
3533
}

0 commit comments

Comments
 (0)