active_mcp 0.3.11 → 0.4.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 +85 -1
- data/app/controllers/concerns/active_mcp/request_handler.rb +16 -0
- data/app/models/active_mcp/resource_reader.rb +78 -0
- data/app/views/active_mcp/resources_list.json.jbuilder +10 -0
- data/app/views/active_mcp/resources_read.json.jbuilder +16 -0
- data/lib/active_mcp/engine.rb +9 -0
- data/lib/active_mcp/resource.rb +64 -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/active_mcp.rb +1 -0
- data/lib/generators/active_mcp/resource/resource_generator.rb +17 -0
- data/lib/generators/active_mcp/resource/templates/resource.rb.erb +37 -0
- metadata +9 -2
- /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: acd47906160af7e22cbfa3fe974288e7d16639898b026ef6288ab91444e799be
|
4
|
+
data.tar.gz: 0bc89e6d457537dbe75450f1ad9ec525ed37b9168df48f08c10adbbcb2aebc5f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea4c71409b27eb65027b33fa0e43ddbaf4e3924768625d28788a2d23e9604099fc14eb6de3c962066a260a96bac8415605066ad05c4cc3ff63f31a33a47fbbc3
|
7
|
+
data.tar.gz: 4960bc1dee62aa05245159e0d53cf618583739e63aecbbdaf581db3cbed6133317a2537ec9f1d12313956c5e00facb24a6f1fefa32899d685061671fe529adfc
|
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,75 @@ 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 < ActiveMcp::Resource
|
318
|
+
uri "data://localhost/user"
|
319
|
+
mime_type "application/json"
|
320
|
+
description "User profile data"
|
321
|
+
|
322
|
+
def text(auth_info: nil)
|
323
|
+
# Authenticate if needed
|
324
|
+
user = User.find_by(id: 1)
|
325
|
+
|
326
|
+
# Return JSON data
|
327
|
+
{
|
328
|
+
id: user.id,
|
329
|
+
name: user.name,
|
330
|
+
email: user.email,
|
331
|
+
created_at: user.created_at
|
332
|
+
}
|
333
|
+
end
|
334
|
+
end
|
335
|
+
```
|
336
|
+
|
337
|
+
### Resource Types
|
338
|
+
|
339
|
+
Resources can return two types of content:
|
340
|
+
|
341
|
+
1. **Text Content** - Use the `text` method to return structured data:
|
342
|
+
|
343
|
+
```ruby
|
344
|
+
def text(auth_info: nil)
|
345
|
+
# Return strings, arrays, hashes, or any JSON-serializable object
|
346
|
+
{ items: Product.all.map(&:attributes) }
|
347
|
+
end
|
348
|
+
```
|
349
|
+
|
350
|
+
2. **Binary Content** - Use the `blob` method to return binary files:
|
351
|
+
|
352
|
+
```ruby
|
353
|
+
class ImageResource < ActiveMcp::Resource
|
354
|
+
uri "data://localhost/image"
|
355
|
+
mime_type "image/png"
|
356
|
+
description "Profile image"
|
357
|
+
|
358
|
+
def blob(auth_info: nil)
|
359
|
+
# Return binary file content
|
360
|
+
File.read(Rails.root.join("public", "profile.png"))
|
361
|
+
end
|
362
|
+
end
|
363
|
+
```
|
364
|
+
|
365
|
+
Resources can be protected using the same authorization mechanism as tools:
|
366
|
+
|
367
|
+
```ruby
|
368
|
+
def self.visible?(auth_info)
|
369
|
+
return false unless auth_info
|
370
|
+
return false unless auth_info[:type] == :bearer
|
371
|
+
|
372
|
+
# Check if the token belongs to an admin
|
373
|
+
User.find_by_token(auth_info[:token])&.admin?
|
374
|
+
end
|
375
|
+
```
|
376
|
+
|
293
377
|
## ⚙️ Advanced Configuration
|
294
378
|
|
295
379
|
### Custom Controller
|
@@ -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 = ActiveMcp::Resource.authorized_resources(auth_info)
|
40
|
+
@format = :jsonrpc
|
41
|
+
render 'active_mcp/resources_list', formats: :json
|
42
|
+
when Method::RESOURCES_READ
|
43
|
+
@resource = ActiveMcp::ResourceReader.read(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 = ActiveMcp::Resource.authorized_resources(auth_info)
|
66
|
+
@format = :json
|
67
|
+
render 'active_mcp/resources_list', formats: :json
|
68
|
+
when Method::RESOURCES_READ
|
69
|
+
@resource = ActiveMcp::ResourceReader.read(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,78 @@
|
|
1
|
+
module ActiveMcp
|
2
|
+
class ResourceReader
|
3
|
+
def self.read(params:, auth_info:)
|
4
|
+
if params[:jsonrpc].present?
|
5
|
+
uri = params[:params][:uri]
|
6
|
+
else
|
7
|
+
uri = params[:uri]
|
8
|
+
end
|
9
|
+
|
10
|
+
unless uri
|
11
|
+
return {
|
12
|
+
isError: true,
|
13
|
+
contents: []
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
resource_class = Resource.registered_resources.find do |r|
|
18
|
+
r._uri == uri
|
19
|
+
end
|
20
|
+
|
21
|
+
unless resource_class
|
22
|
+
return {
|
23
|
+
isError: true,
|
24
|
+
contents: []
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
unless resource_class.visible?(auth_info)
|
29
|
+
return {
|
30
|
+
isError: true,
|
31
|
+
contents: []
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
resource = resource_class.new
|
36
|
+
|
37
|
+
begin
|
38
|
+
if content = resource.text(auth_info:)
|
39
|
+
return {
|
40
|
+
contents: [
|
41
|
+
{
|
42
|
+
uri:,
|
43
|
+
mimeType: resource_class._mime_type,
|
44
|
+
text: formatted(content)
|
45
|
+
}
|
46
|
+
]
|
47
|
+
}
|
48
|
+
elsif content = resource.blob(auth_info:)
|
49
|
+
return {
|
50
|
+
contents: [
|
51
|
+
{
|
52
|
+
uri:,
|
53
|
+
mimeType: resource_class._mime_type,
|
54
|
+
blob: Base64.strict_encode64(content)
|
55
|
+
}
|
56
|
+
]
|
57
|
+
}
|
58
|
+
end
|
59
|
+
rescue
|
60
|
+
return {
|
61
|
+
isError: true,
|
62
|
+
contents: []
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.formatted(object)
|
68
|
+
case object
|
69
|
+
when String
|
70
|
+
object
|
71
|
+
when Hash
|
72
|
+
object.to_json
|
73
|
+
else
|
74
|
+
object.to_s
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
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
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "json-schema"
|
2
|
+
|
3
|
+
module ActiveMcp
|
4
|
+
class Resource
|
5
|
+
class << self
|
6
|
+
attr_reader :_description, :schema, :_uri, :_mime_type
|
7
|
+
|
8
|
+
def resource_name
|
9
|
+
name ? name.underscore.sub(/_resource$/, "") : ""
|
10
|
+
end
|
11
|
+
|
12
|
+
def uri(value)
|
13
|
+
@_uri = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def description(value)
|
17
|
+
@_description = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def mime_type(value)
|
21
|
+
@_mime_type = value
|
22
|
+
end
|
23
|
+
|
24
|
+
def registered_resources
|
25
|
+
@registered_resources ||= []
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_writer :registered_resources
|
29
|
+
|
30
|
+
def inherited(subclass)
|
31
|
+
registered_resources << subclass
|
32
|
+
end
|
33
|
+
|
34
|
+
def visible?(auth_info)
|
35
|
+
if respond_to?(:authorized?)
|
36
|
+
authorized?(auth_info)
|
37
|
+
else
|
38
|
+
true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def authorized_resources(auth_info = nil)
|
43
|
+
registered_resources.select do |tool_class|
|
44
|
+
tool_class.visible?(auth_info)
|
45
|
+
end.map do |tool_class|
|
46
|
+
{
|
47
|
+
uri: tool_class._uri,
|
48
|
+
name: tool_class.resource_name,
|
49
|
+
mimeType: tool_class._mime_type,
|
50
|
+
description: tool_class._description
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def text(auth_info:)
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def blob(auth_info:)
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -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
data/lib/active_mcp.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,37 @@
|
|
1
|
+
class <%= class_name %> < ActiveMcp::Resource
|
2
|
+
uri "data://localhost/<%= file_name %>"
|
3
|
+
mime_type "application/json"
|
4
|
+
description "<%= file_name.humanize %>"
|
5
|
+
|
6
|
+
# Uncomment and modify this method to implement authorization control
|
7
|
+
# This controls who can see and use this tool
|
8
|
+
# def self.visible?(auth_info)
|
9
|
+
# # Example: require authentication
|
10
|
+
# # return false unless auth_info
|
11
|
+
#
|
12
|
+
# # Example: require a specific authentication type
|
13
|
+
# # return false unless auth_info[:type] == :bearer
|
14
|
+
#
|
15
|
+
# # Example: check for admin permissions
|
16
|
+
# # admin_tokens = ["admin-token"]
|
17
|
+
# # return admin_tokens.include?(auth_info[:token])
|
18
|
+
#
|
19
|
+
# # Default: allow all access
|
20
|
+
# true
|
21
|
+
# end
|
22
|
+
|
23
|
+
def text(auth_info: nil)
|
24
|
+
# Authentication information can be accessed via _auth_info parameter
|
25
|
+
# auth_info = { type: :bearer, token: "xxx", header: "Bearer xxx" }
|
26
|
+
# or { type: :basic, token: "base64encoded", header: "Basic base64encoded" }
|
27
|
+
|
28
|
+
# Implement tool logic here
|
29
|
+
|
30
|
+
# Return a string, hash, or any JSON-serializable object
|
31
|
+
{ foo: "bar" }
|
32
|
+
end
|
33
|
+
|
34
|
+
# def blob(auth_info: nil)
|
35
|
+
# File.read("/path/to/file")
|
36
|
+
# end
|
37
|
+
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.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Moeki Kawakami
|
@@ -69,27 +69,34 @@ files:
|
|
69
69
|
- Rakefile
|
70
70
|
- app/controllers/active_mcp/base_controller.rb
|
71
71
|
- app/controllers/concerns/active_mcp/request_handler.rb
|
72
|
+
- app/models/active_mcp/resource_reader.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
|
80
83
|
- lib/active_mcp.rb
|
81
84
|
- lib/active_mcp/configuration.rb
|
82
85
|
- lib/active_mcp/engine.rb
|
86
|
+
- lib/active_mcp/resource.rb
|
83
87
|
- lib/active_mcp/server.rb
|
84
88
|
- lib/active_mcp/server/error_codes.rb
|
85
|
-
- lib/active_mcp/server/
|
89
|
+
- lib/active_mcp/server/method.rb
|
86
90
|
- lib/active_mcp/server/protocol_handler.rb
|
91
|
+
- lib/active_mcp/server/resource_manager.rb
|
87
92
|
- lib/active_mcp/server/stdio_connection.rb
|
88
93
|
- lib/active_mcp/server/tool_manager.rb
|
89
94
|
- lib/active_mcp/tool.rb
|
90
95
|
- lib/active_mcp/version.rb
|
91
96
|
- lib/generators/active_mcp/install/install_generator.rb
|
92
97
|
- lib/generators/active_mcp/install/templates/initializer.rb
|
98
|
+
- lib/generators/active_mcp/resource/resource_generator.rb
|
99
|
+
- lib/generators/active_mcp/resource/templates/resource.rb.erb
|
93
100
|
- lib/generators/active_mcp/tool/templates/tool.rb.erb
|
94
101
|
- lib/generators/active_mcp/tool/tool_generator.rb
|
95
102
|
homepage: https://github.com/moekiorg/active_mcp
|
File without changes
|