actionmcp 0.102.0 → 0.104.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +46 -3
- data/app/models/action_mcp/session.rb +6 -5
- data/lib/action_mcp/configuration.rb +44 -8
- data/lib/action_mcp/server/base_session.rb +5 -1
- data/lib/action_mcp/test_helper/session_store_assertions.rb +0 -70
- data/lib/action_mcp/version.rb +1 -1
- data/lib/action_mcp.rb +0 -1
- data/lib/generators/action_mcp/identifier/templates/identifier.rb.erb +4 -4
- data/lib/generators/action_mcp/install/templates/mcp.yml +11 -1
- data/lib/generators/action_mcp/tool/templates/tool.rb.erb +2 -2
- metadata +1 -26
- data/lib/action_mcp/client/active_record_session_store.rb +0 -57
- data/lib/action_mcp/client/base.rb +0 -225
- data/lib/action_mcp/client/blueprint.rb +0 -163
- data/lib/action_mcp/client/catalog.rb +0 -164
- data/lib/action_mcp/client/collection.rb +0 -168
- data/lib/action_mcp/client/elicitation.rb +0 -34
- data/lib/action_mcp/client/json_rpc_handler.rb +0 -202
- data/lib/action_mcp/client/logging.rb +0 -19
- data/lib/action_mcp/client/messaging.rb +0 -28
- data/lib/action_mcp/client/prompt_book.rb +0 -117
- data/lib/action_mcp/client/prompts.rb +0 -47
- data/lib/action_mcp/client/request_timeouts.rb +0 -74
- data/lib/action_mcp/client/resources.rb +0 -100
- data/lib/action_mcp/client/roots.rb +0 -13
- data/lib/action_mcp/client/server.rb +0 -60
- data/lib/action_mcp/client/session_store.rb +0 -39
- data/lib/action_mcp/client/session_store_factory.rb +0 -27
- data/lib/action_mcp/client/streamable_client.rb +0 -264
- data/lib/action_mcp/client/streamable_http_transport.rb +0 -306
- data/lib/action_mcp/client/test_session_store.rb +0 -84
- data/lib/action_mcp/client/toolbox.rb +0 -199
- data/lib/action_mcp/client/tools.rb +0 -47
- data/lib/action_mcp/client/transport.rb +0 -137
- data/lib/action_mcp/client/volatile_session_store.rb +0 -38
- data/lib/action_mcp/client.rb +0 -71
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module ActionMCP
|
|
4
|
-
module Client
|
|
5
|
-
module Resources
|
|
6
|
-
# List all available resources from the server
|
|
7
|
-
# @param params [Hash] Optional parameters for pagination
|
|
8
|
-
# @option params [String] :cursor Pagination cursor for fetching next page
|
|
9
|
-
# @option params [Integer] :limit Maximum number of items to return
|
|
10
|
-
# @return [String] Request ID for tracking the request
|
|
11
|
-
def list_resources(params = {})
|
|
12
|
-
request_id = SecureRandom.uuid_v7
|
|
13
|
-
|
|
14
|
-
# Send request with pagination parameters if provided
|
|
15
|
-
request_params = {}
|
|
16
|
-
request_params[:cursor] = params[:cursor] if params[:cursor]
|
|
17
|
-
request_params[:limit] = params[:limit] if params[:limit]
|
|
18
|
-
|
|
19
|
-
send_jsonrpc_request("resources/list",
|
|
20
|
-
params: request_params.empty? ? nil : request_params,
|
|
21
|
-
id: request_id)
|
|
22
|
-
|
|
23
|
-
# Return request ID for tracking the request
|
|
24
|
-
request_id
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# List resource templates from the server
|
|
28
|
-
# @param params [Hash] Optional parameters for pagination
|
|
29
|
-
# @option params [String] :cursor Pagination cursor for fetching next page
|
|
30
|
-
# @option params [Integer] :limit Maximum number of items to return
|
|
31
|
-
# @return [String] Request ID for tracking the request
|
|
32
|
-
def list_resource_templates(params = {})
|
|
33
|
-
request_id = SecureRandom.uuid_v7
|
|
34
|
-
|
|
35
|
-
# Send request with pagination parameters if provided
|
|
36
|
-
request_params = {}
|
|
37
|
-
request_params[:cursor] = params[:cursor] if params[:cursor]
|
|
38
|
-
request_params[:limit] = params[:limit] if params[:limit]
|
|
39
|
-
|
|
40
|
-
send_jsonrpc_request("resources/templates/list",
|
|
41
|
-
params: request_params.empty? ? nil : request_params,
|
|
42
|
-
id: request_id)
|
|
43
|
-
|
|
44
|
-
# Return request ID for tracking the request
|
|
45
|
-
request_id
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Read a specific resource
|
|
49
|
-
# @param uri [String] URI of the resource to read
|
|
50
|
-
# @return [String] Request ID for tracking the request
|
|
51
|
-
def read_resource(uri)
|
|
52
|
-
request_id = SecureRandom.uuid_v7
|
|
53
|
-
|
|
54
|
-
# Send request
|
|
55
|
-
send_jsonrpc_request("resources/read",
|
|
56
|
-
params: { uri: uri },
|
|
57
|
-
id: request_id)
|
|
58
|
-
|
|
59
|
-
# Return request ID for tracking the request
|
|
60
|
-
request_id
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Subscribe to updates for a specific resource
|
|
64
|
-
# @param uri [String] URI of the resource to subscribe to
|
|
65
|
-
# @param update_callback [Proc] Callback for resource updates
|
|
66
|
-
# @return [String] Request ID for tracking the request
|
|
67
|
-
def subscribe_resource(uri, update_callback)
|
|
68
|
-
@resource_subscriptions ||= {}
|
|
69
|
-
@resource_subscriptions[uri] = update_callback
|
|
70
|
-
|
|
71
|
-
request_id = SecureRandom.uuid_v7
|
|
72
|
-
|
|
73
|
-
# Send request
|
|
74
|
-
send_jsonrpc_request("resources/subscribe",
|
|
75
|
-
params: { uri: uri },
|
|
76
|
-
id: request_id)
|
|
77
|
-
|
|
78
|
-
# Return request ID for tracking the request
|
|
79
|
-
request_id
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Unsubscribe from updates for a specific resource
|
|
83
|
-
# @param uri [String] URI of the resource to unsubscribe from
|
|
84
|
-
# @return [String] Request ID for tracking the request
|
|
85
|
-
def unsubscribe_resource(uri)
|
|
86
|
-
@resource_subscriptions&.delete(uri)
|
|
87
|
-
|
|
88
|
-
request_id = SecureRandom.uuid_v7
|
|
89
|
-
|
|
90
|
-
# Send request
|
|
91
|
-
send_jsonrpc_request("resources/unsubscribe",
|
|
92
|
-
params: { uri: uri },
|
|
93
|
-
id: request_id)
|
|
94
|
-
|
|
95
|
-
# Return request ID for tracking the request
|
|
96
|
-
request_id
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module ActionMCP
|
|
4
|
-
module Client
|
|
5
|
-
module Roots
|
|
6
|
-
# Notify the server that the roots list has changed
|
|
7
|
-
def roots_list_changed_notification
|
|
8
|
-
send_jsonrpc_notification("notifications/roots/list_changed")
|
|
9
|
-
true
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module ActionMCP
|
|
4
|
-
module Client
|
|
5
|
-
class Server
|
|
6
|
-
attr_reader :name, :version, :server_info, :capabilities
|
|
7
|
-
|
|
8
|
-
def initialize(data)
|
|
9
|
-
# Store protocol version if needed for later use
|
|
10
|
-
@protocol_version = data["protocolVersion"]
|
|
11
|
-
|
|
12
|
-
# Extract server information
|
|
13
|
-
@server_info = data["serverInfo"] || {}
|
|
14
|
-
@name = server_info["name"]
|
|
15
|
-
@version = server_info["version"]
|
|
16
|
-
|
|
17
|
-
# Store capabilities for dynamic checking
|
|
18
|
-
@capabilities = data["capabilities"] || {}
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# Check if 'tools' capability is present
|
|
22
|
-
def tools?
|
|
23
|
-
@capabilities.key?("tools")
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Check if 'prompts' capability is present
|
|
27
|
-
def prompts?
|
|
28
|
-
@capabilities.key?("prompts")
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Check if tools have a dynamic state based on listChanged flag
|
|
32
|
-
def dynamic_tools?
|
|
33
|
-
tool_cap = @capabilities["tools"] || {}
|
|
34
|
-
tool_cap["listChanged"] == true
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Check if logging capability exists
|
|
38
|
-
def logging?
|
|
39
|
-
@capabilities.key?("logging")
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Check if resources capability exists
|
|
43
|
-
def resources?
|
|
44
|
-
@capabilities.key?("resources")
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Check if resources have a dynamic state based on listChanged flag
|
|
48
|
-
def dynamic_resources?
|
|
49
|
-
resources_cap = @capabilities["resources"] || {}
|
|
50
|
-
resources_cap["listChanged"] == true
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def inspect
|
|
54
|
-
"#<#{self.class.name} name: #{name}, version: #{version} with resources: #{resources?}, tools: #{tools?}, prompts: #{prompts?}, logging: #{logging?}>"
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
alias to_s inspect
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module ActionMCP
|
|
4
|
-
module Client
|
|
5
|
-
# Abstract interface for session storage
|
|
6
|
-
module SessionStore
|
|
7
|
-
# Load session data by ID
|
|
8
|
-
def load_session(session_id)
|
|
9
|
-
raise NotImplementedError, "#{self.class} must implement #load_session"
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# Save session data
|
|
13
|
-
def save_session(session_id, session_data)
|
|
14
|
-
raise NotImplementedError, "#{self.class} must implement #save_session"
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# Delete session
|
|
18
|
-
def delete_session(session_id)
|
|
19
|
-
raise NotImplementedError, "#{self.class} must implement #delete_session"
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Check if session exists
|
|
23
|
-
def session_exists?(session_id)
|
|
24
|
-
raise NotImplementedError, "#{self.class} must implement #session_exists?"
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Update specific session attributes
|
|
28
|
-
def update_session(session_id, attributes)
|
|
29
|
-
session_data = load_session(session_id)
|
|
30
|
-
return nil unless session_data
|
|
31
|
-
|
|
32
|
-
session_data.merge!(attributes)
|
|
33
|
-
save_session(session_id, session_data)
|
|
34
|
-
# Return the reloaded session to get the actual saved values
|
|
35
|
-
load_session(session_id)
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module ActionMCP
|
|
4
|
-
module Client
|
|
5
|
-
# Factory for creating session stores
|
|
6
|
-
class SessionStoreFactory
|
|
7
|
-
def self.create(type = nil, **_options)
|
|
8
|
-
type ||= default_type
|
|
9
|
-
|
|
10
|
-
case type.to_sym
|
|
11
|
-
when :volatile, :memory
|
|
12
|
-
VolatileSessionStore.new
|
|
13
|
-
when :active_record, :persistent
|
|
14
|
-
ActiveRecordSessionStore.new
|
|
15
|
-
when :test
|
|
16
|
-
TestSessionStore.new
|
|
17
|
-
else
|
|
18
|
-
raise ArgumentError, "Unknown session store type: #{type}"
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def self.default_type
|
|
23
|
-
ActionMCP.configuration.client_session_store_type
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module ActionMCP
|
|
4
|
-
module Client
|
|
5
|
-
# MCP client using Server-Sent Events (SSE) transport
|
|
6
|
-
class StreamableClient < Base
|
|
7
|
-
# Define a custom error class for connection issues
|
|
8
|
-
class ConnectionError < StandardError; end
|
|
9
|
-
|
|
10
|
-
SSE_TIMEOUT = 10
|
|
11
|
-
ENDPOINT_TIMEOUT = 5 # Seconds
|
|
12
|
-
|
|
13
|
-
attr_reader :base_url, :sse_path, :post_url, :session
|
|
14
|
-
|
|
15
|
-
def initialize(url, connect: true, logger: ActionMCP.logger, **_options)
|
|
16
|
-
gem "faraday", ">= 2.0"
|
|
17
|
-
require "faraday"
|
|
18
|
-
require "uri"
|
|
19
|
-
super(logger: logger)
|
|
20
|
-
@type = :sse
|
|
21
|
-
setup_connection(url)
|
|
22
|
-
@buffer = +""
|
|
23
|
-
@stop_requested = false
|
|
24
|
-
@endpoint_received = false
|
|
25
|
-
@endpoint_mutex = Mutex.new
|
|
26
|
-
@endpoint_condition = ConditionVariable.new
|
|
27
|
-
@connection_mutex = Mutex.new
|
|
28
|
-
@connection_condition = ConditionVariable.new
|
|
29
|
-
|
|
30
|
-
self.connect if connect
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
protected
|
|
34
|
-
|
|
35
|
-
def start_transport
|
|
36
|
-
log_debug("Connecting to #{@base_url}#{@sse_path}...")
|
|
37
|
-
@stop_requested = false
|
|
38
|
-
|
|
39
|
-
# Reset connection state before starting
|
|
40
|
-
@connection_mutex.synchronize do
|
|
41
|
-
@connected = false
|
|
42
|
-
@connection_error = nil
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# Start connection thread
|
|
46
|
-
@sse_thread = Thread.new { listen_sse }
|
|
47
|
-
|
|
48
|
-
# Wait for endpoint
|
|
49
|
-
wait_for_endpoint
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def stop_transport
|
|
53
|
-
log_debug("Stopping SSE connection...")
|
|
54
|
-
@stop_requested = true
|
|
55
|
-
cleanup_sse_thread
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def send_message(json_rpc)
|
|
59
|
-
response = @conn.post(post_url,
|
|
60
|
-
json_rpc,
|
|
61
|
-
{ "Content-Type" => "application/json" })
|
|
62
|
-
response.success?
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def ready?
|
|
66
|
-
endpoint_ready?
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
private
|
|
70
|
-
|
|
71
|
-
def setup_connection(url)
|
|
72
|
-
uri = URI.parse(url)
|
|
73
|
-
@base_url = "#{uri.scheme}://#{uri.host}:#{uri.port}"
|
|
74
|
-
@sse_path = uri.path
|
|
75
|
-
|
|
76
|
-
@conn = Faraday.new(url: @base_url) do |f|
|
|
77
|
-
f.headers["User-Agent"] = user_agent
|
|
78
|
-
f.options.timeout = nil # No read timeout
|
|
79
|
-
f.options.open_timeout = SSE_TIMEOUT # Connection timeout
|
|
80
|
-
|
|
81
|
-
# Use Net::HTTP adapter explicitly as it works well with streaming
|
|
82
|
-
f.adapter :net_http do |http|
|
|
83
|
-
http.read_timeout = nil # No read timeout at adapter level too
|
|
84
|
-
http.open_timeout = SSE_TIMEOUT # Connection timeout
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
@post_url = nil
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def wait_for_endpoint
|
|
92
|
-
success = false
|
|
93
|
-
error = nil
|
|
94
|
-
|
|
95
|
-
@endpoint_mutex.synchronize do
|
|
96
|
-
unless @endpoint_received
|
|
97
|
-
# Wait with timeout for endpoint
|
|
98
|
-
timeout = @endpoint_condition.wait(@endpoint_mutex, ENDPOINT_TIMEOUT)
|
|
99
|
-
|
|
100
|
-
# Handle timeout
|
|
101
|
-
unless timeout || @endpoint_received
|
|
102
|
-
error = "Timeout waiting for MCP endpoint (#{ENDPOINT_TIMEOUT} seconds)"
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
success = @endpoint_received
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
if error
|
|
110
|
-
log_error(error)
|
|
111
|
-
raise ConnectionError, error
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# If we have the endpoint, consider the connection successful
|
|
115
|
-
if success
|
|
116
|
-
@connection_mutex.synchronize do
|
|
117
|
-
@connected = true
|
|
118
|
-
@connection_condition.broadcast
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
success
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def endpoint_ready?
|
|
126
|
-
@endpoint_mutex.synchronize { @endpoint_received }
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def listen_sse
|
|
130
|
-
log_debug("Starting SSE listener...")
|
|
131
|
-
|
|
132
|
-
begin
|
|
133
|
-
@conn.get(@sse_path) do |req|
|
|
134
|
-
req.headers["Accept"] = "text/event-stream"
|
|
135
|
-
req.headers["Cache-Control"] = "no-cache"
|
|
136
|
-
|
|
137
|
-
req.options.on_data = proc do |chunk, bytes|
|
|
138
|
-
handle_sse_data(chunk, bytes)
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
# This should never be reached during normal operation
|
|
143
|
-
# as the SSE connection stays open
|
|
144
|
-
rescue Faraday::ConnectionFailed => e
|
|
145
|
-
handle_connection_error(format_connection_error(e))
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def format_connection_error(error)
|
|
150
|
-
if error.message.include?("Connection refused")
|
|
151
|
-
"Connection refused - server at #{@base_url} is not running or not accepting connections"
|
|
152
|
-
else
|
|
153
|
-
"Connection failed: #{error.message}"
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def connection_error=(message)
|
|
158
|
-
@connection_mutex.synchronize do
|
|
159
|
-
@connection_error = message
|
|
160
|
-
@connection_condition.broadcast
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def handle_connection_error(message)
|
|
165
|
-
log_error("SSE connection failed: #{message}")
|
|
166
|
-
self.connection_error = message
|
|
167
|
-
@connected = false
|
|
168
|
-
@error_callback&.call(StandardError.new(message))
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def handle_sse_data(chunk, _overall_bytes)
|
|
172
|
-
process_chunk(chunk)
|
|
173
|
-
throw :halt if @stop_requested
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def process_chunk(chunk)
|
|
177
|
-
@buffer << chunk
|
|
178
|
-
# If the buffer does not contain a newline but appears to be a complete JSON object,
|
|
179
|
-
# flush it as a complete event.
|
|
180
|
-
if @buffer.strip.match?(/^\{.*\}$/)
|
|
181
|
-
(@current_event ||= []) << @buffer.strip
|
|
182
|
-
@buffer = +""
|
|
183
|
-
return handle_complete_event
|
|
184
|
-
end
|
|
185
|
-
process_buffer while @buffer.include?("\n")
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def process_buffer
|
|
189
|
-
line, _sep, rest = @buffer.partition("\n")
|
|
190
|
-
@buffer = rest
|
|
191
|
-
|
|
192
|
-
if line.strip.empty?
|
|
193
|
-
handle_complete_event
|
|
194
|
-
else
|
|
195
|
-
(@current_event ||= []) << line.strip
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
def handle_complete_event
|
|
200
|
-
return unless @current_event
|
|
201
|
-
|
|
202
|
-
handle_event(@current_event)
|
|
203
|
-
@current_event = nil
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def handle_event(lines)
|
|
207
|
-
event_data = parse_event(lines)
|
|
208
|
-
process_event(event_data)
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
def parse_event(lines)
|
|
212
|
-
event_data = { type: "message", data: +"" }
|
|
213
|
-
has_data_prefix = false
|
|
214
|
-
|
|
215
|
-
lines.each do |line|
|
|
216
|
-
if line.start_with?("event:")
|
|
217
|
-
event_data[:type] = line.split(":", 2)[1].strip
|
|
218
|
-
elsif line.start_with?("data:")
|
|
219
|
-
has_data_prefix = true
|
|
220
|
-
event_data[:data] << line.split(":", 2)[1].strip
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
# If no "data:" prefix was found, treat the entire event as data
|
|
225
|
-
event_data[:data] = lines.join("\n") unless has_data_prefix
|
|
226
|
-
event_data
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
def process_event(event_data)
|
|
230
|
-
case event_data[:type]
|
|
231
|
-
when "endpoint" then set_post_endpoint(event_data[:data])
|
|
232
|
-
when "message" then handle_raw_message(event_data[:data])
|
|
233
|
-
else log_error("Unknown event type: #{event_data[:type]}")
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
def set_post_endpoint(endpoint_path)
|
|
238
|
-
@post_url = build_post_url(endpoint_path)
|
|
239
|
-
log_debug("Received POST endpoint: #{post_url}")
|
|
240
|
-
|
|
241
|
-
# Signal that we have received the endpoint
|
|
242
|
-
@endpoint_mutex.synchronize do
|
|
243
|
-
@endpoint_received = true
|
|
244
|
-
@endpoint_condition.broadcast
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
# Now that we have the endpoint, send initial capabilities
|
|
248
|
-
send_initial_capabilities
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
def build_post_url(endpoint_path)
|
|
252
|
-
URI.join(@base_url, endpoint_path).to_s
|
|
253
|
-
rescue StandardError
|
|
254
|
-
"#{@base_url}#{endpoint_path}"
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
def cleanup_sse_thread
|
|
258
|
-
return unless @sse_thread
|
|
259
|
-
|
|
260
|
-
@sse_thread.join(SSE_TIMEOUT) || @sse_thread.kill
|
|
261
|
-
end
|
|
262
|
-
end
|
|
263
|
-
end
|
|
264
|
-
end
|