Skip to content

Commit c0634b7

Browse files
committed
Add GetSkills and SearchSkills to the provider interface
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
1 parent 0754234 commit c0634b7

6 files changed

Lines changed: 147 additions & 0 deletions

File tree

pkg/registry/mocks/mock_provider.go

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/registry/provider.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,10 @@ type Provider interface {
2323

2424
// ListAvailableSkills returns skills discovered from the registry data
2525
ListAvailableSkills() ([]types.Skill, error)
26+
27+
// GetSkill returns a specific skill by namespace and name
28+
GetSkill(namespace, name string) (*types.Skill, error)
29+
30+
// SearchSkills searches for skills matching the query
31+
SearchSkills(query string) ([]types.Skill, error)
2632
}

pkg/registry/provider_api.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type APIRegistryProvider struct {
2424
allowPrivateIp bool
2525
client api.Client
2626
tokenSource auth.TokenSource
27+
skillsClient api.SkillsClient
2728
}
2829

2930
// NewAPIRegistryProvider creates a new API registry provider.
@@ -35,11 +36,15 @@ func NewAPIRegistryProvider(apiURL string, allowPrivateIp bool, tokenSource auth
3536
return nil, fmt.Errorf("failed to create API client: %w", err)
3637
}
3738

39+
// Create skills client (best-effort — skills API may not be available)
40+
skillsClient, _ := api.NewSkillsClient(apiURL, allowPrivateIp, tokenSource)
41+
3842
p := &APIRegistryProvider{
3943
apiURL: apiURL,
4044
allowPrivateIp: allowPrivateIp,
4145
client: client,
4246
tokenSource: tokenSource,
47+
skillsClient: skillsClient,
4348
}
4449

4550
// Initialize the base provider with the GetRegistry function
@@ -173,6 +178,36 @@ func (p *APIRegistryProvider) ListServers() ([]types.ServerMetadata, error) {
173178
return ConvertServersToMetadata(servers)
174179
}
175180

181+
// GetSkill returns a specific skill by namespace and name from the API.
182+
func (p *APIRegistryProvider) GetSkill(namespace, name string) (*types.Skill, error) {
183+
if p.skillsClient == nil {
184+
return nil, nil
185+
}
186+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
187+
defer cancel()
188+
return p.skillsClient.GetSkill(ctx, namespace, name)
189+
}
190+
191+
// SearchSkills searches for skills matching the query via the API.
192+
func (p *APIRegistryProvider) SearchSkills(query string) ([]types.Skill, error) {
193+
if p.skillsClient == nil {
194+
return nil, nil
195+
}
196+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
197+
defer cancel()
198+
result, err := p.skillsClient.SearchSkills(ctx, query)
199+
if err != nil {
200+
return nil, err
201+
}
202+
skills := make([]types.Skill, 0, len(result.Skills))
203+
for _, s := range result.Skills {
204+
if s != nil {
205+
skills = append(skills, *s)
206+
}
207+
}
208+
return skills, nil
209+
}
210+
176211
// ConvertServerJSON converts an MCP Registry API ServerJSON to ToolHive ServerMetadata
177212
// Uses converters from converters.go (same package)
178213
// Note: Only handles OCI packages and remote servers, skips npm/pypi by design

pkg/registry/provider_base.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,16 @@ func (*BaseProvider) ListAvailableSkills() ([]types.Skill, error) {
126126
return nil, nil
127127
}
128128

129+
// GetSkill returns nil for providers that don't support skills.
130+
func (*BaseProvider) GetSkill(_, _ string) (*types.Skill, error) {
131+
return nil, nil
132+
}
133+
134+
// SearchSkills returns nil for providers that don't support skills.
135+
func (*BaseProvider) SearchSkills(_ string) ([]types.Skill, error) {
136+
return nil, nil
137+
}
138+
129139
// matchesQuery checks if a server matches the search query
130140
func matchesQuery(name, description string, tags []string, query string) bool {
131141
// Search in name

pkg/registry/provider_local.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"log/slog"
1010
"os"
11+
"strings"
1112
"sync"
1213

1314
catalog "github.com/stacklok/toolhive-catalog/pkg/catalog/toolhive"
@@ -124,6 +125,38 @@ func (p *LocalRegistryProvider) ListAvailableSkills() ([]types.Skill, error) {
124125
return skills, nil
125126
}
126127

128+
// GetSkill returns a specific skill by namespace and name.
129+
func (p *LocalRegistryProvider) GetSkill(namespace, name string) (*types.Skill, error) {
130+
skills, err := p.ListAvailableSkills()
131+
if err != nil {
132+
return nil, err
133+
}
134+
for i := range skills {
135+
if skills[i].Namespace == namespace && skills[i].Name == name {
136+
return &skills[i], nil
137+
}
138+
}
139+
return nil, nil
140+
}
141+
142+
// SearchSkills searches for skills matching the query in name or description.
143+
func (p *LocalRegistryProvider) SearchSkills(query string) ([]types.Skill, error) {
144+
skills, err := p.ListAvailableSkills()
145+
if err != nil {
146+
return nil, err
147+
}
148+
query = strings.ToLower(query)
149+
var results []types.Skill
150+
for _, s := range skills {
151+
if strings.Contains(strings.ToLower(s.Name), query) ||
152+
strings.Contains(strings.ToLower(s.Description), query) ||
153+
strings.Contains(strings.ToLower(s.Namespace), query) {
154+
results = append(results, s)
155+
}
156+
}
157+
return results, nil
158+
}
159+
127160
// parseRegistryData parses JSON data into a Registry struct
128161
func parseRegistryData(data []byte) (*types.Registry, error) {
129162
registry := &types.Registry{}

pkg/registry/provider_remote.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io"
1010
"log/slog"
1111
"net/http"
12+
"strings"
1213
"sync"
1314
"time"
1415

@@ -194,6 +195,38 @@ func (p *RemoteRegistryProvider) ListAvailableSkills() ([]types.Skill, error) {
194195
return skills, nil
195196
}
196197

198+
// GetSkill returns a specific skill by namespace and name.
199+
func (p *RemoteRegistryProvider) GetSkill(namespace, name string) (*types.Skill, error) {
200+
skills, err := p.ListAvailableSkills()
201+
if err != nil {
202+
return nil, err
203+
}
204+
for i := range skills {
205+
if skills[i].Namespace == namespace && skills[i].Name == name {
206+
return &skills[i], nil
207+
}
208+
}
209+
return nil, nil
210+
}
211+
212+
// SearchSkills searches for skills matching the query in name or description.
213+
func (p *RemoteRegistryProvider) SearchSkills(query string) ([]types.Skill, error) {
214+
skills, err := p.ListAvailableSkills()
215+
if err != nil {
216+
return nil, err
217+
}
218+
query = strings.ToLower(query)
219+
var results []types.Skill
220+
for _, s := range skills {
221+
if strings.Contains(strings.ToLower(s.Name), query) ||
222+
strings.Contains(strings.ToLower(s.Description), query) ||
223+
strings.Contains(strings.ToLower(s.Namespace), query) {
224+
results = append(results, s)
225+
}
226+
}
227+
return results, nil
228+
}
229+
197230
func (p *RemoteRegistryProvider) setSkills(skills []types.Skill) {
198231
p.skillsMu.Lock()
199232
defer p.skillsMu.Unlock()

0 commit comments

Comments
 (0)