active_mcp 0.9.1 → 0.9.3

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.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -4
  3. data/app/controllers/concerns/active_mcp/request_handlable.rb +23 -23
  4. data/app/controllers/concerns/active_mcp/resource_readable.rb +8 -8
  5. data/app/controllers/concerns/active_mcp/tool_executable.rb +17 -17
  6. data/app/views/active_mcp/completion_complete.json.jbuilder +2 -9
  7. data/app/views/active_mcp/initialize.json.jbuilder +2 -2
  8. data/app/views/active_mcp/prompts_get.json.jbuilder +1 -6
  9. data/app/views/active_mcp/prompts_list.json.jbuilder +2 -12
  10. data/app/views/active_mcp/resource_templates_list.json.jbuilder +2 -13
  11. data/app/views/active_mcp/resources_list.json.jbuilder +2 -13
  12. data/app/views/active_mcp/resources_read.json.jbuilder +1 -3
  13. data/app/views/active_mcp/tools_call.json.jbuilder +1 -3
  14. data/app/views/active_mcp/tools_list.json.jbuilder +2 -12
  15. data/lib/active_mcp/completion.rb +2 -2
  16. data/lib/active_mcp/message/resource.rb +1 -1
  17. data/lib/active_mcp/prompt/base.rb +1 -1
  18. data/lib/active_mcp/resource/base.rb +1 -1
  19. data/lib/active_mcp/server/fetcher.rb +7 -6
  20. data/lib/active_mcp/server/protocol_handler.rb +35 -90
  21. data/lib/active_mcp/server/stdio_connection.rb +2 -2
  22. data/lib/active_mcp/server.rb +28 -12
  23. data/lib/active_mcp/version.rb +1 -1
  24. data/lib/generators/active_mcp/install/install_generator.rb +1 -1
  25. data/lib/generators/active_mcp/install/templates/initializer.rb +2 -2
  26. data/lib/generators/active_mcp/tool/templates/tool.rb.erb +1 -1
  27. metadata +6 -3
  28. /data/lib/active_mcp/server/{error_codes.rb → error_code.rb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de9c4899c65706f1fb1281e3ac52c7eb5399e4ac6e0224dce35e0ef532779df7
4
- data.tar.gz: ad0abe6c7af1b985260fbea629302fa5bcb69f1ac1c94455ccb187e771664691
3
+ metadata.gz: faf657746457a11ca96f625b591bfbf82a9e2fe0f89e63629fd9402b14b414af
4
+ data.tar.gz: 4b457a166d61a70751546a2c30a77d4d650e4ed018f4d2e7225a9fad77a4cbaa
5
5
  SHA512:
6
- metadata.gz: 8416f28a91cfbc836a78e05b5790b70e0236450f6d901f48df2070dae02095f5e8f436cb39f05dc2a4edd57240c5c8bba99e1a7a32b365c04cac6ec5ed32faaa
7
- data.tar.gz: b3cacc88a0649c82959aaf7fa9b3f39fc0d9b9f3c2c1135ad82282667c92ecda8ce71ddc84d7133694a790fcb6a69caa3c45c48f7196662d4952e9c56d304aba
6
+ metadata.gz: e46fa704ad3ec0a901c506c15616f82cf54de6614fd9dba1433cdbe8948e8901c9baa540ed1b0472f76c80eb3547b676cbad9ae8de4e9f17f06a4e5b235c9eff
7
+ data.tar.gz: be0fc4c8a010d6be63efd0985ab256c685a1e2325b9eb1d35dd6bd84c021f0b960e6bba38dc57567b0079d346e68559930a3a5b24797b3c6fa02e42896fff7bf
data/README.md CHANGED
@@ -344,7 +344,6 @@ Resources are Ruby classes `**Resource`:
344
344
  class UserResource < ActiveMcp::Resource::Base
345
345
  def initialize(id:)
346
346
  @user = User.find(id)
347
- @auth_info = auth_info
348
347
  end
349
348
 
350
349
  def resource_name
@@ -411,7 +410,7 @@ class ImageResource < ActiveMcp::Resource::Base
411
410
  end
412
411
 
413
412
  def resource_name
414
- "Profile Image"
413
+ "profile_image"
415
414
  end
416
415
 
417
416
  def uri
@@ -453,7 +452,7 @@ Resources are Ruby classes `**ResourceTemplates`:
453
452
  class UserResource < ActiveMcp::Resource::Base
454
453
  class << self
455
454
  def resource_template_name
456
- "Users"
455
+ "users"
457
456
  end
458
457
 
459
458
  def uri_template
@@ -593,7 +592,7 @@ end
593
592
  Always validate and sanitize inputs in your tool implementations:
594
593
 
595
594
  ```ruby
596
- def call(user_id:, **args, context: {})
595
+ def call(user_id:, context: {})
597
596
  # Validate input
598
597
  unless user_id.is_a?(Integer) || user_id.to_s.match?(/^\d+$/)
599
598
  raise "Invalid user ID format"
@@ -12,50 +12,50 @@ module ActiveMcp
12
12
 
13
13
  def handle_mcp_client_request
14
14
  @id = params[:id]
15
-
15
+
16
16
  case params[:method]
17
17
  when Method::INITIALIZE
18
- render 'active_mcp/initialize', formats: :json
18
+ render "active_mcp/initialize", formats: :json
19
19
  when Method::INITIALIZED
20
- render 'active_mcp/initialized', formats: :json
20
+ render "active_mcp/initialized", formats: :json
21
21
  when Method::CANCELLED
22
- render 'active_mcp/cancelled', formats: :json
22
+ render "active_mcp/cancelled", formats: :json
23
23
  when Method::RESOURCES_LIST
24
24
  @resources = schema.resources
25
25
  @format = :jsonrpc
26
- render 'active_mcp/resources_list', formats: :json
26
+ render "active_mcp/resources_list", formats: :json
27
27
  when Method::RESOURCES_TEMPLATES_LIST
28
28
  @resource_templates = schema.resource_templates
29
29
  @format = :jsonrpc
30
- render 'active_mcp/resource_templates_list', formats: :json
30
+ render "active_mcp/resource_templates_list", formats: :json
31
31
  when Method::RESOURCES_READ
32
32
  @resource = read_resource(params:, context:)
33
33
  @format = :jsonrpc
34
- render 'active_mcp/resources_read', formats: :json
34
+ render "active_mcp/resources_read", formats: :json
35
35
  when Method::TOOLS_LIST
36
36
  @tools = schema.tools
37
37
  @format = :jsonrpc
38
- render 'active_mcp/tools_list', formats: :json
38
+ render "active_mcp/tools_list", formats: :json
39
39
  when Method::TOOLS_CALL
40
40
  @tool_result = execute_tool(params:, context:)
41
41
  @format = :jsonrpc
42
- render 'active_mcp/tools_call', formats: :json
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.resource_templates : schema.prompts)
46
46
  @format = :jsonrpc
47
47
  render "active_mcp/completion_complete", formats: :json
48
48
  when Method::PROMPTS_LIST
49
49
  @prompts = schema.prompts
50
50
  @format = :jsonrpc
51
- render 'active_mcp/prompts_list', formats: :json
51
+ render "active_mcp/prompts_list", formats: :json
52
52
  when Method::PROMPTS_GET
53
53
  @prompt = schema.prompts.find { _1.prompt_name == params[:params][:name] }.new(**params[:params][:arguments].permit!.to_h.symbolize_keys)
54
54
  @format = :jsonrpc
55
- render 'active_mcp/prompts_get', formats: :json
55
+ render "active_mcp/prompts_get", formats: :json
56
56
  else
57
57
  @format = :jsonrpc
58
- render 'active_mcp/no_method', formats: :json
58
+ render "active_mcp/no_method", formats: :json
59
59
  end
60
60
  end
61
61
 
@@ -64,39 +64,39 @@ module ActiveMcp
64
64
  when Method::RESOURCES_LIST
65
65
  @resources = schema.resources
66
66
  @format = :json
67
- render 'active_mcp/resources_list', formats: :json
67
+ render "active_mcp/resources_list", formats: :json
68
68
  when Method::RESOURCES_READ
69
69
  @resource = read_resource(params:, context:)
70
70
  @format = :json
71
- render 'active_mcp/resources_read', formats: :json
71
+ render "active_mcp/resources_read", formats: :json
72
72
  when Method::RESOURCES_TEMPLATES_LIST
73
73
  @resource_templates = schema.resource_templates
74
74
  @format = :json
75
- render 'active_mcp/resource_templates_list', formats: :json
75
+ render "active_mcp/resource_templates_list", formats: :json
76
76
  when Method::TOOLS_LIST
77
77
  @tools = schema.tools
78
78
  @format = :json
79
- render 'active_mcp/tools_list', formats: :json
79
+ render "active_mcp/tools_list", formats: :json
80
80
  when Method::TOOLS_CALL
81
81
  @tool_result = execute_tool(params:, context:)
82
82
  @format = :json
83
- render 'active_mcp/tools_call', formats: :json
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.resource_templates : schema.prompts)
87
87
  @format = :json
88
88
  render "active_mcp/completion_complete", formats: :json
89
89
  when Method::PROMPTS_LIST
90
90
  @prompts = schema.prompts
91
91
  @format = :json
92
- render 'active_mcp/prompts_list', formats: :json
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.prompts&.find { _1.prompt_name == params[:params][:name] }&.new(**params[:params][:arguments].permit!.to_h.symbolize_keys)
95
95
  @format = :json
96
- render 'active_mcp/prompts_get', formats: :json
96
+ render "active_mcp/prompts_get", formats: :json
97
97
  else
98
98
  @format = :json
99
- render 'active_mcp/no_method', formats: :json
99
+ render "active_mcp/no_method", formats: :json
100
100
  end
101
101
  end
102
102
 
@@ -5,10 +5,10 @@ module ActiveMcp
5
5
  private
6
6
 
7
7
  def read_resource(params:, context:)
8
- if params[:jsonrpc].present?
9
- uri = params[:params][:uri]
8
+ uri = if params[:jsonrpc].present?
9
+ params[:params][:uri]
10
10
  else
11
- uri = params[:uri]
11
+ params[:uri]
12
12
  end
13
13
 
14
14
  unless uri
@@ -37,8 +37,8 @@ module ActiveMcp
37
37
  end
38
38
 
39
39
  begin
40
- if resource.respond_to?(:text) && content = resource.content
41
- return {
40
+ if resource.respond_to?(:text) && (content = resource.content)
41
+ {
42
42
  contents: [
43
43
  {
44
44
  uri:,
@@ -47,8 +47,8 @@ module ActiveMcp
47
47
  }
48
48
  ]
49
49
  }
50
- elsif content = resource.blob
51
- return {
50
+ elsif (content = resource.blob)
51
+ {
52
52
  contents: [
53
53
  {
54
54
  uri:,
@@ -59,7 +59,7 @@ module ActiveMcp
59
59
  }
60
60
  end
61
61
  rescue
62
- return {
62
+ {
63
63
  isError: true,
64
64
  contents: []
65
65
  }
@@ -12,14 +12,14 @@ module ActiveMcp
12
12
  tool_name = params[:name]
13
13
  tool_params = params[:arguments]
14
14
  end
15
-
15
+
16
16
  unless tool_name
17
17
  return {
18
18
  isError: true,
19
19
  content: [
20
20
  {
21
21
  type: "text",
22
- text: "Invalid params: missing tool name",
22
+ text: "Invalid params: missing tool name"
23
23
  }
24
24
  ]
25
25
  }
@@ -28,44 +28,44 @@ module ActiveMcp
28
28
  tool = schema.tools.find do |tc|
29
29
  tc.tool_name == tool_name
30
30
  end
31
-
31
+
32
32
  unless tool
33
33
  return {
34
34
  isError: true,
35
35
  content: [
36
36
  {
37
37
  type: "text",
38
- text: "Tool not found: #{tool_name}",
38
+ text: "Tool not found: #{tool_name}"
39
39
  }
40
40
  ]
41
41
  }
42
42
  end
43
-
43
+
44
44
  unless tool.visible?(context:)
45
45
  return {
46
46
  isError: true,
47
47
  content: [
48
48
  {
49
49
  type: "text",
50
- text: "Unauthorized: Access to tool '#{tool_name}' denied",
50
+ text: "Unauthorized: Access to tool '#{tool_name}' denied"
51
51
  }
52
52
  ]
53
53
  }
54
54
  end
55
55
 
56
- if tool_params.is_a?(String)
57
- arguments = JSON.parse(tool_params).symbolize_keys
56
+ arguments = if tool_params.is_a?(String)
57
+ JSON.parse(tool_params).symbolize_keys
58
58
  elsif tool_params
59
- arguments = tool_params.permit!.to_hash.symbolize_keys
59
+ tool_params.permit!.to_hash.symbolize_keys
60
60
  else
61
- arguments = {}
61
+ {}
62
62
  end
63
63
 
64
64
  arguments = arguments.transform_values do |value|
65
65
  if !value.is_a?(String)
66
66
  value
67
67
  else
68
- value.match(/^\d+$/) ? value.to_i : value
68
+ /^\d+$/.match?(value) ? value.to_i : value
69
69
  end
70
70
  end
71
71
 
@@ -77,15 +77,15 @@ module ActiveMcp
77
77
  content: [
78
78
  {
79
79
  type: "text",
80
- text: validation_result[:error],
80
+ text: validation_result[:error]
81
81
  }
82
82
  ]
83
83
  }
84
84
  end
85
-
85
+
86
86
  # Execute the tool
87
87
  begin
88
- return {
88
+ {
89
89
  content: [
90
90
  {
91
91
  type: "text",
@@ -94,18 +94,18 @@ module ActiveMcp
94
94
  ]
95
95
  }
96
96
  rescue => e
97
- return {
97
+ {
98
98
  isError: true,
99
99
  content: [
100
100
  {
101
101
  type: "text",
102
- text: "Error: #{e.message}",
102
+ text: "Error: #{e.message}"
103
103
  }
104
104
  ]
105
105
  }
106
106
  end
107
107
  end
108
-
108
+
109
109
  def formatted(object)
110
110
  case object
111
111
  when String
@@ -1,15 +1,8 @@
1
1
  json.jsonrpc ActiveMcp::JSON_RPC_VERSION if @format == :jsonrpc
2
2
  json.id @id if @format == :jsonrpc && @id.present?
3
3
 
4
- if @format == :jsonrpc
5
- json.result do
6
- json.completion do
7
- json.values @completion[:values]
8
- json.total @completion[:total]
9
- end
10
- end
11
- else
12
- json.result do
4
+ json.result do
5
+ json.completion do
13
6
  json.values @completion[:values]
14
7
  json.total @completion[:total]
15
8
  end
@@ -3,7 +3,7 @@ json.id @id
3
3
  json.result do
4
4
  json.protocolVersion ActiveMcp::PROTOCOL_VERSION
5
5
  json.capabilities do
6
- json.logging Hash.new
6
+ json.logging({})
7
7
  json.capabilities do
8
8
  json.resources do
9
9
  json.subscribe false
@@ -18,4 +18,4 @@ json.result do
18
18
  json.name ActiveMcp.config.respond_to?(:server_name) ? ActiveMcp.config.server_name : "Active MCP Server"
19
19
  json.version ActiveMcp.config.respond_to?(:server_version) ? ActiveMcp.config.server_version : ActiveMcp::VERSION
20
20
  end
21
- end
21
+ end
@@ -1,12 +1,7 @@
1
1
  json.jsonrpc ActiveMcp::JSON_RPC_VERSION if @format == :jsonrpc
2
2
  json.id @id if @format == :jsonrpc && @id.present?
3
3
 
4
- if @format == :jsonrpc
5
- json.result do
6
- json.description @prompt.class.description
7
- json.messages @prompt.messages.map(&:to_h)
8
- end
9
- else
4
+ json.result do
10
5
  json.description @prompt.class.description
11
6
  json.messages @prompt.messages.map(&:to_h)
12
7
  end
@@ -1,18 +1,8 @@
1
1
  json.jsonrpc ActiveMcp::JSON_RPC_VERSION if @format == :jsonrpc
2
2
  json.id @id if @format == :jsonrpc && @id.present?
3
3
 
4
- if @format == :jsonrpc
5
- json.result do
6
- json.prompts do
7
- json.array!(@prompts) do |prompt|
8
- json.name prompt.prompt_name
9
- json.description prompt.description
10
- json.arguments prompt.arguments.map { _1.except(:complete) }
11
- end
12
- end
13
- end
14
- else
15
- json.result do
4
+ json.result do
5
+ json.prompts do
16
6
  json.array!(@prompts) do |prompt|
17
7
  json.name prompt.prompt_name
18
8
  json.description prompt.description
@@ -1,19 +1,8 @@
1
1
  json.jsonrpc ActiveMcp::JSON_RPC_VERSION if @format == :jsonrpc
2
2
  json.id @id if @format == :jsonrpc && @id.present?
3
3
 
4
- if @format == :jsonrpc
5
- json.result do
6
- json.resourceTemplates do
7
- json.array!(@resource_templates) do |resource|
8
- json.name resource.resource_template_name
9
- json.uriTemplate resource.uri_template
10
- json.mimeType resource.mime_type
11
- json.description resource.description
12
- end
13
- end
14
- end
15
- else
16
- json.result do
4
+ json.result do
5
+ json.resourceTemplates do
17
6
  json.array!(@resource_templates) do |resource|
18
7
  json.name resource.resource_template_name
19
8
  json.uriTemplate resource.uri_template
@@ -1,19 +1,8 @@
1
1
  json.jsonrpc ActiveMcp::JSON_RPC_VERSION if @format == :jsonrpc
2
2
  json.id @id if @format == :jsonrpc && @id.present?
3
3
 
4
- if @format == :jsonrpc
5
- json.result do
6
- json.resources do
7
- json.array!(@resources) do |resource|
8
- json.name resource.resource_name
9
- json.uri resource.uri
10
- json.mimeType resource.class.mime_type
11
- json.description resource.description
12
- end
13
- end
14
- end
15
- else
16
- json.result do
4
+ json.result do
5
+ json.resources do
17
6
  json.array!(@resources) do |resource|
18
7
  json.name resource.resource_name
19
8
  json.uri resource.uri
@@ -1,9 +1,7 @@
1
1
  json.jsonrpc ActiveMcp::JSON_RPC_VERSION if @format == :jsonrpc
2
2
  json.id @id if @format == :jsonrpc && @id.present?
3
3
 
4
- if @format == :jsonrpc
5
- json.result @resource
6
- else
4
+ json.result do
7
5
  json.isError @resource[:isError] if @resource[:isError]
8
6
  json.contents do
9
7
  json.array!(@resource[:contents]) do |content|
@@ -1,9 +1,7 @@
1
1
  json.jsonrpc ActiveMcp::JSON_RPC_VERSION if @format == :jsonrpc
2
2
  json.id @id if @format == :jsonrpc && @id.present?
3
3
 
4
- if @format == :jsonrpc
5
- json.result @tool_result
6
- else
4
+ json.result do
7
5
  json.isError @tool_result[:isError] if @tool_result[:isError]
8
6
  json.content do
9
7
  json.array!(@tool_result[:content]) do |content|
@@ -1,18 +1,8 @@
1
1
  json.jsonrpc ActiveMcp::JSON_RPC_VERSION if @format == :jsonrpc
2
2
  json.id @id if @format == :jsonrpc && @id.present?
3
3
 
4
- if @format == :jsonrpc
5
- json.result do
6
- json.tools do
7
- json.array!(@tools) do |tool|
8
- json.name tool.tool_name
9
- json.description tool.description
10
- json.inputSchema tool.class.schema
11
- end
12
- end
13
- end
14
- else
15
- json.result do
4
+ json.result do
5
+ json.tools do
16
6
  json.array!(@tools) do |tool|
17
7
  json.name tool.tool_name
18
8
  json.description tool.description
@@ -9,11 +9,11 @@ module ActiveMcp
9
9
  if uri_template
10
10
  resource_class = refs.find { _1.uri_template == uri_template }
11
11
  values = resource_class.arguments[arg_name.to_sym].call(value)
12
- { values:, total: values.length }
12
+ {values:, total: values.length}
13
13
  elsif ref_name
14
14
  prompt_class = refs.find { _1.prompt_name == ref_name }
15
15
  values = prompt_class.arguments.find { _1[:name] == arg_name.to_sym }[:complete].call(value)
16
- { values:, total: values.length }
16
+ {values:, total: values.length}
17
17
  end
18
18
  end
19
19
  end
@@ -14,7 +14,7 @@ module ActiveMcp
14
14
  resource: {
15
15
  uri: @resource.uri,
16
16
  mimeType: @resource.class.mime_type,
17
- text: @resource.content,
17
+ text: @resource.content
18
18
  }
19
19
  }
20
20
  }
@@ -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: nil, complete: -> {})
8
8
  @arguments ||= []
9
9
 
10
10
  @arguments << {
@@ -8,7 +8,7 @@ module ActiveMcp
8
8
 
9
9
  def resource_template_name
10
10
  end
11
-
11
+
12
12
  def description
13
13
  end
14
14
 
@@ -5,7 +5,7 @@ module ActiveMcp
5
5
  @base_uri = base_uri
6
6
 
7
7
  if auth
8
- @auth_header = "#{auth[:type] == :bearer ? "Bearer" : "Basic"} #{auth[:token]}"
8
+ @auth_header = "#{(auth[:type] == :bearer) ? "Bearer" : "Basic"} #{auth[:token]}"
9
9
  end
10
10
  end
11
11
 
@@ -13,20 +13,20 @@ module ActiveMcp
13
13
  return unless @base_uri
14
14
 
15
15
  require "net/http"
16
-
16
+
17
17
  unless @base_uri.is_a?(URI) || @base_uri.is_a?(String)
18
18
  Server.log_error("Invalid URI type", StandardError.new("URI must be a String or URI object"))
19
19
  return
20
20
  end
21
-
21
+
22
22
  begin
23
23
  uri = URI.parse(@base_uri.to_s)
24
-
24
+
25
25
  unless uri.scheme =~ /\Ahttps?\z/ && !uri.host.nil?
26
26
  Server.log_error("Invalid URI", StandardError.new("URI must have a valid scheme and host"))
27
27
  return
28
28
  end
29
-
29
+
30
30
  if defined?(Rails) && Rails.env.production? && uri.scheme != "https"
31
31
  Server.log_error("HTTPS is required in production environment", StandardError.new("Non-HTTPS URI in production"))
32
32
  return
@@ -35,7 +35,7 @@ module ActiveMcp
35
35
  Server.log_error("Invalid URI format", e)
36
36
  return
37
37
  end
38
-
38
+
39
39
  request = Net::HTTP::Post.new(uri)
40
40
  request.body = JSON.generate(params)
41
41
  request["Content-Type"] = "application/json"
@@ -49,6 +49,7 @@ module ActiveMcp
49
49
  JSON.parse(response.body, symbolize_names: true)
50
50
  rescue => e
51
51
  Server.log_error("Error fetching resource_templates", e)
52
+ nil
52
53
  end
53
54
  end
54
55
  end
@@ -15,7 +15,11 @@ module ActiveMcp
15
15
  message = message.to_s.force_encoding("UTF-8")
16
16
  result = begin
17
17
  request = JSON.parse(message, symbolize_names: true)
18
- handle_request(request)
18
+ if !request[:jsonrpc] || !request[:method]
19
+ error_response(nil, ErrorCode::INVALID_REQUEST, "Invalid JSON-RPC format")
20
+ else
21
+ handle_request(request)
22
+ end
19
23
  rescue JSON::ParserError => e
20
24
  Server.log_error("JSON parse error", e)
21
25
  error_response(nil, ErrorCode::PARSE_ERROR, "Invalid JSON format")
@@ -57,6 +61,9 @@ module ActiveMcp
57
61
  else
58
62
  error_response(request[:id], ErrorCode::METHOD_NOT_FOUND, "Unknown method: #{request[:method]}")
59
63
  end
64
+ rescue => e
65
+ Server.log_error("Error #{name}", e)
66
+ error_response(request[:id], ErrorCode::INTERNAL_ERROR, "An error occurred")
60
67
  end
61
68
 
62
69
  def handle_initialize(request)
@@ -84,7 +91,7 @@ module ActiveMcp
84
91
  capabilities: {
85
92
  resources: {},
86
93
  tools: {},
87
- prompts: {},
94
+ prompts: {}
88
95
  },
89
96
  serverInfo: {
90
97
  name: @server.name,
@@ -109,134 +116,72 @@ module ActiveMcp
109
116
  def handle_list_resources(request)
110
117
  success_response(
111
118
  request[:id],
112
- {
113
- resources: @server.fetch(
114
- params: {
115
- method: Method::RESOURCES_LIST,
116
- arguments: {}
117
- }
118
- )[:result]
119
- }
119
+ @server.fetch(
120
+ params: {
121
+ method: Method::RESOURCES_LIST,
122
+ arguments: {}
123
+ }
124
+ )[:result]
120
125
  )
121
126
  end
122
127
 
123
128
  def handle_list_resource_templates(request)
124
129
  success_response(
125
130
  request[:id],
126
- {
127
- resourceTemplates: @server.fetch(
128
- params: {
129
- method: Method::RESOURCES_TEMPLATES_LIST,
130
- arguments: {}
131
- }
132
- )[:result]
133
- }
131
+ @server.fetch(
132
+ params: {
133
+ method: Method::RESOURCES_TEMPLATES_LIST,
134
+ arguments: {}
135
+ }
136
+ )[:result]
134
137
  )
135
138
  end
136
139
 
137
140
  def handle_list_tools(request)
138
141
  success_response(
139
142
  request[:id],
140
- {
141
- tools: @server.fetch(
142
- params: {
143
- method: Method::TOOLS_LIST,
144
- arguments: {}
145
- }
146
- )[:result]
147
- }
143
+ @server.fetch(params: {method: Method::TOOLS_LIST, arguments: {}})[:result]
148
144
  )
149
145
  end
150
146
 
151
147
  def handle_call_tool(request)
152
148
  name = request.dig(:params, :name)
153
149
  arguments = request.dig(:params, :arguments) || {}
150
+ result = @server.fetch(params: {method: Method::TOOLS_CALL, name:, arguments:})
154
151
 
155
- begin
156
- result = @server.fetch(
157
- params: {
158
- method: Method::TOOLS_CALL,
159
- name:,
160
- arguments:,
161
- }
162
- )
163
-
164
- success_response(request[:id], result)
165
- rescue => e
166
- Server.log_error("Error calling tool #{name}", e)
167
- error_response(request[:id], ErrorCode::INTERNAL_ERROR, "An error occurred while calling the tool")
168
- end
152
+ success_response(request[:id], result[:result])
169
153
  end
170
154
 
171
155
  def handle_read_resource(request)
172
156
  uri = request.dig(:params, :uri)
173
- begin
174
- result = @server.fetch(
175
- params: {
176
- method: Method::RESOURCES_READ,
177
- uri:,
178
- arguments: {},
179
- }
180
- )
157
+ result = @server.fetch(params: {method: Method::RESOURCES_READ, uri:, arguments: {}})
181
158
 
182
- success_response(request[:id], result)
183
- rescue => e
184
- Server.log_error("Error reading resource #{uri}", e)
185
- error_response(request[:id], ErrorCode::INTERNAL_ERROR, "An error occurred while reading the resource")
186
- end
159
+ success_response(request[:id], result[:result])
187
160
  end
188
161
 
189
162
  def handle_complete(request)
190
- begin
191
- result = @server.fetch(
192
- params: {
193
- method: Method::COMPLETION_COMPLETE,
194
- params: request[:params],
195
- }
196
- )
197
- success_response(request[:id], { completion: result[:result] })
198
- rescue => e
199
- Server.log_error("Error reading resource #{uri}", e)
200
- error_response(request[:id], ErrorCode::INTERNAL_ERROR, "An error occurred while reading the resource")
201
- end
163
+ result = @server.fetch(params: {method: Method::COMPLETION_COMPLETE, params: request[:params]})
164
+ success_response(request[:id], result[:result])
202
165
  end
203
166
 
204
167
  def handle_list_prompts(request)
205
- begin
206
- result = @server.fetch(params: { method: Method::PROMPTS_LIST })
207
- success_response(request[:id], { prompts: result[:result] })
208
- rescue => e
209
- Server.log_error("Error reading resource #{uri}", e)
210
- error_response(request[:id], ErrorCode::INTERNAL_ERROR, "An error occurred while reading the resource")
211
- end
168
+ result = @server.fetch(params: {method: Method::PROMPTS_LIST})
169
+ success_response(request[:id], result[:result])
212
170
  end
213
171
 
214
172
  def handle_get_prompt(request)
215
173
  name = request.dig(:params, :name)
216
174
  arguments = request.dig(:params, :arguments)
217
- begin
218
- result = @server.fetch(
219
- params: {
220
- method: Method::PROMPTS_GET,
221
- params: {
222
- name:,
223
- arguments:,
224
- }
225
- }
226
- )
175
+ result = @server.fetch(params: {method: Method::PROMPTS_GET, params: {name:, arguments:}})
227
176
 
228
- success_response(request[:id], result)
229
- rescue => e
230
- Server.log_error("Error reading resource #{uri}", e)
231
- error_response(request[:id], ErrorCode::INTERNAL_ERROR, "An error occurred while reading the resource")
232
- end
177
+ success_response(request[:id], result[:result])
233
178
  end
234
179
 
235
180
  def success_response(id, result)
236
181
  {
237
182
  jsonrpc: JSON_RPC_VERSION,
238
183
  id: id,
239
- result: result
184
+ result:
240
185
  }
241
186
  end
242
187
 
@@ -245,8 +190,8 @@ module ActiveMcp
245
190
  jsonrpc: JSON_RPC_VERSION,
246
191
  id: id || 0,
247
192
  error: {
248
- code: code,
249
- message: message
193
+ code:,
194
+ message:
250
195
  }
251
196
  }
252
197
  response[:error][:data] = data if data
@@ -8,11 +8,11 @@ module ActiveMcp
8
8
 
9
9
  def read_next_message
10
10
  message = $stdin.gets&.chomp
11
- message.to_s.force_encoding("UTF-8")
11
+ message.to_s.dup.force_encoding("UTF-8")
12
12
  end
13
13
 
14
14
  def send_message(message)
15
- message = message.to_s.force_encoding("UTF-8")
15
+ message = message.to_s.dup.force_encoding("UTF-8")
16
16
  $stdout.binmode
17
17
  $stdout.write(message + "\n")
18
18
  $stdout.flush
@@ -1,13 +1,40 @@
1
1
  require "json"
2
2
  require "English"
3
3
  require_relative "server/method"
4
- require_relative "server/error_codes"
4
+ require_relative "server/error_code"
5
5
  require_relative "server/stdio_connection"
6
6
  require_relative "server/fetcher"
7
7
  require_relative "server/protocol_handler"
8
8
 
9
9
  module ActiveMcp
10
10
  class Server
11
+ class Logger
12
+ attr_reader :messages
13
+
14
+ def initialize
15
+ @messages = []
16
+ end
17
+
18
+ def log(message, error = nil)
19
+ @messages << {message: message, error: error}
20
+ if defined?(Rails)
21
+ Rails.logger.error("#{message}: #{error&.message}")
22
+ else
23
+ warn("#{message}: #{error&.message}")
24
+ end
25
+ end
26
+ end
27
+
28
+ class << self
29
+ def logger
30
+ @logger ||= Logger.new
31
+ end
32
+
33
+ def log_error(message, error)
34
+ logger.log(message, error)
35
+ end
36
+ end
37
+
11
38
  attr_reader :name, :version, :uri, :protocol_handler, :fetcher
12
39
 
13
40
  def initialize(
@@ -23,17 +50,6 @@ module ActiveMcp
23
50
  @protocol_handler = ProtocolHandler.new(self)
24
51
  end
25
52
 
26
- def self.log_error(message, error)
27
- error_details = "#{message}: #{error.message}\n"
28
- error_details += error.backtrace.join("\n") if error.backtrace
29
-
30
- if defined?(Rails)
31
- Rails.logger.error(error_details)
32
- else
33
- $stderr.puts(error_details)
34
- end
35
- end
36
-
37
53
  def fetch(params:)
38
54
  @fetcher.call(params:)
39
55
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveMcp
2
- VERSION = "0.9.1"
2
+ VERSION = "0.9.3"
3
3
  end
@@ -1,7 +1,7 @@
1
1
  module ActiveMcp
2
2
  module Generators
3
3
  class InstallGenerator < Rails::Generators::Base
4
- source_root File.expand_path('templates', __dir__)
4
+ source_root File.expand_path("templates", __dir__)
5
5
 
6
6
  desc "Creates an Active MCP initializer"
7
7
 
@@ -1,5 +1,5 @@
1
1
  # Active MCP configuration
2
2
  ActiveMcp.configure do |config|
3
- config.server_name = 'MCP Server'
4
- config.server_version = '1.0.0'
3
+ config.server_name = "MCP Server"
4
+ config.server_version = "1.0.0"
5
5
  end
@@ -1,6 +1,6 @@
1
1
  class <%= class_name %> < ActiveMcp::Tool::Base
2
2
  def tool_name
3
- "<%= file_name.humanize %>"
3
+ "<%= file_name %>"
4
4
  end
5
5
 
6
6
  def description
metadata CHANGED
@@ -1,10 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Moeki Kawakami
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
11
  date: 2025-04-08 00:00:00.000000000 Z
@@ -97,7 +98,7 @@ files:
97
98
  - lib/active_mcp/resource/base.rb
98
99
  - lib/active_mcp/schema/base.rb
99
100
  - lib/active_mcp/server.rb
100
- - lib/active_mcp/server/error_codes.rb
101
+ - lib/active_mcp/server/error_code.rb
101
102
  - lib/active_mcp/server/fetcher.rb
102
103
  - lib/active_mcp/server/method.rb
103
104
  - lib/active_mcp/server/protocol_handler.rb
@@ -114,6 +115,7 @@ homepage: https://github.com/moekiorg/active_mcp
114
115
  licenses:
115
116
  - MIT
116
117
  metadata: {}
118
+ post_install_message:
117
119
  rdoc_options: []
118
120
  require_paths:
119
121
  - lib
@@ -128,7 +130,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
130
  - !ruby/object:Gem::Version
129
131
  version: '0'
130
132
  requirements: []
131
- rubygems_version: 3.6.6
133
+ rubygems_version: 3.4.19
134
+ signing_key:
132
135
  specification_version: 4
133
136
  summary: Rails engine for the Model Context Protocol (MCP)
134
137
  test_files: []