Class: VSM::MCP::JSONRPC::Stdio

Inherits:
Object
  • Object
show all
Includes:
MonitorMixin
Defined in:
lib/vsm/mcp/jsonrpc.rb

Overview

Minimal NDJSON (one JSON per line) JSON-RPC transport over IO. Note: MCP servers often speak LSP framing; we can add that later.

Instance Method Summary collapse

Constructor Details

#initialize(r:, w:) ⇒ Stdio

Returns a new instance of Stdio.



13
14
15
16
17
18
# File 'lib/vsm/mcp/jsonrpc.rb', line 13

def initialize(r:, w:)
  @r = r
  @w = w
  @seq = 0
  mon_initialize
end

Instance Method Details

#notify(method, params = {}) ⇒ Object



34
35
36
# File 'lib/vsm/mcp/jsonrpc.rb', line 34

def notify(method, params = {})
  write({ jsonrpc: "2.0", method: method, params: params })
end

#readObject



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/vsm/mcp/jsonrpc.rb', line 38

def read
  line = @r.gets
  return nil unless line
  # Handle LSP-style framing: "Content-Length: N" followed by blank line and JSON body.
  if line =~ /\AContent-Length:\s*(\d+)\s*\r?\n?\z/i
    length = Integer($1)
    # Consume optional additional headers until blank line
    while (hdr = @r.gets)
      break if hdr.strip.empty?
    end
    body = read_exact(length)
    $stderr.puts("[mcp-rpc] < #{body}") if ENV["VSM_MCP_DEBUG"] == "1"
    return JSON.parse(body)
  end
  # Otherwise assume NDJSON (one JSON object per line)
  $stderr.puts("[mcp-rpc] < #{line.strip}") if ENV["VSM_MCP_DEBUG"] == "1"
  JSON.parse(line)
end

#request(method, params = {}) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/vsm/mcp/jsonrpc.rb', line 20

def request(method, params = {})
  id = next_id
  write({ jsonrpc: "2.0", id: id, method: method, params: params })
  loop do
    msg = read
    next unless msg
    if msg["id"].to_s == id.to_s
      err = msg["error"]
      raise(err.is_a?(Hash) ? (err["message"] || err.inspect) : err.to_s) if err
      return msg["result"]
    end
  end
end

#write(obj) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/vsm/mcp/jsonrpc.rb', line 57

def write(obj)
  body = JSON.dump(obj)
  $stderr.puts("[mcp-rpc] > #{body}") if ENV["VSM_MCP_DEBUG"] == "1"
  synchronize do
    # Prefer NDJSON for broad compatibility; some servers require LSP.
    # If VSM_MCP_LSP=1, use Content-Length framing.
    if ENV["VSM_MCP_LSP"] == "1"
      @w.write("Content-Length: #{body.bytesize}\r\n\r\n")
      @w.write(body)
      @w.flush
    else
      @w.puts(body)
      @w.flush
    end
  end
end