ruby-mcp-client 0.8.1 → 0.9.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 +226 -893
- data/lib/mcp_client/auth/browser_oauth.rb +424 -0
- data/lib/mcp_client/auth/oauth_provider.rb +131 -19
- data/lib/mcp_client/auth.rb +1 -1
- data/lib/mcp_client/client.rb +260 -4
- data/lib/mcp_client/errors.rb +3 -0
- data/lib/mcp_client/http_transport_base.rb +7 -1
- data/lib/mcp_client/json_rpc_common.rb +7 -1
- data/lib/mcp_client/root.rb +63 -0
- data/lib/mcp_client/server_factory.rb +6 -2
- data/lib/mcp_client/server_http.rb +39 -1
- data/lib/mcp_client/server_sse/sse_parser.rb +11 -0
- data/lib/mcp_client/server_sse.rb +256 -5
- data/lib/mcp_client/server_stdio.rb +240 -1
- data/lib/mcp_client/server_streamable_http/json_rpc_transport.rb +8 -1
- data/lib/mcp_client/server_streamable_http.rb +263 -7
- data/lib/mcp_client/tool.rb +40 -4
- data/lib/mcp_client/version.rb +2 -2
- data/lib/mcp_client.rb +317 -4
- metadata +4 -2
|
@@ -9,9 +9,9 @@ require 'faraday/retry'
|
|
|
9
9
|
require 'faraday/follow_redirects'
|
|
10
10
|
|
|
11
11
|
module MCPClient
|
|
12
|
-
# Implementation of MCP server that communicates via Streamable HTTP transport (MCP 2025-
|
|
12
|
+
# Implementation of MCP server that communicates via Streamable HTTP transport (MCP 2025-06-18)
|
|
13
13
|
# This transport uses HTTP POST for RPC calls with optional SSE responses, and GET for event streams
|
|
14
|
-
# Compliant with MCP specification version 2025-
|
|
14
|
+
# Compliant with MCP specification version 2025-06-18
|
|
15
15
|
#
|
|
16
16
|
# Key features:
|
|
17
17
|
# - Supports server-sent events (SSE) for real-time notifications
|
|
@@ -91,12 +91,13 @@ module MCPClient
|
|
|
91
91
|
@headers = opts[:headers].merge({
|
|
92
92
|
'Content-Type' => 'application/json',
|
|
93
93
|
'Accept' => 'text/event-stream, application/json',
|
|
94
|
-
'Accept-Encoding' => 'gzip
|
|
94
|
+
'Accept-Encoding' => 'gzip',
|
|
95
95
|
'User-Agent' => "ruby-mcp-client/#{MCPClient::VERSION}",
|
|
96
96
|
'Cache-Control' => 'no-cache'
|
|
97
97
|
})
|
|
98
98
|
|
|
99
99
|
@read_timeout = opts[:read_timeout]
|
|
100
|
+
@faraday_config = opts[:faraday_config]
|
|
100
101
|
@tools = nil
|
|
101
102
|
@tools_data = nil
|
|
102
103
|
@prompts = nil
|
|
@@ -116,6 +117,9 @@ module MCPClient
|
|
|
116
117
|
@events_connection = nil
|
|
117
118
|
@events_thread = nil
|
|
118
119
|
@buffer = '' # Buffer for partial SSE event data
|
|
120
|
+
@elicitation_request_callback = nil # MCP 2025-06-18
|
|
121
|
+
@roots_list_request_callback = nil # MCP 2025-06-18
|
|
122
|
+
@sampling_request_callback = nil # MCP 2025-06-18
|
|
119
123
|
end
|
|
120
124
|
|
|
121
125
|
# Connect to the MCP server over Streamable HTTP
|
|
@@ -215,6 +219,33 @@ module MCPClient
|
|
|
215
219
|
end
|
|
216
220
|
end
|
|
217
221
|
|
|
222
|
+
# Request completion suggestions from the server (MCP 2025-06-18)
|
|
223
|
+
# @param ref [Hash] reference object (e.g., { 'type' => 'ref/prompt', 'name' => 'prompt_name' })
|
|
224
|
+
# @param argument [Hash] the argument being completed (e.g., { 'name' => 'arg_name', 'value' => 'partial' })
|
|
225
|
+
# @return [Hash] completion result with 'values', optional 'total', and 'hasMore' fields
|
|
226
|
+
# @raise [MCPClient::Errors::ServerError] if server returns an error
|
|
227
|
+
def complete(ref:, argument:)
|
|
228
|
+
result = rpc_request('completion/complete', { ref: ref, argument: argument })
|
|
229
|
+
result['completion'] || { 'values' => [] }
|
|
230
|
+
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError
|
|
231
|
+
raise
|
|
232
|
+
rescue StandardError => e
|
|
233
|
+
raise MCPClient::Errors::ServerError, "Error requesting completion: #{e.message}"
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Set the logging level on the server (MCP 2025-06-18)
|
|
237
|
+
# @param level [String] the log level ('debug', 'info', 'notice', 'warning', 'error',
|
|
238
|
+
# 'critical', 'alert', 'emergency')
|
|
239
|
+
# @return [Hash] empty result on success
|
|
240
|
+
# @raise [MCPClient::Errors::ServerError] if server returns an error
|
|
241
|
+
def log_level=(level)
|
|
242
|
+
rpc_request('logging/setLevel', { level: level })
|
|
243
|
+
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError
|
|
244
|
+
raise
|
|
245
|
+
rescue StandardError => e
|
|
246
|
+
raise MCPClient::Errors::ServerError, "Error setting log level: #{e.message}"
|
|
247
|
+
end
|
|
248
|
+
|
|
218
249
|
# List all prompts available from the MCP server
|
|
219
250
|
# @return [Array<MCPClient::Prompt>] list of available prompts
|
|
220
251
|
# @raise [MCPClient::Errors::PromptGetError] if prompts list retrieval fails
|
|
@@ -451,6 +482,27 @@ module MCPClient
|
|
|
451
482
|
end
|
|
452
483
|
end
|
|
453
484
|
|
|
485
|
+
# Register a callback for elicitation requests (MCP 2025-06-18)
|
|
486
|
+
# @param block [Proc] callback that receives (request_id, params) and returns response hash
|
|
487
|
+
# @return [void]
|
|
488
|
+
def on_elicitation_request(&block)
|
|
489
|
+
@elicitation_request_callback = block
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# Register a callback for roots/list requests (MCP 2025-06-18)
|
|
493
|
+
# @param block [Proc] callback that receives (request_id, params) and returns response hash
|
|
494
|
+
# @return [void]
|
|
495
|
+
def on_roots_list_request(&block)
|
|
496
|
+
@roots_list_request_callback = block
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
# Register a callback for sampling requests (MCP 2025-06-18)
|
|
500
|
+
# @param block [Proc] callback that receives (request_id, params) and returns response hash
|
|
501
|
+
# @return [void]
|
|
502
|
+
def on_sampling_request(&block)
|
|
503
|
+
@sampling_request_callback = block
|
|
504
|
+
end
|
|
505
|
+
|
|
454
506
|
private
|
|
455
507
|
|
|
456
508
|
def perform_initialize
|
|
@@ -475,7 +527,8 @@ module MCPClient
|
|
|
475
527
|
retry_backoff: 1,
|
|
476
528
|
name: nil,
|
|
477
529
|
logger: nil,
|
|
478
|
-
oauth_provider: nil
|
|
530
|
+
oauth_provider: nil,
|
|
531
|
+
faraday_config: nil
|
|
479
532
|
}
|
|
480
533
|
end
|
|
481
534
|
|
|
@@ -621,6 +674,9 @@ module MCPClient
|
|
|
621
674
|
end
|
|
622
675
|
end
|
|
623
676
|
|
|
677
|
+
# Apply user's Faraday customizations after defaults
|
|
678
|
+
@faraday_config&.call(conn)
|
|
679
|
+
|
|
624
680
|
@logger.debug("Establishing SSE events connection to #{@endpoint}") if @logger.level <= Logger::DEBUG
|
|
625
681
|
|
|
626
682
|
response = conn.get(@endpoint) do |req|
|
|
@@ -755,12 +811,12 @@ module MCPClient
|
|
|
755
811
|
# Handle ping requests from server (keepalive mechanism)
|
|
756
812
|
if message['method'] == 'ping' && message.key?('id')
|
|
757
813
|
handle_ping_request(message['id'])
|
|
814
|
+
elsif message['method'] && message.key?('id')
|
|
815
|
+
# Handle server-to-client requests (MCP 2025-06-18)
|
|
816
|
+
handle_server_request(message)
|
|
758
817
|
elsif message['method'] && !message.key?('id')
|
|
759
818
|
# Handle server notifications (messages without id)
|
|
760
819
|
@notification_callback&.call(message['method'], message['params'])
|
|
761
|
-
elsif message.key?('id')
|
|
762
|
-
# This might be a server-to-client request (future MCP versions)
|
|
763
|
-
@logger.warn("Received unhandled server request: #{message['method']}")
|
|
764
820
|
end
|
|
765
821
|
rescue JSON::ParserError => e
|
|
766
822
|
@logger.error("Invalid JSON in server message: #{e.message}")
|
|
@@ -796,5 +852,205 @@ module MCPClient
|
|
|
796
852
|
@logger.error("Failed to send pong response: #{e.message}")
|
|
797
853
|
end
|
|
798
854
|
end
|
|
855
|
+
|
|
856
|
+
# Handle incoming JSON-RPC request from server (MCP 2025-06-18)
|
|
857
|
+
# @param msg [Hash] the JSON-RPC request message
|
|
858
|
+
# @return [void]
|
|
859
|
+
def handle_server_request(msg)
|
|
860
|
+
request_id = msg['id']
|
|
861
|
+
method = msg['method']
|
|
862
|
+
params = msg['params'] || {}
|
|
863
|
+
|
|
864
|
+
@logger.debug("Received server request: #{method} (id: #{request_id})")
|
|
865
|
+
|
|
866
|
+
case method
|
|
867
|
+
when 'elicitation/create'
|
|
868
|
+
handle_elicitation_create(request_id, params)
|
|
869
|
+
when 'roots/list'
|
|
870
|
+
handle_roots_list(request_id, params)
|
|
871
|
+
when 'sampling/createMessage'
|
|
872
|
+
handle_sampling_create_message(request_id, params)
|
|
873
|
+
else
|
|
874
|
+
# Unknown request method, send error response
|
|
875
|
+
send_error_response(request_id, -32_601, "Method not found: #{method}")
|
|
876
|
+
end
|
|
877
|
+
rescue StandardError => e
|
|
878
|
+
@logger.error("Error handling server request: #{e.message}")
|
|
879
|
+
send_error_response(request_id, -32_603, "Internal error: #{e.message}")
|
|
880
|
+
end
|
|
881
|
+
|
|
882
|
+
# Handle elicitation/create request from server (MCP 2025-06-18)
|
|
883
|
+
# @param request_id [String, Integer] the JSON-RPC request ID (used as elicitationId)
|
|
884
|
+
# @param params [Hash] the elicitation parameters
|
|
885
|
+
# @return [void]
|
|
886
|
+
def handle_elicitation_create(request_id, params)
|
|
887
|
+
# The request_id is the elicitationId per MCP spec
|
|
888
|
+
elicitation_id = request_id
|
|
889
|
+
|
|
890
|
+
# If no callback is registered, decline the request
|
|
891
|
+
unless @elicitation_request_callback
|
|
892
|
+
@logger.warn('Received elicitation request but no callback registered, declining')
|
|
893
|
+
send_elicitation_response(elicitation_id, { 'action' => 'decline' })
|
|
894
|
+
return
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
# Call the registered callback
|
|
898
|
+
result = @elicitation_request_callback.call(request_id, params)
|
|
899
|
+
|
|
900
|
+
# Send the response back to the server
|
|
901
|
+
send_elicitation_response(elicitation_id, result)
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
# Handle roots/list request from server (MCP 2025-06-18)
|
|
905
|
+
# @param request_id [String, Integer] the JSON-RPC request ID
|
|
906
|
+
# @param params [Hash] the request parameters
|
|
907
|
+
# @return [void]
|
|
908
|
+
def handle_roots_list(request_id, params)
|
|
909
|
+
# If no callback is registered, return empty roots list
|
|
910
|
+
unless @roots_list_request_callback
|
|
911
|
+
@logger.debug('Received roots/list request but no callback registered, returning empty list')
|
|
912
|
+
send_roots_list_response(request_id, { 'roots' => [] })
|
|
913
|
+
return
|
|
914
|
+
end
|
|
915
|
+
|
|
916
|
+
# Call the registered callback
|
|
917
|
+
result = @roots_list_request_callback.call(request_id, params)
|
|
918
|
+
|
|
919
|
+
# Send the response back to the server
|
|
920
|
+
send_roots_list_response(request_id, result)
|
|
921
|
+
end
|
|
922
|
+
|
|
923
|
+
# Handle sampling/createMessage request from server (MCP 2025-06-18)
|
|
924
|
+
# @param request_id [String, Integer] the JSON-RPC request ID
|
|
925
|
+
# @param params [Hash] the sampling parameters
|
|
926
|
+
# @return [void]
|
|
927
|
+
def handle_sampling_create_message(request_id, params)
|
|
928
|
+
# If no callback is registered, return error
|
|
929
|
+
unless @sampling_request_callback
|
|
930
|
+
@logger.warn('Received sampling request but no callback registered, returning error')
|
|
931
|
+
send_error_response(request_id, -1, 'Sampling not supported')
|
|
932
|
+
return
|
|
933
|
+
end
|
|
934
|
+
|
|
935
|
+
# Call the registered callback
|
|
936
|
+
result = @sampling_request_callback.call(request_id, params)
|
|
937
|
+
|
|
938
|
+
# Send the response back to the server
|
|
939
|
+
send_sampling_response(request_id, result)
|
|
940
|
+
end
|
|
941
|
+
|
|
942
|
+
# Send roots/list response back to server via HTTP POST (MCP 2025-06-18)
|
|
943
|
+
# @param request_id [String, Integer] the JSON-RPC request ID
|
|
944
|
+
# @param result [Hash] the roots list result
|
|
945
|
+
# @return [void]
|
|
946
|
+
def send_roots_list_response(request_id, result)
|
|
947
|
+
response = {
|
|
948
|
+
'jsonrpc' => '2.0',
|
|
949
|
+
'id' => request_id,
|
|
950
|
+
'result' => result
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
# Send response via HTTP POST
|
|
954
|
+
post_jsonrpc_response(response)
|
|
955
|
+
rescue StandardError => e
|
|
956
|
+
@logger.error("Error sending roots/list response: #{e.message}")
|
|
957
|
+
end
|
|
958
|
+
|
|
959
|
+
# Send sampling response back to server via HTTP POST (MCP 2025-06-18)
|
|
960
|
+
# @param request_id [String, Integer] the JSON-RPC request ID
|
|
961
|
+
# @param result [Hash] the sampling result (role, content, model, stopReason)
|
|
962
|
+
# @return [void]
|
|
963
|
+
def send_sampling_response(request_id, result)
|
|
964
|
+
# Check if result contains an error
|
|
965
|
+
if result.is_a?(Hash) && result['error']
|
|
966
|
+
send_error_response(request_id, result['error']['code'] || -1, result['error']['message'] || 'Sampling error')
|
|
967
|
+
return
|
|
968
|
+
end
|
|
969
|
+
|
|
970
|
+
response = {
|
|
971
|
+
'jsonrpc' => '2.0',
|
|
972
|
+
'id' => request_id,
|
|
973
|
+
'result' => result
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
# Send response via HTTP POST
|
|
977
|
+
post_jsonrpc_response(response)
|
|
978
|
+
rescue StandardError => e
|
|
979
|
+
@logger.error("Error sending sampling response: #{e.message}")
|
|
980
|
+
end
|
|
981
|
+
|
|
982
|
+
# Send elicitation response back to server via HTTP POST (MCP 2025-06-18)
|
|
983
|
+
# For streamable HTTP, this is sent as a JSON-RPC request (not response)
|
|
984
|
+
# because HTTP is unidirectional.
|
|
985
|
+
# @param elicitation_id [String] the elicitation ID from the server
|
|
986
|
+
# @param result [Hash] the elicitation result (action and optional content)
|
|
987
|
+
# @return [void]
|
|
988
|
+
def send_elicitation_response(elicitation_id, result)
|
|
989
|
+
params = {
|
|
990
|
+
'elicitationId' => elicitation_id,
|
|
991
|
+
'action' => result['action']
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
# Only include content if present (typically for 'accept' action)
|
|
995
|
+
params['content'] = result['content'] if result['content']
|
|
996
|
+
|
|
997
|
+
request = {
|
|
998
|
+
'jsonrpc' => '2.0',
|
|
999
|
+
'method' => 'elicitation/response',
|
|
1000
|
+
'params' => params
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
# Send as a JSON-RPC request via HTTP POST
|
|
1004
|
+
post_jsonrpc_response(request)
|
|
1005
|
+
rescue StandardError => e
|
|
1006
|
+
@logger.error("Error sending elicitation response: #{e.message}")
|
|
1007
|
+
end
|
|
1008
|
+
|
|
1009
|
+
# Send error response back to server via HTTP POST (MCP 2025-06-18)
|
|
1010
|
+
# @param request_id [String, Integer] the JSON-RPC request ID
|
|
1011
|
+
# @param code [Integer] the error code
|
|
1012
|
+
# @param message [String] the error message
|
|
1013
|
+
# @return [void]
|
|
1014
|
+
def send_error_response(request_id, code, message)
|
|
1015
|
+
response = {
|
|
1016
|
+
'jsonrpc' => '2.0',
|
|
1017
|
+
'id' => request_id,
|
|
1018
|
+
'error' => {
|
|
1019
|
+
'code' => code,
|
|
1020
|
+
'message' => message
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
# Send response via HTTP POST to the endpoint
|
|
1025
|
+
post_jsonrpc_response(response)
|
|
1026
|
+
rescue StandardError => e
|
|
1027
|
+
@logger.error("Error sending error response: #{e.message}")
|
|
1028
|
+
end
|
|
1029
|
+
|
|
1030
|
+
# Post a JSON-RPC response message to the server via HTTP
|
|
1031
|
+
# @param response [Hash] the JSON-RPC response
|
|
1032
|
+
# @return [void]
|
|
1033
|
+
# @private
|
|
1034
|
+
def post_jsonrpc_response(response)
|
|
1035
|
+
# Send response in a separate thread to avoid blocking event processing
|
|
1036
|
+
Thread.new do
|
|
1037
|
+
conn = http_connection
|
|
1038
|
+
json_body = JSON.generate(response)
|
|
1039
|
+
|
|
1040
|
+
resp = conn.post(@endpoint) do |req|
|
|
1041
|
+
@headers.each { |k, v| req.headers[k] = v }
|
|
1042
|
+
req.headers['Mcp-Session-Id'] = @session_id if @session_id
|
|
1043
|
+
req.body = json_body
|
|
1044
|
+
end
|
|
1045
|
+
|
|
1046
|
+
if resp.success?
|
|
1047
|
+
@logger.debug("Sent JSON-RPC response: #{json_body}")
|
|
1048
|
+
else
|
|
1049
|
+
@logger.warn("Failed to send JSON-RPC response: HTTP #{resp.status}")
|
|
1050
|
+
end
|
|
1051
|
+
rescue StandardError => e
|
|
1052
|
+
@logger.error("Failed to send JSON-RPC response: #{e.message}")
|
|
1053
|
+
end
|
|
1054
|
+
end
|
|
799
1055
|
end
|
|
800
1056
|
end
|
data/lib/mcp_client/tool.rb
CHANGED
|
@@ -8,20 +8,28 @@ module MCPClient
|
|
|
8
8
|
# @!attribute [r] description
|
|
9
9
|
# @return [String] the description of the tool
|
|
10
10
|
# @!attribute [r] schema
|
|
11
|
-
# @return [Hash] the JSON schema for the tool
|
|
11
|
+
# @return [Hash] the JSON schema for the tool inputs
|
|
12
|
+
# @!attribute [r] output_schema
|
|
13
|
+
# @return [Hash, nil] optional JSON schema for structured tool outputs (MCP 2025-06-18)
|
|
14
|
+
# @!attribute [r] annotations
|
|
15
|
+
# @return [Hash, nil] optional annotations describing tool behavior (e.g., readOnly, destructive)
|
|
12
16
|
# @!attribute [r] server
|
|
13
17
|
# @return [MCPClient::ServerBase, nil] the server this tool belongs to
|
|
14
|
-
attr_reader :name, :description, :schema, :server
|
|
18
|
+
attr_reader :name, :description, :schema, :output_schema, :annotations, :server
|
|
15
19
|
|
|
16
20
|
# Initialize a new Tool
|
|
17
21
|
# @param name [String] the name of the tool
|
|
18
22
|
# @param description [String] the description of the tool
|
|
19
|
-
# @param schema [Hash] the JSON schema for the tool
|
|
23
|
+
# @param schema [Hash] the JSON schema for the tool inputs
|
|
24
|
+
# @param output_schema [Hash, nil] optional JSON schema for structured tool outputs (MCP 2025-06-18)
|
|
25
|
+
# @param annotations [Hash, nil] optional annotations describing tool behavior
|
|
20
26
|
# @param server [MCPClient::ServerBase, nil] the server this tool belongs to
|
|
21
|
-
def initialize(name:, description:, schema:, server: nil)
|
|
27
|
+
def initialize(name:, description:, schema:, output_schema: nil, annotations: nil, server: nil)
|
|
22
28
|
@name = name
|
|
23
29
|
@description = description
|
|
24
30
|
@schema = schema
|
|
31
|
+
@output_schema = output_schema
|
|
32
|
+
@annotations = annotations
|
|
25
33
|
@server = server
|
|
26
34
|
end
|
|
27
35
|
|
|
@@ -33,10 +41,14 @@ module MCPClient
|
|
|
33
41
|
# Some servers (Playwright MCP CLI) use 'inputSchema' instead of 'schema'
|
|
34
42
|
# Handle both string and symbol keys
|
|
35
43
|
schema = data['inputSchema'] || data[:inputSchema] || data['schema'] || data[:schema]
|
|
44
|
+
output_schema = data['outputSchema'] || data[:outputSchema]
|
|
45
|
+
annotations = data['annotations'] || data[:annotations]
|
|
36
46
|
new(
|
|
37
47
|
name: data['name'] || data[:name],
|
|
38
48
|
description: data['description'] || data[:description],
|
|
39
49
|
schema: schema,
|
|
50
|
+
output_schema: output_schema,
|
|
51
|
+
annotations: annotations,
|
|
40
52
|
server: server
|
|
41
53
|
)
|
|
42
54
|
end
|
|
@@ -74,6 +86,30 @@ module MCPClient
|
|
|
74
86
|
}
|
|
75
87
|
end
|
|
76
88
|
|
|
89
|
+
# Check if the tool is marked as read-only
|
|
90
|
+
# @return [Boolean] true if the tool is read-only
|
|
91
|
+
def read_only?
|
|
92
|
+
@annotations && @annotations['readOnly'] == true
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Check if the tool is marked as destructive
|
|
96
|
+
# @return [Boolean] true if the tool is destructive
|
|
97
|
+
def destructive?
|
|
98
|
+
@annotations && @annotations['destructive'] == true
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Check if the tool requires confirmation before execution
|
|
102
|
+
# @return [Boolean] true if the tool requires confirmation
|
|
103
|
+
def requires_confirmation?
|
|
104
|
+
@annotations && @annotations['requiresConfirmation'] == true
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Check if the tool supports structured outputs (MCP 2025-06-18)
|
|
108
|
+
# @return [Boolean] true if the tool has an output schema defined
|
|
109
|
+
def structured_output?
|
|
110
|
+
!@output_schema.nil? && !@output_schema.empty?
|
|
111
|
+
end
|
|
112
|
+
|
|
77
113
|
private
|
|
78
114
|
|
|
79
115
|
# Recursively remove "$schema" keys that are not accepted by Vertex AI
|
data/lib/mcp_client/version.rb
CHANGED