mcp_on_ruby 0.3.0 → 1.0.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/CHANGELOG.md +56 -28
- data/CODE_OF_CONDUCT.md +30 -58
- data/CONTRIBUTING.md +61 -67
- data/LICENSE.txt +2 -2
- data/README.md +159 -509
- data/bin/console +11 -0
- data/bin/setup +6 -0
- data/docs/advanced-usage.md +132 -0
- data/docs/api-reference.md +35 -0
- data/docs/testing.md +55 -0
- data/examples/claude/README.md +171 -0
- data/examples/claude/claude-bridge.js +122 -0
- data/lib/mcp_on_ruby/configuration.rb +74 -0
- data/lib/mcp_on_ruby/errors.rb +137 -0
- data/lib/mcp_on_ruby/generators/install_generator.rb +46 -0
- data/lib/mcp_on_ruby/generators/resource_generator.rb +63 -0
- data/lib/mcp_on_ruby/generators/templates/README +31 -0
- data/lib/mcp_on_ruby/generators/templates/application_resource.rb +20 -0
- data/lib/mcp_on_ruby/generators/templates/application_tool.rb +18 -0
- data/lib/mcp_on_ruby/generators/templates/initializer.rb +41 -0
- data/lib/mcp_on_ruby/generators/templates/resource.rb +50 -0
- data/lib/mcp_on_ruby/generators/templates/resource_spec.rb +67 -0
- data/lib/mcp_on_ruby/generators/templates/sample_resource.rb +57 -0
- data/lib/mcp_on_ruby/generators/templates/sample_tool.rb +59 -0
- data/lib/mcp_on_ruby/generators/templates/tool.rb +38 -0
- data/lib/mcp_on_ruby/generators/templates/tool_spec.rb +55 -0
- data/lib/mcp_on_ruby/generators/tool_generator.rb +51 -0
- data/lib/mcp_on_ruby/railtie.rb +108 -0
- data/lib/mcp_on_ruby/resource.rb +161 -0
- data/lib/mcp_on_ruby/server.rb +378 -0
- data/lib/mcp_on_ruby/tool.rb +134 -0
- data/lib/mcp_on_ruby/transport.rb +330 -0
- data/lib/mcp_on_ruby/version.rb +6 -0
- data/lib/mcp_on_ruby.rb +142 -0
- metadata +62 -173
- data/lib/ruby_mcp/client.rb +0 -43
- data/lib/ruby_mcp/configuration.rb +0 -90
- data/lib/ruby_mcp/errors.rb +0 -17
- data/lib/ruby_mcp/models/context.rb +0 -52
- data/lib/ruby_mcp/models/engine.rb +0 -31
- data/lib/ruby_mcp/models/message.rb +0 -60
- data/lib/ruby_mcp/providers/anthropic.rb +0 -269
- data/lib/ruby_mcp/providers/base.rb +0 -57
- data/lib/ruby_mcp/providers/openai.rb +0 -265
- data/lib/ruby_mcp/schemas.rb +0 -56
- data/lib/ruby_mcp/server/app.rb +0 -84
- data/lib/ruby_mcp/server/base_controller.rb +0 -49
- data/lib/ruby_mcp/server/content_controller.rb +0 -68
- data/lib/ruby_mcp/server/contexts_controller.rb +0 -67
- data/lib/ruby_mcp/server/controller.rb +0 -29
- data/lib/ruby_mcp/server/engines_controller.rb +0 -34
- data/lib/ruby_mcp/server/generate_controller.rb +0 -140
- data/lib/ruby_mcp/server/messages_controller.rb +0 -30
- data/lib/ruby_mcp/server/router.rb +0 -84
- data/lib/ruby_mcp/storage/active_record.rb +0 -414
- data/lib/ruby_mcp/storage/base.rb +0 -43
- data/lib/ruby_mcp/storage/error.rb +0 -8
- data/lib/ruby_mcp/storage/memory.rb +0 -69
- data/lib/ruby_mcp/storage/redis.rb +0 -197
- data/lib/ruby_mcp/storage_factory.rb +0 -43
- data/lib/ruby_mcp/validator.rb +0 -45
- data/lib/ruby_mcp/version.rb +0 -6
- data/lib/ruby_mcp.rb +0 -71
@@ -1,414 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
begin
|
4
|
-
require 'active_record'
|
5
|
-
rescue LoadError
|
6
|
-
# ActiveRecord is an optional dependency
|
7
|
-
# This error will be handled by the storage factory
|
8
|
-
end
|
9
|
-
|
10
|
-
require 'securerandom'
|
11
|
-
require 'json'
|
12
|
-
require_relative 'base'
|
13
|
-
|
14
|
-
module RubyMCP
|
15
|
-
module Storage
|
16
|
-
# ActiveRecord-based storage implementation for RubyMCP
|
17
|
-
class ActiveRecord < Base
|
18
|
-
# Initialize ActiveRecord storage with options
|
19
|
-
# @param options [Hash] Options for ActiveRecord storage
|
20
|
-
# @option options [Hash] :connection ActiveRecord connection configuration
|
21
|
-
# @option options [String] :table_prefix Prefix for table names (default: 'mcp_')
|
22
|
-
def initialize(options = {})
|
23
|
-
super
|
24
|
-
@table_prefix = options[:table_prefix] || 'mcp_'
|
25
|
-
@logger = options[:logger] || RubyMCP.logger
|
26
|
-
|
27
|
-
# Set up ActiveRecord connection if provided
|
28
|
-
::ActiveRecord::Base.establish_connection(options[:connection]) if options[:connection].is_a?(Hash)
|
29
|
-
|
30
|
-
setup_models
|
31
|
-
ensure_tables_exist
|
32
|
-
end
|
33
|
-
|
34
|
-
# Create a new context
|
35
|
-
# @param context [RubyMCP::Models::Context] Context to create
|
36
|
-
# @return [RubyMCP::Models::Context] Created context
|
37
|
-
def create_context(context)
|
38
|
-
# Check if context already exists
|
39
|
-
if @context_model.exists?(external_id: context.id)
|
40
|
-
raise RubyMCP::Errors::ContextError, "Context already exists: #{context.id}"
|
41
|
-
end
|
42
|
-
|
43
|
-
# Create the context record
|
44
|
-
ar_context = @context_model.create!(
|
45
|
-
external_id: context.id,
|
46
|
-
metadata: JSON.generate(context.metadata || {}),
|
47
|
-
created_at: context.created_at,
|
48
|
-
updated_at: context.updated_at
|
49
|
-
)
|
50
|
-
|
51
|
-
# Create message records if any
|
52
|
-
context.messages.each do |message|
|
53
|
-
create_message_record(ar_context.id, message)
|
54
|
-
end
|
55
|
-
|
56
|
-
# Create content records if any
|
57
|
-
context.content_map.each do |content_id, content_data|
|
58
|
-
create_content_record(ar_context.id, content_id, content_data)
|
59
|
-
end
|
60
|
-
|
61
|
-
context
|
62
|
-
end
|
63
|
-
|
64
|
-
# Get a context by ID
|
65
|
-
# @param context_id [String] ID of the context to get
|
66
|
-
# @return [RubyMCP::Models::Context] Found context
|
67
|
-
# @raise [RubyMCP::Errors::ContextError] If context not found
|
68
|
-
def get_context(context_id)
|
69
|
-
ar_context = @context_model.find_by(external_id: context_id)
|
70
|
-
raise RubyMCP::Errors::ContextError, "Context not found: #{context_id}" unless ar_context
|
71
|
-
|
72
|
-
# Parse metadata
|
73
|
-
metadata = begin
|
74
|
-
json = JSON.parse(ar_context.metadata)
|
75
|
-
symbolize_keys(json)
|
76
|
-
rescue JSON::ParserError => e
|
77
|
-
@logger.warn("Error parsing context metadata: #{e.message}")
|
78
|
-
{}
|
79
|
-
end
|
80
|
-
|
81
|
-
# Create a new context object
|
82
|
-
context = RubyMCP::Models::Context.new(
|
83
|
-
id: ar_context.external_id,
|
84
|
-
metadata: metadata
|
85
|
-
)
|
86
|
-
|
87
|
-
# Set timestamps
|
88
|
-
context.instance_variable_set(:@created_at, ar_context.created_at)
|
89
|
-
context.instance_variable_set(:@updated_at, ar_context.updated_at)
|
90
|
-
|
91
|
-
# Load messages
|
92
|
-
ar_messages = @message_model.where(context_id: ar_context.id).order(:created_at)
|
93
|
-
ar_messages.each do |ar_message|
|
94
|
-
# Parse message metadata
|
95
|
-
msg_metadata = begin
|
96
|
-
json = JSON.parse(ar_message.metadata)
|
97
|
-
symbolize_keys(json)
|
98
|
-
rescue JSON::ParserError => e
|
99
|
-
@logger.warn("Error parsing message metadata: #{e.message}")
|
100
|
-
{}
|
101
|
-
end
|
102
|
-
|
103
|
-
message = RubyMCP::Models::Message.new(
|
104
|
-
id: ar_message.external_id,
|
105
|
-
role: ar_message.role,
|
106
|
-
content: ar_message.content,
|
107
|
-
metadata: msg_metadata
|
108
|
-
)
|
109
|
-
message.instance_variable_set(:@created_at, ar_message.created_at)
|
110
|
-
context.instance_variable_get(:@messages) << message
|
111
|
-
end
|
112
|
-
|
113
|
-
# Load content
|
114
|
-
ar_contents = @content_model.where(context_id: ar_context.id)
|
115
|
-
ar_contents.each do |ar_content|
|
116
|
-
content_data = if ar_content.content_type == 'json'
|
117
|
-
begin
|
118
|
-
json = JSON.parse(ar_content.data_json)
|
119
|
-
symbolize_keys(json)
|
120
|
-
rescue JSON::ParserError => e
|
121
|
-
@logger.warn("Error parsing content JSON: #{e.message}")
|
122
|
-
{}
|
123
|
-
end
|
124
|
-
else
|
125
|
-
ar_content.data_binary
|
126
|
-
end
|
127
|
-
context.instance_variable_get(:@content_map)[ar_content.external_id] = content_data
|
128
|
-
end
|
129
|
-
|
130
|
-
context
|
131
|
-
end
|
132
|
-
|
133
|
-
# Update a context
|
134
|
-
# @param context [RubyMCP::Models::Context] Context to update
|
135
|
-
# @return [RubyMCP::Models::Context] Updated context
|
136
|
-
# @raise [RubyMCP::Errors::ContextError] If context not found
|
137
|
-
def update_context(context)
|
138
|
-
ar_context = @context_model.find_by(external_id: context.id)
|
139
|
-
raise RubyMCP::Errors::ContextError, "Context not found: #{context.id}" unless ar_context
|
140
|
-
|
141
|
-
# Update the context record
|
142
|
-
ar_context.update!(
|
143
|
-
metadata: JSON.generate(context.metadata || {}),
|
144
|
-
updated_at: context.updated_at
|
145
|
-
)
|
146
|
-
|
147
|
-
# We don't update messages or content here as they are added separately
|
148
|
-
context
|
149
|
-
end
|
150
|
-
|
151
|
-
# Delete a context
|
152
|
-
# @param context_id [String] ID of the context to delete
|
153
|
-
# @return [Boolean] True if deleted
|
154
|
-
# @raise [RubyMCP::Errors::ContextError] If context not found
|
155
|
-
def delete_context(context_id)
|
156
|
-
ar_context = @context_model.find_by(external_id: context_id)
|
157
|
-
raise RubyMCP::Errors::ContextError, "Context not found: #{context_id}" unless ar_context
|
158
|
-
|
159
|
-
# Delete all related records manually since we can't rely on cascading in tests
|
160
|
-
@message_model.where(context_id: ar_context.id).delete_all
|
161
|
-
@content_model.where(context_id: ar_context.id).delete_all
|
162
|
-
ar_context.destroy
|
163
|
-
|
164
|
-
true
|
165
|
-
end
|
166
|
-
|
167
|
-
# List contexts with pagination
|
168
|
-
# @param limit [Integer] Maximum number of contexts to return
|
169
|
-
# @param offset [Integer] Number of contexts to skip
|
170
|
-
# @return [Array<RubyMCP::Models::Context>] List of contexts
|
171
|
-
def list_contexts(limit: 100, offset: 0)
|
172
|
-
ar_contexts = @context_model.order(updated_at: :desc).limit(limit).offset(offset)
|
173
|
-
|
174
|
-
ar_contexts.map do |ar_context|
|
175
|
-
get_context(ar_context.external_id)
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
# List all content for a context
|
180
|
-
# @param context_id [String] ID of the context
|
181
|
-
# @return [Hash] Map of content_id to content data
|
182
|
-
# @raise [RubyMCP::Errors::ContextError] If context not found
|
183
|
-
def list_content(context_id)
|
184
|
-
ar_context = @context_model.find_by(external_id: context_id)
|
185
|
-
raise RubyMCP::Errors::ContextError, "Context not found: #{context_id}" unless ar_context
|
186
|
-
|
187
|
-
content_map = {}
|
188
|
-
ar_contents = @content_model.where(context_id: ar_context.id)
|
189
|
-
|
190
|
-
ar_contents.each do |ar_content|
|
191
|
-
content_data = if ar_content.content_type == 'json'
|
192
|
-
begin
|
193
|
-
json = JSON.parse(ar_content.data_json)
|
194
|
-
symbolize_keys(json)
|
195
|
-
rescue JSON::ParserError => e
|
196
|
-
raise RubyMCP::Errors::ContentError,
|
197
|
-
"Invalid JSON in content #{ar_content.external_id}: #{e.message}"
|
198
|
-
end
|
199
|
-
else
|
200
|
-
ar_content.data_binary
|
201
|
-
end
|
202
|
-
content_map[ar_content.external_id] = content_data
|
203
|
-
end
|
204
|
-
|
205
|
-
content_map
|
206
|
-
end
|
207
|
-
|
208
|
-
# Add a message to a context
|
209
|
-
# @param context_id [String] ID of the context
|
210
|
-
# @param message [RubyMCP::Models::Message] Message to add
|
211
|
-
# @return [RubyMCP::Models::Message] Added message
|
212
|
-
# @raise [RubyMCP::Errors::ContextError] If context not found
|
213
|
-
def add_message(context_id, message)
|
214
|
-
ar_context = @context_model.find_by(external_id: context_id)
|
215
|
-
raise RubyMCP::Errors::ContextError, "Context not found: #{context_id}" unless ar_context
|
216
|
-
|
217
|
-
# Create the message record
|
218
|
-
create_message_record(ar_context.id, message)
|
219
|
-
|
220
|
-
# Update the context's updated_at timestamp
|
221
|
-
ar_context.touch
|
222
|
-
|
223
|
-
message
|
224
|
-
end
|
225
|
-
|
226
|
-
# Add content to a context
|
227
|
-
# @param context_id [String] ID of the context
|
228
|
-
# @param content_id [String] ID of the content
|
229
|
-
# @param content_data [Object] Content data
|
230
|
-
# @return [String] Content ID
|
231
|
-
# @raise [RubyMCP::Errors::ContextError] If context not found
|
232
|
-
def add_content(context_id, content_id, content_data)
|
233
|
-
ar_context = @context_model.find_by(external_id: context_id)
|
234
|
-
raise RubyMCP::Errors::ContextError, "Context not found: #{context_id}" unless ar_context
|
235
|
-
|
236
|
-
# Create the content record
|
237
|
-
create_content_record(ar_context.id, content_id, content_data)
|
238
|
-
|
239
|
-
# Update the context's updated_at timestamp
|
240
|
-
ar_context.touch
|
241
|
-
|
242
|
-
content_id
|
243
|
-
end
|
244
|
-
|
245
|
-
# Get content from a context
|
246
|
-
# @param context_id [String] ID of the context
|
247
|
-
# @param content_id [String] ID of the content
|
248
|
-
# @return [Object] Content data
|
249
|
-
# @raise [RubyMCP::Errors::ContextError] If context not found
|
250
|
-
# @raise [RubyMCP::Errors::ContentError] If content not found
|
251
|
-
def get_content(context_id, content_id)
|
252
|
-
ar_context = @context_model.find_by(external_id: context_id)
|
253
|
-
raise RubyMCP::Errors::ContextError, "Context not found: #{context_id}" unless ar_context
|
254
|
-
|
255
|
-
ar_content = @content_model.find_by(context_id: ar_context.id, external_id: content_id)
|
256
|
-
raise RubyMCP::Errors::ContentError, "Content not found: #{content_id}" unless ar_content
|
257
|
-
|
258
|
-
if ar_content.content_type == 'json'
|
259
|
-
begin
|
260
|
-
json = JSON.parse(ar_content.data_json)
|
261
|
-
symbolize_keys(json)
|
262
|
-
rescue JSON::ParserError => e
|
263
|
-
raise RubyMCP::Errors::ContentError, "Invalid JSON in content #{content_id}: #{e.message}"
|
264
|
-
end
|
265
|
-
else
|
266
|
-
ar_content.data_binary
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
private
|
271
|
-
|
272
|
-
# Setup model classes
|
273
|
-
def setup_models
|
274
|
-
prefix = @table_prefix
|
275
|
-
|
276
|
-
# Context model
|
277
|
-
@context_model = Class.new(::ActiveRecord::Base) do
|
278
|
-
self.table_name = "#{prefix}contexts"
|
279
|
-
end
|
280
|
-
Object.const_set("MCPContext#{SecureRandom.hex(4)}", @context_model)
|
281
|
-
|
282
|
-
# Message model
|
283
|
-
@message_model = Class.new(::ActiveRecord::Base) do
|
284
|
-
self.table_name = "#{prefix}messages"
|
285
|
-
end
|
286
|
-
Object.const_set("MCPMessage#{SecureRandom.hex(4)}", @message_model)
|
287
|
-
|
288
|
-
# Content model
|
289
|
-
@content_model = Class.new(::ActiveRecord::Base) do
|
290
|
-
self.table_name = "#{prefix}contents"
|
291
|
-
end
|
292
|
-
Object.const_set("MCPContent#{SecureRandom.hex(4)}", @content_model)
|
293
|
-
end
|
294
|
-
|
295
|
-
# Ensure necessary tables exist
|
296
|
-
def ensure_tables_exist
|
297
|
-
connection = ::ActiveRecord::Base.connection
|
298
|
-
|
299
|
-
# Drop tables if they exist (for clean setup)
|
300
|
-
connection.drop_table("#{@table_prefix}contents") if connection.table_exists?("#{@table_prefix}contents")
|
301
|
-
|
302
|
-
connection.drop_table("#{@table_prefix}messages") if connection.table_exists?("#{@table_prefix}messages")
|
303
|
-
|
304
|
-
connection.drop_table("#{@table_prefix}contexts") if connection.table_exists?("#{@table_prefix}contexts")
|
305
|
-
|
306
|
-
# Create tables in proper order
|
307
|
-
create_contexts_table
|
308
|
-
create_messages_table
|
309
|
-
create_contents_table
|
310
|
-
end
|
311
|
-
|
312
|
-
# Check if a table exists
|
313
|
-
def table_exists?(table_name)
|
314
|
-
::ActiveRecord::Base.connection.table_exists?(table_name)
|
315
|
-
end
|
316
|
-
|
317
|
-
# Create the contexts table
|
318
|
-
def create_contexts_table
|
319
|
-
connection = ::ActiveRecord::Base.connection
|
320
|
-
connection.create_table("#{@table_prefix}contexts") do |t|
|
321
|
-
t.string :external_id, null: false
|
322
|
-
t.text :metadata, default: '{}'
|
323
|
-
t.timestamps
|
324
|
-
end
|
325
|
-
connection.add_index("#{@table_prefix}contexts", :external_id, unique: true)
|
326
|
-
connection.add_index("#{@table_prefix}contexts", :updated_at)
|
327
|
-
end
|
328
|
-
|
329
|
-
# Create the messages table
|
330
|
-
def create_messages_table
|
331
|
-
connection = ::ActiveRecord::Base.connection
|
332
|
-
connection.create_table("#{@table_prefix}messages") do |t|
|
333
|
-
t.bigint :context_id, null: false
|
334
|
-
t.string :external_id, null: false
|
335
|
-
t.string :role, null: false
|
336
|
-
t.text :content, null: false
|
337
|
-
t.text :metadata, default: '{}'
|
338
|
-
t.timestamps
|
339
|
-
end
|
340
|
-
connection.add_index("#{@table_prefix}messages", %i[context_id external_id], unique: true)
|
341
|
-
connection.add_index("#{@table_prefix}messages", :context_id)
|
342
|
-
end
|
343
|
-
|
344
|
-
# Create the contents table
|
345
|
-
def create_contents_table
|
346
|
-
connection = ::ActiveRecord::Base.connection
|
347
|
-
connection.create_table("#{@table_prefix}contents") do |t|
|
348
|
-
t.bigint :context_id, null: false
|
349
|
-
t.string :external_id, null: false
|
350
|
-
t.binary :data_binary
|
351
|
-
t.text :data_json
|
352
|
-
t.string :content_type
|
353
|
-
t.timestamps
|
354
|
-
end
|
355
|
-
connection.add_index("#{@table_prefix}contents", %i[context_id external_id], unique: true)
|
356
|
-
connection.add_index("#{@table_prefix}contents", :context_id)
|
357
|
-
end
|
358
|
-
|
359
|
-
# Create a message record
|
360
|
-
def create_message_record(context_id, message)
|
361
|
-
@message_model.create!(
|
362
|
-
context_id: context_id,
|
363
|
-
external_id: message.id,
|
364
|
-
role: message.role,
|
365
|
-
content: message.content.to_s,
|
366
|
-
metadata: JSON.generate(message.metadata || {}),
|
367
|
-
created_at: message.created_at
|
368
|
-
)
|
369
|
-
end
|
370
|
-
|
371
|
-
# Create a content record
|
372
|
-
def create_content_record(context_id, content_id, content_data)
|
373
|
-
# Validate data type first
|
374
|
-
unless content_data.is_a?(String) || content_data.is_a?(Hash) ||
|
375
|
-
content_data.is_a?(Array) || content_data.is_a?(Numeric) ||
|
376
|
-
content_data.is_a?(TrueClass) || content_data.is_a?(FalseClass) ||
|
377
|
-
content_data.is_a?(NilClass)
|
378
|
-
raise RubyMCP::Errors::ContentError,
|
379
|
-
"Invalid data type: #{content_data.class.name}. Must be String, Hash, Array, Numeric, Boolean, or nil."
|
380
|
-
end
|
381
|
-
|
382
|
-
if content_data.is_a?(Hash) || content_data.is_a?(Array)
|
383
|
-
@content_model.create!(
|
384
|
-
context_id: context_id,
|
385
|
-
external_id: content_id,
|
386
|
-
data_json: JSON.generate(content_data),
|
387
|
-
content_type: 'json'
|
388
|
-
)
|
389
|
-
else
|
390
|
-
@content_model.create!(
|
391
|
-
context_id: context_id,
|
392
|
-
external_id: content_id,
|
393
|
-
data_binary: content_data.to_s,
|
394
|
-
content_type: 'binary'
|
395
|
-
)
|
396
|
-
end
|
397
|
-
end
|
398
|
-
|
399
|
-
# Helper method to symbolize keys in hashes (including nested ones)
|
400
|
-
def symbolize_keys(obj)
|
401
|
-
case obj
|
402
|
-
when Hash
|
403
|
-
obj.each_with_object({}) do |(key, value), result|
|
404
|
-
result[key.to_sym] = symbolize_keys(value)
|
405
|
-
end
|
406
|
-
when Array
|
407
|
-
obj.map { |item| symbolize_keys(item) }
|
408
|
-
else
|
409
|
-
obj
|
410
|
-
end
|
411
|
-
end
|
412
|
-
end
|
413
|
-
end
|
414
|
-
end
|
@@ -1,43 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RubyMCP
|
4
|
-
module Storage
|
5
|
-
class Base
|
6
|
-
def initialize(options = {})
|
7
|
-
@options = options
|
8
|
-
end
|
9
|
-
|
10
|
-
def create_context(context)
|
11
|
-
raise NotImplementedError, "#{self.class.name} must implement #create_context"
|
12
|
-
end
|
13
|
-
|
14
|
-
def get_context(context_id)
|
15
|
-
raise NotImplementedError, "#{self.class.name} must implement #get_context"
|
16
|
-
end
|
17
|
-
|
18
|
-
def update_context(context)
|
19
|
-
raise NotImplementedError, "#{self.class.name} must implement #update_context"
|
20
|
-
end
|
21
|
-
|
22
|
-
def delete_context(context_id)
|
23
|
-
raise NotImplementedError, "#{self.class.name} must implement #delete_context"
|
24
|
-
end
|
25
|
-
|
26
|
-
def list_contexts(limit: 100, offset: 0)
|
27
|
-
raise NotImplementedError, "#{self.class.name} must implement #list_contexts"
|
28
|
-
end
|
29
|
-
|
30
|
-
def add_message(context_id, message)
|
31
|
-
raise NotImplementedError, "#{self.class.name} must implement #add_message"
|
32
|
-
end
|
33
|
-
|
34
|
-
def add_content(context_id, content_id, content_data)
|
35
|
-
raise NotImplementedError, "#{self.class.name} must implement #add_content"
|
36
|
-
end
|
37
|
-
|
38
|
-
def get_content(context_id, content_id)
|
39
|
-
raise NotImplementedError, "#{self.class.name} must implement #get_content"
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
@@ -1,69 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'base'
|
4
|
-
|
5
|
-
module RubyMCP
|
6
|
-
module Storage
|
7
|
-
class Memory < Base
|
8
|
-
def initialize(options = {})
|
9
|
-
super
|
10
|
-
@contexts = {}
|
11
|
-
@contents = {}
|
12
|
-
end
|
13
|
-
|
14
|
-
def create_context(context)
|
15
|
-
@contexts[context.id] = context
|
16
|
-
context
|
17
|
-
end
|
18
|
-
|
19
|
-
def get_context(context_id)
|
20
|
-
context = @contexts[context_id]
|
21
|
-
raise RubyMCP::Errors::ContextError, "Context not found: #{context_id}" unless context
|
22
|
-
|
23
|
-
context
|
24
|
-
end
|
25
|
-
|
26
|
-
def update_context(context)
|
27
|
-
@contexts[context.id] = context
|
28
|
-
context
|
29
|
-
end
|
30
|
-
|
31
|
-
def delete_context(context_id)
|
32
|
-
context = get_context(context_id)
|
33
|
-
@contexts.delete(context_id)
|
34
|
-
@contents.delete(context_id)
|
35
|
-
context
|
36
|
-
end
|
37
|
-
|
38
|
-
def list_contexts(limit: 100, offset: 0)
|
39
|
-
@contexts.values
|
40
|
-
.sort_by(&:updated_at)
|
41
|
-
.reverse
|
42
|
-
.slice(offset, limit) || []
|
43
|
-
end
|
44
|
-
|
45
|
-
def add_message(context_id, message)
|
46
|
-
context = get_context(context_id)
|
47
|
-
context.add_message(message)
|
48
|
-
update_context(context)
|
49
|
-
message
|
50
|
-
end
|
51
|
-
|
52
|
-
def add_content(context_id, content_id, content_data)
|
53
|
-
get_context(context_id)
|
54
|
-
@contents[context_id] ||= {}
|
55
|
-
@contents[context_id][content_id] = content_data
|
56
|
-
content_id
|
57
|
-
end
|
58
|
-
|
59
|
-
def get_content(context_id, content_id)
|
60
|
-
get_context(context_id)
|
61
|
-
contents = @contents[context_id] || {}
|
62
|
-
content = contents[content_id]
|
63
|
-
raise RubyMCP::Errors::ContentError, "Content not found: #{content_id}" unless content
|
64
|
-
|
65
|
-
content
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|