Skip to content

Commit fcd70a5

Browse files
add: support for custom_metadata filterds in search_assets tool
1 parent 8bfe222 commit fcd70a5

File tree

3 files changed

+115
-13
lines changed

3 files changed

+115
-13
lines changed

modelcontextprotocol/server.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
@mcp.tool()
2828
def search_assets_tool(
2929
conditions=None,
30+
custom_metadata_conditions=None,
3031
negative_conditions=None,
3132
some_conditions=None,
3233
min_somes=1,
@@ -50,6 +51,8 @@ def search_assets_tool(
5051
Args:
5152
conditions (Dict[str, Any], optional): Dictionary of attribute conditions to match.
5253
Format: {"attribute_name": value} or {"attribute_name": {"operator": operator, "value": value}}
54+
custom_metadata_conditions (List[Dict[str, Any]], optional): List of custom metadata conditions to match.
55+
Format: [{"custom_metadata": value}] or [{"custom_metadata": {"operator": operator, "value": value}}]
5356
negative_conditions (Dict[str, Any], optional): Dictionary of attribute conditions to exclude.
5457
Format: {"attribute_name": value} or {"attribute_name": {"operator": operator, "value": value}}
5558
some_conditions (Dict[str, Any], optional): Conditions for where_some() queries that require min_somes of them to match.
@@ -95,6 +98,15 @@ def search_assets_tool(
9598
include_attributes=["owner_users", "owner_groups"]
9699
)
97100
101+
# Search for assets with custom metadata
102+
asset_list_1 = search_assets(
103+
custom_metadata_conditions=[{"custom_metadata_filter": {"display_name": "test-mcp", "property_filters": [{"property_name": "mcp_allow_status", "property_value": "yes"}]}}]
104+
)
105+
106+
asset_list_2 = search_assets(
107+
custom_metadata_conditions=[{"custom_metadata_filter": {"display_name": "test-mcp", "property_filters": [{"property_name": "mcp_allow_status", "property_value": "yes", "operator": "eq"}]}}]
108+
)
109+
98110
# Search for columns with specific certificate status
99111
columns = search_assets(
100112
asset_type="Column",
@@ -219,6 +231,7 @@ def search_assets_tool(
219231
try:
220232
# Parse JSON string parameters if needed
221233
conditions = parse_json_parameter(conditions)
234+
custom_metadata_conditions = parse_json_parameter(custom_metadata_conditions)
222235
negative_conditions = parse_json_parameter(negative_conditions)
223236
some_conditions = parse_json_parameter(some_conditions)
224237
date_range = parse_json_parameter(date_range)
@@ -229,6 +242,7 @@ def search_assets_tool(
229242

230243
return search_assets(
231244
conditions,
245+
custom_metadata_conditions,
232246
negative_conditions,
233247
some_conditions,
234248
min_somes,
@@ -683,42 +697,42 @@ def create_glossary_categories(categories) -> List[Dict[str, Any]]:
683697
def detect_custom_metadata_from_query(query_text: str) -> Dict[str, Any]:
684698
"""
685699
Detect custom metadata triggers from natural language queries.
686-
687-
This tool analyzes natural language text to identify when users are referencing
688-
custom metadata (business metadata) and automatically provides context about
700+
701+
This tool analyzes natural language text to identify when users are referencing
702+
custom metadata (business metadata) and automatically provides context about
689703
available custom metadata definitions. Use this tool when you receive natural
690704
language queries that might involve custom metadata concepts.
691-
705+
692706
Args:
693707
query_text (str): Natural language query text to analyze for custom metadata references
694-
708+
695709
Returns:
696710
Dict[str, Any]: Dictionary containing:
697711
- detected: Boolean indicating if custom metadata was detected
698712
- context: Custom metadata context if detected (list of metadata definitions)
699713
- detection_reasons: List of reasons why custom metadata was detected
700714
- suggested_attributes: List of suggested custom metadata attributes
701-
715+
702716
Detection Triggers:
703717
The tool detects custom metadata usage when the query contains:
704718
- Business metadata keywords (e.g., "business metadata", "data classification")
705719
- Data governance terms (e.g., "PII", "GDPR", "compliance", "data quality")
706720
- Attribute patterns (e.g., "sensitivity level", "business owner", "data steward")
707721
- Quality and classification terms (e.g., "quality score", "classification level")
708-
722+
709723
Examples:
710724
# Query mentioning data classification
711725
result = detect_custom_metadata_from_query("Find all tables with sensitive data classification")
712-
726+
713727
# Query about data quality
714728
result = detect_custom_metadata_from_query("Show me assets with poor data quality scores")
715-
729+
716730
# Query about business ownership
717731
result = detect_custom_metadata_from_query("Which datasets have John as the business owner?")
718-
732+
719733
# Query about compliance
720734
result = detect_custom_metadata_from_query("Find all PII data that needs GDPR compliance review")
721-
735+
722736
Use Cases:
723737
- Analyze user queries before executing searches to provide custom metadata context
724738
- Understand when users are asking about business metadata attributes
@@ -733,7 +747,7 @@ def detect_custom_metadata_from_query(query_text: str) -> Dict[str, Any]:
733747
"context": None,
734748
"detection_reasons": [],
735749
"suggested_attributes": [],
736-
"error": f"Failed to detect custom metadata: {str(e)}"
750+
"error": f"Failed to detect custom metadata: {str(e)}",
737751
}
738752

739753

modelcontextprotocol/tools/search.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
def search_assets(
1616
conditions: Optional[Union[Dict[str, Any], str]] = None,
17+
custom_metadata_conditions: Optional[List[Dict[str, Any]]] = None,
1718
negative_conditions: Optional[Dict[str, Any]] = None,
1819
some_conditions: Optional[Dict[str, Any]] = None,
1920
min_somes: int = 1,
@@ -40,6 +41,8 @@ def search_assets(
4041
Args:
4142
conditions (Dict[str, Any], optional): Dictionary of attribute conditions to match.
4243
Format: {"attribute_name": value} or {"attribute_name": {"operator": operator, "value": value}}
44+
custom_metadata_conditions (List[Dict[str, Any]], optional): List of custom metadata conditions to match.
45+
Format: [{"custom_metadata": value}] or [{"custom_metadata": {"operator": operator, "value": value}}]
4346
negative_conditions (Dict[str, Any], optional): Dictionary of attribute conditions to exclude.
4447
Format: {"attribute_name": value} or {"attribute_name": {"operator": operator, "value": value}}
4548
some_conditions (Dict[str, Any], optional): Conditions for where_some() queries that require min_somes of them to match.
@@ -187,6 +190,19 @@ def search_assets(
187190
)
188191
search = search.min_somes(min_somes)
189192

193+
if custom_metadata_conditions:
194+
logger.debug(
195+
f"Applying custom metadata conditions: {custom_metadata_conditions}"
196+
)
197+
for custom_metadata_filter_onject in custom_metadata_conditions:
198+
if isinstance(custom_metadata_filter_onject, dict):
199+
_, condition = next(iter(custom_metadata_filter_onject.items()))
200+
else:
201+
condition = custom_metadata_filter_onject
202+
search = SearchUtils._process_custom_metadata_condition(
203+
search, condition, "where"
204+
)
205+
190206
# Apply date range filters
191207
if date_range:
192208
logger.debug(f"Applying date range filters: {date_range}")

modelcontextprotocol/utils/search.py

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
from typing import Dict, Any
21
import logging
2+
from typing import Dict, Any
33
from pyatlan.model.assets import Asset
4+
from pyatlan.model.fields.atlan_fields import CustomMetadataField
5+
from pyatlan.model.fluent_search import FluentSearch
46

57
logger = logging.getLogger(__name__)
68

@@ -170,3 +172,73 @@ def _process_condition(
170172
)
171173
search = search_method(attr.eq(condition))
172174
return search
175+
176+
@staticmethod
177+
def _process_custom_metadata_condition(
178+
search: FluentSearch, condition: Dict[str, Any], search_method_name: str
179+
):
180+
"""
181+
Process a single custom metadata condition and apply it to the search using the specified method.
182+
183+
Args:
184+
search: The FluentSearch object
185+
condition: Dictionary containing display_name, property_name, property_value, and optional operator
186+
search_method_name: The search method to use ('where', 'where_not', 'where_some')
187+
188+
Returns:
189+
FluentSearch: The updated search object
190+
"""
191+
if not isinstance(condition, dict):
192+
logger.warning("Custom metadata condition must be a dictionary")
193+
return search
194+
195+
# Validate required fields
196+
required_fields = ["display_name", "property_filters"]
197+
if not all(field in condition for field in required_fields):
198+
logger.warning(
199+
f"Custom metadata condition missing required fields: {required_fields}"
200+
)
201+
return search
202+
203+
search_method = getattr(search, search_method_name)
204+
205+
# Get operator, default to "eq"
206+
for property_filter in condition["property_filters"]:
207+
operator = property_filter.get("operator", "eq")
208+
property_name = property_filter.get("property_name")
209+
property_value = property_filter.get("property_value")
210+
211+
try:
212+
# Create the custom metadata field
213+
custom_metadata_field = CustomMetadataField(
214+
set_name=condition["display_name"], attribute_name=property_name
215+
)
216+
217+
# Apply the appropriate operator
218+
if property_value == "any":
219+
# For "any" value, use has_any_value() method
220+
query_condition = custom_metadata_field.has_any_value()
221+
else:
222+
# Get the operator method dynamically
223+
if hasattr(custom_metadata_field, operator):
224+
operator_method = getattr(custom_metadata_field, operator)
225+
query_condition = operator_method(property_value)
226+
else:
227+
# Fallback to eq if operator not found
228+
logger.warning(
229+
f"Operator '{operator}' not found, falling back to 'eq'"
230+
)
231+
query_condition = custom_metadata_field.eq(property_value)
232+
233+
# Apply the condition to the search
234+
search = search_method(query_condition)
235+
logger.info(search)
236+
logger.debug(
237+
f"Applied custom metadata condition: {condition['display_name']}.{condition['property_name']} {operator} {condition['property_value']}"
238+
)
239+
240+
except Exception as e:
241+
logger.error(f"Error processing custom metadata condition: {e}")
242+
logger.exception("Exception details:")
243+
244+
return search

0 commit comments

Comments
 (0)