From fa6552afcd5075aa6206dfdcd6318ec2dcbd593b Mon Sep 17 00:00:00 2001 From: bobby-smedley Date: Mon, 13 Apr 2026 11:36:25 -0400 Subject: [PATCH 1/4] feat: add MCP tool annotations for read-only vs destructive operations --- pkg/chip/server.go | 2 ++ pkg/tools/add_business_term/tool.go | 6 +++++- pkg/tools/add_data_classification_match/tool.go | 6 +++++- pkg/tools/create_asset/tool.go | 6 +++++- pkg/tools/discover_business_glossary/tool.go | 2 ++ pkg/tools/discover_data_assets/tool.go | 2 ++ pkg/tools/get_asset_details/tool.go | 2 ++ pkg/tools/get_business_term_data/tool.go | 2 ++ pkg/tools/get_column_semantics/tool.go | 2 ++ pkg/tools/get_lineage_downstream/tool.go | 2 ++ pkg/tools/get_lineage_entity/tool.go | 2 ++ pkg/tools/get_lineage_transformation/tool.go | 2 ++ pkg/tools/get_lineage_upstream/tool.go | 2 ++ pkg/tools/get_measure_data/tool.go | 2 ++ pkg/tools/get_table_semantics/tool.go | 2 ++ pkg/tools/list_asset_types/tool.go | 2 ++ pkg/tools/list_data_contracts/tool.go | 2 ++ pkg/tools/prepare_add_business_term/tool.go | 2 ++ pkg/tools/prepare_create_asset/tool.go | 2 ++ pkg/tools/pull_data_contract_manifest/tool.go | 2 ++ pkg/tools/push_data_contract_manifest/tool.go | 6 +++++- pkg/tools/remove_data_classification_match/tool.go | 6 +++++- pkg/tools/search_asset_keyword/tool.go | 2 ++ pkg/tools/search_data_classes/tool.go | 2 ++ pkg/tools/search_data_classification_matches/tool.go | 2 ++ pkg/tools/search_lineage_entities/tool.go | 2 ++ pkg/tools/search_lineage_transformations/tool.go | 2 ++ 27 files changed, 69 insertions(+), 5 deletions(-) diff --git a/pkg/chip/server.go b/pkg/chip/server.go index 8dc957c..0c9af64 100644 --- a/pkg/chip/server.go +++ b/pkg/chip/server.go @@ -89,6 +89,7 @@ type Tool[In, Out any] struct { Description string Handler ToolHandlerFunc[In, Out] Permissions []string + Annotations *mcp.ToolAnnotations } func RegisterTool[In, Out any](s *Server, tool *Tool[In, Out]) { @@ -131,6 +132,7 @@ func RegisterTool[In, Out any](s *Server, tool *Tool[In, Out]) { Description: tool.Description, InputSchema: buildSchema[In](), OutputSchema: buildSchema[Out](), + Annotations: tool.Annotations, }, handler) } diff --git a/pkg/tools/add_business_term/tool.go b/pkg/tools/add_business_term/tool.go index d34b9b7..bfef81f 100644 --- a/pkg/tools/add_business_term/tool.go +++ b/pkg/tools/add_business_term/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) const ( @@ -40,7 +41,8 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Name: "add_business_term", Description: "Create a business term asset with definition and optional attributes in Collibra.", Handler: handler(collibraClient), - Permissions: []string{}, + Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{DestructiveHint: boolPtr(false)}, } } @@ -85,3 +87,5 @@ func handler(collibraClient *http.Client) chip.ToolHandlerFunc[Input, Output] { return Output{AssetId: assetId}, nil } } + +func boolPtr(b bool) *bool { return &b } diff --git a/pkg/tools/add_data_classification_match/tool.go b/pkg/tools/add_data_classification_match/tool.go index dacd03c..488ff63 100644 --- a/pkg/tools/add_data_classification_match/tool.go +++ b/pkg/tools/add_data_classification_match/tool.go @@ -8,6 +8,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -26,7 +27,8 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Name: "add_data_classification_match", Description: "Associate a data classification (data class) with a specific data asset in Collibra. Requires both the asset UUID and the classification UUID.", Handler: handler(collibraClient), - Permissions: []string{"dgc.classify", "dgc.catalog"}, + Permissions: []string{"dgc.classify", "dgc.catalog"}, + Annotations: &mcp.ToolAnnotations{DestructiveHint: boolPtr(false)}, } } @@ -74,3 +76,5 @@ func validateInput(input Input) (Output, bool) { return Output{}, false } + +func boolPtr(b bool) *bool { return &b } diff --git a/pkg/tools/create_asset/tool.go b/pkg/tools/create_asset/tool.go index 00825c9..db5033c 100644 --- a/pkg/tools/create_asset/tool.go +++ b/pkg/tools/create_asset/tool.go @@ -7,6 +7,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) // Input defines the parameters for the create_asset tool. @@ -29,7 +30,8 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Name: "create_asset", Description: "Create a new data asset with optional attributes in Collibra.", Handler: handler(collibraClient), - Permissions: []string{}, + Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{DestructiveHint: boolPtr(false)}, } } @@ -65,3 +67,5 @@ func handler(collibraClient *http.Client) chip.ToolHandlerFunc[Input, Output] { return Output{AssetID: assetResp.ID}, nil } } + +func boolPtr(b bool) *bool { return &b } diff --git a/pkg/tools/discover_business_glossary/tool.go b/pkg/tools/discover_business_glossary/tool.go index d29c7f4..89bf601 100644 --- a/pkg/tools/discover_business_glossary/tool.go +++ b/pkg/tools/discover_business_glossary/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -22,6 +23,7 @@ func NewTool(collibraHttpClient *http.Client) *chip.Tool[Input, Output] { Description: "Perform a semantic search across business glossary content in Collibra. Ask natural language questions to discover business terms, acronyms, KPIs, and other business glossary content.", Handler: handler(collibraHttpClient), Permissions: []string{"dgc.ai-copilot"}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/discover_data_assets/tool.go b/pkg/tools/discover_data_assets/tool.go index 7ce2d31..af6de10 100644 --- a/pkg/tools/discover_data_assets/tool.go +++ b/pkg/tools/discover_data_assets/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -22,6 +23,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Perform a semantic search across available data assets in Collibra. Ask natural language questions to discover tables, columns, datasets, and other data assets.", Handler: handler(collibraClient), Permissions: []string{"dgc.ai-copilot"}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/get_asset_details/tool.go b/pkg/tools/get_asset_details/tool.go index 21054df..c4567a5 100644 --- a/pkg/tools/get_asset_details/tool.go +++ b/pkg/tools/get_asset_details/tool.go @@ -11,6 +11,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" "github.com/google/uuid" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -42,6 +43,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Get detailed information about a specific asset by its UUID, including attributes, relations, responsibilities (owners, stewards, and other role assignments), and metadata. Returns up to 100 attributes per type and supports cursor-based pagination for relations (50 per page).", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/get_business_term_data/tool.go b/pkg/tools/get_business_term_data/tool.go index e83617a..16d3f83 100644 --- a/pkg/tools/get_business_term_data/tool.go +++ b/pkg/tools/get_business_term_data/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type ColumnWithTable struct { @@ -47,6 +48,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Retrieve the physical data assets (Columns and Tables) associated with a Business Term via the path Business Term → Data Attribute → Column → Table.", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/get_column_semantics/tool.go b/pkg/tools/get_column_semantics/tool.go index beff244..330d09c 100644 --- a/pkg/tools/get_column_semantics/tool.go +++ b/pkg/tools/get_column_semantics/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) // AssetWithDescription represents an enriched asset used in traversal tool outputs. @@ -40,6 +41,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Retrieve all connected Data Attribute assets for a Column, including descriptions and related Measures and generic business assets with their descriptions.", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/get_lineage_downstream/tool.go b/pkg/tools/get_lineage_downstream/tool.go index 0ee1caa..c35fd7c 100644 --- a/pkg/tools/get_lineage_downstream/tool.go +++ b/pkg/tools/get_lineage_downstream/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -21,6 +22,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, clients.GetLineageDi Description: "Get the downstream technical lineage graph for a data entity -- all direct and indirect consumer entities that are impacted by it, along with the transformations connecting them. This traces through all data objects across external systems (including unregistered assets, temporary tables, and source code), not just assets in the Collibra Data Catalog. Use this to answer \"What depends on this data?\" or \"If this table changes, what else is affected?\" Essential for impact analysis before modifying or deprecating a data asset. Results are paginated.", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/get_lineage_entity/tool.go b/pkg/tools/get_lineage_entity/tool.go index d61d6dd..3ef7e65 100644 --- a/pkg/tools/get_lineage_entity/tool.go +++ b/pkg/tools/get_lineage_entity/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -18,6 +19,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, clients.GetLineageEn Description: "Get detailed metadata about a specific data entity in the technical lineage graph. Technical lineage covers all data objects across external systems -- including source code, transformations, and temporary tables -- regardless of whether they are registered in Collibra (unlike business lineage, which only covers assets ingested into the Data Catalog). An entity represents any tracked data asset such as a table, column, file, report, API endpoint, or topic. Returns the entity's name, type, source systems, parent entity, and linked Data Governance Catalog (DGC) identifier. Use this when you have an entity ID from a lineage traversal, search result, or user input and need its full details.", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/get_lineage_transformation/tool.go b/pkg/tools/get_lineage_transformation/tool.go index 6ecfb6d..581cfaf 100644 --- a/pkg/tools/get_lineage_transformation/tool.go +++ b/pkg/tools/get_lineage_transformation/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -18,6 +19,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, clients.GetLineageTr Description: "Get detailed information about a specific data transformation, including its SQL or script logic. A transformation represents a data processing activity (ETL job, SQL query, script, etc.) that connects source entities to target entities in the lineage graph. Use this when you found a transformation ID in an upstream/downstream lineage result and want to see what the transformation actually does -- the SQL query, script content, or processing logic.", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/get_lineage_upstream/tool.go b/pkg/tools/get_lineage_upstream/tool.go index 4dd591a..37b4aec 100644 --- a/pkg/tools/get_lineage_upstream/tool.go +++ b/pkg/tools/get_lineage_upstream/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -21,6 +22,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, clients.GetLineageDi Description: "Get the upstream technical lineage graph for a data entity -- all direct and indirect source entities that feed data into it, along with the transformations connecting them. This traces through all data objects across external systems (including unregistered assets, temporary tables, and source code), not just assets in the Collibra Data Catalog. Use this to answer \"Where does this data come from?\" or \"What are the sources feeding this table?\" Each relation in the result connects a source entity to a target entity through one or more transformations. Results are paginated.", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/get_measure_data/tool.go b/pkg/tools/get_measure_data/tool.go index 6a0056c..eddd5c1 100644 --- a/pkg/tools/get_measure_data/tool.go +++ b/pkg/tools/get_measure_data/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type AssetWithDescription struct { @@ -46,6 +47,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Retrieve all underlying Column assets connected to a Measure via the path Measure → Data Attribute → Column, including each Column's description and parent Table.", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/get_table_semantics/tool.go b/pkg/tools/get_table_semantics/tool.go index efa8e8c..d888e9e 100644 --- a/pkg/tools/get_table_semantics/tool.go +++ b/pkg/tools/get_table_semantics/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type AssetWithDescription struct { @@ -47,6 +48,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Retrieve the semantic layer for a Table asset: Columns, their Data Attributes, and connected Measures. Answers 'What is the semantic context of this table?' or 'Which metrics use data from this table?'.", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/list_asset_types/tool.go b/pkg/tools/list_asset_types/tool.go index 1a683e0..9089d94 100644 --- a/pkg/tools/list_asset_types/tool.go +++ b/pkg/tools/list_asset_types/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -38,6 +39,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "List asset types available in Collibra with their properties and metadata.", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/list_data_contracts/tool.go b/pkg/tools/list_data_contracts/tool.go index 733651c..3ad6461 100644 --- a/pkg/tools/list_data_contracts/tool.go +++ b/pkg/tools/list_data_contracts/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -33,6 +34,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "List data contracts available in Collibra. Returns a paginated list of data contract metadata, sorted by the last modified date in descending order.", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/prepare_add_business_term/tool.go b/pkg/tools/prepare_add_business_term/tool.go index 0a90dd8..e9d4bb5 100644 --- a/pkg/tools/prepare_add_business_term/tool.go +++ b/pkg/tools/prepare_add_business_term/tool.go @@ -7,6 +7,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) const businessTermPublicID = "BusinessTerm" @@ -81,6 +82,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Validate business term data, resolve domains, check for duplicates, and hydrate attribute schemas. Returns structured status with pre-fetched options for missing fields.", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/prepare_create_asset/tool.go b/pkg/tools/prepare_create_asset/tool.go index 0480f68..b131997 100644 --- a/pkg/tools/prepare_create_asset/tool.go +++ b/pkg/tools/prepare_create_asset/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) const maxOptions = 20 @@ -76,6 +77,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Resolve asset type, domain, hydrate full attribute schema, check duplicates — return structured status for asset creation readiness.", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/pull_data_contract_manifest/tool.go b/pkg/tools/pull_data_contract_manifest/tool.go index a6301c6..d0de293 100644 --- a/pkg/tools/pull_data_contract_manifest/tool.go +++ b/pkg/tools/pull_data_contract_manifest/tool.go @@ -8,6 +8,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" "github.com/google/uuid" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -26,6 +27,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Download the manifest file for the currently active version of a specific data contract. Returns the manifest content as a string.", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/push_data_contract_manifest/tool.go b/pkg/tools/push_data_contract_manifest/tool.go index 864a73a..28c2200 100644 --- a/pkg/tools/push_data_contract_manifest/tool.go +++ b/pkg/tools/push_data_contract_manifest/tool.go @@ -7,6 +7,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -30,7 +31,8 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Name: "push_data_contract_manifest", Description: "Upload a new version of a data contract manifest to Collibra. The manifestID and version are automatically parsed from the manifest content if it adheres to the Open Data Contract Standard.", Handler: handler(collibraClient), - Permissions: []string{"dgc.data-contract"}, + Permissions: []string{"dgc.data-contract"}, + Annotations: &mcp.ToolAnnotations{DestructiveHint: boolPtr(true)}, } } @@ -67,3 +69,5 @@ func handler(collibraClient *http.Client) chip.ToolHandlerFunc[Input, Output] { }, nil } } + +func boolPtr(b bool) *bool { return &b } diff --git a/pkg/tools/remove_data_classification_match/tool.go b/pkg/tools/remove_data_classification_match/tool.go index deb8007..ac076bf 100644 --- a/pkg/tools/remove_data_classification_match/tool.go +++ b/pkg/tools/remove_data_classification_match/tool.go @@ -8,6 +8,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -24,7 +25,8 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Name: "remove_data_classification_match", Description: "Remove a classification match (association between a data class and an asset) from Collibra. Requires the UUID of the classification match to remove.", Handler: handler(collibraClient), - Permissions: []string{"dgc.classify", "dgc.catalog", "dgc.data-classes-edit"}, + Permissions: []string{"dgc.classify", "dgc.catalog", "dgc.data-classes-edit"}, + Annotations: &mcp.ToolAnnotations{DestructiveHint: boolPtr(true), IdempotentHint: true}, } } @@ -59,3 +61,5 @@ func validateInput(input Input) (Output, bool) { return Output{}, false } + +func boolPtr(b bool) *bool { return &b } diff --git a/pkg/tools/search_asset_keyword/tool.go b/pkg/tools/search_asset_keyword/tool.go index 9937827..802dfaa 100644 --- a/pkg/tools/search_asset_keyword/tool.go +++ b/pkg/tools/search_asset_keyword/tool.go @@ -7,6 +7,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -42,6 +43,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Perform a wildcard keyword search for assets in the Collibra knowledge graph. Supports filtering by resource type, community, domain, asset type, status, and creator.", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/search_data_classes/tool.go b/pkg/tools/search_data_classes/tool.go index 32ce780..9517ed8 100644 --- a/pkg/tools/search_data_classes/tool.go +++ b/pkg/tools/search_data_classes/tool.go @@ -7,6 +7,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -30,6 +31,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Search for data classes in Collibra's classification service. Supports filtering by name, description, and whether they contain rules.", Handler: handler(collibraClient), Permissions: []string{"dgc.data-classes-read"}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/search_data_classification_matches/tool.go b/pkg/tools/search_data_classification_matches/tool.go index 7c38feb..0d450bb 100644 --- a/pkg/tools/search_data_classification_matches/tool.go +++ b/pkg/tools/search_data_classification_matches/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -31,6 +32,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Search for classification matches (associations between data classes and assets) in Collibra. Supports filtering by asset IDs, statuses (ACCEPTED/REJECTED/SUGGESTED), classification IDs, and asset type IDs.", Handler: handler(collibraClient), Permissions: []string{"dgc.classify", "dgc.catalog"}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/search_lineage_entities/tool.go b/pkg/tools/search_lineage_entities/tool.go index e66970f..78beb4c 100644 --- a/pkg/tools/search_lineage_entities/tool.go +++ b/pkg/tools/search_lineage_entities/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -22,6 +23,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, clients.SearchLineag Description: "Search for data entities in the technical lineage graph by name, type, or DGC identifier. Technical lineage covers all data objects across external systems -- including source code, transformations, and temporary tables -- regardless of whether they are registered in Collibra (unlike business lineage, which only covers assets ingested into the Data Catalog). Returns a paginated list of matching entities. This is typically the starting tool when you don't have a specific entity ID -- for example, to find all tables with \"sales\" in the name, or to find the lineage entity linked to a specific Collibra catalog asset via its DGC UUID. Supports partial name matching (case insensitive).", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } diff --git a/pkg/tools/search_lineage_transformations/tool.go b/pkg/tools/search_lineage_transformations/tool.go index 4edcbcf..dfbe9f0 100644 --- a/pkg/tools/search_lineage_transformations/tool.go +++ b/pkg/tools/search_lineage_transformations/tool.go @@ -6,6 +6,7 @@ import ( "github.com/collibra/chip/pkg/chip" "github.com/collibra/chip/pkg/clients" + "github.com/modelcontextprotocol/go-sdk/mcp" ) type Input struct { @@ -20,6 +21,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, clients.SearchLineag Description: "Search for transformations in the technical lineage graph by name. Returns a paginated list of matching transformation summaries. Use this to discover ETL jobs, SQL queries, or other processing activities without knowing their IDs. For example, find all transformations with \"etl\" or \"sales\" in the name. To see the full transformation logic (SQL/script), use get_lineage_transformation with the returned ID.", Handler: handler(collibraClient), Permissions: []string{}, + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, } } From 3b5f6e2b22ad2a8d6f3777e8f2f674dcda934618 Mon Sep 17 00:00:00 2001 From: bobby-smedley Date: Mon, 13 Apr 2026 12:22:33 -0400 Subject: [PATCH 2/4] feat: add MCP tool annotations for read-only vs destructive operations --- pkg/chip/ptr.go | 4 ++++ pkg/tools/add_business_term/tool.go | 4 +--- pkg/tools/add_data_classification_match/tool.go | 4 +--- pkg/tools/create_asset/tool.go | 4 +--- pkg/tools/push_data_contract_manifest/tool.go | 4 +--- pkg/tools/remove_data_classification_match/tool.go | 4 +--- 6 files changed, 9 insertions(+), 15 deletions(-) create mode 100644 pkg/chip/ptr.go diff --git a/pkg/chip/ptr.go b/pkg/chip/ptr.go new file mode 100644 index 0000000..b9df9e1 --- /dev/null +++ b/pkg/chip/ptr.go @@ -0,0 +1,4 @@ +package chip + +// Ptr returns a pointer to the given value. +func Ptr[T any](v T) *T { return &v } diff --git a/pkg/tools/add_business_term/tool.go b/pkg/tools/add_business_term/tool.go index bfef81f..356eadf 100644 --- a/pkg/tools/add_business_term/tool.go +++ b/pkg/tools/add_business_term/tool.go @@ -42,7 +42,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Create a business term asset with definition and optional attributes in Collibra.", Handler: handler(collibraClient), Permissions: []string{}, - Annotations: &mcp.ToolAnnotations{DestructiveHint: boolPtr(false)}, + Annotations: &mcp.ToolAnnotations{DestructiveHint: chip.Ptr(false)}, } } @@ -87,5 +87,3 @@ func handler(collibraClient *http.Client) chip.ToolHandlerFunc[Input, Output] { return Output{AssetId: assetId}, nil } } - -func boolPtr(b bool) *bool { return &b } diff --git a/pkg/tools/add_data_classification_match/tool.go b/pkg/tools/add_data_classification_match/tool.go index 488ff63..17c0617 100644 --- a/pkg/tools/add_data_classification_match/tool.go +++ b/pkg/tools/add_data_classification_match/tool.go @@ -28,7 +28,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Associate a data classification (data class) with a specific data asset in Collibra. Requires both the asset UUID and the classification UUID.", Handler: handler(collibraClient), Permissions: []string{"dgc.classify", "dgc.catalog"}, - Annotations: &mcp.ToolAnnotations{DestructiveHint: boolPtr(false)}, + Annotations: &mcp.ToolAnnotations{DestructiveHint: chip.Ptr(false)}, } } @@ -76,5 +76,3 @@ func validateInput(input Input) (Output, bool) { return Output{}, false } - -func boolPtr(b bool) *bool { return &b } diff --git a/pkg/tools/create_asset/tool.go b/pkg/tools/create_asset/tool.go index db5033c..d792361 100644 --- a/pkg/tools/create_asset/tool.go +++ b/pkg/tools/create_asset/tool.go @@ -31,7 +31,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Create a new data asset with optional attributes in Collibra.", Handler: handler(collibraClient), Permissions: []string{}, - Annotations: &mcp.ToolAnnotations{DestructiveHint: boolPtr(false)}, + Annotations: &mcp.ToolAnnotations{DestructiveHint: chip.Ptr(false)}, } } @@ -67,5 +67,3 @@ func handler(collibraClient *http.Client) chip.ToolHandlerFunc[Input, Output] { return Output{AssetID: assetResp.ID}, nil } } - -func boolPtr(b bool) *bool { return &b } diff --git a/pkg/tools/push_data_contract_manifest/tool.go b/pkg/tools/push_data_contract_manifest/tool.go index 28c2200..791ecc2 100644 --- a/pkg/tools/push_data_contract_manifest/tool.go +++ b/pkg/tools/push_data_contract_manifest/tool.go @@ -32,7 +32,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Upload a new version of a data contract manifest to Collibra. The manifestID and version are automatically parsed from the manifest content if it adheres to the Open Data Contract Standard.", Handler: handler(collibraClient), Permissions: []string{"dgc.data-contract"}, - Annotations: &mcp.ToolAnnotations{DestructiveHint: boolPtr(true)}, + Annotations: &mcp.ToolAnnotations{DestructiveHint: chip.Ptr(true)}, } } @@ -69,5 +69,3 @@ func handler(collibraClient *http.Client) chip.ToolHandlerFunc[Input, Output] { }, nil } } - -func boolPtr(b bool) *bool { return &b } diff --git a/pkg/tools/remove_data_classification_match/tool.go b/pkg/tools/remove_data_classification_match/tool.go index ac076bf..5982ee3 100644 --- a/pkg/tools/remove_data_classification_match/tool.go +++ b/pkg/tools/remove_data_classification_match/tool.go @@ -26,7 +26,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Remove a classification match (association between a data class and an asset) from Collibra. Requires the UUID of the classification match to remove.", Handler: handler(collibraClient), Permissions: []string{"dgc.classify", "dgc.catalog", "dgc.data-classes-edit"}, - Annotations: &mcp.ToolAnnotations{DestructiveHint: boolPtr(true), IdempotentHint: true}, + Annotations: &mcp.ToolAnnotations{DestructiveHint: chip.Ptr(true), IdempotentHint: true}, } } @@ -61,5 +61,3 @@ func validateInput(input Input) (Output, bool) { return Output{}, false } - -func boolPtr(b bool) *bool { return &b } From 03148636c8c1ef876d8b268f3a01b814713f334f Mon Sep 17 00:00:00 2001 From: bobby-smedley Date: Mon, 13 Apr 2026 12:25:46 -0400 Subject: [PATCH 3/4] feat: add MCP tool annotations for read-only vs destructive operations --- pkg/tools/add_business_term/tool.go | 2 +- pkg/tools/add_data_classification_match/tool.go | 2 +- pkg/tools/create_asset/tool.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/tools/add_business_term/tool.go b/pkg/tools/add_business_term/tool.go index 356eadf..e0a3366 100644 --- a/pkg/tools/add_business_term/tool.go +++ b/pkg/tools/add_business_term/tool.go @@ -42,7 +42,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Create a business term asset with definition and optional attributes in Collibra.", Handler: handler(collibraClient), Permissions: []string{}, - Annotations: &mcp.ToolAnnotations{DestructiveHint: chip.Ptr(false)}, + Annotations: &mcp.ToolAnnotations{DestructiveHint: chip.Ptr(true)}, } } diff --git a/pkg/tools/add_data_classification_match/tool.go b/pkg/tools/add_data_classification_match/tool.go index 17c0617..49fe87b 100644 --- a/pkg/tools/add_data_classification_match/tool.go +++ b/pkg/tools/add_data_classification_match/tool.go @@ -28,7 +28,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Associate a data classification (data class) with a specific data asset in Collibra. Requires both the asset UUID and the classification UUID.", Handler: handler(collibraClient), Permissions: []string{"dgc.classify", "dgc.catalog"}, - Annotations: &mcp.ToolAnnotations{DestructiveHint: chip.Ptr(false)}, + Annotations: &mcp.ToolAnnotations{DestructiveHint: chip.Ptr(true)}, } } diff --git a/pkg/tools/create_asset/tool.go b/pkg/tools/create_asset/tool.go index d792361..879b6f0 100644 --- a/pkg/tools/create_asset/tool.go +++ b/pkg/tools/create_asset/tool.go @@ -31,7 +31,7 @@ func NewTool(collibraClient *http.Client) *chip.Tool[Input, Output] { Description: "Create a new data asset with optional attributes in Collibra.", Handler: handler(collibraClient), Permissions: []string{}, - Annotations: &mcp.ToolAnnotations{DestructiveHint: chip.Ptr(false)}, + Annotations: &mcp.ToolAnnotations{DestructiveHint: chip.Ptr(true)}, } } From 3884795b349116d855470f4d84ad06a6e75d23d5 Mon Sep 17 00:00:00 2001 From: bobby-smedley Date: Tue, 14 Apr 2026 07:32:44 -0400 Subject: [PATCH 4/4] bump version --- pkg/chip/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/chip/version.go b/pkg/chip/version.go index 50e15d3..822a1c3 100644 --- a/pkg/chip/version.go +++ b/pkg/chip/version.go @@ -1,3 +1,3 @@ package chip -var Version = "0.0.29-SNAPSHOT" +var Version = "0.0.30-SNAPSHOT"