Module: Riffer::Mcp::AuthenticatedTool

Defined in:
lib/riffer/mcp/authenticated_tool.rb

Overview

Wraps MCP-generated tool classes so tools/call uses Riffer.config.mcp.credentials per invocation while delegating metadata to the inner class.

Class Method Summary collapse

Class Method Details

.wrap_all(tool_classes, manifest, matched_tags) ⇒ Object

Returns one wrapper class per inner tool, sharing manifest and matched_tags.

– : (Array, Riffer::Mcp::Manifest, Array) -> Array



12
13
14
# File 'lib/riffer/mcp/authenticated_tool.rb', line 12

def self.wrap_all(tool_classes, manifest, matched_tags)
  tool_classes.map { |tc| wrap_one(tc, manifest, matched_tags) }
end

.wrap_one(inner_class, manifest, matched_tags) ⇒ Object

– : (singleton(Riffer::Tool), Riffer::Mcp::Manifest, Array) -> singleton(Riffer::Tool) Class.new(Riffer::Tool) is typed as ::Class by steep — it cannot verify the subtype relationship for dynamically created anonymous classes, so the ignore is required.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/riffer/mcp/authenticated_tool.rb', line 20

def self.wrap_one(inner_class, manifest, matched_tags) # steep:ignore MethodBodyTypeMismatch
  inner = inner_class
  man = manifest
  tags = matched_tags

  Class.new(Riffer::Tool) do
    @identifier = inner.identifier

    define_singleton_method(:name) { inner.name }
    define_singleton_method(:mcp_server_tool_name) { inner.mcp_server_tool_name }
    define_singleton_method(:description) { inner.description }
    define_singleton_method(:parameters_schema) { |strict: false| inner.parameters_schema(strict: strict) }

    # Builds a client for a single +tools/call+ invocation.
    #
    # Creates a fresh client per call so headers from the credentials proc stay
    # current.
    # TODO: A per-headers cache would reduce connection churn under load, and
    # requires a follow-up investigation to determine how to invalidate failing
    # clients.
    define_method(:build_call_client) do |endpoint, headers|
      Riffer::Mcp::Client.new(endpoint: endpoint, headers: headers)
    end
    private :build_call_client

    define_method(:call) do |context:, **kwargs|
      cred = Riffer.config.mcp.credentials
      unless cred
        # `next` rather than `return`: inside define_method the block IS the method
        # body, so both exit :call identically at runtime. `next` avoids a false
        # steep ReturnTypeMismatch that would otherwise need a steep:ignore.
        next inner.new.call(context: context, **kwargs)
      end

      headers = cred.call(manifest: man, matched_tags: tags, context: context)
      if headers.nil?
        raise Riffer::Mcp::CredentialsDeniedError,
          "MCP credentials returned nil for server '#{man.name}' during tools/call"
      end

      client = build_call_client(man.endpoint, headers)
      text(client.tools_call(inner.mcp_server_tool_name, kwargs))
    end
  end
end