Class: SolidLoop::McpClient

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

Overview

Thin MCP protocol layer: builds JSON-RPC envelopes and delegates I/O to a transport implementing SolidLoop::Mcp::Transport. Defaults to Mcp::HttpTransport built from the url/token/headers keywords.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(url = nil, token: nil, timeout: 60, headers: {}, initialize_params: {}, stateless: false, transport: nil, context: nil) ⇒ McpClient

Returns a new instance of McpClient.



14
15
16
17
18
19
20
# File 'lib/solid_loop/mcp_client.rb', line 14

def initialize(url = nil, token: nil, timeout: 60, headers: {}, initialize_params: {}, stateless: false, transport: nil, context: nil)
  @initialize_params = initialize_params
  @stateless = stateless
  @transport = transport || Mcp::HttpTransport.new(url: url, token: token, timeout: timeout, headers: headers)
  @context = context
  @last_result = nil
end

Instance Attribute Details

#last_resultObject (readonly)

Returns the value of attribute last_result.



12
13
14
# File 'lib/solid_loop/mcp_client.rb', line 12

def last_result
  @last_result
end

#transportObject (readonly)

Returns the value of attribute transport.



12
13
14
# File 'lib/solid_loop/mcp_client.rb', line 12

def transport
  @transport
end

Instance Method Details

#call_tool(session_id, name, arguments, meta: nil) ⇒ Object

meta: optional MCP _meta hash for the tools/call params (e.g. the per-ToolCall idempotency key under "solidloop/idempotencyKey").



62
63
64
65
66
67
68
69
70
# File 'lib/solid_loop/mcp_client.rb', line 62

def call_tool(session_id, name, arguments, meta: nil)
  params = { name: name, arguments: arguments }
  params[:_meta] = meta if meta.present?

  deliver(envelope("tools/call", params), session_id: session_id)
  validate_result!(session_id)

  process_tool_result(last_result.body)
end

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

Returns the MCP prompt result: { "description" => ..., "messages" => [...] }.



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

def get_prompt(session_id, name, arguments = {})
  deliver(envelope("prompts/get", { name: name, arguments: arguments }), session_id: session_id)
  validate_result!(session_id)

  body_hash["result"] || {}
end

#initialize_sessionObject



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/solid_loop/mcp_client.rb', line 34

def initialize_session
  base_params = {
    protocolVersion: "2024-11-05",
    capabilities:    {},
    clientInfo:      { name: "solid_loop", version: SolidLoop::VERSION }
  }

  deliver(envelope("initialize", base_params.deep_merge(@initialize_params)))

  unless success?
    raise McpError, "Failed to initialize MCP session: #{last_result.status} #{body_string}"
  end
  if (error = protocol_error)
    raise McpError, "Failed to initialize MCP session: #{error.to_json}"
  end

  last_result.session_id || stateless_session_id
end

#last_tool_error?Boolean

Returns:

  • (Boolean)


78
79
80
# File 'lib/solid_loop/mcp_client.rb', line 78

def last_tool_error?
  body_hash.dig("result", "isError") == true
end

#list_prompts(session_id) ⇒ Object



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

def list_prompts(session_id)
  deliver(envelope("prompts/list", {}), session_id: session_id)
  validate_result!(session_id)

  body_hash.dig("result", "prompts") || []
end

#list_resources(session_id) ⇒ Object



97
98
99
100
101
102
# File 'lib/solid_loop/mcp_client.rb', line 97

def list_resources(session_id)
  deliver(envelope("resources/list", {}), session_id: session_id)
  validate_result!(session_id)

  body_hash.dig("result", "resources") || []
end

#list_tools(session_id) ⇒ Object



53
54
55
56
57
58
# File 'lib/solid_loop/mcp_client.rb', line 53

def list_tools(session_id)
  deliver(envelope("tools/list", {}), session_id: session_id)
  validate_result!(session_id)

  body_hash.dig("result", "tools") || []
end

#read_resource(session_id, uri) ⇒ Object

Returns the MCP resource contents array (text and/or blob entries).



105
106
107
108
109
110
# File 'lib/solid_loop/mcp_client.rb', line 105

def read_resource(session_id, uri)
  deliver(envelope("resources/read", { uri: uri }), session_id: session_id)
  validate_result!(session_id)

  body_hash.dig("result", "contents") || []
end

#server_capabilitiesObject

Capabilities advertised by the server in the last initialize response. Meaningful right after initialize_session.



74
75
76
# File 'lib/solid_loop/mcp_client.rb', line 74

def server_capabilities
  body_hash.dig("result", "capabilities") || {}
end

#wire_logObject



22
23
24
25
26
27
28
29
30
31
32
# File 'lib/solid_loop/mcp_client.rb', line 22

def wire_log
  return "No response captured" unless last_result

  lines = [
    "request: #{pretty_json(last_result.raw_request)}",
    "status: #{last_result.status}",
    "response: #{pretty_json(last_result.raw_response)}"
  ]
  lines << "stderr: #{last_result.stderr}" if last_result.stderr.present?
  lines.join("\n")
end