Skip to content

Commit be13534

Browse files
hingerabhinavHarness
authored andcommitted
feat: [ML-1188]: add a tool to find similar templates querying on vector db (#43)
* update tool name, add to readme * fix count float * feat: [ML-1188]: add a tool to find similar templates querying on vector db
1 parent 2688815 commit be13534

File tree

7 files changed

+210
-0
lines changed

7 files changed

+210
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ Toolset Name: `logs`
138138
Toolset Name: `templates`
139139

140140
- `list_templates`: List templates at a given scope
141+
- `intelligent_template_search`: Find the most relevant templates based on a natural language description.
141142

142143
#### Internal Developer Portal Toolset
143144

client/dto/intelligence.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package dto
2+
3+
// SimilaritySearchRequest represents a request for similarity search
4+
type SimilaritySearchRequest struct {
5+
AccountID string `json:"account_identifier"`
6+
OrgID string `json:"org_identifier,omitempty"`
7+
ProjectID string `json:"project_identifier,omitempty"`
8+
Description string `json:"description"`
9+
Count int `json:"count,omitempty"`
10+
TemplateType string `json:"template_type,omitempty"`
11+
}
12+
13+
// SimilaritySearchResult represents a single similarity search result
14+
type SimilaritySearchResult struct {
15+
ID string `json:"id"`
16+
Text string `json:"text"`
17+
Score float64 `json:"score"`
18+
Metadata interface{} `json:"metadata,omitempty"`
19+
}
20+
21+
// SimilaritySearchResponse represents the response from a similarity search
22+
type SimilaritySearchResponse struct {
23+
Results []SimilaritySearchResult `json:"results"`
24+
}

client/intelligence.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/harness/harness-mcp/client/dto"
8+
)
9+
10+
const (
11+
// Base API paths
12+
similaritySearchPath = "api/v1/template-search"
13+
)
14+
15+
type IntelligenceService struct {
16+
Client *Client
17+
}
18+
19+
func (ts *IntelligenceService) buildPath(basePath string) string {
20+
return basePath
21+
}
22+
23+
// SimilaritySearch searches for similar templates based on the provided request
24+
func (ts *IntelligenceService) SimilaritySearch(ctx context.Context, request *dto.SimilaritySearchRequest) (*dto.SimilaritySearchResponse, error) {
25+
endpoint := ts.buildPath(similaritySearchPath)
26+
27+
// Validate required parameters
28+
if request.AccountID == "" {
29+
return nil, fmt.Errorf("account_identifier is required")
30+
}
31+
if request.Description == "" {
32+
return nil, fmt.Errorf("description is required")
33+
}
34+
35+
// Create query parameters map for all request fields
36+
params := make(map[string]string)
37+
params["account_identifier"] = request.AccountID
38+
39+
if request.OrgID != "" {
40+
params["org_identifier"] = request.OrgID
41+
}
42+
if request.ProjectID != "" {
43+
params["project_identifier"] = request.ProjectID
44+
}
45+
46+
// Always include description as it's required
47+
params["description"] = request.Description
48+
49+
// Include optional parameters
50+
if request.TemplateType != "" {
51+
params["template_type"] = request.TemplateType
52+
}
53+
if request.Count > 0 {
54+
params["count"] = fmt.Sprintf("%d", request.Count)
55+
}
56+
57+
var result dto.SimilaritySearchResponse
58+
err := ts.Client.Get(ctx, endpoint, params, map[string]string{}, &result)
59+
if err != nil {
60+
return nil, fmt.Errorf("failed to perform similarity search: %w", err)
61+
}
62+
63+
return &result, nil
64+
}

cmd/harness-mcp-server/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ type Config struct {
3838
ChaosManagerSvcSecret string
3939
TemplateSvcBaseURL string
4040
TemplateSvcSecret string
41+
IntelligenceSvcBaseURL string
42+
IntelligenceSvcSecret string
4143
CodeSvcBaseURL string
4244
CodeSvcSecret string
4345
LogSvcBaseURL string

cmd/harness-mcp-server/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ var (
152152
ChaosManagerSvcSecret: viper.GetString("chaos_manager_svc_secret"),
153153
TemplateSvcBaseURL: viper.GetString("template_svc_base_url"),
154154
TemplateSvcSecret: viper.GetString("template_svc_secret"),
155+
IntelligenceSvcBaseURL: viper.GetString("intelligence_svc_base_url"),
156+
IntelligenceSvcSecret: viper.GetString("intelligence_svc_secret"),
155157
CodeSvcBaseURL: viper.GetString("code_svc_base_url"),
156158
CodeSvcSecret: viper.GetString("code_svc_secret"),
157159
LogSvcBaseURL: viper.GetString("log_svc_base_url"),
@@ -251,6 +253,8 @@ func init() {
251253
_ = viper.BindPFlag("nextgen_ce_secret", internalCmd.Flags().Lookup("nextgen-ce-secret"))
252254
_ = viper.BindPFlag("template_svc_base_url", internalCmd.Flags().Lookup("template-svc-base-url"))
253255
_ = viper.BindPFlag("template_svc_secret", internalCmd.Flags().Lookup("template-svc-secret"))
256+
_ = viper.BindPFlag("intelligence_svc_base_url", internalCmd.Flags().Lookup("intelligence-svc-base-url"))
257+
_ = viper.BindPFlag("intelligence_svc_secret", internalCmd.Flags().Lookup("intelligence-svc-secret"))
254258
_ = viper.BindPFlag("chaos_manager_svc_base_url", internalCmd.Flags().Lookup("chaos-manager-svc-base-url"))
255259
_ = viper.BindPFlag("chaos_manager_svc_secret", internalCmd.Flags().Lookup("chaos-manager-svc-secret"))
256260
_ = viper.BindPFlag("code_svc_base_url", internalCmd.Flags().Lookup("code-svc-base-url"))

pkg/harness/intelligence.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package harness
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/harness/harness-mcp/client"
9+
"github.com/harness/harness-mcp/client/dto"
10+
"github.com/harness/harness-mcp/cmd/harness-mcp-server/config"
11+
"github.com/mark3labs/mcp-go/mcp"
12+
"github.com/mark3labs/mcp-go/server"
13+
)
14+
15+
// FindSimilarTemplates creates a tool that allows finding similar templates based on provided description.
16+
func FindSimilarTemplates(config *config.Config, client *client.IntelligenceService) (tool mcp.Tool, handler server.ToolHandlerFunc) {
17+
return mcp.NewTool("intelligent_template_search",
18+
mcp.WithDescription("Finds the most relevant templates based on a natural language description. " +
19+
"Searches across template identifiers, names, types, capabilities, and use cases to find the best matches. " +
20+
"Returns templates ranked by similarity score with metadata including template IDs and organizational context. " +
21+
"Ideal for discovering templates that fulfill specific requirements without knowing exact identifiers."),
22+
mcp.WithString("description",
23+
mcp.Required(),
24+
mcp.Description("Description of the template to find similar templates"),
25+
),
26+
mcp.WithString("template_type",
27+
mcp.Description("Type of templates to find similar templates (e.g., Step, Stage, Pipeline)"),
28+
),
29+
mcp.WithNumber("count",
30+
mcp.Description("Maximum number of similar templates to return"),
31+
),
32+
WithScope(config, false),
33+
),
34+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
35+
// Get required description parameter
36+
description, err := requiredParam[string](request, "description")
37+
if err != nil {
38+
return mcp.NewToolResultError(err.Error()), nil
39+
}
40+
41+
// Fetch optional parameters
42+
templateType, err := OptionalParam[string](request, "template_type")
43+
if err != nil {
44+
return mcp.NewToolResultError(err.Error()), nil
45+
}
46+
47+
// Handle count parameter as float64 (JSON default) and convert to int
48+
countFloat, err := OptionalParam[float64](request, "count")
49+
if err != nil {
50+
return mcp.NewToolResultError(err.Error()), nil
51+
}
52+
count := int(countFloat)
53+
54+
// Try to fetch scope parameters (account_id, org_id, project_id) if provided
55+
scope, err := fetchScope(config, request, false)
56+
if err != nil {
57+
return mcp.NewToolResultError(err.Error()), nil
58+
}
59+
60+
// Create similarity search request
61+
similarityRequest := &dto.SimilaritySearchRequest{
62+
AccountID: scope.AccountID,
63+
OrgID: scope.OrgID,
64+
ProjectID: scope.ProjectID,
65+
Description: description,
66+
Count: count,
67+
TemplateType: templateType,
68+
}
69+
70+
// Call the similarity search API
71+
result, err := client.SimilaritySearch(ctx, similarityRequest)
72+
if err != nil {
73+
return nil, fmt.Errorf("failed to perform similarity search: %w", err)
74+
}
75+
76+
// Marshal and return the result
77+
r, err := json.Marshal(result)
78+
if err != nil {
79+
return nil, fmt.Errorf("failed to marshal similarity search response: %w", err)
80+
}
81+
82+
return mcp.NewToolResultText(string(r)), nil
83+
}
84+
}

pkg/harness/tools.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ func InitToolsets(config *config.Config) (*toolsets.ToolsetGroup, error) {
9898
return nil, err
9999
}
100100

101+
if err := registerIntelligence(config, tsg); err != nil {
102+
return nil, err
103+
}
104+
101105
if err := registerInternalDeveloperPortal(config, tsg); err != nil {
102106
return nil, err
103107
}
@@ -700,6 +704,33 @@ func registerTemplates(config *config.Config, tsg *toolsets.ToolsetGroup) error
700704
return nil
701705
}
702706

707+
// registerIntelligence registers the intelligence toolset
708+
func registerIntelligence(config *config.Config, tsg *toolsets.ToolsetGroup) error {
709+
// Determine the base URL and secret for intelligence
710+
baseURL := buildServiceURL(config, config.IntelligenceSvcBaseURL, config.BaseURL, "harness-intelligence")
711+
secret := config.IntelligenceSvcSecret
712+
713+
// Create base client for intelligence service
714+
c, err := createClient(baseURL, config, secret)
715+
if err != nil {
716+
return err
717+
}
718+
719+
intelligenceClient := &client.IntelligenceService{
720+
Client: c,
721+
}
722+
723+
// Create the intelligence toolset
724+
intelligence := toolsets.NewToolset("intelligence", "Harness Intelligence related tools").
725+
AddReadTools(
726+
toolsets.NewServerTool(FindSimilarTemplates(config, intelligenceClient)),
727+
)
728+
729+
// Add toolset to the group
730+
tsg.AddToolset(intelligence)
731+
return nil
732+
}
733+
703734
func registerInternalDeveloperPortal(config *config.Config, tsg *toolsets.ToolsetGroup) error {
704735
// Determine the base URL and secret for IDP service
705736
baseURL := buildServiceURL(config, config.IDPSvcBaseURL, config.BaseURL, "")

0 commit comments

Comments
 (0)