Skip to content

Commit 405bbad

Browse files
authored
Merge pull request #43 from patvice/improved-mcp-interface
Improved MCP Connection Interface
2 parents c0ef3fe + 99574e9 commit 405bbad

File tree

3 files changed

+118
-2
lines changed

3 files changed

+118
-2
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,23 @@ end
399399

400400
You can also avoid this completely manually start and stop the clients if you so choose.
401401

402+
If you want to use the clients outside of the block, you can use the `clients` method to get the clients.
403+
404+
```ruby
405+
clients = RubyLLM::MCP.establish_connection
406+
chat = RubyLLM.chat(model: "gpt-4")
407+
chat.with_tools(*clients.tools)
408+
409+
response = chat.ask("Hello, world!")
410+
puts response
411+
```
412+
413+
However, you will be responsible for closing the connection when you are done with it.
414+
415+
```ruby
416+
RubyLLM::MCP.close_connection
417+
```
418+
402419
## Client Lifecycle Management
403420

404421
You can manage the MCP client connection lifecycle:

lib/ruby_llm/mcp.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,18 @@ def client(...)
3333

3434
def establish_connection(&)
3535
clients.each(&:start)
36-
yield clients
37-
ensure
36+
if block_given?
37+
begin
38+
yield clients
39+
ensure
40+
close_connection
41+
end
42+
else
43+
clients
44+
end
45+
end
46+
47+
def close_connection
3848
clients.each do |client|
3949
client.stop if client.alive?
4050
end

spec/ruby_llm/mcp_spec.rb

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,95 @@
118118
end
119119
end
120120

121+
describe "#establish_connection" do
122+
let(:client_streamable_http) { instance_double(RubyLLM::MCP::Client) }
123+
let(:client_stdio) { instance_double(RubyLLM::MCP::Client) }
124+
let(:clients) { [client_streamable_http, client_stdio] }
125+
126+
before do
127+
allow(RubyLLM::MCP).to receive(:clients).and_return(clients)
128+
allow(client_streamable_http).to receive(:start)
129+
allow(client_stdio).to receive(:start)
130+
allow(client_streamable_http).to receive(:alive?).and_return(true)
131+
allow(client_stdio).to receive(:alive?).and_return(true)
132+
allow(client_streamable_http).to receive(:stop)
133+
allow(client_stdio).to receive(:stop)
134+
end
135+
136+
it "starts all clients" do
137+
RubyLLM::MCP.establish_connection
138+
139+
expect(client_streamable_http).to have_received(:start)
140+
expect(client_stdio).to have_received(:start)
141+
end
142+
143+
it "returns clients when no block given" do
144+
result = RubyLLM::MCP.establish_connection
145+
146+
expect(result).to eq(clients)
147+
end
148+
149+
context "when block is given" do
150+
it "yields clients to the block" do
151+
yielded_clients = nil
152+
RubyLLM::MCP.establish_connection do |c|
153+
yielded_clients = c
154+
end
155+
156+
expect(yielded_clients).to eq(clients)
157+
end
158+
159+
it "calls close_connection after the block executes" do
160+
RubyLLM::MCP.establish_connection { |_c| "test" }
161+
162+
expect(client_streamable_http).to have_received(:stop)
163+
expect(client_stdio).to have_received(:stop)
164+
end
165+
166+
it "calls close_connection even if block raises an exception" do
167+
expect do
168+
RubyLLM::MCP.establish_connection { |_c| raise "test error" }
169+
end.to raise_error("test error")
170+
171+
expect(client_streamable_http).to have_received(:stop)
172+
expect(client_stdio).to have_received(:stop)
173+
end
174+
end
175+
end
176+
177+
describe "#close_connection" do
178+
let(:alive_client) { instance_double(RubyLLM::MCP::Client) }
179+
let(:dead_client) { instance_double(RubyLLM::MCP::Client) }
180+
let(:clients) { [alive_client, dead_client] }
181+
182+
before do
183+
allow(RubyLLM::MCP).to receive(:clients).and_return(clients)
184+
allow(alive_client).to receive(:alive?).and_return(true)
185+
allow(dead_client).to receive(:alive?).and_return(false)
186+
allow(alive_client).to receive(:stop)
187+
allow(dead_client).to receive(:stop)
188+
end
189+
190+
it "stops all alive clients" do
191+
RubyLLM::MCP.close_connection
192+
193+
expect(alive_client).to have_received(:stop)
194+
end
195+
196+
it "does not stop clients that are not alive" do
197+
RubyLLM::MCP.close_connection
198+
199+
expect(dead_client).not_to have_received(:stop)
200+
end
201+
202+
it "checks alive status of all clients" do
203+
RubyLLM::MCP.close_connection
204+
205+
expect(alive_client).to have_received(:alive?)
206+
expect(dead_client).to have_received(:alive?)
207+
end
208+
end
209+
121210
describe "#tools" do
122211
let(:add) { instance_double(RubyLLM::MCP::Tool, name: "add") }
123212
let(:sub) { instance_double(RubyLLM::MCP::Tool, name: "sub") }

0 commit comments

Comments
 (0)