Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,28 @@ go 1.25.0

require (
github.com/google/go-querystring v1.1.0
github.com/google/jsonschema-go v0.4.2
github.com/google/uuid v1.6.0
github.com/modelcontextprotocol/go-sdk v1.1.0
github.com/modelcontextprotocol/go-sdk v1.5.0
github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.21.0
)

require (
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/google/jsonschema-go v0.3.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect
github.com/segmentio/asm v1.1.3 // indirect
github.com/segmentio/encoding v0.5.4 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.32.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
26 changes: 16 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
Expand All @@ -22,8 +24,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/modelcontextprotocol/go-sdk v1.1.0 h1:Qjayg53dnKC4UZ+792W21e4BpwEZBzwgRW6LrjLWSwA=
github.com/modelcontextprotocol/go-sdk v1.1.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
github.com/modelcontextprotocol/go-sdk v1.5.0 h1:CHU0FIX9kpueNkxuYtfYQn1Z0slhFzBZuq+x6IiblIU=
github.com/modelcontextprotocol/go-sdk v1.5.0/go.mod h1:gggDIhoemhWs3BGkGwd1umzEXCEMMvAnhTrnbXJKKKA=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand All @@ -32,6 +34,10 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0=
github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
Expand All @@ -48,14 +54,14 @@ github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zI
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
Expand Down
47 changes: 47 additions & 0 deletions pkg/chip/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package chip
import (
"context"
"log"
"strings"
"testing"

"github.com/modelcontextprotocol/go-sdk/mcp"
Expand Down Expand Up @@ -30,6 +31,52 @@ func handleTool() ToolHandlerFunc[toolInput, toolOutput] {
}
}

func TestTool_SchemaValidationReturnsToolExecutionError(t *testing.T) {
chipServer := NewServer()
RegisterTool[toolInput, toolOutput](chipServer, newTool())
chipSession := newChipSession(t.Context(), chipServer)
defer closeSilently(chipSession)

res, err := chipSession.CallTool(t.Context(), &mcp.CallToolParams{
Name: "the_tool",
Arguments: map[string]any{},
})
if err != nil {
t.Fatalf("expected tool execution error, got protocol error: %v", err)
}
if !res.IsError {
t.Fatal("expected isError: true for missing required field")
}
if len(res.Content) == 0 {
t.Fatal("expected content describing the validation failure")
}
text, ok := res.Content[0].(*mcp.TextContent)
if !ok {
t.Fatalf("expected TextContent, got %T", res.Content[0])
}
if !strings.Contains(text.Text, "input") {
t.Fatalf("expected error mentioning missing field 'input', got %q", text.Text)
}
}

func TestTool_WrongTypeReturnsToolExecutionError(t *testing.T) {
chipServer := NewServer()
RegisterTool[toolInput, toolOutput](chipServer, newTool())
chipSession := newChipSession(t.Context(), chipServer)
defer closeSilently(chipSession)

res, err := chipSession.CallTool(t.Context(), &mcp.CallToolParams{
Name: "the_tool",
Arguments: map[string]any{"input": 123},
})
if err != nil {
t.Fatalf("expected tool execution error, got protocol error: %v", err)
}
if !res.IsError {
t.Fatal("expected isError: true for wrong-typed field")
}
}

func TestTool_IgnoreUnknownFields(t *testing.T) {
chipServer := NewServer()
RegisterTool[toolInput, toolOutput](chipServer, newTool())
Expand Down
11 changes: 11 additions & 0 deletions pkg/tools/add_business_term/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package add_business_term

import (
"context"
"fmt"
"net/http"

"github.com/collibra/chip/pkg/chip"
"github.com/collibra/chip/pkg/clients"
"github.com/collibra/chip/pkg/tools/validation"
"github.com/modelcontextprotocol/go-sdk/mcp"
)

Expand Down Expand Up @@ -48,6 +50,15 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] {

func handler(collibraClient *http.Client) chip.ToolHandlerFunc[Input, Output] {
return func(ctx context.Context, input Input) (Output, error) {
if err := validation.UUID("domainId", input.DomainId); err != nil {
return Output{}, err
}
for i, attr := range input.Attributes {
if err := validation.UUID(fmt.Sprintf("attributes[%d].typeId", i), attr.TypeId); err != nil {
return Output{}, err
}
}

// Step 1: Create the business term asset
assetResp, err := clients.CreateBusinessTermAsset(ctx, collibraClient, clients.AddBusinessTermAssetRequest{
Name: input.Name,
Expand Down
16 changes: 8 additions & 8 deletions pkg/tools/add_business_term/tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ func TestAddBusinessTermSuccess(t *testing.T) {
if req.TypePublicId != "BusinessTerm" {
t.Errorf("expected typePublicId 'BusinessTerm', got '%s'", req.TypePublicId)
}
if req.DomainId != "domain-uuid-123" {
t.Errorf("expected domainId 'domain-uuid-123', got '%s'", req.DomainId)
if req.DomainId != "00000000-0000-0000-0000-000000000123" {
t.Errorf("expected domainId '00000000-0000-0000-0000-000000000123', got '%s'", req.DomainId)
}
return http.StatusCreated, clients.AddBusinessTermAssetResponse{Id: "new-asset-uuid-456"}
}))
Expand All @@ -45,7 +45,7 @@ func TestAddBusinessTermSuccess(t *testing.T) {
client := testutil.NewClient(server)
output, err := add_business_term.NewTool(client).Handler(t.Context(), add_business_term.Input{
Name: "Revenue",
DomainId: "domain-uuid-123",
DomainId: "00000000-0000-0000-0000-000000000123",
Definition: "Total income generated from sales",
})
if err != nil {
Expand Down Expand Up @@ -97,7 +97,7 @@ func TestAddBusinessTermNoDefinitionNoAttributes(t *testing.T) {
client := testutil.NewClient(server)
output, err := add_business_term.NewTool(client).Handler(t.Context(), add_business_term.Input{
Name: "Simple Term",
DomainId: "domain-uuid-456",
DomainId: "00000000-0000-0000-0000-000000000456",
})
if err != nil {
t.Fatalf("expected no error, got: %v", err)
Expand Down Expand Up @@ -132,11 +132,11 @@ func TestAddBusinessTermWithAdditionalAttributes(t *testing.T) {
client := testutil.NewClient(server)
output, err := add_business_term.NewTool(client).Handler(t.Context(), add_business_term.Input{
Name: "Complex Term",
DomainId: "domain-uuid-789",
DomainId: "00000000-0000-0000-0000-000000000789",
Definition: "A complex business term",
Attributes: []add_business_term.InputAttribute{
{TypeId: "custom-type-1", Value: "custom value 1"},
{TypeId: "custom-type-2", Value: "custom value 2"},
{TypeId: "00000000-0000-0000-0000-0000000000c1", Value: "custom value 1"},
{TypeId: "00000000-0000-0000-0000-0000000000c2", Value: "custom value 2"},
},
})
if err != nil {
Expand Down Expand Up @@ -169,7 +169,7 @@ func TestAddBusinessTermAttributeCreationError(t *testing.T) {
client := testutil.NewClient(server)
_, err := add_business_term.NewTool(client).Handler(t.Context(), add_business_term.Input{
Name: "Failing Term",
DomainId: "domain-uuid-123",
DomainId: "00000000-0000-0000-0000-000000000123",
Definition: "This should fail on attribute creation",
})
if err == nil {
Expand Down
28 changes: 6 additions & 22 deletions pkg/tools/add_data_classification_match/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import (
"context"
"fmt"
"net/http"
"strings"

"github.com/collibra/chip/pkg/chip"
"github.com/collibra/chip/pkg/clients"
"github.com/collibra/chip/pkg/tools/validation"
"github.com/modelcontextprotocol/go-sdk/mcp"
)

Expand All @@ -34,9 +34,11 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] {

func handler(collibraClient *http.Client) chip.ToolHandlerFunc[Input, Output] {
return func(ctx context.Context, input Input) (Output, error) {
output, isNotValid := validateInput(input)
if isNotValid {
return output, nil
if err := validation.UUID("assetId", input.AssetID); err != nil {
return Output{}, err
}
if err := validation.UUID("classificationId", input.ClassificationID); err != nil {
return Output{}, err
}

request := clients.AddDataClassificationMatchRequest{
Expand All @@ -58,21 +60,3 @@ func handler(collibraClient *http.Client) chip.ToolHandlerFunc[Input, Output] {
}, nil
}
}

func validateInput(input Input) (Output, bool) {
if strings.TrimSpace(input.AssetID) == "" {
return Output{
Success: false,
Error: "Asset ID is required",
}, true
}

if strings.TrimSpace(input.ClassificationID) == "" {
return Output{
Success: false,
Error: "Classification ID is required",
}, true
}

return Output{}, false
}
30 changes: 6 additions & 24 deletions pkg/tools/add_data_classification_match/tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,18 +81,9 @@ func TestAddClassificationMatch_MissingAssetID(t *testing.T) {
ClassificationID: "be45c001-b173-48ff-ac91-3f6e45868c8b",
}

output, err := tools.NewTool(client).Handler(t.Context(), input)

if err != nil {
t.Fatalf("Expected no error, got %v", err)
}

if output.Success {
t.Error("Expected success=false for missing asset ID")
}

if output.Error == "" {
t.Error("Expected error message for missing asset ID")
_, err := tools.NewTool(client).Handler(t.Context(), input)
if err == nil {
t.Fatal("Expected UUID validation error, got nil")
}
}

Expand All @@ -103,18 +94,9 @@ func TestAddClassificationMatch_MissingClassificationID(t *testing.T) {
AssetID: "9179b887-04ef-4ce5-ab3a-b5bbd39ea3c8",
}

output, err := tools.NewTool(client).Handler(t.Context(), input)

if err != nil {
t.Fatalf("Expected no error, got %v", err)
}

if output.Success {
t.Error("Expected success=false for missing classification ID")
}

if output.Error == "" {
t.Error("Expected error message for missing classification ID")
_, err := tools.NewTool(client).Handler(t.Context(), input)
if err == nil {
t.Fatal("Expected UUID validation error, got nil")
}
}

Expand Down
13 changes: 13 additions & 0 deletions pkg/tools/create_asset/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/collibra/chip/pkg/chip"
"github.com/collibra/chip/pkg/clients"
"github.com/collibra/chip/pkg/tools/validation"
"github.com/modelcontextprotocol/go-sdk/mcp"
)

Expand Down Expand Up @@ -37,6 +38,18 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] {

func handler(collibraClient *http.Client) chip.ToolHandlerFunc[Input, Output] {
return func(ctx context.Context, input Input) (Output, error) {
if err := validation.UUID("assetTypeId", input.AssetTypeID); err != nil {
return Output{}, err
}
if err := validation.UUID("domainId", input.DomainID); err != nil {
return Output{}, err
}
for typeID := range input.Attributes {
if err := validation.UUID(fmt.Sprintf("attributes[%q]", typeID), typeID); err != nil {
return Output{}, err
}
}

// Create the asset
assetReq := clients.CreateAssetRequest{
Name: input.Name,
Expand Down
Loading
Loading