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

Commita456b82

Browse files
perf: add inventory cache for stateless server patterns
Add CachedInventory to build tool/resource/prompt definitions once atstartup rather than per-request. This is particularly useful for theremote server pattern where a new server instance is created per request.Key features:- InitInventoryCache(t) initializes the cache once at startup- InitInventoryCacheWithExtras(t, tools, resources, prompts) allows injecting additional items (e.g., remote-only Copilot tools)- CachedInventoryBuilder() returns a builder with pre-cached definitions- Per-request configuration (read-only, toolsets, feature flags) still works- Thread-safe via sync.Once- Backward compatible: NewInventory(t) still works without cachingThis addresses the performance concern raised in go-sdk PR#685 at ahigher level by caching the entire []ServerTool slice rather thanindividual schemas.Related:modelcontextprotocol/go-sdk#685
1 parent97feb5c commita456b82

File tree

2 files changed

+428
-0
lines changed

2 files changed

+428
-0
lines changed

‎pkg/github/inventory_cache.go‎

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package github
2+
3+
import (
4+
"sync"
5+
6+
"github.com/github/github-mcp-server/pkg/inventory"
7+
"github.com/github/github-mcp-server/pkg/translations"
8+
)
9+
10+
// CachedInventory provides a cached inventory builder that builds tool definitions
11+
// only once, regardless of how many times NewInventoryBuilder is called.
12+
//
13+
// This is particularly useful for stateless server patterns (like the remote server)
14+
// where a new server instance is created per request. Without caching, every request
15+
// would rebuild all ~130 tool definitions including JSON schema generation, causing
16+
// significant performance overhead.
17+
//
18+
// Usage:
19+
//
20+
//// Option 1: Initialize once at startup with your translator
21+
//github.InitInventoryCache(myTranslator)
22+
//
23+
//// Then get pre-built inventory on each request
24+
//inv := github.CachedInventoryBuilder().
25+
// WithReadOnly(cfg.ReadOnly).
26+
// WithToolsets(cfg.Toolsets).
27+
// Build()
28+
//
29+
//// Option 2: Use NewInventory which doesn't use the cache (legacy behavior)
30+
//inv := github.NewInventory(myTranslator).Build()
31+
//
32+
// The cache stores the built []ServerTool, []ServerResourceTemplate, and []ServerPrompt.
33+
// Per-request configuration (read-only, toolsets, feature flags, filters) is still
34+
// applied when building the Inventory from the cached data.
35+
typeCachedInventorystruct {
36+
once sync.Once
37+
tools []inventory.ServerTool
38+
resources []inventory.ServerResourceTemplate
39+
prompts []inventory.ServerPrompt
40+
}
41+
42+
// global singleton for caching
43+
varglobalInventoryCache=&CachedInventory{}
44+
45+
// InitInventoryCache initializes the global inventory cache with the given translator.
46+
// This should be called once at startup before any requests are processed.
47+
// It's safe to call multiple times - only the first call has any effect.
48+
//
49+
// For the local server, this is typically called with the configured translator.
50+
// For the remote server, use translations.NullTranslationHelper since translations
51+
// aren't needed per-request.
52+
//
53+
// Example:
54+
//
55+
//func main() {
56+
// t, _ := translations.TranslationHelper()
57+
// github.InitInventoryCache(t)
58+
// // ... start server
59+
//}
60+
funcInitInventoryCache(t translations.TranslationHelperFunc) {
61+
globalInventoryCache.init(t,nil,nil,nil)
62+
}
63+
64+
// InitInventoryCacheWithExtras initializes the global inventory cache with the given
65+
// translator plus additional tools, resources, and prompts.
66+
//
67+
// This is useful for the remote server which has additional tools (e.g., Copilot tools)
68+
// that aren't part of the base github-mcp-server package.
69+
//
70+
// The extra items are appended to the base items from AllTools/AllResources/AllPrompts.
71+
// It's safe to call multiple times - only the first call has any effect.
72+
//
73+
// Example:
74+
//
75+
//func init() {
76+
// github.InitInventoryCacheWithExtras(
77+
// translations.NullTranslationHelper,
78+
// remoteOnlyTools, // []inventory.ServerTool
79+
// remoteOnlyResources, // []inventory.ServerResourceTemplate
80+
// remoteOnlyPrompts, // []inventory.ServerPrompt
81+
// )
82+
//}
83+
funcInitInventoryCacheWithExtras(
84+
t translations.TranslationHelperFunc,
85+
extraTools []inventory.ServerTool,
86+
extraResources []inventory.ServerResourceTemplate,
87+
extraPrompts []inventory.ServerPrompt,
88+
) {
89+
globalInventoryCache.init(t,extraTools,extraResources,extraPrompts)
90+
}
91+
92+
// init initializes the cache with the given translator and optional extras (sync.Once protected).
93+
func (c*CachedInventory)init(
94+
t translations.TranslationHelperFunc,
95+
extraTools []inventory.ServerTool,
96+
extraResources []inventory.ServerResourceTemplate,
97+
extraPrompts []inventory.ServerPrompt,
98+
) {
99+
c.once.Do(func() {
100+
c.tools=AllTools(t)
101+
c.resources=AllResources(t)
102+
c.prompts=AllPrompts(t)
103+
104+
// Append extra items if provided
105+
iflen(extraTools)>0 {
106+
c.tools=append(c.tools,extraTools...)
107+
}
108+
iflen(extraResources)>0 {
109+
c.resources=append(c.resources,extraResources...)
110+
}
111+
iflen(extraPrompts)>0 {
112+
c.prompts=append(c.prompts,extraPrompts...)
113+
}
114+
})
115+
}
116+
117+
// CachedInventoryBuilder returns an inventory.Builder pre-populated with cached
118+
// tool/resource/prompt definitions.
119+
//
120+
// The cache must be initialized via InitInventoryCache before calling this function.
121+
// If the cache is not initialized, this will initialize it with NullTranslationHelper.
122+
//
123+
// Per-request configuration can still be applied via the builder methods:
124+
// - WithReadOnly(bool) - filter to read-only tools
125+
// - WithToolsets([]string) - enable specific toolsets
126+
// - WithTools([]string) - enable specific tools
127+
// - WithFeatureChecker(func) - per-request feature flag evaluation
128+
// - WithFilter(func) - custom filtering
129+
//
130+
// Example:
131+
//
132+
//inv := github.CachedInventoryBuilder().
133+
// WithReadOnly(cfg.ReadOnly).
134+
// WithToolsets(cfg.EnabledToolsets).
135+
// WithFeatureChecker(createFeatureChecker(cfg.EnabledFeatures)).
136+
// Build()
137+
funcCachedInventoryBuilder()*inventory.Builder {
138+
// Ensure cache is initialized (with NullTranslationHelper as fallback)
139+
globalInventoryCache.init(translations.NullTranslationHelper,nil,nil,nil)
140+
141+
returninventory.NewBuilder().
142+
SetTools(globalInventoryCache.tools).
143+
SetResources(globalInventoryCache.resources).
144+
SetPrompts(globalInventoryCache.prompts)
145+
}
146+
147+
// IsCacheInitialized returns true if the inventory cache has been initialized.
148+
// This is primarily useful for testing.
149+
funcIsCacheInitialized()bool {
150+
// We can't directly check sync.Once state, but we can check if tools are populated
151+
returnlen(globalInventoryCache.tools)>0
152+
}
153+
154+
// ResetInventoryCache resets the global inventory cache, allowing it to be
155+
// reinitialized with a different translator. This should only be used in tests.
156+
//
157+
// WARNING: This is not thread-safe and should never be called in production code.
158+
funcResetInventoryCache() {
159+
globalInventoryCache=&CachedInventory{}
160+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp