Class: Assistant::AdlabsToolBuilder

Inherits:
Object
  • Object
show all
Defined in:
app/services/assistant/adlabs_tool_builder.rb

Overview

Builds RubyLLM tools for the AdLabs (Amazon Ads / PPC) MCP server so the
assistant (Sunny) can drive AdLabs's stateful, reference-based workflow.

Unlike the curated one-shot builders (e.g. AhrefsToolBuilder), AdLabs is a
full conversational MCP surface, so tools are discovered dynamically and
exposed with their upstream names and JSON schemas passed straight through to
the LLM. Keeping the upstream names is deliberate: AdLabs ships its own
operating instructions (read at runtime via
+read_resource(uri: 'adlabs://instructions')+) and its tool descriptions
cross-reference each other by name, so renaming would break that coherence.

A single Marketing::AdlabsMcpClient instance backs every tool in one build
so the +chat_session_id+ and +mcp://data/...+ references resolve within a
turn. The catalog (tool names + schemas) is cached app-wide; only the
execution client is per-build.

Read vs. write is role-gated. The mutating tools — which change bids,
targets, tags, and jobs on the live Amazon Ads account — are built only for
accounts with the +amazon_ad_specialist+ role (or admins). Everyone else who
has the +adlabs+ service gets the read-only subset. The mixed-action tools
(+optimizer+, +tags+, +logs+) are held whole to the specialist role because a
single +*_action+ argument flips them between reading and writing.

Constant Summary collapse

WRITE_TOOLS =

Tools excluded from the read-only set (they can mutate the live ad account
or revert applied jobs). See class docs for why the mixed-action tools are
included here rather than action-gated.

%w[create_entities update_entities optimizer tags logs].freeze

Class Method Summary collapse

Class Method Details

.tools(account: nil, write: nil) ⇒ Array<RubyLLM::Tool>

Returns instantiated tools (empty on any failure).

Parameters:

  • account (Account, nil) (defaults to: nil)

    CRM account; gates the write tools

  • write (Boolean, nil) (defaults to: nil)

    explicit override (tests); nil derives it
    from +account+

Returns:

  • (Array<RubyLLM::Tool>)

    instantiated tools (empty on any failure)



37
38
39
40
41
42
43
44
45
46
47
48
# File 'app/services/assistant/adlabs_tool_builder.rb', line 37

def tools(account: nil, write: nil)
  write = write_access?() if write.nil?
  catalog = Marketing::AdlabsMcpClient.catalog
  return [] if catalog.blank?

  client = Marketing::AdlabsMcpClient.new
  catalog = catalog.reject { |t| WRITE_TOOLS.include?(t['name']) } unless write
  catalog.map { |t| build_tool(t, client) }
rescue StandardError => e
  Rails.logger.warn("[AdlabsToolBuilder] Failed to build AdLabs tools: #{e.message}")
  []
end

.write_access?(account) ⇒ Boolean

True when +account+ may use the mutating AdLabs tools.

Parameters:

Returns:

  • (Boolean)


53
54
55
56
57
# File 'app/services/assistant/adlabs_tool_builder.rb', line 53

def write_access?()
  return false unless 

  .is_admin? || .has_role?('amazon_ad_specialist', admin_check: false)
end