Class: McpAuthorization::FacadeBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/mcp_authorization/facade_builder.rb

Overview

Builds grouped facade tools for a faceted domain.

A facade is a per-request synthetic MCP::Tool subclass — one per non-empty category — whose description carries routing signal only (group summary + RBAC-filtered tool one-liners) and whose inputSchema defers per-tool argument schemas out of the selection prompt. A tools/call on a facade names the inner tool (+tool_name+) and its arguments; dispatch resolves the real tool through ToolRegistry.tool_class_for — re-running permitted? — and delegates to its materialized call, so authorization, gating, and input/output filtering apply exactly as if the tool had been called directly.

Facades are never registered in ToolRegistry; they are produced on demand by facades_for. See docs/designs/tool-grouping-facades.md.

Defined Under Namespace

Classes: FacadeNameCollisionError, UncategorizedToolError

Constant Summary collapse

FALLBACK_CATEGORY =

Fallback group for uncategorized tools (default mode).

:uncategorized

Class Method Summary collapse

Class Method Details

.facade_for(domain:, name:, server_context:) ⇒ Object

A single facade by its tool name (e.g. "orders_tools"), or nil when the name matches no non-empty group for this caller. Used by tools/call routing so one call doesn't build every facade.

Built with for_dispatch: true — a tools/call only needs the advertised name set to route through, never the _meta per-tool schemas (those are a tools/list concern). Skipping them avoids recompiling every tool's input schema in the group on each call, preserving the "compile only the invoked tool" cost that facades' large target domains most depend on.

Signature:

  • (domain: String, name: String, server_context: untyped) -> singleton(MCP::Tool)?



56
57
58
59
60
61
62
63
64
65
66
# File 'lib/mcp_authorization/facade_builder.rb', line 56

def facade_for(domain:, name:, server_context:)
  config = McpAuthorization.config.facet_config(domain)
  return nil unless config

  group_tools(domain, server_context, config).each do |category, tools|
    if facade_name(category, config) == name
      return build_facade(domain, category, tools, server_context, config, for_dispatch: true)
    end
  end
  nil
end

.facade_name(category, config) ⇒ Object

The MCP tool name a category's facade is exposed under. The suffix is per-domain (facet config facade_suffix, default "tools"): category :ordersorders_tools (or orders_hire, etc.).

Signature:

  • (Symbol, Hash[Symbol, untyped]) -> String



72
73
74
75
# File 'lib/mcp_authorization/facade_builder.rb', line 72

def facade_name(category, config)
  suffix = config[:facade_suffix] || Configuration::DEFAULT_FACADE_SUFFIX
  "#{category}_#{suffix}"
end

.facades_for(domain:, server_context:) ⇒ Object

All facades for a domain, one per non-empty group the caller has at least one permitted tool in. Empty groups produce no facade — which also guarantees no facade ever advertises an empty enum.

Signature:

  • (domain: String, server_context: untyped) -> Array[singleton(MCP::Tool)]



36
37
38
39
40
41
42
43
# File 'lib/mcp_authorization/facade_builder.rb', line 36

def facades_for(domain:, server_context:)
  config = McpAuthorization.config.facet_config(domain)
  return [] unless config

  group_tools(domain, server_context, config).map do |category, tools|
    build_facade(domain, category, tools, server_context, config)
  end
end