Skip to content

Commit 058d123

Browse files
Add map indexes for O(1) lookups in Registry
Address review feedback to use maps for collections. Added lookup maps (toolsByName, resourcesByURI, promptsByName) while keeping slices for ordered iteration. This provides O(1) lookup for: - FindToolByName - filterToolsByName (used by ForMCPRequest) - filterResourcesByURI - filterPromptsByName Maps are built once during Build() and shared in ForMCPRequest copies.
1 parent 58c2078 commit 058d123

File tree

4 files changed

+41
-27
lines changed

4 files changed

+41
-27
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ bin/
1919
# binary
2020
github-mcp-server
2121

22-
.historyconformance-report/
22+
.history
23+
conformance-report/

pkg/registry/builder.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,22 @@ func (b *Builder) Build() *Registry {
124124
featureChecker: b.featureChecker,
125125
}
126126

127+
// Build lookup maps for O(1) access
128+
r.toolsByName = make(map[string]*ServerTool, len(b.tools))
129+
for i := range r.tools {
130+
r.toolsByName[r.tools[i].Tool.Name] = &r.tools[i]
131+
}
132+
133+
r.resourcesByURI = make(map[string]*ServerResourceTemplate, len(b.resourceTemplates))
134+
for i := range r.resourceTemplates {
135+
r.resourcesByURI[r.resourceTemplates[i].Template.URITemplate] = &r.resourceTemplates[i]
136+
}
137+
138+
r.promptsByName = make(map[string]*ServerPrompt, len(b.prompts))
139+
for i := range r.prompts {
140+
r.promptsByName[r.prompts[i].Prompt.Name] = &r.prompts[i]
141+
}
142+
127143
// Process toolsets
128144
r.enabledToolsets, r.unrecognizedToolsets = b.processToolsets()
129145

pkg/registry/filters.go

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -151,40 +151,31 @@ func (r *Registry) AvailablePrompts(ctx context.Context) []ServerPrompt {
151151
// filterToolsByName returns tools matching the given name, checking deprecated aliases.
152152
// Returns from the current tools slice (respects existing filter chain).
153153
func (r *Registry) filterToolsByName(name string) []ServerTool {
154-
// First check for exact match
155-
for i := range r.tools {
156-
if r.tools[i].Tool.Name == name {
157-
return []ServerTool{r.tools[i]}
158-
}
154+
// First check for exact match using map
155+
if tool, ok := r.toolsByName[name]; ok {
156+
return []ServerTool{*tool}
159157
}
160158
// Check if name is a deprecated alias
161159
if canonical, isAlias := r.deprecatedAliases[name]; isAlias {
162-
for i := range r.tools {
163-
if r.tools[i].Tool.Name == canonical {
164-
return []ServerTool{r.tools[i]}
165-
}
160+
if tool, ok := r.toolsByName[canonical]; ok {
161+
return []ServerTool{*tool}
166162
}
167163
}
168164
return []ServerTool{}
169165
}
170166

171167
// filterResourcesByURI returns resource templates matching the given URI pattern.
172168
func (r *Registry) filterResourcesByURI(uri string) []ServerResourceTemplate {
173-
for i := range r.resourceTemplates {
174-
// Check if URI matches the template pattern (exact match on URITemplate string)
175-
if r.resourceTemplates[i].Template.URITemplate == uri {
176-
return []ServerResourceTemplate{r.resourceTemplates[i]}
177-
}
169+
if res, ok := r.resourcesByURI[uri]; ok {
170+
return []ServerResourceTemplate{*res}
178171
}
179172
return []ServerResourceTemplate{}
180173
}
181174

182175
// filterPromptsByName returns prompts matching the given name.
183176
func (r *Registry) filterPromptsByName(name string) []ServerPrompt {
184-
for i := range r.prompts {
185-
if r.prompts[i].Prompt.Name == name {
186-
return []ServerPrompt{r.prompts[i]}
187-
}
177+
if prompt, ok := r.promptsByName[name]; ok {
178+
return []ServerPrompt{*prompt}
188179
}
189180
return []ServerPrompt{}
190181
}

pkg/registry/registry.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,18 @@ import (
2525
// - Lazy dependency injection during registration via RegisterAll()
2626
// - Runtime toolset enabling for dynamic toolsets mode
2727
type Registry struct {
28-
// tools holds all tools in this group
28+
// tools holds all tools in this group (ordered for iteration)
2929
tools []ServerTool
30-
// resourceTemplates holds all resource templates in this group
30+
// toolsByName provides O(1) lookup by tool name
31+
toolsByName map[string]*ServerTool
32+
// resourceTemplates holds all resource templates in this group (ordered for iteration)
3133
resourceTemplates []ServerResourceTemplate
32-
// prompts holds all prompts in this group
34+
// resourcesByURI provides O(1) lookup by URI template
35+
resourcesByURI map[string]*ServerResourceTemplate
36+
// prompts holds all prompts in this group (ordered for iteration)
3337
prompts []ServerPrompt
38+
// promptsByName provides O(1) lookup by prompt name
39+
promptsByName map[string]*ServerPrompt
3440
// deprecatedAliases maps old tool names to new canonical names
3541
deprecatedAliases map[string]string
3642

@@ -92,8 +98,11 @@ func (r *Registry) ForMCPRequest(method string, itemName string) *Registry {
9298
// Create a shallow copy with shared filter settings
9399
result := &Registry{
94100
tools: r.tools,
101+
toolsByName: r.toolsByName,
95102
resourceTemplates: r.resourceTemplates,
103+
resourcesByURI: r.resourcesByURI,
96104
prompts: r.prompts,
105+
promptsByName: r.promptsByName,
97106
deprecatedAliases: r.deprecatedAliases,
98107
readOnly: r.readOnly,
99108
enabledToolsets: r.enabledToolsets, // shared, not modified
@@ -269,11 +278,8 @@ func (r *Registry) ResolveToolAliases(toolNames []string) (resolved []string, al
269278
// Returns the tool, its toolset ID, and an error if not found.
270279
// This searches ALL tools regardless of filters.
271280
func (r *Registry) FindToolByName(toolName string) (*ServerTool, ToolsetID, error) {
272-
for i := range r.tools {
273-
tool := &r.tools[i]
274-
if tool.Tool.Name == toolName {
275-
return tool, tool.Toolset.ID, nil
276-
}
281+
if tool, ok := r.toolsByName[toolName]; ok {
282+
return tool, tool.Toolset.ID, nil
277283
}
278284
return nil, "", NewToolDoesNotExistError(toolName)
279285
}

0 commit comments

Comments
 (0)