active_mcp 0.7.0 → 0.9.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: c9367d905ac3b7dfa87f27063c41f24f1ed93de9d88a5fc009010ed35bfafa85
4
- data.tar.gz: 7b1ed7d1f79ad9487c92a4e0595727cc60125c3584ae7029be8d3e86f9c2537b
3
+ metadata.gz: 5e4d7ce1740274e7a16c0f4b42f0d1285997c068ed416e166313076f7629ddc1
4
+ data.tar.gz: 3ff81f15c762fd1e8393afc2f29c1ef462bc4a6b3dcf544b826b01a5c6e05e3f
5
5
  SHA512:
6
- metadata.gz: 5f429c060c395a06e9e146c68ea859a1750a00add2b28ba5fe3a968b20b8b335fcc3908a1f89b63aeaf24f092942af9edd4979eec2a3b380d431519ee35623d4
7
- data.tar.gz: 1a0bff75ee866fbba70a5549ee867f6260cd8e925a592b9a044b3769a91ba9955487e3798647c7fa6b356e4c9cbfbd3610550a713aa4cee97dfff896c7514c15
6
+ metadata.gz: 86bf41931c683bd3a462521fd8b95d379aeb901dc1ae4087a913edd7ca302bf5d018a37b49e4f3da043cc1e1133752e352b407804fe9dbd3d5c4fc2a3720071c
7
+ data.tar.gz: 6d91c11ebf3b8f8485d9a0acc13f75b64965c68fd5443f512d818e57f755989c5d744c5d76b1650f24bcac0d681643de881ef18a15997cb46cc9f37269a36c79
data/README.md CHANGED
@@ -36,6 +36,8 @@ A Ruby on Rails engine for the [Model Context Protocol (MCP)](https://modelconte
36
36
  - [Resource Types](#resource-types)
37
37
  - [📦 MCP Resource Templates](#-mcp-resource-templates)
38
38
  - [Creating Resource Templates](#creating-resource-templates)
39
+ - [💬 MCP Prompts](#-mcp-prompts)
40
+ - [Creating Prompt](#creating-prompt)
39
41
  - [💡 Best Practices](#-best-practices)
40
42
  - [1. Create Specific Tool Classes](#1-create-specific-tool-classes)
41
43
  - [2. Validate and Sanitize Inputs](#2-validate-and-sanitize-inputs)
@@ -92,7 +94,7 @@ $ rails generate active_mcp:tool create_note
92
94
 
93
95
  ```ruby
94
96
  class CreateNoteTool < ActiveMcp::Tool::Base
95
- def name
97
+ def tool_name
96
98
  "Create Note"
97
99
  end
98
100
 
@@ -213,7 +215,7 @@ MCP tools are Ruby classes that inherit from `ActiveMcp::Tool::Base` and define
213
215
 
214
216
  ```ruby
215
217
  class SearchUsersTool < ActiveMcp::Tool::Base
216
- def name
218
+ def tool_name
217
219
  "Search Users"
218
220
  end
219
221
 
@@ -268,7 +270,7 @@ Control access to tools by overriding the `visible?` class method:
268
270
 
269
271
  ```ruby
270
272
  class AdminOnlyTool < ActiveMcp::Tool::Base
271
- def name
273
+ def tool_name
272
274
  "Admin-only tool"
273
275
  end
274
276
 
@@ -339,13 +341,13 @@ MCP Resources allow you to share data and files with AI assistants. Resources ha
339
341
  Resources are Ruby classes `**Resource`:
340
342
 
341
343
  ```ruby
342
- class UserResource
344
+ class UserResource < ActiveMcp::Resource::Base
343
345
  def initialize(id:)
344
346
  @user = User.find(id)
345
347
  @auth_info = auth_info
346
348
  end
347
349
 
348
- def name
350
+ def resource_name
349
351
  @user.name
350
352
  end
351
353
 
@@ -401,8 +403,14 @@ end
401
403
  2. **Binary Content** - Use the `blob` method to return binary files:
402
404
 
403
405
  ```ruby
404
- class ImageResource
405
- def name
406
+ class ImageResource < ActiveMcp::Resource::Base
407
+ class << self
408
+ def mime_type
409
+ "image/png"
410
+ end
411
+ end
412
+
413
+ def resource_name
406
414
  "Profile Image"
407
415
  end
408
416
 
@@ -410,10 +418,6 @@ class ImageResource
410
418
  "data://localhost/image"
411
419
  end
412
420
 
413
- def mime_type
414
- "image/png"
415
- end
416
-
417
421
  def description
418
422
  "Profile image"
419
423
  end
@@ -446,32 +450,123 @@ MCP Resource Teamplates allow you to define template of resources.
446
450
  Resources are Ruby classes `**ResourceTemplates`:
447
451
 
448
452
  ```ruby
449
- class UserResourceTemplate
450
- def name
451
- "Users"
453
+ class UserResource < ActiveMcp::Resource::Base
454
+ class << self
455
+ def resource_template_name
456
+ "Users"
457
+ end
458
+
459
+ def uri_template
460
+ "data://localhost/users/{id}"
461
+ end
462
+
463
+ def mime_type
464
+ "application/json"
465
+ end
466
+
467
+ def description
468
+ "This is a test."
469
+ end
470
+
471
+ def visible?(context:)
472
+ # Your logic...
473
+ end
452
474
  end
453
475
 
454
- def uri_template
455
- "data://localhost/users/{id}"
476
+ argument :id, ->(value) do
477
+ User.all.pluck(:id).filter { _1.match(value) }
456
478
  end
457
479
 
458
- def mime_type
459
- "application/json"
480
+ def initialize(id:)
481
+ @user = User.find(id)
482
+ end
483
+
484
+ def resource_name
485
+ @user.name
460
486
  end
461
487
 
462
488
  def description
463
- "This is a test."
489
+ @user.profile
464
490
  end
465
491
 
466
- def visible?(context:)
467
- # Your logic...
492
+ def uri
493
+ "data://localhost/users/#{@user.name}"
494
+ end
495
+
496
+ def text
497
+ { name: @user.name }
498
+ end
499
+ end
500
+ ```
501
+
502
+ ```ruby
503
+ class MySchema < ActiveMcp::Schema::Base
504
+ User.all.each do |user|
505
+ resource UserResource.new(id: user.id)
506
+ end
507
+ end
508
+ ```
509
+
510
+ ## 💬 MCP Prompts
511
+
512
+ MCP Prompts allow you to define prompt set.
513
+
514
+ ### Creating Prompt
515
+
516
+ Resources are Ruby classes `**Prompt`:
517
+
518
+ ```ruby
519
+ class HelloPrompt < ActiveMcp::Prompt::Base
520
+ class << self
521
+ def prompt_name
522
+ "hello"
523
+ end
524
+
525
+ def description
526
+ "This is a test."
527
+ end
528
+
529
+ def visible?(context:)
530
+ # Your logic...
531
+ end
532
+ end
533
+
534
+ argument :name, ->(value) do
535
+ User.all.pluck(:name).filter { _1.match(value) }
536
+ end
537
+
538
+ def initialize(name:)
539
+ @name = name
540
+ end
541
+
542
+ def messages
543
+ [
544
+ ActiveMcp::Message::Text.new(
545
+ role: "user",
546
+ text: "Hello! #{@name}"
547
+ ),
548
+ ActiveMcp::Message::Image.new(
549
+ role: "assistant",
550
+ data: File.read(file),
551
+ mime_type: "image/png"
552
+ ),
553
+ ActiveMcp::Message::Audio.new(
554
+ role: "user",
555
+ data: File.read(file),
556
+ mime_type: "audio/mpeg"
557
+ ),
558
+ ActiveMcp::Message::Resource.new(
559
+ role: "assistant",
560
+ resource: UserResource.new(name: @name)
561
+ )
562
+ ]
468
563
  end
469
564
  end
470
565
  ```
471
566
 
472
567
  ```ruby
473
568
  class MySchema < ActiveMcp::Schema::Base
474
- resource_template UserResourceTemplate.new
569
+ prompt HelloPrompt
475
570
  end
476
571
  ```
477
572
 
@@ -40,6 +40,19 @@ module ActiveMcp
40
40
  @tool_result = execute_tool(params:, context:)
41
41
  @format = :jsonrpc
42
42
  render 'active_mcp/tools_call', formats: :json
43
+ when Method::COMPLETION_COMPLETE
44
+ type = params.dig(:params, :ref, :type)
45
+ @completion = ActiveMcp::Completion.new.complete(params: params[:params], context:, refs: type === "ref/resource" ? schema.resource_templates : schema.prompts)
46
+ @format = :jsonrpc
47
+ render "active_mcp/completion_complete", formats: :json
48
+ when Method::PROMPTS_LIST
49
+ @prompts = schema.prompts
50
+ @format = :jsonrpc
51
+ render 'active_mcp/prompts_list', formats: :json
52
+ when Method::PROMPTS_GET
53
+ @prompt = schema.prompts.find { _1.prompt_name == params[:params][:name] }.new(**params[:params][:arguments].permit!.to_h.symbolize_keys)
54
+ @format = :jsonrpc
55
+ render 'active_mcp/prompts_get', formats: :json
43
56
  else
44
57
  @format = :jsonrpc
45
58
  render 'active_mcp/no_method', formats: :json
@@ -68,6 +81,19 @@ module ActiveMcp
68
81
  @tool_result = execute_tool(params:, context:)
69
82
  @format = :json
70
83
  render 'active_mcp/tools_call', formats: :json
84
+ when Method::COMPLETION_COMPLETE
85
+ type = params.dig(:params, :ref, :type)
86
+ @completion = ActiveMcp::Completion.new.complete(params: params[:params], context:, refs: type == "ref/resource" ? schema.resource_templates : schema.prompts)
87
+ @format = :json
88
+ render "active_mcp/completion_complete", formats: :json
89
+ when Method::PROMPTS_LIST
90
+ @prompts = schema.prompts
91
+ @format = :json
92
+ render 'active_mcp/prompts_list', formats: :json
93
+ when Method::PROMPTS_GET
94
+ @prompt = schema.prompts&.find { _1.prompt_name == params[:params][:name] }.new(**params[:params][:arguments].permit!.to_h.symbolize_keys)
95
+ @format = :json
96
+ render 'active_mcp/prompts_get', formats: :json
71
97
  else
72
98
  @format = :json
73
99
  render 'active_mcp/no_method', formats: :json
@@ -37,13 +37,13 @@ module ActiveMcp
37
37
  end
38
38
 
39
39
  begin
40
- if resource.respond_to?(:text) && content = resource.text
40
+ if resource.respond_to?(:text) && content = resource.content
41
41
  return {
42
42
  contents: [
43
43
  {
44
44
  uri:,
45
- mimeType: resource.mime_type,
46
- text: formatted(content)
45
+ mimeType: resource.class.mime_type,
46
+ text: content
47
47
  }
48
48
  ]
49
49
  }
@@ -52,7 +52,7 @@ module ActiveMcp
52
52
  contents: [
53
53
  {
54
54
  uri:,
55
- mimeType: resource.mime_type,
55
+ mimeType: resource.class.mime_type,
56
56
  blob: Base64.strict_encode64(content)
57
57
  }
58
58
  ]
@@ -65,16 +65,5 @@ module ActiveMcp
65
65
  }
66
66
  end
67
67
  end
68
-
69
- def formatted(object)
70
- case object
71
- when String
72
- object
73
- when Hash
74
- object.to_json
75
- else
76
- object.to_s
77
- end
78
- end
79
68
  end
80
69
  end
@@ -26,7 +26,7 @@ module ActiveMcp
26
26
  end
27
27
 
28
28
  tool = schema.tools.find do |tc|
29
- tc.name == tool_name
29
+ tc.tool_name == tool_name
30
30
  end
31
31
 
32
32
  unless tool
@@ -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 do
6
+ json.completion do
7
+ json.values @completion[:values]
8
+ json.total @completion[:total]
9
+ end
10
+ end
11
+ else
12
+ json.result do
13
+ json.values @completion[:values]
14
+ json.total @completion[:total]
15
+ end
16
+ end
@@ -0,0 +1,12 @@
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.description @prompt.class.description
7
+ json.messages @prompt.messages.map(&:to_h)
8
+ end
9
+ else
10
+ json.description @prompt.class.description
11
+ json.messages @prompt.messages.map(&:to_h)
12
+ end
@@ -0,0 +1,22 @@
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.prompts do
7
+ json.array!(@prompts) do |prompt|
8
+ json.name prompt.prompt_name
9
+ json.description prompt.description
10
+ json.arguments prompt.arguments.map { _1.except(:complete) }
11
+ end
12
+ end
13
+ end
14
+ else
15
+ json.result do
16
+ json.array!(@prompts) do |prompt|
17
+ json.name prompt.prompt_name
18
+ json.description prompt.description
19
+ json.arguments prompt.arguments.map { _1.except(:complete) }
20
+ end
21
+ end
22
+ end
@@ -5,7 +5,7 @@ if @format == :jsonrpc
5
5
  json.result do
6
6
  json.resourceTemplates do
7
7
  json.array!(@resource_templates) do |resource|
8
- json.name resource.name
8
+ json.name resource.resource_template_name
9
9
  json.uriTemplate resource.uri_template
10
10
  json.mimeType resource.mime_type
11
11
  json.description resource.description
@@ -15,7 +15,7 @@ if @format == :jsonrpc
15
15
  else
16
16
  json.result do
17
17
  json.array!(@resource_templates) do |resource|
18
- json.name resource.name
18
+ json.name resource.resource_template_name
19
19
  json.uriTemplate resource.uri_template
20
20
  json.mimeType resource.mime_type
21
21
  json.description resource.description
@@ -5,9 +5,9 @@ if @format == :jsonrpc
5
5
  json.result do
6
6
  json.resources do
7
7
  json.array!(@resources) do |resource|
8
- json.name resource.name
8
+ json.name resource.resource_name
9
9
  json.uri resource.uri
10
- json.mimeType resource.mime_type
10
+ json.mimeType resource.class.mime_type
11
11
  json.description resource.description
12
12
  end
13
13
  end
@@ -15,9 +15,9 @@ if @format == :jsonrpc
15
15
  else
16
16
  json.result do
17
17
  json.array!(@resources) do |resource|
18
- json.name resource.name
18
+ json.name resource.resource_name
19
19
  json.uri resource.uri
20
- json.mimeType resource.mime_type
20
+ json.mimeType resource.class.mime_type
21
21
  json.description resource.description
22
22
  end
23
23
  end
@@ -5,7 +5,7 @@ if @format == :jsonrpc
5
5
  json.result do
6
6
  json.tools do
7
7
  json.array!(@tools) do |tool|
8
- json.name tool.name
8
+ json.name tool.tool_name
9
9
  json.description tool.description
10
10
  json.inputSchema tool.class.schema
11
11
  end
@@ -14,7 +14,7 @@ if @format == :jsonrpc
14
14
  else
15
15
  json.result do
16
16
  json.array!(@tools) do |tool|
17
- json.name tool.name
17
+ json.name tool.tool_name
18
18
  json.description tool.description
19
19
  json.inputSchema tool.class.schema
20
20
  end
@@ -0,0 +1,20 @@
1
+ module ActiveMcp
2
+ class Completion
3
+ def complete(params: {}, context: {}, refs: [])
4
+ ref_name = params.dig(:ref, :name)
5
+ uri_template = params.dig(:ref, :uri)
6
+ arg_name = params.dig(:argument, :name)
7
+ value = params.dig(:argument, :value)
8
+
9
+ if uri_template
10
+ resource_class = refs.find { _1.uri_template == uri_template }
11
+ values = resource_class.arguments[arg_name.to_sym].call(value)
12
+ { values:, total: values.length }
13
+ elsif ref_name
14
+ prompt_class = refs.find { _1.prompt_name == ref_name }
15
+ values = prompt_class.arguments.find { _1[:name] == arg_name.to_sym }[:complete].call(value)
16
+ { values:, total: values.length }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -7,6 +7,7 @@ module ActiveMcp
7
7
  Rails.root.join("app", "mcp", "tools"),
8
8
  Rails.root.join("app", "mcp", "resources"),
9
9
  Rails.root.join("app", "mcp", "resource_templates"),
10
+ Rails.root.join("app", "mcp", "prompts"),
10
11
  Rails.root.join("app", "mcp", "schemas")
11
12
  ].each do |tools_path|
12
13
  if Dir.exist?(tools_path)
@@ -0,0 +1,22 @@
1
+ module ActiveMcp
2
+ module Message
3
+ class Audio
4
+ def initialize(role:, data:, mime_type:)
5
+ @role = role
6
+ @data = data
7
+ @mime_type = mime_type
8
+ end
9
+
10
+ def to_h
11
+ {
12
+ role: @role,
13
+ content: {
14
+ type: "audio",
15
+ data: Base64.strict_encode64(@data),
16
+ mimeType: @mime_type
17
+ }
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module ActiveMcp
2
+ module Message
3
+ class Image
4
+ def initialize(role:, data:, mime_type:)
5
+ @role = role
6
+ @data = data
7
+ @mime_type = mime_type
8
+ end
9
+
10
+ def to_h
11
+ {
12
+ role: @role,
13
+ content: {
14
+ type: "image",
15
+ data: Base64.strict_encode64(@data),
16
+ mimeType: @mime_type
17
+ }
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ module ActiveMcp
2
+ module Message
3
+ class Resource
4
+ def initialize(role:, resource:)
5
+ @role = role
6
+ @resource = resource
7
+ end
8
+
9
+ def to_h
10
+ {
11
+ role: @role,
12
+ content: {
13
+ type: "resource",
14
+ resource: {
15
+ uri: @resource.uri,
16
+ mimeType: @resource.class.mime_type,
17
+ text: @resource.content,
18
+ }
19
+ }
20
+ }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ module ActiveMcp
2
+ module Message
3
+ class Text
4
+ def initialize(role:, text:)
5
+ @role = role
6
+ @text = text
7
+ end
8
+
9
+ def to_h
10
+ {
11
+ role: @role,
12
+ content: {
13
+ type: "text",
14
+ text: @text
15
+ }
16
+ }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,37 @@
1
+ module ActiveMcp
2
+ module Prompt
3
+ class Base
4
+ class << self
5
+ attr_reader :arguments
6
+
7
+ def argument(name, required: false, description: nil, complete: ->(){})
8
+ @arguments ||= []
9
+
10
+ @arguments << {
11
+ name:,
12
+ description:,
13
+ required:,
14
+ complete:
15
+ }
16
+ end
17
+ end
18
+
19
+ def initialize(*args, context: {})
20
+ end
21
+
22
+ def prompt_name
23
+ end
24
+
25
+ def description
26
+ end
27
+
28
+ def visible?(context: {})
29
+ true
30
+ end
31
+
32
+ def messages
33
+ raise NotImplementedError, "#{self.class.name}#messages must be implemented"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,49 @@
1
+ require "json-schema"
2
+
3
+ module ActiveMcp
4
+ module Resource
5
+ class Base
6
+ class << self
7
+ attr_reader :schema, :arguments
8
+
9
+ def resource_template_name
10
+ end
11
+
12
+ def description
13
+ end
14
+
15
+ def mime_type
16
+ end
17
+
18
+ def argument(name, complete)
19
+ @arguments = {}
20
+ @arguments[name] = complete
21
+ end
22
+ end
23
+
24
+ def initialize
25
+ end
26
+
27
+ def resource_name
28
+ end
29
+
30
+ def description
31
+ end
32
+
33
+ def visible?(context: {})
34
+ true
35
+ end
36
+
37
+ def content
38
+ case text
39
+ when String
40
+ text
41
+ when Hash
42
+ text.to_json
43
+ else
44
+ text.to_s
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -2,22 +2,27 @@ module ActiveMcp
2
2
  module Schema
3
3
  class Base
4
4
  class << self
5
- attr_reader :resources, :resource_templates, :tools
5
+ attr_reader :resources, :resource_templates, :tools, :prompts
6
6
 
7
7
  def resource(klass)
8
8
  @resources ||= []
9
9
  @resources << klass
10
- end
11
10
 
12
- def resource_template(klass)
13
- @resource_templates ||= []
14
- @resource_templates << klass
11
+ if klass.class.respond_to?(:uri_template)
12
+ @resource_templates ||= []
13
+ @resource_templates << klass.class unless klass.class.in?(@resource_templates)
14
+ end
15
15
  end
16
16
 
17
17
  def tool(klass)
18
18
  @tools ||= []
19
19
  @tools << klass
20
20
  end
21
+
22
+ def prompt(klass)
23
+ @prompts ||= []
24
+ @prompts << klass
25
+ end
21
26
  end
22
27
 
23
28
  def initialize(context: {})
@@ -25,22 +30,28 @@ module ActiveMcp
25
30
  end
26
31
 
27
32
  def resources
28
- self.class.resources.filter do |resource|
33
+ self.class.resources&.filter do |resource|
29
34
  !resource.respond_to?(:visible?) || resource.visible?(context: @context)
30
35
  end
31
36
  end
32
37
 
33
38
  def resource_templates
34
- self.class.resource_templates.filter do |tool_resource|
35
- !tool_resource.respond_to?(:visible?) || tool_resource.visible?(context: @context)
39
+ self.class.resource_templates&.filter do |template|
40
+ !template.respond_to?(:visible?) || template.visible?(context: @context)
36
41
  end
37
42
  end
38
43
 
39
44
  def tools
40
- self.class.tools.filter do |tool|
45
+ self.class.tools&.filter do |tool|
41
46
  !tool.respond_to?(:visible?) || tool.visible?(context: @context)
42
47
  end
43
48
  end
49
+
50
+ def prompts
51
+ self.class.prompts&.filter do |resource|
52
+ !resource.respond_to?(:visible?) || resource.visible?(context: @context)
53
+ end
54
+ end
44
55
  end
45
56
  end
46
57
  end
@@ -11,5 +11,8 @@ module ActiveMcp
11
11
  RESOURCES_LIST = "resources/list"
12
12
  RESOURCES_READ = "resources/read"
13
13
  RESOURCES_TEMPLATES_LIST = "resources/templates/list"
14
+ COMPLETION_COMPLETE = "completion/complete"
15
+ PROMPTS_LIST = "prompts/list"
16
+ PROMPTS_GET = "prompts/get"
14
17
  end
15
18
  end
@@ -31,16 +31,6 @@ module ActiveMcp
31
31
  private
32
32
 
33
33
  def handle_request(request)
34
- allowed_methods = [
35
- Method::INITIALIZE,
36
- Method::INITIALIZED,
37
- Method::PING
38
- ]
39
-
40
- if !@initialized && !allowed_methods.include?(request[:method])
41
- return error_response(request[:id], ErrorCode::NOT_INITIALIZED, "Server not initialized")
42
- end
43
-
44
34
  case request[:method]
45
35
  when Method::INITIALIZE
46
36
  handle_initialize(request)
@@ -58,6 +48,12 @@ module ActiveMcp
58
48
  handle_call_tool(request)
59
49
  when Method::RESOURCES_READ
60
50
  handle_read_resource(request)
51
+ when Method::COMPLETION_COMPLETE
52
+ handle_complete(request)
53
+ when Method::PROMPTS_LIST
54
+ handle_list_prompts(request)
55
+ when Method::PROMPTS_GET
56
+ handle_get_prompt(request)
61
57
  else
62
58
  error_response(request[:id], ErrorCode::METHOD_NOT_FOUND, "Unknown method: #{request[:method]}")
63
59
  end
@@ -86,13 +82,9 @@ module ActiveMcp
86
82
  result: {
87
83
  protocolVersion: PROTOCOL_VERSION,
88
84
  capabilities: {
89
- resources: {
90
- subscribe: false,
91
- listChanged: false
92
- },
93
- tools: {
94
- listChanged: false
95
- }
85
+ resources: {},
86
+ tools: {},
87
+ prompts: {},
96
88
  },
97
89
  serverInfo: {
98
90
  name: @server.name,
@@ -189,7 +181,53 @@ module ActiveMcp
189
181
 
190
182
  success_response(request[:id], result)
191
183
  rescue => e
192
- Server.("Error reading resource #{uri}", e)
184
+ Server.log_error("Error reading resource #{uri}", e)
185
+ error_response(request[:id], ErrorCode::INTERNAL_ERROR, "An error occurred while reading the resource")
186
+ end
187
+ end
188
+
189
+ def handle_complete(request)
190
+ begin
191
+ result = @server.fetch(
192
+ params: {
193
+ method: Method::COMPLETION_COMPLETE,
194
+ params: request[:params],
195
+ }
196
+ )
197
+ success_response(request[:id], { completion: result[:result] })
198
+ rescue => e
199
+ Server.log_error("Error reading resource #{uri}", e)
200
+ error_response(request[:id], ErrorCode::INTERNAL_ERROR, "An error occurred while reading the resource")
201
+ end
202
+ end
203
+
204
+ def handle_list_prompts(request)
205
+ begin
206
+ result = @server.fetch(params: { method: Method::PROMPTS_LIST })
207
+ success_response(request[:id], { prompts: result[:result] })
208
+ rescue => e
209
+ Server.log_error("Error reading resource #{uri}", e)
210
+ error_response(request[:id], ErrorCode::INTERNAL_ERROR, "An error occurred while reading the resource")
211
+ end
212
+ end
213
+
214
+ def handle_get_prompt(request)
215
+ name = request.dig(:params, :name)
216
+ arguments = request.dig(:params, :arguments)
217
+ begin
218
+ result = @server.fetch(
219
+ params: {
220
+ method: Method::PROMPTS_GET,
221
+ params: {
222
+ name:,
223
+ arguments:,
224
+ }
225
+ }
226
+ )
227
+
228
+ success_response(request[:id], result)
229
+ rescue => e
230
+ Server.log_error("Error reading resource #{uri}", e)
193
231
  error_response(request[:id], ErrorCode::INTERNAL_ERROR, "An error occurred while reading the resource")
194
232
  end
195
233
  end
@@ -26,7 +26,7 @@ module ActiveMcp
26
26
  def initialize
27
27
  end
28
28
 
29
- def name
29
+ def tool_name
30
30
  end
31
31
 
32
32
  def description
@@ -1,3 +1,3 @@
1
1
  module ActiveMcp
2
- VERSION = "0.7.0"
2
+ VERSION = "0.9.0"
3
3
  end
data/lib/active_mcp.rb CHANGED
@@ -5,7 +5,14 @@ require_relative "active_mcp/version"
5
5
  require_relative "active_mcp/configuration"
6
6
  require_relative "active_mcp/schema/base"
7
7
  require_relative "active_mcp/tool/base"
8
+ require_relative "active_mcp/resource/base"
9
+ require_relative "active_mcp/prompt/base"
10
+ require_relative "active_mcp/message/text"
11
+ require_relative "active_mcp/message/image"
12
+ require_relative "active_mcp/message/audio"
13
+ require_relative "active_mcp/message/resource"
8
14
  require_relative "active_mcp/server"
15
+ require_relative "active_mcp/completion"
9
16
 
10
17
  if defined? ::Rails
11
18
  require_relative "active_mcp/engine"
@@ -1,5 +1,5 @@
1
1
  class <%= class_name %>
2
- def name
2
+ def resource_name
3
3
  "<%= file_name %>"
4
4
  end
5
5
 
@@ -1,5 +1,5 @@
1
1
  class <%= class_name %> < ActiveMcp::Tool::Base
2
- def name
2
+ def tool_name
3
3
  "<%= file_name.humanize %>"
4
4
  end
5
5
 
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.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Moeki Kawakami
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-04-07 00:00:00.000000000 Z
10
+ date: 2025-04-08 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -73,9 +73,12 @@ files:
73
73
  - app/controllers/concerns/active_mcp/resource_readable.rb
74
74
  - app/controllers/concerns/active_mcp/tool_executable.rb
75
75
  - app/views/active_mcp/cancelled.json.jbuilder
76
+ - app/views/active_mcp/completion_complete.json.jbuilder
76
77
  - app/views/active_mcp/initialize.json.jbuilder
77
78
  - app/views/active_mcp/initialized.json.jbuilder
78
79
  - app/views/active_mcp/no_method.json.jbuilder
80
+ - app/views/active_mcp/prompts_get.json.jbuilder
81
+ - app/views/active_mcp/prompts_list.json.jbuilder
79
82
  - app/views/active_mcp/resource_templates_list.json.jbuilder
80
83
  - app/views/active_mcp/resources_list.json.jbuilder
81
84
  - app/views/active_mcp/resources_read.json.jbuilder
@@ -83,9 +86,16 @@ files:
83
86
  - app/views/active_mcp/tools_list.json.jbuilder
84
87
  - config/routes.rb
85
88
  - lib/active_mcp.rb
89
+ - lib/active_mcp/completion.rb
86
90
  - lib/active_mcp/configuration.rb
87
91
  - lib/active_mcp/controller/base.rb
88
92
  - lib/active_mcp/engine.rb
93
+ - lib/active_mcp/message/audio.rb
94
+ - lib/active_mcp/message/image.rb
95
+ - lib/active_mcp/message/resource.rb
96
+ - lib/active_mcp/message/text.rb
97
+ - lib/active_mcp/prompt/base.rb
98
+ - lib/active_mcp/resource/base.rb
89
99
  - lib/active_mcp/schema/base.rb
90
100
  - lib/active_mcp/server.rb
91
101
  - lib/active_mcp/server/error_codes.rb