Enhanced Tag Matching Migration Guide¶
Upgrading from exact tag matching to smart
+
/-
operators
This guide helps you migrate existing MCP Mesh applications to take advantage of enhanced tag matching with preference and exclusion operators introduced in v0.4+.
What Changed¶
Before v0.4 - Exact Matching Only¶
@mesh.tool(
dependencies=[{
"capability": "llm_service",
"tags": ["claude", "opus"] # ALL tags must match exactly
}]
)
Problem: Rigid matching - either exact match or total failure. No fallback options.
After v0.4 - Smart Matching¶
@mesh.tool(
dependencies=[{
"capability": "llm_service",
"tags": [
"claude", # Required (same as before)
"+opus", # Preferred (bonus if present)
"-experimental" # Excluded (must NOT be present)
]
}]
)
Benefits: Smart provider selection, graceful degradation, cost control, safety exclusions.
Backward Compatibility¶
✅ 100% Backward Compatible: All existing tag configurations continue to work unchanged.
- Tags without prefixes remain "required" tags
- Existing dependency resolution behavior preserved
- No breaking changes to APIs or data structures
- Zero migration required for basic use cases
Migration Strategies¶
Strategy 1: Gradual Enhancement (Recommended)¶
Start with your existing exact tags and gradually add preferences/exclusions:
# Phase 1: Existing configuration (works unchanged)
"tags": ["claude", "production"]
# Phase 2: Add preferences for better provider selection
"tags": ["claude", "production", "+opus"]
# Phase 3: Add safety exclusions
"tags": ["claude", "production", "+opus", "-experimental", "-beta"]
Strategy 2: Full Smart Matching¶
Transform exact requirements into intelligent preferences:
Before (Rigid)¶
@mesh.tool(
capability="chat_service",
dependencies=[{
"capability": "llm_service",
"tags": ["claude", "opus"] # Fails if no opus available
}]
)
def premium_chat(llm_service: mesh.McpMeshAgent = None):
return llm_service() if llm_service else "Service unavailable"
After (Smart)¶
@mesh.tool(
capability="chat_service",
dependencies=[{
"capability": "llm_service",
"tags": [
"claude", # Still require Claude
"+opus", # Prefer opus quality
"+sonnet", # Fallback to sonnet
"-experimental" # Never use experimental
]
}]
)
def adaptive_chat(llm_service: mesh.McpMeshAgent = None):
"""
Smart chat that adapts to available services:
- Prefers opus when available
- Falls back to sonnet if opus unavailable
- Never uses experimental/unstable services
"""
return llm_service() if llm_service else "No suitable service available"
Common Migration Patterns¶
Pattern 1: Cost Optimization¶
Transform expensive exact requirements into cost-conscious preferences:
# Before: Always uses expensive premium service
@mesh.tool(
dependencies=[{"capability": "llm_service", "tags": ["claude", "opus", "premium"]}]
)
# After: Prefers quality but allows cost-effective alternatives
@mesh.tool(
dependencies=[{
"capability": "llm_service",
"tags": [
"claude", # Required model family
"+opus", # Prefer best quality
"+sonnet", # Accept good quality
"-premium" # Exclude expensive tiers when cost matters
]
}]
)
Pattern 2: Environment Safety¶
Add safety exclusions to prevent production issues:
# Before: Could accidentally get experimental services
@mesh.tool(
dependencies=[{"capability": "database_service", "tags": ["postgres"]}]
)
# After: Explicit safety guardrails
@mesh.tool(
dependencies=[{
"capability": "database_service",
"tags": [
"postgres", # Required database type
"+primary", # Prefer primary instance
"+ssd", # Prefer SSD performance
"-experimental", # Safety: no experimental features
"-beta", # Safety: no beta versions
"-replica" # Safety: no read-only replicas for writes
]
}]
)
Pattern 3: Multi-Region Preferences¶
Transform region-specific exact matching into intelligent preferences:
# Before: Hard-coded to specific region
@mesh.tool(
dependencies=[{
"capability": "storage_service",
"tags": ["aws", "us-east-1", "primary"]
}]
)
# After: Regional preferences with fallbacks
@mesh.tool(
dependencies=[{
"capability": "storage_service",
"tags": [
"aws", # Required cloud provider
"+us-east-1", # Prefer primary region
"+us-west-2", # Acceptable fallback region
"+primary", # Prefer primary storage
"-experimental" # No experimental storage
]
}]
)
Advanced Migration Examples¶
Multi-Service Architecture Migration¶
Before: Brittle Exact Matching¶
@mesh.tool(
capability="data_pipeline",
dependencies=[
{"capability": "database", "tags": ["postgres", "v15", "primary"]},
{"capability": "cache", "tags": ["redis", "cluster", "v7"]},
{"capability": "queue", "tags": ["rabbitmq", "ha", "v3.10"]}
]
)
def rigid_pipeline():
"""Brittle pipeline that fails if any exact version unavailable."""
pass
After: Resilient Smart Matching¶
@mesh.tool(
capability="data_pipeline",
dependencies=[
{
"capability": "database",
"tags": [
"postgres", # Required database
"+primary", # Prefer primary instance
"+v15", # Prefer latest version
"+ssd", # Prefer SSD performance
"-experimental", # No experimental versions
"-replica" # No read-only for this pipeline
]
},
{
"capability": "cache",
"tags": [
"redis", # Required cache type
"+cluster", # Prefer clustered setup
"+v7", # Prefer latest Redis 7.x
"+memory-optimized", # Prefer memory optimization
"-single-node" # Avoid single points of failure
]
},
{
"capability": "queue",
"tags": [
"rabbitmq", # Required message queue
"+ha", # Prefer high availability
"+v3.10", # Prefer stable version
"-beta" # No beta versions in production
]
}
]
)
def resilient_pipeline():
"""
Resilient pipeline that:
- Prefers optimal configurations
- Gracefully degrades when needed
- Maintains safety guardrails
"""
pass
Testing Your Migration¶
1. Validate Enhanced Matching Works¶
# Start multiple providers with different tags
python provider_haiku.py # Tags: ["claude", "haiku", "fast"]
python provider_sonnet.py # Tags: ["claude", "sonnet", "balanced"]
python provider_opus.py # Tags: ["claude", "opus", "premium"]
# Test consumer with preferences
python consumer.py # Tags: ["claude", "+opus", "-experimental"]
# Should select opus provider (preferred)
# Stop opus provider
pkill -f provider_opus.py
# Test fallback behavior
python consumer.py # Should fallback to sonnet provider
2. Verify Exclusion Works¶
# Start experimental provider
python experimental.py # Tags: ["claude", "experimental"]
# Test exclusion
python consumer.py # Tags: ["claude", "-experimental"]
# Should find NO providers (experimental excluded)
3. Monitor Provider Selection¶
# Check which providers are selected
curl -s http://localhost:8000/agents | \
jq '.agents[] | select(.dependencies_resolved > 0) |
{name: .name, resolved_dependencies: .resolved_dependencies}'
Performance Considerations¶
Scoring Overhead¶
Enhanced matching adds minimal overhead:
- Simple string prefix checking: O(n) with number of tags
- Priority scoring: O(n) calculation per provider
- Provider ranking: O(n log n) sorting of candidates
Network Efficiency¶
Smart matching can improve network efficiency:
- Better provider selection reduces retry attempts
- Regional preferences minimize latency
- Cost exclusions prevent expensive provider usage
Troubleshooting Migration¶
Issue: No Providers Match¶
# Check what providers are available
curl -s http://localhost:8000/agents | jq '.agents[].capabilities'
# Verify your tag requirements aren't too restrictive
# Try removing some exclusions or changing requirements to preferences
Issue: Unexpected Provider Selected¶
# Debug provider scoring
# Add logging to see scoring decisions:
export MCP_MESH_LOG_LEVEL=DEBUG
python your_consumer.py
# Look for log messages like:
# "Provider score: agent_id=claude-opus score=15"
# "Selected provider: claude-opus (highest score)"
Issue: Legacy Behavior Changed¶
If you need exactly the old behavior:
Best Practices After Migration¶
1. Use Descriptive Tag Hierarchies¶
# Good: Clear hierarchy and purpose
"tags": [
"llm", # Service type
"claude", # Provider
"+opus", # Preferred model
"+us-east-1", # Preferred region
"-experimental", # Safety exclusion
"-expensive" # Cost control
]
# Avoid: Cryptic or overly generic tags
"tags": ["svc", "v1", "+opt", "-bad"]
2. Balance Preferences vs Requirements¶
# Good: Some requirements, some preferences
"tags": [
"postgres", # Required: specific database
"+primary", # Preferred: better performance
"-experimental" # Safety: exclude risky versions
]
# Avoid: All preferences (too loose) or all requirements (too rigid)
"tags": ["+postgres", "+primary", "+ssd"] # Too loose
"tags": ["postgres", "v15.2", "us-east-1a", "i3.xlarge"] # Too rigid
3. Document Your Tag Strategy¶
@mesh.tool(
capability="financial_processor",
dependencies=[{
"capability": "database_service",
"tags": [
# Required for compliance
"postgres", # SOX compliance requirement
"encrypted", # PCI DSS requirement
# Performance preferences
"+primary", # Prefer primary for consistency
"+ssd", # Prefer SSD for speed
# Safety exclusions
"-experimental", # Never use experimental in finance
"-replica" # Never use read-only for transactions
]
}],
description="Financial processor with strict compliance and performance requirements"
)
Summary¶
Enhanced tag matching provides powerful capabilities while maintaining full backward compatibility:
✅ Zero breaking changes - existing code works unchanged ✅ Gradual migration - enhance at your own pace ✅ Smart fallbacks - graceful degradation when preferred services unavailable ✅ Cost control - exclude expensive services with -premium
✅ Safety guardrails - exclude experimental/beta with -experimental
, -beta
✅ Regional preferences - prefer local services with +us-east-1
Start small with simple preferences, then gradually add more sophisticated matching logic as you see the benefits in your specific use cases.