Skip to content
Open
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
d69b616
Move some utilities to go SDK
omgitsads Nov 13, 2025
8084335
Remove commented out tool
omgitsads Nov 13, 2025
5a92f9c
Add intermediate structs for toolsets
omgitsads Nov 13, 2025
385dd8d
Move to go-sdk IOTransport
omgitsads Nov 13, 2025
28bc3f4
Update context tools and tests to use ToolHandlerFor with typed argum…
omgitsads Nov 13, 2025
42ada5f
comment out broken tools for now, we'll tackle them one by one
omgitsads Nov 13, 2025
afc455d
fix schema issues
omgitsads Nov 13, 2025
b2f07e5
Get tests fully working
omgitsads Nov 17, 2025
11a154c
Add sdk migration agent
omgitsads Nov 17, 2025
5483a5b
Lets try actually making the changes in the subagent
omgitsads Nov 17, 2025
cabc7b6
Be explicit about the files to use
omgitsads Nov 17, 2025
99f2d8d
Update about toolsnaps
omgitsads Nov 17, 2025
6fa137b
Add info to allow this to be parallelized
omgitsads Nov 17, 2025
2b323c2
Dont use the request var
omgitsads Nov 17, 2025
6ce7550
Take any ToolHandlerFor
omgitsads Nov 17, 2025
5b4c6df
More info about moved files
omgitsads Nov 17, 2025
655bcca
move files rather than commenting out
omgitsads Nov 17, 2025
c946ada
Move files back, use build tags
omgitsads Nov 17, 2025
3620126
Update licenses
omgitsads Nov 18, 2025
1d257b1
Update agent to use build tags
omgitsads Nov 18, 2025
1e7a3f0
Merge branch 'main' into omgitsads/go-sdk
omgitsads Nov 18, 2025
9f25ebe
Remove dupe import from merge conflict
omgitsads Nov 18, 2025
faa90d2
fix linter issues
omgitsads Nov 18, 2025
b1ac345
Don't assert without a testing.T
omgitsads Nov 18, 2025
1286e06
just return the tool & handler
omgitsads Nov 18, 2025
102181d
use lowercase strings for the jsonschema types
omgitsads Nov 18, 2025
3f03753
Update cmd/github-mcp-server/generate_docs.go
omgitsads Nov 18, 2025
407a974
Add Close method to IOLogger to close underlying reader and writer
omgitsads Nov 18, 2025
5ab610c
Merge branch 'omgitsads/go-sdk' of https://github.com/github/github-m…
omgitsads Nov 18, 2025
cd77c13
Dont bubble up an error for getClient
omgitsads Nov 18, 2025
6c07546
Migrate gists toolset to modelcontextprotocol/go-sdk (#1431)
Copilot Nov 19, 2025
a405519
Migrate code-scanning toolset to modelcontextprotocol/go-sdk (#1430)
Copilot Nov 19, 2025
eab4876
Merge branch 'omgitsads/go-sdk' of https://github.com/github/github-m…
omgitsads Nov 19, 2025
c06ace3
Migrate security_advisories toolset to modelcontextprotocol/go-sdk (#…
Copilot Nov 19, 2025
66e6ad5
Migrate secret_scanning toolset to modelcontextprotocol/go-sdk (#1436)
Copilot Nov 19, 2025
ef60ef7
Merge branch 'omgitsads/go-sdk' of https://github.com/github/github-m…
omgitsads Nov 19, 2025
eaf411c
Migrate git toolset to modelcontextprotocol/go-sdk (#1432)
Copilot Nov 19, 2025
9bf905b
Migrate labels toolset to modelcontextprotocol/go-sdk (#1433)
Copilot Nov 19, 2025
726c683
Migrate issues toolset to modelcontextprotocol/go-sdk (#1440)
Copilot Nov 20, 2025
1b769a5
Migrate dependabot toolset to modelcontextprotocol/go-sdk (#1429)
Copilot Nov 20, 2025
0a19bf4
Migrate Repository Resources to the Go SDK (#1457)
omgitsads Nov 20, 2025
948fe76
Fix handling of multi path resources (#1458)
omgitsads Nov 20, 2025
42b5533
Migrate dynamic toolset to modelcontextprotocol/go-sdk (#1450)
Copilot Nov 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 24 additions & 22 deletions cmd/github-mcp-server/generate_docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import (
"github.com/github/github-mcp-server/pkg/toolsets"
"github.com/github/github-mcp-server/pkg/translations"
gogithub "github.com/google/go-github/v79/github"
"github.com/mark3labs/mcp-go/mcp"
"github.com/google/jsonschema-go/jsonschema"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/shurcooL/githubv4"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -224,7 +225,16 @@ func generateToolDoc(tool mcp.Tool) string {
lines = append(lines, fmt.Sprintf("- **%s** - %s", tool.Name, tool.Annotations.Title))

// Parameters
schema := tool.InputSchema
if tool.InputSchema == nil {
lines = append(lines, " - No parameters required")
return strings.Join(lines, "\n")
}
schema, ok := tool.InputSchema.(*jsonschema.Schema)
if !ok || schema == nil {
lines = append(lines, " - No parameters required")
return strings.Join(lines, "\n")
}

if len(schema.Properties) > 0 {
// Get parameter names and sort them for deterministic order
var paramNames []string
Expand All @@ -241,30 +251,22 @@ func generateToolDoc(tool mcp.Tool) string {
requiredStr = "required"
}

// Get the type and description
typeStr := "unknown"
description := ""

if propMap, ok := prop.(map[string]interface{}); ok {
if typeVal, ok := propMap["type"].(string); ok {
if typeVal == "array" {
if items, ok := propMap["items"].(map[string]interface{}); ok {
if itemType, ok := items["type"].(string); ok {
typeStr = itemType + "[]"
}
} else {
typeStr = "array"
}
} else {
typeStr = typeVal
}
}
var typeStr, description string

if desc, ok := propMap["description"].(string); ok {
description = desc
// Get the type and description
switch prop.Type {
case "array":
if prop.Items != nil {
typeStr = prop.Items.Type + "[]"
} else {
typeStr = "array"
}
default:
typeStr = prop.Type
}

description = prop.Description

paramLine := fmt.Sprintf(" - `%s`: %s (%s, %s)", propName, description, typeStr, requiredStr)
lines = append(lines, paramLine)
}
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.24.0

require (
github.com/google/go-github/v79 v79.0.0
github.com/google/jsonschema-go v0.3.0
github.com/josephburnett/jd v1.9.2
github.com/mark3labs/mcp-go v0.36.0
github.com/microcosm-cc/bluemonday v1.0.27
Expand Down Expand Up @@ -40,6 +41,7 @@ require (
github.com/google/go-querystring v1.1.0
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/modelcontextprotocol/go-sdk v1.1.0
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
Expand All @@ -52,7 +54,7 @@ require (
github.com/spf13/pflag v1.0.10
github.com/subosito/gotenv v1.6.0 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/time v0.5.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ github.com/google/go-github/v79 v79.0.0 h1:MdodQojuFPBhmtwHiBcIGLw/e/wei2PvFX9nd
github.com/google/go-github/v79 v79.0.0/go.mod h1:OAFbNhq7fQwohojb06iIIQAB9CBGYLq999myfUFnrS4=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
Expand Down Expand Up @@ -63,6 +65,8 @@ github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwX
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/migueleliasweb/go-github-mock v1.3.0 h1:2sVP9JEMB2ubQw1IKto3/fzF51oFC6eVWOOFDgQoq88=
github.com/migueleliasweb/go-github-mock v1.3.0/go.mod h1:ipQhV8fTcj/G6m7BKzin08GaJ/3B5/SonRAkgrk0zCY=
github.com/modelcontextprotocol/go-sdk v1.1.0 h1:Qjayg53dnKC4UZ+792W21e4BpwEZBzwgRW6LrjLWSwA=
github.com/modelcontextprotocol/go-sdk v1.1.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
Expand Down Expand Up @@ -112,6 +116,8 @@ golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
Expand Down
136 changes: 77 additions & 59 deletions internal/ghmcp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"
"io"
"log"
"log/slog"
"net/http"
"net/url"
Expand All @@ -20,8 +19,7 @@ import (
"github.com/github/github-mcp-server/pkg/raw"
"github.com/github/github-mcp-server/pkg/translations"
gogithub "github.com/google/go-github/v79/github"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/shurcooL/githubv4"
)

Expand Down Expand Up @@ -54,11 +52,12 @@ type MCPServerConfig struct {

// LockdownMode indicates if we should enable lockdown mode
LockdownMode bool
}

const stdioServerLogPrefix = "stdioserver"
// Logger is used for logging within the server
Logger *slog.Logger
}

func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) {
apiHost, err := parseAPIHost(cfg.Host)
if err != nil {
return nil, fmt.Errorf("failed to parse API host: %w", err)
Expand All @@ -81,34 +80,6 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
} // We're going to wrap the Transport later in beforeInit
gqlClient := githubv4.NewEnterpriseClient(apiHost.graphqlURL.String(), gqlHTTPClient)

// When a client send an initialize request, update the user agent to include the client info.
beforeInit := func(_ context.Context, _ any, message *mcp.InitializeRequest) {
userAgent := fmt.Sprintf(
"github-mcp-server/%s (%s/%s)",
cfg.Version,
message.Params.ClientInfo.Name,
message.Params.ClientInfo.Version,
)

restClient.UserAgent = userAgent

gqlHTTPClient.Transport = &userAgentTransport{
transport: gqlHTTPClient.Transport,
agent: userAgent,
}
}

hooks := &server.Hooks{
OnBeforeInitialize: []server.OnBeforeInitializeFunc{beforeInit},
OnBeforeAny: []server.BeforeAnyHookFunc{
func(ctx context.Context, _ any, _ mcp.MCPMethod, _ any) {
// Ensure the context is cleared of any previous errors
// as context isn't propagated through middleware
errors.ContextWithGitHubErrors(ctx)
},
},
}

enabledToolsets := cfg.EnabledToolsets

// If dynamic toolsets are enabled, remove "all" from the enabled toolsets
Expand All @@ -135,10 +106,14 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
// Generate instructions based on enabled toolsets
instructions := github.GenerateInstructions(enabledToolsets)

ghServer := github.NewServer(cfg.Version,
server.WithInstructions(instructions),
server.WithHooks(hooks),
)
ghServer := github.NewServer(cfg.Version, &mcp.ServerOptions{
Instructions: instructions,
Logger: cfg.Logger,
})

// Add middlewares
ghServer.AddReceivingMiddleware(addGitHubAPIErrorToContext)
ghServer.AddReceivingMiddleware(addUserAgentsMiddleware(cfg, restClient, gqlHTTPClient))

getClient := func(_ context.Context) (*gogithub.Client, error) {
return restClient, nil // closing over client
Expand Down Expand Up @@ -229,23 +204,6 @@ func RunStdioServer(cfg StdioServerConfig) error {

t, dumpTranslations := translations.TranslationHelper()

ghServer, err := NewMCPServer(MCPServerConfig{
Version: cfg.Version,
Host: cfg.Host,
Token: cfg.Token,
EnabledToolsets: cfg.EnabledToolsets,
DynamicToolsets: cfg.DynamicToolsets,
ReadOnly: cfg.ReadOnly,
Translator: t,
ContentWindowSize: cfg.ContentWindowSize,
LockdownMode: cfg.LockdownMode,
})
if err != nil {
return fmt.Errorf("failed to create MCP server: %w", err)
}

stdioServer := server.NewStdioServer(ghServer)

var slogHandler slog.Handler
var logOutput io.Writer
if cfg.LogFilePath != "" {
Expand All @@ -261,8 +219,22 @@ func RunStdioServer(cfg StdioServerConfig) error {
}
logger := slog.New(slogHandler)
logger.Info("starting server", "version", cfg.Version, "host", cfg.Host, "dynamicToolsets", cfg.DynamicToolsets, "readOnly", cfg.ReadOnly, "lockdownEnabled", cfg.LockdownMode)
stdLogger := log.New(logOutput, stdioServerLogPrefix, 0)
stdioServer.SetErrorLogger(stdLogger)

ghServer, err := NewMCPServer(MCPServerConfig{
Version: cfg.Version,
Host: cfg.Host,
Token: cfg.Token,
EnabledToolsets: cfg.EnabledToolsets,
DynamicToolsets: cfg.DynamicToolsets,
ReadOnly: cfg.ReadOnly,
Translator: t,
ContentWindowSize: cfg.ContentWindowSize,
LockdownMode: cfg.LockdownMode,
Logger: logger,
})
if err != nil {
return fmt.Errorf("failed to create MCP server: %w", err)
}

if cfg.ExportTranslations {
// Once server is initialized, all translations are loaded
Expand All @@ -272,15 +244,20 @@ func RunStdioServer(cfg StdioServerConfig) error {
// Start listening for messages
errC := make(chan error, 1)
go func() {
in, out := io.Reader(os.Stdin), io.Writer(os.Stdout)
var in io.ReadCloser
var out io.WriteCloser

in = os.Stdin
out = os.Stdout

if cfg.EnableCommandLogging {
loggedIO := mcplog.NewIOLogger(in, out, logger)
in, out = loggedIO, loggedIO
}

// enable GitHub errors in the context
ctx := errors.ContextWithGitHubErrors(ctx)
errC <- stdioServer.Listen(ctx, in, out)
errC <- ghServer.Run(ctx, &mcp.IOTransport{Reader: in, Writer: out})
}()

// Output github-mcp-server string
Expand Down Expand Up @@ -497,3 +474,44 @@ func (t *bearerAuthTransport) RoundTrip(req *http.Request) (*http.Response, erro
req.Header.Set("Authorization", "Bearer "+t.token)
return t.transport.RoundTrip(req)
}

func addGitHubAPIErrorToContext(next mcp.MethodHandler) mcp.MethodHandler {
return func(ctx context.Context, method string, req mcp.Request) (result mcp.Result, err error) {
// Ensure the context is cleared of any previous errors
// as context isn't propagated through middleware
ctx = errors.ContextWithGitHubErrors(ctx)
return next(ctx, method, req)
}
}

func addUserAgentsMiddleware(cfg MCPServerConfig, restClient *gogithub.Client, gqlHTTPClient *http.Client) func(next mcp.MethodHandler) mcp.MethodHandler {
return func(next mcp.MethodHandler) mcp.MethodHandler {
return func(ctx context.Context, method string, request mcp.Request) (result mcp.Result, err error) {
if method != "initialize" {
return next(ctx, method, request)
}

initializeRequest, ok := request.(*mcp.InitializeRequest)
if !ok {
return next(ctx, method, request)
}

message := initializeRequest
userAgent := fmt.Sprintf(
"github-mcp-server/%s (%s/%s)",
cfg.Version,
message.Params.ClientInfo.Name,
message.Params.ClientInfo.Version,
)

restClient.UserAgent = userAgent

gqlHTTPClient.Transport = &userAgentTransport{
transport: gqlHTTPClient.Transport,
agent: userAgent,
}

return next(ctx, method, request)
}
}
}
7 changes: 4 additions & 3 deletions pkg/errors/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import (
"context"
"fmt"

"github.com/github/github-mcp-server/pkg/utils"
"github.com/google/go-github/v79/github"
"github.com/mark3labs/mcp-go/mcp"
"github.com/modelcontextprotocol/go-sdk/mcp"
)

type GitHubAPIError struct {
Expand Down Expand Up @@ -112,7 +113,7 @@ func NewGitHubAPIErrorResponse(ctx context.Context, message string, resp *github
if ctx != nil {
_, _ = addGitHubAPIErrorToContext(ctx, apiErr) // Explicitly ignore error for graceful handling
}
return mcp.NewToolResultErrorFromErr(message, err)
return utils.NewToolResultErrorFromErr(message, err)
}

// NewGitHubGraphQLErrorResponse returns an mcp.NewToolResultError and retains the error in the context for access via middleware
Expand All @@ -121,5 +122,5 @@ func NewGitHubGraphQLErrorResponse(ctx context.Context, message string, err erro
if ctx != nil {
_, _ = addGitHubGraphQLErrorToContext(ctx, graphQLErr) // Explicitly ignore error for graceful handling
}
return mcp.NewToolResultErrorFromErr(message, err)
return utils.NewToolResultErrorFromErr(message, err)
}
Loading
Loading