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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6663bc950db47a5719e93db048f5601acb9dd58b87421fe0eb94e2c78cec2333
4
- data.tar.gz: ffacf7506cc82ad8fc680dd57f3f74ee50875e8bb7053ff460dc28e59c232cfa
3
+ metadata.gz: c9367d905ac3b7dfa87f27063c41f24f1ed93de9d88a5fc009010ed35bfafa85
4
+ data.tar.gz: 7b1ed7d1f79ad9487c92a4e0595727cc60125c3584ae7029be8d3e86f9c2537b
5
5
  SHA512:
6
- metadata.gz: 6631c864d9b2fbf566f5202eb31c16a4cb0154a0bcac33b23165c365adab03a01b7ee54da8996dd87660c78046457d2baa6c23f1e1e4dd5a57ea6c06601d84ca
7
- data.tar.gz: b3f85f31d012218cafba456a4ebe9b50de9261686aec9b40d1f48a783e74bf6dba9506e113fb0d4372f8e5f82b6000911ec40016151063759d1b2865b68c7abe
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
- - [⚙️ Advanced Configuration](#️-advanced-configuration)
40
- - [Custom Controller](#custom-controller)
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
- ### Using the Install Generator (Recommended)
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
- 1. Create a configuration initializer at `config/initializers/active_mcp.rb`
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
- After running the generator, follow the displayed instructions to create and configure your MCP tools.
89
+ ```bash
90
+ $ rails generate active_mcp:tool create_note
91
+ ```
95
92
 
96
- ### Manual Setup
93
+ ```ruby
94
+ class CreateNoteTool < ActiveMcp::Tool::Base
95
+ def name
96
+ "Create Note"
97
+ end
97
98
 
98
- If you prefer to set up manually:
99
+ def description
100
+ "Create Note"
101
+ end
99
102
 
100
- 1. Mount the ActiveMcp engine in your `config/routes.rb`:
103
+ argument :title, :string, required: true
104
+ argument :content, :string, required: true
101
105
 
102
- ```ruby
103
- Rails.application.routes.draw do
104
- mount ActiveMcp::Engine, at: "/mcp"
106
+ def call(title:, content:, context:)
107
+ note = Note.create(title: title, content: content)
105
108
 
106
- # Your other routes
109
+ "Created note with ID: #{note.id}"
110
+ end
107
111
  end
108
112
  ```
109
113
 
110
- 2. Create a tool by inheriting from `ActiveMcp::Tool`:
114
+ 3. Create schema for your application:
111
115
 
112
116
  ```ruby
113
- class CreateNoteTool < ActiveMcp::Tool
114
- description "Create Note"
115
-
116
- argument :title, :string, required: true
117
- argument :content, :string, required: true
117
+ class MySchema < ActiveMcp::Schema::Base
118
+ tool CreateNoteTool.new
119
+ end
120
+ ```
118
121
 
119
- def call(title:, content:)
120
- note = Note.create(title: title, content: content)
122
+ 4. Create controller ans set up routing:
121
123
 
122
- "Created note with ID: #{note.id}"
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
- description 'Search users by criteria'
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
- description "Admin-only tool"
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 self.visible?(auth_info)
258
- return false unless auth_info
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
- auth_info[:token] == "admin-token" || User.find_by_token(auth_info[:token])&.admin?
287
+ context[:auth_info] == "admin-token" || User.find_by_token(context[:auth_info])&.admin?
263
288
  end
264
289
 
265
- def call(command:, auth_info: nil)
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:, auth_info: nil, **args)
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 that inherit from `ActiveMcp::Resource`:
339
+ Resources are Ruby classes `**Resource`:
315
340
 
316
341
  ```ruby
317
342
  class UserResource
318
- def initialize(id:, auth_info: nil)
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
- "image"
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 auth_info
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
- ## ⚙️ Advanced Configuration
440
+ ## 📦 MCP Resource Templates
441
+
442
+ MCP Resource Teamplates allow you to define template of resources.
404
443
 
405
- ### Custom Controller
444
+ ### Creating Resource Templates
406
445
 
407
- Create a custom controller for advanced needs:
446
+ Resources are Ruby classes `**ResourceTemplates`:
408
447
 
409
448
  ```ruby
410
- class CustomMcpController < ActiveMcp::BaseController
411
- # Custom MCP handling logic
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
- Rails.application.routes.draw do
419
- post "/mcp", to: "custom_mcp#index"
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:, **args)
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 = resources_list
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:, auth_info:)
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 = ActiveMcp::Tool.authorized_tools(auth_info)
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: params, auth_info: auth_info)
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 = resources_list
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:, auth_info:)
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 = ActiveMcp::Tool.authorized_tools(auth_info)
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: params, auth_info: auth_info)
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 authenticate
87
- auth_header = request.headers["Authorization"]
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 resources_list
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 = resources_list.find do |r|
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:, auth_info:)
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
- tool_class = Tool.registered_tools.find do |tc|
29
- tc.tool_name == tool_name
28
+ tool = schema.tools.find do |tc|
29
+ tc.name == tool_name
30
30
  end
31
31
 
32
- unless tool_class
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 tool_class.visible?(auth_info)
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.symbolize_keys))
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 @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 @tools
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
@@ -0,0 +1,7 @@
1
+ require_relative "../../../app/controllers/active_mcp/base_controller"
2
+
3
+ module ActiveMcp
4
+ module Controller
5
+ Base = ActiveMcp::BaseController
6
+ end
7
+ end