Skip to content

Tag Matching

Smart service selection with ± operators

Overview

When multiple providers offer the same capability, tags determine which one gets selected. MCP Mesh uses a scoring system with preference operators.

Tag Operators

Operator Prefix Meaning Effect
Required none Must be present Provider rejected if missing
Preferred + Bonus if present Higher score if present
Excluded - Must NOT be present Provider rejected if present

Examples

# Required: must have "claude"
# Preferred: bonus for "opus"
# Excluded: never use "experimental"
tags = ["claude", "+opus", "-experimental"]

Scoring Algorithm

Point Values

Tag Type Points
Required (present) 5 points
Preferred (present) 10 bonus points
Preferred (missing) 0 points
Excluded (present) Provider eliminated

Scoring Example

Available providers:

# Provider A: claude-haiku
tags = ["claude", "haiku", "fast"]

# Provider B: claude-sonnet
tags = ["claude", "sonnet", "balanced"]

# Provider C: claude-opus
tags = ["claude", "opus", "premium"]

# Provider D: claude-experimental
tags = ["claude", "experimental"]

Consumer request:

tags = ["claude", "+opus", "-experimental"]

Scoring:

Provider Calculation Score Result
claude-haiku claude(5) 5 Candidate
claude-sonnet claude(5) 5 Candidate
claude-opus claude(5) + opus(10) 15 SELECTED
claude-experimental ELIMINATED - Has "experimental"

Use Cases

Cost Optimization

# Prefer cheaper models, exclude premium
@mesh.tool(dependencies=[{
    "capability": "llm",
    "tags": ["claude", "+haiku", "-premium"]
}])

Quality Prioritization

# Prefer highest quality
@mesh.tool(dependencies=[{
    "capability": "llm",
    "tags": ["claude", "+opus", "+premium"]
}])

Production Safety

# Never use experimental or beta
@mesh.tool(dependencies=[{
    "capability": "database",
    "tags": ["postgres", "-experimental", "-beta", "-staging"]
}])

Multi-Preference

# Multiple preferences (additive scoring)
@mesh.tool(dependencies=[{
    "capability": "storage",
    "tags": [
        "s3",          # Required
        "+fast",       # Prefer fast
        "+ssd",        # Prefer SSD
        "+replicated", # Prefer replicated
        "-beta"        # Exclude beta
    ]
}])

Tie Breaking

The resolver's tiebreaker is HighestScoreThenVersion:

  1. Score — highest tag-match score wins
  2. Version — among equal scores, higher semver wins
  3. Agent ID — among equal scores and versions, the lowest agent ID wins

Exactly one candidate is selected, and the sort is stable, so an unchanged topology resolves the same way every time. See Tiebreaker for the canonical description.

Combining with Versions

Tags work with version constraints:

@mesh.tool(dependencies=[{
    "capability": "api",
    "tags": ["rest", "+v2", "-deprecated"],
    "version": ">=2.0.0,<3.0.0"
}])

Both tags AND version must match.

Namespace Interaction

Tags operate within namespaces:

@mesh.tool(dependencies=[{
    "capability": "database",
    "tags": ["+primary"],
    "namespace": "production"  # Only search production
}])

Best Practices

1. Use Descriptive Tags

# ✅ Good - clear categories
tags = ["llm", "claude", "sonnet", "balanced", "production"]

# ❌ Bad - vague
tags = ["service", "v1"]

2. Required + Preferred

# ✅ Good - required base, preferred optimization
tags = ["claude", "+opus", "+fast"]

# ❌ Bad - all required (too strict)
tags = ["claude", "opus", "fast"]  # May find nothing

3. Safety Exclusions

# ✅ Always exclude unsafe options in production
tags = ["database", "-experimental", "-beta", "-test"]

4. Document Tag Conventions

# Project tag conventions:
# - llm providers: claude, openai, local
# - quality tiers: haiku, sonnet, opus
# - environments: production, staging, development
# - stability: stable, beta, experimental

Debugging

Check Available Tags

curl http://localhost:8000/agents | jq '.agents[].capabilities | to_entries[] | {capability: .key, tags: .value.tags}'

Test Matching

# Use meshctl to test capability resolution
meshctl call my_tool  # See which provider was selected

See Also