345584c9ca
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build and Test Go Project / build (macos-latest) (push) Has been cancelled
Build and Test Go Project / build (ubuntu-latest) (push) Has been cancelled
Build and Test Go Project / build (windows-latest) (push) Has been cancelled
- Renamed deprecated NewServerTool[In, Out] to NewServerToolWithDeps[In, Out] - Updated dynamic_tools.go to use NewServerToolWithDeps (for special case with DynamicToolDependencies) - Renamed NewServerToolWithRawContextHandler to NewServerTool - Updated all call sites in dependencies.go and registry_test.go - All tests pass, linter passes Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
193 lines
7.4 KiB
Go
193 lines
7.4 KiB
Go
package github
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/github/github-mcp-server/pkg/inventory"
|
|
"github.com/github/github-mcp-server/pkg/lockdown"
|
|
"github.com/github/github-mcp-server/pkg/raw"
|
|
"github.com/github/github-mcp-server/pkg/scopes"
|
|
"github.com/github/github-mcp-server/pkg/translations"
|
|
gogithub "github.com/google/go-github/v79/github"
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
"github.com/shurcooL/githubv4"
|
|
)
|
|
|
|
// depsContextKey is the context key for ToolDependencies.
|
|
// Using a private type prevents collisions with other packages.
|
|
type depsContextKey struct{}
|
|
|
|
// ErrDepsNotInContext is returned when ToolDependencies is not found in context.
|
|
var ErrDepsNotInContext = errors.New("ToolDependencies not found in context; use ContextWithDeps to inject")
|
|
|
|
// ContextWithDeps returns a new context with the ToolDependencies stored in it.
|
|
// This is used to inject dependencies at request time rather than at registration time,
|
|
// avoiding expensive closure creation during server initialization.
|
|
//
|
|
// For the local server, this is called once at startup since deps don't change.
|
|
// For the remote server, this is called per-request with request-specific deps.
|
|
func ContextWithDeps(ctx context.Context, deps ToolDependencies) context.Context {
|
|
return context.WithValue(ctx, depsContextKey{}, deps)
|
|
}
|
|
|
|
// DepsFromContext retrieves ToolDependencies from the context.
|
|
// Returns the deps and true if found, or nil and false if not present.
|
|
// Use MustDepsFromContext if you want to panic on missing deps (for handlers
|
|
// that require deps to function).
|
|
func DepsFromContext(ctx context.Context) (ToolDependencies, bool) {
|
|
deps, ok := ctx.Value(depsContextKey{}).(ToolDependencies)
|
|
return deps, ok
|
|
}
|
|
|
|
// MustDepsFromContext retrieves ToolDependencies from the context.
|
|
// Panics if deps are not found - use this in handlers where deps are required.
|
|
func MustDepsFromContext(ctx context.Context) ToolDependencies {
|
|
deps, ok := DepsFromContext(ctx)
|
|
if !ok {
|
|
panic(ErrDepsNotInContext)
|
|
}
|
|
return deps
|
|
}
|
|
|
|
// ToolDependencies defines the interface for dependencies that tool handlers need.
|
|
// This is an interface to allow different implementations:
|
|
// - Local server: stores closures that create clients on demand
|
|
// - Remote server: can store pre-created clients per-request for efficiency
|
|
//
|
|
// The toolsets package uses `any` for deps and tool handlers type-assert to this interface.
|
|
type ToolDependencies interface {
|
|
// GetClient returns a GitHub REST API client
|
|
GetClient(ctx context.Context) (*gogithub.Client, error)
|
|
|
|
// GetGQLClient returns a GitHub GraphQL client
|
|
GetGQLClient(ctx context.Context) (*githubv4.Client, error)
|
|
|
|
// GetRawClient returns a raw content client for GitHub
|
|
GetRawClient(ctx context.Context) (*raw.Client, error)
|
|
|
|
// GetRepoAccessCache returns the lockdown mode repo access cache
|
|
GetRepoAccessCache() *lockdown.RepoAccessCache
|
|
|
|
// GetT returns the translation helper function
|
|
GetT() translations.TranslationHelperFunc
|
|
|
|
// GetFlags returns feature flags
|
|
GetFlags() FeatureFlags
|
|
|
|
// GetContentWindowSize returns the content window size for log truncation
|
|
GetContentWindowSize() int
|
|
}
|
|
|
|
// BaseDeps is the standard implementation of ToolDependencies for the local server.
|
|
// It stores pre-created clients. The remote server can create its own struct
|
|
// implementing ToolDependencies with different client creation strategies.
|
|
type BaseDeps struct {
|
|
// Pre-created clients
|
|
Client *gogithub.Client
|
|
GQLClient *githubv4.Client
|
|
RawClient *raw.Client
|
|
|
|
// Static dependencies
|
|
RepoAccessCache *lockdown.RepoAccessCache
|
|
T translations.TranslationHelperFunc
|
|
Flags FeatureFlags
|
|
ContentWindowSize int
|
|
}
|
|
|
|
// NewBaseDeps creates a BaseDeps with the provided clients and configuration.
|
|
func NewBaseDeps(
|
|
client *gogithub.Client,
|
|
gqlClient *githubv4.Client,
|
|
rawClient *raw.Client,
|
|
repoAccessCache *lockdown.RepoAccessCache,
|
|
t translations.TranslationHelperFunc,
|
|
flags FeatureFlags,
|
|
contentWindowSize int,
|
|
) *BaseDeps {
|
|
return &BaseDeps{
|
|
Client: client,
|
|
GQLClient: gqlClient,
|
|
RawClient: rawClient,
|
|
RepoAccessCache: repoAccessCache,
|
|
T: t,
|
|
Flags: flags,
|
|
ContentWindowSize: contentWindowSize,
|
|
}
|
|
}
|
|
|
|
// GetClient implements ToolDependencies.
|
|
func (d BaseDeps) GetClient(_ context.Context) (*gogithub.Client, error) {
|
|
return d.Client, nil
|
|
}
|
|
|
|
// GetGQLClient implements ToolDependencies.
|
|
func (d BaseDeps) GetGQLClient(_ context.Context) (*githubv4.Client, error) {
|
|
return d.GQLClient, nil
|
|
}
|
|
|
|
// GetRawClient implements ToolDependencies.
|
|
func (d BaseDeps) GetRawClient(_ context.Context) (*raw.Client, error) {
|
|
return d.RawClient, nil
|
|
}
|
|
|
|
// GetRepoAccessCache implements ToolDependencies.
|
|
func (d BaseDeps) GetRepoAccessCache() *lockdown.RepoAccessCache { return d.RepoAccessCache }
|
|
|
|
// GetT implements ToolDependencies.
|
|
func (d BaseDeps) GetT() translations.TranslationHelperFunc { return d.T }
|
|
|
|
// GetFlags implements ToolDependencies.
|
|
func (d BaseDeps) GetFlags() FeatureFlags { return d.Flags }
|
|
|
|
// GetContentWindowSize implements ToolDependencies.
|
|
func (d BaseDeps) GetContentWindowSize() int { return d.ContentWindowSize }
|
|
|
|
// NewTool creates a ServerTool that retrieves ToolDependencies from context at call time.
|
|
// This avoids creating closures at registration time, which is important for performance
|
|
// in servers that create a new server instance per request (like the remote server).
|
|
//
|
|
// The handler function receives deps extracted from context via MustDepsFromContext.
|
|
// Ensure ContextWithDeps is called to inject deps before any tool handlers are invoked.
|
|
//
|
|
// requiredScopes specifies the minimum OAuth scopes needed for this tool.
|
|
// AcceptedScopes are automatically derived using the scope hierarchy (e.g., if
|
|
// public_repo is required, repo is also accepted since repo grants public_repo).
|
|
func NewTool[In, Out any](
|
|
toolset inventory.ToolsetMetadata,
|
|
tool mcp.Tool,
|
|
requiredScopes []scopes.Scope,
|
|
handler func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, Out, error),
|
|
) inventory.ServerTool {
|
|
st := inventory.NewServerToolWithContextHandler(tool, toolset, func(ctx context.Context, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, Out, error) {
|
|
deps := MustDepsFromContext(ctx)
|
|
return handler(ctx, deps, req, args)
|
|
})
|
|
st.RequiredScopes = scopes.ToStringSlice(requiredScopes...)
|
|
st.AcceptedScopes = scopes.ExpandScopes(requiredScopes...)
|
|
return st
|
|
}
|
|
|
|
// NewToolFromHandler creates a ServerTool that retrieves ToolDependencies from context at call time.
|
|
// Use this when you have a handler that conforms to mcp.ToolHandler directly.
|
|
//
|
|
// The handler function receives deps extracted from context via MustDepsFromContext.
|
|
// Ensure ContextWithDeps is called to inject deps before any tool handlers are invoked.
|
|
//
|
|
// requiredScopes specifies the minimum OAuth scopes needed for this tool.
|
|
// AcceptedScopes are automatically derived using the scope hierarchy.
|
|
func NewToolFromHandler(
|
|
toolset inventory.ToolsetMetadata,
|
|
tool mcp.Tool,
|
|
requiredScopes []scopes.Scope,
|
|
handler func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest) (*mcp.CallToolResult, error),
|
|
) inventory.ServerTool {
|
|
st := inventory.NewServerTool(tool, toolset, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
deps := MustDepsFromContext(ctx)
|
|
return handler(ctx, deps, req)
|
|
})
|
|
st.RequiredScopes = scopes.ToStringSlice(requiredScopes...)
|
|
st.AcceptedScopes = scopes.ExpandScopes(requiredScopes...)
|
|
return st
|
|
}
|