Class: MCPClient::ServerStdio

Inherits:
ServerBase show all
Includes:
JsonRpcTransport
Defined in:
lib/mcp_client/server_stdio.rb,
lib/mcp_client/server_stdio/json_rpc_transport.rb

Overview

JSON-RPC implementation of MCP server over stdio.

Defined Under Namespace

Modules: JsonRpcTransport

Constant Summary collapse

READ_TIMEOUT =

Timeout in seconds for responses

15

Instance Attribute Summary collapse

Attributes inherited from ServerBase

#name

Instance Method Summary collapse

Methods included from JsonRpcTransport

#call_tool_streaming, #ensure_initialized, #next_id, #perform_initialize, #rpc_notify, #rpc_request, #send_request, #wait_response

Methods included from JsonRpcCommon

#build_jsonrpc_notification, #build_jsonrpc_request, #initialization_params, #ping, #process_jsonrpc_response, #with_retry

Methods inherited from ServerBase

#call_tool_streaming, #on_notification, #ping, #rpc_notify, #rpc_request

Constructor Details

#initialize(command:, retries: 0, retry_backoff: 1, read_timeout: READ_TIMEOUT, name: nil, logger: nil, env: {}) ⇒ ServerStdio

Initialize a new ServerStdio instance

Parameters:

  • command (String, Array)

    the stdio command to launch the MCP JSON-RPC server For improved security, passing an Array is recommended to avoid shell injection issues

  • retries (Integer) (defaults to: 0)

    number of retry attempts on transient errors

  • retry_backoff (Numeric) (defaults to: 1)

    base delay in seconds for exponential backoff

  • read_timeout (Numeric) (defaults to: READ_TIMEOUT)

    timeout in seconds for reading responses

  • name (String, nil) (defaults to: nil)

    optional name for this server

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

    optional logger

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

    optional environment variables for the subprocess



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

def initialize(command:, retries: 0, retry_backoff: 1, read_timeout: READ_TIMEOUT, name: nil, logger: nil, env: {})
  super(name: name)
  @command_array = command.is_a?(Array) ? command : nil
  @command = command.is_a?(Array) ? command.join(' ') : command
  @mutex = Mutex.new
  @cond = ConditionVariable.new
  @next_id = 1
  @pending = {}
  @initialized = false
  @server_info = nil
  @capabilities = nil
  initialize_logger(logger)
  @max_retries   = retries
  @retry_backoff = retry_backoff
  @read_timeout  = read_timeout
  @env           = env || {}
  @elicitation_request_callback = nil # MCP 2025-06-18
  @roots_list_request_callback = nil # MCP 2025-06-18
  @sampling_request_callback = nil # MCP 2025-06-18
end

Instance Attribute Details

#capabilitiesHash? (readonly)

Server capabilities from the initialize response

Returns:

  • (Hash, nil)

    Server capabilities



60
61
62
# File 'lib/mcp_client/server_stdio.rb', line 60

def capabilities
  @capabilities
end

#commandString, Array (readonly)

Returns the command used to launch the server.

Returns:

  • (String, Array)

    the command used to launch the server



19
20
21
# File 'lib/mcp_client/server_stdio.rb', line 19

def command
  @command
end

#envObject (readonly)

Returns the value of attribute env.



19
# File 'lib/mcp_client/server_stdio.rb', line 19

attr_reader :command, :env

#server_infoHash? (readonly)

Server info from the initialize response

Returns:

  • (Hash, nil)

    Server information



56
57
58
# File 'lib/mcp_client/server_stdio.rb', line 56

def server_info
  @server_info
end

Instance Method Details

#call_tool(tool_name, parameters) ⇒ Object

Call a tool 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:



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/mcp_client/server_stdio.rb', line 324

def call_tool(tool_name, parameters)
  ensure_initialized
  req_id = next_id
  # JSON-RPC method for calling a tool
  req = {
    'jsonrpc' => '2.0',
    'id' => req_id,
    'method' => 'tools/call',
    'params' => { 'name' => tool_name, 'arguments' => parameters }
  }
  send_request(req)
  res = wait_response(req_id)
  if (err = res['error'])
    raise MCPClient::Errors::ServerError, err['message']
  end

  res['result']
rescue StandardError => e
  raise MCPClient::Errors::ToolCallError, "Error calling tool '#{tool_name}': #{e.message}"
end

#cleanupvoid

This method returns an undefined value.

Clean up the server connection Closes all stdio handles and terminates any running processes and threads



576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
# File 'lib/mcp_client/server_stdio.rb', line 576

def cleanup
  return unless @stdin

  @stdin.close unless @stdin.closed?
  @stdout.close unless @stdout.closed?
  @stderr.close unless @stderr.closed?
  if @wait_thread&.alive?
    Process.kill('TERM', @wait_thread.pid)
    @wait_thread.join(1)
  end
  @reader_thread&.kill
rescue StandardError
  # Clean up resources during unexpected termination
ensure
  @stdin = @stdout = @stderr = @wait_thread = @reader_thread = nil
end

#complete(ref:, argument:) ⇒ Hash

Request completion suggestions from the server (MCP 2025-06-18)

Parameters:

  • ref (Hash)

    reference object (e.g., { ‘type’ => ‘ref/prompt’, ‘name’ => ‘prompt_name’ })

  • argument (Hash)

    the argument being completed (e.g., { ‘name’ => ‘arg_name’, ‘value’ => ‘partial’ })

Returns:

  • (Hash)

    completion result with ‘values’, optional ‘total’, and ‘hasMore’ fields

Raises:



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/mcp_client/server_stdio.rb', line 350

def complete(ref:, argument:)
  ensure_initialized
  req_id = next_id
  req = {
    'jsonrpc' => '2.0',
    'id' => req_id,
    'method' => 'completion/complete',
    'params' => { 'ref' => ref, 'argument' => argument }
  }
  send_request(req)
  res = wait_response(req_id)
  if (err = res['error'])
    raise MCPClient::Errors::ServerError, err['message']
  end

  res.dig('result', 'completion') || { 'values' => [] }
rescue StandardError => e
  raise MCPClient::Errors::ServerError, "Error requesting completion: #{e.message}"
end

#connectBoolean

Connect to the MCP server by launching the command process via stdin/stdout

Returns:

  • (Boolean)

    true if connection was successful

Raises:



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/mcp_client/server_stdio.rb', line 65

def connect
  if @command_array
    if @env.any?
      @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(@env, *@command_array)
    else
      @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(*@command_array)
    end
  elsif @env.any?
    @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(@env, @command)
  else
    @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(@command)
  end
  true
rescue StandardError => e
  raise MCPClient::Errors::ConnectionError, "Failed to connect to MCP server: #{e.message}"
end

#get_prompt(prompt_name, parameters) ⇒ Object

Get a prompt with the given parameters

Parameters:

  • prompt_name (String)

    the name of the prompt to get

  • parameters (Hash)

    the parameters to pass to the prompt

Returns:

  • (Object)

    the result of the prompt interpolation

Raises:



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/mcp_client/server_stdio.rb', line 151

def get_prompt(prompt_name, parameters)
  ensure_initialized
  req_id = next_id
  # JSON-RPC method for getting a prompt
  req = {
    'jsonrpc' => '2.0',
    'id' => req_id,
    'method' => 'prompts/get',
    'params' => { 'name' => prompt_name, 'arguments' => parameters }
  }
  send_request(req)
  res = wait_response(req_id)
  if (err = res['error'])
    raise MCPClient::Errors::ServerError, err['message']
  end

  res['result']
rescue StandardError => e
  raise MCPClient::Errors::PromptGetError, "Error calling prompt '#{prompt_name}': #{e.message}"
end

#handle_elicitation_create(request_id, params) ⇒ void

This method returns an undefined value.

Handle elicitation/create request from server (MCP 2025-06-18)

Parameters:

  • request_id (String, Integer)

    the JSON-RPC request ID

  • params (Hash)

    the elicitation parameters



446
447
448
449
450
451
452
453
454
455
456
457
458
459
# File 'lib/mcp_client/server_stdio.rb', line 446

def handle_elicitation_create(request_id, params)
  # If no callback is registered, decline the request
  unless @elicitation_request_callback
    @logger.warn('Received elicitation request but no callback registered, declining')
    send_elicitation_response(request_id, { 'action' => 'decline' })
    return
  end

  # Call the registered callback
  result = @elicitation_request_callback.call(request_id, params)

  # Send the response back to the server
  send_elicitation_response(request_id, result)
end

#handle_line(line) ⇒ void

This method returns an undefined value.

Handle a line of output from the stdio server Parses JSON-RPC messages and adds them to pending responses

Parameters:

  • line (String)

    line of output to parse



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/mcp_client/server_stdio.rb', line 98

def handle_line(line)
  msg = JSON.parse(line)
  @logger.debug("Received line: #{line.chomp}")

  # Dispatch JSON-RPC requests from server (has id AND method) - MCP 2025-06-18
  if msg['method'] && msg.key?('id')
    handle_server_request(msg)
    return
  end

  # Dispatch JSON-RPC notifications (no id, has method)
  if msg['method'] && !msg.key?('id')
    @notification_callback&.call(msg['method'], msg['params'])
    return
  end

  # Handle standard JSON-RPC responses (has id, no method)
  id = msg['id']
  return unless id

  @mutex.synchronize do
    @pending[id] = msg
    @cond.broadcast
  end
rescue JSON::ParserError
  # Skip non-JSONRPC lines in the output stream
end

#handle_roots_list(request_id, params) ⇒ void

This method returns an undefined value.

Handle roots/list request from server (MCP 2025-06-18)

Parameters:

  • request_id (String, Integer)

    the JSON-RPC request ID

  • params (Hash)

    the request parameters



465
466
467
468
469
470
471
472
473
474
475
476
477
478
# File 'lib/mcp_client/server_stdio.rb', line 465

def handle_roots_list(request_id, params)
  # If no callback is registered, return empty roots list
  unless @roots_list_request_callback
    @logger.debug('Received roots/list request but no callback registered, returning empty list')
    send_roots_list_response(request_id, { 'roots' => [] })
    return
  end

  # Call the registered callback
  result = @roots_list_request_callback.call(request_id, params)

  # Send the response back to the server
  send_roots_list_response(request_id, result)
end

#handle_sampling_create_message(request_id, params) ⇒ void

This method returns an undefined value.

Handle sampling/createMessage request from server (MCP 2025-06-18)

Parameters:

  • request_id (String, Integer)

    the JSON-RPC request ID

  • params (Hash)

    the sampling parameters



484
485
486
487
488
489
490
491
492
493
494
495
496
497
# File 'lib/mcp_client/server_stdio.rb', line 484

def handle_sampling_create_message(request_id, params)
  # If no callback is registered, return error
  unless @sampling_request_callback
    @logger.warn('Received sampling request but no callback registered, returning error')
    send_error_response(request_id, -1, 'Sampling not supported')
    return
  end

  # Call the registered callback
  result = @sampling_request_callback.call(request_id, params)

  # Send the response back to the server
  send_sampling_response(request_id, result)
end

#handle_server_request(msg) ⇒ void

This method returns an undefined value.

Handle incoming JSON-RPC request from server (MCP 2025-06-18)

Parameters:

  • msg (Hash)

    the JSON-RPC request message



419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# File 'lib/mcp_client/server_stdio.rb', line 419

def handle_server_request(msg)
  request_id = msg['id']
  method = msg['method']
  params = msg['params'] || {}

  @logger.debug("Received server request: #{method} (id: #{request_id})")

  case method
  when 'elicitation/create'
    handle_elicitation_create(request_id, params)
  when 'roots/list'
    handle_roots_list(request_id, params)
  when 'sampling/createMessage'
    handle_sampling_create_message(request_id, params)
  else
    # Unknown request method, send error response
    send_error_response(request_id, -32_601, "Method not found: #{method}")
  end
rescue StandardError => e
  @logger.error("Error handling server request: #{e.message}")
  send_error_response(request_id, -32_603, "Internal error: #{e.message}")
end

#list_promptsArray<MCPClient::Prompt>

List all prompts available from the MCP server

Returns:

Raises:



130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/mcp_client/server_stdio.rb', line 130

def list_prompts
  ensure_initialized
  req_id = next_id
  req = { 'jsonrpc' => '2.0', 'id' => req_id, 'method' => 'prompts/list', 'params' => {} }
  send_request(req)
  res = wait_response(req_id)
  if (err = res['error'])
    raise MCPClient::Errors::ServerError, err['message']
  end

  (res.dig('result', 'prompts') || []).map { |td| MCPClient::Prompt.from_json(td, server: self) }
rescue StandardError => e
  raise MCPClient::Errors::PromptGetError, "Error listing prompts: #{e.message}"
end

#list_resource_templates(cursor: nil) ⇒ Hash

List all resource templates available from the MCP server

Parameters:

  • cursor (String, nil) (defaults to: nil)

    optional cursor for pagination

Returns:

  • (Hash)

    result containing resourceTemplates array and optional nextCursor

Raises:



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/mcp_client/server_stdio.rb', line 229

def list_resource_templates(cursor: nil)
  ensure_initialized
  req_id = next_id
  params = {}
  params['cursor'] = cursor if cursor
  req = { 'jsonrpc' => '2.0', 'id' => req_id, 'method' => 'resources/templates/list', 'params' => params }
  send_request(req)
  res = wait_response(req_id)
  if (err = res['error'])
    raise MCPClient::Errors::ServerError, err['message']
  end

  result = res['result'] || {}
  templates = (result['resourceTemplates'] || []).map { |td| MCPClient::ResourceTemplate.from_json(td, server: self) }
  { 'resourceTemplates' => templates, 'nextCursor' => result['nextCursor'] }
rescue StandardError => e
  raise MCPClient::Errors::ResourceReadError, "Error listing resource templates: #{e.message}"
end

#list_resources(cursor: nil) ⇒ Hash

List all resources available from the MCP server

Parameters:

  • cursor (String, nil) (defaults to: nil)

    optional cursor for pagination

Returns:

  • (Hash)

    result containing resources array and optional nextCursor

Raises:



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/mcp_client/server_stdio.rb', line 177

def list_resources(cursor: nil)
  ensure_initialized
  req_id = next_id
  params = {}
  params['cursor'] = cursor if cursor
  req = { 'jsonrpc' => '2.0', 'id' => req_id, 'method' => 'resources/list', 'params' => params }
  send_request(req)
  res = wait_response(req_id)
  if (err = res['error'])
    raise MCPClient::Errors::ServerError, err['message']
  end

  result = res['result'] || {}
  resources = (result['resources'] || []).map { |td| MCPClient::Resource.from_json(td, server: self) }
  { 'resources' => resources, 'nextCursor' => result['nextCursor'] }
rescue StandardError => e
  raise MCPClient::Errors::ResourceReadError, "Error listing resources: #{e.message}"
end

#list_toolsArray<MCPClient::Tool>

List all tools available from the MCP server

Returns:

Raises:



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/mcp_client/server_stdio.rb', line 302

def list_tools
  ensure_initialized
  req_id = next_id
  # JSON-RPC method for listing tools
  req = { 'jsonrpc' => '2.0', 'id' => req_id, 'method' => 'tools/list', 'params' => {} }
  send_request(req)
  res = wait_response(req_id)
  if (err = res['error'])
    raise MCPClient::Errors::ServerError, err['message']
  end

  (res.dig('result', 'tools') || []).map { |td| MCPClient::Tool.from_json(td, server: self) }
rescue StandardError => e
  raise MCPClient::Errors::ToolCallError, "Error listing tools: #{e.message}"
end

#log_level=(level) ⇒ Hash

Set the logging level on the server (MCP 2025-06-18)

Parameters:

  • level (String)

    the log level (‘debug’, ‘info’, ‘notice’, ‘warning’, ‘error’, ‘critical’, ‘alert’, ‘emergency’)

Returns:

  • (Hash)

    empty result on success

Raises:



375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'lib/mcp_client/server_stdio.rb', line 375

def log_level=(level)
  ensure_initialized
  req_id = next_id
  req = {
    'jsonrpc' => '2.0',
    'id' => req_id,
    'method' => 'logging/setLevel',
    'params' => { 'level' => level }
  }
  send_request(req)
  res = wait_response(req_id)
  if (err = res['error'])
    raise MCPClient::Errors::ServerError, err['message']
  end

  res['result'] || {}
rescue StandardError => e
  raise MCPClient::Errors::ServerError, "Error setting log level: #{e.message}"
end

#on_elicitation_request(&block) ⇒ void

This method returns an undefined value.

Register a callback for elicitation requests (MCP 2025-06-18)

Parameters:

  • block (Proc)

    callback that receives (request_id, params) and returns response hash



398
399
400
# File 'lib/mcp_client/server_stdio.rb', line 398

def on_elicitation_request(&block)
  @elicitation_request_callback = block
end

#on_roots_list_request(&block) ⇒ void

This method returns an undefined value.

Register a callback for roots/list requests (MCP 2025-06-18)

Parameters:

  • block (Proc)

    callback that receives (request_id, params) and returns response hash



405
406
407
# File 'lib/mcp_client/server_stdio.rb', line 405

def on_roots_list_request(&block)
  @roots_list_request_callback = block
end

#on_sampling_request(&block) ⇒ void

This method returns an undefined value.

Register a callback for sampling requests (MCP 2025-06-18)

Parameters:

  • block (Proc)

    callback that receives (request_id, params) and returns response hash



412
413
414
# File 'lib/mcp_client/server_stdio.rb', line 412

def on_sampling_request(&block)
  @sampling_request_callback = block
end

#read_resource(uri) ⇒ Array<MCPClient::ResourceContent>

Read a resource by its URI

Parameters:

  • uri (String)

    the URI of the resource to read

Returns:

Raises:



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/mcp_client/server_stdio.rb', line 201

def read_resource(uri)
  ensure_initialized
  req_id = next_id
  # JSON-RPC method for reading a resource
  req = {
    'jsonrpc' => '2.0',
    'id' => req_id,
    'method' => 'resources/read',
    'params' => { 'uri' => uri }
  }
  send_request(req)
  res = wait_response(req_id)
  if (err = res['error'])
    raise MCPClient::Errors::ServerError, err['message']
  end

  result = res['result'] || {}
  contents = result['contents'] || []
  contents.map { |content| MCPClient::ResourceContent.from_json(content) }
rescue StandardError => e
  raise MCPClient::Errors::ResourceReadError, "Error reading resource '#{uri}': #{e.message}"
end

#send_elicitation_response(request_id, result) ⇒ void

This method returns an undefined value.

Send elicitation response back to server (MCP 2025-06-18)

Parameters:

  • request_id (String, Integer)

    the JSON-RPC request ID

  • result (Hash)

    the elicitation result (action and optional content)



535
536
537
538
539
540
541
542
# File 'lib/mcp_client/server_stdio.rb', line 535

def send_elicitation_response(request_id, result)
  response = {
    'jsonrpc' => '2.0',
    'id' => request_id,
    'result' => result
  }
  send_message(response)
end

#send_error_response(request_id, code, message) ⇒ void

This method returns an undefined value.

Send error response back to server (MCP 2025-06-18)

Parameters:

  • request_id (String, Integer)

    the JSON-RPC request ID

  • code (Integer)

    the error code

  • message (String)

    the error message



549
550
551
552
553
554
555
556
557
558
559
# File 'lib/mcp_client/server_stdio.rb', line 549

def send_error_response(request_id, code, message)
  response = {
    'jsonrpc' => '2.0',
    'id' => request_id,
    'error' => {
      'code' => code,
      'message' => message
    }
  }
  send_message(response)
end

#send_message(message) ⇒ void

This method returns an undefined value.

Send a JSON-RPC message to the server

Parameters:

  • message (Hash)

    the message to send



564
565
566
567
568
569
570
571
# File 'lib/mcp_client/server_stdio.rb', line 564

def send_message(message)
  json = JSON.generate(message)
  @stdin.puts(json)
  @stdin.flush
  @logger.debug("Sent message: #{json}")
rescue StandardError => e
  @logger.error("Error sending message: #{e.message}")
end

#send_roots_list_response(request_id, result) ⇒ void

This method returns an undefined value.

Send roots/list response back to server (MCP 2025-06-18)

Parameters:

  • request_id (String, Integer)

    the JSON-RPC request ID

  • result (Hash)

    the roots list result



503
504
505
506
507
508
509
510
# File 'lib/mcp_client/server_stdio.rb', line 503

def send_roots_list_response(request_id, result)
  response = {
    'jsonrpc' => '2.0',
    'id' => request_id,
    'result' => result
  }
  send_message(response)
end

#send_sampling_response(request_id, result) ⇒ void

This method returns an undefined value.

Send sampling response back to server (MCP 2025-06-18)

Parameters:

  • request_id (String, Integer)

    the JSON-RPC request ID

  • result (Hash)

    the sampling result (role, content, model, stopReason)



516
517
518
519
520
521
522
523
524
525
526
527
528
529
# File 'lib/mcp_client/server_stdio.rb', line 516

def send_sampling_response(request_id, result)
  # Check if result contains an error
  if result.is_a?(Hash) && result['error']
    send_error_response(request_id, result['error']['code'] || -1, result['error']['message'] || 'Sampling error')
    return
  end

  response = {
    'jsonrpc' => '2.0',
    'id' => request_id,
    'result' => result
  }
  send_message(response)
end

#start_readerThread

Spawn a reader thread to collect JSON-RPC responses

Returns:

  • (Thread)

    the reader thread



84
85
86
87
88
89
90
91
92
# File 'lib/mcp_client/server_stdio.rb', line 84

def start_reader
  @reader_thread = Thread.new do
    @stdout.each_line do |line|
      handle_line(line)
    end
  rescue StandardError
    # Reader thread aborted unexpectedly
  end
end

#subscribe_resource(uri) ⇒ Boolean

Subscribe to resource updates

Parameters:

  • uri (String)

    the URI of the resource to subscribe to

Returns:

  • (Boolean)

    true if subscription successful

Raises:



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/mcp_client/server_stdio.rb', line 253

def subscribe_resource(uri)
  ensure_initialized
  req_id = next_id
  req = {
    'jsonrpc' => '2.0',
    'id' => req_id,
    'method' => 'resources/subscribe',
    'params' => { 'uri' => uri }
  }
  send_request(req)
  res = wait_response(req_id)
  if (err = res['error'])
    raise MCPClient::Errors::ServerError, err['message']
  end

  true
rescue StandardError => e
  raise MCPClient::Errors::ResourceReadError, "Error subscribing to resource '#{uri}': #{e.message}"
end

#unsubscribe_resource(uri) ⇒ Boolean

Unsubscribe from resource updates

Parameters:

  • uri (String)

    the URI of the resource to unsubscribe from

Returns:

  • (Boolean)

    true if unsubscription successful

Raises:



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/mcp_client/server_stdio.rb', line 278

def unsubscribe_resource(uri)
  ensure_initialized
  req_id = next_id
  req = {
    'jsonrpc' => '2.0',
    'id' => req_id,
    'method' => 'resources/unsubscribe',
    'params' => { 'uri' => uri }
  }
  send_request(req)
  res = wait_response(req_id)
  if (err = res['error'])
    raise MCPClient::Errors::ServerError, err['message']
  end

  true
rescue StandardError => e
  raise MCPClient::Errors::ResourceReadError, "Error unsubscribing from resource '#{uri}': #{e.message}"
end