Class: Clacky::Mcp::Client
- Inherits:
-
Object
- Object
- Clacky::Mcp::Client
- Defined in:
- lib/clacky/mcp/client.rb
Overview
JSON-RPC 2.0 client for a single MCP server.
Lifecycle: open transport on #start, send ‘initialize` handshake, then any number of `tools/list` / `tools/call` requests, then #stop closes the transport. Transport is selected by spec: “stdio” (default) or “http”.
Defined Under Namespace
Classes: McpError, ProtocolError
Constant Summary collapse
- TransportError =
Mcp::Transport::TransportError
- DEFAULT_TIMEOUT =
30- INIT_TIMEOUT =
15- PROTOCOL_VERSION =
"2024-11-05"
Instance Attribute Summary collapse
-
#last_used_at ⇒ Object
readonly
Returns the value of attribute last_used_at.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#server_info ⇒ Object
readonly
Returns the value of attribute server_info.
-
#started_at ⇒ Object
readonly
Returns the value of attribute started_at.
-
#tools ⇒ Object
readonly
Returns the value of attribute tools.
Class Method Summary collapse
-
.from_spec(name, spec) ⇒ Object
Build a Client from an mcp.json spec hash.
Instance Method Summary collapse
- #call_tool(tool_name, arguments = {}) ⇒ Object
-
#initialize(name:, transport: nil, command: nil, args: [], env: {}, cwd: nil) ⇒ Client
constructor
A new instance of Client.
- #start ⇒ Object
- #started? ⇒ Boolean
- #stderr_tail(bytes: 4096) ⇒ Object
- #stop ⇒ Object
- #tool_definitions ⇒ Object
Constructor Details
#initialize(name:, transport: nil, command: nil, args: [], env: {}, cwd: nil) ⇒ Client
Returns a new instance of Client.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/clacky/mcp/client.rb', line 61 def initialize(name:, transport: nil, command: nil, args: [], env: {}, cwd: nil) @name = name @transport = transport || StdioTransport.new(name: name, command: command, args: args, env: env, cwd: cwd) @pending = {} @next_id = 0 @lock = Monitor.new @started = false @tools = [] @server_info = nil @started_at = nil @last_used_at = nil @transport. do |msg| if msg["__transport_closed__"] @lock.synchronize do @pending.each_value { |q| q.push({ "error" => { "code" => -32000, "message" => "transport closed" } }) } @pending.clear end next end id = msg["id"] if id && (queue = @lock.synchronize { @pending.delete(id) }) queue.push(msg) end end end |
Instance Attribute Details
#last_used_at ⇒ Object (readonly)
Returns the value of attribute last_used_at.
27 28 29 |
# File 'lib/clacky/mcp/client.rb', line 27 def last_used_at @last_used_at end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
27 28 29 |
# File 'lib/clacky/mcp/client.rb', line 27 def name @name end |
#server_info ⇒ Object (readonly)
Returns the value of attribute server_info.
27 28 29 |
# File 'lib/clacky/mcp/client.rb', line 27 def server_info @server_info end |
#started_at ⇒ Object (readonly)
Returns the value of attribute started_at.
27 28 29 |
# File 'lib/clacky/mcp/client.rb', line 27 def started_at @started_at end |
#tools ⇒ Object (readonly)
Returns the value of attribute tools.
27 28 29 |
# File 'lib/clacky/mcp/client.rb', line 27 def tools @tools end |
Class Method Details
.from_spec(name, spec) ⇒ Object
Build a Client from an mcp.json spec hash. Recognized fields:
stdio: command (required), args, env, cwd
http: type: "http", url (required), headers
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/clacky/mcp/client.rb', line 33 def self.from_spec(name, spec) type = (spec["type"] || (spec["url"] ? "http" : "stdio")).to_s case type when "stdio" new( name: name, transport: StdioTransport.new( name: name, command: spec["command"], args: Array(spec["args"]), env: spec["env"] || {}, cwd: spec["cwd"] ) ) when "http", "streamable-http" new( name: name, transport: HttpTransport.new( name: name, url: spec["url"], headers: spec["headers"] || {} ) ) else raise McpError, "unsupported MCP transport type '#{type}' for server '#{name}'" end end |
Instance Method Details
#call_tool(tool_name, arguments = {}) ⇒ Object
137 138 139 140 141 |
# File 'lib/clacky/mcp/client.rb', line 137 def call_tool(tool_name, arguments = {}) ensure_started! @last_used_at = Time.now request("tools/call", { name: tool_name, arguments: arguments || {} }) end |
#start ⇒ Object
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/clacky/mcp/client.rb', line 95 def start already_started = false @lock.synchronize do if @started already_started = true else @transport.start end end return self if already_started handshake fetch_tools @lock.synchronize do @started = true @started_at = Time.now @last_used_at = @started_at end self end |
#started? ⇒ Boolean
91 92 93 |
# File 'lib/clacky/mcp/client.rb', line 91 def started? @started end |
#stderr_tail(bytes: 4096) ⇒ Object
143 144 145 |
# File 'lib/clacky/mcp/client.rb', line 143 def stderr_tail(bytes: 4096) @transport.stderr_tail(bytes: bytes) end |
#stop ⇒ Object
117 118 119 120 121 122 |
# File 'lib/clacky/mcp/client.rb', line 117 def stop @lock.synchronize do @transport.stop rescue nil @started = false end end |
#tool_definitions ⇒ Object
124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/clacky/mcp/client.rb', line 124 def tool_definitions @tools.map do |t| { type: "function", function: { name: t["name"], description: t["description"].to_s, parameters: t["inputSchema"] || { type: "object", properties: {} } } } end end |