Module: Textus::Surfaces::MCP::Catalog
- Defined in:
- lib/textus/surfaces/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.
Constant Summary collapse
- WRITE_VERBS =
%i[ put propose key_delete key_mv accept reject enqueue ].freeze
- MAINTENANCE_VERBS =
%i[ data_mv key_mv_prefix key_delete_prefix drain rule_lint ].freeze
Class Method Summary collapse
-
.call(name, session:, store:, args:) ⇒ Object
rubocop:disable Metrics/AbcSize.
- .mcp_surfaced?(klass) ⇒ Boolean
- .names ⇒ Object
-
.read_verbs ⇒ Object
MCP-surfaced read verbs, by Dispatcher class namespace — the agent’s real read/discovery surface.
-
.specs ⇒ Object
Contracts of every MCP-surfaced verb, in Dispatcher order.
- .tool_schemas ⇒ Object
-
.write_verbs ⇒ Object
MCP-surfaced write verbs, by Dispatcher class namespace — the mirror of read_verbs for the write side.
Class Method Details
.call(name, session:, store:, args:) ⇒ Object
rubocop:disable Metrics/AbcSize
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/textus/surfaces/mcp/catalog.rb', line 64 def call(name, session:, store:, args:) # rubocop:disable Metrics/AbcSize klass = Textus::Action::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) invoke = lambda do |effective_inputs| pos, kwargs = Textus::Contract::Binder.bind(spec, effective_inputs, session: session) spec.args.select(&:positional).zip(pos).each { |a, v| kwargs[a.name] = v unless kwargs.key?(a.name) } cmd_class = Textus::Gate::VERB_COMMAND.fetch(spec.verb) do raise Textus::MCP::ToolError.new("unknown verb: #{spec.verb}") end merged = kwargs.merge(role: session.role) filled = cmd_class.members.to_h { |m| [m, merged.key?(m) ? merged[m] : nil] } cmd = cmd_class.new(**filled) store.gate.dispatch(cmd) end result = if spec.around Textus::Contract::Around.with(spec.around, scope: store.as(session.role), inputs: inputs, session: session, &invoke) else invoke.call(inputs) end 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.}") end |
.mcp_surfaced?(klass) ⇒ Boolean
60 61 62 |
# File 'lib/textus/surfaces/mcp/catalog.rb', line 60 def mcp_surfaced?(klass) klass.respond_to?(:contract?) && klass.contract? && klass.contract.mcp? end |
.names ⇒ Object
33 34 35 |
# File 'lib/textus/surfaces/mcp/catalog.rb', line 33 def names specs.map { |s| s.verb.to_s } end |
.read_verbs ⇒ Object
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 verbs by verb identity (routing may be legacy UseCases or Dispatch::Actions).
42 43 44 45 46 47 |
# File 'lib/textus/surfaces/mcp/catalog.rb', line 42 def read_verbs Textus::Action::VERBS .reject { |verb, _klass| WRITE_VERBS.include?(verb) || MAINTENANCE_VERBS.include?(verb) } .select { |_verb, klass| mcp_surfaced?(klass) } .keys.map(&:to_s) end |
.specs ⇒ Object
Contracts of every MCP-surfaced verb, in Dispatcher order.
21 22 23 24 25 |
# File 'lib/textus/surfaces/mcp/catalog.rb', line 21 def specs Textus::Action::VERBS.values .select { |k| mcp_surfaced?(k) } .map(&:contract) end |
.tool_schemas ⇒ Object
27 28 29 30 31 |
# File 'lib/textus/surfaces/mcp/catalog.rb', line 27 def tool_schemas specs.map do |s| { name: s.verb.to_s, description: s.summary, inputSchema: s.input_schema } end.freeze end |
.write_verbs ⇒ Object
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).
54 55 56 57 58 |
# File 'lib/textus/surfaces/mcp/catalog.rb', line 54 def write_verbs Textus::Action::VERBS .select { |verb, klass| WRITE_VERBS.include?(verb) && mcp_surfaced?(klass) } .keys.map(&:to_s) end |