ruby_llm-mcp 0.3.0 → 0.4.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/README.md +121 -2
- data/lib/ruby_llm/mcp/capabilities.rb +22 -2
- data/lib/ruby_llm/mcp/client.rb +104 -136
- data/lib/ruby_llm/mcp/configuration.rb +66 -0
- data/lib/ruby_llm/mcp/coordinator.rb +276 -0
- data/lib/ruby_llm/mcp/error.rb +34 -0
- data/lib/ruby_llm/mcp/errors.rb +38 -3
- data/lib/ruby_llm/mcp/logging.rb +16 -0
- data/lib/ruby_llm/mcp/parameter.rb +5 -2
- data/lib/ruby_llm/mcp/progress.rb +33 -0
- data/lib/ruby_llm/mcp/prompt.rb +20 -13
- data/lib/ruby_llm/mcp/providers/anthropic/complex_parameter_support.rb +7 -3
- data/lib/ruby_llm/mcp/providers/gemini/complex_parameter_support.rb +8 -4
- data/lib/ruby_llm/mcp/providers/openai/complex_parameter_support.rb +8 -4
- data/lib/ruby_llm/mcp/requests/base.rb +3 -3
- data/lib/ruby_llm/mcp/requests/cancelled_notification.rb +32 -0
- data/lib/ruby_llm/mcp/requests/completion_prompt.rb +3 -3
- data/lib/ruby_llm/mcp/requests/completion_resource.rb +3 -3
- data/lib/ruby_llm/mcp/requests/initialization.rb +24 -18
- data/lib/ruby_llm/mcp/requests/initialize_notification.rb +20 -0
- data/lib/ruby_llm/mcp/requests/logging_set_level.rb +28 -0
- data/lib/ruby_llm/mcp/requests/meta.rb +30 -0
- data/lib/ruby_llm/mcp/requests/ping.rb +20 -0
- data/lib/ruby_llm/mcp/requests/ping_response.rb +28 -0
- data/lib/ruby_llm/mcp/requests/prompt_call.rb +3 -3
- data/lib/ruby_llm/mcp/requests/prompt_list.rb +1 -1
- data/lib/ruby_llm/mcp/requests/resource_list.rb +1 -1
- data/lib/ruby_llm/mcp/requests/resource_read.rb +4 -4
- data/lib/ruby_llm/mcp/requests/resource_template_list.rb +1 -1
- data/lib/ruby_llm/mcp/requests/resources_subscribe.rb +30 -0
- data/lib/ruby_llm/mcp/requests/tool_call.rb +6 -3
- data/lib/ruby_llm/mcp/requests/tool_list.rb +17 -11
- data/lib/ruby_llm/mcp/resource.rb +28 -7
- data/lib/ruby_llm/mcp/resource_template.rb +17 -12
- data/lib/ruby_llm/mcp/result.rb +90 -0
- data/lib/ruby_llm/mcp/tool.rb +36 -10
- data/lib/ruby_llm/mcp/transport/sse.rb +82 -75
- data/lib/ruby_llm/mcp/transport/stdio.rb +33 -17
- data/lib/ruby_llm/mcp/transport/streamable_http.rb +647 -0
- data/lib/ruby_llm/mcp/version.rb +1 -1
- data/lib/ruby_llm/mcp.rb +18 -0
- data/lib/tasks/release.rake +23 -0
- metadata +22 -51
- data/lib/ruby_llm/mcp/requests/notification.rb +0 -14
- data/lib/ruby_llm/mcp/transport/streamable.rb +0 -299
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2dd0be50f01aa4fd126828c9c4f81d2e5a7b3cb35f4b32a4ed8e3e4deb731202
|
4
|
+
data.tar.gz: d6efbfbb3345544da2e201968ff26ff1e238a72efc95e7eb5cc2cb77adaaaf94
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 769aa222a4f5afd5dc124f1fa1bb62771fd2d2dd5d60211fa3f86e664aa030811600f7ee448ec7607a7d244d2d6f961a9ded088f3db7eaa611fff8041528b6c1
|
7
|
+
data.tar.gz: d3a494430dcce6c0ee9246f6378d682771458c0009779c3e7f34aba43309b2ba6b72722ddbbc9e8d2b84061358cc8375f5fedd19c76a187795fce5adfd49338b
|
data/README.md
CHANGED
@@ -103,13 +103,39 @@ response = chat.ask("Can you help me search for recent files in my project?")
|
|
103
103
|
puts response
|
104
104
|
```
|
105
105
|
|
106
|
+
### Human in the Loop
|
107
|
+
|
108
|
+
You can use the `on_human_in_the_loop` callback to allow the human to intervene in the tool call. This is useful for tools that require human input or programic input to verify if the tool should be executed.
|
109
|
+
|
110
|
+
For tool calls that have access to do important operations, there SHOULD always be a human in the loop with the ability to deny tool invocations.
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
client.on_human_in_the_loop do |name, params|
|
114
|
+
name == "add" && params[:a] == 1 && params[:b] == 2
|
115
|
+
end
|
116
|
+
|
117
|
+
tool = client.tool("add")
|
118
|
+
result = tool.execute(a: 1, b: 2)
|
119
|
+
puts result # 3
|
120
|
+
|
121
|
+
# If the human in the loop returns false, the tool call will be cancelled
|
122
|
+
result = tool.execute(a: 2, b: 2)
|
123
|
+
puts result # Tool execution error: Tool call was cancelled by the client
|
124
|
+
```
|
125
|
+
|
126
|
+
tool = client.tool("add")
|
127
|
+
result = tool.execute(a: 1, b: 2)
|
128
|
+
puts result
|
129
|
+
|
130
|
+
````
|
131
|
+
|
106
132
|
### Support Complex Parameters
|
107
133
|
|
108
134
|
If you want to support complex parameters, like an array of objects it currently requires a patch to RubyLLM itself. This is planned to be temporary until the RubyLLM is updated.
|
109
135
|
|
110
136
|
```ruby
|
111
137
|
RubyLLM::MCP.support_complex_parameters!
|
112
|
-
|
138
|
+
````
|
113
139
|
|
114
140
|
### Streaming Responses with Tool Calls
|
115
141
|
|
@@ -341,6 +367,14 @@ client.restart!
|
|
341
367
|
client.stop
|
342
368
|
```
|
343
369
|
|
370
|
+
### Ping
|
371
|
+
|
372
|
+
You can ping the MCP server to check if it is alive:
|
373
|
+
|
374
|
+
```ruby
|
375
|
+
client.ping # => true or false
|
376
|
+
```
|
377
|
+
|
344
378
|
## Refreshing Cached Data
|
345
379
|
|
346
380
|
The client caches tools, resources, prompts, and resource templates list calls are cached to reduce round trips back to the MCP server. You can refresh this cache:
|
@@ -363,6 +397,71 @@ prompt = client.prompt("daily_greeting", refresh: true)
|
|
363
397
|
template = client.resource_template("user_logs", refresh: true)
|
364
398
|
```
|
365
399
|
|
400
|
+
## Notifications
|
401
|
+
|
402
|
+
MCPs can produce notifications that happen in an async nature outside normal calls to the MCP server.
|
403
|
+
|
404
|
+
### Subscribing to a Resource Update
|
405
|
+
|
406
|
+
By default, the client will look for any resource cha to resource updates and refresh the resource content when it changes.
|
407
|
+
|
408
|
+
### Logging Notifications
|
409
|
+
|
410
|
+
MCPs can produce logging notifications for long-running tool operations. Logging notifications allow tools to send real-time updates about their execution status.
|
411
|
+
|
412
|
+
```ruby
|
413
|
+
client.on_logging do |logging|
|
414
|
+
puts "Logging: #{logging.level} - #{logging.message}"
|
415
|
+
end
|
416
|
+
|
417
|
+
# Execute a tool that supports logging notifications
|
418
|
+
tool = client.tool("long_running_operation")
|
419
|
+
result = tool.execute(operation: "data_processing")
|
420
|
+
|
421
|
+
# Logging: info - Processing data...
|
422
|
+
# Logging: info - Processing data...
|
423
|
+
# Logging: warning - Something went wrong but not major...
|
424
|
+
```
|
425
|
+
|
426
|
+
Different levels of logging are supported:
|
427
|
+
|
428
|
+
```ruby
|
429
|
+
client.on_logging(RubyLLM::MCP::Logging::WARNING) do |logging|
|
430
|
+
puts "Logging: #{logging.level} - #{logging.message}"
|
431
|
+
end
|
432
|
+
|
433
|
+
# Execute a tool that supports logging notifications
|
434
|
+
tool = client.tool("long_running_operation")
|
435
|
+
result = tool.execute(operation: "data_processing")
|
436
|
+
|
437
|
+
# Logging: warning - Something went wrong but not major...
|
438
|
+
```
|
439
|
+
|
440
|
+
### Progress Notifications
|
441
|
+
|
442
|
+
MCPs can produce progress notifications for long-running tool operations. Progress notifications allow tools to send real-time updates about their execution status.
|
443
|
+
|
444
|
+
**Note:** that we only support progress notifications for tool calls today.
|
445
|
+
|
446
|
+
```ruby
|
447
|
+
# Set up progress tracking
|
448
|
+
client.on_progress do |progress|
|
449
|
+
puts "Progress: #{progress.progress}% - #{progress.message}"
|
450
|
+
end
|
451
|
+
|
452
|
+
# Execute a tool that supports progress notifications
|
453
|
+
tool = client.tool("long_running_operation")
|
454
|
+
result = tool.execute(operation: "data_processing")
|
455
|
+
|
456
|
+
# Progress 25% - Processing data...
|
457
|
+
# Progress 50% - Processing data...
|
458
|
+
# Progress 75% - Processing data...
|
459
|
+
# Progress 100% - Processing data...
|
460
|
+
puts result
|
461
|
+
|
462
|
+
# Result: { status: "success", data: "Processed data" }
|
463
|
+
```
|
464
|
+
|
366
465
|
## Transport Types
|
367
466
|
|
368
467
|
### SSE (Server-Sent Events)
|
@@ -411,7 +510,27 @@ client = RubyLLM::MCP.client(
|
|
411
510
|
)
|
412
511
|
```
|
413
512
|
|
414
|
-
## Configuration Options
|
513
|
+
## RubyLLM::MCP and Client Configuration Options
|
514
|
+
|
515
|
+
MCP comes with some common configuration options that can be set on the client.
|
516
|
+
|
517
|
+
```ruby
|
518
|
+
RubyLLM::MCP.configure do |config|
|
519
|
+
# Set the progress handler
|
520
|
+
config.support_complex_parameters!
|
521
|
+
|
522
|
+
# Set parameters on the built in logger
|
523
|
+
config.log_file = $stdout
|
524
|
+
config.log_level = Logger::ERROR
|
525
|
+
|
526
|
+
# Or add a custom logger
|
527
|
+
config.logger = Logger.new(STDOUT)
|
528
|
+
end
|
529
|
+
```
|
530
|
+
|
531
|
+
### MCP Client Options
|
532
|
+
|
533
|
+
MCP client options are set on the client itself.
|
415
534
|
|
416
535
|
- `name`: A unique identifier for your MCP client
|
417
536
|
- `transport_type`: Either `:sse`, `:streamable`, or `:stdio`
|
@@ -9,7 +9,11 @@ module RubyLLM
|
|
9
9
|
@capabilities = capabilities
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
12
|
+
def resources_list?
|
13
|
+
!@capabilities["resources"].nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
def resources_list_changes?
|
13
17
|
@capabilities.dig("resources", "listChanged") || false
|
14
18
|
end
|
15
19
|
|
@@ -17,13 +21,29 @@ module RubyLLM
|
|
17
21
|
@capabilities.dig("resources", "subscribe") || false
|
18
22
|
end
|
19
23
|
|
20
|
-
def
|
24
|
+
def tools_list?
|
25
|
+
!@capabilities["tools"].nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
def tools_list_changes?
|
21
29
|
@capabilities.dig("tools", "listChanged") || false
|
22
30
|
end
|
23
31
|
|
32
|
+
def prompt_list?
|
33
|
+
!@capabilities["prompts"].nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
def prompt_list_changes?
|
37
|
+
@capabilities.dig("prompts", "listChanged") || false
|
38
|
+
end
|
39
|
+
|
24
40
|
def completion?
|
25
41
|
!@capabilities["completions"].nil?
|
26
42
|
end
|
43
|
+
|
44
|
+
def logging?
|
45
|
+
!@capabilities["logging"].nil?
|
46
|
+
end
|
27
47
|
end
|
28
48
|
end
|
29
49
|
end
|
data/lib/ruby_llm/mcp/client.rb
CHANGED
@@ -1,223 +1,191 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "forwardable"
|
4
|
+
|
3
5
|
module RubyLLM
|
4
6
|
module MCP
|
5
7
|
class Client
|
6
|
-
|
7
|
-
PV_2024_11_05 = "2024-11-05"
|
8
|
+
extend Forwardable
|
8
9
|
|
9
|
-
attr_reader :name, :config, :transport_type, :
|
10
|
-
:capabilities
|
10
|
+
attr_reader :name, :config, :transport_type, :request_timeout, :log_level, :on
|
11
11
|
|
12
|
-
def initialize(name:, transport_type:, start: true, request_timeout:
|
12
|
+
def initialize(name:, transport_type:, start: true, request_timeout: MCP.config.request_timeout, config: {})
|
13
13
|
@name = name
|
14
|
-
@config = config
|
15
|
-
@protocol_version = PROTOCOL_VERSION
|
16
|
-
@headers = config[:headers] || {}
|
17
|
-
|
14
|
+
@config = config.merge(request_timeout: request_timeout)
|
18
15
|
@transport_type = transport_type.to_sym
|
19
|
-
@
|
16
|
+
@request_timeout = request_timeout
|
20
17
|
|
21
|
-
@
|
18
|
+
@coordinator = setup_coordinator
|
22
19
|
|
23
|
-
@
|
24
|
-
@
|
20
|
+
@on = {}
|
21
|
+
@tools = {}
|
22
|
+
@resources = {}
|
23
|
+
@resource_templates = {}
|
24
|
+
@prompts = {}
|
25
25
|
|
26
|
-
|
27
|
-
self.start
|
28
|
-
end
|
29
|
-
end
|
26
|
+
@log_level = nil
|
30
27
|
|
31
|
-
|
32
|
-
@transport.request(body, **options)
|
28
|
+
@coordinator.start_transport if start
|
33
29
|
end
|
34
30
|
|
35
|
-
|
36
|
-
case @transport_type
|
37
|
-
when :sse
|
38
|
-
@transport = RubyLLM::MCP::Transport::SSE.new(@config[:url], request_timeout: @request_timeout,
|
39
|
-
headers: @headers)
|
40
|
-
when :stdio
|
41
|
-
@transport = RubyLLM::MCP::Transport::Stdio.new(@config[:command], request_timeout: @request_timeout,
|
42
|
-
args: @config[:args], env: @config[:env])
|
43
|
-
when :streamable
|
44
|
-
@transport = RubyLLM::MCP::Transport::Streamable.new(@config[:url], request_timeout: @request_timeout,
|
45
|
-
headers: @headers)
|
46
|
-
else
|
47
|
-
raise "Invalid transport type: #{transport_type}"
|
48
|
-
end
|
31
|
+
def_delegators :@coordinator, :alive?, :capabilities, :ping
|
49
32
|
|
50
|
-
|
51
|
-
@
|
52
|
-
notification_request
|
33
|
+
def start
|
34
|
+
@coordinator.start_transport
|
53
35
|
end
|
54
36
|
|
55
37
|
def stop
|
56
|
-
@
|
57
|
-
@transport = nil
|
38
|
+
@coordinator.stop_transport
|
58
39
|
end
|
59
40
|
|
60
41
|
def restart!
|
61
|
-
|
62
|
-
start
|
63
|
-
end
|
64
|
-
|
65
|
-
def alive?
|
66
|
-
!!@transport&.alive?
|
42
|
+
@coordinator.restart_transport
|
67
43
|
end
|
68
44
|
|
69
45
|
def tools(refresh: false)
|
70
|
-
|
71
|
-
|
46
|
+
return [] unless capabilities.tools_list?
|
47
|
+
|
48
|
+
fetch(:tools, refresh) do
|
49
|
+
tools = @coordinator.tool_list
|
50
|
+
build_map(tools, MCP::Tool)
|
51
|
+
end
|
52
|
+
|
72
53
|
@tools.values
|
73
54
|
end
|
74
55
|
|
75
56
|
def tool(name, refresh: false)
|
76
|
-
|
77
|
-
@tools ||= fetch_and_create_tools
|
57
|
+
tools(refresh: refresh)
|
78
58
|
|
79
59
|
@tools[name]
|
80
60
|
end
|
81
61
|
|
62
|
+
def reset_tools!
|
63
|
+
@tools = {}
|
64
|
+
end
|
65
|
+
|
82
66
|
def resources(refresh: false)
|
83
|
-
|
84
|
-
|
67
|
+
return [] unless capabilities.resources_list?
|
68
|
+
|
69
|
+
fetch(:resources, refresh) do
|
70
|
+
resources = @coordinator.resource_list
|
71
|
+
build_map(resources, MCP::Resource)
|
72
|
+
end
|
73
|
+
|
85
74
|
@resources.values
|
86
75
|
end
|
87
76
|
|
88
77
|
def resource(name, refresh: false)
|
89
|
-
|
90
|
-
@resources ||= fetch_and_create_resources
|
78
|
+
resources(refresh: refresh)
|
91
79
|
|
92
80
|
@resources[name]
|
93
81
|
end
|
94
82
|
|
83
|
+
def reset_resources!
|
84
|
+
@resources = {}
|
85
|
+
end
|
86
|
+
|
95
87
|
def resource_templates(refresh: false)
|
96
|
-
|
97
|
-
|
88
|
+
return [] unless capabilities.resources_list?
|
89
|
+
|
90
|
+
fetch(:resource_templates, refresh) do
|
91
|
+
resource_templates = @coordinator.resource_template_list
|
92
|
+
build_map(resource_templates, MCP::ResourceTemplate)
|
93
|
+
end
|
94
|
+
|
98
95
|
@resource_templates.values
|
99
96
|
end
|
100
97
|
|
101
98
|
def resource_template(name, refresh: false)
|
102
|
-
|
103
|
-
@resource_templates ||= fetch_and_create_resource_templates
|
99
|
+
resource_templates(refresh: refresh)
|
104
100
|
|
105
101
|
@resource_templates[name]
|
106
102
|
end
|
107
103
|
|
108
|
-
def
|
109
|
-
@
|
110
|
-
@prompts ||= fetch_and_create_prompts
|
111
|
-
@prompts.values
|
104
|
+
def reset_resource_templates!
|
105
|
+
@resource_templates = {}
|
112
106
|
end
|
113
107
|
|
114
|
-
def
|
115
|
-
|
116
|
-
@prompts ||= fetch_and_create_prompts
|
117
|
-
|
118
|
-
@prompts[name]
|
119
|
-
end
|
108
|
+
def prompts(refresh: false)
|
109
|
+
return [] unless capabilities.prompt_list?
|
120
110
|
|
121
|
-
|
122
|
-
|
123
|
-
|
111
|
+
fetch(:prompts, refresh) do
|
112
|
+
prompts = @coordinator.prompt_list
|
113
|
+
build_map(prompts, MCP::Prompt)
|
114
|
+
end
|
124
115
|
|
125
|
-
|
126
|
-
RubyLLM::MCP::Requests::ResourceRead.new(self, **args).call
|
116
|
+
@prompts.values
|
127
117
|
end
|
128
118
|
|
129
|
-
def
|
130
|
-
|
131
|
-
end
|
119
|
+
def prompt(name, refresh: false)
|
120
|
+
prompts(refresh: refresh)
|
132
121
|
|
133
|
-
|
134
|
-
RubyLLM::MCP::Requests::CompletionPrompt.new(self, **args).call
|
122
|
+
@prompts[name]
|
135
123
|
end
|
136
124
|
|
137
|
-
def
|
138
|
-
|
125
|
+
def reset_prompts!
|
126
|
+
@prompts = {}
|
139
127
|
end
|
140
128
|
|
141
|
-
|
142
|
-
|
143
|
-
def initialize_request
|
144
|
-
RubyLLM::MCP::Requests::Initialization.new(self).call
|
129
|
+
def tracking_progress?
|
130
|
+
@on.key?(:progress) && !@on[:progress].nil?
|
145
131
|
end
|
146
132
|
|
147
|
-
def
|
148
|
-
|
133
|
+
def on_progress(&block)
|
134
|
+
@on[:progress] = block
|
135
|
+
self
|
149
136
|
end
|
150
137
|
|
151
|
-
def
|
152
|
-
|
138
|
+
def human_in_the_loop?
|
139
|
+
@on.key?(:human_in_the_loop) && !@on[:human_in_the_loop].nil?
|
153
140
|
end
|
154
141
|
|
155
|
-
def
|
156
|
-
|
142
|
+
def on_human_in_the_loop(&block)
|
143
|
+
@on[:human_in_the_loop] = block
|
144
|
+
self
|
157
145
|
end
|
158
146
|
|
159
|
-
def
|
160
|
-
|
147
|
+
def logging_handler_enabled?
|
148
|
+
@on.key?(:logging) && !@on[:logging].nil?
|
161
149
|
end
|
162
150
|
|
163
|
-
def
|
164
|
-
|
151
|
+
def logging_enabled?
|
152
|
+
!@log_level.nil?
|
165
153
|
end
|
166
154
|
|
167
|
-
def
|
168
|
-
|
169
|
-
tools_response = tools_response["result"]["tools"]
|
170
|
-
|
171
|
-
tools = {}
|
172
|
-
tools_response.each do |tool|
|
173
|
-
new_tool = RubyLLM::MCP::Tool.new(self, tool)
|
174
|
-
tools[new_tool.name] = new_tool
|
175
|
-
end
|
155
|
+
def on_logging(level: Logging::WARNING, logger: nil, &block)
|
156
|
+
@coordinator.set_logging(level: level)
|
176
157
|
|
177
|
-
|
158
|
+
@on[:logging] = if block_given?
|
159
|
+
block
|
160
|
+
else
|
161
|
+
lambda do |notification|
|
162
|
+
@coordinator.default_process_logging_message(notification, logger: logger)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
self
|
178
166
|
end
|
179
167
|
|
180
|
-
|
181
|
-
resources_response = resources_list_request
|
182
|
-
resources_response = resources_response["result"]["resources"]
|
183
|
-
|
184
|
-
resources = {}
|
185
|
-
resources_response.each do |resource|
|
186
|
-
new_resource = RubyLLM::MCP::Resource.new(self, resource)
|
187
|
-
resources[new_resource.name] = new_resource
|
188
|
-
end
|
168
|
+
private
|
189
169
|
|
190
|
-
|
170
|
+
def setup_coordinator
|
171
|
+
Coordinator.new(self,
|
172
|
+
transport_type: @transport_type,
|
173
|
+
config: @config)
|
191
174
|
end
|
192
175
|
|
193
|
-
def
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
resource_templates = {}
|
198
|
-
resource_templates_response.each do |resource_template|
|
199
|
-
new_resource_template = RubyLLM::MCP::ResourceTemplate.new(self, resource_template)
|
200
|
-
resource_templates[new_resource_template.name] = new_resource_template
|
176
|
+
def fetch(cache_key, refresh)
|
177
|
+
instance_variable_set("@#{cache_key}", {}) if refresh
|
178
|
+
if instance_variable_get("@#{cache_key}").empty?
|
179
|
+
instance_variable_set("@#{cache_key}", yield)
|
201
180
|
end
|
202
|
-
|
203
|
-
resource_templates
|
181
|
+
instance_variable_get("@#{cache_key}")
|
204
182
|
end
|
205
183
|
|
206
|
-
def
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
prompts = {}
|
211
|
-
prompts_response.each do |prompt|
|
212
|
-
new_prompt = RubyLLM::MCP::Prompt.new(self,
|
213
|
-
name: prompt["name"],
|
214
|
-
description: prompt["description"],
|
215
|
-
arguments: prompt["arguments"])
|
216
|
-
|
217
|
-
prompts[new_prompt.name] = new_prompt
|
184
|
+
def build_map(raw_data, klass)
|
185
|
+
raw_data.each_with_object({}) do |item, acc|
|
186
|
+
instance = klass.new(@coordinator, item)
|
187
|
+
acc[instance.name] = instance
|
218
188
|
end
|
219
|
-
|
220
|
-
prompts
|
221
189
|
end
|
222
190
|
end
|
223
191
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module MCP
|
5
|
+
class Configuration
|
6
|
+
attr_accessor :request_timeout, :log_file, :log_level, :has_support_complex_parameters
|
7
|
+
attr_writer :logger
|
8
|
+
|
9
|
+
REQUEST_TIMEOUT_DEFAULT = 8000
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
set_defaults
|
13
|
+
end
|
14
|
+
|
15
|
+
def reset!
|
16
|
+
set_defaults
|
17
|
+
end
|
18
|
+
|
19
|
+
def support_complex_parameters!
|
20
|
+
return if @has_support_complex_parameters
|
21
|
+
|
22
|
+
@has_support_complex_parameters = true
|
23
|
+
RubyLLM::MCP.support_complex_parameters!
|
24
|
+
end
|
25
|
+
|
26
|
+
def logger
|
27
|
+
@logger ||= Logger.new(
|
28
|
+
log_file,
|
29
|
+
progname: "RubyLLM::MCP",
|
30
|
+
level: log_level
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def inspect
|
35
|
+
redacted = lambda do |name, value|
|
36
|
+
if name.match?(/_id|_key|_secret|_token$/)
|
37
|
+
value.nil? ? "nil" : "[FILTERED]"
|
38
|
+
else
|
39
|
+
value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
inspection = instance_variables.map do |ivar|
|
44
|
+
name = ivar.to_s.delete_prefix("@")
|
45
|
+
value = redacted[name, instance_variable_get(ivar)]
|
46
|
+
"#{name}: #{value}"
|
47
|
+
end.join(", ")
|
48
|
+
|
49
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} #{inspection}>"
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def set_defaults
|
55
|
+
# Connection configuration
|
56
|
+
@request_timeout = REQUEST_TIMEOUT_DEFAULT
|
57
|
+
|
58
|
+
# Logging configuration
|
59
|
+
@log_file = $stdout
|
60
|
+
@log_level = ENV["RUBYLLM_MCP_DEBUG"] ? Logger::DEBUG : Logger::INFO
|
61
|
+
@has_support_complex_parameters = false
|
62
|
+
@logger = nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|