Class: Ask::MCP::Client

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

Constant Summary collapse

PROTOCOL_VERSION =

Deprecated: use Ask::MCP::PROTOCOL_VERSION (the canonical constant). Kept as an alias so existing consumers don't break.

Ask::MCP::PROTOCOL_VERSION

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(transport, options = {}) ⇒ Client

Returns a new instance of Client.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/ask/mcp/client.rb', line 12

def initialize(transport, options = {})
  @transport = transport
  @options = options
  @capabilities = {}
  @server_info = {}
  @tools_cache = nil
  @resources_cache = nil
  @prompts_cache = nil
  @pending_requests = {}
  @pending_mutex = Mutex.new
  @pending_condition = ConditionVariable.new
  @request_handlers = {}
  @next_id = 0
  @initialized = false
  # Negotiated protocol version; nil until start() resolves it.
  @protocol_version = nil
  @stateless = false
end

Instance Attribute Details

#capabilitiesObject (readonly)

Returns the value of attribute capabilities.



10
11
12
# File 'lib/ask/mcp/client.rb', line 10

def capabilities
  @capabilities
end

#server_infoObject (readonly)

Returns the value of attribute server_info.



10
11
12
# File 'lib/ask/mcp/client.rb', line 10

def server_info
  @server_info
end

#transportObject (readonly)

Returns the value of attribute transport.



10
11
12
# File 'lib/ask/mcp/client.rb', line 10

def transport
  @transport
end

Instance Method Details

#call_tool(name, arguments = {}) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
# File 'lib/ask/mcp/client.rb', line 68

def call_tool(name, arguments = {})
  if @options[:validate] && @tools_cache
    tool = @tools_cache[name]
    if tool && tool.input_schema && !tool.input_schema.empty?
      Validator.new(tool.input_schema).validate!(arguments)
    end
  end
  headers = mcp_param_headers(name, arguments)
  response = send_request("tools/call", { name: name, arguments: arguments }, headers: headers)
  response[:content] || response
end

#get_prompt(name, arguments = {}) ⇒ Object



85
86
87
88
# File 'lib/ask/mcp/client.rb', line 85

def get_prompt(name, arguments = {})
  response = send_request("prompts/get", { name: name, arguments: arguments })
  response[:messages] || response
end

#initialized?Boolean

Returns:

  • (Boolean)


90
91
92
# File 'lib/ask/mcp/client.rb', line 90

def initialized?
  @initialized
end

#listen(notifications) ⇒ Object

Open a long-lived notification stream (2026-07-28 subscriptions/listen). notifications is a filter Hash, e.g. { toolsListChanged: true, resourceSubscriptions: ["file:///x"] } Notifications flow into the client's message handling (which resets caches on list_changed). Only supported on transports that implement #listen (StreamableHTTP).



132
133
134
# File 'lib/ask/mcp/client.rb', line 132

def listen(notifications)
  @transport.listen(notifications)
end

#on_elicitation(&handler) ⇒ Object

Handle a server request for user input (client/elicitation, 2025-06-18+). The handler receives the elicitation params (including the Elicitation schema with titled/untitled, single/multi-select enums and default values in 2025-11-25) and returns an ElicitationResponse hash, e.g. { message: "42" } or structured content. Declare client support via Ask::MCP::Client.new(transport, client_capabilities: { elicitation: {} })



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

def on_elicitation(&handler)
  on_request("elicitation/create", &handler)
end

#on_request(method, &handler) ⇒ Object

Register a handler for a server-initiated request (a JSON-RPC Request the server sends to the client, such as elicitation/create or sampling/createMessage). The handler receives the request params and must return the result hash to send back. The handler runs on the transport reader thread, so long-blocking handlers block message processing — return promptly.



100
101
102
# File 'lib/ask/mcp/client.rb', line 100

def on_request(method, &handler)
  @request_handlers[method] = handler
end

#on_sampling(&handler) ⇒ Object

Handle a sampling request (client/sampling). The handler receives the CreateMessageRequest params — including tools and toolChoice when the server offers tool calling (2025-11-25, SEP-1577) — and returns a CreateMessageResult hash, e.g. { role: "assistant", content: { type: "text", text: "..." } } Declare client support via Ask::MCP::Client.new(transport, client_capabilities: { sampling: {} })



122
123
124
# File 'lib/ask/mcp/client.rb', line 122

def on_sampling(&handler)
  on_request("sampling/createMessage", &handler)
end

#promptsObject



60
61
62
63
64
65
66
# File 'lib/ask/mcp/client.rb', line 60

def prompts
  return @prompts_cache if @prompts_cache && !@options[:no_cache]

  response = send_request("prompts/list")
  prompts = (response[:prompts] || []).map { |p| Prompt.from_h(p) }
  @prompts_cache = index_by_name(prompts)
end

#read_resource(uri) ⇒ Object



80
81
82
83
# File 'lib/ask/mcp/client.rb', line 80

def read_resource(uri)
  response = send_request("resources/read", { uri: uri })
  response[:contents] || response
end

#resourcesObject



52
53
54
55
56
57
58
# File 'lib/ask/mcp/client.rb', line 52

def resources
  return @resources_cache if @resources_cache && !@options[:no_cache]

  response = send_request("resources/list")
  resources = (response[:resources] || []).map { |r| Resource.from_h(r) }
  @resources_cache = index_by_uri(resources)
end

#startObject



31
32
33
34
35
36
# File 'lib/ask/mcp/client.rb', line 31

def start
  @transport.on_message { |message| handle_message(message) }
  @transport.start
  negotiate_protocol
  self
end

#stopObject



38
39
40
41
# File 'lib/ask/mcp/client.rb', line 38

def stop
  @transport.stop
  @initialized = false
end

#toolsObject



43
44
45
46
47
48
49
50
# File 'lib/ask/mcp/client.rb', line 43

def tools
  return @tools_cache if @tools_cache && !@options[:no_cache]

  response = send_request("tools/list")
  tools = (response[:tools] || []).map { |t| Tool.from_h(t) }
  tools = reject_invalid_mcp_header_tools(tools)
  @tools_cache = index_by_name(tools)
end