active_mcp 0.5.1 → 0.7.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 +118 -63
- data/app/controllers/active_mcp/base_controller.rb +27 -3
- data/app/controllers/concerns/active_mcp/authenticatable.rb +29 -0
- data/app/controllers/concerns/active_mcp/request_handlable.rb +18 -44
- data/app/controllers/concerns/active_mcp/resource_readable.rb +3 -7
- data/app/controllers/concerns/active_mcp/tool_executable.rb +6 -9
- data/app/views/active_mcp/resource_templates_list.json.jbuilder +24 -0
- data/app/views/active_mcp/tools_list.json.jbuilder +14 -2
- data/lib/active_mcp/controller/base.rb +7 -0
- data/lib/active_mcp/engine.rb +11 -14
- data/lib/active_mcp/schema/base.rb +46 -0
- data/lib/active_mcp/server/fetcher.rb +56 -0
- data/lib/active_mcp/server/method.rb +1 -1
- data/lib/active_mcp/server/protocol_handler.rb +58 -22
- data/lib/active_mcp/server.rb +18 -7
- data/lib/active_mcp/tool/base.rb +52 -0
- data/lib/active_mcp/version.rb +1 -1
- data/lib/active_mcp.rb +3 -1
- data/lib/generators/active_mcp/install/install_generator.rb +3 -7
- data/lib/generators/active_mcp/resource/resource_generator.rb +1 -1
- data/lib/generators/active_mcp/resource/templates/resource.rb.erb +4 -12
- data/lib/generators/active_mcp/tool/templates/tool.rb.erb +15 -9
- data/lib/generators/active_mcp/tool/tool_generator.rb +1 -1
- metadata +7 -4
- data/lib/active_mcp/server/resource_manager.rb +0 -150
- data/lib/active_mcp/server/tool_manager.rb +0 -163
- data/lib/active_mcp/tool.rb +0 -82
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9367d905ac3b7dfa87f27063c41f24f1ed93de9d88a5fc009010ed35bfafa85
|
4
|
+
data.tar.gz: 7b1ed7d1f79ad9487c92a4e0595727cc60125c3584ae7029be8d3e86f9c2537b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f429c060c395a06e9e146c68ea859a1750a00add2b28ba5fe3a968b20b8b335fcc3908a1f89b63aeaf24f092942af9edd4979eec2a3b380d431519ee35623d4
|
7
|
+
data.tar.gz: 1a0bff75ee866fbba70a5549ee867f6260cd8e925a592b9a044b3769a91ba9955487e3798647c7fa6b356e4c9cbfbd3610550a713aa4cee97dfff896c7514c15
|
data/README.md
CHANGED
@@ -17,8 +17,6 @@ A Ruby on Rails engine for the [Model Context Protocol (MCP)](https://modelconte
|
|
17
17
|
- [✨ Features](#-features)
|
18
18
|
- [📦 Installation](#-installation)
|
19
19
|
- [🚀 Setup](#-setup)
|
20
|
-
- [Using the Install Generator (Recommended)](#using-the-install-generator-recommended)
|
21
|
-
- [Manual Setup](#manual-setup)
|
22
20
|
- [🔌 MCP Connection Methods](#-mcp-connection-methods)
|
23
21
|
- [1. Direct HTTP Connection](#1-direct-http-connection)
|
24
22
|
- [2. Standalone MCP Server](#2-standalone-mcp-server)
|
@@ -36,8 +34,8 @@ A Ruby on Rails engine for the [Model Context Protocol (MCP)](https://modelconte
|
|
36
34
|
- [📦 MCP Resources](#-mcp-resources)
|
37
35
|
- [Creating Resources](#creating-resources)
|
38
36
|
- [Resource Types](#resource-types)
|
39
|
-
- [
|
40
|
-
- [
|
37
|
+
- [📦 MCP Resource Templates](#-mcp-resource-templates)
|
38
|
+
- [Creating Resource Templates](#creating-resource-templates)
|
41
39
|
- [💡 Best Practices](#-best-practices)
|
42
40
|
- [1. Create Specific Tool Classes](#1-create-specific-tool-classes)
|
43
41
|
- [2. Validate and Sanitize Inputs](#2-validate-and-sanitize-inputs)
|
@@ -76,7 +74,7 @@ $ gem install active_mcp
|
|
76
74
|
|
77
75
|
## 🚀 Setup
|
78
76
|
|
79
|
-
|
77
|
+
1. Initialize
|
80
78
|
|
81
79
|
The easiest way to set up Active MCP in your Rails application is to use the install generator:
|
82
80
|
|
@@ -84,46 +82,61 @@ The easiest way to set up Active MCP in your Rails application is to use the ins
|
|
84
82
|
$ rails generate active_mcp:install
|
85
83
|
```
|
86
84
|
|
87
|
-
This generator will
|
85
|
+
This generator will create a configuration initializer at `config/initializers/active_mcp.rb`
|
88
86
|
|
89
|
-
|
90
|
-
2. Mount the ActiveMcp engine in your routes
|
91
|
-
3. Create an MCP server script at `script/mcp_server.rb`
|
92
|
-
4. Show instructions for next steps
|
87
|
+
2. Create a tool by inheriting from `ActiveMcp::Tool::Base`:
|
93
88
|
|
94
|
-
|
89
|
+
```bash
|
90
|
+
$ rails generate active_mcp:tool create_note
|
91
|
+
```
|
95
92
|
|
96
|
-
|
93
|
+
```ruby
|
94
|
+
class CreateNoteTool < ActiveMcp::Tool::Base
|
95
|
+
def name
|
96
|
+
"Create Note"
|
97
|
+
end
|
97
98
|
|
98
|
-
|
99
|
+
def description
|
100
|
+
"Create Note"
|
101
|
+
end
|
99
102
|
|
100
|
-
|
103
|
+
argument :title, :string, required: true
|
104
|
+
argument :content, :string, required: true
|
101
105
|
|
102
|
-
|
103
|
-
|
104
|
-
mount ActiveMcp::Engine, at: "/mcp"
|
106
|
+
def call(title:, content:, context:)
|
107
|
+
note = Note.create(title: title, content: content)
|
105
108
|
|
106
|
-
|
109
|
+
"Created note with ID: #{note.id}"
|
110
|
+
end
|
107
111
|
end
|
108
112
|
```
|
109
113
|
|
110
|
-
|
114
|
+
3. Create schema for your application:
|
111
115
|
|
112
116
|
```ruby
|
113
|
-
class
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
argument :content, :string, required: true
|
117
|
+
class MySchema < ActiveMcp::Schema::Base
|
118
|
+
tool CreateNoteTool.new
|
119
|
+
end
|
120
|
+
```
|
118
121
|
|
119
|
-
|
120
|
-
note = Note.create(title: title, content: content)
|
122
|
+
4. Create controller ans set up routing:
|
121
123
|
|
122
|
-
|
124
|
+
```ruby
|
125
|
+
class MyMcpController < ActiveMcp::Controller::Base
|
126
|
+
def schema
|
127
|
+
MySchema.new(context:)
|
123
128
|
end
|
124
129
|
end
|
125
130
|
```
|
126
131
|
|
132
|
+
```ruby
|
133
|
+
Rails.application.routes.draw do
|
134
|
+
post "/mcp", to: "my_mcp#index"
|
135
|
+
|
136
|
+
# Your other routes
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
127
140
|
## 🔌 MCP Connection Methods
|
128
141
|
|
129
142
|
Active MCP supports two connection methods:
|
@@ -182,7 +195,7 @@ Create new MCP tools quickly:
|
|
182
195
|
$ rails generate active_mcp:tool search_users
|
183
196
|
```
|
184
197
|
|
185
|
-
This creates a new tool file at `app/tools/search_users_tool.rb` with ready-to-customize starter code.
|
198
|
+
This creates a new tool file at `app/mcp/tools/search_users_tool.rb` with ready-to-customize starter code.
|
186
199
|
|
187
200
|
### Resource Generator
|
188
201
|
|
@@ -192,21 +205,27 @@ Generate new MCP resources to share data with AI:
|
|
192
205
|
$ rails generate active_mcp:resource profile_image
|
193
206
|
```
|
194
207
|
|
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.
|
208
|
+
This creates a new resource file at `app/mcp/resources/profile_image_resource.rb` that you can customize to provide various types of content to AI assistants.
|
196
209
|
|
197
210
|
## 🧰 Creating MCP Tools
|
198
211
|
|
199
|
-
MCP tools are Ruby classes that inherit from `ActiveMcp::Tool` and define an interface for AI to interact with your application:
|
212
|
+
MCP tools are Ruby classes that inherit from `ActiveMcp::Tool::Base` and define an interface for AI to interact with your application:
|
200
213
|
|
201
214
|
```ruby
|
202
|
-
class SearchUsersTool < ActiveMcp::Tool
|
203
|
-
|
215
|
+
class SearchUsersTool < ActiveMcp::Tool::Base
|
216
|
+
def name
|
217
|
+
"Search Users"
|
218
|
+
end
|
219
|
+
|
220
|
+
def description
|
221
|
+
'Search users by criteria'
|
222
|
+
end
|
204
223
|
|
205
224
|
argument :email, :string, required: false, description: 'Email to search for'
|
206
225
|
argument :name, :string, required: false, description: 'Name to search for'
|
207
226
|
argument :limit, :integer, required: false, description: 'Maximum number of records to return'
|
208
227
|
|
209
|
-
def call(email: nil, name: nil, limit: 10)
|
228
|
+
def call(email: nil, name: nil, limit: 10, context: {})
|
210
229
|
criteria = {}
|
211
230
|
criteria[:email] = email if email.present?
|
212
231
|
criteria[:name] = name if name.present?
|
@@ -248,21 +267,27 @@ Supported types:
|
|
248
267
|
Control access to tools by overriding the `visible?` class method:
|
249
268
|
|
250
269
|
```ruby
|
251
|
-
class AdminOnlyTool < ActiveMcp::Tool
|
252
|
-
|
270
|
+
class AdminOnlyTool < ActiveMcp::Tool::Base
|
271
|
+
def name
|
272
|
+
"Admin-only tool"
|
273
|
+
end
|
274
|
+
|
275
|
+
def description
|
276
|
+
"Admin-only tool"
|
277
|
+
end
|
253
278
|
|
254
279
|
argument :command, :string, required: true, description: "Admin command"
|
255
280
|
|
256
281
|
# Only allow admins to access this tool
|
257
|
-
def
|
258
|
-
return false unless
|
259
|
-
return false unless auth_info[:type] == :bearer
|
282
|
+
def visible?(context:)
|
283
|
+
return false unless context
|
284
|
+
return false unless context[:auth_info][:type] == :bearer
|
260
285
|
|
261
286
|
# Check if the token belongs to an admin
|
262
|
-
|
287
|
+
context[:auth_info] == "admin-token" || User.find_by_token(context[:auth_info])&.admin?
|
263
288
|
end
|
264
289
|
|
265
|
-
def call(command:,
|
290
|
+
def call(command:, context: {})
|
266
291
|
# Tool implementation
|
267
292
|
end
|
268
293
|
end
|
@@ -287,14 +312,14 @@ server.start
|
|
287
312
|
#### 2. Token Verification in Tools
|
288
313
|
|
289
314
|
```ruby
|
290
|
-
def call(resource_id:,
|
315
|
+
def call(resource_id:, context: {})
|
291
316
|
# Check if authentication is provided
|
292
|
-
unless auth_info.present?
|
317
|
+
unless context[:auth_info].present?
|
293
318
|
raise "Authentication required"
|
294
319
|
end
|
295
320
|
|
296
321
|
# Verify the token
|
297
|
-
user = User.authenticate_with_token(auth_info[:token])
|
322
|
+
user = User.authenticate_with_token(context[:auth_info][:token])
|
298
323
|
|
299
324
|
unless user
|
300
325
|
raise "Invalid authentication token"
|
@@ -311,11 +336,11 @@ MCP Resources allow you to share data and files with AI assistants. Resources ha
|
|
311
336
|
|
312
337
|
### Creating Resources
|
313
338
|
|
314
|
-
Resources are Ruby classes
|
339
|
+
Resources are Ruby classes `**Resource`:
|
315
340
|
|
316
341
|
```ruby
|
317
342
|
class UserResource
|
318
|
-
def initialize(id
|
343
|
+
def initialize(id:)
|
319
344
|
@user = User.find(id)
|
320
345
|
@auth_info = auth_info
|
321
346
|
end
|
@@ -336,6 +361,10 @@ class UserResource
|
|
336
361
|
@user.profile
|
337
362
|
end
|
338
363
|
|
364
|
+
def visible?(context:)
|
365
|
+
# Your logic...
|
366
|
+
end
|
367
|
+
|
339
368
|
def text
|
340
369
|
# Return JSON data
|
341
370
|
{
|
@@ -348,6 +377,14 @@ class UserResource
|
|
348
377
|
end
|
349
378
|
```
|
350
379
|
|
380
|
+
```ruby
|
381
|
+
class MySchema < ActiveMcp::Schema::Base
|
382
|
+
User.all.each do |user|
|
383
|
+
resource UserResource.new(id: user.id)
|
384
|
+
end
|
385
|
+
end
|
386
|
+
```
|
387
|
+
|
351
388
|
### Resource Types
|
352
389
|
|
353
390
|
Resources can return two types of content:
|
@@ -366,7 +403,7 @@ end
|
|
366
403
|
```ruby
|
367
404
|
class ImageResource
|
368
405
|
def name
|
369
|
-
"
|
406
|
+
"Profile Image"
|
370
407
|
end
|
371
408
|
|
372
409
|
def uri
|
@@ -391,32 +428,50 @@ end
|
|
391
428
|
Resources can be protected using the same authorization mechanism as tools:
|
392
429
|
|
393
430
|
```ruby
|
394
|
-
def visible?
|
395
|
-
return false unless
|
396
|
-
return false unless auth_info[:type] == :bearer
|
431
|
+
def visible?(context: {})
|
432
|
+
return false unless context
|
433
|
+
return false unless context[:auth_info][:type] == :bearer
|
397
434
|
|
398
435
|
# Check if the token belongs to an admin
|
399
|
-
User.find_by_token(auth_info[:token])&.admin?
|
436
|
+
User.find_by_token(context[:auth_info][:token])&.admin?
|
400
437
|
end
|
401
438
|
```
|
402
439
|
|
403
|
-
##
|
440
|
+
## 📦 MCP Resource Templates
|
441
|
+
|
442
|
+
MCP Resource Teamplates allow you to define template of resources.
|
404
443
|
|
405
|
-
###
|
444
|
+
### Creating Resource Templates
|
406
445
|
|
407
|
-
|
446
|
+
Resources are Ruby classes `**ResourceTemplates`:
|
408
447
|
|
409
448
|
```ruby
|
410
|
-
class
|
411
|
-
|
449
|
+
class UserResourceTemplate
|
450
|
+
def name
|
451
|
+
"Users"
|
452
|
+
end
|
453
|
+
|
454
|
+
def uri_template
|
455
|
+
"data://localhost/users/{id}"
|
456
|
+
end
|
457
|
+
|
458
|
+
def mime_type
|
459
|
+
"application/json"
|
460
|
+
end
|
461
|
+
|
462
|
+
def description
|
463
|
+
"This is a test."
|
464
|
+
end
|
465
|
+
|
466
|
+
def visible?(context:)
|
467
|
+
# Your logic...
|
468
|
+
end
|
412
469
|
end
|
413
470
|
```
|
414
471
|
|
415
|
-
Update routes:
|
416
|
-
|
417
472
|
```ruby
|
418
|
-
|
419
|
-
|
473
|
+
class MySchema < ActiveMcp::Schema::Base
|
474
|
+
resource_template UserResourceTemplate.new
|
420
475
|
end
|
421
476
|
```
|
422
477
|
|
@@ -428,12 +483,12 @@ Create dedicated tool classes for each model or operation instead of generic too
|
|
428
483
|
|
429
484
|
```ruby
|
430
485
|
# ✅ GOOD: Specific tool for a single purpose
|
431
|
-
class SearchUsersTool < ActiveMcp::Tool
|
486
|
+
class SearchUsersTool < ActiveMcp::Tool::Base
|
432
487
|
# ...specific implementation
|
433
488
|
end
|
434
489
|
|
435
490
|
# ❌ BAD: Generic tool that dynamically loads models
|
436
|
-
class GenericSearchTool < ActiveMcp::Tool
|
491
|
+
class GenericSearchTool < ActiveMcp::Tool::Base
|
437
492
|
# Avoid this pattern - security and maintainability issues
|
438
493
|
end
|
439
494
|
```
|
@@ -443,7 +498,7 @@ end
|
|
443
498
|
Always validate and sanitize inputs in your tool implementations:
|
444
499
|
|
445
500
|
```ruby
|
446
|
-
def call(user_id:, **args)
|
501
|
+
def call(user_id:, **args, context: {})
|
447
502
|
# Validate input
|
448
503
|
unless user_id.is_a?(Integer) || user_id.to_s.match?(/^\d+$/)
|
449
504
|
raise "Invalid user ID format"
|
@@ -460,7 +515,7 @@ end
|
|
460
515
|
Return structured responses that are easy for AI to parse:
|
461
516
|
|
462
517
|
```ruby
|
463
|
-
def call(query:,
|
518
|
+
def call(query:, context: {})
|
464
519
|
results = User.search(query)
|
465
520
|
|
466
521
|
{
|
@@ -1,9 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "../concerns/active_mcp/authenticatable"
|
4
|
+
require_relative "../concerns/active_mcp/request_handlable"
|
5
|
+
require_relative "../concerns/active_mcp/resource_readable"
|
6
|
+
require_relative "../concerns/active_mcp/tool_executable"
|
7
|
+
|
3
8
|
module ActiveMcp
|
4
9
|
class BaseController < ActionController::Base
|
5
|
-
include RequestHandlable
|
6
|
-
include ResourceReadable
|
7
|
-
include ToolExecutable
|
10
|
+
include ::ActiveMcp::RequestHandlable
|
11
|
+
include ::ActiveMcp::ResourceReadable
|
12
|
+
include ::ActiveMcp::ToolExecutable
|
13
|
+
include ::ActiveMcp::Authenticatable
|
14
|
+
|
15
|
+
protect_from_forgery with: :null_session
|
16
|
+
skip_before_action :verify_authenticity_token
|
17
|
+
before_action :authenticate, only: [:index]
|
18
|
+
|
19
|
+
def index
|
20
|
+
if json_rpc_request?
|
21
|
+
handle_mcp_client_request
|
22
|
+
else
|
23
|
+
handle_mcp_server_request
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def schema
|
30
|
+
nil
|
31
|
+
end
|
8
32
|
end
|
9
33
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ActiveMcp
|
2
|
+
module Authenticatable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def authenticate
|
8
|
+
auth_header = request.headers["Authorization"]
|
9
|
+
if auth_header.present?
|
10
|
+
@context ||= {}
|
11
|
+
@context[:auth_info] = {
|
12
|
+
header: auth_header,
|
13
|
+
type: if auth_header.start_with?("Bearer ")
|
14
|
+
:bearer
|
15
|
+
elsif auth_header.start_with?("Basic ")
|
16
|
+
:basic
|
17
|
+
else
|
18
|
+
:unknown
|
19
|
+
end,
|
20
|
+
token: auth_header.split(" ").last
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def context
|
26
|
+
@context
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -4,20 +4,6 @@ module ActiveMcp
|
|
4
4
|
module RequestHandlable
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
-
included do
|
8
|
-
protect_from_forgery with: :null_session
|
9
|
-
skip_before_action :verify_authenticity_token
|
10
|
-
before_action :authenticate, only: [:index]
|
11
|
-
end
|
12
|
-
|
13
|
-
def index
|
14
|
-
if json_rpc_request?
|
15
|
-
handle_mcp_client_request
|
16
|
-
else
|
17
|
-
handle_mcp_server_request
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
7
|
private
|
22
8
|
|
23
9
|
def json_rpc_request?
|
@@ -26,7 +12,6 @@ module ActiveMcp
|
|
26
12
|
|
27
13
|
def handle_mcp_client_request
|
28
14
|
@id = params[:id]
|
29
|
-
@auth_info = auth_info
|
30
15
|
|
31
16
|
case params[:method]
|
32
17
|
when Method::INITIALIZE
|
@@ -36,19 +21,23 @@ module ActiveMcp
|
|
36
21
|
when Method::CANCELLED
|
37
22
|
render 'active_mcp/cancelled', formats: :json
|
38
23
|
when Method::RESOURCES_LIST
|
39
|
-
@resources =
|
24
|
+
@resources = schema.resources
|
40
25
|
@format = :jsonrpc
|
41
26
|
render 'active_mcp/resources_list', formats: :json
|
27
|
+
when Method::RESOURCES_TEMPLATES_LIST
|
28
|
+
@resource_templates = schema.resource_templates
|
29
|
+
@format = :jsonrpc
|
30
|
+
render 'active_mcp/resource_templates_list', formats: :json
|
42
31
|
when Method::RESOURCES_READ
|
43
|
-
@resource = read_resource(params:,
|
32
|
+
@resource = read_resource(params:, context:)
|
44
33
|
@format = :jsonrpc
|
45
34
|
render 'active_mcp/resources_read', formats: :json
|
46
35
|
when Method::TOOLS_LIST
|
47
|
-
@tools =
|
36
|
+
@tools = schema.tools
|
48
37
|
@format = :jsonrpc
|
49
38
|
render 'active_mcp/tools_list', formats: :json
|
50
39
|
when Method::TOOLS_CALL
|
51
|
-
@tool_result = execute_tool(params
|
40
|
+
@tool_result = execute_tool(params:, context:)
|
52
41
|
@format = :jsonrpc
|
53
42
|
render 'active_mcp/tools_call', formats: :json
|
54
43
|
else
|
@@ -58,23 +47,25 @@ module ActiveMcp
|
|
58
47
|
end
|
59
48
|
|
60
49
|
def handle_mcp_server_request
|
61
|
-
@auth_info = auth_info
|
62
|
-
|
63
50
|
case params[:method]
|
64
51
|
when Method::RESOURCES_LIST
|
65
|
-
@resources =
|
52
|
+
@resources = schema.resources
|
66
53
|
@format = :json
|
67
54
|
render 'active_mcp/resources_list', formats: :json
|
68
55
|
when Method::RESOURCES_READ
|
69
|
-
@resource = read_resource(params:,
|
56
|
+
@resource = read_resource(params:, context:)
|
70
57
|
@format = :json
|
71
58
|
render 'active_mcp/resources_read', formats: :json
|
59
|
+
when Method::RESOURCES_TEMPLATES_LIST
|
60
|
+
@resource_templates = schema.resource_templates
|
61
|
+
@format = :json
|
62
|
+
render 'active_mcp/resource_templates_list', formats: :json
|
72
63
|
when Method::TOOLS_LIST
|
73
|
-
@tools =
|
64
|
+
@tools = schema.tools
|
74
65
|
@format = :json
|
75
66
|
render 'active_mcp/tools_list', formats: :json
|
76
67
|
when Method::TOOLS_CALL
|
77
|
-
@tool_result = execute_tool(params
|
68
|
+
@tool_result = execute_tool(params:, context:)
|
78
69
|
@format = :json
|
79
70
|
render 'active_mcp/tools_call', formats: :json
|
80
71
|
else
|
@@ -83,25 +74,8 @@ module ActiveMcp
|
|
83
74
|
end
|
84
75
|
end
|
85
76
|
|
86
|
-
def
|
87
|
-
|
88
|
-
if auth_header.present?
|
89
|
-
@auth_info = {
|
90
|
-
header: auth_header,
|
91
|
-
type: if auth_header.start_with?("Bearer ")
|
92
|
-
:bearer
|
93
|
-
elsif auth_header.start_with?("Basic ")
|
94
|
-
:basic
|
95
|
-
else
|
96
|
-
:unknown
|
97
|
-
end,
|
98
|
-
token: auth_header.split(" ").last
|
99
|
-
}
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def auth_info
|
104
|
-
@auth_info
|
77
|
+
def context
|
78
|
+
@context ||= {}
|
105
79
|
end
|
106
80
|
end
|
107
81
|
end
|
@@ -4,11 +4,7 @@ module ActiveMcp
|
|
4
4
|
|
5
5
|
private
|
6
6
|
|
7
|
-
def
|
8
|
-
[]
|
9
|
-
end
|
10
|
-
|
11
|
-
def read_resource(params:, auth_info:)
|
7
|
+
def read_resource(params:, context:)
|
12
8
|
if params[:jsonrpc].present?
|
13
9
|
uri = params[:params][:uri]
|
14
10
|
else
|
@@ -22,7 +18,7 @@ module ActiveMcp
|
|
22
18
|
}
|
23
19
|
end
|
24
20
|
|
25
|
-
resource =
|
21
|
+
resource = schema.resources.find do |r|
|
26
22
|
r.uri == uri
|
27
23
|
end
|
28
24
|
|
@@ -33,7 +29,7 @@ module ActiveMcp
|
|
33
29
|
}
|
34
30
|
end
|
35
31
|
|
36
|
-
if resource.respond_to?(:visible?) && !resource.visible?
|
32
|
+
if resource.respond_to?(:visible?) && !resource.visible?(context:)
|
37
33
|
return {
|
38
34
|
isError: true,
|
39
35
|
contents: []
|
@@ -4,7 +4,7 @@ module ActiveMcp
|
|
4
4
|
|
5
5
|
private
|
6
6
|
|
7
|
-
def execute_tool(params:,
|
7
|
+
def execute_tool(params:, context: {})
|
8
8
|
if params[:jsonrpc].present?
|
9
9
|
tool_name = params[:params][:name]
|
10
10
|
tool_params = params[:params][:arguments]
|
@@ -25,11 +25,11 @@ module ActiveMcp
|
|
25
25
|
}
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
tc.
|
28
|
+
tool = schema.tools.find do |tc|
|
29
|
+
tc.name == tool_name
|
30
30
|
end
|
31
31
|
|
32
|
-
unless
|
32
|
+
unless tool
|
33
33
|
return {
|
34
34
|
isError: true,
|
35
35
|
content: [
|
@@ -41,7 +41,7 @@ module ActiveMcp
|
|
41
41
|
}
|
42
42
|
end
|
43
43
|
|
44
|
-
unless
|
44
|
+
unless tool.visible?(context:)
|
45
45
|
return {
|
46
46
|
isError: true,
|
47
47
|
content: [
|
@@ -69,7 +69,6 @@ module ActiveMcp
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
-
tool = tool_class.new
|
73
72
|
validation_result = tool.validate_arguments(arguments)
|
74
73
|
|
75
74
|
if validation_result.is_a?(Hash) && validation_result[:error]
|
@@ -86,13 +85,11 @@ module ActiveMcp
|
|
86
85
|
|
87
86
|
# Execute the tool
|
88
87
|
begin
|
89
|
-
arguments[:auth_info] = auth_info if auth_info.present?
|
90
|
-
|
91
88
|
return {
|
92
89
|
content: [
|
93
90
|
{
|
94
91
|
type: "text",
|
95
|
-
text: formatted(tool.call(**arguments
|
92
|
+
text: formatted(tool.call(**arguments, context:))
|
96
93
|
}
|
97
94
|
]
|
98
95
|
}
|
@@ -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.resourceTemplates do
|
7
|
+
json.array!(@resource_templates) do |resource|
|
8
|
+
json.name resource.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
|
17
|
+
json.array!(@resource_templates) do |resource|
|
18
|
+
json.name resource.name
|
19
|
+
json.uriTemplate resource.uri_template
|
20
|
+
json.mimeType resource.mime_type
|
21
|
+
json.description resource.description
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -3,8 +3,20 @@ json.id @id if @format == :jsonrpc && @id.present?
|
|
3
3
|
|
4
4
|
if @format == :jsonrpc
|
5
5
|
json.result do
|
6
|
-
json.tools
|
6
|
+
json.tools do
|
7
|
+
json.array!(@tools) do |tool|
|
8
|
+
json.name tool.name
|
9
|
+
json.description tool.description
|
10
|
+
json.inputSchema tool.class.schema
|
11
|
+
end
|
12
|
+
end
|
7
13
|
end
|
8
14
|
else
|
9
|
-
json.result
|
15
|
+
json.result do
|
16
|
+
json.array!(@tools) do |tool|
|
17
|
+
json.name tool.name
|
18
|
+
json.description tool.description
|
19
|
+
json.inputSchema tool.class.schema
|
20
|
+
end
|
21
|
+
end
|
10
22
|
end
|