actionmcp 0.31.1 → 0.33.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 +143 -5
- data/app/controllers/action_mcp/mcp_controller.rb +13 -17
- data/app/controllers/action_mcp/messages_controller.rb +3 -1
- data/app/controllers/action_mcp/sse_controller.rb +22 -4
- data/app/controllers/action_mcp/unified_controller.rb +147 -52
- data/app/models/action_mcp/session/message.rb +1 -0
- data/app/models/action_mcp/session/sse_event.rb +55 -0
- data/app/models/action_mcp/session.rb +235 -12
- data/app/models/concerns/mcp_console_helpers.rb +68 -0
- data/app/models/concerns/mcp_message_inspect.rb +73 -0
- data/config/routes.rb +4 -2
- data/db/migrate/20250329120300_add_registries_to_sessions.rb +9 -0
- data/db/migrate/20250329150312_create_action_mcp_sse_events.rb +16 -0
- data/lib/action_mcp/capability.rb +16 -0
- data/lib/action_mcp/configuration.rb +16 -4
- data/lib/action_mcp/console_detector.rb +12 -0
- data/lib/action_mcp/engine.rb +3 -0
- data/lib/action_mcp/json_rpc_handler_base.rb +1 -1
- data/lib/action_mcp/resource_template.rb +11 -0
- data/lib/action_mcp/server/capabilities.rb +28 -22
- data/lib/action_mcp/server/configuration.rb +63 -0
- data/lib/action_mcp/server/json_rpc_handler.rb +35 -9
- data/lib/action_mcp/server/notifications.rb +14 -5
- data/lib/action_mcp/server/prompts.rb +18 -5
- data/lib/action_mcp/server/registry_management.rb +32 -0
- data/lib/action_mcp/server/resources.rb +3 -2
- data/lib/action_mcp/server/simple_pub_sub.rb +145 -0
- data/lib/action_mcp/server/solid_cable_adapter.rb +222 -0
- data/lib/action_mcp/server/tools.rb +50 -6
- data/lib/action_mcp/server.rb +84 -2
- data/lib/action_mcp/sse_listener.rb +6 -5
- data/lib/action_mcp/tagged_stream_logging.rb +47 -0
- data/lib/action_mcp/test_helper.rb +57 -34
- data/lib/action_mcp/tool.rb +45 -9
- data/lib/action_mcp/version.rb +1 -1
- data/lib/action_mcp.rb +4 -4
- data/lib/generators/action_mcp/config/config_generator.rb +29 -0
- data/lib/generators/action_mcp/config/templates/mcp.yml +36 -0
- metadata +23 -13
@@ -10,46 +10,52 @@ module ActionMCP
|
|
10
10
|
# @param params [Hash] The JSON-RPC parameters.
|
11
11
|
# @return [Hash] A hash representing the JSON-RPC response (success or error).
|
12
12
|
def send_capabilities(request_id, params = {})
|
13
|
-
# 1. Validate Parameters
|
14
13
|
client_protocol_version = params["protocolVersion"]
|
15
14
|
client_info = params["clientInfo"]
|
16
15
|
client_capabilities = params["capabilities"]
|
17
16
|
|
18
17
|
unless client_protocol_version.is_a?(String) && client_protocol_version.present?
|
19
|
-
|
18
|
+
send_jsonrpc_error(request_id, :invalid_params, "Missing or invalid 'protocolVersion'")
|
19
|
+
return { type: :error, id: request_id, payload: { jsonrpc: "2.0", id: request_id, error: { code: -32602, message: "Missing or invalid 'protocolVersion'" } } }
|
20
20
|
end
|
21
|
-
#
|
21
|
+
# Check if the protocol version is supported
|
22
|
+
unless ActionMCP::SUPPORTED_VERSIONS.include?(client_protocol_version)
|
23
|
+
error_data = {
|
24
|
+
supported: ActionMCP::SUPPORTED_VERSIONS,
|
25
|
+
requested: client_protocol_version
|
26
|
+
}
|
27
|
+
send_jsonrpc_error(request_id, :invalid_params, "Unsupported protocol version", error_data)
|
28
|
+
return { type: :error, id: request_id, payload: { jsonrpc: "2.0", id: request_id, error: { code: -32602, message: "Unsupported protocol version", data: error_data } } }
|
29
|
+
end
|
30
|
+
|
22
31
|
unless client_info.is_a?(Hash)
|
23
|
-
|
32
|
+
send_jsonrpc_error(request_id, :invalid_params, "Missing or invalid 'clientInfo'")
|
33
|
+
return { type: :error, id: request_id, payload: { jsonrpc: "2.0", id: request_id, error: { code: -32602, message: "Missing or invalid 'clientInfo'" } } }
|
24
34
|
end
|
25
35
|
unless client_capabilities.is_a?(Hash)
|
26
|
-
|
36
|
+
send_jsonrpc_error(request_id, :invalid_params, "Missing or invalid 'capabilities'")
|
37
|
+
return { type: :error, id: request_id, payload: { jsonrpc: "2.0", id: request_id, error: { code: -32602, message: "Missing or invalid 'capabilities'" } } }
|
27
38
|
end
|
28
39
|
|
29
|
-
# 2. Check Protocol Version
|
30
|
-
server_protocol_version = ActionMCP::PROTOCOL_VERSION
|
31
|
-
unless client_protocol_version == server_protocol_version
|
32
|
-
error_data = {
|
33
|
-
supported: [ server_protocol_version ],
|
34
|
-
requested: client_protocol_version
|
35
|
-
}
|
36
|
-
# Using -32602 Invalid Params code as per spec example for version mismatch
|
37
|
-
return send_jsonrpc_error(request_id, :invalid_params, "Unsupported protocol version", error_data)
|
38
|
-
end
|
39
40
|
|
40
|
-
|
41
|
+
|
42
|
+
# Store client information
|
41
43
|
session.store_client_info(client_info)
|
42
44
|
session.store_client_capabilities(client_capabilities)
|
43
|
-
session.set_protocol_version(client_protocol_version)
|
45
|
+
session.set_protocol_version(client_protocol_version)
|
44
46
|
|
45
|
-
#
|
47
|
+
# Initialize the session
|
46
48
|
unless session.initialize!
|
47
|
-
|
48
|
-
return
|
49
|
+
send_jsonrpc_error(request_id, :internal_error, "Failed to initialize session")
|
50
|
+
return { type: :error, id: request_id, payload: { jsonrpc: "2.0", id: request_id, error: { code: -32603, message: "Failed to initialize session" } } }
|
49
51
|
end
|
50
52
|
|
51
|
-
#
|
52
|
-
|
53
|
+
# Send the successful response with the protocol version the client requested
|
54
|
+
capabilities_payload = session.server_capabilities_payload
|
55
|
+
capabilities_payload[:protocolVersion] = client_protocol_version # Use the client's requested version
|
56
|
+
|
57
|
+
send_jsonrpc_response(request_id, result: capabilities_payload)
|
58
|
+
{ type: :responses, id: request_id, payload: { jsonrpc: "2.0", id: request_id, result: capabilities_payload } }
|
53
59
|
end
|
54
60
|
end
|
55
61
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
require "erb"
|
5
|
+
|
6
|
+
module ActionMCP
|
7
|
+
module Server
|
8
|
+
# Configuration loader for ActionMCP server
|
9
|
+
class Configuration
|
10
|
+
attr_reader :config
|
11
|
+
|
12
|
+
def initialize(config_path = nil)
|
13
|
+
@config_path = config_path || default_config_path
|
14
|
+
@config = load_config
|
15
|
+
end
|
16
|
+
|
17
|
+
# Get the configuration for the current environment
|
18
|
+
def for_env(env = nil)
|
19
|
+
environment = env || (defined?(Rails) ? Rails.env : "development")
|
20
|
+
config[environment] || config["development"] || {}
|
21
|
+
end
|
22
|
+
|
23
|
+
# Get the adapter name for the current environment
|
24
|
+
def adapter_name(env = nil)
|
25
|
+
env_config = for_env(env)
|
26
|
+
env_config["adapter"]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Get the adapter options for the current environment
|
30
|
+
def adapter_options(env = nil)
|
31
|
+
env_config = for_env(env)
|
32
|
+
env_config.except("adapter")
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def load_config
|
38
|
+
return {} unless File.exist?(@config_path.to_s)
|
39
|
+
|
40
|
+
yaml = ERB.new(File.read(@config_path)).result
|
41
|
+
YAML.safe_load(yaml, aliases: true) || {}
|
42
|
+
rescue => e
|
43
|
+
Rails.logger.error("Error loading ActionMCP config: #{e.message}") if defined?(Rails) && Rails.respond_to?(:logger)
|
44
|
+
{}
|
45
|
+
end
|
46
|
+
|
47
|
+
def default_config_path
|
48
|
+
return Rails.root.join("config", "mcp.yml") if defined?(Rails) && Rails.respond_to?(:root)
|
49
|
+
|
50
|
+
# Fallback to looking for a mcp.yml in the current directory or parent directories
|
51
|
+
path = Dir.pwd
|
52
|
+
while path != "/"
|
53
|
+
config_path = File.join(path, "config", "mcp.yml")
|
54
|
+
return config_path if File.exist?(config_path)
|
55
|
+
path = File.dirname(path)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Default to an empty config if no mcp.yml found
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -3,29 +3,55 @@
|
|
3
3
|
module ActionMCP
|
4
4
|
module Server
|
5
5
|
class JsonRpcHandler < JsonRpcHandlerBase
|
6
|
-
protected
|
7
|
-
|
8
6
|
# Handle server-specific methods
|
9
7
|
# @param rpc_method [String]
|
10
8
|
# @param id [String, Integer]
|
11
9
|
# @param params [Hash]
|
10
|
+
def call(line)
|
11
|
+
request = if line.is_a?(String)
|
12
|
+
line.strip!
|
13
|
+
return if line.empty?
|
14
|
+
|
15
|
+
begin
|
16
|
+
MultiJson.load(line)
|
17
|
+
rescue MultiJson::ParseError => e
|
18
|
+
Rails.logger.error("Failed to parse JSON: #{e.message}")
|
19
|
+
return
|
20
|
+
end
|
21
|
+
else
|
22
|
+
line
|
23
|
+
end
|
24
|
+
|
25
|
+
# Store the request ID for error responses
|
26
|
+
@current_request_id = request["id"] if request.is_a?(Hash)
|
27
|
+
|
28
|
+
process_request(request)
|
29
|
+
end
|
30
|
+
|
12
31
|
def handle_method(rpc_method, id, params)
|
32
|
+
# Ensure we have the current request ID
|
33
|
+
@current_request_id = id
|
34
|
+
|
13
35
|
case rpc_method
|
14
|
-
when "initialize"
|
36
|
+
when "initialize"
|
15
37
|
transport.send_capabilities(id, params)
|
16
|
-
when %r{^prompts/}
|
38
|
+
when %r{^prompts/}
|
17
39
|
process_prompts(rpc_method, id, params)
|
18
|
-
when %r{^resources/}
|
40
|
+
when %r{^resources/}
|
19
41
|
process_resources(rpc_method, id, params)
|
20
|
-
when %r{^tools/}
|
42
|
+
when %r{^tools/}
|
21
43
|
process_tools(rpc_method, id, params)
|
22
|
-
when "completion/complete"
|
44
|
+
when "completion/complete"
|
23
45
|
process_completion_complete(id, params)
|
24
46
|
else
|
25
|
-
transport.send_jsonrpc_error(id, :method_not_found, "Method not found")
|
47
|
+
transport.send_jsonrpc_error(id, :method_not_found, "Method not found #{rpc_method}")
|
26
48
|
end
|
49
|
+
rescue StandardError => e
|
50
|
+
Rails.logger.error("Error handling method #{rpc_method}: #{e.message}")
|
51
|
+
transport.send_jsonrpc_error(id, :internal_error, "Internal error: #{e.message}")
|
27
52
|
end
|
28
53
|
|
54
|
+
|
29
55
|
# Server methods (client → server)
|
30
56
|
|
31
57
|
# @param id [String]
|
@@ -79,7 +105,7 @@ module ActionMCP
|
|
79
105
|
def process_tools(rpc_method, id, params)
|
80
106
|
case rpc_method
|
81
107
|
when "tools/list" # List available tools
|
82
|
-
transport.send_tools_list(id)
|
108
|
+
transport.send_tools_list(id, params)
|
83
109
|
when "tools/call" # Call a tool
|
84
110
|
transport.send_tools_call(id, params["name"], params["arguments"])
|
85
111
|
else
|
@@ -34,15 +34,24 @@ module ActionMCP
|
|
34
34
|
send_jsonrpc_notification("notifications/logging/message", params)
|
35
35
|
end
|
36
36
|
|
37
|
-
#
|
38
|
-
def send_progress_notification(
|
37
|
+
# Updated to match MCP 2025-03-26 specification
|
38
|
+
def send_progress_notification(progressToken:, progress:, total: nil, message: nil, **options)
|
39
39
|
params = {
|
40
|
-
|
41
|
-
|
40
|
+
progressToken: progressToken,
|
41
|
+
progress: progress
|
42
42
|
}
|
43
|
+
# Only include total and message if they are present (not nil)
|
44
|
+
params[:total] = total unless total.nil?
|
43
45
|
params[:message] = message if message.present?
|
46
|
+
params.merge!(options) if options.any?
|
44
47
|
|
45
|
-
send_jsonrpc_notification("
|
48
|
+
send_jsonrpc_notification("notifications/progress", params)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Backward compatibility method for old API
|
52
|
+
def send_progress_notification_legacy(token:, value:, message: nil)
|
53
|
+
Rails.logger.warn("DEPRECATION: send_progress_notification with token/value is deprecated. Use progressToken/progress instead.")
|
54
|
+
send_progress_notification(progressToken: token, progress: value, message: message)
|
46
55
|
end
|
47
56
|
end
|
48
57
|
end
|
@@ -4,16 +4,29 @@ module ActionMCP
|
|
4
4
|
module Server
|
5
5
|
module Prompts
|
6
6
|
def send_prompts_list(request_id)
|
7
|
-
|
7
|
+
# Use session's registered prompts
|
8
|
+
prompts = session.registered_prompts.map(&:to_h)
|
8
9
|
send_jsonrpc_response(request_id, result: { prompts: prompts })
|
9
10
|
end
|
10
11
|
|
11
12
|
def send_prompts_get(request_id, prompt_name, params)
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
# Find prompt in session's registry
|
14
|
+
prompt_class = session.registered_prompts.find { |p| p.prompt_name == prompt_name }
|
15
|
+
|
16
|
+
if prompt_class
|
17
|
+
# Create prompt and set execution context
|
18
|
+
prompt = prompt_class.new(params)
|
19
|
+
prompt.with_context({ session: session })
|
20
|
+
|
21
|
+
result = prompt.call
|
22
|
+
|
23
|
+
if result.is_error
|
24
|
+
send_jsonrpc_response(request_id, error: result)
|
25
|
+
else
|
26
|
+
send_jsonrpc_response(request_id, result: result)
|
27
|
+
end
|
15
28
|
else
|
16
|
-
|
29
|
+
send_jsonrpc_error(request_id, :method_not_found, "Prompt '#{prompt_name}' not available in this session")
|
17
30
|
end
|
18
31
|
end
|
19
32
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# lib/action_mcp/server/registry_management.rb
|
2
|
+
module ActionMCP
|
3
|
+
module Server
|
4
|
+
module RegistryManagement
|
5
|
+
def send_registry_add_tool(request_id, tool_name)
|
6
|
+
tool_class = ActionMCP::ToolsRegistry.find(tool_name)
|
7
|
+
|
8
|
+
if tool_class
|
9
|
+
session.register_tool(tool_class)
|
10
|
+
send_jsonrpc_response(request_id, result: { success: true })
|
11
|
+
else
|
12
|
+
send_jsonrpc_error(request_id, :invalid_params, "Tool '#{tool_name}' not found")
|
13
|
+
end
|
14
|
+
rescue ActionMCP::RegistryBase::NotFound
|
15
|
+
send_jsonrpc_error(request_id, :invalid_params, "Tool '#{tool_name}' not found")
|
16
|
+
end
|
17
|
+
|
18
|
+
def send_registry_remove_tool(request_id, tool_name)
|
19
|
+
tool_class = session.available_tools.find { |t| t.tool_name == tool_name }
|
20
|
+
|
21
|
+
if tool_class
|
22
|
+
session.unregister_tool(tool_class)
|
23
|
+
send_jsonrpc_response(request_id, result: { success: true })
|
24
|
+
else
|
25
|
+
send_jsonrpc_error(request_id, :invalid_params, "Tool '#{tool_name}' not in session")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Similar methods for prompts and resources
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -46,10 +46,11 @@ module ActionMCP
|
|
46
46
|
# # Sends: {"jsonrpc":"2.0","id":"req-789","result":{"contents":[{"uri":"file:///example.txt","text":"Example content"}]}}
|
47
47
|
def send_resource_read(id, params)
|
48
48
|
if (template = ResourceTemplatesRegistry.find_template_for_uri(params[:uri]))
|
49
|
+
# Create template instance and set execution context
|
49
50
|
record = template.process(params[:uri])
|
51
|
+
record.with_context({ session: session })
|
52
|
+
|
50
53
|
if (resource = record.call)
|
51
|
-
# if resource is a array or a collection, return each item then it ok
|
52
|
-
# else wrap it in a array
|
53
54
|
resource = [ resource ] unless resource.respond_to?(:each)
|
54
55
|
content = resource.map(&:to_h)
|
55
56
|
send_jsonrpc_response(id, result: { contents: content })
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
require "concurrent/map"
|
5
|
+
require "concurrent/array"
|
6
|
+
require "concurrent/executor/thread_pool_executor"
|
7
|
+
|
8
|
+
module ActionMCP
|
9
|
+
module Server
|
10
|
+
# Simple in-memory PubSub implementation for testing and development
|
11
|
+
class SimplePubSub
|
12
|
+
# Thread pool configuration
|
13
|
+
DEFAULT_MIN_THREADS = 5
|
14
|
+
DEFAULT_MAX_THREADS = 10
|
15
|
+
DEFAULT_MAX_QUEUE = 100
|
16
|
+
DEFAULT_THREAD_TIMEOUT = 60 # seconds
|
17
|
+
|
18
|
+
def initialize(options = {})
|
19
|
+
@subscriptions = Concurrent::Map.new
|
20
|
+
@channels = Concurrent::Map.new
|
21
|
+
|
22
|
+
# Initialize thread pool for callbacks
|
23
|
+
pool_options = {
|
24
|
+
min_threads: options["min_threads"] || DEFAULT_MIN_THREADS,
|
25
|
+
max_threads: options["max_threads"] || DEFAULT_MAX_THREADS,
|
26
|
+
max_queue: options["max_queue"] || DEFAULT_MAX_QUEUE,
|
27
|
+
fallback_policy: :caller_runs, # Execute in the caller's thread if queue is full
|
28
|
+
idletime: DEFAULT_THREAD_TIMEOUT
|
29
|
+
}
|
30
|
+
@thread_pool = Concurrent::ThreadPoolExecutor.new(pool_options)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Subscribe to a channel
|
34
|
+
# @param channel [String] The channel name
|
35
|
+
# @param message_callback [Proc] Callback for received messages
|
36
|
+
# @param success_callback [Proc] Callback for successful subscription
|
37
|
+
# @return [String] Subscription ID
|
38
|
+
def subscribe(channel, message_callback, success_callback = nil)
|
39
|
+
subscription_id = SecureRandom.uuid
|
40
|
+
|
41
|
+
@subscriptions[subscription_id] = {
|
42
|
+
channel: channel,
|
43
|
+
message_callback: message_callback
|
44
|
+
}
|
45
|
+
|
46
|
+
@channels[channel] ||= Concurrent::Array.new
|
47
|
+
@channels[channel] << subscription_id
|
48
|
+
|
49
|
+
log_subscription_event(channel, "Subscribed", subscription_id)
|
50
|
+
success_callback&.call
|
51
|
+
|
52
|
+
subscription_id
|
53
|
+
end
|
54
|
+
|
55
|
+
# Check if we're already subscribed to a channel
|
56
|
+
# @param channel [String] The channel name
|
57
|
+
# @return [Boolean] True if we're already subscribed
|
58
|
+
def subscribed_to?(channel)
|
59
|
+
channel_subs = @channels[channel]
|
60
|
+
return false if channel_subs.nil?
|
61
|
+
!channel_subs.empty?
|
62
|
+
end
|
63
|
+
|
64
|
+
# Unsubscribe from a channel
|
65
|
+
# @param channel [String] The channel name
|
66
|
+
# @param callback [Proc] Optional callback for unsubscribe completion
|
67
|
+
def unsubscribe(channel, callback = nil)
|
68
|
+
# Remove our subscriptions
|
69
|
+
subscription_ids = @channels[channel] || []
|
70
|
+
subscription_ids.each do |subscription_id|
|
71
|
+
@subscriptions.delete(subscription_id)
|
72
|
+
end
|
73
|
+
|
74
|
+
@channels.delete(channel)
|
75
|
+
|
76
|
+
log_subscription_event(channel, "Unsubscribed")
|
77
|
+
callback&.call
|
78
|
+
end
|
79
|
+
|
80
|
+
# Broadcast a message to a channel
|
81
|
+
# @param channel [String] The channel name
|
82
|
+
# @param message [String] The message to broadcast
|
83
|
+
def broadcast(channel, message)
|
84
|
+
subscription_ids = @channels[channel] || []
|
85
|
+
return if subscription_ids.empty?
|
86
|
+
|
87
|
+
log_broadcast_event(channel, message)
|
88
|
+
|
89
|
+
subscription_ids.each do |subscription_id|
|
90
|
+
subscription = @subscriptions[subscription_id]
|
91
|
+
next unless subscription && subscription[:message_callback]
|
92
|
+
|
93
|
+
@thread_pool.post do
|
94
|
+
begin
|
95
|
+
subscription[:message_callback].call(message)
|
96
|
+
rescue StandardError => e
|
97
|
+
log_error("Error in message callback: #{e.message}\n#{e.backtrace.join("\n")}")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Check if a channel has subscribers
|
104
|
+
# @param channel [String] The channel name
|
105
|
+
# @return [Boolean] True if channel has subscribers
|
106
|
+
def has_subscribers?(channel)
|
107
|
+
subscribers = @channels[channel]
|
108
|
+
return false unless subscribers
|
109
|
+
!subscribers.empty?
|
110
|
+
end
|
111
|
+
|
112
|
+
# Shut down the thread pool gracefully
|
113
|
+
def shutdown
|
114
|
+
@thread_pool.shutdown
|
115
|
+
@thread_pool.wait_for_termination(5) # Wait up to 5 seconds for tasks to complete
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def log_subscription_event(channel, action, subscription_id = nil)
|
121
|
+
return unless defined?(Rails) && Rails.respond_to?(:logger)
|
122
|
+
|
123
|
+
message = "SimplePubSub: #{action} channel=#{channel}"
|
124
|
+
message += " subscription_id=#{subscription_id}" if subscription_id
|
125
|
+
|
126
|
+
Rails.logger.debug(message)
|
127
|
+
end
|
128
|
+
|
129
|
+
def log_broadcast_event(channel, message)
|
130
|
+
return unless defined?(Rails) && Rails.respond_to?(:logger)
|
131
|
+
|
132
|
+
# Truncate the message for logging
|
133
|
+
truncated_message = message.to_s[0..100]
|
134
|
+
truncated_message += "..." if message.to_s.length > 100
|
135
|
+
|
136
|
+
Rails.logger.debug("SimplePubSub: Broadcasting to channel=#{channel} message=#{truncated_message}")
|
137
|
+
end
|
138
|
+
|
139
|
+
def log_error(message)
|
140
|
+
return unless defined?(Rails) && Rails.respond_to?(:logger)
|
141
|
+
Rails.logger.error("SimplePubSub: #{message}")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|