active_mcp 0.3.3 → 0.3.4

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: 1552f0de026c1ac0790e5fa34924b9263c0ce93ed7b331d174267e2b7dc2f7da
4
- data.tar.gz: 7450308c7d05d625aa5f51e81695afa84039a0994ff055db068794ea8c9274fa
3
+ metadata.gz: 8c67705b1452f86930b120080e883a10cdb4d231bf2a8a533621adb55b3099c6
4
+ data.tar.gz: 62d30c1eba1c08c25b6c0e8a74337e3c735a3d14fe85c8f81a19505a0db248cd
5
5
  SHA512:
6
- metadata.gz: 392a25227f1dc6d0d073821dec6f6e0b17fa3fcdf1c2bb55793b4d71eab2422c173abb21a70820134d119e785f0991fe2e68cfa85aee987ef5f542cd966b5b49
7
- data.tar.gz: fb1af057ea0a2f4a0dcdb664c2228013a8f058f13850be2de19be1516e26febbb9df06d51c9d74ec6262d2aa1a14f283067390ee579b5a93c96c1a736b2b15fa
6
+ metadata.gz: ce288f544ec46e5a5c44da9651c344fa985e17b730dbd463230cb86143f1bb8ddd2992c64219ee3eacb9dc3264820ae1b92d4c751938dd01dcac5ac3b2a6d2e0
7
+ data.tar.gz: 9709e2ce17bbe2f86fd3e15a3ecee1748a7708b69b5ecf79843cd94a342fabb1009563d2651d0d8377f0335c663e9d9e26b3017587d33d1fdb3ab7e580577aca
@@ -2,72 +2,6 @@
2
2
 
3
3
  module ActiveMcp
4
4
  class BaseController < ActionController::Base
5
- protect_from_forgery with: :null_session
6
- skip_before_action :verify_authenticity_token
7
- before_action :authenticate, only: [:index]
8
-
9
- def index
10
- if params[:jsonrpc]
11
- process_request_from_mcp_client
12
- else
13
- process_request_from_mcp_server
14
- end
15
- end
16
-
17
- private
18
-
19
- def process_request_from_mcp_server
20
- case params[:method]
21
- when Method::TOOLS_LIST
22
- result = Response::ToolsList::Json.call(
23
- tools: Response::Tools.to_hash(auth_info: @auth_info)
24
- )
25
- when Method::TOOLS_CALL
26
- result = Response::ToolsCall::Json.call(params:, auth_info: @auth_info)
27
- else
28
- result = Response::NoMethod.call
29
- end
30
-
31
- render json: result, status: 200
32
- end
33
-
34
- def process_request_from_mcp_client
35
- case params[:method]
36
- when Method::INITIALIZE
37
- result = Response::Initialize.call(id: params[:id])
38
- when Method::INITIALIZED
39
- result = Response::Initialized.call
40
- when Method::CANCELLED
41
- result = Response::Cancelled.call
42
- when Method::TOOLS_LIST
43
- result = Response::ToolsList::Jsonrpc.call(
44
- id: params[:id],
45
- tools: Response::Tools.to_hash(auth_info: @auth_info)
46
- )
47
- when Method::TOOLS_CALL
48
- result = Response::ToolsCall::Jsonrpc.call(id: params[:id], params:, auth_info: @auth_info)
49
- else
50
- result = Response::NoMethod.call
51
- end
52
-
53
- render json: result, status: 200
54
- end
55
-
56
- def authenticate
57
- auth_header = request.headers["Authorization"]
58
- if auth_header.present?
59
- @auth_info = {
60
- header: auth_header,
61
- type: if auth_header.start_with?("Bearer ")
62
- :bearer
63
- elsif auth_header.start_with?("Basic ")
64
- :basic
65
- else
66
- :unknown
67
- end,
68
- token: auth_header.split(" ").last
69
- }
70
- end
71
- end
5
+ include RequestHandler
72
6
  end
73
7
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveMcp
4
+ module RequestHandler
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ protect_from_forgery with: :null_session
9
+ skip_before_action :verify_authenticity_token
10
+ before_action :authenticate, only: [:index]
11
+ end
12
+
13
+ def index
14
+ if json_rpc_request?
15
+ handle_mcp_client_request
16
+ else
17
+ handle_mcp_server_request
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def json_rpc_request?
24
+ params[:jsonrpc].present?
25
+ end
26
+
27
+ def handle_mcp_client_request
28
+ @id = params[:id]
29
+ @auth_info = auth_info
30
+
31
+ case params[:method]
32
+ when Method::INITIALIZE
33
+ render 'active_mcp/initialize', formats: :json
34
+ when Method::INITIALIZED
35
+ render 'active_mcp/initialized', formats: :json
36
+ when Method::CANCELLED
37
+ render 'active_mcp/cancelled', formats: :json
38
+ when Method::TOOLS_LIST
39
+ @tools = ActiveMcp::Tool.authorized_tools(auth_info)
40
+ @format = :jsonrpc
41
+ render 'active_mcp/tools_list', formats: :json
42
+ when Method::TOOLS_CALL
43
+ @tool_result = ActiveMcp::ToolExecutor.execute(params: params, auth_info: auth_info)
44
+ @format = :jsonrpc
45
+ render 'active_mcp/tools_call', formats: :json
46
+ else
47
+ @format = :jsonrpc
48
+ render 'active_mcp/no_method', formats: :json
49
+ end
50
+ end
51
+
52
+ def handle_mcp_server_request
53
+ @auth_info = auth_info
54
+
55
+ case params[:method]
56
+ when Method::TOOLS_LIST
57
+ @tools = ActiveMcp::Tool.authorized_tools(auth_info)
58
+ @format = :json
59
+ render 'active_mcp/tools_list', formats: :json
60
+ when Method::TOOLS_CALL
61
+ @tool_result = ActiveMcp::ToolExecutor.execute(params: params, auth_info: auth_info)
62
+ @format = :json
63
+ render 'active_mcp/tools_call', formats: :json
64
+ else
65
+ @format = :json
66
+ render 'active_mcp/no_method', formats: :json
67
+ end
68
+ end
69
+
70
+ def authenticate
71
+ auth_header = request.headers["Authorization"]
72
+ if auth_header.present?
73
+ @auth_info = {
74
+ header: auth_header,
75
+ type: if auth_header.start_with?("Bearer ")
76
+ :bearer
77
+ elsif auth_header.start_with?("Basic ")
78
+ :basic
79
+ else
80
+ :unknown
81
+ end,
82
+ token: auth_header.split(" ").last
83
+ }
84
+ end
85
+ end
86
+
87
+ def auth_info
88
+ @auth_info
89
+ end
90
+ end
91
+ end
@@ -1,8 +1,14 @@
1
1
  module ActiveMcp
2
- module ToolExecutor
3
- def self.call(params:, auth_info:)
4
- tool_name = params[:name]
5
-
2
+ class ToolExecutor
3
+ def self.execute(params:, auth_info:)
4
+ if params[:jsonrpc].present?
5
+ tool_name = params[:params][:name]
6
+ tool_params = params[:params][:arguments]
7
+ else
8
+ tool_name = params[:name]
9
+ tool_params = params[:arguments]
10
+ end
11
+
6
12
  unless tool_name
7
13
  return {
8
14
  isError: true,
@@ -18,7 +24,7 @@ module ActiveMcp
18
24
  tool_class = Tool.registered_tools.find do |tc|
19
25
  tc.tool_name == tool_name
20
26
  end
21
-
27
+
22
28
  unless tool_class
23
29
  return {
24
30
  isError: true,
@@ -30,7 +36,7 @@ module ActiveMcp
30
36
  ]
31
37
  }
32
38
  end
33
-
39
+
34
40
  unless tool_class.visible?(auth_info)
35
41
  return {
36
42
  isError: true,
@@ -43,12 +49,16 @@ module ActiveMcp
43
49
  }
44
50
  end
45
51
 
46
- arguments = params[:arguments].permit!.to_hash.symbolize_keys.transform_values do |value|
47
- if !value.is_a?(String)
48
- value
49
- else
50
- value.match(/^\d+$/) ? value.to_i : value
52
+ if tool_params
53
+ arguments = tool_params.permit!.to_hash.symbolize_keys.transform_values do |value|
54
+ if !value.is_a?(String)
55
+ value
56
+ else
57
+ value.match(/^\d+$/) ? value.to_i : value
58
+ end
51
59
  end
60
+ else
61
+ arguments = {}
52
62
  end
53
63
 
54
64
  tool = tool_class.new
@@ -65,10 +75,11 @@ module ActiveMcp
65
75
  ]
66
76
  }
67
77
  end
68
-
78
+
79
+ # Execute the tool
69
80
  begin
70
81
  arguments[:auth_info] = auth_info if auth_info.present?
71
-
82
+
72
83
  return {
73
84
  content: [
74
85
  {
@@ -89,7 +100,7 @@ module ActiveMcp
89
100
  }
90
101
  end
91
102
  end
92
-
103
+
93
104
  def self.formatted(object)
94
105
  case object
95
106
  when String
@@ -0,0 +1,2 @@
1
+ json.jsonrpc ActiveMcp::JSON_RPC_VERSION
2
+ json.result true
@@ -0,0 +1,21 @@
1
+ json.jsonrpc ActiveMcp::JSON_RPC_VERSION
2
+ json.id @id
3
+ json.result do
4
+ json.protocolVersion ActiveMcp::PROTOCOL_VERSION
5
+ json.capabilities do
6
+ json.logging Hash.new
7
+ json.capabilities do
8
+ json.resources do
9
+ json.subscribe false
10
+ json.listChanged false
11
+ end
12
+ json.tools do
13
+ json.listChanged false
14
+ end
15
+ end
16
+ end
17
+ json.serverInfo do
18
+ json.name ActiveMcp.config.respond_to?(:server_name) ? ActiveMcp.config.server_name : "Active MCP Server"
19
+ json.version ActiveMcp.config.respond_to?(:server_version) ? ActiveMcp.config.server_version : ActiveMcp::VERSION
20
+ end
21
+ end
@@ -0,0 +1,2 @@
1
+ json.jsonrpc ActiveMcp::JSON_RPC_VERSION
2
+ json.method ActiveMcp::Method::INITIALIZED
@@ -0,0 +1,4 @@
1
+ json.jsonrpc ActiveMcp::JSON_RPC_VERSION if @format == :jsonrpc
2
+ json.id @id if @format == :jsonrpc && @id.present?
3
+
4
+ json.error "Method not found"
@@ -0,0 +1,9 @@
1
+ json.jsonrpc ActiveMcp::JSON_RPC_VERSION if @format == :jsonrpc
2
+ json.id @id if @format == :jsonrpc && @id.present?
3
+
4
+ if @format == :jsonrpc
5
+ json.result @tool_result
6
+ else
7
+ json.isError @tool_result[:isError] if @tool_result[:isError]
8
+ json.content @tool_result[:content]
9
+ end
@@ -0,0 +1,10 @@
1
+ json.jsonrpc ActiveMcp::JSON_RPC_VERSION if @format == :jsonrpc
2
+ json.id @id if @format == :jsonrpc && @id.present?
3
+
4
+ if @format == :jsonrpc
5
+ json.result do
6
+ json.tools @tools
7
+ end
8
+ else
9
+ json.result @tools
10
+ end
@@ -10,5 +10,12 @@ module ActiveMcp
10
10
  end
11
11
  end
12
12
  end
13
+
14
+ initializer "active_mcp.configure_jbuilder" do |app|
15
+ if Rails.env.development?
16
+ Jbuilder.key_format camelize: :lower
17
+ Jbuilder.prettify if Jbuilder.respond_to?(:prettify)
18
+ end
19
+ end
13
20
  end
14
21
  end
@@ -40,7 +40,23 @@ module ActiveMcp
40
40
  end
41
41
 
42
42
  def visible?(auth_info)
43
- true
43
+ if respond_to?(:authorized?)
44
+ authorized?(auth_info)
45
+ else
46
+ true
47
+ end
48
+ end
49
+
50
+ def authorized_tools(auth_info = nil)
51
+ registered_tools.select do |tool_class|
52
+ tool_class.visible?(auth_info)
53
+ end.map do |tool_class|
54
+ {
55
+ name: tool_class.tool_name,
56
+ description: tool_class.desc,
57
+ inputSchema: tool_class.schema
58
+ }
59
+ end
44
60
  end
45
61
  end
46
62
 
@@ -1,3 +1,3 @@
1
1
  module ActiveMcp
2
- VERSION = "0.3.3"
2
+ VERSION = "0.3.4"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Moeki Kawakami
@@ -43,6 +43,20 @@ dependencies:
43
43
  - - ">="
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: jbuilder
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '2.7'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '2.7'
46
60
  description: A Rails engine that provides MCP capabilities to your Rails application
47
61
  email:
48
62
  - hi@moeki.org
@@ -54,16 +68,14 @@ files:
54
68
  - README.md
55
69
  - Rakefile
56
70
  - app/controllers/active_mcp/base_controller.rb
57
- - app/models/active_mcp/response/cancelled.rb
58
- - app/models/active_mcp/response/initialize.rb
59
- - app/models/active_mcp/response/initialized.rb
60
- - app/models/active_mcp/response/no_method.rb
61
- - app/models/active_mcp/response/tools.rb
62
- - app/models/active_mcp/response/tools_call/json.rb
63
- - app/models/active_mcp/response/tools_call/jsonrpc.rb
64
- - app/models/active_mcp/response/tools_list/json.rb
65
- - app/models/active_mcp/response/tools_list/jsonrpc.rb
71
+ - app/controllers/concerns/active_mcp/request_handler.rb
66
72
  - app/models/active_mcp/tool_executor.rb
73
+ - app/views/active_mcp/cancelled.json.jbuilder
74
+ - app/views/active_mcp/initialize.json.jbuilder
75
+ - app/views/active_mcp/initialized.json.jbuilder
76
+ - app/views/active_mcp/no_method.json.jbuilder
77
+ - app/views/active_mcp/tools_call.json.jbuilder
78
+ - app/views/active_mcp/tools_list.json.jbuilder
67
79
  - config/routes.rb
68
80
  - lib/active_mcp.rb
69
81
  - lib/active_mcp/configuration.rb
@@ -1,12 +0,0 @@
1
- module ActiveMcp
2
- module Response
3
- class Cancelled
4
- def self.call
5
- {
6
- jsonrpc: JSON_RPC_VERSION,
7
- method: Method::CANCELLED
8
- }
9
- end
10
- end
11
- end
12
- end
@@ -1,31 +0,0 @@
1
- module ActiveMcp
2
- module Response
3
- class Initialize
4
- def self.call(id:)
5
- {
6
- jsonrpc: JSON_RPC_VERSION,
7
- id:,
8
- result: {
9
- protocolVersion: PROTOCOL_VERSION,
10
- capabilities: {
11
- logging: {},
12
- capabilities: {
13
- resources: {
14
- subscribe: false,
15
- listChanged: false
16
- },
17
- tools: {
18
- listChanged: false
19
- }
20
- },
21
- },
22
- serverInfo: {
23
- name: ActiveMcp.config.server_name,
24
- version: ActiveMcp.config.server_version
25
- }
26
- }
27
- }
28
- end
29
- end
30
- end
31
- end
@@ -1,12 +0,0 @@
1
- module ActiveMcp
2
- module Response
3
- class Initialized
4
- def self.call
5
- {
6
- jsonrpc: JSON_RPC_VERSION,
7
- method: Method::INITIALIZED
8
- }
9
- end
10
- end
11
- end
12
- end
@@ -1,11 +0,0 @@
1
- module ActiveMcp
2
- module Response
3
- class NoMethod
4
- def self.call
5
- {
6
- error: "Method not found"
7
- }
8
- end
9
- end
10
- end
11
- end
@@ -1,17 +0,0 @@
1
- module ActiveMcp
2
- module Response
3
- class Tools
4
- def self.to_hash(auth_info:)
5
- Tool.registered_tools.select do |tool_class|
6
- tool_class.visible?(auth_info)
7
- end.map do |tool_class|
8
- {
9
- name: tool_class.tool_name,
10
- description: tool_class.desc,
11
- inputSchema: tool_class.schema
12
- }
13
- end
14
- end
15
- end
16
- end
17
- end
@@ -1,11 +0,0 @@
1
- module ActiveMcp
2
- module Response
3
- module ToolsCall
4
- class Json
5
- def self.call(params:, auth_info:)
6
- ActiveMcp::ToolExecutor.call(params:, auth_info:)
7
- end
8
- end
9
- end
10
- end
11
- end
@@ -1,16 +0,0 @@
1
- module ActiveMcp
2
- module Response
3
- module ToolsCall
4
- class Jsonrpc
5
- def self.call(id:, params:, auth_info:)
6
- result = ActiveMcp::ToolExecutor.call(params: params[:params], auth_info:)
7
- {
8
- jsonrpc: JSON_RPC_VERSION,
9
- id:,
10
- result:
11
- }
12
- end
13
- end
14
- end
15
- end
16
- end
@@ -1,13 +0,0 @@
1
- module ActiveMcp
2
- module Response
3
- module ToolsList
4
- class Json
5
- def self.call(tools:)
6
- {
7
- result: tools
8
- }
9
- end
10
- end
11
- end
12
- end
13
- end
@@ -1,15 +0,0 @@
1
- module ActiveMcp
2
- module Response
3
- module ToolsList
4
- class Jsonrpc
5
- def self.call(id:, tools:)
6
- {
7
- jsonrpc: JSON_RPC_VERSION,
8
- id:,
9
- result: {tools:}
10
- }
11
- end
12
- end
13
- end
14
- end
15
- end