Class: MCPClient::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/mcp_client/client.rb

Overview

MCP Client for integrating with the Model Context Protocol This is the main entry point for using MCP tools

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(mcp_server_configs: [], logger: nil) ⇒ Client

Initialize a new MCPClient::Client

Parameters:

  • mcp_server_configs (Array<Hash>) (defaults to: [])

    configurations for MCP servers

  • logger (Logger, nil) (defaults to: nil)

    optional logger, defaults to STDOUT



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/mcp_client/client.rb', line 20

def initialize(mcp_server_configs: [], logger: nil)
  @logger = logger || Logger.new($stdout, level: Logger::WARN)
  @logger.progname = self.class.name
  @logger.formatter = proc { |severity, _datetime, progname, msg| "#{severity} [#{progname}] #{msg}\n" }
  @servers = mcp_server_configs.map do |config|
    @logger.debug("Creating server with config: #{config.inspect}")
    MCPClient::ServerFactory.create(config)
  end
  @tool_cache = {}
  # JSON-RPC notification listeners
  @notification_listeners = []
  # Register default and user-defined notification handlers on each server
  @servers.each do |server|
    server.on_notification do |method, params|
      # Default handling: clear tool cache on tools list change
      clear_cache if method == 'notifications/tools/list_changed'
      # Invoke user listeners
      @notification_listeners.each { |cb| cb.call(server, method, params) }
    end
  end
end

Instance Attribute Details

#loggerObject (readonly)

Returns the value of attribute logger.



15
# File 'lib/mcp_client/client.rb', line 15

attr_reader :servers, :tool_cache, :logger

#serversArray<MCPClient::ServerBase> (readonly)

Returns list of servers.

Returns:



15
16
17
# File 'lib/mcp_client/client.rb', line 15

def servers
  @servers
end

#tool_cacheHash<String, MCPClient::Tool> (readonly)

Returns cache of tools by name.

Returns:



15
# File 'lib/mcp_client/client.rb', line 15

attr_reader :servers, :tool_cache, :logger

Instance Method Details

#call_tool(tool_name, parameters) ⇒ Object

Calls a specific tool by name with the given parameters

Parameters:

  • tool_name (String)

    the name of the tool to call

  • parameters (Hash)

    the parameters to pass to the tool

Returns:

  • (Object)

    the result of the tool invocation

Raises:



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/mcp_client/client.rb', line 63

def call_tool(tool_name, parameters)
  tools = list_tools
  tool = tools.find { |t| t.name == tool_name }

  raise MCPClient::Errors::ToolNotFound, "Tool '#{tool_name}' not found" unless tool

  # Validate parameters against tool schema
  validate_params!(tool, parameters)

  # Find the server that owns this tool
  server = find_server_for_tool(tool)
  raise MCPClient::Errors::ServerNotFound, "No server found for tool '#{tool_name}'" unless server

  server.call_tool(tool_name, parameters)
end

#call_tool_streaming(tool_name, parameters) ⇒ Enumerator

Stream call of a specific tool by name with the given parameters. Returns an Enumerator yielding streaming updates if supported.

Parameters:

  • tool_name (String)

    the name of the tool to call

  • parameters (Hash)

    the parameters to pass to the tool

Returns:

  • (Enumerator)

    streaming enumerator or single-value enumerator

Raises:



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/mcp_client/client.rb', line 146

def call_tool_streaming(tool_name, parameters)
  tools = list_tools
  tool = tools.find { |t| t.name == tool_name }
  raise MCPClient::Errors::ToolNotFound, "Tool '#{tool_name}' not found" unless tool

  # Validate parameters against tool schema
  validate_params!(tool, parameters)
  # Find the server that owns this tool
  server = find_server_for_tool(tool)
  raise MCPClient::Errors::ServerNotFound, "No server found for tool '#{tool_name}'" unless server

  if server.respond_to?(:call_tool_streaming)
    server.call_tool_streaming(tool_name, parameters)
  else
    Enumerator.new do |yielder|
      yielder << server.call_tool(tool_name, parameters)
    end
  end
end

#call_tools(calls) ⇒ Array<Object>

Call multiple tools in batch

Parameters:

  • calls (Array<Hash>)

    array of calls in the form { name: tool_name, parameters: … }

Returns:

  • (Array<Object>)

    array of results for each tool invocation



133
134
135
136
137
138
139
# File 'lib/mcp_client/client.rb', line 133

def call_tools(calls)
  calls.map do |call|
    name = call[:name] || call['name']
    params = call[:parameters] || call['parameters'] || {}
    call_tool(name, params)
  end
end

#cleanupObject

Clean up all server connections



98
99
100
# File 'lib/mcp_client/client.rb', line 98

def cleanup
  servers.each(&:cleanup)
end

#clear_cachevoid

This method returns an undefined value.

Clear the cached tools so that next list_tools will fetch fresh data



104
105
106
# File 'lib/mcp_client/client.rb', line 104

def clear_cache
  @tool_cache.clear
end

#find_tool(pattern) ⇒ MCPClient::Tool?

Find the first tool whose name matches the given pattern

Parameters:

  • pattern (String, Regexp)

    pattern to match tool names

Returns:



126
127
128
# File 'lib/mcp_client/client.rb', line 126

def find_tool(pattern)
  find_tools(pattern).first
end

#find_tools(pattern) ⇒ Array<MCPClient::Tool>

Find all tools whose name matches the given pattern (String or Regexp)

Parameters:

  • pattern (String, Regexp)

    pattern to match tool names

Returns:



118
119
120
121
# File 'lib/mcp_client/client.rb', line 118

def find_tools(pattern)
  rx = pattern.is_a?(Regexp) ? pattern : /#{Regexp.escape(pattern)}/
  list_tools.select { |t| t.name.match(rx) }
end

#list_tools(cache: true) ⇒ Array<MCPClient::Tool>

Lists all available tools from all connected MCP servers

Parameters:

  • cache (Boolean) (defaults to: true)

    whether to use cached tools or fetch fresh

Returns:



45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/mcp_client/client.rb', line 45

def list_tools(cache: true)
  return @tool_cache.values if cache && !@tool_cache.empty?

  tools = []
  servers.each do |server|
    server.list_tools.each do |tool|
      @tool_cache[tool.name] = tool
      tools << tool
    end
  end

  tools
end

#on_notification {|server, method, params| ... } ⇒ void

This method returns an undefined value.

Register a callback for JSON-RPC notifications from servers

Yields:

  • (server, method, params)


111
112
113
# File 'lib/mcp_client/client.rb', line 111

def on_notification(&block)
  @notification_listeners << block
end

#ping(params = {}, server_index: nil) ⇒ Object

Ping the MCP server to check connectivity

Parameters:

  • params (Hash) (defaults to: {})

    optional parameters for the ping request

  • server_index (Integer, nil) (defaults to: nil)

    optional index of a specific server to ping, nil for first available

Returns:

  • (Object)

    result from the ping request

Raises:



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/mcp_client/client.rb', line 171

def ping(params = {}, server_index: nil)
  if server_index.nil?
    # Ping first available server
    raise MCPClient::Errors::ServerNotFound, 'No server available for ping' if @servers.empty?

    @servers.first.ping(params)
  else
    # Ping specified server
    if server_index >= @servers.length
      raise MCPClient::Errors::ServerNotFound,
            "Server at index #{server_index} not found"
    end

    @servers[server_index].ping(params)
  end
end

#to_anthropic_tools(tool_names: nil) ⇒ Array<Hash>

Convert MCP tools to Anthropic Claude tool specifications

Parameters:

  • tool_names (Array<String>, nil) (defaults to: nil)

    optional list of tool names to include, nil means all tools

Returns:

  • (Array<Hash>)

    Anthropic Claude tool specifications



91
92
93
94
95
# File 'lib/mcp_client/client.rb', line 91

def to_anthropic_tools(tool_names: nil)
  tools = list_tools
  tools = tools.select { |t| tool_names.include?(t.name) } if tool_names
  tools.map(&:to_anthropic_tool)
end

#to_openai_tools(tool_names: nil) ⇒ Array<Hash>

Convert MCP tools to OpenAI function specifications

Parameters:

  • tool_names (Array<String>, nil) (defaults to: nil)

    optional list of tool names to include, nil means all tools

Returns:

  • (Array<Hash>)

    OpenAI function specifications



82
83
84
85
86
# File 'lib/mcp_client/client.rb', line 82

def to_openai_tools(tool_names: nil)
  tools = list_tools
  tools = tools.select { |t| tool_names.include?(t.name) } if tool_names
  tools.map(&:to_openai_tool)
end