actionmcp 0.106.1 → 0.107.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/README.md +45 -103
- data/app/controllers/action_mcp/application_controller.rb +1 -0
- data/app/models/action_mcp/session/message.rb +16 -33
- data/app/models/action_mcp/session/subscription.rb +7 -10
- data/app/models/action_mcp/session/task.rb +39 -0
- data/app/models/action_mcp/session.rb +23 -53
- data/db/migrate/20250512154359_consolidated_migration.rb +0 -37
- data/db/migrate/20260303000001_add_session_data_to_action_mcp_sessions.rb +9 -0
- data/db/migrate/20260304000001_remove_session_resources.rb +26 -0
- data/lib/action_mcp/capability.rb +2 -0
- data/lib/action_mcp/configuration.rb +1 -12
- data/lib/action_mcp/gateway.rb +19 -0
- data/lib/action_mcp/server/base_session.rb +2 -18
- data/lib/action_mcp/server/base_session_store.rb +0 -1
- data/lib/action_mcp/server.rb +0 -88
- data/lib/action_mcp/version.rb +1 -1
- data/lib/action_mcp.rb +1 -10
- data/lib/generators/action_mcp/install/install_generator.rb +15 -1
- data/lib/generators/action_mcp/install/templates/application_gateway.rb +10 -0
- data/lib/generators/action_mcp/install/templates/bin/mcp +28 -0
- data/lib/generators/action_mcp/install/templates/mcp/config.ru.tt +41 -0
- data/lib/generators/action_mcp/install/templates/mcp.yml +5 -33
- metadata +5 -5
- data/app/models/action_mcp/session/resource.rb +0 -52
- data/lib/action_mcp/server/configuration.rb +0 -66
- data/lib/action_mcp/server/simple_pub_sub.rb +0 -146
- data/lib/action_mcp/server/solid_mcp_adapter.rb +0 -170
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c333cdfa82e92c5954fd45b97c45d3f569f7fb9a5711d85195d3e2f960a284a3
|
|
4
|
+
data.tar.gz: 6d9fa7a7aa0b9ee2bb7b1549a5a6ca288db448fe7e729c3e833147cc8e71742c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f4f60433d3d29a41ef1b884a54954954c08f08efa086b6b4cd239eb64527106297f8b4d33bf2dc97c328fe2292701ba2d235a1c2052bbba3127cc732f5b2b39c
|
|
7
|
+
data.tar.gz: ea0ed7c13e3b0cb92b09774a8aef1657550fc3d0e4244de24c502fe289164ffbb2b33e78ff55771f9a13df20194ae2c02cf1a8ebfba6c11baa4e93a4fabc8793
|
data/README.md
CHANGED
|
@@ -491,51 +491,6 @@ end
|
|
|
491
491
|
|
|
492
492
|
For dynamic versioning, consider adding the `rails_app_version` gem.
|
|
493
493
|
|
|
494
|
-
|
|
495
|
-
### PubSub Configuration
|
|
496
|
-
|
|
497
|
-
ActionMCP uses a pub/sub system for real-time communication. You can choose between several adapters:
|
|
498
|
-
|
|
499
|
-
1. **SolidMCP** - Database-backed pub/sub (no Redis required)
|
|
500
|
-
2. **Simple** - In-memory pub/sub for development and testing
|
|
501
|
-
3. **Redis** - Redis-backed pub/sub (if you prefer Redis)
|
|
502
|
-
|
|
503
|
-
#### Migrating from ActionCable
|
|
504
|
-
|
|
505
|
-
If you were previously using ActionCable with ActionMCP, you will need to migrate to the new PubSub system. Here's how:
|
|
506
|
-
|
|
507
|
-
1. Remove the ActionCable dependency from your Gemfile (if you don't need it for other purposes)
|
|
508
|
-
2. Install one of the PubSub adapters (SolidMCP recommended)
|
|
509
|
-
3. Create a configuration file at `config/mcp.yml` (you can use the generator: `bin/rails g action_mcp:config`)
|
|
510
|
-
4. Run your tests to ensure everything works correctly
|
|
511
|
-
|
|
512
|
-
The new PubSub system maintains the same API as the previous ActionCable-based implementation, so your existing code should continue to work without changes.
|
|
513
|
-
|
|
514
|
-
Configure your adapter in `config/mcp.yml`:
|
|
515
|
-
|
|
516
|
-
```yaml
|
|
517
|
-
development:
|
|
518
|
-
adapter: solid_mcp
|
|
519
|
-
polling_interval: 0.1.seconds
|
|
520
|
-
# Thread pool configuration (optional)
|
|
521
|
-
# min_threads: 5 # Minimum number of threads in the pool
|
|
522
|
-
# max_threads: 10 # Maximum number of threads in the pool
|
|
523
|
-
# max_queue: 100 # Maximum number of tasks that can be queued
|
|
524
|
-
|
|
525
|
-
test:
|
|
526
|
-
adapter: test # Uses the simple in-memory adapter
|
|
527
|
-
|
|
528
|
-
production:
|
|
529
|
-
adapter: solid_mcp
|
|
530
|
-
polling_interval: 0.5.seconds
|
|
531
|
-
# Optional: connects_to: cable # If using a separate database
|
|
532
|
-
|
|
533
|
-
# Thread pool configuration for high-traffic environments
|
|
534
|
-
min_threads: 10 # Minimum number of threads in the pool
|
|
535
|
-
max_threads: 20 # Maximum number of threads in the pool
|
|
536
|
-
max_queue: 500 # Maximum number of tasks that can be queued
|
|
537
|
-
```
|
|
538
|
-
|
|
539
494
|
### Server Instructions
|
|
540
495
|
|
|
541
496
|
Server instructions help LLMs understand **what your server is for** and **when to use it**. They describe the server's purpose and goal, not technical details like rate limits or authentication (tools are self-documented via their own descriptions).
|
|
@@ -565,46 +520,6 @@ production:
|
|
|
565
520
|
|
|
566
521
|
Instructions are sent as a single string (joined by newlines) at the top level of the initialization response, helping LLMs understand your server's purpose.
|
|
567
522
|
|
|
568
|
-
#### SolidMCP (Database-backed, Recommended)
|
|
569
|
-
|
|
570
|
-
For SolidMCP, add it to your Gemfile:
|
|
571
|
-
|
|
572
|
-
```ruby
|
|
573
|
-
gem "solid_mcp" # Database-backed adapter optimized for MCP
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
Then install it:
|
|
577
|
-
|
|
578
|
-
```bash
|
|
579
|
-
bundle install
|
|
580
|
-
bin/rails solid_mcp:install:migrations
|
|
581
|
-
bin/rails db:migrate
|
|
582
|
-
```
|
|
583
|
-
|
|
584
|
-
The installer will create the necessary database migration for message storage. Configure it in your `config/mcp.yml`.
|
|
585
|
-
|
|
586
|
-
#### Redis Adapter
|
|
587
|
-
|
|
588
|
-
If you prefer Redis, add it to your Gemfile:
|
|
589
|
-
|
|
590
|
-
```ruby
|
|
591
|
-
gem "redis", "~> 5.0"
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
Then configure the Redis adapter in your `config/mcp.yml`:
|
|
595
|
-
|
|
596
|
-
```yaml
|
|
597
|
-
production:
|
|
598
|
-
adapter: redis
|
|
599
|
-
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
|
|
600
|
-
channel_prefix: your_app_production
|
|
601
|
-
|
|
602
|
-
# Thread pool configuration for high-traffic environments
|
|
603
|
-
min_threads: 10 # Minimum number of threads in the pool
|
|
604
|
-
max_threads: 20 # Maximum number of threads in the pool
|
|
605
|
-
max_queue: 500 # Maximum number of tasks that can be queued
|
|
606
|
-
```
|
|
607
|
-
|
|
608
523
|
## Session Storage
|
|
609
524
|
|
|
610
525
|
ActionMCP provides a pluggable session storage system that allows you to choose how sessions are persisted based on your environment and requirements.
|
|
@@ -747,7 +662,6 @@ You can configure the thread pool in your `config/mcp.yml`:
|
|
|
747
662
|
|
|
748
663
|
```yaml
|
|
749
664
|
production:
|
|
750
|
-
adapter: solid_mcp
|
|
751
665
|
# Thread pool configuration
|
|
752
666
|
min_threads: 10 # Minimum number of threads to keep in the pool
|
|
753
667
|
max_threads: 20 # Maximum number of threads the pool can grow to
|
|
@@ -772,7 +686,9 @@ This ensures all thread pools are properly terminated and tasks are completed.
|
|
|
772
686
|
|
|
773
687
|
## Engine and Mounting
|
|
774
688
|
|
|
775
|
-
**
|
|
689
|
+
> **WARNING: Do NOT mount ActionMCP::Engine in your `routes.rb`.** ActionMCP is a standalone Rack application that runs on its own port via `mcp/config.ru`. Mounting it as a Rails engine route will not work correctly.
|
|
690
|
+
|
|
691
|
+
When you use `run ActionMCP.server` in your `mcp/config.ru`, the MCP endpoint is available at the root path (`/`) by default and can be configured via `config.action_mcp.base_path`. Always use `ActionMCP.server` (not `ActionMCP::Engine` directly) — it initializes required subsystems.
|
|
776
692
|
|
|
777
693
|
### Installing ActionMCP
|
|
778
694
|
|
|
@@ -789,12 +705,14 @@ This will create:
|
|
|
789
705
|
- `app/mcp/resource_templates/application_mcp_res_template.rb` - Base resource template class
|
|
790
706
|
- `app/mcp/application_gateway.rb` - Gateway for authentication
|
|
791
707
|
- `config/mcp.yml` - Configuration file with example settings for all environments
|
|
708
|
+
- `mcp/config.ru` - Standalone Rack server configuration
|
|
709
|
+
- `bin/mcp` - Server binstub (prefers Falcon, falls back to Puma)
|
|
792
710
|
|
|
793
711
|
> **Note:** Authentication and authorization are not included. You are responsible for securing the endpoint.
|
|
794
712
|
|
|
795
713
|
## Authentication with Gateway
|
|
796
714
|
|
|
797
|
-
ActionMCP provides a Gateway system
|
|
715
|
+
ActionMCP provides a Gateway system for handling authentication. The Gateway allows you to authenticate users and make them available throughout your MCP components.
|
|
798
716
|
|
|
799
717
|
ActionMCP uses a Gateway pattern with pluggable identifiers for authentication. You can implement custom authentication strategies using session-based auth, API keys, bearer tokens, or integrate with existing authentication systems like Warden, Devise, or external OAuth providers.
|
|
800
718
|
|
|
@@ -877,19 +795,43 @@ ActionMCP uses Rails' CurrentAttributes to store the authenticated context. The
|
|
|
877
795
|
- `ActionMCP::Current.gateway` - The gateway instance
|
|
878
796
|
- Any other attributes you define with `identified_by`
|
|
879
797
|
|
|
880
|
-
###
|
|
798
|
+
### Persisting Context on Sessions
|
|
799
|
+
|
|
800
|
+
Override `configure_session` in your gateway to persist auth context (user_id, tenant_id, roles) on the session for use in audit trails, background jobs, and cross-request scenarios:
|
|
801
|
+
|
|
802
|
+
```ruby
|
|
803
|
+
class ApplicationGateway < ActionMCP::Gateway
|
|
804
|
+
identified_by UserIdentifier
|
|
805
|
+
|
|
806
|
+
def configure_session(session)
|
|
807
|
+
session.session_data = { "user_id" => user.id, "tenant_id" => user.tenant_id }
|
|
808
|
+
end
|
|
809
|
+
end
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
Tools access it via `session_data["user_id"]`. See [GATEWAY.md](GATEWAY.md) for details.
|
|
813
|
+
|
|
814
|
+
### 1. Create `mcp/config.ru`
|
|
815
|
+
|
|
816
|
+
The install generator (`rails generate action_mcp:install`) creates this automatically. If you need to create it manually:
|
|
881
817
|
|
|
882
818
|
```ruby
|
|
883
819
|
# Load the full Rails environment to access models, DB, Redis, etc.
|
|
884
|
-
require_relative "config/environment"
|
|
820
|
+
require_relative "../config/environment"
|
|
821
|
+
|
|
822
|
+
$stdout.sync = true
|
|
823
|
+
|
|
824
|
+
# Eager load so all tools, prompts, and resources are registered.
|
|
825
|
+
Rails.application.eager_load!
|
|
885
826
|
|
|
886
|
-
#
|
|
887
|
-
#
|
|
888
|
-
run ActionMCP
|
|
827
|
+
# IMPORTANT: Use ActionMCP.server — it initializes required subsystems.
|
|
828
|
+
# Do NOT use ActionMCP::Engine directly.
|
|
829
|
+
run ActionMCP.server
|
|
889
830
|
```
|
|
890
831
|
### 2. Start the server
|
|
891
832
|
```bash
|
|
892
|
-
bin/
|
|
833
|
+
bin/mcp # Uses Falcon (recommended)
|
|
834
|
+
bundle exec rails s -c mcp/config.ru -p 62770 # Uses Puma (fallback)
|
|
893
835
|
```
|
|
894
836
|
|
|
895
837
|
### Dealing with Middleware Conflicts
|
|
@@ -933,17 +875,17 @@ Run MCPS0 on its own TCP port (commonly `62770`):
|
|
|
933
875
|
|
|
934
876
|
**With Falcon:**
|
|
935
877
|
```bash
|
|
936
|
-
bundle exec falcon serve --bind http://0.0.0.0:62770 --config mcp.ru
|
|
878
|
+
bundle exec falcon serve --bind http://0.0.0.0:62770 --config mcp/config.ru
|
|
937
879
|
```
|
|
938
880
|
|
|
939
881
|
**With Puma:**
|
|
940
882
|
```bash
|
|
941
|
-
bundle exec rails s -c mcp.ru -p 62770
|
|
883
|
+
bundle exec rails s -c mcp/config.ru -p 62770
|
|
942
884
|
```
|
|
943
885
|
|
|
944
886
|
**With Passenger:**
|
|
945
887
|
```bash
|
|
946
|
-
passenger start --rackup mcp.ru --port 62770
|
|
888
|
+
passenger start --rackup mcp/config.ru --port 62770
|
|
947
889
|
```
|
|
948
890
|
|
|
949
891
|
Then, use your web server (Nginx, Apache, etc.) to reverse proxy requests to this port.
|
|
@@ -954,17 +896,17 @@ Alternatively, you can run MCPS0 on a Unix socket for improved performance and s
|
|
|
954
896
|
|
|
955
897
|
**With Falcon:**
|
|
956
898
|
```bash
|
|
957
|
-
bundle exec falcon serve --bind unix:/tmp/mcps0.sock mcp.ru
|
|
899
|
+
bundle exec falcon serve --bind unix:/tmp/mcps0.sock mcp/config.ru
|
|
958
900
|
```
|
|
959
901
|
|
|
960
902
|
**With Puma:**
|
|
961
903
|
```bash
|
|
962
|
-
bundle exec puma -C config/puma.rb -b unix:///tmp/mcps0.sock -c mcp.ru
|
|
904
|
+
bundle exec puma -C config/puma.rb -b unix:///tmp/mcps0.sock -c mcp/config.ru
|
|
963
905
|
```
|
|
964
906
|
|
|
965
907
|
**With Passenger:**
|
|
966
908
|
```bash
|
|
967
|
-
passenger start --rackup mcp.ru --socket /tmp/mcps0.sock
|
|
909
|
+
passenger start --rackup mcp/config.ru --socket /tmp/mcps0.sock
|
|
968
910
|
```
|
|
969
911
|
|
|
970
912
|
And configure your web server to proxy to the socket:
|
|
@@ -994,7 +936,7 @@ location ~* ^/mcp {
|
|
|
994
936
|
root /path/to/current/public;
|
|
995
937
|
passenger_app_root /path/to/current;
|
|
996
938
|
passenger_enabled on;
|
|
997
|
-
passenger_startup_file mcp.ru;
|
|
939
|
+
passenger_startup_file mcp/config.ru;
|
|
998
940
|
passenger_app_group_name mcp;
|
|
999
941
|
}
|
|
1000
942
|
```
|
|
@@ -1018,7 +960,7 @@ bin/rails action_mcp:install:migrations # to copy the migrations
|
|
|
1018
960
|
bin/rails generate action_mcp:install
|
|
1019
961
|
```
|
|
1020
962
|
|
|
1021
|
-
This will create the base application classes, configuration file,
|
|
963
|
+
This will create the base application classes, configuration file, authentication gateway, `mcp/config.ru` rackup file, and `bin/mcp` binstub in your app directory.
|
|
1022
964
|
|
|
1023
965
|
### Generate a New Prompt
|
|
1024
966
|
|
|
@@ -1071,7 +1013,7 @@ You can use the MCP Inspector to test your server implementation:
|
|
|
1071
1013
|
|
|
1072
1014
|
```bash
|
|
1073
1015
|
# Start your MCP server
|
|
1074
|
-
bundle exec rails s -c mcp.ru -p 62770
|
|
1016
|
+
bundle exec rails s -c mcp/config.ru -p 62770
|
|
1075
1017
|
|
|
1076
1018
|
# In another terminal, run the inspector
|
|
1077
1019
|
npx @modelcontextprotocol/inspector --url http://localhost:62770
|
|
@@ -5,17 +5,17 @@
|
|
|
5
5
|
# database_dialect = "SQLite"
|
|
6
6
|
#
|
|
7
7
|
# columns = [
|
|
8
|
-
# { name = "id", type = "integer",
|
|
9
|
-
# { name = "
|
|
10
|
-
# { name = "direction", type = "string",
|
|
11
|
-
# { name = "
|
|
12
|
-
# { name = "jsonrpc_id", type = "string"
|
|
13
|
-
# { name = "message_json", type = "json"
|
|
14
|
-
# { name = "
|
|
15
|
-
# { name = "request_acknowledged", type = "boolean",
|
|
16
|
-
# { name = "request_cancelled", type = "boolean",
|
|
17
|
-
# { name = "
|
|
18
|
-
# { name = "updated_at", type = "datetime",
|
|
8
|
+
# { name = "id", type = "integer", pk = true, null = false },
|
|
9
|
+
# { name = "created_at", type = "datetime", null = false },
|
|
10
|
+
# { name = "direction", type = "string", null = false, default = "client" },
|
|
11
|
+
# { name = "is_ping", type = "boolean", null = false },
|
|
12
|
+
# { name = "jsonrpc_id", type = "string" },
|
|
13
|
+
# { name = "message_json", type = "json" },
|
|
14
|
+
# { name = "message_type", type = "string", null = false },
|
|
15
|
+
# { name = "request_acknowledged", type = "boolean", null = false },
|
|
16
|
+
# { name = "request_cancelled", type = "boolean", null = false },
|
|
17
|
+
# { name = "session_id", type = "string", null = false },
|
|
18
|
+
# { name = "updated_at", type = "datetime", null = false }
|
|
19
19
|
# ]
|
|
20
20
|
#
|
|
21
21
|
# indexes = [
|
|
@@ -26,14 +26,10 @@
|
|
|
26
26
|
# { column = "session_id", references_table = "action_mcp_sessions", references_column = "id", on_delete = "cascade", on_update = "cascade" }
|
|
27
27
|
# ]
|
|
28
28
|
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# - String column 'message_type' has no length limit - consider adding one
|
|
34
|
-
# - String column 'jsonrpc_id' has no length limit - consider adding one
|
|
35
|
-
# - Column 'message_type' is commonly used in queries - consider adding an index
|
|
36
|
-
# - Column 'is_ping' uses non-conventional prefix - consider removing 'is_' or 'has_'
|
|
29
|
+
# [callbacks]
|
|
30
|
+
# after_create = [{ method = "acknowledge_request", if = ["proc"] }]
|
|
31
|
+
#
|
|
32
|
+
# notes = ["message_json:NOT_NULL", "direction:LIMIT", "jsonrpc_id:LIMIT", "message_type:LIMIT", "session_id:LIMIT", "message_type:INDEX"]
|
|
37
33
|
# <rails-lens:schema:end>
|
|
38
34
|
module ActionMCP
|
|
39
35
|
class Session
|
|
@@ -49,15 +45,12 @@ module ActionMCP
|
|
|
49
45
|
inverse_of: :messages,
|
|
50
46
|
counter_cache: true
|
|
51
47
|
|
|
52
|
-
delegate :
|
|
53
|
-
:role,
|
|
54
|
-
:session_key,
|
|
48
|
+
delegate :role,
|
|
55
49
|
to: :session
|
|
56
50
|
|
|
57
51
|
# Virtual attribute for data
|
|
58
52
|
attr_reader :data
|
|
59
53
|
|
|
60
|
-
after_create_commit :broadcast_message, if: :outgoing_message?
|
|
61
54
|
# Set is_ping on responses if the original request was a ping
|
|
62
55
|
after_create :acknowledge_request, if: -> { %w[response error].include?(message_type) }
|
|
63
56
|
|
|
@@ -117,16 +110,6 @@ module ActionMCP
|
|
|
117
110
|
|
|
118
111
|
private
|
|
119
112
|
|
|
120
|
-
def outgoing_message?
|
|
121
|
-
direction != role
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def broadcast_message
|
|
125
|
-
return unless adapter.present?
|
|
126
|
-
|
|
127
|
-
adapter.broadcast(session_key, data.to_json)
|
|
128
|
-
end
|
|
129
|
-
|
|
130
113
|
def process_json_content(content)
|
|
131
114
|
if content.is_a?(JSON_RPC::Notification) || content.is_a?(JSON_RPC::Request) || content.is_a?(JSON_RPC::Response)
|
|
132
115
|
content = content.to_h.with_indifferent_access
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
# database_dialect = "SQLite"
|
|
6
6
|
#
|
|
7
7
|
# columns = [
|
|
8
|
-
# { name = "id", type = "integer",
|
|
9
|
-
# { name = "
|
|
10
|
-
# { name = "
|
|
11
|
-
# { name = "
|
|
12
|
-
# { name = "
|
|
13
|
-
# { name = "
|
|
8
|
+
# { name = "id", type = "integer", pk = true, null = false },
|
|
9
|
+
# { name = "created_at", type = "datetime", null = false },
|
|
10
|
+
# { name = "last_notification_at", type = "datetime" },
|
|
11
|
+
# { name = "session_id", type = "string", null = false },
|
|
12
|
+
# { name = "updated_at", type = "datetime", null = false },
|
|
13
|
+
# { name = "uri", type = "string", null = false }
|
|
14
14
|
# ]
|
|
15
15
|
#
|
|
16
16
|
# indexes = [
|
|
@@ -21,10 +21,7 @@
|
|
|
21
21
|
# { column = "session_id", references_table = "action_mcp_sessions", references_column = "id", on_delete = "cascade" }
|
|
22
22
|
# ]
|
|
23
23
|
#
|
|
24
|
-
#
|
|
25
|
-
# - Consider adding counter cache for 'session'
|
|
26
|
-
# - String column 'session_id' has no length limit - consider adding one
|
|
27
|
-
# - String column 'uri' has no length limit - consider adding one
|
|
24
|
+
# notes = ["session:COUNTER_CACHE", "session_id:LIMIT", "uri:LIMIT"]
|
|
28
25
|
# <rails-lens:schema:end>
|
|
29
26
|
module ActionMCP
|
|
30
27
|
class Session
|
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# <rails-lens:schema:begin>
|
|
4
|
+
# table = "action_mcp_session_tasks"
|
|
5
|
+
# database_dialect = "SQLite"
|
|
6
|
+
#
|
|
7
|
+
# columns = [
|
|
8
|
+
# { name = "id", type = "string", pk = true, null = false },
|
|
9
|
+
# { name = "created_at", type = "datetime", null = false },
|
|
10
|
+
# { name = "last_updated_at", type = "datetime", null = false },
|
|
11
|
+
# { name = "poll_interval", type = "integer" },
|
|
12
|
+
# { name = "progress_message", type = "string" },
|
|
13
|
+
# { name = "progress_percent", type = "integer" },
|
|
14
|
+
# { name = "request_method", type = "string" },
|
|
15
|
+
# { name = "request_name", type = "string" },
|
|
16
|
+
# { name = "request_params", type = "json" },
|
|
17
|
+
# { name = "result_payload", type = "json" },
|
|
18
|
+
# { name = "session_id", type = "string", null = false },
|
|
19
|
+
# { name = "status", type = "string", null = false, default = "working" },
|
|
20
|
+
# { name = "status_message", type = "string" },
|
|
21
|
+
# { name = "ttl", type = "integer" },
|
|
22
|
+
# { name = "updated_at", type = "datetime", null = false }
|
|
23
|
+
# ]
|
|
24
|
+
#
|
|
25
|
+
# indexes = [
|
|
26
|
+
# { name = "index_action_mcp_session_tasks_on_status", columns = ["status"] },
|
|
27
|
+
# { name = "index_action_mcp_session_tasks_on_session_id", columns = ["session_id"] },
|
|
28
|
+
# { name = "index_action_mcp_session_tasks_on_session_id_and_status", columns = ["session_id", "status"] },
|
|
29
|
+
# { name = "index_action_mcp_session_tasks_on_created_at", columns = ["created_at"] }
|
|
30
|
+
# ]
|
|
31
|
+
#
|
|
32
|
+
# foreign_keys = [
|
|
33
|
+
# { column = "session_id", references_table = "action_mcp_sessions", references_column = "id", on_delete = "cascade", on_update = "cascade" }
|
|
34
|
+
# ]
|
|
35
|
+
#
|
|
36
|
+
# [callbacks]
|
|
37
|
+
# before_validation = [{ method = "set_last_updated_at" }]
|
|
38
|
+
# around_validation = [{ method = "machine" }]
|
|
39
|
+
#
|
|
40
|
+
# notes = ["index_action_mcp_session_tasks_on_session_id:REDUND_IDX", "session:COUNTER_CACHE", "poll_interval:NOT_NULL", "progress_message:NOT_NULL", "progress_percent:NOT_NULL", "request_method:NOT_NULL", "request_name:NOT_NULL", "request_params:NOT_NULL", "result_payload:NOT_NULL", "status_message:NOT_NULL", "ttl:NOT_NULL", "status_message:DEFAULT", "id:LIMIT", "progress_message:LIMIT", "request_method:LIMIT", "request_name:LIMIT", "session_id:LIMIT", "status:LIMIT", "status_message:LIMIT", "status_message:INDEX"]
|
|
41
|
+
# <rails-lens:schema:end>
|
|
3
42
|
require "state_machines-activerecord"
|
|
4
43
|
|
|
5
44
|
module ActionMCP
|
|
@@ -5,44 +5,31 @@
|
|
|
5
5
|
# database_dialect = "SQLite"
|
|
6
6
|
#
|
|
7
7
|
# columns = [
|
|
8
|
-
# { name = "id", type = "string",
|
|
9
|
-
# { name = "
|
|
10
|
-
# { name = "
|
|
11
|
-
# { name = "
|
|
12
|
-
# { name = "
|
|
13
|
-
# { name = "
|
|
14
|
-
# { name = "
|
|
15
|
-
# { name = "
|
|
16
|
-
# { name = "
|
|
17
|
-
# { name = "
|
|
18
|
-
# { name = "
|
|
19
|
-
# { name = "
|
|
20
|
-
# { name = "
|
|
21
|
-
# { name = "
|
|
22
|
-
# { name = "
|
|
23
|
-
# { name = "
|
|
24
|
-
# { name = "
|
|
25
|
-
# { name = "
|
|
8
|
+
# { name = "id", type = "string", pk = true, null = false },
|
|
9
|
+
# { name = "client_capabilities", type = "json" },
|
|
10
|
+
# { name = "client_info", type = "json" },
|
|
11
|
+
# { name = "consents", type = "json", null = false, default = "{}" },
|
|
12
|
+
# { name = "session_data", type = "json", null = false, default = "{}" },
|
|
13
|
+
# { name = "created_at", type = "datetime", null = false },
|
|
14
|
+
# { name = "ended_at", type = "datetime" },
|
|
15
|
+
# { name = "initialized", type = "boolean", null = false },
|
|
16
|
+
# { name = "messages_count", type = "integer", null = false, default = 0 },
|
|
17
|
+
# { name = "prompt_registry", type = "json", default = "[]" },
|
|
18
|
+
# { name = "protocol_version", type = "string" },
|
|
19
|
+
# { name = "resource_registry", type = "json", default = "[]" },
|
|
20
|
+
# { name = "role", type = "string", null = false, default = "server" },
|
|
21
|
+
# { name = "server_capabilities", type = "json" },
|
|
22
|
+
# { name = "server_info", type = "json" },
|
|
23
|
+
# { name = "status", type = "string", null = false, default = "pre_initialize" },
|
|
24
|
+
# { name = "tool_registry", type = "json", default = "[]" },
|
|
25
|
+
# { name = "updated_at", type = "datetime", null = false }
|
|
26
26
|
# ]
|
|
27
27
|
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# - Column 'protocol_version' should probably have NOT NULL constraint
|
|
34
|
-
# - Column 'server_capabilities' should probably have NOT NULL constraint
|
|
35
|
-
# - Column 'client_capabilities' should probably have NOT NULL constraint
|
|
36
|
-
# - Column 'server_info' should probably have NOT NULL constraint
|
|
37
|
-
# - Column 'client_info' should probably have NOT NULL constraint
|
|
38
|
-
# - Column 'tool_registry' should probably have NOT NULL constraint
|
|
39
|
-
# - Column 'prompt_registry' should probably have NOT NULL constraint
|
|
40
|
-
# - Column 'resource_registry' should probably have NOT NULL constraint
|
|
41
|
-
# - String column 'id' has no length limit - consider adding one
|
|
42
|
-
# - String column 'role' has no length limit - consider adding one
|
|
43
|
-
# - String column 'status' has no length limit - consider adding one
|
|
44
|
-
# - String column 'protocol_version' has no length limit - consider adding one
|
|
45
|
-
# - Column 'status' is commonly used in queries - consider adding an index
|
|
28
|
+
# [callbacks]
|
|
29
|
+
# before_create = [{ method = "initialize_registries" }, { method = "set_server_info", if = ["proc"] }, { method = "set_server_capabilities", if = ["proc"] }]
|
|
30
|
+
# after_initialize = [{ method = "proc" }]
|
|
31
|
+
#
|
|
32
|
+
# notes = ["messages:N_PLUS_ONE", "subscriptions:N_PLUS_ONE", "tasks:N_PLUS_ONE", "client_capabilities:NOT_NULL", "client_info:NOT_NULL", "prompt_registry:NOT_NULL", "protocol_version:NOT_NULL", "resource_registry:NOT_NULL", "server_capabilities:NOT_NULL", "server_info:NOT_NULL", "tool_registry:NOT_NULL", "id:LIMIT", "protocol_version:LIMIT", "role:LIMIT", "status:LIMIT", "status:INDEX"]
|
|
46
33
|
# <rails-lens:schema:end>
|
|
47
34
|
module ActionMCP
|
|
48
35
|
##
|
|
@@ -67,12 +54,6 @@ module ActionMCP
|
|
|
67
54
|
foreign_key: "session_id",
|
|
68
55
|
dependent: :delete_all,
|
|
69
56
|
inverse_of: :session
|
|
70
|
-
has_many :resources,
|
|
71
|
-
class_name: "ActionMCP::Session::Resource",
|
|
72
|
-
foreign_key: "session_id",
|
|
73
|
-
dependent: :delete_all,
|
|
74
|
-
inverse_of: :session
|
|
75
|
-
|
|
76
57
|
has_many :tasks,
|
|
77
58
|
class_name: "ActionMCP::Session::Task",
|
|
78
59
|
foreign_key: "session_id",
|
|
@@ -93,8 +74,6 @@ module ActionMCP
|
|
|
93
74
|
validates :protocol_version, inclusion: { in: ActionMCP::SUPPORTED_VERSIONS }, allow_nil: true
|
|
94
75
|
|
|
95
76
|
def close!
|
|
96
|
-
dummy_callback = ->(*) { } # this callback seem broken
|
|
97
|
-
adapter.unsubscribe(session_key, dummy_callback)
|
|
98
77
|
if messages_count.zero?
|
|
99
78
|
# if there are no messages, we can delete the session immediately
|
|
100
79
|
destroy
|
|
@@ -119,14 +98,6 @@ module ActionMCP
|
|
|
119
98
|
messages.create!(data: data, direction: role)
|
|
120
99
|
end
|
|
121
100
|
|
|
122
|
-
def session_key
|
|
123
|
-
"action_mcp:session:#{id}"
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def adapter
|
|
127
|
-
@adapter ||= ActionMCP::Server.server.pubsub
|
|
128
|
-
end
|
|
129
|
-
|
|
130
101
|
def set_protocol_version(version)
|
|
131
102
|
update(protocol_version: version)
|
|
132
103
|
end
|
|
@@ -321,7 +292,6 @@ module ActionMCP
|
|
|
321
292
|
resource_registry == [ "*" ]
|
|
322
293
|
end
|
|
323
294
|
|
|
324
|
-
|
|
325
295
|
# Consent management methods as per MCP specification
|
|
326
296
|
# These methods manage user consents for tools and resources
|
|
327
297
|
|
|
@@ -17,7 +17,6 @@ class ConsolidatedMigration < ActiveRecord::Migration[8.0]
|
|
|
17
17
|
t.json :client_info, comment: 'The information about the client'
|
|
18
18
|
t.boolean :initialized, null: false, default: false
|
|
19
19
|
t.integer :messages_count, null: false, default: 0
|
|
20
|
-
t.integer :sse_event_counter, default: 0, null: false
|
|
21
20
|
t.json :tool_registry, default: []
|
|
22
21
|
t.json :prompt_registry, default: []
|
|
23
22
|
t.json :resource_registry, default: []
|
|
@@ -57,46 +56,10 @@ class ConsolidatedMigration < ActiveRecord::Migration[8.0]
|
|
|
57
56
|
end
|
|
58
57
|
end
|
|
59
58
|
|
|
60
|
-
# Create session resources table
|
|
61
|
-
unless table_exists?(:action_mcp_session_resources)
|
|
62
|
-
create_table :action_mcp_session_resources do |t|
|
|
63
|
-
t.references :session,
|
|
64
|
-
null: false,
|
|
65
|
-
foreign_key: { to_table: :action_mcp_sessions, on_delete: :cascade },
|
|
66
|
-
type: :string
|
|
67
|
-
t.string :uri, null: false
|
|
68
|
-
t.string :name
|
|
69
|
-
t.text :description
|
|
70
|
-
t.string :mime_type, null: false
|
|
71
|
-
t.boolean :created_by_tool, default: false
|
|
72
|
-
t.datetime :last_accessed_at
|
|
73
|
-
t.json :metadata
|
|
74
|
-
t.timestamps
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Create SSE events table
|
|
79
|
-
unless table_exists?(:action_mcp_sse_events)
|
|
80
|
-
create_table :action_mcp_sse_events do |t|
|
|
81
|
-
t.references :session, null: false, foreign_key: { to_table: :action_mcp_sessions }, index: true, type: :string
|
|
82
|
-
t.integer :event_id, null: false
|
|
83
|
-
t.text :data, null: false
|
|
84
|
-
t.timestamps
|
|
85
|
-
|
|
86
|
-
# Index for efficiently retrieving events after a given ID for a specific session
|
|
87
|
-
t.index %i[session_id event_id], unique: true
|
|
88
|
-
t.index :created_at # For cleanup of old events
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
|
|
92
59
|
# Add missing columns to existing tables if they exist
|
|
93
60
|
|
|
94
61
|
# For action_mcp_sessions
|
|
95
62
|
if table_exists?(:action_mcp_sessions)
|
|
96
|
-
unless column_exists?(:action_mcp_sessions, :sse_event_counter)
|
|
97
|
-
add_column :action_mcp_sessions, :sse_event_counter, :integer, default: 0, null: false
|
|
98
|
-
end
|
|
99
|
-
|
|
100
63
|
unless column_exists?(:action_mcp_sessions, :tool_registry)
|
|
101
64
|
add_column :action_mcp_sessions, :tool_registry, :json, default: []
|
|
102
65
|
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddSessionDataToActionMCPSessions < ActiveRecord::Migration[8.1]
|
|
4
|
+
def change
|
|
5
|
+
return if column_exists?(:action_mcp_sessions, :session_data)
|
|
6
|
+
|
|
7
|
+
add_column :action_mcp_sessions, :session_data, :json, default: {}, null: false
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class RemoveSessionResources < ActiveRecord::Migration[8.1]
|
|
4
|
+
def up
|
|
5
|
+
drop_table :action_mcp_session_resources, if_exists: true
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def down
|
|
9
|
+
return if table_exists?(:action_mcp_session_resources)
|
|
10
|
+
|
|
11
|
+
create_table :action_mcp_session_resources do |t|
|
|
12
|
+
t.references :session,
|
|
13
|
+
null: false,
|
|
14
|
+
foreign_key: { to_table: :action_mcp_sessions, on_delete: :cascade },
|
|
15
|
+
type: :string
|
|
16
|
+
t.string :uri, null: false
|
|
17
|
+
t.string :name
|
|
18
|
+
t.text :description
|
|
19
|
+
t.string :mime_type, null: false
|
|
20
|
+
t.boolean :created_by_tool, default: false
|
|
21
|
+
t.datetime :last_accessed_at
|
|
22
|
+
t.json :metadata
|
|
23
|
+
t.timestamps
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|