active_mcp 0.9.3 → 0.10.1
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 +69 -25
- data/app/controllers/concerns/active_mcp/request_handlable.rb +12 -12
- data/app/controllers/concerns/active_mcp/resource_readable.rb +1 -1
- data/app/controllers/concerns/active_mcp/tool_executable.rb +1 -1
- data/app/views/active_mcp/prompts_get.json.jbuilder +2 -2
- data/app/views/active_mcp/prompts_list.json.jbuilder +1 -1
- data/lib/active_mcp/prompt/base.rb +1 -1
- data/lib/active_mcp/schema/base.rb +13 -33
- data/lib/active_mcp/tool/base.rb +1 -1
- data/lib/active_mcp/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b553c506022715ad2f57af006408ab14b08167505efd4aac1fc0bb08ffdde6c4
|
4
|
+
data.tar.gz: b6c2818a416c3da6021e04a8852e08820f757a33cba12b4ca88e7d0ad212caa5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f022889f1b6dba59d4fe68586d102cd6671948e8b402e8ebcf268fa68d2a6c45b1f8cfaecc097e70499f941d9e432e8efd53c25637ce480a6b4b45f4dfb5a294
|
7
|
+
data.tar.gz: 5c9293d59c6d9f2f66d756baa505daaec323108b5921e761502894a54bdb008e879441ba873f0cae57b4813bdd741d50310fcce92af4656875176fbf2f2bf54d
|
data/README.md
CHANGED
@@ -38,6 +38,7 @@ A Ruby on Rails engine for the [Model Context Protocol (MCP)](https://modelconte
|
|
38
38
|
- [Creating Resource Templates](#creating-resource-templates)
|
39
39
|
- [💬 MCP Prompts](#-mcp-prompts)
|
40
40
|
- [Creating Prompt](#creating-prompt)
|
41
|
+
- [📥 Using Context in the Schema](#-using-context-in-the-schema)
|
41
42
|
- [💡 Best Practices](#-best-practices)
|
42
43
|
- [1. Create Specific Tool Classes](#1-create-specific-tool-classes)
|
43
44
|
- [2. Validate and Sanitize Inputs](#2-validate-and-sanitize-inputs)
|
@@ -95,7 +96,7 @@ $ rails generate active_mcp:tool create_note
|
|
95
96
|
```ruby
|
96
97
|
class CreateNoteTool < ActiveMcp::Tool::Base
|
97
98
|
def tool_name
|
98
|
-
"
|
99
|
+
"create_note"
|
99
100
|
end
|
100
101
|
|
101
102
|
def description
|
@@ -117,7 +118,11 @@ end
|
|
117
118
|
|
118
119
|
```ruby
|
119
120
|
class MySchema < ActiveMcp::Schema::Base
|
120
|
-
|
121
|
+
def tools
|
122
|
+
[
|
123
|
+
CreateNoteTool.new
|
124
|
+
]
|
125
|
+
end
|
121
126
|
end
|
122
127
|
```
|
123
128
|
|
@@ -125,6 +130,9 @@ end
|
|
125
130
|
|
126
131
|
```ruby
|
127
132
|
class MyMcpController < ActiveMcp::BaseController
|
133
|
+
|
134
|
+
private
|
135
|
+
|
128
136
|
def schema
|
129
137
|
MySchema.new(context:)
|
130
138
|
end
|
@@ -271,7 +279,7 @@ Control access to tools by overriding the `visible?` class method:
|
|
271
279
|
```ruby
|
272
280
|
class AdminOnlyTool < ActiveMcp::Tool::Base
|
273
281
|
def tool_name
|
274
|
-
"
|
282
|
+
"admin_only_tool"
|
275
283
|
end
|
276
284
|
|
277
285
|
def description
|
@@ -380,8 +388,10 @@ end
|
|
380
388
|
|
381
389
|
```ruby
|
382
390
|
class MySchema < ActiveMcp::Schema::Base
|
383
|
-
|
384
|
-
|
391
|
+
def resources
|
392
|
+
User.all.each do |user|
|
393
|
+
UserResource.new(id: user.id)
|
394
|
+
end
|
385
395
|
end
|
386
396
|
end
|
387
397
|
```
|
@@ -500,8 +510,10 @@ end
|
|
500
510
|
|
501
511
|
```ruby
|
502
512
|
class MySchema < ActiveMcp::Schema::Base
|
503
|
-
|
504
|
-
|
513
|
+
def resources
|
514
|
+
User.all.each do |user|
|
515
|
+
UserResource.new(id: user.id)
|
516
|
+
end
|
505
517
|
end
|
506
518
|
end
|
507
519
|
```
|
@@ -516,33 +528,31 @@ Resources are Ruby classes `**Prompt`:
|
|
516
528
|
|
517
529
|
```ruby
|
518
530
|
class HelloPrompt < ActiveMcp::Prompt::Base
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
end
|
531
|
+
argument :name, required: true, description: "User name", complete: ->(value) do
|
532
|
+
User.all.pluck(:name).filter { _1.match(value) }
|
533
|
+
end
|
523
534
|
|
524
|
-
|
525
|
-
|
526
|
-
|
535
|
+
def initialize(greeting:)
|
536
|
+
@greeting = greeting
|
537
|
+
end
|
527
538
|
|
528
|
-
|
529
|
-
|
530
|
-
end
|
539
|
+
def prompt_name
|
540
|
+
"hello"
|
531
541
|
end
|
532
542
|
|
533
|
-
|
534
|
-
|
543
|
+
def description
|
544
|
+
"This is a test."
|
535
545
|
end
|
536
546
|
|
537
|
-
def
|
538
|
-
|
547
|
+
def visible?(context:)
|
548
|
+
# Your logic...
|
539
549
|
end
|
540
550
|
|
541
|
-
def messages
|
551
|
+
def messages(name:)
|
542
552
|
[
|
543
553
|
ActiveMcp::Message::Text.new(
|
544
554
|
role: "user",
|
545
|
-
text: "
|
555
|
+
text: "#{@greeting} #{name}"
|
546
556
|
),
|
547
557
|
ActiveMcp::Message::Image.new(
|
548
558
|
role: "assistant",
|
@@ -565,7 +575,41 @@ end
|
|
565
575
|
|
566
576
|
```ruby
|
567
577
|
class MySchema < ActiveMcp::Schema::Base
|
568
|
-
|
578
|
+
def prompts
|
579
|
+
[
|
580
|
+
HelloPrompt.new(greeting: "Hello!")
|
581
|
+
]
|
582
|
+
end
|
583
|
+
end
|
584
|
+
```
|
585
|
+
|
586
|
+
## 📥 Using Context in the Schema
|
587
|
+
|
588
|
+
```ruby
|
589
|
+
class MySchema < ActiveMcp::Schema::Base
|
590
|
+
def prompts
|
591
|
+
user = User.find_by_token(context[:auth_info][:token])
|
592
|
+
|
593
|
+
user.greetings.map do |greeting|
|
594
|
+
GreetingPrompt.new(greeting: greeting)
|
595
|
+
end
|
596
|
+
end
|
597
|
+
end
|
598
|
+
```
|
599
|
+
|
600
|
+
```ruby
|
601
|
+
class GreetingPrompt < ActiveMcp::Prompt::Base
|
602
|
+
def initialize(greeting:)
|
603
|
+
@greeting = greeting
|
604
|
+
end
|
605
|
+
|
606
|
+
def prompt_name
|
607
|
+
"greeting_#{@greeting.text}"
|
608
|
+
end
|
609
|
+
|
610
|
+
def messages
|
611
|
+
# ...
|
612
|
+
end
|
569
613
|
end
|
570
614
|
```
|
571
615
|
|
@@ -624,7 +668,7 @@ end
|
|
624
668
|
|
625
669
|
## 🧪 Development
|
626
670
|
|
627
|
-
After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rake` to run the tests.
|
671
|
+
After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rake` to run the tests.
|
628
672
|
|
629
673
|
## 👥 Contributing
|
630
674
|
|
@@ -21,11 +21,11 @@ module ActiveMcp
|
|
21
21
|
when Method::CANCELLED
|
22
22
|
render "active_mcp/cancelled", formats: :json
|
23
23
|
when Method::RESOURCES_LIST
|
24
|
-
@resources = schema.
|
24
|
+
@resources = schema.visible_resources
|
25
25
|
@format = :jsonrpc
|
26
26
|
render "active_mcp/resources_list", formats: :json
|
27
27
|
when Method::RESOURCES_TEMPLATES_LIST
|
28
|
-
@resource_templates = schema.
|
28
|
+
@resource_templates = schema.visible_resource_templates
|
29
29
|
@format = :jsonrpc
|
30
30
|
render "active_mcp/resource_templates_list", formats: :json
|
31
31
|
when Method::RESOURCES_READ
|
@@ -33,7 +33,7 @@ module ActiveMcp
|
|
33
33
|
@format = :jsonrpc
|
34
34
|
render "active_mcp/resources_read", formats: :json
|
35
35
|
when Method::TOOLS_LIST
|
36
|
-
@tools = schema.
|
36
|
+
@tools = schema.visible_tools
|
37
37
|
@format = :jsonrpc
|
38
38
|
render "active_mcp/tools_list", formats: :json
|
39
39
|
when Method::TOOLS_CALL
|
@@ -42,15 +42,15 @@ module ActiveMcp
|
|
42
42
|
render "active_mcp/tools_call", formats: :json
|
43
43
|
when Method::COMPLETION_COMPLETE
|
44
44
|
type = params.dig(:params, :ref, :type)
|
45
|
-
@completion = ActiveMcp::Completion.new.complete(params: params[:params], context:, refs: (type === "ref/resource") ? schema.
|
45
|
+
@completion = ActiveMcp::Completion.new.complete(params: params[:params], context:, refs: (type === "ref/resource") ? schema.visible_resource_templates : schema.visible_prompts)
|
46
46
|
@format = :jsonrpc
|
47
47
|
render "active_mcp/completion_complete", formats: :json
|
48
48
|
when Method::PROMPTS_LIST
|
49
|
-
@prompts = schema.
|
49
|
+
@prompts = schema.visible_prompts
|
50
50
|
@format = :jsonrpc
|
51
51
|
render "active_mcp/prompts_list", formats: :json
|
52
52
|
when Method::PROMPTS_GET
|
53
|
-
@prompt = schema.
|
53
|
+
@prompt = schema.visible_prompts.find { _1.prompt_name == params[:params][:name] }
|
54
54
|
@format = :jsonrpc
|
55
55
|
render "active_mcp/prompts_get", formats: :json
|
56
56
|
else
|
@@ -62,7 +62,7 @@ module ActiveMcp
|
|
62
62
|
def handle_mcp_server_request
|
63
63
|
case params[:method]
|
64
64
|
when Method::RESOURCES_LIST
|
65
|
-
@resources = schema.
|
65
|
+
@resources = schema.visible_resources
|
66
66
|
@format = :json
|
67
67
|
render "active_mcp/resources_list", formats: :json
|
68
68
|
when Method::RESOURCES_READ
|
@@ -70,11 +70,11 @@ module ActiveMcp
|
|
70
70
|
@format = :json
|
71
71
|
render "active_mcp/resources_read", formats: :json
|
72
72
|
when Method::RESOURCES_TEMPLATES_LIST
|
73
|
-
@resource_templates = schema.
|
73
|
+
@resource_templates = schema.visible_resource_templates
|
74
74
|
@format = :json
|
75
75
|
render "active_mcp/resource_templates_list", formats: :json
|
76
76
|
when Method::TOOLS_LIST
|
77
|
-
@tools = schema.
|
77
|
+
@tools = schema.visible_tools
|
78
78
|
@format = :json
|
79
79
|
render "active_mcp/tools_list", formats: :json
|
80
80
|
when Method::TOOLS_CALL
|
@@ -83,15 +83,15 @@ module ActiveMcp
|
|
83
83
|
render "active_mcp/tools_call", formats: :json
|
84
84
|
when Method::COMPLETION_COMPLETE
|
85
85
|
type = params.dig(:params, :ref, :type)
|
86
|
-
@completion = ActiveMcp::Completion.new.complete(params: params[:params], context:, refs: (type == "ref/resource") ? schema.
|
86
|
+
@completion = ActiveMcp::Completion.new.complete(params: params[:params], context:, refs: (type == "ref/resource") ? schema.visible_resource_templates : schema.visible_prompts)
|
87
87
|
@format = :json
|
88
88
|
render "active_mcp/completion_complete", formats: :json
|
89
89
|
when Method::PROMPTS_LIST
|
90
|
-
@prompts = schema.
|
90
|
+
@prompts = schema.visible_prompts
|
91
91
|
@format = :json
|
92
92
|
render "active_mcp/prompts_list", formats: :json
|
93
93
|
when Method::PROMPTS_GET
|
94
|
-
@prompt = schema.
|
94
|
+
@prompt = schema.visible_prompts&.find { _1.prompt_name == params[:params][:name] }
|
95
95
|
@format = :json
|
96
96
|
render "active_mcp/prompts_get", formats: :json
|
97
97
|
else
|
@@ -2,6 +2,6 @@ json.jsonrpc ActiveMcp::JSON_RPC_VERSION if @format == :jsonrpc
|
|
2
2
|
json.id @id if @format == :jsonrpc && @id.present?
|
3
3
|
|
4
4
|
json.result do
|
5
|
-
json.description @prompt.
|
6
|
-
json.messages @prompt.messages.map(&:to_h)
|
5
|
+
json.description @prompt.description
|
6
|
+
json.messages @prompt.messages(**params[:params][:arguments].permit!.to_h.symbolize_keys).map(&:to_h)
|
7
7
|
end
|
@@ -6,7 +6,7 @@ json.result do
|
|
6
6
|
json.array!(@prompts) do |prompt|
|
7
7
|
json.name prompt.prompt_name
|
8
8
|
json.description prompt.description
|
9
|
-
json.arguments prompt.arguments.map { _1.except(:complete) }
|
9
|
+
json.arguments prompt.class.arguments.map { _1.except(:complete) }
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
@@ -1,54 +1,34 @@
|
|
1
1
|
module ActiveMcp
|
2
2
|
module Schema
|
3
3
|
class Base
|
4
|
-
|
5
|
-
attr_reader :resources, :resource_templates, :tools, :prompts
|
6
|
-
|
7
|
-
def resource(klass)
|
8
|
-
@resources ||= []
|
9
|
-
@resources << klass
|
10
|
-
|
11
|
-
if klass.class.respond_to?(:uri_template)
|
12
|
-
@resource_templates ||= []
|
13
|
-
@resource_templates << klass.class unless klass.class.in?(@resource_templates)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def tool(klass)
|
18
|
-
@tools ||= []
|
19
|
-
@tools << klass
|
20
|
-
end
|
21
|
-
|
22
|
-
def prompt(klass)
|
23
|
-
@prompts ||= []
|
24
|
-
@prompts << klass
|
25
|
-
end
|
26
|
-
end
|
4
|
+
attr_reader :context
|
27
5
|
|
28
6
|
def initialize(context: {})
|
29
7
|
@context = context
|
30
8
|
end
|
31
9
|
|
32
|
-
def
|
33
|
-
|
34
|
-
!resource.respond_to?(:visible?) || resource.visible?(context: @context)
|
10
|
+
def visible_resources
|
11
|
+
resources&.filter do |resource|
|
12
|
+
!resource.class.respond_to?(:uri_template) && !resource.respond_to?(:visible?) || resource.visible?(context: @context)
|
35
13
|
end
|
36
14
|
end
|
37
15
|
|
38
|
-
def
|
39
|
-
|
40
|
-
!
|
16
|
+
def visible_resource_templates
|
17
|
+
resource_instances = resources&.filter do |resource|
|
18
|
+
resource.class.respond_to?(:uri_template) && (!resource.respond_to?(:visible?) || resource.visible?(context: @context))
|
41
19
|
end
|
20
|
+
|
21
|
+
resource_instances.map(&:class)
|
42
22
|
end
|
43
23
|
|
44
|
-
def
|
45
|
-
|
24
|
+
def visible_tools
|
25
|
+
tools&.filter do |tool|
|
46
26
|
!tool.respond_to?(:visible?) || tool.visible?(context: @context)
|
47
27
|
end
|
48
28
|
end
|
49
29
|
|
50
|
-
def
|
51
|
-
|
30
|
+
def visible_prompts
|
31
|
+
prompts&.filter do |resource|
|
52
32
|
!resource.respond_to?(:visible?) || resource.visible?(context: @context)
|
53
33
|
end
|
54
34
|
end
|
data/lib/active_mcp/tool/base.rb
CHANGED
@@ -6,7 +6,7 @@ module ActiveMcp
|
|
6
6
|
class << self
|
7
7
|
attr_reader :schema
|
8
8
|
|
9
|
-
def argument(name, type, required: false, description:
|
9
|
+
def argument(name, type, required: false, description: "")
|
10
10
|
@schema ||= default_schema
|
11
11
|
|
12
12
|
@schema["properties"][name.to_s] = {"type" => type.to_s}
|
data/lib/active_mcp/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_mcp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Moeki Kawakami
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-04-
|
11
|
+
date: 2025-04-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|