Module: Textus::MCP::Catalog

Defined in:
lib/textus/mcp/catalog.rb

Overview

Derives the entire MCP tool surface from the per-verb contracts (ADR 0039). ‘tool_schemas` feeds tools/list; `call` is the generic tools/call dispatch: map JSON args -> (positional, keyword) per the contract, invoke the verb through the role scope, then shape the return value with the contract’s default view. No per-tool code.

Class Method Summary collapse

Class Method Details

.call(name, session:, store:, args:) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/textus/mcp/catalog.rb', line 53

def call(name, session:, store:, args:)
  klass = Textus::Dispatcher::VERBS[name.to_sym]
  raise ToolError.new("unknown tool: #{name}") unless klass && mcp_surfaced?(klass)

  spec = klass.contract
  inputs = Textus::Contract::Binder.inputs_from_wire(spec, args)
  result = store.as(session.role).dispatch_bound(spec.verb, inputs, session: session)
  Textus::Contract::View.render(spec, :default, result, inputs)
rescue Textus::Contract::MissingArgs => e
  raise ToolError.new("#{spec.verb}: missing #{e.missing.map { |a| a.wire.to_s }.join(", ")}")
rescue ContractDrift, CursorExpired
  raise
rescue Textus::Error => e
  raise ToolError.new("#{name}: #{e.message}")
end

.mcp_surfaced?(klass) ⇒ Boolean

Returns:

  • (Boolean)


49
50
51
# File 'lib/textus/mcp/catalog.rb', line 49

def mcp_surfaced?(klass)
  klass.respond_to?(:contract?) && klass.contract? && klass.contract.mcp?
end

.namesObject



24
25
26
# File 'lib/textus/mcp/catalog.rb', line 24

def names
  specs.map { |s| s.verb.to_s }
end

.read_verbsObject

MCP-surfaced read verbs, by Dispatcher class namespace — the agent’s real read/discovery surface. ‘boot.agent_quickstart.read_verbs` derives from this so it can never advertise a verb the agent cannot call, nor omit one it can (ADR 0056). Excludes Write/Maintenance.



32
33
34
35
36
# File 'lib/textus/mcp/catalog.rb', line 32

def read_verbs
  Textus::Dispatcher::VERBS
    .select { |_verb, klass| mcp_surfaced?(klass) && klass.name.start_with?("Textus::Read::") }
    .keys.map(&:to_s)
end

.specsObject

Contracts of every MCP-surfaced verb, in Dispatcher order.



12
13
14
15
16
# File 'lib/textus/mcp/catalog.rb', line 12

def specs
  Textus::Dispatcher::VERBS.values
                           .select { |k| mcp_surfaced?(k) }
                           .map(&:contract)
end

.tool_schemasObject



18
19
20
21
22
# File 'lib/textus/mcp/catalog.rb', line 18

def tool_schemas
  specs.map do |s|
    { name: s.verb.to_s, description: s.summary, inputSchema: s.input_schema }
  end.freeze
end

.write_verbsObject

MCP-surfaced write verbs, by Dispatcher class namespace — the mirror of read_verbs for the write side. ‘boot.agent_quickstart.write_verbs` derives from this so it advertises bare verb names the agent can call (no `–as`/ `–stdin` CLI framing), finishing the de-CLI-ing of the agent surface (ADR 0056, ADR 0057).



43
44
45
46
47
# File 'lib/textus/mcp/catalog.rb', line 43

def write_verbs
  Textus::Dispatcher::VERBS
    .select { |_verb, klass| mcp_surfaced?(klass) && klass.name.start_with?("Textus::Write::") }
    .keys.map(&:to_s)
end