Fix merge conflicts
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-github/v69/github"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
// NewServer creates a new GitHub MCP server with the specified GH client and logger.
|
||||
|
||||
func NewServer(version string, opts ...server.ServerOption) *server.MCPServer {
|
||||
// Add default options
|
||||
defaultOpts := []server.ServerOption{
|
||||
server.WithToolCapabilities(true),
|
||||
server.WithResourceCapabilities(true, true),
|
||||
server.WithLogging(),
|
||||
}
|
||||
opts = append(defaultOpts, opts...)
|
||||
|
||||
// Create a new MCP server
|
||||
s := server.NewMCPServer(
|
||||
"github-mcp-server",
|
||||
version,
|
||||
opts...,
|
||||
)
|
||||
return s
|
||||
}
|
||||
|
||||
// OptionalParamOK is a helper function that can be used to fetch a requested parameter from the request.
|
||||
// It returns the value, a boolean indicating if the parameter was present, and an error if the type is wrong.
|
||||
func OptionalParamOK[T any](r mcp.CallToolRequest, p string) (value T, ok bool, err error) {
|
||||
// Check if the parameter is present in the request
|
||||
val, exists := r.Params.Arguments[p]
|
||||
if !exists {
|
||||
// Not present, return zero value, false, no error
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the parameter is of the expected type
|
||||
value, ok = val.(T)
|
||||
if !ok {
|
||||
// Present but wrong type
|
||||
err = fmt.Errorf("parameter %s is not of type %T, is %T", p, value, val)
|
||||
ok = true // Set ok to true because the parameter *was* present, even if wrong type
|
||||
return
|
||||
}
|
||||
|
||||
// Present and correct type
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// isAcceptedError checks if the error is an accepted error.
|
||||
func isAcceptedError(err error) bool {
|
||||
var acceptedError *github.AcceptedError
|
||||
return errors.As(err, &acceptedError)
|
||||
}
|
||||
|
||||
// requiredParam is a helper function that can be used to fetch a requested parameter from the request.
|
||||
// It does the following checks:
|
||||
// 1. Checks if the parameter is present in the request.
|
||||
// 2. Checks if the parameter is of the expected type.
|
||||
// 3. Checks if the parameter is not empty, i.e: non-zero value
|
||||
func requiredParam[T comparable](r mcp.CallToolRequest, p string) (T, error) {
|
||||
var zero T
|
||||
|
||||
// Check if the parameter is present in the request
|
||||
if _, ok := r.Params.Arguments[p]; !ok {
|
||||
return zero, fmt.Errorf("missing required parameter: %s", p)
|
||||
}
|
||||
|
||||
// Check if the parameter is of the expected type
|
||||
if _, ok := r.Params.Arguments[p].(T); !ok {
|
||||
return zero, fmt.Errorf("parameter %s is not of type %T", p, zero)
|
||||
}
|
||||
|
||||
if r.Params.Arguments[p].(T) == zero {
|
||||
return zero, fmt.Errorf("missing required parameter: %s", p)
|
||||
|
||||
}
|
||||
|
||||
return r.Params.Arguments[p].(T), nil
|
||||
}
|
||||
|
||||
// RequiredInt is a helper function that can be used to fetch a requested parameter from the request.
|
||||
// It does the following checks:
|
||||
// 1. Checks if the parameter is present in the request.
|
||||
// 2. Checks if the parameter is of the expected type.
|
||||
// 3. Checks if the parameter is not empty, i.e: non-zero value
|
||||
func RequiredInt(r mcp.CallToolRequest, p string) (int, error) {
|
||||
v, err := requiredParam[float64](r, p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(v), nil
|
||||
}
|
||||
|
||||
// OptionalParam is a helper function that can be used to fetch a requested parameter from the request.
|
||||
// It does the following checks:
|
||||
// 1. Checks if the parameter is present in the request, if not, it returns its zero-value
|
||||
// 2. If it is present, it checks if the parameter is of the expected type and returns it
|
||||
func OptionalParam[T any](r mcp.CallToolRequest, p string) (T, error) {
|
||||
var zero T
|
||||
|
||||
// Check if the parameter is present in the request
|
||||
if _, ok := r.Params.Arguments[p]; !ok {
|
||||
return zero, nil
|
||||
}
|
||||
|
||||
// Check if the parameter is of the expected type
|
||||
if _, ok := r.Params.Arguments[p].(T); !ok {
|
||||
return zero, fmt.Errorf("parameter %s is not of type %T, is %T", p, zero, r.Params.Arguments[p])
|
||||
}
|
||||
|
||||
return r.Params.Arguments[p].(T), nil
|
||||
}
|
||||
|
||||
// OptionalIntParam is a helper function that can be used to fetch a requested parameter from the request.
|
||||
// It does the following checks:
|
||||
// 1. Checks if the parameter is present in the request, if not, it returns its zero-value
|
||||
// 2. If it is present, it checks if the parameter is of the expected type and returns it
|
||||
func OptionalIntParam(r mcp.CallToolRequest, p string) (int, error) {
|
||||
v, err := OptionalParam[float64](r, p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(v), nil
|
||||
}
|
||||
|
||||
// OptionalIntParamWithDefault is a helper function that can be used to fetch a requested parameter from the request
|
||||
// similar to optionalIntParam, but it also takes a default value.
|
||||
func OptionalIntParamWithDefault(r mcp.CallToolRequest, p string, d int) (int, error) {
|
||||
v, err := OptionalIntParam(r, p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if v == 0 {
|
||||
return d, nil
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// OptionalBoolParamWithDefault is a helper function that can be used to fetch a requested parameter from the request
|
||||
// similar to optionalParam, but it also takes a default value.
|
||||
func OptionalBoolParamWithDefault(r mcp.CallToolRequest, p string, d bool) (bool, error) {
|
||||
v, err := OptionalParam[bool](r, p)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !v {
|
||||
return d, nil
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// OptionalStringParam is a helper function that can be used to fetch a requested parameter from the request.
|
||||
// It does the following checks:
|
||||
// 1. Checks if the parameter is present in the request, if not, it returns its zero-value
|
||||
// 2. If it is present, it checks if the parameter is of the expected type and returns it
|
||||
func OptionalStringParam(r mcp.CallToolRequest, p string) (string, error) {
|
||||
v, err := OptionalParam[string](r, p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if v == "" {
|
||||
return "", nil
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// OptionalStringParamWithDefault is a helper function that can be used to fetch a requested parameter from the request
|
||||
// similar to optionalParam, but it also takes a default value.
|
||||
func OptionalStringParamWithDefault(r mcp.CallToolRequest, p string, d string) (string, error) {
|
||||
v, err := OptionalParam[string](r, p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if v == "" {
|
||||
return d, nil
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// OptionalStringArrayParam is a helper function that can be used to fetch a requested parameter from the request.
|
||||
// It does the following checks:
|
||||
// 1. Checks if the parameter is present in the request, if not, it returns its zero-value
|
||||
// 2. If it is present, iterates the elements and checks each is a string
|
||||
func OptionalStringArrayParam(r mcp.CallToolRequest, p string) ([]string, error) {
|
||||
// Check if the parameter is present in the request
|
||||
if _, ok := r.Params.Arguments[p]; !ok {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
switch v := r.Params.Arguments[p].(type) {
|
||||
case nil:
|
||||
return []string{}, nil
|
||||
case []string:
|
||||
return v, nil
|
||||
case []any:
|
||||
strSlice := make([]string, len(v))
|
||||
for i, v := range v {
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
return []string{}, fmt.Errorf("parameter %s is not of type string, is %T", p, v)
|
||||
}
|
||||
strSlice[i] = s
|
||||
}
|
||||
return strSlice, nil
|
||||
default:
|
||||
return []string{}, fmt.Errorf("parameter %s could not be coerced to []string, is %T", p, r.Params.Arguments[p])
|
||||
}
|
||||
}
|
||||
|
||||
// WithPagination returns a ToolOption that adds "page" and "perPage" parameters to the tool.
|
||||
// The "page" parameter is optional, min 1. The "perPage" parameter is optional, min 1, max 100.
|
||||
func WithPagination() mcp.ToolOption {
|
||||
return func(tool *mcp.Tool) {
|
||||
mcp.WithNumber("page",
|
||||
mcp.Description("Page number for pagination (min 1)"),
|
||||
mcp.Min(1),
|
||||
)(tool)
|
||||
|
||||
mcp.WithNumber("perPage",
|
||||
mcp.Description("Results per page for pagination (min 1, max 100)"),
|
||||
mcp.Min(1),
|
||||
mcp.Max(100),
|
||||
)(tool)
|
||||
}
|
||||
}
|
||||
|
||||
type PaginationParams struct {
|
||||
page int
|
||||
perPage int
|
||||
}
|
||||
|
||||
// OptionalPaginationParams returns the "page" and "perPage" parameters from the request,
|
||||
// or their default values if not present, "page" default is 1, "perPage" default is 30.
|
||||
// In future, we may want to make the default values configurable, or even have this
|
||||
// function returned from `withPagination`, where the defaults are provided alongside
|
||||
// the min/max values.
|
||||
func OptionalPaginationParams(r mcp.CallToolRequest) (PaginationParams, error) {
|
||||
page, err := OptionalIntParamWithDefault(r, "page", 1)
|
||||
if err != nil {
|
||||
return PaginationParams{}, err
|
||||
}
|
||||
perPage, err := OptionalIntParamWithDefault(r, "perPage", 30)
|
||||
if err != nil {
|
||||
return PaginationParams{}, err
|
||||
}
|
||||
return PaginationParams{
|
||||
page: page,
|
||||
perPage: perPage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -78,6 +78,19 @@ func InitToolsets(passedToolsets []string, readOnly bool, getClient GetClientFn,
|
||||
toolsets.NewServerTool(GetSecretScanningAlert(getClient, t)),
|
||||
toolsets.NewServerTool(ListSecretScanningAlerts(getClient, t)),
|
||||
)
|
||||
|
||||
notifications := toolsets.NewToolset("notifications", "GitHub Notifications related tools").
|
||||
AddReadTools(
|
||||
|
||||
toolsets.NewServerTool(MarkNotificationRead(getClient, t)),
|
||||
toolsets.NewServerTool(MarkAllNotificationsRead(getClient, t)),
|
||||
toolsets.NewServerTool(MarkNotificationDone(getClient, t)),
|
||||
).
|
||||
AddWriteTools(
|
||||
toolsets.NewServerTool(GetNotifications(getClient, t)),
|
||||
toolsets.NewServerTool(GetNotificationThread(getClient, t)),
|
||||
)
|
||||
|
||||
// Keep experiments alive so the system doesn't error out when it's always enabled
|
||||
experiments := toolsets.NewToolset("experiments", "Experimental features that are not considered stable yet")
|
||||
|
||||
@@ -88,6 +101,7 @@ func InitToolsets(passedToolsets []string, readOnly bool, getClient GetClientFn,
|
||||
tsg.AddToolset(pullRequests)
|
||||
tsg.AddToolset(codeSecurity)
|
||||
tsg.AddToolset(secretProtection)
|
||||
tsg.AddToolset(notifications)
|
||||
tsg.AddToolset(experiments)
|
||||
// Enable the requested features
|
||||
|
||||
|
||||
Reference in New Issue
Block a user