Skip to content

Commit c89ee14

Browse files
committed
client: add session initialization
- Send initialize request with protocolVersion and clientInfo - Store session_info from server response - Send notifications/initialized after successful init - Methods now automatically initialize session on first use - Add session management with Mcp-Session-Id header - Track session_id from server responses - Automatically include Mcp-Session-Id header in subsequent requests - Expose session_id as readable `Client` attribute
1 parent 753676f commit c89ee14

File tree

2 files changed

+70
-2
lines changed

2 files changed

+70
-2
lines changed

lib/mcp/client.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,44 @@ def initialize(transport:, client_info: nil, protocol_version: "2025-06-18")
2727
# So keeping it public
2828
attr_reader :transport, :client_info, :protocol_version, :session_info
2929

30+
# Initializes the MCP session with the server.
31+
# This method performs the MCP handshake: sends an initialize request,
32+
# receives session information, and sends the initialized notification.
33+
#
34+
# @return [Hash] The session information including server_info
35+
#
36+
# @example
37+
# client.init
38+
# # => { server_info: { name: "my-server", version: "1.0.0" }, ... }
39+
def init
40+
return @session_info if @initialized
41+
42+
response = transport.send_request(request: {
43+
jsonrpc: JsonRpcHandler::Version::V2_0,
44+
id: request_id,
45+
method: "initialize",
46+
params: {
47+
protocolVersion: protocol_version,
48+
capabilities: {},
49+
clientInfo: {
50+
name: client_info[:name] || client_info["name"],
51+
version: client_info[:version] || client_info["version"],
52+
},
53+
},
54+
})
55+
56+
@session_info = response.dig("result")
57+
@initialized = true
58+
59+
# Send the initialized notification
60+
transport.send_notification(notification: {
61+
jsonrpc: JsonRpcHandler::Version::V2_0,
62+
method: "notifications/initialized",
63+
})
64+
65+
@session_info
66+
end
67+
3068
# Returns the list of tools available from the server.
3169
# Each call will make a new request – the result is not cached.
3270
#
@@ -38,6 +76,8 @@ def initialize(transport:, client_info: nil, protocol_version: "2025-06-18")
3876
# puts tool.name
3977
# end
4078
def tools
79+
init unless @initialized
80+
4181
response = transport.send_request(request: {
4282
jsonrpc: JsonRpcHandler::Version::V2_0,
4383
id: request_id,
@@ -58,6 +98,8 @@ def tools
5898
#
5999
# @return [Array<Hash>] An array of available resources.
60100
def resources
101+
init unless @initialized
102+
61103
response = transport.send_request(request: {
62104
jsonrpc: JsonRpcHandler::Version::V2_0,
63105
id: request_id,
@@ -82,6 +124,8 @@ def resources
82124
# The exact requirements for `arguments` are determined by the transport layer in use.
83125
# Consult the documentation for your transport (e.g., MCP::Client::HTTP) for details.
84126
def call_tool(tool:, arguments: nil)
127+
init unless @initialized
128+
85129
transport.send_request(request: {
86130
jsonrpc: JsonRpcHandler::Version::V2_0,
87131
id: request_id,
@@ -95,6 +139,8 @@ def call_tool(tool:, arguments: nil)
95139
# @param uri [String] The URI of the resource to read.
96140
# @return [Array<Hash>] An array of resource contents (text or blob).
97141
def read_resource(uri:)
142+
init unless @initialized
143+
98144
response = transport.send_request(request: {
99145
jsonrpc: JsonRpcHandler::Version::V2_0,
100146
id: request_id,

lib/mcp/client/http.rb

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,29 @@
33
module MCP
44
class Client
55
class HTTP
6-
attr_reader :url
6+
attr_reader :url, :session_id
77

88
def initialize(url:, headers: {})
99
@url = url
1010
@headers = headers
11+
@session_id = nil
1112
end
1213

1314
def send_request(request:)
1415
method = request[:method] || request["method"]
1516
params = request[:params] || request["params"]
1617

17-
client.post("", request).body
18+
# Update session header if we have one
19+
update_session_header!
20+
21+
response = client.post("", request)
22+
23+
# Store session ID from response headers if present
24+
if response.headers["Mcp-Session-Id"]
25+
@session_id = response.headers["Mcp-Session-Id"]
26+
end
27+
28+
response.body
1829
rescue Faraday::BadRequestError => e
1930
raise RequestHandlerError.new(
2031
"The #{method} request is invalid",
@@ -76,6 +87,17 @@ def client
7687
end
7788
end
7889

90+
# Updates the session header on the Faraday client
91+
def update_session_header!
92+
return unless @client
93+
94+
if @session_id
95+
@client.headers["Mcp-Session-Id"] = @session_id
96+
else
97+
@client.headers.delete("Mcp-Session-Id")
98+
end
99+
end
100+
79101
def require_faraday!
80102
require "faraday"
81103
rescue LoadError

0 commit comments

Comments
 (0)