fast_mcp_pubsub 1.1.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 +7 -0
- data/.mcp.json +30 -0
- data/.rubocop.yml +40 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +40 -0
- data/CLAUDE.md +108 -0
- data/LICENSE.txt +21 -0
- data/README.md +168 -0
- data/Rakefile +16 -0
- data/lib/.DS_Store +0 -0
- data/lib/fast_mcp_pubsub/configuration.rb +15 -0
- data/lib/fast_mcp_pubsub/rack_transport_patch.rb +76 -0
- data/lib/fast_mcp_pubsub/railtie.rb +53 -0
- data/lib/fast_mcp_pubsub/service.rb +146 -0
- data/lib/fast_mcp_pubsub/version.rb +5 -0
- data/lib/fast_mcp_pubsub.rb +34 -0
- data/sig/fast_mcp_pubsub.rbs +4 -0
- metadata +160 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: db696202b8d3b7dae7c2231fcb132d6dba086b20e578eac993aac24bb92d820b
|
|
4
|
+
data.tar.gz: 67f0d7bf55c87fcc35a1f9a9cc2686fef93329ff958c4377e823027670724aea
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 50b8171fab26238c6177e7d450eef9218adc02b76bb1e6434e65a80ef4c0ff998e43f2c3e538fccadde7097ac8928ff0b20c129aba73d36ec8d2a128a213ab2d
|
|
7
|
+
data.tar.gz: d386411918a12d71f91cb2b9a40a2b3aaa6bf11d527bb637c231a3bc5c9dc76e53814d91a222d418c8dea02ced23ad41aff72bb5db94d48f1982083e65000fca
|
data/.mcp.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"workvector-production": {
|
|
4
|
+
"type": "sse",
|
|
5
|
+
"name": "WorkVector Production",
|
|
6
|
+
"url": "https://workvector.com/mcp/sse",
|
|
7
|
+
"headers": {
|
|
8
|
+
"Authorization": "Bearer ${WORKVECTOR_TOKEN}"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"filesystem-project": {
|
|
12
|
+
"type": "stdio",
|
|
13
|
+
"name": "Filesystem",
|
|
14
|
+
"command": "npx",
|
|
15
|
+
"args": [
|
|
16
|
+
"-y",
|
|
17
|
+
"@modelcontextprotocol/server-filesystem",
|
|
18
|
+
"${PWD}"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"llmmn-production": {
|
|
22
|
+
"type": "sse",
|
|
23
|
+
"name": "LLM Memory Notes Production",
|
|
24
|
+
"url": "https://llm-memory.com/mcp/sse",
|
|
25
|
+
"headers": {
|
|
26
|
+
"Authorization": "Bearer ${LLMMN_TOKEN}"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.1
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
|
|
6
|
+
plugins:
|
|
7
|
+
- rubocop-minitest
|
|
8
|
+
|
|
9
|
+
Layout/LineLength:
|
|
10
|
+
Max: 200
|
|
11
|
+
|
|
12
|
+
Style/StringLiterals:
|
|
13
|
+
EnforcedStyle: double_quotes
|
|
14
|
+
|
|
15
|
+
Style/StringLiteralsInInterpolation:
|
|
16
|
+
EnforcedStyle: double_quotes
|
|
17
|
+
|
|
18
|
+
# Relax some metrics for reasonable code
|
|
19
|
+
Metrics/ClassLength:
|
|
20
|
+
Max: 150
|
|
21
|
+
|
|
22
|
+
Metrics/MethodLength:
|
|
23
|
+
Max: 30
|
|
24
|
+
|
|
25
|
+
Metrics/AbcSize:
|
|
26
|
+
Max: 35
|
|
27
|
+
|
|
28
|
+
# Allow development dependencies in gemspec for gems
|
|
29
|
+
Gemspec/DevelopmentDependencies:
|
|
30
|
+
Enabled: false
|
|
31
|
+
|
|
32
|
+
Style/Documentation:
|
|
33
|
+
Enabled: false
|
|
34
|
+
|
|
35
|
+
# Allow longer blocks for configuration and setup
|
|
36
|
+
Metrics/BlockLength:
|
|
37
|
+
Exclude:
|
|
38
|
+
- "test/**/*"
|
|
39
|
+
- "*.gemspec"
|
|
40
|
+
- "lib/fast_mcp_pubsub/railtie.rb" # Debug logging temporarily increases block length
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruby-3.4.2
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2025-08-19
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial implementation of PostgreSQL NOTIFY/LISTEN clustering for FastMcp RackTransport
|
|
12
|
+
- FastMcpPubsub::Service for message broadcasting and listening
|
|
13
|
+
- FastMcpPubsub::Configuration for configurable settings (enabled, channel_name, auto_start, connection_pool_size)
|
|
14
|
+
- RackTransport monkey patch for cluster mode message distribution
|
|
15
|
+
- Rails Railtie for automatic initialization and Rails integration
|
|
16
|
+
- Puma cluster mode integration with automatic worker hooks
|
|
17
|
+
- Payload size validation (7800 bytes limit) with fallback error responses
|
|
18
|
+
- Thread-safe listener management with automatic restart on errors
|
|
19
|
+
- Comprehensive logging via FastMcpPubsub.logger (Rails.logger)
|
|
20
|
+
- Connection pooling for database operations
|
|
21
|
+
- Automatic patch application during Rails initialization
|
|
22
|
+
- Method redefinition protection to avoid warnings
|
|
23
|
+
- Full test coverage (18 tests, 33 assertions)
|
|
24
|
+
- RuboCop compliance (0 offenses)
|
|
25
|
+
|
|
26
|
+
### Implementation Details
|
|
27
|
+
- **Automatic Integration**: No manual configuration required - just add to Gemfile
|
|
28
|
+
- **Smart Timing**: Patch applied after Rails initializers load via `after: :load_config_initializers`
|
|
29
|
+
- **Dual Mode Support**: Works in both single-worker and cluster mode
|
|
30
|
+
- **Clean Logging**: Simplified logging without complex conditional checks
|
|
31
|
+
- **Warning-Free**: Eliminated method redefinition warnings using proper mocking patterns
|
|
32
|
+
- **Robust Error Handling**: Fallback to local delivery if PostgreSQL NOTIFY fails
|
|
33
|
+
- **Rails-Specific**: Designed for Rails applications with ActiveRecord and PostgreSQL
|
|
34
|
+
|
|
35
|
+
### Technical Architecture
|
|
36
|
+
- **Patch Strategy**: Monkey patches `FastMcp::Transports::RackTransport#send_message`
|
|
37
|
+
- **Broadcasting**: Uses PostgreSQL NOTIFY/LISTEN for inter-worker communication
|
|
38
|
+
- **Listener Management**: Dedicated thread per worker with automatic lifecycle management
|
|
39
|
+
- **Configuration**: Simple configuration object with sensible defaults
|
|
40
|
+
- **Integration**: Rails Railtie for seamless Rails integration
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code when working with the `fast_mcp_pubsub` gem.
|
|
4
|
+
|
|
5
|
+
## Gem Overview
|
|
6
|
+
|
|
7
|
+
FastMcp PubSub provides PostgreSQL NOTIFY/LISTEN clustering support for FastMcp RackTransport, enabling message broadcasting across multiple Puma workers in cluster mode.
|
|
8
|
+
|
|
9
|
+
## Code Conventions
|
|
10
|
+
|
|
11
|
+
### Code Quality
|
|
12
|
+
- Max 200 chars/line (soft limit - prefer readability over strict compliance)
|
|
13
|
+
- breaking Ruby chain calls destroys the natural sentence flow and readability
|
|
14
|
+
- 14 lines/method, 110 lines/class
|
|
15
|
+
- Comments and tests in English
|
|
16
|
+
- KEEP CODE DRY (Don't Repeat Yourself)
|
|
17
|
+
|
|
18
|
+
### Error Handling
|
|
19
|
+
- Use meaningful exception classes (not generic StandardError)
|
|
20
|
+
- Log errors with context using the configured logger
|
|
21
|
+
- Proper error propagation with fallback mechanisms
|
|
22
|
+
- Use `rescue_from` for common exceptions in Rails integration
|
|
23
|
+
|
|
24
|
+
### Performance Considerations
|
|
25
|
+
- Use database connection pooling efficiently
|
|
26
|
+
- Avoid blocking operations in main threads
|
|
27
|
+
- Cache expensive operations
|
|
28
|
+
- Monitor thread lifecycle and cleanup
|
|
29
|
+
|
|
30
|
+
### Thread Safety
|
|
31
|
+
- All operations must be thread-safe for cluster mode
|
|
32
|
+
- Use proper synchronization when accessing shared resources
|
|
33
|
+
- Handle thread lifecycle correctly (creation, monitoring, cleanup)
|
|
34
|
+
- Use connection checkout/checkin pattern for database operations
|
|
35
|
+
|
|
36
|
+
### Gem Specific Guidelines
|
|
37
|
+
|
|
38
|
+
#### Configuration
|
|
39
|
+
- Use configuration object pattern for all settings
|
|
40
|
+
- Provide sensible defaults that work out of the box
|
|
41
|
+
- Make all components configurable but not required
|
|
42
|
+
- Support both programmatic and initializer-based configuration
|
|
43
|
+
|
|
44
|
+
#### Rails Integration
|
|
45
|
+
- Use Railtie for automatic Rails integration
|
|
46
|
+
- Hook into appropriate Rails lifecycle events
|
|
47
|
+
- Respect Rails conventions for logging and error handling
|
|
48
|
+
- Provide manual configuration options for non-Rails usage
|
|
49
|
+
|
|
50
|
+
#### Error Recovery
|
|
51
|
+
- Implement automatic retry with backoff for transient errors
|
|
52
|
+
- Provide fallback mechanisms when PubSub fails
|
|
53
|
+
- Log errors appropriately without flooding logs
|
|
54
|
+
- Handle connection failures gracefully
|
|
55
|
+
|
|
56
|
+
#### Testing
|
|
57
|
+
- Test all public interfaces
|
|
58
|
+
- Mock external dependencies (PostgreSQL, FastMcp)
|
|
59
|
+
- Test error conditions and edge cases
|
|
60
|
+
- Provide test helpers for gem users
|
|
61
|
+
- Test both Rails and non-Rails usage
|
|
62
|
+
|
|
63
|
+
## Architecture
|
|
64
|
+
|
|
65
|
+
### Components
|
|
66
|
+
|
|
67
|
+
1. **FastMcpPubsub::Service** - Core PostgreSQL NOTIFY/LISTEN service
|
|
68
|
+
2. **FastMcpPubsub::Configuration** - Configuration management
|
|
69
|
+
3. **FastMcpPubsub::RackTransportPatch** - Monkey patch for FastMcp transport
|
|
70
|
+
4. **FastMcpPubsub::Railtie** - Rails integration and lifecycle management
|
|
71
|
+
|
|
72
|
+
### Message Flow
|
|
73
|
+
|
|
74
|
+
1. `RackTransport#send_message` → `FastMcpPubsub::Service.broadcast`
|
|
75
|
+
2. `Service.broadcast` → PostgreSQL NOTIFY
|
|
76
|
+
3. Each worker's listener thread receives NOTIFY
|
|
77
|
+
4. Listener calls `RackTransport#send_local_message` for local clients
|
|
78
|
+
|
|
79
|
+
### Thread Management
|
|
80
|
+
|
|
81
|
+
- One listener thread per worker process
|
|
82
|
+
- Thread cleanup on process exit
|
|
83
|
+
- Automatic restart on listener errors
|
|
84
|
+
- Connection pooling for database operations
|
|
85
|
+
|
|
86
|
+
## Dependencies
|
|
87
|
+
|
|
88
|
+
- **Rails** (>= 7.0) - Core framework integration
|
|
89
|
+
- **PostgreSQL** (via pg gem >= 1.0) - Database NOTIFY/LISTEN
|
|
90
|
+
- **ActiveRecord** - Connection pooling and database access
|
|
91
|
+
- **FastMcp** - The transport being patched (development/test dependency)
|
|
92
|
+
|
|
93
|
+
## Development
|
|
94
|
+
|
|
95
|
+
### Running Tests
|
|
96
|
+
```bash
|
|
97
|
+
bundle exec rake test
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Linting
|
|
101
|
+
```bash
|
|
102
|
+
bundle exec rubocop
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Console
|
|
106
|
+
```bash
|
|
107
|
+
bundle exec rake console
|
|
108
|
+
```
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 josefchmel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# FastMcp PubSub
|
|
2
|
+
|
|
3
|
+
**Multi-worker cluster support extension for [fast-mcp](https://github.com/yjacquin/fast-mcp) gem.**
|
|
4
|
+
|
|
5
|
+
This gem extends the [FastMcp](https://github.com/yjacquin/fast-mcp) gem to work with multiple Puma workers by adding PostgreSQL NOTIFY/LISTEN clustering support for `FastMcp::Transports::RackTransport` in Rails applications.
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
FastMcp::Transports::RackTransport stores SSE clients in an in-memory hash `@sse_clients`. In cluster mode (multiple Puma workers), messages don't reach between workers because each has its own memory space.
|
|
10
|
+
|
|
11
|
+
## Solution
|
|
12
|
+
|
|
13
|
+
This gem provides PostgreSQL NOTIFY/LISTEN system for broadcasting messages between workers:
|
|
14
|
+
|
|
15
|
+
1. `send_message` → PostgreSQL NOTIFY
|
|
16
|
+
2. Listener thread in each worker → PostgreSQL LISTEN
|
|
17
|
+
3. On notification → `send_local_message` to local clients
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
**Prerequisites**: This gem requires the [fast-mcp](https://github.com/yjacquin/fast-mcp) gem to be installed first.
|
|
22
|
+
|
|
23
|
+
Add both gems to your application's Gemfile:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
gem 'fast-mcp', '~> 1.5.0' # Required base gem
|
|
27
|
+
gem 'fast_mcp_pubsub' # This extension
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
And then execute:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bundle install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Note**: The `fast-mcp` gem provides the core MCP (Model Context Protocol) server functionality, while this gem extends it with multi-worker support.
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
### Automatic Integration
|
|
41
|
+
|
|
42
|
+
**No configuration needed!** Just add the gem to your Gemfile and it works automatically.
|
|
43
|
+
|
|
44
|
+
The gem will:
|
|
45
|
+
- ✅ **Automatically patch** FastMcp::Transports::RackTransport during Rails initialization
|
|
46
|
+
- ✅ **Start listener** automatically when Rails server starts
|
|
47
|
+
- ✅ **Use Rails.logger** for logging (no configuration required)
|
|
48
|
+
- ✅ **Work in both** single-worker and cluster mode
|
|
49
|
+
|
|
50
|
+
### Optional Configuration
|
|
51
|
+
|
|
52
|
+
If you need custom settings:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
# config/initializers/fast_mcp_pubsub.rb (optional)
|
|
56
|
+
FastMcpPubsub.configure do |config|
|
|
57
|
+
config.enabled = Rails.env.production? # Enable only in production
|
|
58
|
+
config.channel_name = 'my_custom_channel' # Custom PostgreSQL NOTIFY channel
|
|
59
|
+
config.auto_start = true # Start listener automatically (default: true)
|
|
60
|
+
config.connection_pool_size = 10 # Database connection pool size
|
|
61
|
+
end
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Puma Cluster Mode
|
|
65
|
+
|
|
66
|
+
For cluster mode (multiple workers), you need to manually start the listener in each worker process:
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
# config/puma/production.rb
|
|
70
|
+
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
|
|
71
|
+
preload_app!
|
|
72
|
+
|
|
73
|
+
on_worker_boot do
|
|
74
|
+
Rails.logger.info "MCP Transport: Starting PubSub listener for cluster mode worker #{Process.pid}"
|
|
75
|
+
|
|
76
|
+
# Start FastMcpPubsub listener in each worker
|
|
77
|
+
FastMcpPubsub::Service.start_listener
|
|
78
|
+
|
|
79
|
+
# Your other worker boot code...
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Why manual setup is required:**
|
|
84
|
+
- 🔧 **Master process** automatically detects cluster mode and skips listener startup
|
|
85
|
+
- 👷 **Worker processes** need explicit listener startup in `on_worker_boot` hook
|
|
86
|
+
- 📡 **Each worker** gets its own listener thread for receiving broadcasts
|
|
87
|
+
- 🔄 **Automatic cleanup** happens on worker shutdown
|
|
88
|
+
|
|
89
|
+
### Manual Control
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
# Manually start/stop listener
|
|
93
|
+
FastMcpPubsub::Service.start_listener
|
|
94
|
+
FastMcpPubsub::Service.stop_listener
|
|
95
|
+
|
|
96
|
+
# Check listener status
|
|
97
|
+
FastMcpPubsub::Service.listener_thread&.alive?
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## How It Works
|
|
101
|
+
|
|
102
|
+
1. **Patches FastMcp::Transports::RackTransport**: Overrides `send_message` method
|
|
103
|
+
2. **Broadcasts via PostgreSQL**: Uses `NOTIFY channel, payload`
|
|
104
|
+
3. **Listener threads**: Each worker has a dedicated listener thread
|
|
105
|
+
4. **Local delivery**: Messages are delivered to local SSE clients in each worker
|
|
106
|
+
|
|
107
|
+
## Configuration Options
|
|
108
|
+
|
|
109
|
+
| Option | Default | Description |
|
|
110
|
+
|--------|---------|-------------|
|
|
111
|
+
| `enabled` | `true` | Enable/disable PubSub functionality |
|
|
112
|
+
| `channel_name` | `'mcp_broadcast'` | PostgreSQL NOTIFY channel name |
|
|
113
|
+
| `auto_start` | `true` | Start listener automatically |
|
|
114
|
+
| `connection_pool_size` | `5` | Database connection pool size |
|
|
115
|
+
|
|
116
|
+
**Note**: Logging is handled automatically via `FastMcpPubsub.logger` which returns `Rails.logger`.
|
|
117
|
+
|
|
118
|
+
## Error Handling
|
|
119
|
+
|
|
120
|
+
- **Payload size limit**: 7800 bytes (PostgreSQL NOTIFY limit)
|
|
121
|
+
- **Fallback mechanism**: Falls back to local delivery if PubSub fails
|
|
122
|
+
- **Automatic restart**: Listener restarts on connection errors
|
|
123
|
+
- **Graceful shutdown**: Proper cleanup on process exit
|
|
124
|
+
|
|
125
|
+
## Requirements
|
|
126
|
+
|
|
127
|
+
**This gem is an extension for Rails applications using FastMcp and requires:**
|
|
128
|
+
|
|
129
|
+
- **[FastMcp gem](https://github.com/yjacquin/fast-mcp)** ~> 1.5.0 (the base MCP server this gem extends)
|
|
130
|
+
- **Rails 7.0+** (required for Railtie integration)
|
|
131
|
+
- **PostgreSQL database** (for NOTIFY/LISTEN functionality)
|
|
132
|
+
- **Puma web server** in cluster mode (multi-worker setup)
|
|
133
|
+
|
|
134
|
+
**Important Notes**:
|
|
135
|
+
- This gem will not work in standalone Ruby applications or non-Rails frameworks, as it relies heavily on Rails infrastructure (ActiveRecord, Railtie, Rails.logger, etc.)
|
|
136
|
+
- **PostgreSQL is mandatory** - this gem will NOT work with MySQL, SQLite, or other databases as it requires PostgreSQL's NOTIFY/LISTEN functionality. Support for other databases would require significant additional development.
|
|
137
|
+
|
|
138
|
+
## Thread Safety
|
|
139
|
+
|
|
140
|
+
All operations are thread-safe and designed for multi-worker environments:
|
|
141
|
+
|
|
142
|
+
- Connection pooling for database operations
|
|
143
|
+
- Proper thread lifecycle management
|
|
144
|
+
- Automatic cleanup on process termination
|
|
145
|
+
|
|
146
|
+
## Development
|
|
147
|
+
|
|
148
|
+
After checking out the repo, run:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
bin/setup # Install dependencies
|
|
152
|
+
rake test # Run tests
|
|
153
|
+
bin/console # Interactive prompt
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
To install locally:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
bundle exec rake install
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Contributing
|
|
163
|
+
|
|
164
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/jchsoft/fast_mcp_pubsub.
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
|
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
|
7
|
+
t.libs << "test"
|
|
8
|
+
t.libs << "lib"
|
|
9
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require "rubocop/rake_task"
|
|
13
|
+
|
|
14
|
+
RuboCop::RakeTask.new
|
|
15
|
+
|
|
16
|
+
task default: %i[test rubocop]
|
data/lib/.DS_Store
ADDED
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FastMcpPubsub
|
|
4
|
+
# Configuration class for FastMcpPubsub gem settings
|
|
5
|
+
class Configuration
|
|
6
|
+
attr_accessor :enabled, :channel_name, :auto_start, :connection_pool_size
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@enabled = true
|
|
10
|
+
@channel_name = "mcp_broadcast"
|
|
11
|
+
@auto_start = true
|
|
12
|
+
@connection_pool_size = 5
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Monkey patch for FastMcp::Transports::RackTransport
|
|
4
|
+
# Adds PostgreSQL PubSub support for cluster mode
|
|
5
|
+
|
|
6
|
+
module FastMcpPubsub
|
|
7
|
+
# Lazy patch application - applies patch when FastMcp transport is first accessed
|
|
8
|
+
module RackTransportPatch
|
|
9
|
+
@patch_applied = false
|
|
10
|
+
|
|
11
|
+
def self.apply_patch!
|
|
12
|
+
if @patch_applied
|
|
13
|
+
FastMcpPubsub.logger.debug "FastMcpPubsub: RackTransport patch already applied, skipping"
|
|
14
|
+
return
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
unless defined?(FastMcp::Transports::RackTransport)
|
|
18
|
+
FastMcpPubsub.logger.debug "FastMcpPubsub: FastMcp::Transports::RackTransport not defined yet, skipping patch"
|
|
19
|
+
return
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
FastMcpPubsub.logger.info "FastMcpPubsub: Patching FastMcp::Transports::RackTransport for PostgreSQL PubSub support"
|
|
23
|
+
|
|
24
|
+
patch_transport_class
|
|
25
|
+
@patch_applied = true
|
|
26
|
+
FastMcpPubsub.logger.info "FastMcpPubsub: RackTransport patch applied successfully"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.patch_transport_class
|
|
30
|
+
add_basic_methods
|
|
31
|
+
add_send_message_override
|
|
32
|
+
add_fallback_method
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.add_basic_methods
|
|
36
|
+
FastMcp::Transports::RackTransport.class_eval do
|
|
37
|
+
alias_method :send_local_message, :send_message unless method_defined?(:send_local_message)
|
|
38
|
+
define_method(:running?) { @running } unless method_defined?(:running?)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.add_send_message_override
|
|
43
|
+
FastMcp::Transports::RackTransport.class_eval do
|
|
44
|
+
return if method_defined?(:send_message_with_pubsub)
|
|
45
|
+
|
|
46
|
+
alias_method :send_message_original, :send_message if method_defined?(:send_message)
|
|
47
|
+
|
|
48
|
+
define_method(:send_message) do |message|
|
|
49
|
+
FastMcpPubsub.config.enabled ? broadcast_with_fallback(message) : send_local_message(message)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
alias_method :send_message_with_pubsub, :send_message
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.add_fallback_method
|
|
57
|
+
FastMcp::Transports::RackTransport.class_eval do
|
|
58
|
+
return if method_defined?(:broadcast_with_fallback)
|
|
59
|
+
|
|
60
|
+
define_method(:broadcast_with_fallback) do |message|
|
|
61
|
+
FastMcpPubsub.logger.debug "RackTransport: Broadcasting message via PostgreSQL PubSub"
|
|
62
|
+
FastMcpPubsub::Service.broadcast(message)
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
FastMcpPubsub.logger.error "RackTransport: Error broadcasting message: #{e.message}"
|
|
65
|
+
send_local_message(message)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.patch_applied?
|
|
71
|
+
@patch_applied
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# NOTE: Patch is automatically applied by Railtie initializer when Rails loads
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FastMcpPubsub
|
|
4
|
+
# Rails integration for automatic FastMcpPubsub setup and Puma cluster mode hooks
|
|
5
|
+
class Railtie < Rails::Railtie
|
|
6
|
+
# Start listener when Rails is ready
|
|
7
|
+
initializer "fast_mcp_pubsub.start_listener" do |app|
|
|
8
|
+
railtie = self
|
|
9
|
+
app.config.to_prepare do
|
|
10
|
+
# Non-cluster mode initialization (rails server)
|
|
11
|
+
# Only start if we're in a web server environment
|
|
12
|
+
Rails.logger.info "FastMcpPubsub: Checking listener startup conditions - cluster_mode: #{railtie.send(:cluster_mode?)}, should_start: #{railtie.send(:should_start_listener?)}"
|
|
13
|
+
|
|
14
|
+
if railtie.send(:should_start_listener?)
|
|
15
|
+
Rails.logger.info "FastMcpPubsub: Starting listener for non-cluster mode"
|
|
16
|
+
FastMcpPubsub::Service.start_listener
|
|
17
|
+
railtie.instance_variable_set(:@listener_started, true)
|
|
18
|
+
else
|
|
19
|
+
Rails.logger.info "FastMcpPubsub: Not starting listener in master process (cluster mode detected or conditions not met)"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Apply patch to FastMcp::Transports::RackTransport after all initializers are loaded
|
|
25
|
+
initializer "fast_mcp_pubsub.apply_patch", after: :load_config_initializers do
|
|
26
|
+
FastMcpPubsub.logger.debug "FastMcpPubsub: Attempting to apply RackTransport patch"
|
|
27
|
+
FastMcpPubsub::RackTransportPatch.apply_patch!
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# NOTE: For cluster mode, add FastMcpPubsub::Service.start_listener to your
|
|
31
|
+
# on_worker_boot hook in config/puma/production.rb
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def should_start_listener?
|
|
36
|
+
web_server_environment? &&
|
|
37
|
+
!cluster_mode? &&
|
|
38
|
+
FastMcpPubsub.config.enabled &&
|
|
39
|
+
FastMcpPubsub.config.auto_start &&
|
|
40
|
+
!instance_variable_get(:@listener_started)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def web_server_environment?
|
|
44
|
+
defined?(Rails::Server) || defined?(Puma) || ENV["MCP_SERVER_AUTO_START"] == "true"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def cluster_mode?
|
|
48
|
+
# Check if Puma is running in cluster mode (multiple workers)
|
|
49
|
+
defined?(Puma.cli_config) &&
|
|
50
|
+
Puma.cli_config&.options&.[](:workers).to_i > 1
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FastMcpPubsub
|
|
4
|
+
# Core PostgreSQL NOTIFY/LISTEN service for broadcasting MCP messages across Puma workers
|
|
5
|
+
class Service
|
|
6
|
+
MAX_PAYLOAD_SIZE = 7800 # PostgreSQL NOTIFY limit is 8000 bytes, leave some margin
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
attr_reader :listener_thread
|
|
10
|
+
|
|
11
|
+
def broadcast(message)
|
|
12
|
+
payload = message.to_json
|
|
13
|
+
|
|
14
|
+
payload_too_large?(payload) ? send_error_response(message, payload) : send_payload(payload)
|
|
15
|
+
rescue StandardError => e
|
|
16
|
+
FastMcpPubsub.logger.error "FastMcpPubsub: Error broadcasting message: #{e.message}"
|
|
17
|
+
raise
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def start_listener
|
|
21
|
+
unless FastMcpPubsub.config.enabled
|
|
22
|
+
FastMcpPubsub.logger.info "FastMcpPubsub: Not starting listener - disabled in config for PID #{Process.pid}"
|
|
23
|
+
return
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if @listener_thread&.alive?
|
|
27
|
+
FastMcpPubsub.logger.info "FastMcpPubsub: Listener already running for PID #{Process.pid}"
|
|
28
|
+
return
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
FastMcpPubsub.logger.info "FastMcpPubsub: Starting listener thread for PID #{Process.pid}"
|
|
32
|
+
|
|
33
|
+
@listener_thread = Thread.new do
|
|
34
|
+
Thread.current.name = "fast-mcp-pubsub-listener"
|
|
35
|
+
listen_loop
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Register shutdown hook
|
|
39
|
+
at_exit { stop_listener }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def stop_listener
|
|
43
|
+
return unless @listener_thread&.alive?
|
|
44
|
+
|
|
45
|
+
FastMcpPubsub.logger.info "FastMcpPubsub: Stopping listener thread for PID #{Process.pid}"
|
|
46
|
+
@listener_thread.kill
|
|
47
|
+
@listener_thread.join(5) # Wait max 5 seconds
|
|
48
|
+
@listener_thread = nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def send_payload(payload)
|
|
54
|
+
channel = FastMcpPubsub.config.channel_name
|
|
55
|
+
FastMcpPubsub.logger.debug "FastMcpPubsub: Broadcasting message to #{channel}: #{payload.bytesize} bytes"
|
|
56
|
+
|
|
57
|
+
ActiveRecord::Base.connection.execute(
|
|
58
|
+
"NOTIFY #{channel}, #{ActiveRecord::Base.connection.quote(payload)}"
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def payload_too_large?(payload)
|
|
63
|
+
payload.bytesize > MAX_PAYLOAD_SIZE
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def send_error_response(message, payload)
|
|
67
|
+
FastMcpPubsub.logger.error "FastMcpPubsub: Payload too large (#{payload.bytesize} bytes > #{MAX_PAYLOAD_SIZE} bytes)"
|
|
68
|
+
|
|
69
|
+
error_message = {
|
|
70
|
+
jsonrpc: "2.0",
|
|
71
|
+
id: message[:id],
|
|
72
|
+
error: {
|
|
73
|
+
code: -32_001,
|
|
74
|
+
message: "Response too large for PostgreSQL NOTIFY. Try requesting smaller page size."
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
send_payload(error_message.to_json)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def listen_loop
|
|
82
|
+
channel = FastMcpPubsub.config.channel_name
|
|
83
|
+
|
|
84
|
+
begin
|
|
85
|
+
ActiveRecord::Base.connection_pool.with_connection do |conn|
|
|
86
|
+
raw_conn = conn.raw_connection
|
|
87
|
+
|
|
88
|
+
FastMcpPubsub.logger.info "FastMcpPubsub: Listening on #{channel} for PID #{Process.pid}"
|
|
89
|
+
raw_conn.async_exec("LISTEN #{channel}")
|
|
90
|
+
|
|
91
|
+
begin
|
|
92
|
+
loop do
|
|
93
|
+
raw_conn.wait_for_notify do |channel, pid, payload|
|
|
94
|
+
handle_notification(channel, pid, payload)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
ensure
|
|
98
|
+
begin
|
|
99
|
+
raw_conn.async_exec("UNLISTEN #{channel}")
|
|
100
|
+
rescue StandardError => e
|
|
101
|
+
FastMcpPubsub.logger.error "FastMcpPubsub: Error during UNLISTEN: #{e.message}"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
rescue StandardError => e
|
|
106
|
+
FastMcpPubsub.logger.error "FastMcpPubsub: Listener error: #{e.message}"
|
|
107
|
+
FastMcpPubsub.logger.error e.backtrace.join("\n")
|
|
108
|
+
|
|
109
|
+
# Restart after error
|
|
110
|
+
sleep 1
|
|
111
|
+
retry
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def handle_notification(_channel, pid, payload)
|
|
116
|
+
FastMcpPubsub.logger.debug "FastMcpPubsub: Received notification from PID #{pid}: #{payload}"
|
|
117
|
+
|
|
118
|
+
begin
|
|
119
|
+
message = JSON.parse(payload)
|
|
120
|
+
|
|
121
|
+
# Find active RackTransport instances and send to local clients
|
|
122
|
+
if defined?(FastMcp::Transports::RackTransport)
|
|
123
|
+
transports = transport_instances
|
|
124
|
+
FastMcpPubsub.logger.debug "FastMcpPubsub: Found #{transports.size} transport instances"
|
|
125
|
+
|
|
126
|
+
transports.each do |transport|
|
|
127
|
+
FastMcpPubsub.logger.debug "FastMcpPubsub: Sending message to transport #{transport.object_id}"
|
|
128
|
+
transport.send_local_message(message)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
rescue JSON::ParserError => e
|
|
132
|
+
FastMcpPubsub.logger.error "FastMcpPubsub: Invalid JSON payload: #{e.message}"
|
|
133
|
+
rescue StandardError => e
|
|
134
|
+
FastMcpPubsub.logger.error "FastMcpPubsub: Error handling notification: #{e.message}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def transport_instances
|
|
139
|
+
# Find all RackTransport instances - don't filter by running? since it's not reliably implemented
|
|
140
|
+
ObjectSpace.each_object(FastMcp::Transports::RackTransport).to_a
|
|
141
|
+
rescue StandardError
|
|
142
|
+
[]
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "fast_mcp_pubsub/version"
|
|
4
|
+
require_relative "fast_mcp_pubsub/configuration"
|
|
5
|
+
require_relative "fast_mcp_pubsub/service"
|
|
6
|
+
|
|
7
|
+
# PostgreSQL NOTIFY/LISTEN clustering support for FastMcp RackTransport.
|
|
8
|
+
# Enables FastMcp RackTransport to work in cluster mode by broadcasting messages
|
|
9
|
+
# via PostgreSQL NOTIFY/LISTEN across multiple Puma workers.
|
|
10
|
+
module FastMcpPubsub
|
|
11
|
+
class Error < StandardError; end
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
attr_accessor :configuration
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.configure
|
|
18
|
+
self.configuration ||= Configuration.new
|
|
19
|
+
yield(configuration)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.config
|
|
23
|
+
self.configuration ||= Configuration.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Simple logging helper - uses Rails.logger since this gem is Rails-specific
|
|
27
|
+
def self.logger
|
|
28
|
+
Rails.logger
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Load patch after module is fully defined
|
|
33
|
+
require_relative "fast_mcp_pubsub/rack_transport_patch"
|
|
34
|
+
require_relative "fast_mcp_pubsub/railtie" if defined?(Rails)
|
metadata
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: fast_mcp_pubsub
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- josefchmel
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2025-10-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: concurrent-ruby
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: pg
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rails
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '7.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '7.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: minitest
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '5.16'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '5.16'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rubocop
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '1.21'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '1.21'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rubocop-minitest
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0.25'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0.25'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: rubocop-rails
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '2.0'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '2.0'
|
|
110
|
+
description: Enables FastMcp RackTransport to work in cluster mode by broadcasting
|
|
111
|
+
messages via PostgreSQL NOTIFY/LISTEN across multiple Puma workers
|
|
112
|
+
email:
|
|
113
|
+
- chmel@jchsoft.cz
|
|
114
|
+
executables: []
|
|
115
|
+
extensions: []
|
|
116
|
+
extra_rdoc_files: []
|
|
117
|
+
files:
|
|
118
|
+
- ".mcp.json"
|
|
119
|
+
- ".rubocop.yml"
|
|
120
|
+
- ".ruby-version"
|
|
121
|
+
- CHANGELOG.md
|
|
122
|
+
- CLAUDE.md
|
|
123
|
+
- LICENSE.txt
|
|
124
|
+
- README.md
|
|
125
|
+
- Rakefile
|
|
126
|
+
- lib/.DS_Store
|
|
127
|
+
- lib/fast_mcp_pubsub.rb
|
|
128
|
+
- lib/fast_mcp_pubsub/configuration.rb
|
|
129
|
+
- lib/fast_mcp_pubsub/rack_transport_patch.rb
|
|
130
|
+
- lib/fast_mcp_pubsub/railtie.rb
|
|
131
|
+
- lib/fast_mcp_pubsub/service.rb
|
|
132
|
+
- lib/fast_mcp_pubsub/version.rb
|
|
133
|
+
- sig/fast_mcp_pubsub.rbs
|
|
134
|
+
homepage: https://github.com/jchsoft/fast_mcp_pubsub
|
|
135
|
+
licenses:
|
|
136
|
+
- MIT
|
|
137
|
+
metadata:
|
|
138
|
+
allowed_push_host: https://rubygems.org
|
|
139
|
+
homepage_uri: https://github.com/jchsoft/fast_mcp_pubsub
|
|
140
|
+
source_code_uri: https://github.com/jchsoft/fast_mcp_pubsub
|
|
141
|
+
changelog_uri: https://github.com/jchsoft/fast_mcp_pubsub/blob/main/CHANGELOG.md
|
|
142
|
+
rubygems_mfa_required: 'true'
|
|
143
|
+
rdoc_options: []
|
|
144
|
+
require_paths:
|
|
145
|
+
- lib
|
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
147
|
+
requirements:
|
|
148
|
+
- - ">="
|
|
149
|
+
- !ruby/object:Gem::Version
|
|
150
|
+
version: 3.1.0
|
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
|
+
requirements:
|
|
153
|
+
- - ">="
|
|
154
|
+
- !ruby/object:Gem::Version
|
|
155
|
+
version: '0'
|
|
156
|
+
requirements: []
|
|
157
|
+
rubygems_version: 3.6.2
|
|
158
|
+
specification_version: 4
|
|
159
|
+
summary: PostgreSQL NOTIFY/LISTEN clustering support for FastMcp RackTransport
|
|
160
|
+
test_files: []
|