active_mcp 0.4.0 → 0.5.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: acd47906160af7e22cbfa3fe974288e7d16639898b026ef6288ab91444e799be
4
- data.tar.gz: 0bc89e6d457537dbe75450f1ad9ec525ed37b9168df48f08c10adbbcb2aebc5f
3
+ metadata.gz: 6663bc950db47a5719e93db048f5601acb9dd58b87421fe0eb94e2c78cec2333
4
+ data.tar.gz: ffacf7506cc82ad8fc680dd57f3f74ee50875e8bb7053ff460dc28e59c232cfa
5
5
  SHA512:
6
- metadata.gz: ea4c71409b27eb65027b33fa0e43ddbaf4e3924768625d28788a2d23e9604099fc14eb6de3c962066a260a96bac8415605066ad05c4cc3ff63f31a33a47fbbc3
7
- data.tar.gz: 4960bc1dee62aa05245159e0d53cf618583739e63aecbbdaf581db3cbed6133317a2537ec9f1d12313956c5e00facb24a6f1fefa32899d685061671fe529adfc
6
+ metadata.gz: 6631c864d9b2fbf566f5202eb31c16a4cb0154a0bcac33b23165c365adab03a01b7ee54da8996dd87660c78046457d2baa6c23f1e1e4dd5a57ea6c06601d84ca
7
+ data.tar.gz: b3f85f31d012218cafba456a4ebe9b50de9261686aec9b40d1f48a783e74bf6dba9506e113fb0d4372f8e5f82b6000911ec40016151063759d1b2865b68c7abe
data/README.md CHANGED
@@ -314,21 +314,35 @@ MCP Resources allow you to share data and files with AI assistants. Resources ha
314
314
  Resources are Ruby classes that inherit from `ActiveMcp::Resource`:
315
315
 
316
316
  ```ruby
317
- class UserResource < ActiveMcp::Resource
318
- uri "data://localhost/user"
319
- mime_type "application/json"
320
- description "User profile data"
317
+ class UserResource
318
+ def initialize(id:, auth_info: nil)
319
+ @user = User.find(id)
320
+ @auth_info = auth_info
321
+ end
322
+
323
+ def name
324
+ @user.name
325
+ end
326
+
327
+ def uri
328
+ "data://localhost/users/#{@user.id}"
329
+ end
330
+
331
+ def mime_type
332
+ "application/json"
333
+ end
321
334
 
322
- def text(auth_info: nil)
323
- # Authenticate if needed
324
- user = User.find_by(id: 1)
335
+ def description
336
+ @user.profile
337
+ end
325
338
 
339
+ def text
326
340
  # Return JSON data
327
341
  {
328
- id: user.id,
329
- name: user.name,
330
- email: user.email,
331
- created_at: user.created_at
342
+ id: @user.id,
343
+ name: @user.name,
344
+ email: @user.email,
345
+ created_at: @user.created_at
332
346
  }
333
347
  end
334
348
  end
@@ -341,7 +355,7 @@ Resources can return two types of content:
341
355
  1. **Text Content** - Use the `text` method to return structured data:
342
356
 
343
357
  ```ruby
344
- def text(auth_info: nil)
358
+ def text
345
359
  # Return strings, arrays, hashes, or any JSON-serializable object
346
360
  { items: Product.all.map(&:attributes) }
347
361
  end
@@ -350,12 +364,24 @@ end
350
364
  2. **Binary Content** - Use the `blob` method to return binary files:
351
365
 
352
366
  ```ruby
353
- class ImageResource < ActiveMcp::Resource
354
- uri "data://localhost/image"
355
- mime_type "image/png"
356
- description "Profile image"
367
+ class ImageResource
368
+ def name
369
+ "image"
370
+ end
371
+
372
+ def uri
373
+ "data://localhost/image"
374
+ end
375
+
376
+ def mime_type
377
+ "image/png"
378
+ end
379
+
380
+ def description
381
+ "Profile image"
382
+ end
357
383
 
358
- def blob(auth_info: nil)
384
+ def blob
359
385
  # Return binary file content
360
386
  File.read(Rails.root.join("public", "profile.png"))
361
387
  end
@@ -365,7 +391,7 @@ end
365
391
  Resources can be protected using the same authorization mechanism as tools:
366
392
 
367
393
  ```ruby
368
- def self.visible?(auth_info)
394
+ def visible?
369
395
  return false unless auth_info
370
396
  return false unless auth_info[:type] == :bearer
371
397
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  module ActiveMcp
4
4
  class BaseController < ActionController::Base
5
- include RequestHandler
5
+ include RequestHandlable
6
+ include ResourceReadable
7
+ include ToolExecutable
6
8
  end
7
9
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveMcp
4
- module RequestHandler
4
+ module RequestHandlable
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
@@ -36,11 +36,11 @@ module ActiveMcp
36
36
  when Method::CANCELLED
37
37
  render 'active_mcp/cancelled', formats: :json
38
38
  when Method::RESOURCES_LIST
39
- @resources = ActiveMcp::Resource.authorized_resources(auth_info)
39
+ @resources = resources_list
40
40
  @format = :jsonrpc
41
41
  render 'active_mcp/resources_list', formats: :json
42
42
  when Method::RESOURCES_READ
43
- @resource = ActiveMcp::ResourceReader.read(params:, auth_info:)
43
+ @resource = read_resource(params:, auth_info:)
44
44
  @format = :jsonrpc
45
45
  render 'active_mcp/resources_read', formats: :json
46
46
  when Method::TOOLS_LIST
@@ -48,7 +48,7 @@ module ActiveMcp
48
48
  @format = :jsonrpc
49
49
  render 'active_mcp/tools_list', formats: :json
50
50
  when Method::TOOLS_CALL
51
- @tool_result = ActiveMcp::ToolExecutor.execute(params: params, auth_info: auth_info)
51
+ @tool_result = execute_tool(params: params, auth_info: auth_info)
52
52
  @format = :jsonrpc
53
53
  render 'active_mcp/tools_call', formats: :json
54
54
  else
@@ -62,11 +62,11 @@ module ActiveMcp
62
62
 
63
63
  case params[:method]
64
64
  when Method::RESOURCES_LIST
65
- @resources = ActiveMcp::Resource.authorized_resources(auth_info)
65
+ @resources = resources_list
66
66
  @format = :json
67
67
  render 'active_mcp/resources_list', formats: :json
68
68
  when Method::RESOURCES_READ
69
- @resource = ActiveMcp::ResourceReader.read(params:, auth_info:)
69
+ @resource = read_resource(params:, auth_info:)
70
70
  @format = :json
71
71
  render 'active_mcp/resources_read', formats: :json
72
72
  when Method::TOOLS_LIST
@@ -74,7 +74,7 @@ module ActiveMcp
74
74
  @format = :json
75
75
  render 'active_mcp/tools_list', formats: :json
76
76
  when Method::TOOLS_CALL
77
- @tool_result = ActiveMcp::ToolExecutor.execute(params: params, auth_info: auth_info)
77
+ @tool_result = execute_tool(params: params, auth_info: auth_info)
78
78
  @format = :json
79
79
  render 'active_mcp/tools_call', formats: :json
80
80
  else
@@ -1,12 +1,20 @@
1
1
  module ActiveMcp
2
- class ResourceReader
3
- def self.read(params:, auth_info:)
2
+ module ResourceReadable
3
+ extend ActiveSupport::Concern
4
+
5
+ private
6
+
7
+ def resources_list
8
+ []
9
+ end
10
+
11
+ def read_resource(params:, auth_info:)
4
12
  if params[:jsonrpc].present?
5
13
  uri = params[:params][:uri]
6
14
  else
7
15
  uri = params[:uri]
8
16
  end
9
-
17
+
10
18
  unless uri
11
19
  return {
12
20
  isError: true,
@@ -14,43 +22,41 @@ module ActiveMcp
14
22
  }
15
23
  end
16
24
 
17
- resource_class = Resource.registered_resources.find do |r|
18
- r._uri == uri
25
+ resource = resources_list.find do |r|
26
+ r.uri == uri
19
27
  end
20
-
21
- unless resource_class
28
+
29
+ unless resource
22
30
  return {
23
31
  isError: true,
24
32
  contents: []
25
33
  }
26
34
  end
27
-
28
- unless resource_class.visible?(auth_info)
35
+
36
+ if resource.respond_to?(:visible?) && !resource.visible?
29
37
  return {
30
38
  isError: true,
31
39
  contents: []
32
40
  }
33
41
  end
34
42
 
35
- resource = resource_class.new
36
-
37
43
  begin
38
- if content = resource.text(auth_info:)
44
+ if resource.respond_to?(:text) && content = resource.text
39
45
  return {
40
46
  contents: [
41
47
  {
42
48
  uri:,
43
- mimeType: resource_class._mime_type,
49
+ mimeType: resource.mime_type,
44
50
  text: formatted(content)
45
51
  }
46
52
  ]
47
53
  }
48
- elsif content = resource.blob(auth_info:)
54
+ elsif content = resource.blob
49
55
  return {
50
56
  contents: [
51
57
  {
52
58
  uri:,
53
- mimeType: resource_class._mime_type,
59
+ mimeType: resource.mime_type,
54
60
  blob: Base64.strict_encode64(content)
55
61
  }
56
62
  ]
@@ -64,7 +70,7 @@ module ActiveMcp
64
70
  end
65
71
  end
66
72
 
67
- def self.formatted(object)
73
+ def formatted(object)
68
74
  case object
69
75
  when String
70
76
  object
@@ -1,6 +1,10 @@
1
1
  module ActiveMcp
2
- class ToolExecutor
3
- def self.execute(params:, auth_info:)
2
+ module ToolExecutable
3
+ extend ActiveSupport::Concern
4
+
5
+ private
6
+
7
+ def execute_tool(params:, auth_info:)
4
8
  if params[:jsonrpc].present?
5
9
  tool_name = params[:params][:name]
6
10
  tool_params = params[:params][:arguments]
@@ -105,7 +109,7 @@ module ActiveMcp
105
109
  end
106
110
  end
107
111
 
108
- def self.formatted(object)
112
+ def formatted(object)
109
113
  case object
110
114
  when String
111
115
  object
@@ -3,8 +3,22 @@ json.id @id if @format == :jsonrpc && @id.present?
3
3
 
4
4
  if @format == :jsonrpc
5
5
  json.result do
6
- json.resources @resources
6
+ json.resources do
7
+ json.array!(@resources) do |resource|
8
+ json.name resource.name
9
+ json.uri resource.uri
10
+ json.mimeType resource.mime_type
11
+ json.description resource.description
12
+ end
13
+ end
7
14
  end
8
15
  else
9
- json.result @resources
16
+ json.result do
17
+ json.array!(@resources) do |resource|
18
+ json.name resource.name
19
+ json.uri resource.uri
20
+ json.mimeType resource.mime_type
21
+ json.description resource.description
22
+ end
23
+ end
10
24
  end
@@ -7,4 +7,4 @@ if @format == :jsonrpc
7
7
  end
8
8
  else
9
9
  json.result @tools
10
- end
10
+ end
@@ -1,3 +1,3 @@
1
1
  module ActiveMcp
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.1"
3
3
  end
data/lib/active_mcp.rb CHANGED
@@ -4,7 +4,6 @@ require "jbuilder"
4
4
  require_relative "active_mcp/version"
5
5
  require_relative "active_mcp/configuration"
6
6
  require_relative "active_mcp/tool"
7
- require_relative "active_mcp/resource"
8
7
  require_relative "active_mcp/server"
9
8
 
10
9
  if defined? ::Rails
@@ -1,37 +1,51 @@
1
- class <%= class_name %> < ActiveMcp::Resource
2
- uri "data://localhost/<%= file_name %>"
3
- mime_type "application/json"
4
- description "<%= file_name.humanize %>"
1
+ class <%= class_name %>
2
+ def initialize(auth_info:)
3
+ @auth_info = auth_info
4
+
5
+ # Authentication information can be accessed via @auth_info parameter
6
+ # @auth_info = { type: :bearer, token: "xxx", header: "Bearer xxx" }
7
+ # or { type: :basic, token: "base64encoded", header: "Basic base64encoded" }
8
+ end
9
+
10
+ def name
11
+ "<%= file_name %>"
12
+ end
13
+
14
+ def uri
15
+ "data://localhost/<%= file_name %>"
16
+ end
17
+
18
+ def mime_type
19
+ "application/json"
20
+ end
21
+
22
+ def description
23
+ "<%= file_name.humanize %>"
24
+ end
5
25
 
6
26
  # Uncomment and modify this method to implement authorization control
7
27
  # This controls who can see and use this tool
8
- # def self.visible?(auth_info)
28
+ # def visible?
9
29
  # # Example: require authentication
10
- # # return false unless auth_info
30
+ # # return false unless @auth_info
11
31
  #
12
32
  # # Example: require a specific authentication type
13
- # # return false unless auth_info[:type] == :bearer
33
+ # # return false unless @auth_info[:type] == :bearer
14
34
  #
15
35
  # # Example: check for admin permissions
16
36
  # # admin_tokens = ["admin-token"]
17
- # # return admin_tokens.include?(auth_info[:token])
37
+ # # return admin_tokens.include?(@auth_info[:token])
18
38
  #
19
39
  # # Default: allow all access
20
40
  # true
21
41
  # end
22
42
 
23
- def text(auth_info: nil)
24
- # Authentication information can be accessed via _auth_info parameter
25
- # auth_info = { type: :bearer, token: "xxx", header: "Bearer xxx" }
26
- # or { type: :basic, token: "base64encoded", header: "Basic base64encoded" }
27
-
28
- # Implement tool logic here
29
-
43
+ def text
30
44
  # Return a string, hash, or any JSON-serializable object
31
45
  { foo: "bar" }
32
46
  end
33
47
 
34
- # def blob(auth_info: nil)
48
+ # def blob
35
49
  # File.read("/path/to/file")
36
50
  # end
37
51
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Moeki Kawakami
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-04-06 00:00:00.000000000 Z
10
+ date: 2025-04-07 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -68,9 +68,9 @@ files:
68
68
  - README.md
69
69
  - Rakefile
70
70
  - app/controllers/active_mcp/base_controller.rb
71
- - app/controllers/concerns/active_mcp/request_handler.rb
72
- - app/models/active_mcp/resource_reader.rb
73
- - app/models/active_mcp/tool_executor.rb
71
+ - app/controllers/concerns/active_mcp/request_handlable.rb
72
+ - app/controllers/concerns/active_mcp/resource_readable.rb
73
+ - app/controllers/concerns/active_mcp/tool_executable.rb
74
74
  - app/views/active_mcp/cancelled.json.jbuilder
75
75
  - app/views/active_mcp/initialize.json.jbuilder
76
76
  - app/views/active_mcp/initialized.json.jbuilder
@@ -83,7 +83,6 @@ files:
83
83
  - lib/active_mcp.rb
84
84
  - lib/active_mcp/configuration.rb
85
85
  - lib/active_mcp/engine.rb
86
- - lib/active_mcp/resource.rb
87
86
  - lib/active_mcp/server.rb
88
87
  - lib/active_mcp/server/error_codes.rb
89
88
  - lib/active_mcp/server/method.rb
@@ -1,64 +0,0 @@
1
- require "json-schema"
2
-
3
- module ActiveMcp
4
- class Resource
5
- class << self
6
- attr_reader :_description, :schema, :_uri, :_mime_type
7
-
8
- def resource_name
9
- name ? name.underscore.sub(/_resource$/, "") : ""
10
- end
11
-
12
- def uri(value)
13
- @_uri = value
14
- end
15
-
16
- def description(value)
17
- @_description = value
18
- end
19
-
20
- def mime_type(value)
21
- @_mime_type = value
22
- end
23
-
24
- def registered_resources
25
- @registered_resources ||= []
26
- end
27
-
28
- attr_writer :registered_resources
29
-
30
- def inherited(subclass)
31
- registered_resources << subclass
32
- end
33
-
34
- def visible?(auth_info)
35
- if respond_to?(:authorized?)
36
- authorized?(auth_info)
37
- else
38
- true
39
- end
40
- end
41
-
42
- def authorized_resources(auth_info = nil)
43
- registered_resources.select do |tool_class|
44
- tool_class.visible?(auth_info)
45
- end.map do |tool_class|
46
- {
47
- uri: tool_class._uri,
48
- name: tool_class.resource_name,
49
- mimeType: tool_class._mime_type,
50
- description: tool_class._description
51
- }
52
- end
53
- end
54
- end
55
-
56
- def text(auth_info:)
57
- nil
58
- end
59
-
60
- def blob(auth_info:)
61
- nil
62
- end
63
- end
64
- end