Class: SmilyCli::Mcp::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/smily_cli/mcp/client.rb

Overview

A minimal MCP client for BookingSync's Streamable-HTTP transport.

It speaks JSON-RPC 2.0 over POST <url>: it performs the initialize handshake (capturing the Mcp-Session-Id and negotiated protocol version), then issues tools/list / tools/call (or any method) within that session. Responses are accepted as either application/json or a single SSE message frame, so it interoperates with both response modes the server may pick.

The session is established lazily on first use and reused for the life of the client (i.e. for a single CLI invocation).

Constant Summary collapse

PROTOCOL_VERSION =
"2025-06-18"
SESSION_HEADER =
"Mcp-Session-Id"

Instance Method Summary collapse

Constructor Details

#initialize(token:, url:, verbose: false) ⇒ Client

Returns a new instance of Client.

Parameters:

  • token (String)

    MCP access token (sent as a Bearer token)

  • url (String)

    MCP server endpoint, e.g. https://www.bookingsync.com/mcp

  • verbose (Boolean) (defaults to: false)

    log traffic to stderr



27
28
29
30
31
32
33
34
35
# File 'lib/smily_cli/mcp/client.rb', line 27

def initialize(token:, url:, verbose: false)
  @token = token
  @uri = URI(url)
  @verbose = verbose
  @id = 0
  @session_id = nil
  @protocol_version = nil
  @server_info = nil
end

Instance Method Details

#call_tool(name, arguments = {}) ⇒ Hash

Call a tool by name.

Parameters:

  • name (String)
  • arguments (Hash) (defaults to: {})

Returns:

  • (Hash)

    the tool result ({ "content" =>, "structuredContent" =>, "isError" => })



67
68
69
70
# File 'lib/smily_cli/mcp/client.rb', line 67

def call_tool(name, arguments = {})
  connect
  rpc("tools/call", { "name" => name, "arguments" => arguments })
end

#closevoid

This method returns an undefined value.

End the session (best-effort; ignores failures).



81
82
83
84
85
86
87
88
89
# File 'lib/smily_cli/mcp/client.rb', line 81

def close
  return unless @session_id

  http_request(Net::HTTP::Delete.new(@uri))
rescue StandardError
  nil
ensure
  @session_id = nil
end

#connectHash

Perform (once) the initialize handshake and return the server's info: { "protocolVersion" =>, "capabilities" =>, "serverInfo" => }.

Returns:

  • (Hash)


41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/smily_cli/mcp/client.rb', line 41

def connect
  return @server_info if @server_info

  result = rpc("initialize", {
                 "protocolVersion" => PROTOCOL_VERSION,
                 "capabilities" => {},
                 "clientInfo" => { "name" => "smily-cli", "version" => SmilyCli::VERSION }
               }, expect_session: true)

  @protocol_version = result["protocolVersion"] || PROTOCOL_VERSION
  # Best-effort per MCP spec; the server accepts it as a 202 notification.
  notify("notifications/initialized")
  @server_info = result
end

#list_resourcesArray<Hash>

Returns MCP resource descriptors, if the server exposes any.

Returns:

  • (Array<Hash>)

    MCP resource descriptors, if the server exposes any



73
74
75
76
# File 'lib/smily_cli/mcp/client.rb', line 73

def list_resources
  connect
  collect_paginated("resources/list", "resources")
end

#list_toolsArray<Hash>

Returns tool definitions ({ "name", "description", "inputSchema" }).

Returns:

  • (Array<Hash>)

    tool definitions ({ "name", "description", "inputSchema" })



57
58
59
60
# File 'lib/smily_cli/mcp/client.rb', line 57

def list_tools
  connect
  collect_paginated("tools/list", "tools")
end