def register_catalog_tools(mcp):
"""Register catalog management tools with the MCP server."""
# Initialize client (will be reused across tool calls)
client = TurbifyStoreAPIClient()
@mcp.tool()
def get_catalog_items(item_ids: List[str]) -> str:
"""
Get details for multiple catalog items.
Args:
item_ids: List of item IDs to retrieve
Returns:
JSON string with item details
"""
try:
response = client.get_item_details(item_ids)
# Convert items to dictionaries if they're not already
items = response.items if response.items else []
serializable_items = []
for item in items:
if item and not isinstance(item, dict):
# If it's a Pydantic model, convert to dict
if hasattr(item, 'dict'):
serializable_items.append(item.dict())
# If it's a dataclass, convert to dict
elif hasattr(item, '__dict__'):
serializable_items.append(item.__dict__)
# Otherwise, try to serialize as is
else:
serializable_items.append(dict(item))
else:
serializable_items.append(item)
return json.dumps({
"status": response.status,
"success": response.is_success,
"messages": response.success_messages,
"errors": response.error_messages,
"item_count": len(serializable_items),
"items": serializable_items
}, indent=2)
except APIError as e:
return json.dumps({
"status": "error",
"success": False,
"errors": [str(e)],
"item_ids": item_ids
}, indent=2)
@mcp.tool()
def search_catalog_items(
keyword: str,
start_index: int = 1,
end_index: int = 100
) -> str:
"""
Search for items in the Turbify Store catalog.
Note: This simple search only matches the keyword against item ID, name, or code fields.
For more advanced search capabilities against other fields, use advanced_search_catalog_items.
Args:
keyword: Search keyword (matches against ID, name, or code fields only)
start_index: Starting index for pagination (default: 1)
end_index: Ending index for pagination (default: 100, max: 1000)
Returns:
JSON string with search results
"""
if end_index > 1000:
end_index = 1000
try:
response = client.simple_search(keyword, start_index, end_index)
# Extract item IDs from the response
item_ids = response.item_ids if response.item_ids else []
return json.dumps({
"status": response.status,
"success": response.is_success,
"messages": response.success_messages,
"errors": response.error_messages,
"keyword": keyword,
"start_index": start_index,
"end_index": end_index,
"total_items": len(item_ids),
"item_ids": item_ids
}, indent=2)
except APIError as e:
return json.dumps({
"status": "error",
"success": False,
"errors": [str(e)],
"keyword": keyword
}, indent=2)
@mcp.tool()
def advanced_search_catalog_items(
criteria_list: List[dict],
match_type: str = "all",
table_id: Optional[str] = None,
start_index: int = 1,
end_index: int = 100
) -> str:
"""
Advanced search for items in the Turbify Store catalog with multiple criteria.
Args:
criteria_list: List of criteria dictionaries, each with 'attribute', 'operator', and 'value'
Example: [{"attribute": "price", "operator": "gt", "value": "10.00"}]
match_type: How to match criteria - "all" (AND) or "any" (OR) (default: "all")
table_id: Optional table ID to search within
start_index: Starting index for pagination (default: 1)
end_index: Ending index for pagination (default: 100, max: 1000)
Returns:
JSON string with search results
"""
if end_index > 1000:
end_index = 1000
# Validate match_type
if match_type not in ["all", "any"]:
return json.dumps({
"status": "error",
"success": False,
"errors": ["match_type must be 'all' or 'any'"],
"criteria_list": criteria_list
}, indent=2)
# Validate criteria_list
if not criteria_list:
return json.dumps({
"status": "error",
"success": False,
"errors": ["At least one search criterion is required"],
"criteria_list": criteria_list
}, indent=2)
# Define attribute types and supported operators based on API documentation
string_attributes = {
"id", "name", "code", "options", "headline", "caption", "abstract", "label",
"product url", "manufacturer", "brand", "gender", "upc", "manufacturer-part-number",
"model-number", "isbn", "merchant-category", "color", "size", "age-range",
"promo-text", "style-number", "style", "ypath", "yahoo-shopping-category", "ean"
}
numeric_attributes = {
"price", "sale-price", "ship-weight", "msrp", "personalization-charge"
}
boolean_attributes = {
"orderable", "taxable", "gift-certificate", "need-bill", "need-payment",
"need-ship", "in-yshopping"
}
enumeration_attributes = {
"age-group", "availability", "classification", "condition", "gender", "medium"
}
all_valid_attributes = string_attributes | numeric_attributes | boolean_attributes | enumeration_attributes
valid_operators = {"lt", "gt", "cn", "eq"}
# Validate each criterion
for i, criteria in enumerate(criteria_list):
if not all(key in criteria for key in ["attribute", "operator", "value"]):
return json.dumps({
"status": "error",
"success": False,
"errors": [f"Criterion {i} must have 'attribute', 'operator', and 'value' fields"],
"criteria_list": criteria_list
}, indent=2)
attribute = criteria["attribute"]
operator = criteria["operator"].lower() # Operators are case-sensitive and must be lowercase
value = str(criteria["value"])
# Validate operator
if operator not in valid_operators:
return json.dumps({
"status": "error",
"success": False,
"errors": [f"Criterion {i}: Invalid operator '{operator}'. Valid operators are: {', '.join(valid_operators)}"],
"criteria_list": criteria_list
}, indent=2)
# Validate attribute
if attribute not in all_valid_attributes:
# Check if it might be a custom field (we'll allow it but warn in documentation)
pass # Custom fields are allowed
# Validate operator against attribute type
if attribute in boolean_attributes:
if operator != "eq":
return json.dumps({
"status": "error",
"success": False,
"errors": [f"Criterion {i}: Boolean attributes only support 'eq' operator"],
"criteria_list": criteria_list
}, indent=2)
if value not in ["0", "1"]:
return json.dumps({
"status": "error",
"success": False,
"errors": [f"Criterion {i}: Boolean attributes only accept values '0' or '1'"],
"criteria_list": criteria_list
}, indent=2)
elif attribute in numeric_attributes:
if operator in ["lt", "gt", "eq"]:
try:
float(value)
except ValueError:
return json.dumps({
"status": "error",
"success": False,
"errors": [f"Criterion {i}: Numeric attributes require numeric values"],
"criteria_list": criteria_list
}, indent=2)
elif attribute in enumeration_attributes:
if operator != "eq":
return json.dumps({
"status": "error",
"success": False,
"errors": [f"Criterion {i}: Enumeration attributes only support 'eq' operator"],
"criteria_list": criteria_list
}, indent=2)
try:
response = client.advanced_search(criteria_list, match_type, table_id, start_index, end_index)
# Extract item IDs from the response
item_ids = response.item_ids if response.item_ids else []
return json.dumps({
"status": response.status,
"success": response.is_success,
"messages": response.success_messages,
"errors": response.error_messages,
"criteria_list": criteria_list,
"match_type": match_type,
"table_id": table_id,
"start_index": start_index,
"end_index": end_index,
"total_items": len(item_ids),
"item_ids": item_ids
}, indent=2)
except APIError as e:
return json.dumps({
"status": "error",
"success": False,
"errors": [str(e)],
"criteria_list": criteria_list
}, indent=2)
@mcp.tool()
def create_items(items: List[CatalogItem]) -> str:
"""
Create items in Turbify Merchant Solutions catalog using the Catalog API.
HTML included in the request values must be contained within CDATA tags.
Args:
items: List of item dictionaries to create. Each item must have:
- id (str): Unique item ID
- name (str): Item name
- price (float): Item price
- orderable (bool): Whether item is orderable
- taxable (bool): Whether item is taxable
- table_id (str): Table ID where item belongs
- custom_data (list, optional): List of {"name": str, "value": str} custom attributes
Returns:
JSON string with creation results including success status and any error messages
"""
# Debugging: Check if items is None or contains None values
if items is None:
logger.error("create_items called with None items")
return json.dumps({
"status": "error",
"success": False,
"errors": ["items parameter is None"],
"items_processed": 0
}, indent=2)
# Check for None values in the list
none_items = [i for i in items if i is None]
if none_items:
logger.error(f"create_items called with None items in list: {none_items}")
return json.dumps({
"status": "error",
"success": False,
"errors": [f"items list contains {len(none_items)} None values"],
"items_processed": 0
}, indent=2)
try:
response = client.create_items(items)
return json.dumps({
"status": response.status,
"success": response.is_success,
"messages": response.success_messages,
"errors": response.error_messages,
"items_processed": len(items)
}, indent=2)
except APIError as e:
return json.dumps({
"status": "error",
"success": False,
"errors": [str(e)],
"items_processed": 0
}, indent=2)
@mcp.tool()
def update_items(items: List[CatalogItem]) -> str:
"""
Update items in Turbify Merchant Solutions catalog using the Catalog API. TableId and Id cannot be updated.
HTML included in the request values must be contained within CDATA tags.
Args:
items: List of item dictionaries to update. Each item must have:
- id (str): Unique item ID, this cannot be updated
Returns:
JSON string with creation results including success status and any error messages
"""
try:
response = client.update_items(items)
return json.dumps({
"status": response.status,
"success": response.is_success,
"messages": response.success_messages,
"errors": response.error_messages,
"items_processed": len(items)
}, indent=2)
except APIError as e:
return json.dumps({
"status": "error",
"success": False,
"errors": [str(e)],
"items_processed": 0
}, indent=2)
@mcp.tool()
def delete_items(item_ids: List[str]) -> str:
"""
Delete multiple items in a single operation.
Args:
item_ids: List of item IDs to delete
Returns:
JSON string with operation result
"""
try:
response = client.delete_items(item_ids)
return json.dumps({
"status": response.status,
"success": response.is_success,
"messages": response.success_messages,
"errors": response.error_messages,
"items_processed": len(item_ids)
}, indent=2)
except APIError as e:
return json.dumps({
"status": "error",
"success": False,
"errors": [str(e)],
"items_processed": 0
}, indent=2)