actionmcp 0.52.1 → 0.52.2
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/lib/action_mcp/server/active_record_session_store.rb +46 -0
- data/lib/action_mcp/server/memory_session.rb +423 -0
- data/lib/action_mcp/server/session_store.rb +0 -719
- data/lib/action_mcp/server/session_store_factory.rb +32 -0
- data/lib/action_mcp/server/test_session_store.rb +141 -0
- data/lib/action_mcp/server/volatile_session_store.rb +101 -0
- data/lib/action_mcp/version.rb +1 -1
- metadata +6 -1
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionMCP
|
4
|
+
module Server
|
5
|
+
class SessionStoreFactory
|
6
|
+
def self.create(type = nil, **options)
|
7
|
+
type ||= default_type
|
8
|
+
|
9
|
+
case type.to_sym
|
10
|
+
when :volatile, :memory
|
11
|
+
VolatileSessionStore.new
|
12
|
+
when :active_record, :persistent
|
13
|
+
ActiveRecordSessionStore.new
|
14
|
+
when :test
|
15
|
+
TestSessionStore.new
|
16
|
+
else
|
17
|
+
raise ArgumentError, "Unknown session store type: #{type}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.default_type
|
22
|
+
if Rails.env.test?
|
23
|
+
:volatile # Use volatile for tests unless explicitly using :test
|
24
|
+
elsif Rails.env.production?
|
25
|
+
:active_record
|
26
|
+
else
|
27
|
+
:volatile
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionMCP
|
4
|
+
module Server
|
5
|
+
# Test session store that tracks all operations for assertions
|
6
|
+
class TestSessionStore < VolatileSessionStore
|
7
|
+
attr_reader :operations, :created_sessions, :loaded_sessions,
|
8
|
+
:saved_sessions, :deleted_sessions, :notifications_sent
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
super
|
12
|
+
@operations = Concurrent::Array.new
|
13
|
+
@created_sessions = Concurrent::Array.new
|
14
|
+
@loaded_sessions = Concurrent::Array.new
|
15
|
+
@saved_sessions = Concurrent::Array.new
|
16
|
+
@deleted_sessions = Concurrent::Array.new
|
17
|
+
@notifications_sent = Concurrent::Array.new
|
18
|
+
@notification_callbacks = Concurrent::Array.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_session(session_id = nil, attributes = {})
|
22
|
+
session = super
|
23
|
+
@operations << { type: :create, session_id: session.id, attributes: attributes }
|
24
|
+
@created_sessions << session.id
|
25
|
+
|
26
|
+
# Hook into the session's write method to capture notifications
|
27
|
+
intercept_session_write(session)
|
28
|
+
|
29
|
+
session
|
30
|
+
end
|
31
|
+
|
32
|
+
def load_session(session_id)
|
33
|
+
session = super
|
34
|
+
@operations << { type: :load, session_id: session_id, found: !session.nil? }
|
35
|
+
@loaded_sessions << session_id if session
|
36
|
+
|
37
|
+
# Hook into the session's write method to capture notifications
|
38
|
+
intercept_session_write(session) if session
|
39
|
+
|
40
|
+
session
|
41
|
+
end
|
42
|
+
|
43
|
+
def save_session(session)
|
44
|
+
super
|
45
|
+
@operations << { type: :save, session_id: session.id }
|
46
|
+
@saved_sessions << session.id
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete_session(session_id)
|
50
|
+
result = super
|
51
|
+
@operations << { type: :delete, session_id: session_id }
|
52
|
+
@deleted_sessions << session_id
|
53
|
+
result
|
54
|
+
end
|
55
|
+
|
56
|
+
def cleanup_expired_sessions(older_than: 24.hours.ago)
|
57
|
+
count = super
|
58
|
+
@operations << { type: :cleanup, older_than: older_than, count: count }
|
59
|
+
count
|
60
|
+
end
|
61
|
+
|
62
|
+
# Test helper methods
|
63
|
+
def session_created?(session_id)
|
64
|
+
@created_sessions.include?(session_id)
|
65
|
+
end
|
66
|
+
|
67
|
+
def session_loaded?(session_id)
|
68
|
+
@loaded_sessions.include?(session_id)
|
69
|
+
end
|
70
|
+
|
71
|
+
def session_saved?(session_id)
|
72
|
+
@saved_sessions.include?(session_id)
|
73
|
+
end
|
74
|
+
|
75
|
+
def session_deleted?(session_id)
|
76
|
+
@deleted_sessions.include?(session_id)
|
77
|
+
end
|
78
|
+
|
79
|
+
def operation_count(type = nil)
|
80
|
+
if type
|
81
|
+
@operations.count { |op| op[:type] == type }
|
82
|
+
else
|
83
|
+
@operations.size
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Notification tracking methods
|
88
|
+
def track_notification(notification)
|
89
|
+
@notifications_sent << notification
|
90
|
+
@notification_callbacks.each { |cb| cb.call(notification) }
|
91
|
+
end
|
92
|
+
|
93
|
+
def on_notification(&block)
|
94
|
+
@notification_callbacks << block
|
95
|
+
end
|
96
|
+
|
97
|
+
def notifications_for_token(token)
|
98
|
+
@notifications_sent.select do |n|
|
99
|
+
n.params[:progressToken] == token
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def clear_notifications
|
104
|
+
@notifications_sent.clear
|
105
|
+
end
|
106
|
+
|
107
|
+
def reset_tracking!
|
108
|
+
@operations.clear
|
109
|
+
@created_sessions.clear
|
110
|
+
@loaded_sessions.clear
|
111
|
+
@saved_sessions.clear
|
112
|
+
@deleted_sessions.clear
|
113
|
+
@notifications_sent.clear
|
114
|
+
@notification_callbacks.clear
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def intercept_session_write(session)
|
120
|
+
return unless session
|
121
|
+
|
122
|
+
# Skip if already intercepted
|
123
|
+
return if session.singleton_methods.include?(:write)
|
124
|
+
|
125
|
+
test_store = self
|
126
|
+
|
127
|
+
# Intercept write method to capture all notifications
|
128
|
+
original_write = session.method(:write)
|
129
|
+
|
130
|
+
session.define_singleton_method(:write) do |data|
|
131
|
+
# Track progress notifications before calling original write
|
132
|
+
if data.is_a?(JSON_RPC::Notification) && data.method == "notifications/progress"
|
133
|
+
test_store.track_notification(data)
|
134
|
+
end
|
135
|
+
|
136
|
+
original_write.call(data)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionMCP
|
4
|
+
module Server
|
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 create_session(session_id = nil, attributes = {})
|
14
|
+
session_id ||= SecureRandom.hex(6)
|
15
|
+
|
16
|
+
session_data = {
|
17
|
+
id: session_id,
|
18
|
+
status: "pre_initialize",
|
19
|
+
initialized: false,
|
20
|
+
role: "server",
|
21
|
+
messages_count: 0,
|
22
|
+
sse_event_counter: 0,
|
23
|
+
created_at: Time.current,
|
24
|
+
updated_at: Time.current
|
25
|
+
}.merge(attributes)
|
26
|
+
|
27
|
+
session = MemorySession.new(session_data, self)
|
28
|
+
|
29
|
+
# Initialize server info and capabilities if server role
|
30
|
+
if session.role == "server"
|
31
|
+
session.server_info = {
|
32
|
+
name: ActionMCP.configuration.name,
|
33
|
+
version: ActionMCP.configuration.version
|
34
|
+
}
|
35
|
+
session.server_capabilities = ActionMCP.configuration.capabilities
|
36
|
+
|
37
|
+
# Initialize registries
|
38
|
+
session.tool_registry = ActionMCP.configuration.filtered_tools.map(&:name)
|
39
|
+
session.prompt_registry = ActionMCP.configuration.filtered_prompts.map(&:name)
|
40
|
+
session.resource_registry = ActionMCP.configuration.filtered_resources.map(&:name)
|
41
|
+
end
|
42
|
+
|
43
|
+
@sessions[session_id] = session
|
44
|
+
session
|
45
|
+
end
|
46
|
+
|
47
|
+
def load_session(session_id)
|
48
|
+
session = @sessions[session_id]
|
49
|
+
if session
|
50
|
+
session.instance_variable_set(:@new_record, false)
|
51
|
+
end
|
52
|
+
session
|
53
|
+
end
|
54
|
+
|
55
|
+
def save_session(session)
|
56
|
+
@sessions[session.id] = session
|
57
|
+
end
|
58
|
+
|
59
|
+
def delete_session(session_id)
|
60
|
+
@sessions.delete(session_id)
|
61
|
+
end
|
62
|
+
|
63
|
+
def session_exists?(session_id)
|
64
|
+
@sessions.key?(session_id)
|
65
|
+
end
|
66
|
+
|
67
|
+
def find_sessions(criteria = {})
|
68
|
+
sessions = @sessions.values
|
69
|
+
|
70
|
+
# Filter by status
|
71
|
+
if criteria[:status]
|
72
|
+
sessions = sessions.select { |s| s.status == criteria[:status] }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Filter by role
|
76
|
+
if criteria[:role]
|
77
|
+
sessions = sessions.select { |s| s.role == criteria[:role] }
|
78
|
+
end
|
79
|
+
|
80
|
+
sessions
|
81
|
+
end
|
82
|
+
|
83
|
+
def cleanup_expired_sessions(older_than: 24.hours.ago)
|
84
|
+
expired_ids = @sessions.select do |_id, session|
|
85
|
+
session.updated_at < older_than
|
86
|
+
end.keys
|
87
|
+
|
88
|
+
expired_ids.each { |id| @sessions.delete(id) }
|
89
|
+
expired_ids.count
|
90
|
+
end
|
91
|
+
|
92
|
+
def clear_all
|
93
|
+
@sessions.clear
|
94
|
+
end
|
95
|
+
|
96
|
+
def session_count
|
97
|
+
@sessions.size
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/action_mcp/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: actionmcp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.52.
|
4
|
+
version: 0.52.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdelkader Boudih
|
@@ -191,6 +191,7 @@ files:
|
|
191
191
|
- lib/action_mcp/resource_template.rb
|
192
192
|
- lib/action_mcp/resource_templates_registry.rb
|
193
193
|
- lib/action_mcp/server.rb
|
194
|
+
- lib/action_mcp/server/active_record_session_store.rb
|
194
195
|
- lib/action_mcp/server/base_messaging.rb
|
195
196
|
- lib/action_mcp/server/capabilities.rb
|
196
197
|
- lib/action_mcp/server/configuration.rb
|
@@ -200,6 +201,7 @@ files:
|
|
200
201
|
- lib/action_mcp/server/handlers/resource_handler.rb
|
201
202
|
- lib/action_mcp/server/handlers/tool_handler.rb
|
202
203
|
- lib/action_mcp/server/json_rpc_handler.rb
|
204
|
+
- lib/action_mcp/server/memory_session.rb
|
203
205
|
- lib/action_mcp/server/messaging.rb
|
204
206
|
- lib/action_mcp/server/notifications.rb
|
205
207
|
- lib/action_mcp/server/prompts.rb
|
@@ -210,10 +212,13 @@ files:
|
|
210
212
|
- lib/action_mcp/server/sampling.rb
|
211
213
|
- lib/action_mcp/server/sampling_request.rb
|
212
214
|
- lib/action_mcp/server/session_store.rb
|
215
|
+
- lib/action_mcp/server/session_store_factory.rb
|
213
216
|
- lib/action_mcp/server/simple_pub_sub.rb
|
214
217
|
- lib/action_mcp/server/solid_cable_adapter.rb
|
218
|
+
- lib/action_mcp/server/test_session_store.rb
|
215
219
|
- lib/action_mcp/server/tools.rb
|
216
220
|
- lib/action_mcp/server/transport_handler.rb
|
221
|
+
- lib/action_mcp/server/volatile_session_store.rb
|
217
222
|
- lib/action_mcp/sse_listener.rb
|
218
223
|
- lib/action_mcp/string_array.rb
|
219
224
|
- lib/action_mcp/tagged_stream_logging.rb
|