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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: faf657746457a11ca96f625b591bfbf82a9e2fe0f89e63629fd9402b14b414af
4
- data.tar.gz: 4b457a166d61a70751546a2c30a77d4d650e4ed018f4d2e7225a9fad77a4cbaa
3
+ metadata.gz: b553c506022715ad2f57af006408ab14b08167505efd4aac1fc0bb08ffdde6c4
4
+ data.tar.gz: b6c2818a416c3da6021e04a8852e08820f757a33cba12b4ca88e7d0ad212caa5
5
5
  SHA512:
6
- metadata.gz: e46fa704ad3ec0a901c506c15616f82cf54de6614fd9dba1433cdbe8948e8901c9baa540ed1b0472f76c80eb3547b676cbad9ae8de4e9f17f06a4e5b235c9eff
7
- data.tar.gz: be0fc4c8a010d6be63efd0985ab256c685a1e2325b9eb1d35dd6bd84c021f0b960e6bba38dc57567b0079d346e68559930a3a5b24797b3c6fa02e42896fff7bf
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
- "Create Note"
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
- tool CreateNoteTool.new
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
- "Admin-only tool"
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
- User.all.each do |user|
384
- resource UserResource.new(id: user.id)
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
- User.all.each do |user|
504
- resource UserResource.new(id: user.id)
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
- class << self
520
- def prompt_name
521
- "hello"
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
- def description
525
- "This is a test."
526
- end
535
+ def initialize(greeting:)
536
+ @greeting = greeting
537
+ end
527
538
 
528
- def visible?(context:)
529
- # Your logic...
530
- end
539
+ def prompt_name
540
+ "hello"
531
541
  end
532
542
 
533
- argument :name, ->(value) do
534
- User.all.pluck(:name).filter { _1.match(value) }
543
+ def description
544
+ "This is a test."
535
545
  end
536
546
 
537
- def initialize(name:)
538
- @name = name
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: "Hello! #{@name}"
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
- prompt HelloPrompt
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. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
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.resources
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.resource_templates
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.tools
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.resource_templates : schema.prompts)
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.prompts
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.prompts.find { _1.prompt_name == params[:params][:name] }.new(**params[:params][:arguments].permit!.to_h.symbolize_keys)
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.resources
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.resource_templates
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.tools
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.resource_templates : schema.prompts)
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.prompts
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.prompts&.find { _1.prompt_name == params[:params][:name] }&.new(**params[:params][:arguments].permit!.to_h.symbolize_keys)
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
@@ -18,7 +18,7 @@ module ActiveMcp
18
18
  }
19
19
  end
20
20
 
21
- resource = schema.resources.find do |r|
21
+ resource = schema.visible_resources.find do |r|
22
22
  r.uri == uri
23
23
  end
24
24
 
@@ -25,7 +25,7 @@ module ActiveMcp
25
25
  }
26
26
  end
27
27
 
28
- tool = schema.tools.find do |tc|
28
+ tool = schema.visible_tools.find do |tc|
29
29
  tc.tool_name == tool_name
30
30
  end
31
31
 
@@ -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.class.description
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
@@ -4,7 +4,7 @@ module ActiveMcp
4
4
  class << self
5
5
  attr_reader :arguments
6
6
 
7
- def argument(name, required: false, description: nil, complete: -> {})
7
+ def argument(name, required: false, description: "", complete: -> {})
8
8
  @arguments ||= []
9
9
 
10
10
  @arguments << {
@@ -1,54 +1,34 @@
1
1
  module ActiveMcp
2
2
  module Schema
3
3
  class Base
4
- class << self
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 resources
33
- self.class.resources&.filter do |resource|
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 resource_templates
39
- self.class.resource_templates&.filter do |template|
40
- !template.respond_to?(:visible?) || template.visible?(context: @context)
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 tools
45
- self.class.tools&.filter do |tool|
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 prompts
51
- self.class.prompts&.filter do |resource|
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
@@ -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: nil)
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}
@@ -1,3 +1,3 @@
1
1
  module ActiveMcp
2
- VERSION = "0.9.3"
2
+ VERSION = "0.10.1"
3
3
  end
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.9.3
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-08 00:00:00.000000000 Z
11
+ date: 2025-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails