riffer 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ # Per-server state managed by Riffer::Mcp::Registry.
5
+ #
6
+ # Created when a server is registered. Discovers tools via the MCP
7
+ # +tools/list+ call, then generates tool classes.
8
+ #
9
+ class Riffer::Mcp::Registration
10
+ # The manifest that describes this server.
11
+ attr_reader :manifest #: Riffer::Mcp::Manifest
12
+
13
+ # Generated Riffer::Tool subclasses.
14
+ #
15
+ #--
16
+ #: () -> Array[singleton(Riffer::Tool)]
17
+ def tools
18
+ @mutex.synchronize { @tools }
19
+ end
20
+
21
+ #--
22
+ #: (Riffer::Mcp::Manifest) -> void
23
+ def initialize(manifest)
24
+ @manifest = manifest
25
+ @cancelled = false
26
+ @tools = []
27
+ @mutex = Mutex.new
28
+ run_discovery
29
+ end
30
+
31
+ # Retires this registration, preventing in-flight discovery from publishing
32
+ # state.
33
+ #
34
+ #--
35
+ #: () -> void
36
+ def retire!
37
+ @mutex.synchronize { @cancelled = true }
38
+ end
39
+
40
+ # Returns true if this registration has been retired.
41
+ #
42
+ #--
43
+ #: () -> bool
44
+ def retired?
45
+ @mutex.synchronize { @cancelled }
46
+ end
47
+
48
+ private
49
+
50
+ # Runs tool discovery using the configured Runner.
51
+ #
52
+ # With +Runner::Sequential+ (default) discovery blocks inline. With
53
+ # +Runner::Threaded+ discovery runs on a pool thread but +map+ still
54
+ # blocks the caller — useful for Rails connection-pool isolation.
55
+ #
56
+ #--
57
+ #: () -> void
58
+ def run_discovery
59
+ Riffer.config.mcp.discovery_runner.map([nil], context: nil) do |_|
60
+ client = build_client
61
+ tool_defs = client.tools_list
62
+ tools = Riffer::Mcp::ToolFactory.build(@manifest.name, client, tool_defs)
63
+
64
+ @mutex.synchronize do
65
+ next if @cancelled
66
+ @tools = tools.freeze
67
+ end
68
+ end
69
+ end
70
+
71
+ #--
72
+ #: () -> Riffer::Mcp::Client
73
+ def build_client
74
+ Riffer::Mcp::Client.new(endpoint: @manifest.endpoint, headers: @manifest.discovery_headers || {})
75
+ end
76
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ # Thread-safe global store for MCP server registrations.
5
+ #
6
+ # Keyed by manifest name. All public methods are mutex-guarded.
7
+ #
8
+ module Riffer::Mcp::Registry
9
+ @mutex = Mutex.new
10
+ @store = {} #: Hash[String, Riffer::Mcp::Registration]
11
+
12
+ class << self
13
+ # Registers an MCP server and starts async tool discovery.
14
+ #
15
+ # Accepts a Manifest instance or a hash of manifest keyword arguments.
16
+ # Replaces any existing registration with the same name.
17
+ #
18
+ #--
19
+ #: ((Hash[Symbol, untyped] | Riffer::Mcp::Manifest)) -> Riffer::Mcp::Registration
20
+ def register(manifest_or_hash)
21
+ manifest = manifest_or_hash.is_a?(Riffer::Mcp::Manifest) ? manifest_or_hash : Riffer::Mcp::Manifest.new(**manifest_or_hash)
22
+ registration = Riffer::Mcp::Registration.new(manifest)
23
+ old = @mutex.synchronize do
24
+ previous = @store[manifest.name]
25
+ @store[manifest.name] = registration
26
+ previous
27
+ end
28
+ old&.retire!
29
+ registration
30
+ end
31
+
32
+ # Removes a registration by name.
33
+ #
34
+ #--
35
+ #: ((String | Symbol)) -> void
36
+ def unregister(name)
37
+ removed = @mutex.synchronize { @store.delete(name.to_s) }
38
+ removed&.retire!
39
+ end
40
+
41
+ # Returns a frozen snapshot of all current registrations.
42
+ #
43
+ #--
44
+ #: () -> Hash[String, Riffer::Mcp::Registration]
45
+ def registrations
46
+ @mutex.synchronize { @store.dup.freeze }
47
+ end
48
+
49
+ # Returns all registrations whose manifest tags intersect with the given tags.
50
+ #
51
+ # Tags are normalized to symbols before matching.
52
+ #
53
+ #--
54
+ #: (Array[Symbol]) -> Array[Riffer::Mcp::Registration]
55
+ def find_by_tags(tags)
56
+ normalized = tags.map(&:to_sym)
57
+ @mutex.synchronize do
58
+ @store.values.select { |reg| (reg.manifest.tags & normalized).any? }
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ # Generates anonymous Riffer::Tool subclasses from MCP tool definitions.
5
+ #
6
+ # Each generated class:
7
+ # - Has +.name+, +.description+, and +.parameters_schema+ derived from the MCP tool definition.
8
+ # - Delegates +#call+ to the MCP client's +tools_call+ method.
9
+ # - Skips Riffer's param validation — the MCP server validates inputs.
10
+ #
11
+ module Riffer::Mcp::ToolFactory
12
+ # Builds one Riffer::Tool subclass per tool definition.
13
+ #
14
+ # Tool names are prefixed with the manifest name to avoid collisions
15
+ # across MCP servers (e.g. +"jira__search"+). The original server-side
16
+ # name is available via +.mcp_server_tool_name+.
17
+ #
18
+ #--
19
+ #: (String, Riffer::Mcp::Client, Array[Hash[Symbol, untyped]]) -> Array[singleton(Riffer::Tool)]
20
+ def self.build(manifest_name, client, tool_defs)
21
+ tool_defs.map { |td| build_tool_class(manifest_name, client, td) }
22
+ end
23
+
24
+ # Replaces characters that are unsafe in LLM tool names.
25
+ #: (String) -> String
26
+ def self.sanitize_name_component(str)
27
+ str.gsub(/[^a-zA-Z0-9_-]/, "_")
28
+ end
29
+ private_class_method :sanitize_name_component
30
+
31
+ private_class_method def self.build_tool_class(manifest_name, client, td)
32
+ prefixed = "#{sanitize_name_component(manifest_name)}__#{sanitize_name_component(td[:name])}"
33
+
34
+ Class.new(Riffer::Tool) do
35
+ @mcp_client = client
36
+ @mcp_server_tool_name = td[:name]
37
+ # Set @identifier directly so .identifier does not fall back to
38
+ # class_name_to_path(nil) on this anonymous class.
39
+ @identifier = prefixed
40
+
41
+ define_singleton_method(:name) { prefixed }
42
+ define_singleton_method(:mcp_server_tool_name) { td[:name] }
43
+ define_singleton_method(:description) { td[:description] }
44
+ define_singleton_method(:parameters_schema) { |strict: false| td[:input_schema] || Riffer::Tool.send(:empty_schema) }
45
+
46
+ define_method(:call) do |context:, **kwargs|
47
+ result = self.class.instance_variable_get(:@mcp_client).tools_call(
48
+ self.class.instance_variable_get(:@mcp_server_tool_name), kwargs
49
+ )
50
+ text(result)
51
+ end
52
+ end
53
+ end
54
+ end
data/lib/riffer/mcp.rb ADDED
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ # Riffer::Mcp provides integration with Model Context Protocol (MCP) servers.
5
+ #
6
+ # Register MCP servers globally; agents opt-in by tag via the +use_mcp+ DSL.
7
+ # Tags are application-defined; see +docs/14_MCP.md+ (Tags section).
8
+ #
9
+ # Riffer::Mcp.register(
10
+ # name: "github",
11
+ # tags: [:github],
12
+ # endpoint: "https://mcp.github.com",
13
+ # discovery_headers: -> { {"Authorization" => "Bearer #{ENV['GITHUB_TOKEN']}"} }
14
+ # )
15
+ #
16
+ # class MyAgent < Riffer::Agent
17
+ # model "openai/gpt-4o"
18
+ # use_mcp :github
19
+ # end
20
+ #
21
+ module Riffer::Mcp
22
+ # Base error for all MCP-related failures.
23
+ class Error < Riffer::Error; end
24
+
25
+ # Raised when +Riffer.config.mcp.credentials+ returns +nil+ during +tools/call+
26
+ # after the server's tools were included for this run.
27
+ class CredentialsDeniedError < Error; end
28
+
29
+ # Registers an MCP server, blocking until tool discovery completes.
30
+ #
31
+ # Raises on discovery failure. Pass a +Manifest+ instance or a hash with
32
+ # the same keys.
33
+ #
34
+ #--
35
+ #: ((Hash[Symbol, untyped] | Riffer::Mcp::Manifest)) -> Riffer::Mcp::Registration
36
+ def self.register(manifest_or_hash)
37
+ Registry.register(manifest_or_hash)
38
+ end
39
+
40
+ # Removes a registration by name.
41
+ #
42
+ # Subsequent agent runs will not see tools from this server.
43
+ #
44
+ #--
45
+ #: (String) -> void
46
+ def self.unregister(name)
47
+ Registry.unregister(name)
48
+ end
49
+
50
+ # Returns all current registrations keyed by name (for introspection).
51
+ #
52
+ #--
53
+ #: () -> Hash[String, Riffer::Mcp::Registration]
54
+ def self.registrations
55
+ Registry.registrations
56
+ end
57
+ end
@@ -150,30 +150,36 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
150
150
  **params,
151
151
  request_options: {extra_headers: {"accept-encoding" => "identity"}}
152
152
  )
153
- current_state[:stream] = stream
154
-
155
- stream.each do |event|
156
- case event
157
- when Anthropic::Models::RawContentBlockStartEvent
158
- handle_raw_content_block_start(event, state: current_state)
159
- when Anthropic::Models::RawContentBlockDeltaEvent
160
- handle_raw_content_block_delta(event, state: current_state)
161
- when Anthropic::Streaming::TextEvent
162
- handle_text_event(event, state: current_state, yielder: yielder)
163
- when Anthropic::Streaming::ThinkingEvent
164
- handle_thinking_event(event, state: current_state, yielder: yielder)
165
- when Anthropic::Streaming::InputJsonEvent
166
- handle_input_json_event(event, state: current_state, yielder: yielder)
167
- when Anthropic::Streaming::ContentBlockStopEvent
168
- block_type = event.content_block&.type.to_s
169
- handle_content_block_stop_text(event, state: current_state, yielder: yielder) if block_type == "text" && current_state[:text]
170
- handle_content_block_stop_tool_use(event, state: current_state, yielder: yielder) if block_type == "tool_use"
171
- handle_content_block_stop_thinking(event, state: current_state, yielder: yielder) if block_type == "thinking" && current_state[:reasoning]
172
- handle_content_block_stop_server_tool_use(event, state: current_state, yielder: yielder) if block_type == "server_tool_use"
173
- handle_content_block_stop_web_search_result(event, state: current_state, yielder: yielder) if block_type == "web_search_tool_result"
174
- when Anthropic::Streaming::MessageStopEvent
175
- handle_message_stop(event, state: current_state, yielder: yielder)
153
+
154
+ begin
155
+ stream.each do |event|
156
+ case event
157
+ when Anthropic::Models::RawContentBlockStartEvent
158
+ handle_raw_content_block_start(event, state: current_state)
159
+ when Anthropic::Models::RawContentBlockDeltaEvent
160
+ handle_raw_content_block_delta(event, state: current_state)
161
+ when Anthropic::Streaming::TextEvent
162
+ handle_text_event(event, state: current_state, yielder: yielder)
163
+ when Anthropic::Streaming::ThinkingEvent
164
+ handle_thinking_event(event, state: current_state, yielder: yielder)
165
+ when Anthropic::Streaming::InputJsonEvent
166
+ handle_input_json_event(event, state: current_state, yielder: yielder)
167
+ when Anthropic::Streaming::ContentBlockStopEvent
168
+ block_type = event.content_block&.type.to_s
169
+ handle_content_block_stop_text(event, state: current_state, yielder: yielder) if block_type == "text" && current_state[:text]
170
+ handle_content_block_stop_tool_use(event, state: current_state, yielder: yielder) if block_type == "tool_use"
171
+ handle_content_block_stop_thinking(event, state: current_state, yielder: yielder) if block_type == "thinking" && current_state[:reasoning]
172
+ handle_content_block_stop_server_tool_use(event, state: current_state, yielder: yielder) if block_type == "server_tool_use"
173
+ handle_content_block_stop_web_search_result(event, state: current_state, yielder: yielder) if block_type == "web_search_tool_result"
174
+ when Anthropic::Streaming::MessageStopEvent
175
+ handle_message_stop(event, accumulated_message: stream.accumulated_message, yielder: yielder)
176
+ end
176
177
  end
178
+ ensure
179
+ # Anthropic SDK does not auto-close the underlying HTTP stream when
180
+ # iteration is interrupted (raise / fiber cancellation), so the SSE
181
+ # socket leaks until GC. close is idempotent and a no-op after EOF.
182
+ stream.close
177
183
  end
178
184
  end
179
185
 
@@ -280,20 +286,19 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
280
286
  end
281
287
 
282
288
  #--
283
- #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
284
- def handle_message_stop(_event, state:, yielder:)
285
- final_message = state[:stream].accumulated_message
286
- if final_message&.usage
287
- usage = final_message.usage
288
- yielder << Riffer::StreamEvents::TokenUsageDone.new(
289
- token_usage: Riffer::TokenUsage.new(
290
- input_tokens: usage.input_tokens,
291
- output_tokens: usage.output_tokens,
292
- cache_creation_tokens: usage.cache_creation_input_tokens,
293
- cache_read_tokens: usage.cache_read_input_tokens
294
- )
289
+ #: (untyped, accumulated_message: Anthropic::Models::Message?, yielder: Enumerator::Yielder) -> void
290
+ def handle_message_stop(_event, accumulated_message:, yielder:)
291
+ usage = accumulated_message&.usage
292
+ return unless usage
293
+
294
+ yielder << Riffer::StreamEvents::TokenUsageDone.new(
295
+ token_usage: Riffer::TokenUsage.new(
296
+ input_tokens: usage.input_tokens,
297
+ output_tokens: usage.output_tokens,
298
+ cache_creation_tokens: usage.cache_creation_input_tokens,
299
+ cache_read_tokens: usage.cache_read_input_tokens
295
300
  )
296
- end
301
+ )
297
302
  end
298
303
 
299
304
  #--
@@ -124,33 +124,40 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
124
124
  }
125
125
 
126
126
  stream = @client.responses.stream(params)
127
- stream.each do |event|
128
- case event.type
129
- when :"response.output_item.added"
130
- handle_output_item_added_function_call(event, state: current_state, yielder: yielder) if event.item&.type == :function_call
131
- when :"response.output_text.delta"
132
- handle_output_text_delta(event, state: current_state, yielder: yielder)
133
- when :"response.output_text.done"
134
- handle_output_text_done(event, state: current_state, yielder: yielder)
135
- when :"response.reasoning_summary_text.delta"
136
- handle_reasoning_summary_text_delta(event, state: current_state, yielder: yielder)
137
- when :"response.reasoning_summary_text.done"
138
- handle_reasoning_summary_text_done(event, state: current_state, yielder: yielder)
139
- when :"response.function_call_arguments.delta"
140
- handle_function_call_arguments_delta(event, state: current_state, yielder: yielder)
141
- when :"response.function_call_arguments.done"
142
- handle_function_call_arguments_done(event, state: current_state, yielder: yielder)
143
- when :"response.web_search_call.in_progress"
144
- handle_web_search_status(event, status: "in_progress", yielder: yielder)
145
- when :"response.web_search_call.searching"
146
- handle_web_search_status(event, status: "searching", yielder: yielder)
147
- when :"response.web_search_call.completed"
148
- handle_web_search_status(event, status: "completed", yielder: yielder)
149
- when :"response.output_item.done"
150
- handle_output_item_done_web_search(event, yielder: yielder) if event.item&.type == :web_search_call
151
- when :"response.completed"
152
- handle_response_completed(event, state: current_state, yielder: yielder)
127
+ begin
128
+ stream.each do |event|
129
+ case event.type
130
+ when :"response.output_item.added"
131
+ handle_output_item_added_function_call(event, state: current_state, yielder: yielder) if event.item&.type == :function_call
132
+ when :"response.output_text.delta"
133
+ handle_output_text_delta(event, state: current_state, yielder: yielder)
134
+ when :"response.output_text.done"
135
+ handle_output_text_done(event, state: current_state, yielder: yielder)
136
+ when :"response.reasoning_summary_text.delta"
137
+ handle_reasoning_summary_text_delta(event, state: current_state, yielder: yielder)
138
+ when :"response.reasoning_summary_text.done"
139
+ handle_reasoning_summary_text_done(event, state: current_state, yielder: yielder)
140
+ when :"response.function_call_arguments.delta"
141
+ handle_function_call_arguments_delta(event, state: current_state, yielder: yielder)
142
+ when :"response.function_call_arguments.done"
143
+ handle_function_call_arguments_done(event, state: current_state, yielder: yielder)
144
+ when :"response.web_search_call.in_progress"
145
+ handle_web_search_status(event, status: "in_progress", yielder: yielder)
146
+ when :"response.web_search_call.searching"
147
+ handle_web_search_status(event, status: "searching", yielder: yielder)
148
+ when :"response.web_search_call.completed"
149
+ handle_web_search_status(event, status: "completed", yielder: yielder)
150
+ when :"response.output_item.done"
151
+ handle_output_item_done_web_search(event, yielder: yielder) if event.item&.type == :web_search_call
152
+ when :"response.completed"
153
+ handle_response_completed(event, state: current_state, yielder: yielder)
154
+ end
153
155
  end
156
+ ensure
157
+ # OpenAI SDK does not auto-close the underlying HTTP stream when
158
+ # iteration is interrupted (raise / fiber cancellation), so the SSE
159
+ # socket leaks until GC. close is idempotent and a no-op after EOF.
160
+ stream.close
154
161
  end
155
162
  end
156
163
 
@@ -2,5 +2,5 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  module Riffer
5
- VERSION = "0.26.0" #: String
5
+ VERSION = "0.27.0" #: String
6
6
  end
@@ -113,6 +113,19 @@ class Riffer::Agent
113
113
  # : (Hash[Symbol, untyped]?) -> Array[singleton(Riffer::Tool)]
114
114
  def self.resolve_uses_tools_config: (Hash[Symbol, untyped]?) -> Array[singleton(Riffer::Tool)]
115
115
 
116
+ # Opts this agent into tools from all MCP registrations that share any of
117
+ # the given tag(s).
118
+ #
119
+ # +tag+ - a String or Symbol; matched against registration manifest tags.
120
+ #
121
+ # : (String | Symbol) -> void
122
+ def self.use_mcp: (String | Symbol) -> void
123
+
124
+ # Returns the accumulated +use_mcp+ configurations for this agent class.
125
+ #
126
+ # : () -> Array[Hash[Symbol, untyped]]
127
+ def self.mcp_configs: () -> Array[Hash[Symbol, untyped]]
128
+
116
129
  # Gets or sets the tool runtime for this agent.
117
130
  #
118
131
  # Accepts a Riffer::ToolRuntime subclass, a Riffer::ToolRuntime instance,
@@ -352,6 +365,26 @@ class Riffer::Agent
352
365
  def resolve_model: () -> String
353
366
 
354
367
  # --
368
+ # : () -> Array[singleton(Riffer::Tool)]
369
+ def resolve_uses_tools_config: () -> Array[singleton(Riffer::Tool)]
370
+
371
+ # --
372
+ # : () -> Array[singleton(Riffer::Tool)]
373
+ def resolve_mcp_tool_classes: () -> Array[singleton(Riffer::Tool)]
374
+
375
+ # Each matching MCP registration once, with tag symbols unioned across +use_mcp+ rows.
376
+ #
377
+ # : (Array[Hash[Symbol, untyped]]) -> Hash[Riffer::Mcp::Registration, Array[Symbol]]
378
+ def gather_mcp_registrations_with_tags: (Array[Hash[Symbol, untyped]]) -> Hash[Riffer::Mcp::Registration, Array[Symbol]]
379
+
380
+ # : (Riffer::Mcp::Registration, Array[Symbol], Proc?, Hash[Symbol, untyped]) -> Array[singleton(Riffer::Tool)]
381
+ def mcp_tools_for_registration: (Riffer::Mcp::Registration, Array[Symbol], Proc?, Hash[Symbol, untyped]) -> Array[singleton(Riffer::Tool)]
382
+
383
+ # Raises if two or more tool classes share the same +.name+ (ambiguous dispatch).
384
+ #
385
+ # : (Array[singleton(Riffer::Tool)]) -> void
386
+ def assert_distinct_tool_names!: (Array[singleton(Riffer::Tool)]) -> void
387
+
355
388
  # : () -> Array[singleton(Riffer::Tool)]
356
389
  def resolved_tools: () -> Array[singleton(Riffer::Tool)]
357
390
 
@@ -63,6 +63,15 @@ class Riffer::Config
63
63
  | ({ ?judge_model: untyped }) -> instance
64
64
  end
65
65
 
66
+ class Mcp < Struct[untyped]
67
+ attr_accessor credentials(): untyped
68
+
69
+ attr_accessor discovery_runner(): untyped
70
+
71
+ def self.new: (?credentials: untyped, ?discovery_runner: untyped) -> instance
72
+ | ({ ?credentials: untyped, ?discovery_runner: untyped }) -> instance
73
+ end
74
+
66
75
  # Skills-related global configuration.
67
76
  #
68
77
  # See <tt>Riffer.config.skills.default_activate_tool</tt> and
@@ -124,6 +133,17 @@ class Riffer::Config
124
133
  # Evals configuration (Struct with +judge_model+).
125
134
  attr_reader evals: Riffer::Config::Evals
126
135
 
136
+ # MCP configuration (Struct with +credentials+ and +discovery_runner+).
137
+ #
138
+ # +credentials+ is an optional Proc for per-run MCP +tools/call+ HTTP headers.
139
+ # Signature: +->(manifest:, matched_tags:, context:) { Hash or nil }+.
140
+ # +nil+ from the proc at tool-resolution time omits that server's tools; +nil+
141
+ # at tool-call time raises Riffer::Mcp::CredentialsDeniedError.
142
+ #
143
+ # +discovery_runner+ is the Riffer::Runner used to execute tool discovery
144
+ # (default +Runner::Sequential+).
145
+ attr_reader mcp: Riffer::Config::Mcp
146
+
127
147
  # Global tool runtime configuration (experimental).
128
148
  #
129
149
  # Accepts a Riffer::ToolRuntime subclass, a Riffer::ToolRuntime instance,
@@ -0,0 +1,17 @@
1
+ # Generated from lib/riffer/mcp/authenticated_tool.rb with RBS::Inline
2
+
3
+ # Wraps MCP-generated tool classes so +tools/call+ uses +Riffer.config.mcp.credentials+
4
+ # per invocation while delegating metadata to the inner class.
5
+ module Riffer::Mcp::AuthenticatedTool
6
+ # Returns one wrapper class per inner tool, sharing +manifest+ and +matched_tags+.
7
+ #
8
+ # --
9
+ # : (Array[singleton(Riffer::Tool)], Riffer::Mcp::Manifest, Array[Symbol]) -> Array[singleton(Riffer::Tool)]
10
+ def self.wrap_all: (Array[singleton(Riffer::Tool)], Riffer::Mcp::Manifest, Array[Symbol]) -> Array[singleton(Riffer::Tool)]
11
+
12
+ # --
13
+ # : (singleton(Riffer::Tool), Riffer::Mcp::Manifest, Array[Symbol]) -> singleton(Riffer::Tool)
14
+ # Class.new(Riffer::Tool) is typed as ::Class by steep — it cannot verify the subtype
15
+ # relationship for dynamically created anonymous classes, so the ignore is required.
16
+ def self.wrap_one: (singleton(Riffer::Tool), Riffer::Mcp::Manifest, Array[Symbol]) -> singleton(Riffer::Tool)
17
+ end
@@ -0,0 +1,33 @@
1
+ # Generated from lib/riffer/mcp/client.rb with RBS::Inline
2
+
3
+ # Thin wrapper around the MCP Ruby SDK client (mcp gem v0.8+).
4
+ #
5
+ # Resolves headers (if a Proc) once at initialization, then provides
6
+ # +tools_list+ and +tools_call+. Used for discovery (+Manifest#discovery_headers+)
7
+ # and for +tools/call+ when no +credentials+ proc is configured.
8
+ #
9
+ # MCP gem API used:
10
+ # MCP::Client::HTTP.new(url:, headers:) — HTTP transport (requires faraday)
11
+ # MCP::Client.new(transport:) — client
12
+ # client.tools — Array<MCP::Client::Tool>
13
+ # client.call_tool(tool:, arguments:) — raw JSON-RPC response Hash
14
+ class Riffer::Mcp::Client
15
+ include Riffer::Helpers::Dependencies
16
+
17
+ # --
18
+ # : (endpoint: String, ?headers: (Hash[String, String] | Proc), ?client: untyped?) -> void
19
+ def initialize: (endpoint: String, ?headers: Hash[String, String] | Proc, ?client: untyped?) -> void
20
+
21
+ # Returns an array of tool definition hashes, each with +:name+, +:description+,
22
+ # and +:input_schema+ keys.
23
+ #
24
+ # --
25
+ # : () -> Array[Hash[Symbol, untyped]]
26
+ def tools_list: () -> Array[Hash[Symbol, untyped]]
27
+
28
+ # Calls a tool on the MCP server and returns joined text content from the response.
29
+ #
30
+ # --
31
+ # : (String, ?Hash[untyped, untyped]) -> String
32
+ def tools_call: (String, ?Hash[untyped, untyped]) -> String
33
+ end
@@ -0,0 +1,30 @@
1
+ # Generated from lib/riffer/mcp/manifest.rb with RBS::Inline
2
+
3
+ # Riffer::Mcp::Manifest holds the configuration for a single MCP server.
4
+ #
5
+ # +name+ - String identifier used as the registration key and generated-agent identifier.
6
+ # +tags+ - Array[Symbol]; normalized to symbols at construction time.
7
+ # +endpoint+ - String HTTPS URL passed to the MCP transport.
8
+ # +discovery_headers+ - Hash or Proc; resolved once when building the discovery client for +tools/list+.
9
+ # +credentials_scope+ - Optional symbol hint: +:global+, +:tenant+, +:user+ — documents whether
10
+ # invocation credentials are expected to depend on tenant and/or user keys in +context+ (no ids stored).
11
+ # Apps may treat +:user+ as "user in tenant" and pass both keys in +context+.
12
+ class Riffer::Mcp::Manifest
13
+ attr_reader name: String
14
+
15
+ attr_reader tags: Array[Symbol]
16
+
17
+ attr_reader endpoint: String
18
+
19
+ attr_reader discovery_headers: (Hash[String, untyped] | ::Proc)?
20
+
21
+ attr_reader credentials_scope: Symbol?
22
+
23
+ # --
24
+ # : (name: String, endpoint: String, ?tags: Array[untyped]?, ?discovery_headers: (Hash[String, untyped] | ::Proc)?, ?credentials_scope: (String | Symbol)?) -> void
25
+ def initialize: (name: String, endpoint: String, ?tags: Array[untyped]?, ?discovery_headers: (Hash[String, untyped] | ::Proc)?, ?credentials_scope: (String | Symbol)?) -> void
26
+
27
+ private
28
+
29
+ def valid_endpoint?: () -> untyped
30
+ end
@@ -0,0 +1,49 @@
1
+ # Generated from lib/riffer/mcp/registration.rb with RBS::Inline
2
+
3
+ # Per-server state managed by Riffer::Mcp::Registry.
4
+ #
5
+ # Created when a server is registered. Discovers tools via the MCP
6
+ # +tools/list+ call, then generates tool classes.
7
+ class Riffer::Mcp::Registration
8
+ # The manifest that describes this server.
9
+ attr_reader manifest: Riffer::Mcp::Manifest
10
+
11
+ # Generated Riffer::Tool subclasses.
12
+ #
13
+ # --
14
+ # : () -> Array[singleton(Riffer::Tool)]
15
+ def tools: () -> Array[singleton(Riffer::Tool)]
16
+
17
+ # --
18
+ # : (Riffer::Mcp::Manifest) -> void
19
+ def initialize: (Riffer::Mcp::Manifest) -> void
20
+
21
+ # Retires this registration, preventing in-flight discovery from publishing
22
+ # state.
23
+ #
24
+ # --
25
+ # : () -> void
26
+ def retire!: () -> void
27
+
28
+ # Returns true if this registration has been retired.
29
+ #
30
+ # --
31
+ # : () -> bool
32
+ def retired?: () -> bool
33
+
34
+ private
35
+
36
+ # Runs tool discovery using the configured Runner.
37
+ #
38
+ # With +Runner::Sequential+ (default) discovery blocks inline. With
39
+ # +Runner::Threaded+ discovery runs on a pool thread but +map+ still
40
+ # blocks the caller — useful for Rails connection-pool isolation.
41
+ #
42
+ # --
43
+ # : () -> void
44
+ def run_discovery: () -> void
45
+
46
+ # --
47
+ # : () -> Riffer::Mcp::Client
48
+ def build_client: () -> Riffer::Mcp::Client
49
+ end