-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expose ghmcp package #437
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Expose ghmcp package #437
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -22,6 +22,10 @@ import ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/sirupsen/logrus" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// TokenProvider is a function that returns the current GitHub token. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// This allows for dynamic token refresh without restarting the server. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type TokenProvider func() string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type MCPServerConfig struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Version of the server | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Version string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -30,8 +34,13 @@ type MCPServerConfig struct { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Host string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// GitHub Token to authenticate with the GitHub API | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Deprecated: Use TokenProvider for dynamic token support | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Token string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// TokenProvider is a function that returns the current GitHub token | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// If set, this takes precedence over Token | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TokenProvider TokenProvider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// EnabledToolsets is a list of toolsets to enable | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// See: https://github.com/github/github-mcp-server?tab=readme-ov-file#tool-configuration | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
EnabledToolsets []string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -53,8 +62,25 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, fmt.Errorf("failed to parse API host: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Construct our REST client | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
restClient := gogithub.NewClient(nil).WithAuthToken(cfg.Token) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Set up token provider - use the provided one or create one from static token | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
tokenProvider := cfg.TokenProvider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if tokenProvider == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if cfg.Token == "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, fmt.Errorf("either Token or TokenProvider must be set") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Create a token provider that returns the static token | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
staticToken := cfg.Token | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
tokenProvider = func() string { return staticToken } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Construct our REST client with a custom transport | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
restHTTPClient := &http.Client{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Transport: &tokenProviderTransport{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
transport: http.DefaultTransport, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
tokenProvider: tokenProvider, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
restClient := gogithub.NewClient(restHTTPClient) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
restClient.UserAgent = fmt.Sprintf("github-mcp-server/%s", cfg.Version) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
restClient.BaseURL = apiHost.baseRESTURL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
restClient.UploadURL = apiHost.uploadURL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -63,9 +89,9 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// We're using NewEnterpriseClient here unconditionally as opposed to NewClient because we already | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// did the necessary API host parsing so that github.com will return the correct URL anyway. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
gqlHTTPClient := &http.Client{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Transport: &bearerAuthTransport{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
transport: http.DefaultTransport, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
token: cfg.Token, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Transport: &bearerAuthProviderTransport{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
transport: http.DefaultTransport, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
tokenProvider: tokenProvider, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} // We're going to wrap the Transport later in beforeInit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
gqlClient := githubv4.NewEnterpriseClient(apiHost.graphqlURL.String(), gqlHTTPClient) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -147,8 +173,13 @@ type StdioServerConfig struct { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Host string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// GitHub Token to authenticate with the GitHub API | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Deprecated: Use TokenProvider for dynamic token support | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Token string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// TokenProvider is a function that returns the current GitHub token | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// If set, this takes precedence over Token | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TokenProvider TokenProvider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// EnabledToolsets is a list of toolsets to enable | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// See: https://github.com/github/github-mcp-server?tab=readme-ov-file#tool-configuration | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
EnabledToolsets []string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -183,6 +214,7 @@ func RunStdioServer(cfg StdioServerConfig) error { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Version: cfg.Version, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Host: cfg.Host, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Token: cfg.Token, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TokenProvider: cfg.TokenProvider, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
EnabledToolsets: cfg.EnabledToolsets, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
DynamicToolsets: cfg.DynamicToolsets, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ReadOnly: cfg.ReadOnly, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -368,6 +400,7 @@ func (t *userAgentTransport) RoundTrip(req *http.Request) (*http.Response, error | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return t.transport.RoundTrip(req) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// bearerAuthTransport is deprecated - use bearerAuthProviderTransport | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type bearerAuthTransport struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
transport http.RoundTripper | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
token string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -378,3 +411,29 @@ func (t *bearerAuthTransport) RoundTrip(req *http.Request) (*http.Response, erro | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
req.Header.Set("Authorization", "Bearer "+t.token) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return t.transport.RoundTrip(req) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// bearerAuthProviderTransport uses a token provider for dynamic token support | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type bearerAuthProviderTransport struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
transport http.RoundTripper | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
tokenProvider TokenProvider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (t *bearerAuthProviderTransport) RoundTrip(req *http.Request) (*http.Response, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
req = req.Clone(req.Context()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
token := t.tokenProvider() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
req.Header.Set("Authorization", "Bearer "+token) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return t.transport.RoundTrip(req) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// tokenProviderTransport adds GitHub authentication using a token provider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type tokenProviderTransport struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
transport http.RoundTripper | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
tokenProvider TokenProvider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (t *tokenProviderTransport) RoundTrip(req *http.Request) (*http.Response, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
req = req.Clone(req.Context()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
token := t.tokenProvider() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
req.Header.Set("Authorization", "Bearer "+token) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return t.transport.RoundTrip(req) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+415
to
+439
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,230 @@ | ||||||
# GitHub MCP Server Go Library | ||||||
|
||||||
This package provides a Go library interface to the GitHub MCP Server, allowing you to embed the server functionality in your own Go applications. | ||||||
|
||||||
## Installation | ||||||
|
||||||
```bash | ||||||
go get github.com/github/github-mcp-server/pkg/ghmcp | ||||||
``` | ||||||
|
||||||
## Usage | ||||||
|
||||||
### Running a Stdio Server with Static Token | ||||||
|
||||||
The most common use case is running the MCP server using stdio for communication: | ||||||
|
||||||
```go | ||||||
package main | ||||||
|
||||||
import ( | ||||||
"log" | ||||||
"os" | ||||||
|
||||||
"github.com/github/github-mcp-server/pkg/ghmcp" | ||||||
) | ||||||
|
||||||
func main() { | ||||||
config := ghmcp.StdioServerConfig{ | ||||||
Version: "1.0.0", | ||||||
Host: "https://github.com", // or your GitHub Enterprise URL | ||||||
Token: os.Getenv("GITHUB_TOKEN"), | ||||||
EnabledToolsets: []string{"repos", "issues", "pulls"}, | ||||||
ReadOnly: false, | ||||||
} | ||||||
|
||||||
if err := ghmcp.RunStdioServer(config); err != nil { | ||||||
log.Fatal(err) | ||||||
} | ||||||
} | ||||||
``` | ||||||
|
||||||
### Running a Stdio Server with Dynamic Token Provider | ||||||
|
||||||
For applications that need to refresh tokens without restarting the server: | ||||||
|
||||||
```go | ||||||
package main | ||||||
|
||||||
import ( | ||||||
"log" | ||||||
"sync" | ||||||
"time" | ||||||
|
||||||
"github.com/github/github-mcp-server/pkg/ghmcp" | ||||||
) | ||||||
|
||||||
// TokenManager manages dynamic token updates | ||||||
type TokenManager struct { | ||||||
mu sync.RWMutex | ||||||
currentToken string | ||||||
} | ||||||
|
||||||
func (tm *TokenManager) GetToken() string { | ||||||
tm.mu.RLock() | ||||||
defer tm.mu.RUnlock() | ||||||
return tm.currentToken | ||||||
} | ||||||
|
||||||
func (tm *TokenManager) UpdateToken(newToken string) { | ||||||
tm.mu.Lock() | ||||||
defer tm.mu.Unlock() | ||||||
tm.currentToken = newToken | ||||||
} | ||||||
|
||||||
func main() { | ||||||
tokenManager := &TokenManager{ | ||||||
currentToken: getInitialToken(), // Your initial token | ||||||
} | ||||||
|
||||||
// The token provider will be called on each API request | ||||||
tokenProvider := func() string { | ||||||
return tokenManager.GetToken() | ||||||
} | ||||||
|
||||||
config := ghmcp.StdioServerConfig{ | ||||||
Version: "1.0.0", | ||||||
Host: "https://github.com", | ||||||
TokenProvider: tokenProvider, // Use TokenProvider instead of Token | ||||||
EnabledToolsets: []string{"repos", "issues", "pulls"}, | ||||||
ReadOnly: false, | ||||||
} | ||||||
|
||||||
// Start a goroutine to refresh the token periodically | ||||||
go func() { | ||||||
for { | ||||||
time.Sleep(30 * time.Minute) | ||||||
newToken := refreshTokenFromAuthService() // Your token refresh logic | ||||||
tokenManager.UpdateToken(newToken) | ||||||
} | ||||||
}() | ||||||
|
||||||
if err := ghmcp.RunStdioServer(config); err != nil { | ||||||
log.Fatal(err) | ||||||
} | ||||||
} | ||||||
``` | ||||||
|
||||||
### Creating a Custom MCP Server | ||||||
|
||||||
For more advanced use cases, you can create an MCP server instance directly: | ||||||
|
||||||
```go | ||||||
package main | ||||||
|
||||||
import ( | ||||||
"log" | ||||||
|
||||||
"github.com/github/github-mcp-server/pkg/ghmcp" | ||||||
"github.com/github/github-mcp-server/pkg/translations" | ||||||
) | ||||||
|
||||||
func main() { | ||||||
config := ghmcp.MCPServerConfig{ | ||||||
Version: "1.0.0", | ||||||
Host: "https://github.com", | ||||||
Token: "your-github-token", | ||||||
EnabledToolsets: []string{"repos", "issues"}, | ||||||
ReadOnly: true, | ||||||
Translator: translations.NullTranslationHelper, | ||||||
} | ||||||
|
||||||
server, err := ghmcp.NewMCPServer(config) | ||||||
if err != nil { | ||||||
log.Fatal(err) | ||||||
} | ||||||
|
||||||
// Use the server instance as needed | ||||||
_ = server | ||||||
} | ||||||
``` | ||||||
|
||||||
## Configuration Options | ||||||
|
||||||
### StdioServerConfig | ||||||
|
||||||
- `Version`: Version of your server | ||||||
- `Host`: GitHub API host (e.g., "https://github.com" or "https://github.enterprise.com") | ||||||
- `Token`: GitHub personal access token (static token, use TokenProvider for dynamic tokens) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||
- `TokenProvider`: Function that returns the current GitHub token (takes precedence over Token) | ||||||
- `EnabledToolsets`: List of toolsets to enable (e.g., "repos", "issues", "pulls", "users", "search") | ||||||
- `DynamicToolsets`: Enable dynamic toolset discovery | ||||||
- `ReadOnly`: Restrict to read-only operations | ||||||
- `ExportTranslations`: Export translations to a JSON file | ||||||
- `EnableCommandLogging`: Log all command requests and responses | ||||||
- `LogFilePath`: Path to log file (defaults to stderr) | ||||||
|
||||||
### MCPServerConfig | ||||||
|
||||||
- `Version`: Version of your server | ||||||
- `Host`: GitHub API host | ||||||
- `Token`: GitHub personal access token (static token, use TokenProvider for dynamic tokens) | ||||||
- `TokenProvider`: Function that returns the current GitHub token (takes precedence over Token) | ||||||
- `EnabledToolsets`: List of toolsets to enable | ||||||
- `DynamicToolsets`: Enable dynamic toolset discovery | ||||||
- `ReadOnly`: Restrict to read-only operations | ||||||
- `Translator`: Translation helper function (use `translations.NullTranslationHelper` for default) | ||||||
|
||||||
## Available Toolsets | ||||||
|
||||||
- `repos`: Repository management tools | ||||||
- `issues`: Issue management tools | ||||||
- `pulls`: Pull request management tools | ||||||
- `users`: User management tools | ||||||
- `search`: Search functionality | ||||||
- `all`: Enable all available toolsets | ||||||
|
||||||
## Token Provider Best Practices | ||||||
|
||||||
When using a `TokenProvider`: | ||||||
|
||||||
1. **Thread Safety**: Ensure your token provider is thread-safe as it will be called concurrently from multiple goroutines. | ||||||
2. **Performance**: The token provider is called on each API request, so it should be fast. Consider caching the token. | ||||||
3. **Error Handling**: The token provider should always return a valid token. Handle errors internally and fall back to a cached token if necessary. | ||||||
4. **Logging**: Be careful not to log the full token. Log only the last few characters for debugging. | ||||||
5. **Graceful Updates**: When updating tokens, ensure there's no downtime. The old token should remain valid until the new one is ready. | ||||||
|
||||||
Example of a production-ready token provider: | ||||||
|
||||||
```go | ||||||
type TokenCache struct { | ||||||
mu sync.RWMutex | ||||||
token string | ||||||
expiry time.Time | ||||||
refreshFunc func() (string, time.Time, error) | ||||||
} | ||||||
|
||||||
func (tc *TokenCache) GetToken() string { | ||||||
tc.mu.RLock() | ||||||
if time.Now().Before(tc.expiry) { | ||||||
defer tc.mu.RUnlock() | ||||||
return tc.token | ||||||
} | ||||||
tc.mu.RUnlock() | ||||||
|
||||||
// Token expired, refresh it | ||||||
tc.mu.Lock() | ||||||
defer tc.mu.Unlock() | ||||||
|
||||||
// Double-check after acquiring write lock | ||||||
if time.Now().Before(tc.expiry) { | ||||||
return tc.token | ||||||
} | ||||||
|
||||||
newToken, newExpiry, err := tc.refreshFunc() | ||||||
if err != nil { | ||||||
// Log error and return cached token | ||||||
log.Printf("Failed to refresh token: %v", err) | ||||||
return tc.token | ||||||
} | ||||||
|
||||||
tc.token = newToken | ||||||
tc.expiry = newExpiry | ||||||
return tc.token | ||||||
} | ||||||
``` | ||||||
|
||||||
## Requirements | ||||||
|
||||||
- Go 1.21 or later | ||||||
- Valid GitHub personal access token with appropriate permissions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make this error message more explicit by referencing the config type and fields, for example:
"MCPServerConfig requires either Token or TokenProvider to be set"
.Copilot uses AI. Check for mistakes.