13. Invent your own predicate vocabulary
@requires (RBAC) and @feature (account flags) aren't enough. You want to gate on plan tier, beta enrollment, A/B bucket โ whatever your domain needs.
Problem. @requires (RBAC) and @feature (account flags) aren't enough. You want to gate on plan tier, beta enrollment, A/B bucket โ whatever your domain needs.
Solution. Any @name(value) that isn't a known constraint tag is a generic predicate. At compile time the gem calls server_context.{name}?(value). Define the predicates on your context; use the tags freely.
# Your server context โ one method per predicate you want to use:
class ServerContext
def requires?(flag) = current_user.can?(flag.to_sym)
def feature?(flag) = account.feature_enabled?(flag.to_s)
def tier?(name) = account.plan_tier?(name.to_s)
def beta?(flag) = account.beta_enrolled?(flag.to_s)
end# Use them on fields...
#: (
#: ?status: "active" | "inactive" | "unlisted" @feature(:opening_status_v2),
#: ?bulk_limit: Integer @tier(:enterprise),
#: ?experimental_ranking: bool @beta(:ranking_v2)
#: ) -> Hash[Symbol, untyped]
# ...and on whole tools, via gate:
class ExportEverythingTool < McpAuthorization::Tool
gate :tier, :enterprise
gate :beta, :bulk_export
endResult. Multiple predicates on one field AND together โ all must pass for the field to appear. If the context doesn't respond to a predicate method, it's skipped (permissive at the field level; fail-open at the tool level with a dev warning). One pipeline, infinite vocabulary.
Collected from COOKBOOK.md in the repository. Edit it there, not here.