actionmcp 0.51.0 → 0.52.1

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +192 -0
  3. data/app/controllers/action_mcp/application_controller.rb +12 -6
  4. data/lib/action_mcp/client/active_record_session_store.rb +57 -0
  5. data/lib/action_mcp/client/session_store.rb +2 -103
  6. data/lib/action_mcp/client/session_store_factory.rb +36 -0
  7. data/lib/action_mcp/client/test_session_store.rb +84 -0
  8. data/lib/action_mcp/client/volatile_session_store.rb +38 -0
  9. data/lib/action_mcp/configuration.rb +16 -1
  10. data/lib/action_mcp/current.rb +19 -0
  11. data/lib/action_mcp/current_helpers.rb +19 -0
  12. data/lib/action_mcp/gateway.rb +85 -0
  13. data/lib/action_mcp/json_rpc_handler_base.rb +6 -1
  14. data/lib/action_mcp/jwt_decoder.rb +26 -0
  15. data/lib/action_mcp/prompt.rb +1 -0
  16. data/lib/action_mcp/resource_template.rb +1 -0
  17. data/lib/action_mcp/server/base_messaging.rb +14 -0
  18. data/lib/action_mcp/server/error_aware.rb +8 -1
  19. data/lib/action_mcp/server/handlers/tool_handler.rb +2 -1
  20. data/lib/action_mcp/server/json_rpc_handler.rb +12 -4
  21. data/lib/action_mcp/server/messaging.rb +12 -1
  22. data/lib/action_mcp/server/registry_management.rb +0 -1
  23. data/lib/action_mcp/server/response_collector.rb +40 -0
  24. data/lib/action_mcp/server/session_store.rb +762 -0
  25. data/lib/action_mcp/server/tools.rb +14 -3
  26. data/lib/action_mcp/server/transport_handler.rb +9 -5
  27. data/lib/action_mcp/server.rb +7 -0
  28. data/lib/action_mcp/tagged_stream_logging.rb +0 -4
  29. data/lib/action_mcp/test_helper/progress_notification_assertions.rb +105 -0
  30. data/lib/action_mcp/test_helper/session_store_assertions.rb +130 -0
  31. data/lib/action_mcp/test_helper.rb +4 -0
  32. data/lib/action_mcp/tool.rb +1 -0
  33. data/lib/action_mcp/version.rb +1 -1
  34. data/lib/action_mcp.rb +0 -1
  35. data/lib/generators/action_mcp/install/install_generator.rb +4 -0
  36. data/lib/generators/action_mcp/install/templates/application_gateway.rb +40 -0
  37. metadata +29 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88b1f1dba04f05f96a9cf1c4ab928412400cb0a796d6635277924e57aa91a3c6
4
- data.tar.gz: 163a43c7d4e3e247ba6f1084250e9452e9f36ee0e2ba2ee4a1d6fe67ca95f147
3
+ metadata.gz: ba6892aa3c28876f79be1ab3b365b2ed70b449e6f55781de9ea565e3f26d4f8e
4
+ data.tar.gz: ba861b9e0ec4dcdfcf743a70e8fed177a98a169c86c56f9f5d7b50c4bf7bf7b6
5
5
  SHA512:
6
- metadata.gz: fae52dbb988f73cdcc4fa4678b12d352cdb151f1111c753a1210c37169b0b2d04dc53dbeafc092cd9dea6e6e5e2397672a7a1252982425f726f546427c52c327
7
- data.tar.gz: 613a305bd349a425d6f7169566b160eeda391870a20b8f997d01b587e558ef50ca005947a5482d24db5c4846a53e136f00301ad9f648f0b0c5d92c7a58b998f6
6
+ metadata.gz: de7d1b0f9db37da9cc7be60ec42aac243ffb521cf878ba4c537269c0afeabdf22c5a2431b6eb5a1a00e79d574bdddbd444d63a1230543fb364b31d8d98ef36da
7
+ data.tar.gz: eafee3ce33017cfde2175f655caf5cc0e292b37a70427d36acf474e21503abea3ea71b124430c69cb8c29168f8993cac385f5f227c05863482a65e9b8f323e6e
data/README.md CHANGED
@@ -330,6 +330,115 @@ production:
330
330
  max_queue: 500 # Maximum number of tasks that can be queued
331
331
  ```
332
332
 
333
+ ## Session Storage
334
+
335
+ ActionMCP provides a pluggable session storage system that allows you to choose how sessions are persisted based on your environment and requirements.
336
+
337
+ ### Session Store Types
338
+
339
+ ActionMCP includes three session store implementations:
340
+
341
+ 1. **`:volatile`** - In-memory storage using Concurrent::Hash
342
+ - Default for development and test environments
343
+ - Sessions are lost on server restart
344
+ - Fast and lightweight for local development
345
+ - No external dependencies
346
+
347
+ 2. **`:active_record`** - Database-backed storage
348
+ - Default for production environment
349
+ - Sessions persist across server restarts
350
+ - Supports session resumability
351
+ - Requires database migrations
352
+
353
+ 3. **`:test`** - Special store for testing
354
+ - Tracks notifications and method calls
355
+ - Provides assertion helpers
356
+ - Automatically used in test environment when using TestHelper
357
+
358
+ ### Configuration
359
+
360
+ You can configure the session store type in your Rails configuration:
361
+
362
+ ```ruby
363
+ # config/application.rb or environment files
364
+ Rails.application.configure do
365
+ config.action_mcp.session_store_type = :active_record # or :volatile
366
+ end
367
+ ```
368
+
369
+ The defaults are:
370
+ - Production: `:active_record`
371
+ - Development: `:volatile`
372
+ - Test: `:volatile` (or `:test` when using TestHelper)
373
+
374
+ ### Using Different Session Stores
375
+
376
+ ```ruby
377
+ # The session store is automatically selected based on configuration
378
+ # You can access it directly if needed:
379
+ session_store = ActionMCP::Server.session_store
380
+
381
+ # Create a session
382
+ session = session_store.create_session(session_id, {
383
+ status: "initialized",
384
+ protocol_version: "2025-03-26",
385
+ # ... other session attributes
386
+ })
387
+
388
+ # Load a session
389
+ session = session_store.load_session(session_id)
390
+
391
+ # Update a session
392
+ session_store.update_session(session_id, { status: "active" })
393
+
394
+ # Delete a session
395
+ session_store.delete_session(session_id)
396
+ ```
397
+
398
+ ### Session Resumability
399
+
400
+ With the `:active_record` store, clients can resume sessions after disconnection:
401
+
402
+ ```ruby
403
+ # Client includes session ID in request headers
404
+ # Server automatically resumes the existing session
405
+ headers["Mcp-Session-Id"] = "existing-session-id"
406
+
407
+ # If the session exists, it will be resumed
408
+ # If not, a new session will be created
409
+ ```
410
+
411
+ ### Custom Session Stores
412
+
413
+ You can create custom session stores by inheriting from `ActionMCP::Server::SessionStore::Base`:
414
+
415
+ ```ruby
416
+ class MyCustomSessionStore < ActionMCP::Server::SessionStore::Base
417
+ def create_session(session_id, payload = {})
418
+ # Implementation
419
+ end
420
+
421
+ def load_session(session_id)
422
+ # Implementation
423
+ end
424
+
425
+ def update_session(session_id, updates)
426
+ # Implementation
427
+ end
428
+
429
+ def delete_session(session_id)
430
+ # Implementation
431
+ end
432
+
433
+ def exists?(session_id)
434
+ # Implementation
435
+ end
436
+ end
437
+
438
+ # Register your custom store
439
+ ActionMCP::Server.session_store = MyCustomSessionStore.new
440
+ ```
441
+
333
442
  ## Thread Pool Management
334
443
 
335
444
  ActionMCP uses thread pools to efficiently handle message callbacks. This prevents the system from being overwhelmed by too many threads under high load.
@@ -380,6 +489,89 @@ This will create `config/mcp.yml` with example configurations for all environmen
380
489
 
381
490
  > **Note:** Authentication and authorization are not included. You are responsible for securing the endpoint.
382
491
 
492
+ ## Authentication with Gateway
493
+
494
+ ActionMCP provides a Gateway system similar to ActionCable's Connection for handling authentication. The Gateway allows you to authenticate users and make them available throughout your MCP components.
495
+
496
+ ### Creating an ApplicationGateway
497
+
498
+ When you run the install generator, it creates an `ApplicationGateway` class:
499
+
500
+ ```ruby
501
+ # app/mcp/application_gateway.rb
502
+ class ApplicationGateway < ActionMCP::Gateway
503
+ # Specify what attributes identify a connection
504
+ identified_by :user
505
+
506
+ protected
507
+
508
+ def authenticate!
509
+ token = extract_bearer_token
510
+ raise ActionMCP::UnauthorizedError, "Missing token" unless token
511
+
512
+ payload = ActionMCP::JwtDecoder.decode(token)
513
+ user = resolve_user(payload)
514
+
515
+ raise ActionMCP::UnauthorizedError, "Unauthorized" unless user
516
+
517
+ # Return a hash with all identified_by attributes
518
+ { user: user }
519
+ end
520
+
521
+ private
522
+
523
+ def resolve_user(payload)
524
+ user_id = payload["user_id"] || payload["sub"]
525
+ User.find_by(id: user_id) if user_id
526
+ end
527
+ end
528
+ ```
529
+
530
+ ### Using Multiple Identifiers
531
+
532
+ You can identify connections by multiple attributes:
533
+
534
+ ```ruby
535
+ class ApplicationGateway < ActionMCP::Gateway
536
+ identified_by :user, :organization
537
+
538
+ protected
539
+
540
+ def authenticate!
541
+ # ... authentication logic ...
542
+
543
+ {
544
+ user: user,
545
+ organization: user.organization
546
+ }
547
+ end
548
+ end
549
+ ```
550
+
551
+ ### Accessing Current User in Components
552
+
553
+ Once authenticated, the current user (and other identifiers) are available in your tools, prompts, and resource templates:
554
+
555
+ ```ruby
556
+ class MyTool < ApplicationMCPTool
557
+ def perform
558
+ # Access the authenticated user
559
+ if current_user
560
+ render text: "Hello, #{current_user.name}!"
561
+ else
562
+ render text: "Hi Stranger! It's been a while "
563
+ end
564
+ end
565
+ end
566
+ ```
567
+
568
+ ### Current Attributes
569
+
570
+ ActionMCP uses Rails' CurrentAttributes to store the authenticated context. The `ActionMCP::Current` class provides:
571
+ - `ActionMCP::Current.user` - The authenticated user
572
+ - `ActionMCP::Current.gateway` - The gateway instance
573
+ - Any other attributes you define with `identified_by`
574
+
383
575
  ### 1. Create `mcp.ru`
384
576
 
385
577
  ```ruby
@@ -158,11 +158,11 @@ module ActionMCP
158
158
  response.headers[MCP_SESSION_ID_HEADER] = session.id
159
159
  end
160
160
 
161
- transport_handler = Server::TransportHandler.new(session)
161
+ # Use return mode for the transport handler when we need to capture responses
162
+ transport_handler = Server::TransportHandler.new(session, messaging_mode: :return)
162
163
  json_rpc_handler = Server::JsonRpcHandler.new(transport_handler)
163
164
 
164
165
  result = json_rpc_handler.call(jsonrpc_params)
165
-
166
166
  process_handler_results(result, session, session_initially_missing, is_initialize_request)
167
167
  rescue ActionController::Live::ClientDisconnected, IOError => e
168
168
  Rails.logger.debug "Unified SSE (POST): Client disconnected during response: #{e.message}"
@@ -182,7 +182,7 @@ module ActionMCP
182
182
  session_id_from_header = extract_session_id
183
183
  return render_bad_request("Mcp-Session-Id header is required for DELETE requests.") unless session_id_from_header
184
184
 
185
- session = Session.find_by(id: session_id_from_header)
185
+ session = Server.session_store.load_session(session_id_from_header)
186
186
  if session.nil?
187
187
  return render_not_found("Session not found.")
188
188
  elsif session.status == "closed"
@@ -206,7 +206,7 @@ module ActionMCP
206
206
  def find_or_initialize_session
207
207
  session_id = extract_session_id
208
208
  if session_id
209
- session = Session.find_by(id: session_id)
209
+ session = Server.session_store.load_session(session_id)
210
210
  if session
211
211
  if ActionMCP.configuration.vibed_ignore_version
212
212
  if session.protocol_version != self.class::REQUIRED_PROTOCOL_VERSION
@@ -218,7 +218,7 @@ module ActionMCP
218
218
  end
219
219
  session
220
220
  else
221
- Session.new(protocol_version: self.class::REQUIRED_PROTOCOL_VERSION)
221
+ Server.session_store.create_session(nil, protocol_version: self.class::REQUIRED_PROTOCOL_VERSION)
222
222
  end
223
223
  end
224
224
 
@@ -266,7 +266,13 @@ module ActionMCP
266
266
  end
267
267
 
268
268
  # Convert to hash for rendering
269
- payload = result.message_json
269
+ payload = if result.respond_to?(:to_h)
270
+ result.to_h
271
+ elsif result.respond_to?(:to_json)
272
+ JSON.parse(result.to_json)
273
+ else
274
+ result
275
+ end
270
276
 
271
277
  # Determine response format
272
278
  server_preference = ActionMCP.configuration.post_response_preference
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMCP
4
+ module Client
5
+ # ActiveRecord-backed session store for production
6
+ class ActiveRecordSessionStore
7
+ include SessionStore
8
+
9
+ def load_session(session_id)
10
+ session = ActionMCP::Session.find_by(id: session_id)
11
+ return nil unless session
12
+
13
+ {
14
+ id: session.id,
15
+ protocol_version: session.protocol_version,
16
+ client_info: session.client_info,
17
+ client_capabilities: session.client_capabilities,
18
+ server_info: session.server_info,
19
+ server_capabilities: session.server_capabilities,
20
+ created_at: session.created_at,
21
+ updated_at: session.updated_at
22
+ }
23
+ end
24
+
25
+ def save_session(session_id, session_data)
26
+ session = ActionMCP::Session.find_or_initialize_by(id: session_id)
27
+
28
+ # Only assign attributes that exist in the database
29
+ attributes = {}
30
+ attributes[:protocol_version] = session_data[:protocol_version] if session_data.key?(:protocol_version)
31
+ attributes[:client_info] = session_data[:client_info] if session_data.key?(:client_info)
32
+ attributes[:client_capabilities] = session_data[:client_capabilities] if session_data.key?(:client_capabilities)
33
+ attributes[:server_info] = session_data[:server_info] if session_data.key?(:server_info)
34
+ attributes[:server_capabilities] = session_data[:server_capabilities] if session_data.key?(:server_capabilities)
35
+
36
+ # Store any extra data in a jsonb column if available
37
+ # For now, we'll skip last_event_id and session_data as they don't exist in the DB
38
+
39
+ session.assign_attributes(attributes)
40
+ session.save!
41
+ session_data
42
+ end
43
+
44
+ def delete_session(session_id)
45
+ ActionMCP::Session.find_by(id: session_id)&.destroy
46
+ end
47
+
48
+ def session_exists?(session_id)
49
+ ActionMCP::Session.exists?(id: session_id)
50
+ end
51
+
52
+ def cleanup_expired_sessions(older_than: 24.hours.ago)
53
+ ActionMCP::Session.where("updated_at < ?", older_than).delete_all
54
+ end
55
+ end
56
+ end
57
+ end
@@ -31,109 +31,8 @@ module ActionMCP
31
31
 
32
32
  session_data.merge!(attributes)
33
33
  save_session(session_id, session_data)
34
- session_data
35
- end
36
- end
37
-
38
- # In-memory session store for development/testing
39
- class MemorySessionStore
40
- include SessionStore
41
-
42
- def initialize
43
- @sessions = {}
44
- @mutex = Mutex.new
45
- end
46
-
47
- def load_session(session_id)
48
- @mutex.synchronize { @sessions[session_id] }
49
- end
50
-
51
- def save_session(session_id, session_data)
52
- @mutex.synchronize { @sessions[session_id] = session_data.dup }
53
- end
54
-
55
- def delete_session(session_id)
56
- @mutex.synchronize { @sessions.delete(session_id) }
57
- end
58
-
59
- def session_exists?(session_id)
60
- @mutex.synchronize { @sessions.key?(session_id) }
61
- end
62
-
63
- def clear_all
64
- @mutex.synchronize { @sessions.clear }
65
- end
66
-
67
- def session_count
68
- @mutex.synchronize { @sessions.size }
69
- end
70
- end
71
-
72
- # ActiveRecord-backed session store for production
73
- class ActiveRecordSessionStore
74
- include SessionStore
75
-
76
- def load_session(session_id)
77
- session = ActionMCP::Session.find_by(id: session_id)
78
- return nil unless session
79
-
80
- {
81
- id: session.id,
82
- protocol_version: session.protocol_version,
83
- client_info: session.client_info,
84
- client_capabilities: session.client_capabilities,
85
- server_info: session.server_info,
86
- server_capabilities: session.server_capabilities,
87
- last_event_id: session.last_event_id,
88
- session_data: session.session_data || {},
89
- created_at: session.created_at,
90
- updated_at: session.updated_at
91
- }
92
- end
93
-
94
- def save_session(session_id, session_data)
95
- session = ActionMCP::Session.find_or_initialize_by(id: session_id)
96
-
97
- session.assign_attributes(
98
- protocol_version: session_data[:protocol_version],
99
- client_info: session_data[:client_info],
100
- client_capabilities: session_data[:client_capabilities],
101
- server_info: session_data[:server_info],
102
- server_capabilities: session_data[:server_capabilities],
103
- last_event_id: session_data[:last_event_id],
104
- session_data: session_data[:session_data] || {}
105
- )
106
-
107
- session.save!
108
- session_data
109
- end
110
-
111
- def delete_session(session_id)
112
- ActionMCP::Session.find_by(id: session_id)&.destroy
113
- end
114
-
115
- def session_exists?(session_id)
116
- ActionMCP::Session.exists?(id: session_id)
117
- end
118
-
119
- def cleanup_expired_sessions(older_than: 24.hours.ago)
120
- ActionMCP::Session.where("updated_at < ?", older_than).delete_all
121
- end
122
- end
123
-
124
- # Factory for creating session stores
125
- class SessionStoreFactory
126
- def self.create(type = nil, **options)
127
- type ||= Rails.env.production? ? :active_record : :memory
128
-
129
- case type.to_sym
130
- when :memory
131
- MemorySessionStore.new
132
- when :active_record
133
- ActiveRecordSessionStore.new
134
- else
135
- raise ArgumentError, "Unknown session store type: #{type}"
136
- end
34
+ # Return the reloaded session to get the actual saved values
35
+ load_session(session_id)
137
36
  end
138
37
  end
139
38
  end
@@ -0,0 +1,36 @@
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
+ # Ensure Rails is defined or provide a fallback if this code can run
24
+ # outside a Rails environment.
25
+ # Will refactor this soon
26
+ if defined?(Rails) && Rails.env.test?
27
+ :volatile # Use volatile for tests unless explicitly using :test
28
+ elsif defined?(Rails) && Rails.env.production?
29
+ :active_record
30
+ else
31
+ :volatile # Default for development or non-Rails environments
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMCP
4
+ module Client
5
+ # Test session store that tracks all operations for assertions
6
+ class TestSessionStore < VolatileSessionStore
7
+ attr_reader :operations, :saved_sessions, :loaded_sessions,
8
+ :deleted_sessions, :updated_sessions
9
+
10
+ def initialize
11
+ super
12
+ @operations = Concurrent::Array.new
13
+ @saved_sessions = Concurrent::Array.new
14
+ @loaded_sessions = Concurrent::Array.new
15
+ @deleted_sessions = Concurrent::Array.new
16
+ @updated_sessions = Concurrent::Array.new
17
+ end
18
+
19
+ def load_session(session_id)
20
+ session = super
21
+ @operations << { type: :load, session_id: session_id, found: !session.nil? }
22
+ @loaded_sessions << session_id if session
23
+ session
24
+ end
25
+
26
+ def save_session(session_id, session_data)
27
+ super
28
+ @operations << { type: :save, session_id: session_id, data: session_data }
29
+ @saved_sessions << session_id
30
+ end
31
+
32
+ def delete_session(session_id)
33
+ result = super
34
+ @operations << { type: :delete, session_id: session_id }
35
+ @deleted_sessions << session_id
36
+ result
37
+ end
38
+
39
+ def update_session(session_id, attributes)
40
+ result = super
41
+ @operations << { type: :update, session_id: session_id, attributes: attributes }
42
+ @updated_sessions << session_id if result
43
+ result
44
+ end
45
+
46
+ # Test helper methods
47
+ def session_saved?(session_id)
48
+ @saved_sessions.include?(session_id)
49
+ end
50
+
51
+ def session_loaded?(session_id)
52
+ @loaded_sessions.include?(session_id)
53
+ end
54
+
55
+ def session_deleted?(session_id)
56
+ @deleted_sessions.include?(session_id)
57
+ end
58
+
59
+ def session_updated?(session_id)
60
+ @updated_sessions.include?(session_id)
61
+ end
62
+
63
+ def operation_count(type = nil)
64
+ if type
65
+ @operations.count { |op| op[:type] == type }
66
+ else
67
+ @operations.size
68
+ end
69
+ end
70
+
71
+ def last_saved_data(session_id)
72
+ @operations.reverse.find { |op| op[:type] == :save && op[:session_id] == session_id }&.dig(:data)
73
+ end
74
+
75
+ def reset_tracking!
76
+ @operations.clear
77
+ @saved_sessions.clear
78
+ @loaded_sessions.clear
79
+ @deleted_sessions.clear
80
+ @updated_sessions.clear
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMCP
4
+ module Client
5
+ # Volatile session store for development (data lost on restart)
6
+ class VolatileSessionStore
7
+ include SessionStore
8
+
9
+ def initialize
10
+ @sessions = Concurrent::Hash.new
11
+ end
12
+
13
+ def load_session(session_id)
14
+ @sessions[session_id]
15
+ end
16
+
17
+ def save_session(session_id, session_data)
18
+ @sessions[session_id] = session_data.dup
19
+ end
20
+
21
+ def delete_session(session_id)
22
+ @sessions.delete(session_id)
23
+ end
24
+
25
+ def session_exists?(session_id)
26
+ @sessions.key?(session_id)
27
+ end
28
+
29
+ def clear_all
30
+ @sessions.clear
31
+ end
32
+
33
+ def session_count
34
+ @sessions.size
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "gateway"
4
+ require "active_support/core_ext/integer/time"
5
+
3
6
  module ActionMCP
4
7
  # Configuration class to hold settings for the ActionMCP server.
5
8
  class Configuration
@@ -30,7 +33,12 @@ module ActionMCP
30
33
  :vibed_ignore_version,
31
34
  # --- SSE Resumability Options ---
32
35
  :sse_event_retention_period,
33
- :max_stored_sse_events
36
+ :max_stored_sse_events,
37
+ # --- Gateway Options ---
38
+ :gateway_class,
39
+ :current_class,
40
+ # --- Session Store Options ---
41
+ :session_store_type
34
42
 
35
43
  def initialize
36
44
  @logging_enabled = true
@@ -47,6 +55,13 @@ module ActionMCP
47
55
  # Resumability defaults
48
56
  @sse_event_retention_period = 15.minutes
49
57
  @max_stored_sse_events = 100
58
+
59
+ # Gateway - default to ApplicationGateway if it exists, otherwise ActionMCP::Gateway
60
+ @gateway_class = defined?(::ApplicationGateway) ? ::ApplicationGateway : ActionMCP::Gateway
61
+ @current_class = nil
62
+
63
+ # Session Store
64
+ @session_store_type = Rails.env.production? ? :active_record : :volatile
50
65
  end
51
66
 
52
67
  def name
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMCP
4
+ class Current < ActiveSupport::CurrentAttributes
5
+ attribute :user
6
+ attribute :gateway
7
+
8
+ def user=(user)
9
+ super
10
+ set_user_time_zone if user.respond_to?(:time_zone)
11
+ end
12
+
13
+ private
14
+
15
+ def set_user_time_zone
16
+ Time.zone = user.time_zone
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMCP
4
+ module CurrentHelpers
5
+ extend ActiveSupport::Concern
6
+
7
+ protected
8
+
9
+ # Access the current user from ActionMCP::Current
10
+ def current_user
11
+ ActionMCP::Current.user
12
+ end
13
+
14
+ # Access the current gateway from ActionMCP::Current
15
+ def current_gateway
16
+ ActionMCP::Current.gateway
17
+ end
18
+ end
19
+ end