active_mcp 0.9.1 → 0.9.2
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 +3 -4
- data/app/controllers/concerns/active_mcp/request_handlable.rb +23 -23
- data/app/controllers/concerns/active_mcp/resource_readable.rb +8 -8
- data/app/controllers/concerns/active_mcp/tool_executable.rb +17 -17
- data/app/views/active_mcp/completion_complete.json.jbuilder +2 -9
- data/app/views/active_mcp/initialize.json.jbuilder +2 -2
- data/app/views/active_mcp/prompts_get.json.jbuilder +1 -6
- data/app/views/active_mcp/prompts_list.json.jbuilder +2 -12
- data/app/views/active_mcp/resource_templates_list.json.jbuilder +2 -13
- data/app/views/active_mcp/resources_list.json.jbuilder +2 -13
- data/app/views/active_mcp/resources_read.json.jbuilder +7 -11
- data/app/views/active_mcp/tools_call.json.jbuilder +1 -3
- data/app/views/active_mcp/tools_list.json.jbuilder +2 -12
- data/lib/active_mcp/completion.rb +2 -2
- data/lib/active_mcp/message/resource.rb +1 -1
- data/lib/active_mcp/prompt/base.rb +1 -1
- data/lib/active_mcp/resource/base.rb +1 -1
- data/lib/active_mcp/server/fetcher.rb +7 -6
- data/lib/active_mcp/server/protocol_handler.rb +35 -90
- data/lib/active_mcp/server/stdio_connection.rb +2 -2
- data/lib/active_mcp/server.rb +28 -12
- data/lib/active_mcp/version.rb +1 -1
- data/lib/generators/active_mcp/install/install_generator.rb +1 -1
- data/lib/generators/active_mcp/install/templates/initializer.rb +2 -2
- data/lib/generators/active_mcp/tool/templates/tool.rb.erb +1 -1
- metadata +6 -3
- /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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a2bdc655ae59a42ab77ecf1767f4a6717be9586100b51d1f6436da37346be92f
|
4
|
+
data.tar.gz: 682c5b2b9ce9d08983172d3cca17ee0a12c98169c081bbf06bb90ad361d8bd73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53e87889ab3a2a9dc1a87a97f9c0181ee9d38d57ce332e70c9096da99df0630b6f16ce2d3e6fff16642216bf1cc509121b0ca6817118dd48e811b1bc62db4a04
|
7
|
+
data.tar.gz: edbff3c0e32089eda22d0c6aa90670f7857b31a253142e3f60517e37c509bb46c7ab488ad6577f3dd991c1d3fb29f21984750d6a83b2465e061ff856be90eed6
|
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
|
-
"
|
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
|
-
"
|
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:,
|
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
|
18
|
+
render "active_mcp/initialize", formats: :json
|
19
19
|
when Method::INITIALIZED
|
20
|
-
render
|
20
|
+
render "active_mcp/initialized", formats: :json
|
21
21
|
when Method::CANCELLED
|
22
|
-
render
|
22
|
+
render "active_mcp/cancelled", formats: :json
|
23
23
|
when Method::RESOURCES_LIST
|
24
24
|
@resources = schema.resources
|
25
25
|
@format = :jsonrpc
|
26
|
-
render
|
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
|
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
|
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
|
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
|
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
|
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
|
55
|
+
render "active_mcp/prompts_get", formats: :json
|
56
56
|
else
|
57
57
|
@format = :jsonrpc
|
58
|
-
render
|
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
|
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
|
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
|
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
|
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
|
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
|
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] }
|
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
|
96
|
+
render "active_mcp/prompts_get", formats: :json
|
97
97
|
else
|
98
98
|
@format = :json
|
99
|
-
render
|
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
|
-
|
8
|
+
uri = if params[:jsonrpc].present?
|
9
|
+
params[:params][:uri]
|
10
10
|
else
|
11
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
56
|
+
arguments = if tool_params.is_a?(String)
|
57
|
+
JSON.parse(tool_params).symbolize_keys
|
58
58
|
elsif tool_params
|
59
|
-
|
59
|
+
tool_params.permit!.to_hash.symbolize_keys
|
60
60
|
else
|
61
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
5
|
-
json.
|
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
|
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
|
-
|
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
|
-
|
5
|
-
json.
|
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
|
-
|
5
|
-
json.
|
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
|
-
|
5
|
-
json.
|
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,16 +1,12 @@
|
|
1
1
|
json.jsonrpc ActiveMcp::JSON_RPC_VERSION if @format == :jsonrpc
|
2
2
|
json.id @id if @format == :jsonrpc && @id.present?
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
json.
|
10
|
-
|
11
|
-
json.mimeType raw content[:mimeType]
|
12
|
-
json.text raw content[:text] if content[:text]
|
13
|
-
json.blob content[:blob] if content[:blob]
|
14
|
-
end
|
4
|
+
json.isError @resource[:isError] if @resource[:isError]
|
5
|
+
json.contents do
|
6
|
+
json.array!(@resource[:contents]) do |content|
|
7
|
+
json.uri content[:uri]
|
8
|
+
json.mimeType raw content[:mimeType]
|
9
|
+
json.text raw content[:text] if content[:text]
|
10
|
+
json.blob content[:blob] if content[:blob]
|
15
11
|
end
|
16
12
|
end
|
@@ -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
|
-
|
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
|
-
|
5
|
-
json.
|
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
|
-
{
|
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
|
-
{
|
16
|
+
{values:, total: values.length}
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
@@ -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
|
-
|
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
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
191
|
-
|
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
|
-
|
206
|
-
|
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
|
-
|
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
|
-
|
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)
|
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:
|
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
|
249
|
-
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
|
data/lib/active_mcp/server.rb
CHANGED
@@ -1,13 +1,40 @@
|
|
1
1
|
require "json"
|
2
2
|
require "English"
|
3
3
|
require_relative "server/method"
|
4
|
-
require_relative "server/
|
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
|
data/lib/active_mcp/version.rb
CHANGED
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.
|
4
|
+
version: 0.9.2
|
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/
|
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.
|
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: []
|
File without changes
|