1d05b3285e
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
License Check / license-check (push) Has been cancelled
- Fix inconsistent terminology (NewInventoryBuilder -> CachedInventoryBuilder) - Add explicit initialized flag for reliable state checking - Clarify contradictory documentation about initialization requirement - Improve ResetInventoryCache warning about thread safety - Add comment explaining why tests don't use t.Parallel()
166 lines
5.8 KiB
Go
166 lines
5.8 KiB
Go
package github
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/github/github-mcp-server/pkg/inventory"
|
|
"github.com/github/github-mcp-server/pkg/translations"
|
|
)
|
|
|
|
// CachedInventory provides a cached inventory builder that builds tool definitions
|
|
// only once, regardless of how many times CachedInventoryBuilder is called.
|
|
//
|
|
// This is particularly useful for stateless server patterns (like the remote server)
|
|
// where a new server instance is created per request. Without caching, every request
|
|
// would rebuild all ~130 tool definitions including JSON schema generation, causing
|
|
// significant performance overhead.
|
|
//
|
|
// Usage:
|
|
//
|
|
// // Option 1: Initialize once at startup with your translator
|
|
// github.InitInventoryCache(myTranslator)
|
|
//
|
|
// // Then get pre-built inventory on each request
|
|
// inv := github.CachedInventoryBuilder().
|
|
// WithReadOnly(cfg.ReadOnly).
|
|
// WithToolsets(cfg.Toolsets).
|
|
// Build()
|
|
//
|
|
// // Option 2: Use NewInventory which doesn't use the cache (legacy behavior)
|
|
// inv := github.NewInventory(myTranslator).Build()
|
|
//
|
|
// The cache stores the built []ServerTool, []ServerResourceTemplate, and []ServerPrompt.
|
|
// Per-request configuration (read-only, toolsets, feature flags, filters) is still
|
|
// applied when building the Inventory from the cached data.
|
|
type CachedInventory struct {
|
|
once sync.Once
|
|
initialized bool // set to true after init completes
|
|
tools []inventory.ServerTool
|
|
resources []inventory.ServerResourceTemplate
|
|
prompts []inventory.ServerPrompt
|
|
}
|
|
|
|
// global singleton for caching
|
|
var globalInventoryCache = &CachedInventory{}
|
|
|
|
// InitInventoryCache initializes the global inventory cache with the given translator.
|
|
// This should be called once at startup before any requests are processed.
|
|
// It's safe to call multiple times - only the first call has any effect.
|
|
//
|
|
// For the local server, this is typically called with the configured translator.
|
|
// For the remote server, use translations.NullTranslationHelper since translations
|
|
// aren't needed per-request.
|
|
//
|
|
// Example:
|
|
//
|
|
// func main() {
|
|
// t, _ := translations.TranslationHelper()
|
|
// github.InitInventoryCache(t)
|
|
// // ... start server
|
|
// }
|
|
func InitInventoryCache(t translations.TranslationHelperFunc) {
|
|
globalInventoryCache.init(t, nil, nil, nil)
|
|
}
|
|
|
|
// InitInventoryCacheWithExtras initializes the global inventory cache with the given
|
|
// translator plus additional tools, resources, and prompts.
|
|
//
|
|
// This is useful for the remote server which has additional tools (e.g., Copilot tools)
|
|
// that aren't part of the base github-mcp-server package.
|
|
//
|
|
// The extra items are appended to the base items from AllTools/AllResources/AllPrompts.
|
|
// It's safe to call multiple times - only the first call has any effect.
|
|
//
|
|
// Example:
|
|
//
|
|
// func init() {
|
|
// github.InitInventoryCacheWithExtras(
|
|
// translations.NullTranslationHelper,
|
|
// remoteOnlyTools, // []inventory.ServerTool
|
|
// remoteOnlyResources, // []inventory.ServerResourceTemplate
|
|
// remoteOnlyPrompts, // []inventory.ServerPrompt
|
|
// )
|
|
// }
|
|
func InitInventoryCacheWithExtras(
|
|
t translations.TranslationHelperFunc,
|
|
extraTools []inventory.ServerTool,
|
|
extraResources []inventory.ServerResourceTemplate,
|
|
extraPrompts []inventory.ServerPrompt,
|
|
) {
|
|
globalInventoryCache.init(t, extraTools, extraResources, extraPrompts)
|
|
}
|
|
|
|
// init initializes the cache with the given translator and optional extras (sync.Once protected).
|
|
func (c *CachedInventory) init(
|
|
t translations.TranslationHelperFunc,
|
|
extraTools []inventory.ServerTool,
|
|
extraResources []inventory.ServerResourceTemplate,
|
|
extraPrompts []inventory.ServerPrompt,
|
|
) {
|
|
c.once.Do(func() {
|
|
c.tools = AllTools(t)
|
|
c.resources = AllResources(t)
|
|
c.prompts = AllPrompts(t)
|
|
|
|
// Append extra items if provided
|
|
if len(extraTools) > 0 {
|
|
c.tools = append(c.tools, extraTools...)
|
|
}
|
|
if len(extraResources) > 0 {
|
|
c.resources = append(c.resources, extraResources...)
|
|
}
|
|
if len(extraPrompts) > 0 {
|
|
c.prompts = append(c.prompts, extraPrompts...)
|
|
}
|
|
|
|
c.initialized = true
|
|
})
|
|
}
|
|
|
|
// CachedInventoryBuilder returns an inventory.Builder pre-populated with cached
|
|
// tool/resource/prompt definitions.
|
|
//
|
|
// The cache should typically be initialized via InitInventoryCache at startup.
|
|
// If not already initialized when this function is called, it will automatically
|
|
// initialize with NullTranslationHelper as a fallback.
|
|
//
|
|
// Per-request configuration can still be applied via the builder methods:
|
|
// - WithReadOnly(bool) - filter to read-only tools
|
|
// - WithToolsets([]string) - enable specific toolsets
|
|
// - WithTools([]string) - enable specific tools
|
|
// - WithFeatureChecker(func) - per-request feature flag evaluation
|
|
// - WithFilter(func) - custom filtering
|
|
//
|
|
// Example:
|
|
//
|
|
// inv := github.CachedInventoryBuilder().
|
|
// WithReadOnly(cfg.ReadOnly).
|
|
// WithToolsets(cfg.EnabledToolsets).
|
|
// WithFeatureChecker(createFeatureChecker(cfg.EnabledFeatures)).
|
|
// Build()
|
|
func CachedInventoryBuilder() *inventory.Builder {
|
|
// Ensure cache is initialized (with NullTranslationHelper as fallback)
|
|
globalInventoryCache.init(translations.NullTranslationHelper, nil, nil, nil)
|
|
|
|
return inventory.NewBuilder().
|
|
SetTools(globalInventoryCache.tools).
|
|
SetResources(globalInventoryCache.resources).
|
|
SetPrompts(globalInventoryCache.prompts)
|
|
}
|
|
|
|
// IsCacheInitialized returns true if the inventory cache has been initialized.
|
|
// This is primarily useful for testing.
|
|
func IsCacheInitialized() bool {
|
|
return globalInventoryCache.initialized
|
|
}
|
|
|
|
// ResetInventoryCache resets the global inventory cache, allowing it to be
|
|
// reinitialized with a different translator.
|
|
//
|
|
// WARNING: This function is for testing only. It is NOT thread-safe and must only
|
|
// be called when no other goroutines are accessing the cache. Tests using this
|
|
// function must not use t.Parallel().
|
|
func ResetInventoryCache() {
|
|
globalInventoryCache = &CachedInventory{}
|
|
}
|