actionmcp 0.52.0 → 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/README.md +109 -0
- data/lib/action_mcp/client/active_record_session_store.rb +57 -0
- data/lib/action_mcp/client/session_store.rb +0 -192
- data/lib/action_mcp/client/session_store_factory.rb +36 -0
- data/lib/action_mcp/client/test_session_store.rb +84 -0
- data/lib/action_mcp/client/volatile_session_store.rb +38 -0
- 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 +10 -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
|
@@ -137,6 +137,7 @@ files:
|
|
137
137
|
- lib/action_mcp/callbacks.rb
|
138
138
|
- lib/action_mcp/capability.rb
|
139
139
|
- lib/action_mcp/client.rb
|
140
|
+
- lib/action_mcp/client/active_record_session_store.rb
|
140
141
|
- lib/action_mcp/client/base.rb
|
141
142
|
- lib/action_mcp/client/blueprint.rb
|
142
143
|
- lib/action_mcp/client/catalog.rb
|
@@ -151,11 +152,14 @@ files:
|
|
151
152
|
- lib/action_mcp/client/roots.rb
|
152
153
|
- lib/action_mcp/client/server.rb
|
153
154
|
- lib/action_mcp/client/session_store.rb
|
155
|
+
- lib/action_mcp/client/session_store_factory.rb
|
154
156
|
- lib/action_mcp/client/sse_client.rb
|
155
157
|
- lib/action_mcp/client/streamable_http_transport.rb
|
158
|
+
- lib/action_mcp/client/test_session_store.rb
|
156
159
|
- lib/action_mcp/client/toolbox.rb
|
157
160
|
- lib/action_mcp/client/tools.rb
|
158
161
|
- lib/action_mcp/client/transport.rb
|
162
|
+
- lib/action_mcp/client/volatile_session_store.rb
|
159
163
|
- lib/action_mcp/configuration.rb
|
160
164
|
- lib/action_mcp/console_detector.rb
|
161
165
|
- lib/action_mcp/content.rb
|
@@ -187,6 +191,7 @@ files:
|
|
187
191
|
- lib/action_mcp/resource_template.rb
|
188
192
|
- lib/action_mcp/resource_templates_registry.rb
|
189
193
|
- lib/action_mcp/server.rb
|
194
|
+
- lib/action_mcp/server/active_record_session_store.rb
|
190
195
|
- lib/action_mcp/server/base_messaging.rb
|
191
196
|
- lib/action_mcp/server/capabilities.rb
|
192
197
|
- lib/action_mcp/server/configuration.rb
|
@@ -196,6 +201,7 @@ files:
|
|
196
201
|
- lib/action_mcp/server/handlers/resource_handler.rb
|
197
202
|
- lib/action_mcp/server/handlers/tool_handler.rb
|
198
203
|
- lib/action_mcp/server/json_rpc_handler.rb
|
204
|
+
- lib/action_mcp/server/memory_session.rb
|
199
205
|
- lib/action_mcp/server/messaging.rb
|
200
206
|
- lib/action_mcp/server/notifications.rb
|
201
207
|
- lib/action_mcp/server/prompts.rb
|
@@ -206,10 +212,13 @@ files:
|
|
206
212
|
- lib/action_mcp/server/sampling.rb
|
207
213
|
- lib/action_mcp/server/sampling_request.rb
|
208
214
|
- lib/action_mcp/server/session_store.rb
|
215
|
+
- lib/action_mcp/server/session_store_factory.rb
|
209
216
|
- lib/action_mcp/server/simple_pub_sub.rb
|
210
217
|
- lib/action_mcp/server/solid_cable_adapter.rb
|
218
|
+
- lib/action_mcp/server/test_session_store.rb
|
211
219
|
- lib/action_mcp/server/tools.rb
|
212
220
|
- lib/action_mcp/server/transport_handler.rb
|
221
|
+
- lib/action_mcp/server/volatile_session_store.rb
|
213
222
|
- lib/action_mcp/sse_listener.rb
|
214
223
|
- lib/action_mcp/string_array.rb
|
215
224
|
- lib/action_mcp/tagged_stream_logging.rb
|