1+ # frozen_string_literal: true
2+
3+ require 'securerandom'
4+
5+ module MCP
6+ module Client
7+ # Main client class for MCP
8+ class Client
9+ attr_reader :id , :name , :options , :logger , :transport , :connection
10+
11+ def initialize ( options = { } )
12+ @id = SecureRandom . uuid
13+ @name = options [ :name ] || 'MCP Ruby Client'
14+ @options = options
15+ @logger = options [ :logger ] || MCP . logger
16+ @transport = initialize_transport ( options )
17+ @connection = nil
18+ @event_handlers = { }
19+ @connected = false
20+ end
21+
22+ # Connect to the MCP server
23+ # @return [self] The client instance
24+ def connect
25+ return self if @connected
26+
27+ @logger . info ( "Connecting to MCP server as '#{ @name } ' (#{ @id } )" )
28+
29+ # Initialize the transport
30+ transport_connection = @transport . connect
31+
32+ # Create the connection manager
33+ @connection = MCP ::Protocol ::Connection . new ( transport_connection )
34+
35+ # Initialize the connection with the server
36+ initialize_connection
37+
38+ @connected = true
39+ self
40+ end
41+
42+ # Disconnect from the MCP server
43+ # @return [self] The client instance
44+ def disconnect
45+ return self unless @connected
46+
47+ @logger . info ( "Disconnecting from MCP server" )
48+
49+ # Disconnect the transport
50+ @transport . disconnect
51+
52+ @connected = false
53+ @connection = nil
54+
55+ self
56+ end
57+
58+ # Check if the client is connected
59+ # @return [Boolean] true if the client is connected
60+ def connected?
61+ @connected && @transport . connected?
62+ end
63+
64+ # List available tools on the server
65+ # @return [Array<Hash>] The list of available tools
66+ def list_tools
67+ ensure_connected
68+
69+ request = MCP ::Protocol ::JsonRPC . request ( 'tools/list' )
70+ response = @connection . send_request ( request )
71+
72+ if response [ :error ]
73+ raise MCP ::Errors ::ClientError , "Error listing tools: #{ response [ :error ] [ :message ] } "
74+ end
75+
76+ response [ :result ] [ :tools ]
77+ end
78+
79+ # Call a tool on the server
80+ # @param name [String] The name of the tool to call
81+ # @param arguments [Hash] The arguments to pass to the tool
82+ # @return [Array<Hash>] The tool result content
83+ def call_tool ( name , arguments = { } )
84+ ensure_connected
85+
86+ request = MCP ::Protocol ::JsonRPC . request ( 'tools/call' , {
87+ name : name ,
88+ arguments : arguments
89+ } )
90+
91+ response = @connection . send_request ( request )
92+
93+ if response [ :error ]
94+ raise MCP ::Errors ::ClientError , "Error calling tool: #{ response [ :error ] [ :message ] } "
95+ end
96+
97+ result = response [ :result ]
98+
99+ if result [ :isError ]
100+ error_message = get_error_message ( result [ :content ] )
101+ raise MCP ::Errors ::ToolError , "Tool error: #{ error_message } "
102+ end
103+
104+ result [ :content ]
105+ end
106+
107+ # Register a handler for a specific event
108+ # @param event [Symbol] The event to handle
109+ # @param &block [Proc] The handler block
110+ def on_event ( event , &block )
111+ @event_handlers [ event ] = block
112+ end
113+
114+ private
115+
116+ # Initialize the transport
117+ # @param options [Hash] The transport options
118+ # @return [MCP::Protocol::Transport::Base] The transport instance
119+ def initialize_transport ( options )
120+ transport_options = options [ :transport_options ] || MCP . configuration . client_transport_options
121+ MCP ::Protocol . create_transport ( transport_options )
122+ end
123+
124+ # Initialize the connection with the server
125+ def initialize_connection
126+ @connection . initialize_connection (
127+ client_info : {
128+ name : @name ,
129+ version : MCP ::VERSION
130+ } ,
131+ protocol_version : MCP ::PROTOCOL_VERSION ,
132+ capabilities : client_capabilities
133+ )
134+ end
135+
136+ # Get the client capabilities
137+ # @return [Hash] The client capabilities
138+ def client_capabilities
139+ {
140+ # Set default capabilities here
141+ }
142+ end
143+
144+ # Get error message from content
145+ # @param content [Array<Hash>] The content array
146+ # @return [String] The error message
147+ def get_error_message ( content )
148+ return "Unknown error" if content . nil? || content . empty?
149+
150+ text_content = content . find { |c | c [ :type ] == 'text' }
151+ text_content ? text_content [ :text ] : "Unknown error"
152+ end
153+
154+ # Ensure the client is connected
155+ # @raise [MCP::Errors::ConnectionError] If the client is not connected
156+ def ensure_connected
157+ unless connected?
158+ raise MCP ::Errors ::ConnectionError , "Client is not connected"
159+ end
160+ end
161+ end
162+ end
163+ end
0 commit comments