raix 0.7.3 → 0.8.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/.rubocop.yml +6 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +16 -0
- data/Gemfile +0 -5
- data/Gemfile.lock +8 -11
- data/README.md +44 -0
- data/lib/raix/mcp.rb +337 -0
- data/lib/raix/version.rb +1 -1
- data/lib/raix.rb +1 -0
- data/raix.gemspec +2 -1
- metadata +19 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '08737974243ed41d4db2406405a4d081e31972cf173cfc3b2859cb97eabf0848'
|
4
|
+
data.tar.gz: 0d78927f5a35fad586f300bff4d86ce773d7bb7cd9f6787320e1cff6297f1aed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c0eec6ef99a37cd8ba69dfec62c8be70c9a8f6ed0be78b24ed184bde364c78f5189ccac71e3196f56af4c85390844b044c30be1b69172fe4e0f3c584903ea7c0
|
7
|
+
data.tar.gz: 90f084213b11dd737b55ed89fd21e75cf23b4c0ef5ab0daca8652f974b9fdb0f40a68ee07ea2abcd0b6af715ae3edc684643034b7e7e3455f7d05fae9ee1e473
|
data/.rubocop.yml
CHANGED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.4.2
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,20 @@
|
|
1
|
+
## [0.8.0] - 2024-05-08
|
2
|
+
### Added
|
3
|
+
* **MCP integration (Experimental)** — new `Raix::MCP` concern and `mcp` DSL for declaring remote MCP servers.
|
4
|
+
* Automatically fetches `tools/list`, registers remote tools as OpenAI‑compatible function schemas, and defines proxy methods that forward `tools/call`.
|
5
|
+
* `ChatCompletion#tools` now returns remote MCP tools alongside local `function` declarations.
|
6
|
+
|
7
|
+
### Changed
|
8
|
+
* `lib/raix.rb` now requires `raix/mcp` so the concern is auto‑loaded.
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
* Internal transcript handling spec expectations updated.
|
12
|
+
|
13
|
+
### Specs
|
14
|
+
* Added `spec/raix/mcp_spec.rb` with comprehensive stubs for tools discovery & call flow.
|
15
|
+
|
1
16
|
## [0.7.3] - 2025-04-23
|
17
|
+
- commit function call and result to transcript in one operation for thread safety
|
2
18
|
|
3
19
|
## [0.7.2] - 2025-04-19
|
4
20
|
- adds support for `messages` parameter in `chat_completion` to override the transcript
|
data/Gemfile
CHANGED
@@ -5,11 +5,6 @@ source "https://rubygems.org"
|
|
5
5
|
# Specify your gem's dependencies in raix-rails.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
-
gem "activesupport", ">= 6.0"
|
9
|
-
gem "faraday-retry"
|
10
|
-
gem "open_router", "~> 0.3"
|
11
|
-
gem "ruby-openai", "~> 7.0"
|
12
|
-
|
13
8
|
group :development do
|
14
9
|
gem "dotenv", ">= 2"
|
15
10
|
gem "guard"
|
data/Gemfile.lock
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
raix (0.
|
4
|
+
raix (0.8.0)
|
5
5
|
activesupport (>= 6.0)
|
6
|
+
faraday-retry (~> 2.0)
|
6
7
|
open_router (~> 0.2)
|
7
|
-
ruby-openai (~> 7
|
8
|
+
ruby-openai (~> 7)
|
8
9
|
|
9
10
|
GEM
|
10
11
|
remote: https://rubygems.org/
|
@@ -43,10 +44,10 @@ GEM
|
|
43
44
|
multipart-post (~> 2)
|
44
45
|
faraday-net_http (3.1.0)
|
45
46
|
net-http
|
46
|
-
faraday-retry (2.
|
47
|
+
faraday-retry (2.3.1)
|
47
48
|
faraday (~> 2.0)
|
48
|
-
ffi (1.17.
|
49
|
-
ffi (1.17.
|
49
|
+
ffi (1.17.2-arm64-darwin)
|
50
|
+
ffi (1.17.2-x86_64-linux-gnu)
|
50
51
|
formatador (1.1.0)
|
51
52
|
guard (2.18.1)
|
52
53
|
formatador (>= 0.2.4)
|
@@ -84,9 +85,9 @@ GEM
|
|
84
85
|
net-http (0.4.1)
|
85
86
|
uri
|
86
87
|
netrc (0.11.0)
|
87
|
-
nokogiri (1.
|
88
|
+
nokogiri (1.18.8-arm64-darwin)
|
88
89
|
racc (~> 1.4)
|
89
|
-
nokogiri (1.
|
90
|
+
nokogiri (1.18.8-x86_64-linux-gnu)
|
90
91
|
racc (~> 1.4)
|
91
92
|
notiffany (0.1.3)
|
92
93
|
nenv (~> 0.1)
|
@@ -216,18 +217,14 @@ PLATFORMS
|
|
216
217
|
x86_64-linux
|
217
218
|
|
218
219
|
DEPENDENCIES
|
219
|
-
activesupport (>= 6.0)
|
220
220
|
dotenv (>= 2)
|
221
|
-
faraday-retry
|
222
221
|
guard
|
223
222
|
guard-rspec
|
224
|
-
open_router (~> 0.3)
|
225
223
|
pry (>= 0.14)
|
226
224
|
raix!
|
227
225
|
rake (~> 13.0)
|
228
226
|
rspec (~> 3.0)
|
229
227
|
rubocop (~> 1.21)
|
230
|
-
ruby-openai (~> 7.0)
|
231
228
|
solargraph-rails (~> 0.2.0.pre)
|
232
229
|
sorbet
|
233
230
|
tapioca
|
data/README.md
CHANGED
@@ -452,6 +452,50 @@ question.ask("Any question")
|
|
452
452
|
# => RuntimeError: Please define a yes and/or no block
|
453
453
|
```
|
454
454
|
|
455
|
+
## Model Context Protocol (Experimental)
|
456
|
+
|
457
|
+
The `Raix::MCP` module provides integration with the Model Context Protocol, allowing you to connect your Raix-powered application to remote MCP servers. This feature is currently **experimental**.
|
458
|
+
|
459
|
+
### Usage
|
460
|
+
|
461
|
+
Include the `Raix::MCP` module in your class and declare MCP servers using the `mcp` DSL:
|
462
|
+
|
463
|
+
```ruby
|
464
|
+
class McpConsumer
|
465
|
+
include Raix::ChatCompletion
|
466
|
+
include Raix::FunctionDispatch
|
467
|
+
include Raix::MCP
|
468
|
+
|
469
|
+
mcp "https://your-mcp-server.example.com/sse"
|
470
|
+
end
|
471
|
+
```
|
472
|
+
|
473
|
+
### Features
|
474
|
+
|
475
|
+
- Automatically fetches available tools from the remote MCP server using `tools/list`
|
476
|
+
- Registers remote tools as OpenAI-compatible function schemas
|
477
|
+
- Defines proxy methods that forward requests to the remote server via `tools/call`
|
478
|
+
- Seamlessly integrates with the existing `FunctionDispatch` workflow
|
479
|
+
- Handles transcript recording to maintain consistent conversation history
|
480
|
+
|
481
|
+
### Filtering Tools
|
482
|
+
|
483
|
+
You can filter which remote tools to include:
|
484
|
+
|
485
|
+
```ruby
|
486
|
+
class FilteredMcpConsumer
|
487
|
+
include Raix::ChatCompletion
|
488
|
+
include Raix::FunctionDispatch
|
489
|
+
include Raix::MCP
|
490
|
+
|
491
|
+
# Only include specific tools
|
492
|
+
mcp "https://server.example.com/sse", only: [:tool_one, :tool_two]
|
493
|
+
|
494
|
+
# Or exclude specific tools
|
495
|
+
mcp "https://server.example.com/sse", except: [:tool_to_exclude]
|
496
|
+
end
|
497
|
+
```
|
498
|
+
|
455
499
|
## Response Format (Experimental)
|
456
500
|
|
457
501
|
The `ResponseFormat` class provides a way to declare a JSON schema for the response format of an AI chat completion. It's particularly useful when you need structured responses from AI models, ensuring the output conforms to your application's requirements.
|
data/lib/raix/mcp.rb
ADDED
@@ -0,0 +1,337 @@
|
|
1
|
+
# Simple integration layer that lets Raix classes declare an MCP server
|
2
|
+
# with a single DSL call:
|
3
|
+
#
|
4
|
+
# mcp "https://my-server.example.com/sse"
|
5
|
+
#
|
6
|
+
# The concern fetches the remote server's tool list (via JSON‑RPC 2.0
|
7
|
+
# `tools/list`) and exposes each remote tool as if it were an inline
|
8
|
+
# `function` declared with Raix::FunctionDispatch. When the tool is
|
9
|
+
# invoked by the model, the generated instance method forwards the
|
10
|
+
# request to the remote server using `tools/call`, captures the result,
|
11
|
+
# and appends the appropriate messages to the transcript so that the
|
12
|
+
# conversation history stays consistent.
|
13
|
+
|
14
|
+
require "active_support/concern"
|
15
|
+
require "active_support/inflector"
|
16
|
+
require "securerandom"
|
17
|
+
require "faraday"
|
18
|
+
require "uri"
|
19
|
+
require "json"
|
20
|
+
|
21
|
+
module Raix
|
22
|
+
# Model Context Protocol integration for Raix
|
23
|
+
#
|
24
|
+
# Allows declaring MCP servers with a simple DSL that automatically:
|
25
|
+
# - Queries tools from the remote server
|
26
|
+
# - Exposes each tool as a function callable by LLMs
|
27
|
+
# - Handles transcript recording and response processing
|
28
|
+
module MCP
|
29
|
+
extend ActiveSupport::Concern
|
30
|
+
|
31
|
+
JSONRPC_VERSION = "2.0".freeze
|
32
|
+
PROTOCOL_VERSION = "2024-11-05".freeze # Current supported protocol version
|
33
|
+
CONNECTION_TIMEOUT = 10
|
34
|
+
OPEN_TIMEOUT = 30
|
35
|
+
|
36
|
+
class_methods do
|
37
|
+
# Declare an MCP server by URL.
|
38
|
+
#
|
39
|
+
# mcp "https://server.example.com/sse"
|
40
|
+
#
|
41
|
+
# This will automatically:
|
42
|
+
# • query `tools/list` on the server
|
43
|
+
# • register each remote tool with FunctionDispatch so that the
|
44
|
+
# OpenAI / OpenRouter request body includes its JSON‑Schema
|
45
|
+
# • define an instance method for each tool that forwards the
|
46
|
+
# call to the server and appends the proper messages to the
|
47
|
+
# transcript.
|
48
|
+
# NOTE TO SELF: NEVER MOCK SERVER RESPONSES! THIS MUST WORK WITH REAL SERVERS!
|
49
|
+
def mcp(url, only: nil, except: nil)
|
50
|
+
@mcp_servers ||= {}
|
51
|
+
|
52
|
+
return if @mcp_servers.key?(url) # avoid duplicate definitions
|
53
|
+
|
54
|
+
# Connect and initialize the SSE endpoint
|
55
|
+
|
56
|
+
result = Thread::Queue.new
|
57
|
+
Thread.new do
|
58
|
+
establish_sse_connection(url, result:)
|
59
|
+
end
|
60
|
+
tools = result.pop
|
61
|
+
|
62
|
+
if tools.empty?
|
63
|
+
puts "[MCP DEBUG] No tools found from MCP server at #{url}"
|
64
|
+
return nil
|
65
|
+
end
|
66
|
+
|
67
|
+
# 3. Register each tool so ChatCompletion#tools picks them up
|
68
|
+
# Apply filters
|
69
|
+
filtered_tools = if only.present?
|
70
|
+
only_symbols = Array(only).map(&:to_sym)
|
71
|
+
tools.select { |tool| only_symbols.include?(tool["name"].to_sym) }
|
72
|
+
elsif except.present?
|
73
|
+
except_symbols = Array(except).map(&:to_sym)
|
74
|
+
tools.reject { |tool| except_symbols.include?(tool["name"].to_sym) }
|
75
|
+
else
|
76
|
+
tools
|
77
|
+
end
|
78
|
+
|
79
|
+
# Ensure FunctionDispatch is included in the class
|
80
|
+
# Explicit include in the class context
|
81
|
+
include FunctionDispatch unless included_modules.include?(FunctionDispatch)
|
82
|
+
puts "[MCP DEBUG] FunctionDispatch included in #{name}"
|
83
|
+
|
84
|
+
filtered_tools.each do |tool|
|
85
|
+
remote_name = tool[:name]
|
86
|
+
# TODO: Revisit later whether this much context is needed in the function name
|
87
|
+
local_name = "#{url.parameterize.underscore}_#{remote_name}".gsub("https_", "").to_sym
|
88
|
+
|
89
|
+
description = tool["description"]
|
90
|
+
input_schema = tool["inputSchema"] || {}
|
91
|
+
|
92
|
+
# --- register with FunctionDispatch (adds to .functions)
|
93
|
+
function(local_name, description, **{}) # placeholder parameters replaced next
|
94
|
+
latest_definition = functions.last
|
95
|
+
latest_definition[:parameters] = input_schema.deep_symbolize_keys if input_schema.present?
|
96
|
+
|
97
|
+
# --- define an instance method that proxies to the server
|
98
|
+
define_method(local_name) do |**arguments|
|
99
|
+
arguments ||= {}
|
100
|
+
|
101
|
+
call_id = SecureRandom.uuid
|
102
|
+
result = Thread::Queue.new
|
103
|
+
Thread.new do
|
104
|
+
self.class.establish_sse_connection(url, name: remote_name, arguments:, result:)
|
105
|
+
end
|
106
|
+
|
107
|
+
content_item = result.pop
|
108
|
+
|
109
|
+
# Decide what to add to the transcript
|
110
|
+
content_text = if content_item.is_a?(Hash) && content_item["type"] == "text"
|
111
|
+
content_item["text"]
|
112
|
+
else
|
113
|
+
content_item.to_json
|
114
|
+
end
|
115
|
+
|
116
|
+
# Mirror FunctionDispatch transcript behaviour
|
117
|
+
transcript << [
|
118
|
+
{
|
119
|
+
role: "assistant",
|
120
|
+
content: nil,
|
121
|
+
tool_calls: [
|
122
|
+
{
|
123
|
+
id: call_id,
|
124
|
+
type: "function",
|
125
|
+
function: {
|
126
|
+
name: remote_name,
|
127
|
+
arguments: arguments.to_json
|
128
|
+
}
|
129
|
+
}
|
130
|
+
]
|
131
|
+
},
|
132
|
+
{
|
133
|
+
role: "tool",
|
134
|
+
tool_call_id: call_id,
|
135
|
+
name: remote_name,
|
136
|
+
content: content_text
|
137
|
+
}
|
138
|
+
]
|
139
|
+
|
140
|
+
# Continue the chat loop if requested (same semantics as FunctionDispatch)
|
141
|
+
chat_completion(**chat_completion_args) if loop
|
142
|
+
|
143
|
+
content_text
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Store the URL and tools for future use
|
148
|
+
@mcp_servers[url] = { tools: }
|
149
|
+
end
|
150
|
+
|
151
|
+
# Establishes an SSE connection to +url+ and returns the JSON‑RPC POST endpoint
|
152
|
+
# advertised by the server. The MCP specification allows two different event
|
153
|
+
# formats during initialization:
|
154
|
+
#
|
155
|
+
# 1. A generic JSON‑RPC *initialize* event (the behaviour previously
|
156
|
+
# implemented):
|
157
|
+
#
|
158
|
+
# event: message (implicit when no explicit event type is given)
|
159
|
+
# data: {"jsonrpc":"2.0","method":"initialize","params":{"endpoint_url":"https://…/rpc"}}
|
160
|
+
#
|
161
|
+
# 2. A dedicated *endpoint* event, as implemented by the reference
|
162
|
+
# TypeScript SDK and the public GitMCP server used in our test-suite:
|
163
|
+
#
|
164
|
+
# event: endpoint\n
|
165
|
+
# data: /rpc\n
|
166
|
+
#
|
167
|
+
# This method now supports **both** formats.
|
168
|
+
#
|
169
|
+
# It uses Net::HTTP directly rather than Faraday streaming because the latter
|
170
|
+
# does not consistently surface partial body reads across adapters. The
|
171
|
+
# implementation reads the response body incrementally, splitting on the
|
172
|
+
# SSE record delimiter (double newline) and processing each event until an
|
173
|
+
# endpoint is discovered (or a timeout / connection error occurs).
|
174
|
+
def establish_sse_connection(url, name: nil, arguments: {}, result: nil)
|
175
|
+
puts "[MCP DEBUG] Establishing MCP connection with URL: #{url}"
|
176
|
+
|
177
|
+
headers = {
|
178
|
+
"Accept" => "text/event-stream",
|
179
|
+
"Cache-Control" => "no-cache",
|
180
|
+
"Connection" => "keep-alive",
|
181
|
+
"MCP-Version" => PROTOCOL_VERSION
|
182
|
+
}
|
183
|
+
|
184
|
+
endpoint_url = nil
|
185
|
+
buffer = ""
|
186
|
+
|
187
|
+
connection = Faraday.new(url:) do |faraday|
|
188
|
+
faraday.options.timeout = CONNECTION_TIMEOUT
|
189
|
+
faraday.options.open_timeout = OPEN_TIMEOUT
|
190
|
+
end
|
191
|
+
|
192
|
+
connection.get do |req|
|
193
|
+
req.headers = headers
|
194
|
+
req.options.on_data = proc do |chunk, _size|
|
195
|
+
buffer << chunk
|
196
|
+
|
197
|
+
# Process complete SSE events (separated by a blank line)
|
198
|
+
while (idx = buffer.index("\n\n"))
|
199
|
+
event_text = buffer.slice!(0..idx + 1) # include delimiter
|
200
|
+
event_type, event_data = parse_sse_fields(event_text)
|
201
|
+
|
202
|
+
case event_type
|
203
|
+
when "endpoint"
|
204
|
+
# event data is expected to be a plain string with the endpoint
|
205
|
+
puts "[MCP DEBUG] Found endpoint event: #{event_data}"
|
206
|
+
endpoint_url = build_absolute_url(url, event_data)
|
207
|
+
initialize_mcp_connection(connection, endpoint_url)
|
208
|
+
when "message"
|
209
|
+
puts "[MCP DEBUG] Received message: #{event_data}"
|
210
|
+
dispatch_event(event_data, connection, endpoint_url, name, arguments, result)
|
211
|
+
else
|
212
|
+
puts "[MCP DEBUG] Unexpected event type: #{event_type} with data: #{event_data}"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Parses an SSE *event block* (text up to the blank line delimiter) and
|
220
|
+
# returns `[event_type, data]` where *event_type* defaults to "message" when
|
221
|
+
# no explicit `event:` field is present. The *data* combines all `data:`
|
222
|
+
# lines separated by newlines, as per the SSE specification.
|
223
|
+
def parse_sse_fields(event_text)
|
224
|
+
event_type = "message"
|
225
|
+
data_lines = []
|
226
|
+
|
227
|
+
event_text.each_line do |line|
|
228
|
+
case line
|
229
|
+
when /^event:\s*(.+)$/
|
230
|
+
event_type = Regexp.last_match(1).strip
|
231
|
+
when /^data:\s*(.*)$/
|
232
|
+
data_lines << Regexp.last_match(1)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
[event_type, data_lines.join("\n").strip]
|
237
|
+
end
|
238
|
+
|
239
|
+
# Builds an absolute URL for +candidate+ relative to +base+.
|
240
|
+
# If +candidate+ is already absolute, it is returned unchanged.
|
241
|
+
def build_absolute_url(base, candidate)
|
242
|
+
uri = URI.parse(candidate)
|
243
|
+
return candidate if uri.absolute?
|
244
|
+
|
245
|
+
URI.join(base, candidate).to_s
|
246
|
+
rescue URI::InvalidURIError
|
247
|
+
candidate # fall back to original string
|
248
|
+
end
|
249
|
+
|
250
|
+
def initialize_mcp_connection(connection, endpoint_url)
|
251
|
+
puts "[MCP DEBUG] Initializing MCP connection with URL: #{endpoint_url}"
|
252
|
+
connection.post(endpoint_url) do |req|
|
253
|
+
req.headers["Content-Type"] = "application/json"
|
254
|
+
req.body = {
|
255
|
+
jsonrpc: JSONRPC_VERSION,
|
256
|
+
id: SecureRandom.uuid,
|
257
|
+
method: "initialize",
|
258
|
+
params: {
|
259
|
+
protocolVersion: PROTOCOL_VERSION,
|
260
|
+
capabilities: {
|
261
|
+
roots: {
|
262
|
+
listChanged: true
|
263
|
+
},
|
264
|
+
sampling: {}
|
265
|
+
},
|
266
|
+
clientInfo: {
|
267
|
+
name: "Raix",
|
268
|
+
version: Raix::VERSION
|
269
|
+
}
|
270
|
+
}
|
271
|
+
}.to_json
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def dispatch_event(event_data, connection, endpoint_url, name, arguments, result)
|
276
|
+
event_data = JSON.parse(event_data, symbolize_names: true)
|
277
|
+
case event_data
|
278
|
+
in { result: { capabilities: { tools: { listChanged: true } } } }
|
279
|
+
puts "[MCP DEBUG] Received listChanged event"
|
280
|
+
acknowledge_event(connection, endpoint_url)
|
281
|
+
fetch_mcp_tools(connection, endpoint_url)
|
282
|
+
in { result: { tools: } }
|
283
|
+
puts "[MCP DEBUG] Received tools event: #{tools}"
|
284
|
+
if name.present?
|
285
|
+
puts "[MCP DEBUG] Calling function: #{name} with params: #{arguments.inspect}"
|
286
|
+
remote_dispatch(connection, endpoint_url, name, arguments)
|
287
|
+
else
|
288
|
+
result << tools # will unblock the pop on the main thread
|
289
|
+
connection.close
|
290
|
+
end
|
291
|
+
in { result: { content: } }
|
292
|
+
puts "[MCP DEBUG] Received content event: #{content}"
|
293
|
+
result << content # will unblock the pop on the main thread
|
294
|
+
connection.close
|
295
|
+
else
|
296
|
+
puts "[MCP DEBUG] Received unexpected event: #{event_data}"
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def remote_dispatch(connection, endpoint_url, name, arguments)
|
301
|
+
connection.post(endpoint_url) do |req|
|
302
|
+
req.headers["Content-Type"] = "application/json"
|
303
|
+
req.body = {
|
304
|
+
jsonrpc: JSONRPC_VERSION,
|
305
|
+
id: SecureRandom.uuid,
|
306
|
+
method: "tools/call",
|
307
|
+
params: { name:, arguments: }
|
308
|
+
}.to_json
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def acknowledge_event(connection, endpoint_url)
|
313
|
+
puts "[MCP DEBUG] Acknowledging event"
|
314
|
+
connection.post(endpoint_url) do |req|
|
315
|
+
req.headers["Content-Type"] = "application/json"
|
316
|
+
req.body = {
|
317
|
+
jsonrpc: JSONRPC_VERSION,
|
318
|
+
method: "notifications/initialized"
|
319
|
+
}.to_json
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
def fetch_mcp_tools(connection, endpoint_url)
|
324
|
+
puts "[MCP DEBUG] Fetching tools"
|
325
|
+
connection.post(endpoint_url) do |req|
|
326
|
+
req.headers["Content-Type"] = "application/json"
|
327
|
+
req.body = {
|
328
|
+
jsonrpc: JSONRPC_VERSION,
|
329
|
+
id: SecureRandom.uuid,
|
330
|
+
method: "tools/list",
|
331
|
+
params: {}
|
332
|
+
}.to_json
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
data/lib/raix/version.rb
CHANGED
data/lib/raix.rb
CHANGED
@@ -6,6 +6,7 @@ require_relative "raix/function_dispatch"
|
|
6
6
|
require_relative "raix/prompt_declarations"
|
7
7
|
require_relative "raix/predicate"
|
8
8
|
require_relative "raix/response_format"
|
9
|
+
require_relative "raix/mcp"
|
9
10
|
|
10
11
|
# The Raix module provides configuration options for the Raix gem.
|
11
12
|
module Raix
|
data/raix.gemspec
CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.require_paths = ["lib"]
|
30
30
|
|
31
31
|
spec.add_dependency "activesupport", ">= 6.0"
|
32
|
+
spec.add_dependency "faraday-retry", "~> 2.0"
|
32
33
|
spec.add_dependency "open_router", "~> 0.2"
|
33
|
-
spec.add_dependency "ruby-openai", "~> 7
|
34
|
+
spec.add_dependency "ruby-openai", "~> 7"
|
34
35
|
end
|
metadata
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: raix
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Obie Fernandez
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
10
|
date: 2025-04-23 00:00:00.000000000 Z
|
@@ -24,6 +23,20 @@ dependencies:
|
|
24
23
|
- - ">="
|
25
24
|
- !ruby/object:Gem::Version
|
26
25
|
version: '6.0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: faraday-retry
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '2.0'
|
27
40
|
- !ruby/object:Gem::Dependency
|
28
41
|
name: open_router
|
29
42
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,15 +57,14 @@ dependencies:
|
|
44
57
|
requirements:
|
45
58
|
- - "~>"
|
46
59
|
- !ruby/object:Gem::Version
|
47
|
-
version: '7
|
60
|
+
version: '7'
|
48
61
|
type: :runtime
|
49
62
|
prerelease: false
|
50
63
|
version_requirements: !ruby/object:Gem::Requirement
|
51
64
|
requirements:
|
52
65
|
- - "~>"
|
53
66
|
- !ruby/object:Gem::Version
|
54
|
-
version: '7
|
55
|
-
description:
|
67
|
+
version: '7'
|
56
68
|
email:
|
57
69
|
- obiefernandez@gmail.com
|
58
70
|
executables: []
|
@@ -74,6 +86,7 @@ files:
|
|
74
86
|
- lib/raix.rb
|
75
87
|
- lib/raix/chat_completion.rb
|
76
88
|
- lib/raix/function_dispatch.rb
|
89
|
+
- lib/raix/mcp.rb
|
77
90
|
- lib/raix/message_adapters/base.rb
|
78
91
|
- lib/raix/predicate.rb
|
79
92
|
- lib/raix/prompt_declarations.rb
|
@@ -88,7 +101,6 @@ metadata:
|
|
88
101
|
homepage_uri: https://github.com/OlympiaAI/raix
|
89
102
|
source_code_uri: https://github.com/OlympiaAI/raix
|
90
103
|
changelog_uri: https://github.com/OlympiaAI/raix/blob/main/CHANGELOG.md
|
91
|
-
post_install_message:
|
92
104
|
rdoc_options: []
|
93
105
|
require_paths:
|
94
106
|
- lib
|
@@ -103,8 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
115
|
- !ruby/object:Gem::Version
|
104
116
|
version: '0'
|
105
117
|
requirements: []
|
106
|
-
rubygems_version: 3.
|
107
|
-
signing_key:
|
118
|
+
rubygems_version: 3.6.2
|
108
119
|
specification_version: 4
|
109
120
|
summary: Ruby AI eXtensions
|
110
121
|
test_files: []
|