riffer 0.31.0 → 0.32.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.
- checksums.yaml +4 -4
- data/.agents/code-style.md +63 -4
- data/.agents/rbs-inline.md +1 -6
- data/.release-please-manifest.json +1 -1
- data/AGENTS.md +1 -2
- data/CHANGELOG.md +18 -0
- data/docs/08_MESSAGES.md +1 -1
- data/docs/14_MCP.md +50 -5
- data/docs/providers/02_AMAZON_BEDROCK.md +14 -0
- data/lib/riffer/agent/config.rb +42 -47
- data/lib/riffer/agent/context.rb +70 -50
- data/lib/riffer/agent/response.rb +4 -20
- data/lib/riffer/agent/run.rb +28 -67
- data/lib/riffer/agent/serializer.rb +22 -81
- data/lib/riffer/agent/session/repair.rb +14 -40
- data/lib/riffer/agent/session.rb +25 -67
- data/lib/riffer/agent/structured_output/result.rb +3 -11
- data/lib/riffer/agent/structured_output.rb +5 -13
- data/lib/riffer/agent.rb +74 -192
- data/lib/riffer/config.rb +34 -101
- data/lib/riffer/evals/evaluator.rb +7 -27
- data/lib/riffer/evals/evaluator_runner.rb +11 -19
- data/lib/riffer/evals/judge.rb +4 -25
- data/lib/riffer/evals/result.rb +1 -18
- data/lib/riffer/evals/run_result.rb +0 -11
- data/lib/riffer/evals/scenario_result.rb +0 -14
- data/lib/riffer/evals.rb +0 -6
- data/lib/riffer/guardrail.rb +4 -27
- data/lib/riffer/guardrails/modification.rb +0 -10
- data/lib/riffer/guardrails/result.rb +3 -30
- data/lib/riffer/guardrails/runner.rb +5 -22
- data/lib/riffer/guardrails/tripwire.rb +1 -19
- data/lib/riffer/guardrails.rb +2 -4
- data/lib/riffer/helpers/call_or_value.rb +4 -3
- data/lib/riffer/helpers/class_name_converter.rb +3 -1
- data/lib/riffer/helpers/dependencies.rb +5 -7
- data/lib/riffer/helpers.rb +0 -5
- data/lib/riffer/mcp/authenticated_tool.rb +9 -9
- data/lib/riffer/mcp/client.rb +12 -17
- data/lib/riffer/mcp/manifest.rb +13 -10
- data/lib/riffer/mcp/registration.rb +2 -11
- data/lib/riffer/mcp/registry.rb +44 -52
- data/lib/riffer/mcp/search_tool.rb +53 -0
- data/lib/riffer/mcp/tool_factory.rb +13 -18
- data/lib/riffer/mcp.rb +12 -17
- data/lib/riffer/messages/assistant.rb +2 -9
- data/lib/riffer/messages/base.rb +46 -16
- data/lib/riffer/messages/file_part.rb +32 -24
- data/lib/riffer/messages/system.rb +0 -5
- data/lib/riffer/messages/tool.rb +0 -10
- data/lib/riffer/messages/user.rb +0 -10
- data/lib/riffer/messages.rb +0 -7
- data/lib/riffer/params/boolean.rb +2 -4
- data/lib/riffer/params/param.rb +28 -39
- data/lib/riffer/params.rb +9 -21
- data/lib/riffer/providers/amazon_bedrock.rb +42 -28
- data/lib/riffer/providers/anthropic.rb +4 -9
- data/lib/riffer/providers/azure_open_ai.rb +3 -19
- data/lib/riffer/providers/base.rb +13 -26
- data/lib/riffer/providers/gemini.rb +4 -4
- data/lib/riffer/providers/mock.rb +6 -26
- data/lib/riffer/providers/open_ai.rb +6 -8
- data/lib/riffer/providers/open_router.rb +4 -10
- data/lib/riffer/providers/repository.rb +4 -3
- data/lib/riffer/providers/token_usage.rb +9 -20
- data/lib/riffer/providers.rb +0 -8
- data/lib/riffer/runner/fibers.rb +10 -16
- data/lib/riffer/runner/sequential.rb +1 -4
- data/lib/riffer/runner/threaded.rb +3 -14
- data/lib/riffer/runner.rb +2 -15
- data/lib/riffer/skills/activate_tool.rb +2 -11
- data/lib/riffer/skills/adapter.rb +4 -22
- data/lib/riffer/skills/backend.rb +7 -21
- data/lib/riffer/skills/config.rb +10 -31
- data/lib/riffer/skills/context.rb +5 -20
- data/lib/riffer/skills/filesystem_backend.rb +7 -25
- data/lib/riffer/skills/frontmatter.rb +10 -28
- data/lib/riffer/skills/markdown_adapter.rb +2 -9
- data/lib/riffer/skills/xml_adapter.rb +2 -8
- data/lib/riffer/stream_events/base.rb +1 -6
- data/lib/riffer/stream_events/guardrail_modification.rb +1 -8
- data/lib/riffer/stream_events/guardrail_tripwire.rb +1 -8
- data/lib/riffer/stream_events/interrupt.rb +4 -7
- data/lib/riffer/stream_events/reasoning_delta.rb +2 -4
- data/lib/riffer/stream_events/reasoning_done.rb +2 -4
- data/lib/riffer/stream_events/skill_activation.rb +2 -4
- data/lib/riffer/stream_events/text_delta.rb +0 -2
- data/lib/riffer/stream_events/text_done.rb +1 -3
- data/lib/riffer/stream_events/token_usage_done.rb +1 -8
- data/lib/riffer/stream_events/tool_call_delta.rb +2 -3
- data/lib/riffer/stream_events/tool_call_done.rb +1 -3
- data/lib/riffer/stream_events/web_search_done.rb +1 -3
- data/lib/riffer/stream_events/web_search_status.rb +2 -3
- data/lib/riffer/stream_events.rb +0 -10
- data/lib/riffer/tool.rb +6 -13
- data/lib/riffer/tools/response.rb +8 -4
- data/lib/riffer/tools/runtime/fibers.rb +0 -3
- data/lib/riffer/tools/runtime/inline.rb +1 -4
- data/lib/riffer/tools/runtime/threaded.rb +0 -2
- data/lib/riffer/tools/runtime.rb +5 -38
- data/lib/riffer/tools/toolable.rb +5 -16
- data/lib/riffer/tools.rb +0 -4
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +7 -8
- data/sig/generated/riffer/agent/config.rbs +29 -46
- data/sig/generated/riffer/agent/context.rbs +40 -48
- data/sig/generated/riffer/agent/response.rbs +4 -20
- data/sig/generated/riffer/agent/run.rbs +12 -61
- data/sig/generated/riffer/agent/serializer.rbs +21 -80
- data/sig/generated/riffer/agent/session/repair.rbs +12 -40
- data/sig/generated/riffer/agent/session.rbs +25 -67
- data/sig/generated/riffer/agent/structured_output/result.rbs +2 -10
- data/sig/generated/riffer/agent/structured_output.rbs +5 -12
- data/sig/generated/riffer/agent.rbs +57 -186
- data/sig/generated/riffer/config.rbs +34 -100
- data/sig/generated/riffer/evals/evaluator.rbs +7 -27
- data/sig/generated/riffer/evals/evaluator_runner.rbs +9 -19
- data/sig/generated/riffer/evals/judge.rbs +4 -24
- data/sig/generated/riffer/evals/result.rbs +1 -17
- data/sig/generated/riffer/evals/run_result.rbs +0 -10
- data/sig/generated/riffer/evals/scenario_result.rbs +0 -13
- data/sig/generated/riffer/evals.rbs +0 -6
- data/sig/generated/riffer/guardrail.rbs +4 -27
- data/sig/generated/riffer/guardrails/modification.rbs +0 -10
- data/sig/generated/riffer/guardrails/result.rbs +3 -30
- data/sig/generated/riffer/guardrails/runner.rbs +5 -22
- data/sig/generated/riffer/guardrails/tripwire.rbs +1 -19
- data/sig/generated/riffer/guardrails.rbs +2 -4
- data/sig/generated/riffer/helpers/call_or_value.rbs +4 -3
- data/sig/generated/riffer/helpers/class_name_converter.rbs +1 -1
- data/sig/generated/riffer/helpers/dependencies.rbs +3 -7
- data/sig/generated/riffer/helpers.rbs +0 -5
- data/sig/generated/riffer/mcp/authenticated_tool.rbs +5 -4
- data/sig/generated/riffer/mcp/client.rbs +10 -16
- data/sig/generated/riffer/mcp/manifest.rbs +9 -9
- data/sig/generated/riffer/mcp/registration.rbs +2 -10
- data/sig/generated/riffer/mcp/registry.rbs +11 -18
- data/sig/generated/riffer/mcp/search_tool.rbs +26 -0
- data/sig/generated/riffer/mcp/tool_factory.rbs +10 -15
- data/sig/generated/riffer/mcp.rbs +10 -17
- data/sig/generated/riffer/messages/assistant.rbs +2 -8
- data/sig/generated/riffer/messages/base.rbs +11 -16
- data/sig/generated/riffer/messages/file_part.rbs +13 -23
- data/sig/generated/riffer/messages/system.rbs +0 -4
- data/sig/generated/riffer/messages/tool.rbs +0 -9
- data/sig/generated/riffer/messages/user.rbs +0 -9
- data/sig/generated/riffer/messages.rbs +0 -7
- data/sig/generated/riffer/params/boolean.rbs +2 -4
- data/sig/generated/riffer/params/param.rbs +21 -39
- data/sig/generated/riffer/params.rbs +9 -21
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +21 -25
- data/sig/generated/riffer/providers/anthropic.rbs +2 -7
- data/sig/generated/riffer/providers/azure_open_ai.rbs +3 -18
- data/sig/generated/riffer/providers/base.rbs +9 -25
- data/sig/generated/riffer/providers/gemini.rbs +0 -2
- data/sig/generated/riffer/providers/mock.rbs +6 -26
- data/sig/generated/riffer/providers/open_ai.rbs +1 -5
- data/sig/generated/riffer/providers/open_router.rbs +4 -10
- data/sig/generated/riffer/providers/repository.rbs +2 -3
- data/sig/generated/riffer/providers/token_usage.rbs +6 -16
- data/sig/generated/riffer/providers.rbs +0 -8
- data/sig/generated/riffer/runner/fibers.rbs +8 -15
- data/sig/generated/riffer/runner/sequential.rbs +1 -3
- data/sig/generated/riffer/runner/threaded.rbs +3 -13
- data/sig/generated/riffer/runner.rbs +2 -14
- data/sig/generated/riffer/skills/activate_tool.rbs +2 -11
- data/sig/generated/riffer/skills/adapter.rbs +4 -22
- data/sig/generated/riffer/skills/backend.rbs +7 -21
- data/sig/generated/riffer/skills/config.rbs +10 -31
- data/sig/generated/riffer/skills/context.rbs +5 -20
- data/sig/generated/riffer/skills/filesystem_backend.rbs +7 -24
- data/sig/generated/riffer/skills/frontmatter.rbs +10 -27
- data/sig/generated/riffer/skills/markdown_adapter.rbs +2 -9
- data/sig/generated/riffer/skills/xml_adapter.rbs +2 -8
- data/sig/generated/riffer/stream_events/base.rbs +1 -6
- data/sig/generated/riffer/stream_events/guardrail_modification.rbs +1 -8
- data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +1 -8
- data/sig/generated/riffer/stream_events/interrupt.rbs +4 -7
- data/sig/generated/riffer/stream_events/reasoning_delta.rbs +2 -4
- data/sig/generated/riffer/stream_events/reasoning_done.rbs +2 -4
- data/sig/generated/riffer/stream_events/skill_activation.rbs +2 -4
- data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
- data/sig/generated/riffer/stream_events/text_done.rbs +1 -3
- data/sig/generated/riffer/stream_events/token_usage_done.rbs +1 -7
- data/sig/generated/riffer/stream_events/tool_call_delta.rbs +2 -3
- data/sig/generated/riffer/stream_events/tool_call_done.rbs +1 -3
- data/sig/generated/riffer/stream_events/web_search_done.rbs +1 -3
- data/sig/generated/riffer/stream_events/web_search_status.rbs +2 -3
- data/sig/generated/riffer/stream_events.rbs +0 -10
- data/sig/generated/riffer/tool.rbs +5 -12
- data/sig/generated/riffer/tools/response.rbs +6 -4
- data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -3
- data/sig/generated/riffer/tools/runtime/inline.rbs +1 -3
- data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -2
- data/sig/generated/riffer/tools/runtime.rbs +5 -37
- data/sig/generated/riffer/tools/toolable.rbs +4 -14
- data/sig/generated/riffer/tools.rbs +0 -4
- data/sig/generated/riffer.rbs +5 -4
- data/sig/manual/riffer/agent/session/repair.rbs +5 -0
- data/sig/manual/riffer/evals/evaluator_runner.rbs +5 -0
- data/sig/manual/riffer/helpers/class_name_converter.rbs +5 -0
- data/sig/manual/riffer/helpers/dependencies.rbs +5 -0
- data/sig/manual/riffer/mcp/authenticated_tool.rbs +5 -0
- data/sig/manual/riffer/mcp/registry.rbs +5 -0
- data/sig/manual/riffer/mcp/tool_factory.rbs +5 -0
- data/sig/manual/riffer/mcp.rbs +5 -0
- data/sig/manual/riffer/providers/repository.rbs +5 -0
- data/sig/manual/riffer.rbs +5 -0
- metadata +17 -9
- data/.agents/rdoc.md +0 -69
- data/lib/riffer/messages/converter.rb +0 -90
- data/sig/generated/riffer/messages/converter.rbs +0 -33
- data/sig/manual/riffer/tools/toolable.rbs +0 -6
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Executes guardrails sequentially
|
|
5
|
-
#
|
|
6
|
-
# The runner processes guardrails in order, passing the output of each
|
|
7
|
-
# to the next. If any guardrail blocks, execution stops and a tripwire
|
|
8
|
-
# is returned.
|
|
9
|
-
#
|
|
10
|
-
# runner = Runner.new(guardrail_configs, phase: :before, context: context)
|
|
11
|
-
# data, tripwire, modifications = runner.run(messages)
|
|
4
|
+
# Executes guardrails sequentially, passing each one's output to the next; if
|
|
5
|
+
# any blocks, execution stops and a tripwire is returned.
|
|
12
6
|
class Riffer::Guardrails::Runner
|
|
13
7
|
# The guardrail configs to execute.
|
|
14
8
|
attr_reader :guardrail_configs #: Array[Hash[Symbol, untyped]]
|
|
@@ -19,12 +13,6 @@ class Riffer::Guardrails::Runner
|
|
|
19
13
|
# The context passed to guardrails.
|
|
20
14
|
attr_reader :context #: untyped
|
|
21
15
|
|
|
22
|
-
# Creates a new runner.
|
|
23
|
-
#
|
|
24
|
-
# [guardrail_configs] configs with :class and :options keys.
|
|
25
|
-
# [phase] :before or :after.
|
|
26
|
-
# [context] optional context to pass to guardrails.
|
|
27
|
-
#
|
|
28
16
|
#--
|
|
29
17
|
#: (Array[Hash[Symbol, untyped]], phase: Symbol, ?context: untyped) -> void
|
|
30
18
|
def initialize(guardrail_configs, phase:, context: nil)
|
|
@@ -33,14 +21,9 @@ class Riffer::Guardrails::Runner
|
|
|
33
21
|
@context = context
|
|
34
22
|
end
|
|
35
23
|
|
|
36
|
-
# Runs the guardrails sequentially.
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# For after phase, data should be a response and messages must be provided.
|
|
40
|
-
#
|
|
41
|
-
# [data] the data to process (messages for before, response for after).
|
|
42
|
-
# [messages] the conversation messages (required for after phase).
|
|
43
|
-
#
|
|
24
|
+
# Runs the guardrails sequentially. For the +:before+ phase +data+ is the
|
|
25
|
+
# messages array; for +:after+ it's the response (and +messages+ must be
|
|
26
|
+
# provided).
|
|
44
27
|
#--
|
|
45
28
|
#: (untyped, ?messages: Array[Riffer::Messages::Base]?) -> [untyped, Riffer::Guardrails::Tripwire?, Array[Riffer::Guardrails::Modification]]
|
|
46
29
|
def run(data, messages: nil)
|
|
@@ -2,16 +2,6 @@
|
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
4
|
# Captures information about a blocked guardrail execution.
|
|
5
|
-
#
|
|
6
|
-
# When a guardrail blocks execution, a Tripwire is created to record
|
|
7
|
-
# the reason, which guardrail triggered it, and which phase it occurred in.
|
|
8
|
-
#
|
|
9
|
-
# tripwire = Tripwire.new(
|
|
10
|
-
# reason: "PII detected in input",
|
|
11
|
-
# guardrail: PiiRedactor,
|
|
12
|
-
# phase: :before,
|
|
13
|
-
# metadata: { detected_types: [:email, :phone] }
|
|
14
|
-
# )
|
|
15
5
|
class Riffer::Guardrails::Tripwire
|
|
16
6
|
PHASES = Riffer::Guardrails::PHASES #: Array[Symbol]
|
|
17
7
|
|
|
@@ -27,15 +17,7 @@ class Riffer::Guardrails::Tripwire
|
|
|
27
17
|
# Optional metadata about the block.
|
|
28
18
|
attr_reader :metadata #: Hash[Symbol, untyped]?
|
|
29
19
|
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
# [reason] the reason for blocking.
|
|
33
|
-
# [guardrail] the guardrail class that blocked.
|
|
34
|
-
# [phase] :before or :after.
|
|
35
|
-
# [metadata] optional additional information.
|
|
36
|
-
#
|
|
37
|
-
# Raises Riffer::ArgumentError if the phase is invalid.
|
|
38
|
-
#
|
|
20
|
+
# Raises Riffer::ArgumentError if +phase+ is invalid.
|
|
39
21
|
#--
|
|
40
22
|
#: (reason: String, guardrail: singleton(Riffer::Guardrail), phase: Symbol, ?metadata: Hash[Symbol, untyped]?) -> void
|
|
41
23
|
def initialize(reason:, guardrail:, phase:, metadata: nil)
|
data/lib/riffer/guardrails.rb
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Namespace
|
|
5
|
-
#
|
|
6
|
-
# Guardrails provide pre-processing of input messages and post-processing
|
|
7
|
-
# of output responses in the agent pipeline.
|
|
4
|
+
# Namespace for guardrail components that pre-process input and post-process
|
|
5
|
+
# output in the agent pipeline.
|
|
8
6
|
module Riffer::Guardrails
|
|
9
7
|
PHASES = %i[before after].freeze #: Array[Symbol]
|
|
10
8
|
end
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Resolves the
|
|
5
|
-
# (passing +context+ when its arity is non-zero); otherwise returns
|
|
6
|
-
# +thing+ unchanged. When +thing+ is +nil+, returns +default+.
|
|
4
|
+
# Resolves the Proc-or-value idiom.
|
|
7
5
|
module Riffer::Helpers::CallOrValue
|
|
8
6
|
extend self
|
|
9
7
|
|
|
8
|
+
# Calls +thing+ when it's a Proc (passing +context+ if its arity is non-zero),
|
|
9
|
+
# returns it unchanged otherwise, or +default+ when +nil+.
|
|
10
|
+
#--
|
|
10
11
|
#: (untyped, ?context: untyped, ?default: untyped) -> untyped
|
|
11
12
|
def resolve(thing, context: nil, default: nil)
|
|
12
13
|
return default if thing.nil?
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
|
|
4
4
|
# Helper module for converting class names.
|
|
5
5
|
module Riffer::Helpers::ClassNameConverter
|
|
6
|
+
extend self
|
|
7
|
+
|
|
6
8
|
DEFAULT_SEPARATOR = "/" #: String
|
|
7
9
|
|
|
8
10
|
# Converts a class name to snake_case identifier format.
|
|
9
11
|
#
|
|
10
12
|
#--
|
|
11
13
|
#: (String, ?separator: String) -> String
|
|
12
|
-
def
|
|
14
|
+
def convert(class_name, separator: DEFAULT_SEPARATOR)
|
|
13
15
|
class_name
|
|
14
16
|
.to_s
|
|
15
17
|
.gsub("::", separator)
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# Used by providers to load their required gems only when needed.
|
|
4
|
+
# Lazy-loads gem dependencies — used by providers to load required gems only
|
|
5
|
+
# when needed.
|
|
7
6
|
module Riffer::Helpers::Dependencies
|
|
7
|
+
extend self
|
|
8
|
+
|
|
8
9
|
# Raised when a required gem cannot be loaded.
|
|
9
10
|
class LoadError < ::LoadError; end
|
|
10
11
|
|
|
11
|
-
# Requires a gem by name
|
|
12
|
-
#
|
|
13
|
-
# Raises LoadError if the gem cannot be required.
|
|
14
|
-
#
|
|
12
|
+
# Requires a gem by name; raises LoadError if it isn't installed.
|
|
15
13
|
#--
|
|
16
14
|
#: (String) -> true
|
|
17
15
|
def depends_on(gem_name)
|
data/lib/riffer/helpers.rb
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Namespace for shared helper modules in the Riffer framework.
|
|
5
|
-
#
|
|
6
|
-
# Helpers provide reusable functionality across the library:
|
|
7
|
-
# - Riffer::Helpers::ClassNameConverter - Class name to path conversion
|
|
8
|
-
# - Riffer::Helpers::Dependencies - Lazy gem dependency loading
|
|
9
4
|
module Riffer::Helpers
|
|
10
5
|
end
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Wraps MCP-generated tool classes so +tools/call+
|
|
5
|
-
# per invocation
|
|
6
|
-
#
|
|
4
|
+
# Wraps MCP-generated tool classes so +tools/call+ resolves
|
|
5
|
+
# +Riffer.config.mcp.credentials+ per invocation, delegating metadata to the
|
|
6
|
+
# inner class.
|
|
7
7
|
module Riffer::Mcp::AuthenticatedTool
|
|
8
|
+
extend self
|
|
9
|
+
|
|
8
10
|
# Returns one wrapper class per inner tool, sharing +manifest+ and +matched_tags+.
|
|
9
11
|
#
|
|
10
12
|
#--
|
|
11
13
|
#: (Array[singleton(Riffer::Tool)], Riffer::Mcp::Manifest, Array[Symbol]) -> Array[singleton(Riffer::Tool)]
|
|
12
|
-
def
|
|
14
|
+
def wrap_all(tool_classes, manifest, matched_tags)
|
|
13
15
|
tool_classes.map { |tc| wrap_one(tc, manifest, matched_tags) }
|
|
14
16
|
end
|
|
15
17
|
|
|
@@ -17,7 +19,7 @@ module Riffer::Mcp::AuthenticatedTool
|
|
|
17
19
|
#: (singleton(Riffer::Tool), Riffer::Mcp::Manifest, Array[Symbol]) -> singleton(Riffer::Tool)
|
|
18
20
|
# Class.new(Riffer::Tool) is typed as ::Class by steep — it cannot verify the subtype
|
|
19
21
|
# relationship for dynamically created anonymous classes, so the ignore is required.
|
|
20
|
-
def
|
|
22
|
+
def wrap_one(inner_class, manifest, matched_tags) # steep:ignore MethodBodyTypeMismatch
|
|
21
23
|
inner = inner_class
|
|
22
24
|
man = manifest
|
|
23
25
|
tags = matched_tags
|
|
@@ -33,10 +35,8 @@ module Riffer::Mcp::AuthenticatedTool
|
|
|
33
35
|
define_singleton_method(:description) { inner.description }
|
|
34
36
|
define_singleton_method(:parameters_schema) { |strict: false| inner.parameters_schema(strict: strict) }
|
|
35
37
|
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# Creates a fresh client per call so headers from the credentials proc stay
|
|
39
|
-
# current.
|
|
38
|
+
# Creates a fresh client per +tools/call+ so headers from the credentials
|
|
39
|
+
# proc stay current.
|
|
40
40
|
# TODO: A per-headers cache would reduce connection churn under load, and
|
|
41
41
|
# requires a follow-up investigation to determine how to invalidate failing
|
|
42
42
|
# clients.
|
data/lib/riffer/mcp/client.rb
CHANGED
|
@@ -1,21 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Thin wrapper around the MCP Ruby SDK client (mcp gem v0.8+).
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# +tools_list+ and +tools_call+. Used for discovery (+Manifest#discovery_headers+)
|
|
8
|
-
# and for +tools/call+ when no +credentials+ proc is configured.
|
|
9
|
-
#
|
|
10
|
-
# MCP gem API used:
|
|
11
|
-
# MCP::Client::HTTP.new(url:, headers:) — HTTP transport (requires faraday)
|
|
12
|
-
# MCP::Client.new(transport:) — client
|
|
13
|
-
# client.tools — Array<MCP::Client::Tool>
|
|
14
|
-
# client.call_tool(tool:, arguments:) — raw JSON-RPC response Hash
|
|
15
|
-
#
|
|
4
|
+
# Thin wrapper around the MCP Ruby SDK client (mcp gem v0.8+). Resolves headers
|
|
5
|
+
# (if a Proc) once at init, then provides +tools_list+ / +tools_call+ — used for
|
|
6
|
+
# discovery and for +tools/call+ when no +credentials+ proc is configured.
|
|
16
7
|
class Riffer::Mcp::Client
|
|
17
|
-
include Riffer::Helpers::Dependencies
|
|
18
|
-
|
|
19
8
|
# @rbs @client: untyped
|
|
20
9
|
|
|
21
10
|
#--
|
|
@@ -31,9 +20,8 @@ class Riffer::Mcp::Client
|
|
|
31
20
|
end
|
|
32
21
|
end
|
|
33
22
|
|
|
34
|
-
# Returns
|
|
35
|
-
#
|
|
36
|
-
#
|
|
23
|
+
# Returns tool definition hashes with +:name+, +:description+, and
|
|
24
|
+
# +:input_schema+ keys.
|
|
37
25
|
#--
|
|
38
26
|
#: () -> Array[Hash[Symbol, untyped]]
|
|
39
27
|
def tools_list
|
|
@@ -66,4 +54,11 @@ class Riffer::Mcp::Client
|
|
|
66
54
|
content = response.dig("result", "content") || []
|
|
67
55
|
content.filter_map { |item| item["text"] }.join
|
|
68
56
|
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
#: (String) -> true
|
|
61
|
+
def depends_on(gem_name)
|
|
62
|
+
Riffer::Helpers::Dependencies.depends_on(gem_name)
|
|
63
|
+
end
|
|
69
64
|
end
|
data/lib/riffer/mcp/manifest.rb
CHANGED
|
@@ -3,23 +3,26 @@
|
|
|
3
3
|
|
|
4
4
|
require "uri"
|
|
5
5
|
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# +name+ - String identifier used as the registration key and generated-agent identifier.
|
|
9
|
-
# +tags+ - Array[Symbol]; normalized to symbols at construction time.
|
|
10
|
-
# +endpoint+ - String HTTPS URL passed to the MCP transport.
|
|
11
|
-
# +discovery_headers+ - Hash or Proc; resolved once when building the discovery client for +tools/list+.
|
|
12
|
-
# +credentials_scope+ - Optional symbol hint: +:global+, +:tenant+, +:user+ — documents whether
|
|
13
|
-
# invocation credentials are expected to depend on tenant and/or user keys in +context+ (no ids stored).
|
|
14
|
-
# Apps may treat +:user+ as "user in tenant" and pass both keys in +context+.
|
|
15
|
-
#
|
|
6
|
+
# Holds the configuration for a single MCP server.
|
|
16
7
|
class Riffer::Mcp::Manifest
|
|
8
|
+
# Identifier used as the registration key and generated-agent identifier.
|
|
17
9
|
attr_reader :name #: String
|
|
10
|
+
|
|
11
|
+
# Tags for matching +use_mcp+.
|
|
18
12
|
attr_reader :tags #: Array[Symbol]
|
|
13
|
+
|
|
14
|
+
# HTTPS URL passed to the MCP transport.
|
|
19
15
|
attr_reader :endpoint #: String
|
|
16
|
+
|
|
17
|
+
# Headers (or a Proc) resolved once when building the discovery client.
|
|
20
18
|
attr_reader :discovery_headers #: (Hash[String, untyped] | ::Proc)?
|
|
19
|
+
|
|
20
|
+
# Optional hint (+:global+/+:tenant+/+:user+) for whether invocation
|
|
21
|
+
# credentials depend on tenant/user keys in +context+.
|
|
21
22
|
attr_reader :credentials_scope #: Symbol?
|
|
22
23
|
|
|
24
|
+
# Raises Riffer::ArgumentError unless +name+ is present and +endpoint+ is a
|
|
25
|
+
# valid HTTPS URL.
|
|
23
26
|
#--
|
|
24
27
|
#: (name: String, endpoint: String, ?tags: Array[untyped]?, ?discovery_headers: (Hash[String, untyped] | ::Proc)?, ?credentials_scope: (String | Symbol)?) -> void
|
|
25
28
|
def initialize(name:, endpoint:, tags: nil, discovery_headers: nil, credentials_scope: nil)
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
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
|
-
#
|
|
4
|
+
# Per-server state managed by Riffer::Mcp::Registry — discovers tools via
|
|
5
|
+
# +tools/list+ and generates tool classes when a server is registered.
|
|
9
6
|
class Riffer::Mcp::Registration
|
|
10
7
|
# @rbs @cancelled: bool
|
|
11
8
|
# @rbs @tools: Array[singleton(Riffer::Tool)]
|
|
@@ -51,12 +48,6 @@ class Riffer::Mcp::Registration
|
|
|
51
48
|
|
|
52
49
|
private
|
|
53
50
|
|
|
54
|
-
# Runs tool discovery using the configured Runner.
|
|
55
|
-
#
|
|
56
|
-
# With +Runner::Sequential+ (default) discovery blocks inline. With
|
|
57
|
-
# +Runner::Threaded+ discovery runs on a pool thread but +map+ still
|
|
58
|
-
# blocks the caller — useful for Rails connection-pool isolation.
|
|
59
|
-
#
|
|
60
51
|
#--
|
|
61
52
|
#: () -> void
|
|
62
53
|
def run_discovery
|
data/lib/riffer/mcp/registry.rb
CHANGED
|
@@ -1,67 +1,59 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Thread-safe global store for MCP server registrations.
|
|
5
|
-
#
|
|
6
|
-
# Keyed by manifest name. All public methods are mutex-guarded.
|
|
7
|
-
#
|
|
4
|
+
# Thread-safe global store for MCP server registrations, keyed by manifest name.
|
|
8
5
|
module Riffer::Mcp::Registry
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
# @rbs @mutex: Thread::Mutex
|
|
9
|
+
# @rbs @store: Hash[String, Riffer::Mcp::Registration]
|
|
11
10
|
|
|
12
11
|
@mutex = Mutex.new
|
|
13
12
|
@store = {} #: Hash[String, Riffer::Mcp::Registration]
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
#
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
manifest =
|
|
27
|
-
|
|
28
|
-
old = @mutex.synchronize do
|
|
29
|
-
previous = @store[manifest.name]
|
|
30
|
-
@store[manifest.name] = registration
|
|
31
|
-
previous
|
|
32
|
-
end
|
|
33
|
-
old&.retire!
|
|
34
|
-
registration
|
|
14
|
+
# Registers an MCP server and starts async tool discovery, replacing any
|
|
15
|
+
# existing registration with the same name.
|
|
16
|
+
#--
|
|
17
|
+
#: ((Hash[Symbol, untyped] | Riffer::Mcp::Manifest)) -> Riffer::Mcp::Registration
|
|
18
|
+
def register(manifest_or_hash)
|
|
19
|
+
# steep cannot verify that an untyped Hash splat supplies Manifest's
|
|
20
|
+
# required name:/endpoint: keywords; Manifest validates them at runtime.
|
|
21
|
+
manifest = manifest_or_hash.is_a?(Riffer::Mcp::Manifest) ? manifest_or_hash : Riffer::Mcp::Manifest.new(**manifest_or_hash) # steep:ignore InsufficientKeywordArguments
|
|
22
|
+
registration = Riffer::Mcp::Registration.new(manifest)
|
|
23
|
+
old = @mutex.synchronize do
|
|
24
|
+
previous = @store[manifest.name]
|
|
25
|
+
@store[manifest.name] = registration
|
|
26
|
+
previous
|
|
35
27
|
end
|
|
28
|
+
old&.retire!
|
|
29
|
+
registration
|
|
30
|
+
end
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
45
40
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
53
48
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@mutex.synchronize do
|
|
63
|
-
@store.values.select { |reg| (reg.manifest.tags & normalized).any? }
|
|
64
|
-
end
|
|
49
|
+
# Returns all registrations whose manifest tags intersect the given tags
|
|
50
|
+
# (normalized to symbols).
|
|
51
|
+
#--
|
|
52
|
+
#: (Array[Symbol]) -> Array[Riffer::Mcp::Registration]
|
|
53
|
+
def find_by_tags(tags)
|
|
54
|
+
normalized = tags.map(&:to_sym)
|
|
55
|
+
@mutex.synchronize do
|
|
56
|
+
@store.values.select { |reg| (reg.manifest.tags & normalized).any? }
|
|
65
57
|
end
|
|
66
58
|
end
|
|
67
59
|
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
# Searches available MCP tools by name or description.
|
|
5
|
+
class Riffer::Mcp::SearchTool < Riffer::Tool
|
|
6
|
+
IDENTIFIER = "mcp_search"
|
|
7
|
+
|
|
8
|
+
# Successful search response carrying the matched tool classes.
|
|
9
|
+
class Result < Riffer::Tools::Response
|
|
10
|
+
# Tool classes that matched the search query.
|
|
11
|
+
attr_reader :discovered_tools #: Array[singleton(Riffer::Tool)]
|
|
12
|
+
|
|
13
|
+
#--
|
|
14
|
+
#: (String, Array[singleton(Riffer::Tool)]) -> void
|
|
15
|
+
def initialize(content, discovered_tools)
|
|
16
|
+
super(content: content, success: true)
|
|
17
|
+
@discovered_tools = discovered_tools
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
identifier IDENTIFIER
|
|
22
|
+
description "Search for available MCP tools by name or description."
|
|
23
|
+
|
|
24
|
+
params do
|
|
25
|
+
required :query, String, description: "Non-empty substring to filter tools by name or description."
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Searches progressive MCP tools and returns a +Result+ with +discovered_tools+.
|
|
29
|
+
#--
|
|
30
|
+
#: (context: Riffer::Agent::Context?, query: String) -> Riffer::Tools::Response
|
|
31
|
+
def call(context:, query:)
|
|
32
|
+
return error("Provide a search query to find MCP tools by name or description.") if query.strip.empty?
|
|
33
|
+
|
|
34
|
+
tools = context&.mcp_progressive_tools || []
|
|
35
|
+
matches = filter(tools, query)
|
|
36
|
+
|
|
37
|
+
return text("No tools found matching '#{query}'.") if matches.empty?
|
|
38
|
+
|
|
39
|
+
names = matches.map(&:name).join(", ")
|
|
40
|
+
Result.new(
|
|
41
|
+
"Found #{matches.length} tool(s): #{names}. They are now available — call them directly.",
|
|
42
|
+
matches
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
#: (Array[singleton(Riffer::Tool)], String) -> Array[singleton(Riffer::Tool)]
|
|
49
|
+
def filter(tools, query)
|
|
50
|
+
q = query.downcase
|
|
51
|
+
tools.select { |t| t.name.downcase.include?(q) || t.description.to_s.downcase.include?(q) }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -2,33 +2,28 @@
|
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
4
|
# Generates anonymous Riffer::Tool subclasses from MCP tool definitions.
|
|
5
|
-
#
|
|
6
|
-
#
|
|
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
|
-
#
|
|
5
|
+
# Generated tools delegate +#call+ to the MCP client and skip Riffer's param
|
|
6
|
+
# validation — the MCP server validates inputs.
|
|
11
7
|
module Riffer::Mcp::ToolFactory
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# name
|
|
17
|
-
#
|
|
8
|
+
extend self
|
|
9
|
+
|
|
10
|
+
# Builds one Riffer::Tool subclass per tool definition, prefixing names with
|
|
11
|
+
# the manifest name to avoid cross-server collisions (e.g. +jira__search+);
|
|
12
|
+
# the server-side name stays on +.mcp_server_tool_name+.
|
|
18
13
|
#--
|
|
19
14
|
#: (String, Riffer::Mcp::Client, Array[Hash[Symbol, untyped]]) -> Array[singleton(Riffer::Tool)]
|
|
20
|
-
def
|
|
15
|
+
def build(manifest_name, client, tool_defs)
|
|
21
16
|
tool_defs.map { |td| build_tool_class(manifest_name, client, td) }
|
|
22
17
|
end
|
|
23
18
|
|
|
24
|
-
|
|
19
|
+
private
|
|
20
|
+
|
|
25
21
|
#: (String) -> String
|
|
26
|
-
def
|
|
22
|
+
def sanitize_name_component(str)
|
|
27
23
|
str.gsub(/[^a-zA-Z0-9_-]/, "_")
|
|
28
24
|
end
|
|
29
|
-
private_class_method :sanitize_name_component
|
|
30
25
|
|
|
31
|
-
|
|
26
|
+
def build_tool_class(manifest_name, client, td)
|
|
32
27
|
prefixed = "#{sanitize_name_component(manifest_name)}__#{sanitize_name_component(td[:name])}"
|
|
33
28
|
|
|
34
29
|
# steep cannot type the body of a dynamically created anonymous class:
|
|
@@ -39,7 +34,7 @@ module Riffer::Mcp::ToolFactory
|
|
|
39
34
|
@mcp_client = client
|
|
40
35
|
@mcp_server_tool_name = td[:name]
|
|
41
36
|
# Set @identifier directly so .identifier does not fall back to
|
|
42
|
-
#
|
|
37
|
+
# Riffer::Helpers::ClassNameConverter.convert(nil) on this anonymous class.
|
|
43
38
|
@identifier = prefixed
|
|
44
39
|
|
|
45
40
|
define_singleton_method(:name) { prefixed }
|
data/lib/riffer/mcp.rb
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# Tags are application-defined; see +docs/14_MCP.md+ (Tags section).
|
|
4
|
+
# Integration with Model Context Protocol (MCP) servers. Register servers
|
|
5
|
+
# globally; agents opt-in by tag via the +use_mcp+ DSL. Tags are
|
|
6
|
+
# application-defined; see +docs/14_MCP.md+.
|
|
8
7
|
#
|
|
9
8
|
# Riffer::Mcp.register(
|
|
10
9
|
# name: "github",
|
|
@@ -19,6 +18,8 @@
|
|
|
19
18
|
# end
|
|
20
19
|
#
|
|
21
20
|
module Riffer::Mcp
|
|
21
|
+
extend self
|
|
22
|
+
|
|
22
23
|
# Base error for all MCP-related failures.
|
|
23
24
|
class Error < Riffer::Error; end
|
|
24
25
|
|
|
@@ -26,32 +27,26 @@ module Riffer::Mcp
|
|
|
26
27
|
# after the server's tools were included for this run.
|
|
27
28
|
class CredentialsDeniedError < Error; end
|
|
28
29
|
|
|
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
|
-
#
|
|
30
|
+
# Registers an MCP server, blocking until tool discovery completes. Raises
|
|
31
|
+
# on discovery failure.
|
|
34
32
|
#--
|
|
35
33
|
#: ((Hash[Symbol, untyped] | Riffer::Mcp::Manifest)) -> Riffer::Mcp::Registration
|
|
36
|
-
def
|
|
34
|
+
def register(manifest_or_hash)
|
|
37
35
|
Registry.register(manifest_or_hash)
|
|
38
36
|
end
|
|
39
37
|
|
|
40
|
-
# Removes a registration by name.
|
|
41
|
-
#
|
|
42
|
-
# Subsequent agent runs will not see tools from this server.
|
|
43
|
-
#
|
|
38
|
+
# Removes a registration by name; subsequent agent runs won't see its tools.
|
|
44
39
|
#--
|
|
45
40
|
#: (String) -> void
|
|
46
|
-
def
|
|
41
|
+
def unregister(name)
|
|
47
42
|
Registry.unregister(name)
|
|
48
43
|
end
|
|
49
44
|
|
|
50
|
-
# Returns all current registrations keyed by name
|
|
45
|
+
# Returns all current registrations keyed by name.
|
|
51
46
|
#
|
|
52
47
|
#--
|
|
53
48
|
#: () -> Hash[String, Riffer::Mcp::Registration]
|
|
54
|
-
def
|
|
49
|
+
def registrations
|
|
55
50
|
Registry.registrations
|
|
56
51
|
end
|
|
57
52
|
end
|
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Represents an assistant (LLM) message in a conversation
|
|
5
|
-
#
|
|
6
|
-
# May include tool calls when the LLM requests tool execution.
|
|
7
|
-
#
|
|
8
|
-
# msg = Riffer::Messages::Assistant.new("Hello!")
|
|
9
|
-
# msg.role # => :assistant
|
|
10
|
-
# msg.content # => "Hello!"
|
|
11
|
-
# msg.tool_calls # => []
|
|
12
|
-
#
|
|
4
|
+
# Represents an assistant (LLM) message in a conversation; may include tool
|
|
5
|
+
# calls when the LLM requests tool execution.
|
|
13
6
|
class Riffer::Messages::Assistant < Riffer::Messages::Base
|
|
14
7
|
ToolCall = Struct.new(:call_id, :name, :arguments, keyword_init: true)
|
|
15
8
|
|