16. Group a large domain into facades
A domain has grown to a hundred-plus tools. tools/list is dominated by call-time argument schemas for tools the model will never pick, and choosing from a fl...
Problem. A domain has grown to a hundred-plus tools. tools/list is dominated by call-time argument schemas for tools the model will never pick, and choosing from a flat list that long is genuinely hard.
Solution. Give each tool a category, then opt the domain into grouping. One facade per group replaces the flat list. Opt-in per domain โ a domain with no facet_domain is untouched, and category is inert there.
class ListOrdersTool < McpAuthorization::Tool
tags "admin"
category :orders # the group this tool belongs to
dynamic_contract OrderHandlers::List
end# config/initializers/mcp_authorization.rb
config.facet_domain :admin, group_by: :category
config.categories do
summary :orders, "Create, inspect, and update orders and their line items."
summary :billing, "Invoices, payments, refunds, and billing profiles."
endCalling a facade names the inner tool and its arguments:
{ "name": "orders_tools",
"arguments": { "tool_name": "update_order", "arguments": { "id": "o_1" } } }Result. tools/list returns orders_tools and billing_tools instead of a hundred entries. Each facade's description is routing signal only: the group summary, then one line per tool this caller may invoke โ so a description that varies by role varies here too. Per-tool argument schemas ride on the facade's _meta (default :vendor_extension) or are omitted entirely (:lazy).
Dispatch goes through the real call path: the name must be one advertised to this caller, the tool is re-resolved through permitted?, and the target's own filter_input/filter_output run โ so grouping changes the shape of the listing, never the authorization. Groups where the caller may invoke nothing are hidden entirely, so a facade never advertises an empty enum.
Tools with no
categoryland in anuncategorizedgroup. Passuncategorized: :errorwhen you'd rather CI catch the omission. Full option table and error list in the README.
Collected from COOKBOOK.md in the repository. Edit it there, not here.