active_mcp 0.3.11 → 0.5.0
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 +111 -1
- data/app/controllers/active_mcp/base_controller.rb +2 -1
- data/app/controllers/concerns/active_mcp/{request_handler.rb → request_handlable.rb} +17 -1
- data/app/controllers/concerns/active_mcp/resource_readable.rb +84 -0
- data/app/views/active_mcp/resources_list.json.jbuilder +24 -0
- data/app/views/active_mcp/resources_read.json.jbuilder +16 -0
- data/app/views/active_mcp/tools_list.json.jbuilder +1 -1
- data/lib/active_mcp/engine.rb +9 -0
- data/lib/active_mcp/server/protocol_handler.rb +14 -13
- data/lib/active_mcp/server/resource_manager.rb +150 -0
- data/lib/active_mcp/server.rb +5 -2
- data/lib/active_mcp/version.rb +1 -1
- data/lib/generators/active_mcp/resource/resource_generator.rb +17 -0
- data/lib/generators/active_mcp/resource/templates/resource.rb.erb +51 -0
- metadata +10 -4
- /data/lib/active_mcp/server/{methods.rb → method.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: 1891356bb19d256dcbd11f7d441fb9c5f668fc9cc0b115ea17eec44aef99ee54
|
4
|
+
data.tar.gz: 8031f7e13bfd5a837cf84ff3ca069bd5c56b46901e7e04c861d6c15eabcf2c94
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20438f69b5a08fa089187a1eb11c0f0e8ccc5525809439e45da36105411b7c62187a981f0a73deda52fdb292ede1ffc429aaa26d0a21f2a5a369b9601bd2306d
|
7
|
+
data.tar.gz: cf9c750474af01cc72c1a8480c568c36a29d271b7c7a9f5a2bf5ecdec8cc42e1fc0fe70493ae6e9a82d3f431445aca068a881fc89c71c6181f976384c8b0e2ca
|
data/README.md
CHANGED
@@ -25,6 +25,7 @@ A Ruby on Rails engine for the [Model Context Protocol (MCP)](https://modelconte
|
|
25
25
|
- [🛠 Rails Generators](#-rails-generators)
|
26
26
|
- [Install Generator](#install-generator)
|
27
27
|
- [Tool Generator](#tool-generator)
|
28
|
+
- [Resource Generator](#resource-generator)
|
28
29
|
- [🧰 Creating MCP Tools](#-creating-mcp-tools)
|
29
30
|
- [📋 Input Schema](#-input-schema)
|
30
31
|
- [🔐 Authorization \& Authentication](#-authorization--authentication)
|
@@ -32,6 +33,9 @@ A Ruby on Rails engine for the [Model Context Protocol (MCP)](https://modelconte
|
|
32
33
|
- [Authentication Options](#authentication-options)
|
33
34
|
- [1. Server Configuration](#1-server-configuration)
|
34
35
|
- [2. Token Verification in Tools](#2-token-verification-in-tools)
|
36
|
+
- [📦 MCP Resources](#-mcp-resources)
|
37
|
+
- [Creating Resources](#creating-resources)
|
38
|
+
- [Resource Types](#resource-types)
|
35
39
|
- [⚙️ Advanced Configuration](#️-advanced-configuration)
|
36
40
|
- [Custom Controller](#custom-controller)
|
37
41
|
- [💡 Best Practices](#-best-practices)
|
@@ -45,7 +49,8 @@ A Ruby on Rails engine for the [Model Context Protocol (MCP)](https://modelconte
|
|
45
49
|
## ✨ Features
|
46
50
|
|
47
51
|
- **Simple Integration**: Easily expose Rails functionality as MCP tools
|
48
|
-
- **
|
52
|
+
- **Resource Support**: Share files and data with AI assistants through MCP resources
|
53
|
+
- **Powerful Generators**: Quickly scaffold MCP tools and resources with Rails generators
|
49
54
|
- **Authentication Support**: Built-in authentication and authorization capabilities
|
50
55
|
- **Flexible Configuration**: Multiple deployment and connection options
|
51
56
|
|
@@ -179,6 +184,16 @@ $ rails generate active_mcp:tool search_users
|
|
179
184
|
|
180
185
|
This creates a new tool file at `app/tools/search_users_tool.rb` with ready-to-customize starter code.
|
181
186
|
|
187
|
+
### Resource Generator
|
188
|
+
|
189
|
+
Generate new MCP resources to share data with AI:
|
190
|
+
|
191
|
+
```bash
|
192
|
+
$ rails generate active_mcp:resource profile_image
|
193
|
+
```
|
194
|
+
|
195
|
+
This creates a new resource file at `app/resources/profile_image_resource.rb` that you can customize to provide various types of content to AI assistants.
|
196
|
+
|
182
197
|
## 🧰 Creating MCP Tools
|
183
198
|
|
184
199
|
MCP tools are Ruby classes that inherit from `ActiveMcp::Tool` and define an interface for AI to interact with your application:
|
@@ -290,6 +305,101 @@ def call(resource_id:, auth_info: nil, **args)
|
|
290
305
|
end
|
291
306
|
```
|
292
307
|
|
308
|
+
## 📦 MCP Resources
|
309
|
+
|
310
|
+
MCP Resources allow you to share data and files with AI assistants. Resources have a URI, MIME type, and can return either text or binary data.
|
311
|
+
|
312
|
+
### Creating Resources
|
313
|
+
|
314
|
+
Resources are Ruby classes that inherit from `ActiveMcp::Resource`:
|
315
|
+
|
316
|
+
```ruby
|
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
|
334
|
+
|
335
|
+
def description
|
336
|
+
@user.profile
|
337
|
+
end
|
338
|
+
|
339
|
+
def text
|
340
|
+
# Return JSON data
|
341
|
+
{
|
342
|
+
id: @user.id,
|
343
|
+
name: @user.name,
|
344
|
+
email: @user.email,
|
345
|
+
created_at: @user.created_at
|
346
|
+
}
|
347
|
+
end
|
348
|
+
end
|
349
|
+
```
|
350
|
+
|
351
|
+
### Resource Types
|
352
|
+
|
353
|
+
Resources can return two types of content:
|
354
|
+
|
355
|
+
1. **Text Content** - Use the `text` method to return structured data:
|
356
|
+
|
357
|
+
```ruby
|
358
|
+
def text
|
359
|
+
# Return strings, arrays, hashes, or any JSON-serializable object
|
360
|
+
{ items: Product.all.map(&:attributes) }
|
361
|
+
end
|
362
|
+
```
|
363
|
+
|
364
|
+
2. **Binary Content** - Use the `blob` method to return binary files:
|
365
|
+
|
366
|
+
```ruby
|
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
|
383
|
+
|
384
|
+
def blob
|
385
|
+
# Return binary file content
|
386
|
+
File.read(Rails.root.join("public", "profile.png"))
|
387
|
+
end
|
388
|
+
end
|
389
|
+
```
|
390
|
+
|
391
|
+
Resources can be protected using the same authorization mechanism as tools:
|
392
|
+
|
393
|
+
```ruby
|
394
|
+
def visible?
|
395
|
+
return false unless auth_info
|
396
|
+
return false unless auth_info[:type] == :bearer
|
397
|
+
|
398
|
+
# Check if the token belongs to an admin
|
399
|
+
User.find_by_token(auth_info[:token])&.admin?
|
400
|
+
end
|
401
|
+
```
|
402
|
+
|
293
403
|
## ⚙️ Advanced Configuration
|
294
404
|
|
295
405
|
### Custom Controller
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveMcp
|
4
|
-
module
|
4
|
+
module RequestHandlable
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
@@ -35,6 +35,14 @@ module ActiveMcp
|
|
35
35
|
render 'active_mcp/initialized', formats: :json
|
36
36
|
when Method::CANCELLED
|
37
37
|
render 'active_mcp/cancelled', formats: :json
|
38
|
+
when Method::RESOURCES_LIST
|
39
|
+
@resources = resources_list
|
40
|
+
@format = :jsonrpc
|
41
|
+
render 'active_mcp/resources_list', formats: :json
|
42
|
+
when Method::RESOURCES_READ
|
43
|
+
@resource = read_resource(params:, auth_info:)
|
44
|
+
@format = :jsonrpc
|
45
|
+
render 'active_mcp/resources_read', formats: :json
|
38
46
|
when Method::TOOLS_LIST
|
39
47
|
@tools = ActiveMcp::Tool.authorized_tools(auth_info)
|
40
48
|
@format = :jsonrpc
|
@@ -53,6 +61,14 @@ module ActiveMcp
|
|
53
61
|
@auth_info = auth_info
|
54
62
|
|
55
63
|
case params[:method]
|
64
|
+
when Method::RESOURCES_LIST
|
65
|
+
@resources = resources_list
|
66
|
+
@format = :json
|
67
|
+
render 'active_mcp/resources_list', formats: :json
|
68
|
+
when Method::RESOURCES_READ
|
69
|
+
@resource = read_resource(params:, auth_info:)
|
70
|
+
@format = :json
|
71
|
+
render 'active_mcp/resources_read', formats: :json
|
56
72
|
when Method::TOOLS_LIST
|
57
73
|
@tools = ActiveMcp::Tool.authorized_tools(auth_info)
|
58
74
|
@format = :json
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module ActiveMcp
|
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:)
|
12
|
+
if params[:jsonrpc].present?
|
13
|
+
uri = params[:params][:uri]
|
14
|
+
else
|
15
|
+
uri = params[:uri]
|
16
|
+
end
|
17
|
+
|
18
|
+
unless uri
|
19
|
+
return {
|
20
|
+
isError: true,
|
21
|
+
contents: []
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
resource = resources_list.find do |r|
|
26
|
+
r.uri == uri
|
27
|
+
end
|
28
|
+
|
29
|
+
unless resource
|
30
|
+
return {
|
31
|
+
isError: true,
|
32
|
+
contents: []
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
if resource.respond_to?(:visible?) && !resource.visible?
|
37
|
+
return {
|
38
|
+
isError: true,
|
39
|
+
contents: []
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
begin
|
44
|
+
if content = resource.text
|
45
|
+
return {
|
46
|
+
contents: [
|
47
|
+
{
|
48
|
+
uri:,
|
49
|
+
mimeType: resource.mime_type,
|
50
|
+
text: formatted(content)
|
51
|
+
}
|
52
|
+
]
|
53
|
+
}
|
54
|
+
elsif content = resource.blob
|
55
|
+
return {
|
56
|
+
contents: [
|
57
|
+
{
|
58
|
+
uri:,
|
59
|
+
mimeType: resource.mime_type,
|
60
|
+
blob: Base64.strict_encode64(content)
|
61
|
+
}
|
62
|
+
]
|
63
|
+
}
|
64
|
+
end
|
65
|
+
rescue
|
66
|
+
return {
|
67
|
+
isError: true,
|
68
|
+
contents: []
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def formatted(object)
|
74
|
+
case object
|
75
|
+
when String
|
76
|
+
object
|
77
|
+
when Hash
|
78
|
+
object.to_json
|
79
|
+
else
|
80
|
+
object.to_s
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,24 @@
|
|
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.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
|
14
|
+
end
|
15
|
+
else
|
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
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
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 @resource
|
6
|
+
else
|
7
|
+
json.isError @resource[:isError] if @resource[:isError]
|
8
|
+
json.contents do
|
9
|
+
json.array!(@resource[:contents]) do |content|
|
10
|
+
json.uri content[:uri]
|
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
|
15
|
+
end
|
16
|
+
end
|
data/lib/active_mcp/engine.rb
CHANGED
@@ -10,5 +10,14 @@ module ActiveMcp
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
13
|
+
|
14
|
+
initializer "active_mcp.eager_load_resources" do |app|
|
15
|
+
tools_path = Rails.root.join("app", "resources")
|
16
|
+
if Dir.exist?(tools_path)
|
17
|
+
Dir[tools_path.join("*.rb")].sort.each do |file|
|
18
|
+
require_dependency file
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
13
22
|
end
|
14
23
|
end
|
@@ -48,12 +48,12 @@ module ActiveMcp
|
|
48
48
|
handle_initialized(request)
|
49
49
|
when Method::PING
|
50
50
|
handle_ping(request)
|
51
|
+
when Method::RESOURCES_LIST
|
52
|
+
handle_list_resources(request)
|
51
53
|
when Method::TOOLS_LIST
|
52
54
|
handle_list_tools(request)
|
53
55
|
when Method::TOOLS_CALL
|
54
56
|
handle_use_tool(request)
|
55
|
-
when Method::RESOURCES_LIST
|
56
|
-
handle_list_resources(request)
|
57
57
|
when Method::RESOURCES_READ
|
58
58
|
handle_read_resource(request)
|
59
59
|
else
|
@@ -112,6 +112,10 @@ module ActiveMcp
|
|
112
112
|
success_response(request[:id], {})
|
113
113
|
end
|
114
114
|
|
115
|
+
def handle_list_resources(request)
|
116
|
+
success_response(request[:id], {resources: @server.resource_manager.resources})
|
117
|
+
end
|
118
|
+
|
115
119
|
def handle_list_tools(request)
|
116
120
|
success_response(request[:id], {tools: @server.tool_manager.tools})
|
117
121
|
end
|
@@ -130,19 +134,16 @@ module ActiveMcp
|
|
130
134
|
end
|
131
135
|
end
|
132
136
|
|
133
|
-
def handle_list_resources(request)
|
134
|
-
success_response(
|
135
|
-
request[:id],
|
136
|
-
{
|
137
|
-
resources: [],
|
138
|
-
nextCursor: "0"
|
139
|
-
}
|
140
|
-
)
|
141
|
-
end
|
142
|
-
|
143
137
|
def handle_read_resource(request)
|
144
138
|
uri = request.dig(:params, :uri)
|
145
|
-
|
139
|
+
begin
|
140
|
+
result = @server.resource_manager.read_resource(uri)
|
141
|
+
|
142
|
+
success_response(request[:id], result)
|
143
|
+
rescue => e
|
144
|
+
log_error("Error reading resource #{uri}", e)
|
145
|
+
error_response(request[:id], ErrorCode::INTERNAL_ERROR, "An error occurred while reading the resource")
|
146
|
+
end
|
146
147
|
end
|
147
148
|
|
148
149
|
def success_response(id, result)
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module ActiveMcp
|
4
|
+
class Server
|
5
|
+
class ResourceManager
|
6
|
+
attr_reader :resources
|
7
|
+
|
8
|
+
def initialize(uri: nil, auth: nil)
|
9
|
+
@resources = {}
|
10
|
+
@base_uri = uri
|
11
|
+
|
12
|
+
if auth
|
13
|
+
@auth_header = "#{auth[:type] == :bearer ? "Bearer" : "Basic"} #{auth[:token]}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_registered_resources
|
18
|
+
fetch_resources
|
19
|
+
end
|
20
|
+
|
21
|
+
def read_resource(uri)
|
22
|
+
require "net/http"
|
23
|
+
|
24
|
+
unless @base_uri.is_a?(URI) || @base_uri.is_a?(String)
|
25
|
+
log_error("Invalid URI type", StandardError.new("URI must be a String or URI object"))
|
26
|
+
return {
|
27
|
+
isError: true,
|
28
|
+
content: [{type: "text", text: "Invalid URI configuration"}]
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
begin
|
33
|
+
base_uri = URI.parse(@base_uri.to_s)
|
34
|
+
|
35
|
+
unless base_uri.scheme =~ /\Ahttps?\z/ && !base_uri.host.nil?
|
36
|
+
log_error("Invalid URI", StandardError.new("URI must have a valid scheme and host"))
|
37
|
+
return {
|
38
|
+
isError: true,
|
39
|
+
content: [{type: "text", text: "Invalid URI configuration"}]
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
if defined?(Rails) && Rails.env.production? && base_uri.scheme != "https"
|
44
|
+
return {
|
45
|
+
isError: true,
|
46
|
+
content: [{type: "text", text: "HTTPS is required in production environment"}]
|
47
|
+
}
|
48
|
+
end
|
49
|
+
rescue URI::InvalidURIError => e
|
50
|
+
log_error("Invalid URI format", e)
|
51
|
+
return {
|
52
|
+
isError: true,
|
53
|
+
content: [{type: "text", text: "Invalid URI format"}]
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
request = Net::HTTP::Post.new(base_uri)
|
58
|
+
request.body = JSON.generate({
|
59
|
+
method: Method::RESOURCES_READ,
|
60
|
+
uri:,
|
61
|
+
})
|
62
|
+
request["Content-Type"] = "application/json"
|
63
|
+
request["Authorization"] = @auth_header
|
64
|
+
|
65
|
+
begin
|
66
|
+
response = Net::HTTP.start(base_uri.hostname, base_uri.port) do |http|
|
67
|
+
http.request(request)
|
68
|
+
end
|
69
|
+
|
70
|
+
if response.code == "200"
|
71
|
+
JSON.parse(response.body, symbolize_names: true)
|
72
|
+
else
|
73
|
+
$stderr.puts(response.body)
|
74
|
+
{
|
75
|
+
isError: true,
|
76
|
+
contents: []
|
77
|
+
}
|
78
|
+
end
|
79
|
+
rescue => e
|
80
|
+
log_error("Error calling tool", e)
|
81
|
+
{
|
82
|
+
isError: true,
|
83
|
+
contents: []
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def fetch_resources
|
91
|
+
return unless @base_uri
|
92
|
+
|
93
|
+
require "net/http"
|
94
|
+
|
95
|
+
unless @base_uri.is_a?(URI) || @base_uri.is_a?(String)
|
96
|
+
log_error("Invalid URI type", StandardError.new("URI must be a String or URI object"))
|
97
|
+
return
|
98
|
+
end
|
99
|
+
|
100
|
+
begin
|
101
|
+
uri = URI.parse(@base_uri.to_s)
|
102
|
+
|
103
|
+
unless uri.scheme =~ /\Ahttps?\z/ && !uri.host.nil?
|
104
|
+
log_error("Invalid URI", StandardError.new("URI must have a valid scheme and host"))
|
105
|
+
return
|
106
|
+
end
|
107
|
+
|
108
|
+
if defined?(Rails) && Rails.env.production? && uri.scheme != "https"
|
109
|
+
log_error("HTTPS is required in production environment", StandardError.new("Non-HTTPS URI in production"))
|
110
|
+
return
|
111
|
+
end
|
112
|
+
rescue URI::InvalidURIError => e
|
113
|
+
log_error("Invalid URI format", e)
|
114
|
+
return
|
115
|
+
end
|
116
|
+
|
117
|
+
request = Net::HTTP::Post.new(uri)
|
118
|
+
request.body = JSON.generate({
|
119
|
+
method: "resources/list",
|
120
|
+
arguments: "{}"
|
121
|
+
})
|
122
|
+
request["Content-Type"] = "application/json"
|
123
|
+
request["Authorization"] = @auth_header
|
124
|
+
|
125
|
+
begin
|
126
|
+
response = Net::HTTP.start(uri.hostname, uri.port) do |http|
|
127
|
+
http.request(request)
|
128
|
+
end
|
129
|
+
|
130
|
+
result = JSON.parse(response.body, symbolize_names: true)
|
131
|
+
@resources = result[:result]
|
132
|
+
rescue => e
|
133
|
+
log_error("Error fetching resources", e)
|
134
|
+
@resources = []
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def log_error(message, error)
|
139
|
+
error_details = "#{message}: #{error.message}\n"
|
140
|
+
error_details += error.backtrace.join("\n") if error.backtrace
|
141
|
+
|
142
|
+
if defined?(Rails)
|
143
|
+
Rails.logger.error(error_details)
|
144
|
+
else
|
145
|
+
$stderr.puts(error_details)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
data/lib/active_mcp/server.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
require "json"
|
2
2
|
require "English"
|
3
|
-
require_relative "server/
|
3
|
+
require_relative "server/method"
|
4
4
|
require_relative "server/error_codes"
|
5
5
|
require_relative "server/stdio_connection"
|
6
|
+
require_relative "server/resource_manager"
|
6
7
|
require_relative "server/tool_manager"
|
7
8
|
require_relative "server/protocol_handler"
|
8
9
|
|
9
10
|
module ActiveMcp
|
10
11
|
class Server
|
11
|
-
attr_reader :name, :version, :uri, :tool_manager, :protocol_handler
|
12
|
+
attr_reader :name, :version, :uri, :tool_manager, :protocol_handler, :resource_manager
|
12
13
|
|
13
14
|
def initialize(
|
14
15
|
version: ActiveMcp::VERSION,
|
@@ -19,9 +20,11 @@ module ActiveMcp
|
|
19
20
|
@name = name
|
20
21
|
@version = version
|
21
22
|
@uri = uri
|
23
|
+
@resource_manager = ResourceManager.new(uri:, auth:)
|
22
24
|
@tool_manager = ToolManager.new(uri: uri, auth:)
|
23
25
|
@protocol_handler = ProtocolHandler.new(self)
|
24
26
|
@tool_manager.load_registered_tools
|
27
|
+
@resource_manager.load_registered_resources
|
25
28
|
end
|
26
29
|
|
27
30
|
def start
|
data/lib/active_mcp/version.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
module ActiveMcp
|
2
|
+
module Generators
|
3
|
+
class ResourceGenerator < Rails::Generators::NamedBase
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
5
|
+
|
6
|
+
def create_resource_file
|
7
|
+
template "resource.rb.erb", File.join("app/resources", "#{file_name}_resource.rb")
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def class_name
|
13
|
+
"#{file_name.camelize}Resource"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,51 @@
|
|
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
|
25
|
+
|
26
|
+
# Uncomment and modify this method to implement authorization control
|
27
|
+
# This controls who can see and use this tool
|
28
|
+
# def visible?
|
29
|
+
# # Example: require authentication
|
30
|
+
# # return false unless @auth_info
|
31
|
+
#
|
32
|
+
# # Example: require a specific authentication type
|
33
|
+
# # return false unless @auth_info[:type] == :bearer
|
34
|
+
#
|
35
|
+
# # Example: check for admin permissions
|
36
|
+
# # admin_tokens = ["admin-token"]
|
37
|
+
# # return admin_tokens.include?(@auth_info[:token])
|
38
|
+
#
|
39
|
+
# # Default: allow all access
|
40
|
+
# true
|
41
|
+
# end
|
42
|
+
|
43
|
+
def text
|
44
|
+
# Return a string, hash, or any JSON-serializable object
|
45
|
+
{ foo: "bar" }
|
46
|
+
end
|
47
|
+
|
48
|
+
# def blob
|
49
|
+
# File.read("/path/to/file")
|
50
|
+
# end
|
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
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Moeki Kawakami
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-04-
|
10
|
+
date: 2025-04-07 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rails
|
@@ -68,12 +68,15 @@ files:
|
|
68
68
|
- README.md
|
69
69
|
- Rakefile
|
70
70
|
- app/controllers/active_mcp/base_controller.rb
|
71
|
-
- app/controllers/concerns/active_mcp/
|
71
|
+
- app/controllers/concerns/active_mcp/request_handlable.rb
|
72
|
+
- app/controllers/concerns/active_mcp/resource_readable.rb
|
72
73
|
- app/models/active_mcp/tool_executor.rb
|
73
74
|
- app/views/active_mcp/cancelled.json.jbuilder
|
74
75
|
- app/views/active_mcp/initialize.json.jbuilder
|
75
76
|
- app/views/active_mcp/initialized.json.jbuilder
|
76
77
|
- app/views/active_mcp/no_method.json.jbuilder
|
78
|
+
- app/views/active_mcp/resources_list.json.jbuilder
|
79
|
+
- app/views/active_mcp/resources_read.json.jbuilder
|
77
80
|
- app/views/active_mcp/tools_call.json.jbuilder
|
78
81
|
- app/views/active_mcp/tools_list.json.jbuilder
|
79
82
|
- config/routes.rb
|
@@ -82,14 +85,17 @@ files:
|
|
82
85
|
- lib/active_mcp/engine.rb
|
83
86
|
- lib/active_mcp/server.rb
|
84
87
|
- lib/active_mcp/server/error_codes.rb
|
85
|
-
- lib/active_mcp/server/
|
88
|
+
- lib/active_mcp/server/method.rb
|
86
89
|
- lib/active_mcp/server/protocol_handler.rb
|
90
|
+
- lib/active_mcp/server/resource_manager.rb
|
87
91
|
- lib/active_mcp/server/stdio_connection.rb
|
88
92
|
- lib/active_mcp/server/tool_manager.rb
|
89
93
|
- lib/active_mcp/tool.rb
|
90
94
|
- lib/active_mcp/version.rb
|
91
95
|
- lib/generators/active_mcp/install/install_generator.rb
|
92
96
|
- lib/generators/active_mcp/install/templates/initializer.rb
|
97
|
+
- lib/generators/active_mcp/resource/resource_generator.rb
|
98
|
+
- lib/generators/active_mcp/resource/templates/resource.rb.erb
|
93
99
|
- lib/generators/active_mcp/tool/templates/tool.rb.erb
|
94
100
|
- lib/generators/active_mcp/tool/tool_generator.rb
|
95
101
|
homepage: https://github.com/moekiorg/active_mcp
|
File without changes
|