Skip to content

Commit e14bbcf

Browse files
deepharnessHarness
authored andcommitted
feat:[SSCA-3948]: Added support for custom events and follow on propts (#64)
* fixed error handling * Fixed comments and minor changes * Apply suggestion from code review * Apply suggestion from code review * Common schema for both event and prompts * minor updates * more refactoring on events * minor refactoring * removed dummy code * Added changes for sampling
1 parent ec9966d commit e14bbcf

File tree

16 files changed

+850
-75
lines changed

16 files changed

+850
-75
lines changed

client/scs/builder.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ func (bb *artifactListingRequestBodyBuilder) WithLicenseFilter(v generated.Licen
3636
}
3737
return bb
3838
}
39+
func (bb *artifactListingRequestBodyBuilder) WithLicenseFilterList(v []generated.LicenseFilter) *artifactListingRequestBodyBuilder {
40+
if len(v) > 0 {
41+
bb.b.LicenseFilterList = &v
42+
}
43+
return bb
44+
}
3945
func (bb *artifactListingRequestBodyBuilder) WithPolicyViolation(v generated.ArtifactListingRequestBodyPolicyViolation) *artifactListingRequestBodyBuilder {
4046
if v != "" {
4147
bb.b.PolicyViolation = &v

client/scs/generated/scs.gen.go

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

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/daixiang0/gci v0.13.6
1010
github.com/golang-jwt/jwt v3.2.2+incompatible
1111
github.com/google/uuid v1.6.0
12-
github.com/mark3labs/mcp-go v0.20.1
12+
github.com/mark3labs/mcp-go v0.34.0
1313
github.com/oapi-codegen/runtime v1.1.1
1414
github.com/rs/zerolog v1.34.0
1515
github.com/spf13/cobra v1.8.0
@@ -33,7 +33,7 @@ require (
3333
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
3434
github.com/sourcegraph/conc v0.3.0 // indirect
3535
github.com/spf13/afero v1.11.0 // indirect
36-
github.com/spf13/cast v1.6.0 // indirect
36+
github.com/spf13/cast v1.7.1 // indirect
3737
github.com/spf13/pflag v1.0.5 // indirect
3838
github.com/subosito/gotenv v1.6.0 // indirect
3939
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
4040
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
4141
github.com/mark3labs/mcp-go v0.20.1 h1:E1Bbx9K8d8kQmDZ1QHblM38c7UU2evQ2LlkANk1U/zw=
4242
github.com/mark3labs/mcp-go v0.20.1/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
43+
github.com/mark3labs/mcp-go v0.34.0 h1:eWy7WBGvhk6EyAAyVzivTCprE52iXJwNtvHV6Cv3bR0=
44+
github.com/mark3labs/mcp-go v0.34.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
4345
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
4446
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
4547
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -73,6 +75,8 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
7375
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
7476
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
7577
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
78+
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
79+
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
7680
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
7781
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
7882
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=

pkg/harness/environments.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,12 +269,12 @@ func MoveEnvironmentConfigsTool(config *config.Config, client *client.Environmen
269269
}
270270

271271
// Set boolean pointers if values were provided
272-
if isNewBranchProvided, ok := request.Params.Arguments["is_new_branch"]; ok && isNewBranchProvided != nil {
272+
if isNewBranchProvided, ok := request.GetArguments()["is_new_branch"]; ok && isNewBranchProvided != nil {
273273
val := isNewBranch
274274
moveRequest.IsNewBranch = &val
275275
}
276276

277-
if isHarnessCodeRepoProvided, ok := request.Params.Arguments["is_harness_code_repo"]; ok && isHarnessCodeRepoProvided != nil {
277+
if isHarnessCodeRepoProvided, ok := request.GetArguments()["is_harness_code_repo"]; ok && isHarnessCodeRepoProvided != nil {
278278
val := isHarnessCodeRepo
279279
moveRequest.IsHarnessCodeRepo = &val
280280
}

pkg/harness/event/builder.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package event
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
)
7+
8+
type Builder interface {
9+
Build(raw json.RawMessage, tool string, args ...any) string
10+
}
11+
12+
type Registry map[string]Builder
13+
14+
func (r Registry) Register(event string, b Builder) { r[event] = b }
15+
func (r Registry) Build(event string, raw json.RawMessage, tool string, args ...any) string {
16+
if b, ok := r[event]; ok {
17+
return b.Build(raw, tool, args...)
18+
}
19+
return r["unknown_event"].Build(raw, tool, args...)
20+
}
21+
22+
// CreateBaseResponse creates a base response with consistent entity info and event type
23+
func CreateBaseResponse(eventType string, tool string) map[string]interface{} {
24+
return map[string]interface{}{
25+
"entity_info": map[string]string{
26+
"entity_type": tool,
27+
},
28+
"type": eventType,
29+
}
30+
}
31+
32+
// FormatEventResponse formats a response map into a consistent event format
33+
func FormatEventResponse(eventType string, responseData map[string]interface{}) string {
34+
out, err := json.MarshalIndent(responseData, "", " ")
35+
if err != nil {
36+
return fmt.Sprintf(`{"type": "%s", "error": "Failed to format event response"}`, eventType)
37+
}
38+
return string(out)
39+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package common
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
builder "github.com/harness/harness-mcp/pkg/harness/event"
8+
)
9+
10+
// EventType defines the type of SCS event
11+
type EventType string
12+
13+
// SCS event types
14+
const (
15+
GenericTableEvent EventType = "table"
16+
OPAEvent EventType = "opa"
17+
PromptEvent EventType = "prompt"
18+
)
19+
20+
var Reg = builder.Registry{
21+
string(GenericTableEvent): GenericTableBuilder{},
22+
string(OPAEvent): OPABuilder{},
23+
string(PromptEvent): PromptBuilder{},
24+
}
25+
26+
type GenericTableBuilder struct{}
27+
28+
func (GenericTableBuilder) Build(raw json.RawMessage, tool string, args ...any) string {
29+
// unmarshal only to discover the shape
30+
var rows []map[string]interface{}
31+
_ = json.Unmarshal(raw, &rows) // ignore errors; empty rows is OK
32+
33+
// determine allowed columns from args[0]
34+
var allowedCols []string
35+
if len(args) > 0 {
36+
if ac, ok := args[0].([]string); ok {
37+
allowedCols = ac
38+
}
39+
}
40+
allowed := map[string]bool{}
41+
for _, col := range allowedCols {
42+
allowed[col] = true
43+
}
44+
45+
// build column descriptors and filter rows in the order of allowedCols
46+
var cols []map[string]string
47+
var filteredRows []map[string]interface{}
48+
if len(allowedCols) > 0 {
49+
// Only include columns in allowedCols, in the order given
50+
for _, col := range allowedCols {
51+
cols = append(cols, map[string]string{
52+
"key": col,
53+
"label": col,
54+
})
55+
}
56+
for _, row := range rows {
57+
filtered := map[string]interface{}{}
58+
for _, col := range allowedCols {
59+
if v, ok := row[col]; ok {
60+
filtered[col] = v
61+
}
62+
}
63+
filteredRows = append(filteredRows, filtered)
64+
}
65+
} else {
66+
// Fallback: include all columns in original order
67+
if len(rows) > 0 {
68+
for k := range rows[0] {
69+
cols = append(cols, map[string]string{
70+
"key": k,
71+
"label": k,
72+
})
73+
}
74+
}
75+
for _, row := range rows {
76+
filtered := map[string]interface{}{}
77+
for k, v := range row {
78+
filtered[k] = v
79+
}
80+
filteredRows = append(filteredRows, filtered)
81+
}
82+
}
83+
84+
// Create base response with consistent structure
85+
env := builder.CreateBaseResponse(string(GenericTableEvent), tool)
86+
// Add table-specific data
87+
env["table"] = map[string]interface{}{
88+
"columns": cols,
89+
"rows": filteredRows,
90+
}
91+
// Format the response using the common formatter
92+
return builder.FormatEventResponse(string(GenericTableEvent), env)
93+
}
94+
95+
type OPABuilder struct{}
96+
97+
type PromptBuilder struct{}
98+
99+
func (PromptBuilder) Build(raw json.RawMessage, tool string, args ...any) string {
100+
eventType := string(PromptEvent)
101+
// Parse the raw JSON into a slice of prompts
102+
var prompts []string
103+
if err := json.Unmarshal(raw, &prompts); err != nil {
104+
// Return a safe error response instead of raw data
105+
return `{"error": "Failed to parse prompt data", "type": "prompt"}`
106+
}
107+
108+
// Create base response with consistent structure
109+
env := builder.CreateBaseResponse(eventType, tool)
110+
env["prompts"] = prompts
111+
112+
// Format the response using the common formatter
113+
return builder.FormatEventResponse(eventType, env)
114+
}
115+
116+
func (OPABuilder) Build(raw json.RawMessage, tool string, args ...any) string {
117+
// Use the event type constant for OPA policies
118+
eventType := string(OPAEvent)
119+
120+
// Parse the raw JSON into a map
121+
var data map[string]interface{}
122+
if err := json.Unmarshal(raw, &data); err != nil {
123+
// Return a safe error response
124+
return fmt.Sprintf(`{"type": "%s", "error": "Failed to parse policy data"}`, eventType)
125+
}
126+
127+
// Create base response with consistent structure
128+
env := builder.CreateBaseResponse(eventType, tool)
129+
// Add OPA-specific data with the new format
130+
env["policy"] = map[string]interface{}{
131+
"name": "deny-list",
132+
"content": data["policy"],
133+
}
134+
env["metadata"] = map[string]interface{}{
135+
"denied_licenses": data["denied_licenses"],
136+
}
137+
138+
// Format the response using the common formatter
139+
return builder.FormatEventResponse(eventType, env)
140+
}

pkg/harness/filter.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ func BuildArtifactListingBody(request mcp.CallToolRequest) (generated.ArtifactLi
4848
if err := bindParam(request, "license_filter", func(v generated.LicenseFilter) { bb.WithLicenseFilter(v) }); err != nil {
4949
return generated.ArtifactListingRequestBody{}, err
5050
}
51+
if err := bindParam(request, "license_filter_list", func(v []generated.LicenseFilter) { bb.WithLicenseFilterList(v) }); err != nil {
52+
return generated.ArtifactListingRequestBody{}, err
53+
}
5154
if err := bindParam(request, "policy_violation", func(v generated.ArtifactListingRequestBodyPolicyViolation) { bb.WithPolicyViolation(v) }); err != nil {
5255
return generated.ArtifactListingRequestBody{}, err
5356
}

pkg/harness/genai.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func AIDevOpsAgentTool(config *config.Config, client *client.GenaiService) (tool
9292
conversationRaw, _ := OptionalParam[[]any](request, "conversation_raw")
9393
harnessContextRaw, _ := OptionalParam[map[string]interface{}](request, "harness_context")
9494
stream := true // default value
95-
if streamArg, ok := request.Params.Arguments["stream"].(bool); ok {
95+
if streamArg, ok := request.GetArguments()["stream"].(bool); ok {
9696
stream = streamArg
9797
}
9898

0 commit comments

Comments
 (0)