mcp_on_ruby 0.1.0 → 0.2.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 +13 -0
- data/README.md +86 -55
- data/lib/ruby_mcp/client.rb +15 -0
- data/lib/ruby_mcp/configuration.rb +31 -1
- data/lib/ruby_mcp/storage/error.rb +8 -0
- data/lib/ruby_mcp/storage/redis.rb +197 -0
- data/lib/ruby_mcp/storage_factory.rb +32 -0
- data/lib/ruby_mcp/version.rb +1 -1
- data/lib/ruby_mcp.rb +16 -0
- metadata +21 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ebcb9d413504e157ae357d8904fd719a2d5c264c2b483042e295848a91413960
|
4
|
+
data.tar.gz: 79f2057e8da7aefb9a093b079dc593a0d8aa786c53b1c54cec0654b485fef76f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50769ce05c60dfd97cc896f9259ef53f774f3fb4bb91accb05c37b0bba86e4829c208cd82c26367086a0ed4793b4e7dcd03d4a6074946ab5ed846cfef8715dc4
|
7
|
+
data.tar.gz: b34e216121ba7765d81234afcb373067f55d59dc7fe21141ca74fe3a205b0e6344d753c423f5b4fda128bf6f6d8b7af1dcea5cb0834fbc69d4f96aad669b1389
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [0.2.0] - 2025-04-21
|
4
|
+
|
5
|
+
### Added
|
6
|
+
- Redis storage backend with comprehensive test coverage
|
7
|
+
- Configurable TTL and namespace support for Redis keys
|
8
|
+
- Rails integration with Redis storage examples
|
9
|
+
- Wiki documentation for Redis storage setup and usage
|
10
|
+
|
11
|
+
### Changed
|
12
|
+
- Enhanced storage factory to support different backend types
|
13
|
+
- Improved configuration API for more intuitive setup
|
14
|
+
- Updated README with Redis storage documentation
|
15
|
+
|
3
16
|
## [0.1.0] - 2025-04-18
|
4
17
|
|
5
18
|
Initial release of version 0.1.0 of the Ruby Gem.
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
<div align="center">
|
2
2
|
|
3
|
-
#
|
4
|
-
|
3
|
+
# MCP on Ruby
|
4
|
+
[](https://badge.fury.io/rb/mcp_on_ruby)
|
5
5
|
[](https://github.com/nagstler/ruby_mcp/actions/workflows/build.yml)
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
7
7
|
[](https://github.com/nagstler/ruby_mcp/actions/workflows/rubocop.yml)
|
@@ -11,51 +11,54 @@
|
|
11
11
|
<strong>The Ruby way to build MCP servers and clients.</strong>
|
12
12
|
</div>
|
13
13
|
|
14
|
-
## Introduction
|
14
|
+
## 🔍 Introduction
|
15
|
+
|
16
|
+
The [Model Context Protocol](https://modelcontextprotocol.io) provides a standardized way for applications to interact with language models. Similar to how REST standardized web APIs, MCP creates a consistent interface for working with providers like OpenAI and Anthropic.
|
15
17
|
|
16
|
-
|
18
|
+

|
17
19
|
|
18
|
-
|
20
|
+
> 📌 If you find this useful, **give it a ⭐ on GitHub**
|
19
21
|
|
20
|
-
## Table of Contents
|
22
|
+
## 📋 Table of Contents
|
21
23
|
|
22
|
-
- [Introduction](
|
23
|
-
- [Why
|
24
|
-
- [Installation](
|
25
|
-
- [Quick Start](
|
26
|
-
- [Interactive Demo](
|
27
|
-
- [Configuration Options](
|
28
|
-
- [Server Endpoints](
|
29
|
-
- [Detailed Usage](
|
24
|
+
- [🔍 Introduction](#-introduction)
|
25
|
+
- [🌟 Why MCP on Ruby?](#-why-mcp-on-ruby)
|
26
|
+
- [📦 Installation](#-installation)
|
27
|
+
- [🚀 Quick Start](#-quick-start)
|
28
|
+
- [🎮 Interactive Demo](#-interactive-demo)
|
29
|
+
- [⚙️ Configuration Options](#️-configuration-options)
|
30
|
+
- [🛣️ Server Endpoints](#️-server-endpoints)
|
31
|
+
- [📚 Detailed Usage](#-detailed-usage)
|
30
32
|
- [Creating a Context](#creating-a-context)
|
31
33
|
- [Adding a Message](#adding-a-message)
|
32
34
|
- [Generating a Response](#generating-a-response)
|
33
35
|
- [Streaming a Response](#streaming-a-response)
|
34
36
|
- [Uploading Content](#uploading-content)
|
35
37
|
- [Using Tool Calls](#using-tool-calls)
|
36
|
-
- [Rails Integration](
|
37
|
-
- [Custom Storage Backend](
|
38
|
-
- [Authentication](
|
39
|
-
- [Development](
|
40
|
-
- [Roadmap](
|
41
|
-
- [Contributing](
|
42
|
-
- [License](
|
43
|
-
|
44
|
-
## Why
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
-
|
49
|
-
-
|
50
|
-
-
|
51
|
-
-
|
52
|
-
-
|
53
|
-
-
|
54
|
-
-
|
38
|
+
- [🚄 Rails Integration](#-rails-integration)
|
39
|
+
- [💾 Custom Storage Backend](#-storage-backends)
|
40
|
+
- [🔒 Authentication](#-authentication)
|
41
|
+
- [🛠️ Development](#️-development)
|
42
|
+
- [🗺️ Roadmap](#️-roadmap)
|
43
|
+
- [👥 Contributing](#-contributing)
|
44
|
+
- [📄 License](#-license)
|
45
|
+
|
46
|
+
## 🌟 Why MCP on Ruby?
|
47
|
+
|
48
|
+
**MCP on Ruby** provides a comprehensive implementation of the Model Context Protocol with these features:
|
49
|
+
|
50
|
+
- **Provider-Ready:** Pre-built adapters for OpenAI and Anthropic - just add your API key
|
51
|
+
- **Complete Protocol Implementation:** Fully implements the MCP specification for compatibility
|
52
|
+
- **Conversation Management:** Context handling for multi-turn conversations
|
53
|
+
- **Flexible Storage:** Extensible storage backends
|
54
|
+
- **Streaming Support:** Real-time response streaming for dynamic UIs
|
55
|
+
- **File Handling:** Upload and reference files in conversations
|
56
|
+
- **Tool Calling:** Support for LLM function calling capabilities
|
57
|
+
- **Battle-Tested:** Comprehensive test suite ensures reliability
|
55
58
|
|
56
59
|
The library is designed to be straightforward to use while maintaining full compatibility with the MCP specification.
|
57
60
|
|
58
|
-
## Installation
|
61
|
+
## 📦 Installation
|
59
62
|
|
60
63
|
Add this line to your application's Gemfile:
|
61
64
|
|
@@ -75,7 +78,7 @@ Or install it yourself as:
|
|
75
78
|
$ gem install mcp_on_ruby
|
76
79
|
```
|
77
80
|
|
78
|
-
## Quick Start
|
81
|
+
## 🚀 Quick Start
|
79
82
|
|
80
83
|
Here's how to get a basic MCP server running:
|
81
84
|
|
@@ -92,29 +95,26 @@ end
|
|
92
95
|
|
93
96
|
# Start the MCP server
|
94
97
|
server = RubyMCP::Server::Controller.new
|
95
|
-
server.start
|
98
|
+
server.start
|
96
99
|
```
|
97
100
|
|
98
|
-
### Interactive Demo
|
101
|
+
### 🎮 Interactive Demo
|
99
102
|
|
100
103
|
The repository includes an interactive demo that walks through all the key MCP concepts:
|
101
104
|
|
102
|
-
First, start the example server:
|
103
|
-
|
104
105
|
```bash
|
106
|
+
# Terminal 1: Start the server
|
105
107
|
cd examples/simple_server
|
106
108
|
ruby server.rb
|
107
|
-
```
|
108
109
|
|
109
|
-
|
110
|
-
|
111
|
-
```bash
|
110
|
+
# Terminal 2: Run the client
|
112
111
|
cd examples/simple_server
|
113
112
|
ruby client.rb
|
114
113
|
```
|
114
|
+
|
115
115
|
This demo provides a guided tour of the MCP functionality, showing each step of creating contexts, adding messages, and generating responses with detailed explanations.
|
116
116
|
|
117
|
-
## Configuration Options
|
117
|
+
## ⚙️ Configuration Options
|
118
118
|
|
119
119
|
RubyMCP offers several configuration options:
|
120
120
|
|
@@ -148,9 +148,9 @@ RubyMCP.configure do |config|
|
|
148
148
|
end
|
149
149
|
```
|
150
150
|
|
151
|
-
## Server Endpoints
|
151
|
+
## 🛣️ Server Endpoints
|
152
152
|
|
153
|
-
The MCP server provides the following endpoints:
|
153
|
+
The MCP server provides the following RESTful endpoints:
|
154
154
|
|
155
155
|
### Engines
|
156
156
|
- `GET /engines` - List available language models
|
@@ -172,7 +172,7 @@ The MCP server provides the following endpoints:
|
|
172
172
|
- `POST /content` - Upload content (files)
|
173
173
|
- `GET /content/:context_id/:id` - Retrieve uploaded content
|
174
174
|
|
175
|
-
## Detailed Usage
|
175
|
+
## 📚 Detailed Usage
|
176
176
|
|
177
177
|
### Creating a Context
|
178
178
|
|
@@ -316,7 +316,7 @@ if response.body["tool_calls"]
|
|
316
316
|
end
|
317
317
|
```
|
318
318
|
|
319
|
-
## Rails Integration
|
319
|
+
## 🚄 Rails Integration
|
320
320
|
|
321
321
|
For Rails applications, create an initializer at `config/initializers/ruby_mcp.rb`:
|
322
322
|
|
@@ -360,8 +360,38 @@ Rails.application.routes.draw do
|
|
360
360
|
end
|
361
361
|
```
|
362
362
|
|
363
|
-
##
|
363
|
+
## 💾 Storage Backends
|
364
|
+
|
365
|
+
### Redis Storage
|
366
|
+
|
367
|
+
MCP on Ruby supports Redis as a persistent storage backend:
|
368
|
+
|
369
|
+
1. Add the Redis gem to your Gemfile:
|
370
|
+
```ruby
|
371
|
+
gem 'redis', '~> 5.0'
|
372
|
+
```
|
373
|
+
|
374
|
+
2. Configure Redis storage:
|
375
|
+
```ruby
|
376
|
+
RubyMCP.configure do |config|
|
377
|
+
config.storage = :redis
|
378
|
+
config.redis = {
|
379
|
+
url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/0'),
|
380
|
+
namespace: "app_mcp_#{Rails.env}",
|
381
|
+
ttl: 86400 # 1 day in seconds
|
382
|
+
}
|
383
|
+
end
|
384
|
+
```
|
385
|
+
|
386
|
+
3. Access the configured client:
|
387
|
+
```ruby
|
388
|
+
client = RubyMCP.client
|
389
|
+
```
|
390
|
+
|
391
|
+
For detailed integration examples, see the [[Redis Storage](https://github.com/nagstler/mcp_on_ruby/wiki/Redis-Storage)] wiki page.
|
392
|
+
|
364
393
|
|
394
|
+
### Custom storage
|
365
395
|
You can implement custom storage backends by extending the base storage class:
|
366
396
|
|
367
397
|
```ruby
|
@@ -409,7 +439,7 @@ RubyMCP.configure do |config|
|
|
409
439
|
end
|
410
440
|
```
|
411
441
|
|
412
|
-
## Authentication
|
442
|
+
## 🔒 Authentication
|
413
443
|
|
414
444
|
To enable JWT authentication:
|
415
445
|
|
@@ -446,7 +476,7 @@ conn.get("http://localhost:3000/contexts") do |req|
|
|
446
476
|
end
|
447
477
|
```
|
448
478
|
|
449
|
-
## Development
|
479
|
+
## 🛠️ Development
|
450
480
|
|
451
481
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
452
482
|
|
@@ -462,11 +492,12 @@ bundle exec rspec
|
|
462
492
|
bundle exec ruby examples/simple_server/server.rb
|
463
493
|
```
|
464
494
|
|
465
|
-
## Roadmap
|
495
|
+
## 🗺️ Roadmap
|
466
496
|
|
467
497
|
While RubyMCP is functional for basic use cases, there are several areas planned for improvement:
|
468
498
|
|
469
|
-
- [
|
499
|
+
- [x] Redis persistent storage backend
|
500
|
+
- [ ] ActiveRecord storage backend
|
470
501
|
- [ ] Complete test coverage, including integration tests
|
471
502
|
- [ ] Improved error handling and recovery strategies
|
472
503
|
- [ ] Rate limiting for provider APIs
|
@@ -476,7 +507,7 @@ While RubyMCP is functional for basic use cases, there are several areas planned
|
|
476
507
|
|
477
508
|
:heart: Contributions in any of these areas are welcome!
|
478
509
|
|
479
|
-
## Contributing
|
510
|
+
## 👥 Contributing
|
480
511
|
|
481
512
|
1. Fork the repository
|
482
513
|
2. Create your feature branch (`git checkout -b feature/my-new-feature`)
|
@@ -486,6 +517,6 @@ While RubyMCP is functional for basic use cases, there are several areas planned
|
|
486
517
|
|
487
518
|
Bug reports and pull requests are welcome on GitHub at https://github.com/nagstler/mcp_on_ruby.
|
488
519
|
|
489
|
-
## License
|
520
|
+
## 📄 License
|
490
521
|
|
491
522
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# lib/ruby_mcp/client.rb
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyMCP
|
5
|
+
class Client
|
6
|
+
attr_reader :storage
|
7
|
+
|
8
|
+
def initialize(storage)
|
9
|
+
@storage = storage
|
10
|
+
end
|
11
|
+
|
12
|
+
# You can add additional convenience methods here
|
13
|
+
# that delegate to storage or provide higher-level functionality
|
14
|
+
end
|
15
|
+
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module RubyMCP
|
4
4
|
class Configuration
|
5
5
|
attr_accessor :providers, :storage, :server_port, :server_host,
|
6
|
-
:auth_required, :jwt_secret, :token_expiry, :max_contexts
|
6
|
+
:auth_required, :jwt_secret, :token_expiry, :max_contexts, :redis
|
7
7
|
|
8
8
|
def initialize
|
9
9
|
@providers = {}
|
@@ -14,6 +14,21 @@ module RubyMCP
|
|
14
14
|
@jwt_secret = nil
|
15
15
|
@token_expiry = 3600 # 1 hour
|
16
16
|
@max_contexts = 1000
|
17
|
+
@storage = :memory # Default to memory storage
|
18
|
+
@redis = {} # Default empty Redis config
|
19
|
+
end
|
20
|
+
|
21
|
+
def storage_config
|
22
|
+
if @storage == :redis
|
23
|
+
{
|
24
|
+
type: :redis,
|
25
|
+
connection: redis_connection_config,
|
26
|
+
namespace: @redis[:namespace] || 'ruby_mcp',
|
27
|
+
ttl: @redis[:ttl] || 86_400
|
28
|
+
}
|
29
|
+
else
|
30
|
+
{ type: @storage }
|
31
|
+
end
|
17
32
|
end
|
18
33
|
|
19
34
|
def storage_instance
|
@@ -35,5 +50,20 @@ module RubyMCP
|
|
35
50
|
|
36
51
|
end
|
37
52
|
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def redis_connection_config
|
57
|
+
if @redis[:url]
|
58
|
+
{ url: @redis[:url] }
|
59
|
+
else
|
60
|
+
{
|
61
|
+
host: @redis[:host] || 'localhost',
|
62
|
+
port: @redis[:port] || 6379,
|
63
|
+
db: @redis[:db] || 0,
|
64
|
+
password: @redis[:password]
|
65
|
+
}.compact
|
66
|
+
end
|
67
|
+
end
|
38
68
|
end
|
39
69
|
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redis'
|
4
|
+
require 'json'
|
5
|
+
require_relative 'error'
|
6
|
+
|
7
|
+
module RubyMCP
|
8
|
+
module Storage
|
9
|
+
# Redis-based storage implementation for RubyMCP
|
10
|
+
class Redis < Base
|
11
|
+
def initialize(options = {})
|
12
|
+
super
|
13
|
+
@redis = if options[:connection].is_a?(::Redis)
|
14
|
+
options[:connection]
|
15
|
+
elsif options[:connection].is_a?(Hash)
|
16
|
+
::Redis.new(options[:connection])
|
17
|
+
else
|
18
|
+
::Redis.new
|
19
|
+
end
|
20
|
+
@namespace = options[:namespace] || 'ruby_mcp'
|
21
|
+
@ttl = options[:ttl] || 86_400 # Default 1 day TTL in seconds
|
22
|
+
end
|
23
|
+
|
24
|
+
# Context management
|
25
|
+
def create_context(context)
|
26
|
+
# Ensure context has an ID
|
27
|
+
context_id = context['id']
|
28
|
+
raise Error, 'Context must have an ID' unless context_id
|
29
|
+
|
30
|
+
# Check if context already exists
|
31
|
+
raise Error, "Context with ID '#{context_id}' already exists" if get_context(context_id)
|
32
|
+
|
33
|
+
# Store the context
|
34
|
+
store_context(context)
|
35
|
+
|
36
|
+
# Add to index with timestamp for ordering
|
37
|
+
@redis.zadd(
|
38
|
+
contexts_index_key,
|
39
|
+
Time.now.to_f,
|
40
|
+
context_id
|
41
|
+
)
|
42
|
+
|
43
|
+
# Return the created context
|
44
|
+
context
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_context(context_id)
|
48
|
+
# Get the context data
|
49
|
+
context_data = @redis.get(context_key(context_id))
|
50
|
+
return nil unless context_data
|
51
|
+
|
52
|
+
# Parse the context
|
53
|
+
context = JSON.parse(context_data)
|
54
|
+
|
55
|
+
# Get messages if any
|
56
|
+
messages = get_messages(context_id)
|
57
|
+
context['messages'] = messages if messages.any?
|
58
|
+
|
59
|
+
context
|
60
|
+
end
|
61
|
+
|
62
|
+
def update_context(context)
|
63
|
+
context_id = context['id']
|
64
|
+
|
65
|
+
# Check if context exists
|
66
|
+
raise Error, "Context with ID '#{context_id}' does not exist" unless get_context(context_id)
|
67
|
+
|
68
|
+
# Store the updated context
|
69
|
+
store_context(context)
|
70
|
+
|
71
|
+
context
|
72
|
+
end
|
73
|
+
|
74
|
+
def delete_context(context_id)
|
75
|
+
# Remove from index
|
76
|
+
@redis.zrem(contexts_index_key, context_id)
|
77
|
+
|
78
|
+
# Delete context data
|
79
|
+
@redis.del(context_key(context_id))
|
80
|
+
|
81
|
+
# Delete messages
|
82
|
+
@redis.del(messages_key(context_id))
|
83
|
+
|
84
|
+
# Delete all content (using pattern matching)
|
85
|
+
content_pattern = key(['context', context_id, 'content', '*'])
|
86
|
+
content_keys = @redis.keys(content_pattern)
|
87
|
+
@redis.del(*content_keys) if content_keys.any?
|
88
|
+
|
89
|
+
true
|
90
|
+
end
|
91
|
+
|
92
|
+
def list_contexts(limit: 100, offset: 0)
|
93
|
+
# Get context IDs from the index, sorted by score (timestamp) descending
|
94
|
+
context_ids = @redis.zrevrange(contexts_index_key, offset, offset + limit - 1)
|
95
|
+
|
96
|
+
# Return early if no contexts
|
97
|
+
return [] if context_ids.empty?
|
98
|
+
|
99
|
+
# Get each context
|
100
|
+
context_ids.map { |id| get_context(id) }.compact
|
101
|
+
end
|
102
|
+
|
103
|
+
# Message handling
|
104
|
+
def add_message(context_id, message)
|
105
|
+
# Ensure context exists
|
106
|
+
raise Error, "Context with ID '#{context_id}' does not exist" unless get_context(context_id)
|
107
|
+
|
108
|
+
# Add message to the messages list
|
109
|
+
message_json = JSON.generate(message)
|
110
|
+
@redis.rpush(messages_key(context_id), message_json)
|
111
|
+
|
112
|
+
# Set TTL on messages key
|
113
|
+
@redis.expire(messages_key(context_id), @ttl)
|
114
|
+
|
115
|
+
message
|
116
|
+
end
|
117
|
+
|
118
|
+
# Content handling
|
119
|
+
def add_content(context_id, content_id, content_data)
|
120
|
+
# Ensure context exists
|
121
|
+
raise Error, "Context with ID '#{context_id}' does not exist" unless get_context(context_id)
|
122
|
+
|
123
|
+
# Store content
|
124
|
+
key = content_key(context_id, content_id)
|
125
|
+
|
126
|
+
# If content is binary or complex, use Base64 encoding
|
127
|
+
if content_data.is_a?(String) &&
|
128
|
+
(content_data.encoding == Encoding::BINARY || content_data.include?("\0"))
|
129
|
+
@redis.set(key, [content_data].pack('m0'))
|
130
|
+
@redis.set("#{key}:encoding", 'base64')
|
131
|
+
else
|
132
|
+
@redis.set(key, content_data)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Set TTL
|
136
|
+
@redis.expire(key, @ttl)
|
137
|
+
@redis.expire("#{key}:encoding", @ttl) if @redis.exists?("#{key}:encoding")
|
138
|
+
|
139
|
+
content_data
|
140
|
+
end
|
141
|
+
|
142
|
+
def get_content(context_id, content_id)
|
143
|
+
key = content_key(context_id, content_id)
|
144
|
+
content = @redis.get(key)
|
145
|
+
return nil unless content
|
146
|
+
|
147
|
+
# Check if we need to decode from Base64
|
148
|
+
encoding = @redis.get("#{key}:encoding")
|
149
|
+
content = content.unpack1('m0') if encoding == 'base64'
|
150
|
+
|
151
|
+
content
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def store_context(context)
|
157
|
+
context_id = context['id']
|
158
|
+
|
159
|
+
# Store the context
|
160
|
+
context_json = JSON.generate(context)
|
161
|
+
@redis.set(context_key(context_id), context_json)
|
162
|
+
|
163
|
+
# Set TTL
|
164
|
+
@redis.expire(context_key(context_id), @ttl)
|
165
|
+
end
|
166
|
+
|
167
|
+
def get_messages(context_id)
|
168
|
+
# Get all messages from the list
|
169
|
+
message_jsons = @redis.lrange(messages_key(context_id), 0, -1)
|
170
|
+
|
171
|
+
# Parse each message
|
172
|
+
message_jsons.map { |json| JSON.parse(json) }
|
173
|
+
end
|
174
|
+
|
175
|
+
# Helper methods for key generation
|
176
|
+
def key(parts)
|
177
|
+
[@namespace, *parts].join(':')
|
178
|
+
end
|
179
|
+
|
180
|
+
def context_key(context_id)
|
181
|
+
key(['context', context_id])
|
182
|
+
end
|
183
|
+
|
184
|
+
def messages_key(context_id)
|
185
|
+
key(['context', context_id, 'messages'])
|
186
|
+
end
|
187
|
+
|
188
|
+
def content_key(context_id, content_id)
|
189
|
+
key(['context', context_id, 'content', content_id])
|
190
|
+
end
|
191
|
+
|
192
|
+
def contexts_index_key
|
193
|
+
key(%w[contexts index])
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# lib/ruby_mcp/storage_factory.rb
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyMCP
|
5
|
+
class StorageFactory
|
6
|
+
def self.create(config)
|
7
|
+
# Support both old and new configuration interfaces
|
8
|
+
storage_config = if config.respond_to?(:storage_config)
|
9
|
+
config.storage_config
|
10
|
+
else
|
11
|
+
config.storage
|
12
|
+
end
|
13
|
+
|
14
|
+
case storage_config[:type]
|
15
|
+
when :memory, nil
|
16
|
+
Storage::Memory.new(storage_config)
|
17
|
+
when :redis
|
18
|
+
# Load Redis dependencies
|
19
|
+
begin
|
20
|
+
require 'redis'
|
21
|
+
require_relative 'storage/redis'
|
22
|
+
rescue LoadError => e
|
23
|
+
raise LoadError, "Redis storage requires the redis gem. Add it to your Gemfile: #{e.message}"
|
24
|
+
end
|
25
|
+
|
26
|
+
Storage::Redis.new(storage_config)
|
27
|
+
else
|
28
|
+
raise ArgumentError, "Unknown storage type: #{storage_config[:type]}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/ruby_mcp/version.rb
CHANGED
data/lib/ruby_mcp.rb
CHANGED
@@ -33,11 +33,20 @@ require_relative 'ruby_mcp/providers/anthropic'
|
|
33
33
|
require_relative 'ruby_mcp/schemas'
|
34
34
|
require_relative 'ruby_mcp/validator'
|
35
35
|
|
36
|
+
require_relative 'ruby_mcp/client'
|
37
|
+
|
36
38
|
module RubyMCP
|
37
39
|
class << self
|
38
40
|
attr_accessor :configuration
|
39
41
|
attr_writer :logger
|
40
42
|
|
43
|
+
def client
|
44
|
+
@client ||= begin
|
45
|
+
initialize_components unless @storage
|
46
|
+
Client.new(@storage)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
41
50
|
def configure
|
42
51
|
self.configuration ||= Configuration.new
|
43
52
|
yield(configuration) if block_given?
|
@@ -48,5 +57,12 @@ module RubyMCP
|
|
48
57
|
log.progname = name
|
49
58
|
end
|
50
59
|
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def initialize_components
|
64
|
+
require_relative 'ruby_mcp/storage_factory'
|
65
|
+
@storage = StorageFactory.create(configuration)
|
66
|
+
end
|
51
67
|
end
|
52
68
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mcp_on_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nagendra Dhanakeerthi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-04-
|
11
|
+
date: 2025-04-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '1.1'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: redis
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '5.0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '5.0'
|
125
139
|
- !ruby/object:Gem::Dependency
|
126
140
|
name: vcr
|
127
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -221,6 +235,7 @@ files:
|
|
221
235
|
- LICENSE.txt
|
222
236
|
- README.md
|
223
237
|
- lib/ruby_mcp.rb
|
238
|
+
- lib/ruby_mcp/client.rb
|
224
239
|
- lib/ruby_mcp/configuration.rb
|
225
240
|
- lib/ruby_mcp/errors.rb
|
226
241
|
- lib/ruby_mcp/models/context.rb
|
@@ -240,7 +255,10 @@ files:
|
|
240
255
|
- lib/ruby_mcp/server/messages_controller.rb
|
241
256
|
- lib/ruby_mcp/server/router.rb
|
242
257
|
- lib/ruby_mcp/storage/base.rb
|
258
|
+
- lib/ruby_mcp/storage/error.rb
|
243
259
|
- lib/ruby_mcp/storage/memory.rb
|
260
|
+
- lib/ruby_mcp/storage/redis.rb
|
261
|
+
- lib/ruby_mcp/storage_factory.rb
|
244
262
|
- lib/ruby_mcp/validator.rb
|
245
263
|
- lib/ruby_mcp/version.rb
|
246
264
|
homepage: https://github.com/nagstler/mcp_on_ruby
|
@@ -267,7 +285,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
267
285
|
- !ruby/object:Gem::Version
|
268
286
|
version: '0'
|
269
287
|
requirements: []
|
270
|
-
rubygems_version: 3.
|
288
|
+
rubygems_version: 3.5.2
|
271
289
|
signing_key:
|
272
290
|
specification_version: 4
|
273
291
|
summary: Ruby implementation of the Model Context Protocol (MCP)
|