ruby_llm-mcp 0.2.1 → 0.3.1
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 +112 -22
- data/lib/ruby_llm/chat.rb +8 -2
- data/lib/ruby_llm/mcp/capabilities.rb +3 -3
- data/lib/ruby_llm/mcp/client.rb +57 -99
- data/lib/ruby_llm/mcp/coordinator.rb +112 -0
- data/lib/ruby_llm/mcp/errors.rb +5 -3
- data/lib/ruby_llm/mcp/parameter.rb +12 -1
- data/lib/ruby_llm/mcp/prompt.rb +25 -14
- data/lib/ruby_llm/mcp/providers/anthropic/complex_parameter_support.rb +26 -6
- data/lib/ruby_llm/mcp/providers/gemini/complex_parameter_support.rb +62 -0
- data/lib/ruby_llm/mcp/providers/openai/complex_parameter_support.rb +20 -5
- data/lib/ruby_llm/mcp/requests/{completion.rb → completion_prompt.rb} +3 -13
- data/lib/ruby_llm/mcp/requests/completion_resource.rb +40 -0
- data/lib/ruby_llm/mcp/requests/{notification.rb → initialize_notification.rb} +1 -1
- data/lib/ruby_llm/mcp/resource.rb +24 -46
- data/lib/ruby_llm/mcp/resource_template.rb +79 -0
- data/lib/ruby_llm/mcp/tool.rb +67 -21
- data/lib/ruby_llm/mcp/transport/sse.rb +12 -7
- data/lib/ruby_llm/mcp/transport/stdio.rb +51 -14
- data/lib/ruby_llm/mcp/transport/streamable.rb +11 -4
- data/lib/ruby_llm/mcp/version.rb +1 -1
- metadata +8 -4
data/lib/ruby_llm/mcp/prompt.rb
CHANGED
@@ -13,16 +13,17 @@ module RubyLLM
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
attr_reader :name, :description, :arguments, :
|
16
|
+
attr_reader :name, :description, :arguments, :coordinator
|
17
17
|
|
18
|
-
def initialize(
|
19
|
-
@
|
20
|
-
@name = name
|
21
|
-
@description = description
|
18
|
+
def initialize(coordinator, prompt)
|
19
|
+
@coordinator = coordinator
|
20
|
+
@name = prompt["name"]
|
21
|
+
@description = prompt["description"]
|
22
|
+
@arguments = parse_arguments(prompt["arguments"])
|
23
|
+
end
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
-
end
|
25
|
+
def fetch(arguments = {})
|
26
|
+
fetch_prompt_messages(arguments)
|
26
27
|
end
|
27
28
|
|
28
29
|
def include(chat, arguments: {})
|
@@ -41,21 +42,21 @@ module RubyLLM
|
|
41
42
|
|
42
43
|
alias say ask
|
43
44
|
|
44
|
-
def
|
45
|
-
if @
|
46
|
-
response = @
|
45
|
+
def complete(argument, value)
|
46
|
+
if @coordinator.capabilities.completion?
|
47
|
+
response = @coordinator.completion_prompt(name: @name, argument: argument, value: value)
|
47
48
|
response = response.dig("result", "completion")
|
48
49
|
|
49
50
|
Completion.new(values: response["values"], total: response["total"], has_more: response["hasMore"])
|
50
51
|
else
|
51
|
-
raise Errors::CompletionNotAvailable
|
52
|
+
raise Errors::CompletionNotAvailable.new(message: "Completion is not available for this MCP server")
|
52
53
|
end
|
53
54
|
end
|
54
55
|
|
55
56
|
private
|
56
57
|
|
57
58
|
def fetch_prompt_messages(arguments)
|
58
|
-
response = @
|
59
|
+
response = @coordinator.execute_prompt(
|
59
60
|
name: @name,
|
60
61
|
arguments: arguments
|
61
62
|
)
|
@@ -86,10 +87,20 @@ module RubyLLM
|
|
86
87
|
attachment = MCP::Attachment.new(content["content"], content["mime_type"])
|
87
88
|
MCP::Content.new(text: nil, attachments: [attachment])
|
88
89
|
when "resource"
|
89
|
-
resource = Resource.new(
|
90
|
+
resource = Resource.new(coordinator, content["resource"])
|
90
91
|
resource.to_content
|
91
92
|
end
|
92
93
|
end
|
94
|
+
|
95
|
+
def parse_arguments(arguments)
|
96
|
+
if arguments.nil?
|
97
|
+
[]
|
98
|
+
else
|
99
|
+
arguments.map do |arg|
|
100
|
+
Argument.new(name: arg["name"], description: arg["description"], required: arg["required"])
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
93
104
|
end
|
94
105
|
end
|
95
106
|
end
|
@@ -20,21 +20,33 @@ module RubyLLM
|
|
20
20
|
def build_properties(param)
|
21
21
|
case param.type
|
22
22
|
when :array
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
if param.item_type == :object
|
24
|
+
{
|
25
|
+
type: param.type,
|
26
|
+
items: { type: param.item_type, properties: clean_parameters(param.properties) }
|
27
|
+
}
|
28
|
+
else
|
29
|
+
{
|
30
|
+
type: param.type,
|
31
|
+
default: param.default,
|
32
|
+
items: { type: param.item_type, enum: param.enum }.compact
|
33
|
+
}.compact
|
34
|
+
end
|
27
35
|
when :object
|
28
36
|
{
|
29
37
|
type: param.type,
|
30
38
|
properties: clean_parameters(param.properties),
|
31
39
|
required: required_parameters(param.properties)
|
32
40
|
}
|
41
|
+
when :union
|
42
|
+
{
|
43
|
+
param.union_type => param.properties.map { |property| build_properties(property) }
|
44
|
+
}
|
33
45
|
else
|
34
46
|
{
|
35
47
|
type: param.type,
|
36
48
|
description: param.description
|
37
|
-
}
|
49
|
+
}.compact
|
38
50
|
end
|
39
51
|
end
|
40
52
|
end
|
@@ -43,4 +55,12 @@ module RubyLLM
|
|
43
55
|
end
|
44
56
|
end
|
45
57
|
|
46
|
-
RubyLLM::Providers::Anthropic
|
58
|
+
module RubyLLM::Providers::Anthropic::Tools
|
59
|
+
def self.clean_parameters(parameters)
|
60
|
+
RubyLLM::MCP::Providers::Anthropic::ComplexParameterSupport.clean_parameters(parameters)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.required_parameters(parameters)
|
64
|
+
RubyLLM::MCP::Providers::Anthropic::ComplexParameterSupport.required_parameters(parameters)
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module MCP
|
5
|
+
module Providers
|
6
|
+
module Gemini
|
7
|
+
module ComplexParameterSupport
|
8
|
+
module_function
|
9
|
+
|
10
|
+
# Format tool parameters for Gemini API
|
11
|
+
def format_parameters(parameters)
|
12
|
+
{
|
13
|
+
type: "OBJECT",
|
14
|
+
properties: parameters.transform_values { |param| build_properties(param) },
|
15
|
+
required: parameters.select { |_, p| p.required }.keys.map(&:to_s)
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def build_properties(param)
|
20
|
+
properties = case param.type
|
21
|
+
when :array
|
22
|
+
if param.item_type == :object
|
23
|
+
{
|
24
|
+
type: param_type_for_gemini(param.type),
|
25
|
+
items: {
|
26
|
+
type: param_type_for_gemini(param.item_type),
|
27
|
+
properties: param.properties.transform_values { |value| build_properties(value) }
|
28
|
+
}
|
29
|
+
}
|
30
|
+
else
|
31
|
+
{
|
32
|
+
type: param_type_for_gemini(param.type),
|
33
|
+
default: param.default,
|
34
|
+
items: { type: param_type_for_gemini(param.item_type), enum: param.enum }.compact
|
35
|
+
}.compact
|
36
|
+
end
|
37
|
+
when :object
|
38
|
+
{
|
39
|
+
type: param_type_for_gemini(param.type),
|
40
|
+
properties: param.properties.transform_values { |value| build_properties(value) },
|
41
|
+
required: param.properties.select { |_, p| p.required }.keys
|
42
|
+
}
|
43
|
+
when :union
|
44
|
+
{
|
45
|
+
param.union_type => param.properties.map { |properties| build_properties(properties) }
|
46
|
+
}
|
47
|
+
else
|
48
|
+
{
|
49
|
+
type: param_type_for_gemini(param.type),
|
50
|
+
description: param.description
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
properties.compact
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
RubyLLM::Providers::Gemini.extend(RubyLLM::MCP::Providers::Gemini::ComplexParameterSupport)
|
@@ -10,21 +10,36 @@ module RubyLLM
|
|
10
10
|
def param_schema(param)
|
11
11
|
properties = case param.type
|
12
12
|
when :array
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
if param.item_type == :object
|
14
|
+
{
|
15
|
+
type: param.type,
|
16
|
+
items: {
|
17
|
+
type: param.item_type,
|
18
|
+
properties: param.properties.transform_values { |value| param_schema(value) }
|
19
|
+
}
|
20
|
+
}
|
21
|
+
else
|
22
|
+
{
|
23
|
+
type: param.type,
|
24
|
+
default: param.default,
|
25
|
+
items: { type: param.item_type, enum: param.enum }.compact
|
26
|
+
}.compact
|
27
|
+
end
|
17
28
|
when :object
|
18
29
|
{
|
19
30
|
type: param.type,
|
20
31
|
properties: param.properties.transform_values { |value| param_schema(value) },
|
21
32
|
required: param.properties.select { |_, p| p.required }.keys
|
22
33
|
}
|
34
|
+
when :union
|
35
|
+
{
|
36
|
+
param.union_type => param.properties.map { |property| param_schema(property) }
|
37
|
+
}
|
23
38
|
else
|
24
39
|
{
|
25
40
|
type: param.type,
|
26
41
|
description: param.description
|
27
|
-
}
|
42
|
+
}.compact
|
28
43
|
end
|
29
44
|
|
30
45
|
properties.compact
|
@@ -3,10 +3,9 @@
|
|
3
3
|
module RubyLLM
|
4
4
|
module MCP
|
5
5
|
module Requests
|
6
|
-
class
|
7
|
-
def initialize(client,
|
6
|
+
class CompletionPrompt
|
7
|
+
def initialize(client, name:, argument:, value:)
|
8
8
|
@client = client
|
9
|
-
@type = type
|
10
9
|
@name = name
|
11
10
|
@argument = argument
|
12
11
|
@value = value
|
@@ -25,7 +24,7 @@ module RubyLLM
|
|
25
24
|
method: "completion/complete",
|
26
25
|
params: {
|
27
26
|
ref: {
|
28
|
-
type:
|
27
|
+
type: "ref/prompt",
|
29
28
|
name: @name
|
30
29
|
},
|
31
30
|
argument: {
|
@@ -35,15 +34,6 @@ module RubyLLM
|
|
35
34
|
}
|
36
35
|
}
|
37
36
|
end
|
38
|
-
|
39
|
-
def ref_type
|
40
|
-
case @type
|
41
|
-
when :prompt
|
42
|
-
"ref/prompt"
|
43
|
-
when :resource
|
44
|
-
"ref/resource"
|
45
|
-
end
|
46
|
-
end
|
47
37
|
end
|
48
38
|
end
|
49
39
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module MCP
|
5
|
+
module Requests
|
6
|
+
class CompletionResource
|
7
|
+
def initialize(client, uri:, argument:, value:)
|
8
|
+
@client = client
|
9
|
+
@uri = uri
|
10
|
+
@argument = argument
|
11
|
+
@value = value
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
@client.request(request_body)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def request_body
|
21
|
+
{
|
22
|
+
jsonrpc: "2.0",
|
23
|
+
id: 1,
|
24
|
+
method: "completion/complete",
|
25
|
+
params: {
|
26
|
+
ref: {
|
27
|
+
type: "ref/resource",
|
28
|
+
uri: @uri
|
29
|
+
},
|
30
|
+
argument: {
|
31
|
+
name: @argument,
|
32
|
+
value: @value
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class RubyLLM::MCP::Requests::
|
3
|
+
class RubyLLM::MCP::Requests::InitializeNotification < RubyLLM::MCP::Requests::Base
|
4
4
|
def call
|
5
5
|
client.request(notification_body, add_id: false, wait_for_response: false)
|
6
6
|
end
|
@@ -3,37 +3,26 @@
|
|
3
3
|
module RubyLLM
|
4
4
|
module MCP
|
5
5
|
class Resource
|
6
|
-
attr_reader :uri, :name, :description, :mime_type, :
|
6
|
+
attr_reader :uri, :name, :description, :mime_type, :coordinator
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
@
|
8
|
+
def initialize(coordinator, resource)
|
9
|
+
@coordinator = coordinator
|
10
10
|
@uri = resource["uri"]
|
11
11
|
@name = resource["name"]
|
12
12
|
@description = resource["description"]
|
13
13
|
@mime_type = resource["mimeType"]
|
14
|
-
|
15
|
-
|
14
|
+
if resource.key?("content_response")
|
15
|
+
@content_response = resource["content_response"]
|
16
|
+
@content = @content_response["text"] || @content_response["blob"]
|
17
|
+
end
|
16
18
|
end
|
17
19
|
|
18
|
-
def content
|
19
|
-
return @content
|
20
|
-
|
21
|
-
response = if template?
|
22
|
-
templated_uri = apply_template(@uri, arguments)
|
23
|
-
|
24
|
-
read_response(uri: templated_uri)
|
25
|
-
else
|
26
|
-
read_response
|
27
|
-
end
|
28
|
-
|
29
|
-
content = response.dig("result", "contents", 0)
|
30
|
-
@type = if content.key?("type")
|
31
|
-
content["type"]
|
32
|
-
else
|
33
|
-
"text"
|
34
|
-
end
|
20
|
+
def content
|
21
|
+
return @content unless @content.nil?
|
35
22
|
|
36
|
-
|
23
|
+
response = read_response
|
24
|
+
@content_response = response.dig("result", "contents", 0)
|
25
|
+
@content = @content_response["text"] || @content_response["blob"]
|
37
26
|
end
|
38
27
|
|
39
28
|
def include(chat, **args)
|
@@ -45,42 +34,37 @@ module RubyLLM
|
|
45
34
|
chat.add_message(message)
|
46
35
|
end
|
47
36
|
|
48
|
-
def to_content
|
49
|
-
content = content
|
37
|
+
def to_content
|
38
|
+
content = self.content
|
50
39
|
|
51
|
-
case
|
40
|
+
case content_type
|
52
41
|
when "text"
|
53
|
-
MCP::Content.new(content)
|
42
|
+
MCP::Content.new(text: "#{name}: #{description}\n\n#{content}")
|
54
43
|
when "blob"
|
55
44
|
attachment = MCP::Attachment.new(content, mime_type)
|
56
45
|
MCP::Content.new(text: "#{name}: #{description}", attachments: [attachment])
|
57
46
|
end
|
58
47
|
end
|
59
48
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
49
|
+
private
|
50
|
+
|
51
|
+
def content_type
|
52
|
+
return "text" if @content_response.nil?
|
64
53
|
|
65
|
-
|
54
|
+
if @content_response.key?("blob")
|
55
|
+
"blob"
|
66
56
|
else
|
67
|
-
|
57
|
+
"text"
|
68
58
|
end
|
69
59
|
end
|
70
60
|
|
71
|
-
def template?
|
72
|
-
@template
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
61
|
def read_response(uri: @uri)
|
78
62
|
parsed = URI.parse(uri)
|
79
63
|
case parsed.scheme
|
80
64
|
when "http", "https"
|
81
65
|
fetch_uri_content(uri)
|
82
66
|
else # file:// or git://
|
83
|
-
@read_response ||= @
|
67
|
+
@read_response ||= @coordinator.resource_read(uri: uri)
|
84
68
|
end
|
85
69
|
end
|
86
70
|
|
@@ -88,12 +72,6 @@ module RubyLLM
|
|
88
72
|
response = Faraday.get(uri)
|
89
73
|
{ "result" => { "contents" => [{ "text" => response.body }] } }
|
90
74
|
end
|
91
|
-
|
92
|
-
def apply_template(uri, arguments)
|
93
|
-
uri.gsub(/\{(\w+)\}/) do
|
94
|
-
arguments[::Regexp.last_match(1).to_sym] || "{#{::Regexp.last_match(1)}}"
|
95
|
-
end
|
96
|
-
end
|
97
75
|
end
|
98
76
|
end
|
99
77
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module MCP
|
5
|
+
class ResourceTemplate
|
6
|
+
attr_reader :uri, :name, :description, :mime_type, :coordinator, :template
|
7
|
+
|
8
|
+
def initialize(coordinator, resource)
|
9
|
+
@coordinator = coordinator
|
10
|
+
@uri = resource["uriTemplate"]
|
11
|
+
@name = resource["name"]
|
12
|
+
@description = resource["description"]
|
13
|
+
@mime_type = resource["mimeType"]
|
14
|
+
end
|
15
|
+
|
16
|
+
def fetch_resource(arguments: {})
|
17
|
+
uri = apply_template(@uri, arguments)
|
18
|
+
response = read_response(uri)
|
19
|
+
content_response = response.dig("result", "contents", 0)
|
20
|
+
|
21
|
+
Resource.new(coordinator, {
|
22
|
+
"uri" => uri,
|
23
|
+
"name" => "#{@name} (#{uri})",
|
24
|
+
"description" => @description,
|
25
|
+
"mimeType" => @mime_type,
|
26
|
+
"content_response" => content_response
|
27
|
+
})
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_content(arguments: {})
|
31
|
+
fetch_resource(arguments: arguments).to_content
|
32
|
+
end
|
33
|
+
|
34
|
+
def complete(argument, value)
|
35
|
+
if @coordinator.capabilities.completion?
|
36
|
+
response = @coordinator.completion_resource(uri: @uri, argument: argument, value: value)
|
37
|
+
response = response.dig("result", "completion")
|
38
|
+
|
39
|
+
Completion.new(values: response["values"], total: response["total"], has_more: response["hasMore"])
|
40
|
+
else
|
41
|
+
raise Errors::CompletionNotAvailable.new(message: "Completion is not available for this MCP server")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def content_type
|
48
|
+
if @content.key?("type")
|
49
|
+
@content["type"]
|
50
|
+
else
|
51
|
+
"text"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def read_response(uri)
|
56
|
+
parsed = URI.parse(uri)
|
57
|
+
case parsed.scheme
|
58
|
+
when "http", "https"
|
59
|
+
fetch_uri_content(uri)
|
60
|
+
else # file:// or git://
|
61
|
+
@coordinator.resource_read(uri: uri)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def fetch_uri_content(uri)
|
66
|
+
response = Faraday.get(uri)
|
67
|
+
{ "result" => { "contents" => [{ "text" => response.body }] } }
|
68
|
+
end
|
69
|
+
|
70
|
+
def apply_template(uri, arguments)
|
71
|
+
uri.gsub(/\{(\w+)\}/) do
|
72
|
+
arguments[::Regexp.last_match(1).to_s] ||
|
73
|
+
arguments[::Regexp.last_match(1).to_sym] ||
|
74
|
+
"{#{::Regexp.last_match(1)}}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/ruby_llm/mcp/tool.rb
CHANGED
@@ -3,28 +3,29 @@
|
|
3
3
|
module RubyLLM
|
4
4
|
module MCP
|
5
5
|
class Tool < RubyLLM::Tool
|
6
|
-
attr_reader :name, :description, :parameters, :
|
6
|
+
attr_reader :name, :description, :parameters, :coordinator, :tool_response
|
7
7
|
|
8
|
-
def initialize(
|
8
|
+
def initialize(coordinator, tool_response)
|
9
9
|
super()
|
10
|
-
@
|
10
|
+
@coordinator = coordinator
|
11
11
|
|
12
12
|
@name = tool_response["name"]
|
13
|
-
@description = tool_response["description"]
|
13
|
+
@description = tool_response["description"].to_s
|
14
14
|
@parameters = create_parameters(tool_response["inputSchema"])
|
15
15
|
end
|
16
16
|
|
17
17
|
def execute(**params)
|
18
|
-
response = @
|
18
|
+
response = @coordinator.execute_tool(
|
19
19
|
name: @name,
|
20
20
|
parameters: params
|
21
21
|
)
|
22
22
|
|
23
23
|
text_values = response.dig("result", "content").map { |content| content["text"] }.compact.join("\n")
|
24
|
+
|
24
25
|
if text_values.empty?
|
25
26
|
create_content_for_message(response.dig("result", "content", 0))
|
26
27
|
else
|
27
|
-
create_content_for_message({ type
|
28
|
+
create_content_for_message({ "type" => "text", "text" => text_values })
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
@@ -32,20 +33,16 @@ module RubyLLM
|
|
32
33
|
|
33
34
|
def create_parameters(input_schema)
|
34
35
|
params = {}
|
36
|
+
return params if input_schema["properties"].nil?
|
37
|
+
|
35
38
|
input_schema["properties"].each_key do |key|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
if param.type == "array"
|
44
|
-
param.items = input_schema["properties"][key]["items"]
|
45
|
-
elsif param.type == "object"
|
46
|
-
properties = create_parameters(input_schema["properties"][key]["properties"])
|
47
|
-
param.properties = properties
|
48
|
-
end
|
39
|
+
param_data = input_schema.dig("properties", key)
|
40
|
+
|
41
|
+
param = if param_data.key?("oneOf") || param_data.key?("anyOf") || param_data.key?("allOf")
|
42
|
+
process_union_parameter(key, param_data)
|
43
|
+
else
|
44
|
+
process_parameter(key, param_data)
|
45
|
+
end
|
49
46
|
|
50
47
|
params[key] = param
|
51
48
|
end
|
@@ -53,15 +50,64 @@ module RubyLLM
|
|
53
50
|
params
|
54
51
|
end
|
55
52
|
|
53
|
+
def process_union_parameter(key, param_data)
|
54
|
+
union_type = param_data.keys.first
|
55
|
+
param = RubyLLM::MCP::Parameter.new(
|
56
|
+
key,
|
57
|
+
type: :union,
|
58
|
+
union_type: union_type
|
59
|
+
)
|
60
|
+
|
61
|
+
param.properties = param_data[union_type].map do |value|
|
62
|
+
process_parameter(key, value, lifted_type: param_data["type"])
|
63
|
+
end.compact
|
64
|
+
|
65
|
+
param
|
66
|
+
end
|
67
|
+
|
68
|
+
def process_parameter(key, param_data, lifted_type: nil)
|
69
|
+
param = RubyLLM::MCP::Parameter.new(
|
70
|
+
key,
|
71
|
+
type: param_data["type"] || lifted_type,
|
72
|
+
desc: param_data["description"],
|
73
|
+
required: param_data["required"],
|
74
|
+
default: param_data["default"]
|
75
|
+
)
|
76
|
+
|
77
|
+
if param.type == :array
|
78
|
+
items = param_data["items"]
|
79
|
+
param.items = items
|
80
|
+
if items.key?("properties")
|
81
|
+
param.properties = create_parameters(items)
|
82
|
+
end
|
83
|
+
if items.key?("enum")
|
84
|
+
param.enum = items["enum"]
|
85
|
+
end
|
86
|
+
elsif param.type == :object
|
87
|
+
if param_data.key?("properties")
|
88
|
+
param.properties = create_parameters(param_data)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
param
|
93
|
+
end
|
94
|
+
|
56
95
|
def create_content_for_message(content)
|
57
96
|
case content["type"]
|
58
97
|
when "text"
|
59
98
|
MCP::Content.new(text: content["text"])
|
60
99
|
when "image", "audio"
|
61
|
-
attachment = MCP::Attachment.new(content["
|
100
|
+
attachment = MCP::Attachment.new(content["data"], content["mimeType"])
|
62
101
|
MCP::Content.new(text: nil, attachments: [attachment])
|
63
102
|
when "resource"
|
64
|
-
|
103
|
+
resource_data = {
|
104
|
+
"name" => name,
|
105
|
+
"description" => description,
|
106
|
+
"uri" => content.dig("resource", "uri"),
|
107
|
+
"content" => content["resource"]
|
108
|
+
}
|
109
|
+
|
110
|
+
resource = Resource.new(coordinator, resource_data)
|
65
111
|
resource.to_content
|
66
112
|
end
|
67
113
|
end
|