Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: partition tools by product/feature#188

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
SamMorrowDrums merged 1 commit intomainfrompartition-tools
Apr 14, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
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
57 changes: 47 additions & 10 deletionscmd/github-mcp-server/main.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,12 +44,16 @@ var (
iferr!=nil {
stdlog.Fatal("Failed to initialize logger:",err)
}

enabledToolsets:=viper.GetStringSlice("toolsets")

logCommands:=viper.GetBool("enable-command-logging")
cfg:=runConfig{
readOnly:readOnly,
logger:logger,
logCommands:logCommands,
exportTranslations:exportTranslations,
enabledToolsets:enabledToolsets,
}
iferr:=runStdioServer(cfg);err!=nil {
stdlog.Fatal("failed to run stdio server:",err)
Expand All@@ -62,26 +66,30 @@ func init() {
cobra.OnInitialize(initConfig)

// Add global flags that will be shared by all commands
rootCmd.PersistentFlags().StringSlice("toolsets",github.DefaultTools,"An optional comma separated list of groups of tools to allow, defaults to enabling all")
rootCmd.PersistentFlags().Bool("dynamic-toolsets",false,"Enable dynamic toolsets")
rootCmd.PersistentFlags().Bool("read-only",false,"Restrict the server to read-only operations")
rootCmd.PersistentFlags().String("log-file","","Path to log file")
rootCmd.PersistentFlags().Bool("enable-command-logging",false,"When enabled, the server will log all command requests and responses to the log file")
rootCmd.PersistentFlags().Bool("export-translations",false,"Save translations to a JSON file")
rootCmd.PersistentFlags().String("gh-host","","Specify the GitHub hostname (for GitHub Enterprise etc.)")

// Bind flag to viper
_=viper.BindPFlag("toolsets",rootCmd.PersistentFlags().Lookup("toolsets"))
_=viper.BindPFlag("dynamic_toolsets",rootCmd.PersistentFlags().Lookup("dynamic-toolsets"))
_=viper.BindPFlag("read-only",rootCmd.PersistentFlags().Lookup("read-only"))
_=viper.BindPFlag("log-file",rootCmd.PersistentFlags().Lookup("log-file"))
_=viper.BindPFlag("enable-command-logging",rootCmd.PersistentFlags().Lookup("enable-command-logging"))
_=viper.BindPFlag("export-translations",rootCmd.PersistentFlags().Lookup("export-translations"))
_=viper.BindPFlag("gh-host",rootCmd.PersistentFlags().Lookup("gh-host"))
_=viper.BindPFlag("host",rootCmd.PersistentFlags().Lookup("gh-host"))

// Add subcommands
rootCmd.AddCommand(stdioCmd)
}

funcinitConfig() {
// Initialize Viper configuration
viper.SetEnvPrefix("APP")
viper.SetEnvPrefix("github")
viper.AutomaticEnv()
}

Expand All@@ -107,6 +115,7 @@ type runConfig struct {
logger*log.Logger
logCommandsbool
exportTranslationsbool
enabledToolsets []string
}

funcrunStdioServer(cfgrunConfig)error {
Expand All@@ -115,18 +124,14 @@ func runStdioServer(cfg runConfig) error {
deferstop()

// Create GH client
token:=os.Getenv("GITHUB_PERSONAL_ACCESS_TOKEN")
token:=viper.GetString("personal_access_token")
iftoken=="" {
cfg.logger.Fatal("GITHUB_PERSONAL_ACCESS_TOKEN not set")
}
ghClient:=gogithub.NewClient(nil).WithAuthToken(token)
ghClient.UserAgent=fmt.Sprintf("github-mcp-server/%s",version)

// Check GH_HOST env var first, then fall back to viper config
host:=os.Getenv("GH_HOST")
ifhost=="" {
host=viper.GetString("gh-host")
}
host:=viper.GetString("host")

ifhost!="" {
varerrerror
Expand All@@ -149,8 +154,40 @@ func runStdioServer(cfg runConfig) error {
hooks:=&server.Hooks{
OnBeforeInitialize: []server.OnBeforeInitializeFunc{beforeInit},
}
// Create
ghServer:=github.NewServer(getClient,version,cfg.readOnly,t,server.WithHooks(hooks))
// Create server
ghServer:=github.NewServer(version,server.WithHooks(hooks))

enabled:=cfg.enabledToolsets
dynamic:=viper.GetBool("dynamic_toolsets")
ifdynamic {
// filter "all" from the enabled toolsets
enabled=make([]string,0,len(cfg.enabledToolsets))
for_,toolset:=rangecfg.enabledToolsets {
iftoolset!="all" {
enabled=append(enabled,toolset)
}
}
}

// Create default toolsets
toolsets,err:=github.InitToolsets(enabled,cfg.readOnly,getClient,t)
context:=github.InitContextToolset(getClient,t)

iferr!=nil {
stdlog.Fatal("Failed to initialize toolsets:",err)
}

// Register resources with the server
github.RegisterResources(ghServer,getClient,t)
// Register the tools with the server
toolsets.RegisterTools(ghServer)
context.RegisterTools(ghServer)

ifdynamic {
dynamic:=github.InitDynamicToolset(ghServer,toolsets,t)
dynamic.RegisterTools(ghServer)
}

stdioServer:=server.NewStdioServer(ghServer)

stdLogger:=stdlog.New(cfg.logger.Writer(),"stdioserver",0)
Expand Down
2 changes: 1 addition & 1 deletiongo.mod
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@ require (
github.com/docker/dockerv28.0.4+incompatible
github.com/google/go-cmpv0.7.0
github.com/google/go-github/v69v69.2.0
github.com/mark3labs/mcp-gov0.18.0
github.com/mark3labs/mcp-gov0.20.1
github.com/migueleliasweb/go-github-mockv1.1.0
github.com/sirupsen/logrusv1.9.3
github.com/spf13/cobrav1.9.1
Expand Down
4 changes: 2 additions & 2 deletionsgo.sum
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,8 +57,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/prettyv0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/textv0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/textv0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mark3labs/mcp-gov0.18.0 h1:YuhgIVjNlTG2ZOwmrkORWyPTp0dz1opPEqvsPtySXao=
github.com/mark3labs/mcp-gov0.18.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
github.com/mark3labs/mcp-gov0.20.1 h1:E1Bbx9K8d8kQmDZ1QHblM38c7UU2evQ2LlkANk1U/zw=
github.com/mark3labs/mcp-gov0.20.1/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
github.com/migueleliasweb/go-github-mockv1.1.0 h1:GKaOBPsrPGkAKgtfuWY8MclS1xR6MInkx1SexJucMwE=
github.com/migueleliasweb/go-github-mockv1.1.0/go.mod h1:pYe/XlGs4BGMfRY4vmeixVsODHnVDDhJ9zoi0qzSMHc=
github.com/moby/docker-image-specv1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
Expand Down
49 changes: 49 additions & 0 deletionspkg/github/context_tools.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
package github

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"

"github.com/github/github-mcp-server/pkg/translations"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)

// GetMe creates a tool to get details of the authenticated user.
funcGetMe(getClientGetClientFn,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
returnmcp.NewTool("get_me",
mcp.WithDescription(t("TOOL_GET_ME_DESCRIPTION","Get details of the authenticated GitHub user. Use this when a request include\"me\",\"my\"...")),
mcp.WithString("reason",
mcp.Description("Optional: reason the session was created"),
),
),
func(ctx context.Context,_ mcp.CallToolRequest) (*mcp.CallToolResult,error) {
client,err:=getClient(ctx)
iferr!=nil {
returnnil,fmt.Errorf("failed to get GitHub client: %w",err)
}
user,resp,err:=client.Users.Get(ctx,"")
iferr!=nil {
returnnil,fmt.Errorf("failed to get user: %w",err)
}
deferfunc() {_=resp.Body.Close() }()

ifresp.StatusCode!=http.StatusOK {
body,err:=io.ReadAll(resp.Body)
iferr!=nil {
returnnil,fmt.Errorf("failed to read response body: %w",err)
}
returnmcp.NewToolResultError(fmt.Sprintf("failed to get user: %s",string(body))),nil
}

r,err:=json.Marshal(user)
iferr!=nil {
returnnil,fmt.Errorf("failed to marshal user: %w",err)
}

returnmcp.NewToolResultText(string(r)),nil
}
}
132 changes: 132 additions & 0 deletionspkg/github/context_tools_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
package github

import (
"context"
"encoding/json"
"net/http"
"testing"
"time"

"github.com/github/github-mcp-server/pkg/translations"
"github.com/google/go-github/v69/github"
"github.com/migueleliasweb/go-github-mock/src/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_GetMe(t *testing.T) {
// Verify tool definition
mockClient := github.NewClient(nil)
tool, _ := GetMe(stubGetClientFn(mockClient), translations.NullTranslationHelper)

assert.Equal(t, "get_me", tool.Name)
assert.NotEmpty(t, tool.Description)
assert.Contains(t, tool.InputSchema.Properties, "reason")
assert.Empty(t, tool.InputSchema.Required) // No required parameters

// Setup mock user response
mockUser := &github.User{
Login: github.Ptr("testuser"),
Name: github.Ptr("Test User"),
Email: github.Ptr("test@example.com"),
Bio: github.Ptr("GitHub user for testing"),
Company: github.Ptr("Test Company"),
Location: github.Ptr("Test Location"),
HTMLURL: github.Ptr("https://github.com/testuser"),
CreatedAt: &github.Timestamp{Time: time.Now().Add(-365 * 24 * time.Hour)},
Type: github.Ptr("User"),
Plan: &github.Plan{
Name: github.Ptr("pro"),
},
}

tests := []struct {
name string
mockedClient *http.Client
requestArgs map[string]interface{}
expectError bool
expectedUser *github.User
expectedErrMsg string
}{
{
name: "successful get user",
mockedClient: mock.NewMockedHTTPClient(
mock.WithRequestMatch(
mock.GetUser,
mockUser,
),
),
requestArgs: map[string]interface{}{},
expectError: false,
expectedUser: mockUser,
},
{
name: "successful get user with reason",
mockedClient: mock.NewMockedHTTPClient(
mock.WithRequestMatch(
mock.GetUser,
mockUser,
),
),
requestArgs: map[string]interface{}{
"reason": "Testing API",
},
expectError: false,
expectedUser: mockUser,
},
{
name: "get user fails",
mockedClient: mock.NewMockedHTTPClient(
mock.WithRequestMatchHandler(
mock.GetUser,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"message": "Unauthorized"}`))
}),
),
),
requestArgs: map[string]interface{}{},
expectError: true,
expectedErrMsg: "failed to get user",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Setup client with mock
client := github.NewClient(tc.mockedClient)
_, handler := GetMe(stubGetClientFn(client), translations.NullTranslationHelper)

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

// Call handler
result, err := handler(context.Background(), request)

// Verify results
if tc.expectError {
require.Error(t, err)
assert.Contains(t, err.Error(), tc.expectedErrMsg)
return
}

require.NoError(t, err)

// Parse result and get text content if no error
textContent := getTextResult(t, result)

// Unmarshal and verify the result
var returnedUser github.User
err = json.Unmarshal([]byte(textContent.Text), &returnedUser)
require.NoError(t, err)

// Verify user details
assert.Equal(t, *tc.expectedUser.Login, *returnedUser.Login)
assert.Equal(t, *tc.expectedUser.Name, *returnedUser.Name)
assert.Equal(t, *tc.expectedUser.Email, *returnedUser.Email)
assert.Equal(t, *tc.expectedUser.Bio, *returnedUser.Bio)
assert.Equal(t, *tc.expectedUser.HTMLURL, *returnedUser.HTMLURL)
assert.Equal(t, *tc.expectedUser.Type, *returnedUser.Type)
})
}
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp