active_mcp 0.8.0 → 0.9.1
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 +73 -8
- data/app/controllers/concerns/active_mcp/request_handlable.rb +18 -2
- data/app/controllers/concerns/active_mcp/resource_readable.rb +2 -13
- data/app/controllers/concerns/active_mcp/tool_executable.rb +1 -1
- data/app/views/active_mcp/prompts_get.json.jbuilder +12 -0
- data/app/views/active_mcp/prompts_list.json.jbuilder +22 -0
- data/app/views/active_mcp/resource_templates_list.json.jbuilder +2 -2
- data/app/views/active_mcp/resources_list.json.jbuilder +2 -2
- data/app/views/active_mcp/tools_list.json.jbuilder +2 -2
- data/lib/active_mcp/completion.rb +3 -1
- data/lib/active_mcp/engine.rb +1 -0
- data/lib/active_mcp/message/audio.rb +22 -0
- data/lib/active_mcp/message/image.rb +22 -0
- data/lib/active_mcp/message/resource.rb +24 -0
- data/lib/active_mcp/message/text.rb +20 -0
- data/lib/active_mcp/prompt/base.rb +37 -0
- data/lib/active_mcp/resource/base.rb +21 -1
- data/lib/active_mcp/schema/base.rb +15 -4
- data/lib/active_mcp/server/method.rb +2 -0
- data/lib/active_mcp/server/protocol_handler.rb +40 -19
- data/lib/active_mcp/tool/base.rb +1 -1
- data/lib/active_mcp/version.rb +1 -1
- data/lib/active_mcp.rb +5 -1
- data/lib/generators/active_mcp/resource/templates/resource.rb.erb +1 -1
- data/lib/generators/active_mcp/tool/templates/tool.rb.erb +1 -1
- metadata +8 -2
- data/lib/active_mcp/controller/base.rb +0 -7
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: de9c4899c65706f1fb1281e3ac52c7eb5399e4ac6e0224dce35e0ef532779df7
         | 
| 4 | 
            +
              data.tar.gz: ad0abe6c7af1b985260fbea629302fa5bcb69f1ac1c94455ccb187e771664691
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 8416f28a91cfbc836a78e05b5790b70e0236450f6d901f48df2070dae02095f5e8f436cb39f05dc2a4edd57240c5c8bba99e1a7a32b365c04cac6ec5ed32faaa
         | 
| 7 | 
            +
              data.tar.gz: b3cacc88a0649c82959aaf7fa9b3f39fc0d9b9f3c2c1135ad82282667c92ecda8ce71ddc84d7133694a790fcb6a69caa3c45c48f7196662d4952e9c56d304aba
         | 
    
        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  | 
| 97 | 
            +
              def tool_name
         | 
| 96 98 | 
             
                "Create Note"
         | 
| 97 99 | 
             
              end
         | 
| 98 100 |  | 
| @@ -122,7 +124,7 @@ end | |
| 122 124 | 
             
            4. Create controller ans set up routing:
         | 
| 123 125 |  | 
| 124 126 | 
             
            ```ruby
         | 
| 125 | 
            -
            class MyMcpController < ActiveMcp:: | 
| 127 | 
            +
            class MyMcpController < ActiveMcp::BaseController
         | 
| 126 128 | 
             
              def schema
         | 
| 127 129 | 
             
                MySchema.new(context:)
         | 
| 128 130 | 
             
              end
         | 
| @@ -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  | 
| 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  | 
| 273 | 
            +
              def tool_name
         | 
| 272 274 | 
             
                "Admin-only tool"
         | 
| 273 275 | 
             
              end
         | 
| 274 276 |  | 
| @@ -345,7 +347,7 @@ class UserResource < ActiveMcp::Resource::Base | |
| 345 347 | 
             
                @auth_info = auth_info
         | 
| 346 348 | 
             
              end
         | 
| 347 349 |  | 
| 348 | 
            -
              def  | 
| 350 | 
            +
              def resource_name
         | 
| 349 351 | 
             
                @user.name
         | 
| 350 352 | 
             
              end
         | 
| 351 353 |  | 
| @@ -408,7 +410,7 @@ class ImageResource < ActiveMcp::Resource::Base | |
| 408 410 | 
             
                end
         | 
| 409 411 | 
             
              end
         | 
| 410 412 |  | 
| 411 | 
            -
              def  | 
| 413 | 
            +
              def resource_name
         | 
| 412 414 | 
             
                "Profile Image"
         | 
| 413 415 | 
             
              end
         | 
| 414 416 |  | 
| @@ -450,7 +452,7 @@ Resources are Ruby classes `**ResourceTemplates`: | |
| 450 452 | 
             
            ```ruby
         | 
| 451 453 | 
             
            class UserResource < ActiveMcp::Resource::Base
         | 
| 452 454 | 
             
              class << self
         | 
| 453 | 
            -
                def  | 
| 455 | 
            +
                def resource_template_name
         | 
| 454 456 | 
             
                  "Users"
         | 
| 455 457 | 
             
                end
         | 
| 456 458 |  | 
| @@ -479,7 +481,7 @@ class UserResource < ActiveMcp::Resource::Base | |
| 479 481 | 
             
                @user = User.find(id)
         | 
| 480 482 | 
             
              end
         | 
| 481 483 |  | 
| 482 | 
            -
              def  | 
| 484 | 
            +
              def resource_name
         | 
| 483 485 | 
             
                @user.name
         | 
| 484 486 | 
             
              end
         | 
| 485 487 |  | 
| @@ -505,6 +507,69 @@ class MySchema < ActiveMcp::Schema::Base | |
| 505 507 | 
             
            end
         | 
| 506 508 | 
             
            ```
         | 
| 507 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 | 
            +
                ]
         | 
| 563 | 
            +
              end
         | 
| 564 | 
            +
            end
         | 
| 565 | 
            +
            ```
         | 
| 566 | 
            +
             | 
| 567 | 
            +
            ```ruby
         | 
| 568 | 
            +
            class MySchema < ActiveMcp::Schema::Base
         | 
| 569 | 
            +
              prompt HelloPrompt
         | 
| 570 | 
            +
            end
         | 
| 571 | 
            +
            ```
         | 
| 572 | 
            +
             | 
| 508 573 | 
             
            ## 💡 Best Practices
         | 
| 509 574 |  | 
| 510 575 | 
             
            ### 1. Create Specific Tool Classes
         | 
| @@ -42,9 +42,17 @@ module ActiveMcp | |
| 42 42 | 
             
                    render 'active_mcp/tools_call', formats: :json
         | 
| 43 43 | 
             
                  when Method::COMPLETION_COMPLETE
         | 
| 44 44 | 
             
                    type = params.dig(:params, :ref, :type)
         | 
| 45 | 
            -
                    @completion = ActiveMcp::Completion.new.complete(params: params[:params], context:, refs: type === "ref/resource" ? schema.resource_templates :  | 
| 45 | 
            +
                    @completion = ActiveMcp::Completion.new.complete(params: params[:params], context:, refs: type === "ref/resource" ? schema.resource_templates : schema.prompts)
         | 
| 46 46 | 
             
                    @format = :jsonrpc
         | 
| 47 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
         | 
| 48 56 | 
             
                  else
         | 
| 49 57 | 
             
                    @format = :jsonrpc
         | 
| 50 58 | 
             
                    render 'active_mcp/no_method', formats: :json
         | 
| @@ -75,9 +83,17 @@ module ActiveMcp | |
| 75 83 | 
             
                    render 'active_mcp/tools_call', formats: :json
         | 
| 76 84 | 
             
                  when Method::COMPLETION_COMPLETE
         | 
| 77 85 | 
             
                    type = params.dig(:params, :ref, :type)
         | 
| 78 | 
            -
                    @completion = ActiveMcp::Completion.new.complete(params: params[:params], context:, refs: type == "ref/resource" ? schema.resource_templates :  | 
| 86 | 
            +
                    @completion = ActiveMcp::Completion.new.complete(params: params[:params], context:, refs: type == "ref/resource" ? schema.resource_templates : schema.prompts)
         | 
| 79 87 | 
             
                    @format = :json
         | 
| 80 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
         | 
| 81 97 | 
             
                  else
         | 
| 82 98 | 
             
                    @format = :json
         | 
| 83 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. | 
| 40 | 
            +
                    if resource.respond_to?(:text) && content = resource.content
         | 
| 41 41 | 
             
                      return {
         | 
| 42 42 | 
             
                        contents: [
         | 
| 43 43 | 
             
                          {
         | 
| 44 44 | 
             
                            uri:,
         | 
| 45 45 | 
             
                            mimeType: resource.class.mime_type,
         | 
| 46 | 
            -
                            text:  | 
| 46 | 
            +
                            text: content
         | 
| 47 47 | 
             
                          }
         | 
| 48 48 | 
             
                        ]
         | 
| 49 49 | 
             
                      }
         | 
| @@ -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
         | 
| @@ -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. | 
| 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. | 
| 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,7 +5,7 @@ if @format == :jsonrpc | |
| 5 5 | 
             
              json.result do
         | 
| 6 6 | 
             
                json.resources do
         | 
| 7 7 | 
             
                  json.array!(@resources) do |resource|
         | 
| 8 | 
            -
                    json.name resource. | 
| 8 | 
            +
                    json.name resource.resource_name
         | 
| 9 9 | 
             
                    json.uri resource.uri
         | 
| 10 10 | 
             
                    json.mimeType resource.class.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!(@resources) do |resource|
         | 
| 18 | 
            -
                  json.name resource. | 
| 18 | 
            +
                  json.name resource.resource_name
         | 
| 19 19 | 
             
                  json.uri resource.uri
         | 
| 20 20 | 
             
                  json.mimeType resource.class.mime_type
         | 
| 21 21 | 
             
                  json.description resource.description
         | 
| @@ -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. | 
| 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. | 
| 17 | 
            +
                  json.name tool.tool_name
         | 
| 18 18 | 
             
                  json.description tool.description
         | 
| 19 19 | 
             
                  json.inputSchema tool.class.schema
         | 
| 20 20 | 
             
                end
         | 
| @@ -11,7 +11,9 @@ module ActiveMcp | |
| 11 11 | 
             
                    values = resource_class.arguments[arg_name.to_sym].call(value)
         | 
| 12 12 | 
             
                    { values:, total: values.length }
         | 
| 13 13 | 
             
                  elsif ref_name
         | 
| 14 | 
            -
                    {  | 
| 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 }
         | 
| 15 17 | 
             
                  end
         | 
| 16 18 | 
             
                end
         | 
| 17 19 | 
             
              end
         | 
    
        data/lib/active_mcp/engine.rb
    CHANGED
    
    | @@ -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,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
         | 
| @@ -6,6 +6,15 @@ module ActiveMcp | |
| 6 6 | 
             
                  class << self
         | 
| 7 7 | 
             
                    attr_reader :schema, :arguments
         | 
| 8 8 |  | 
| 9 | 
            +
                    def resource_template_name
         | 
| 10 | 
            +
                    end
         | 
| 11 | 
            +
              
         | 
| 12 | 
            +
                    def description
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    def mime_type
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
             | 
| 9 18 | 
             
                    def argument(name, complete)
         | 
| 10 19 | 
             
                      @arguments = {}
         | 
| 11 20 | 
             
                      @arguments[name] = complete
         | 
| @@ -15,7 +24,7 @@ module ActiveMcp | |
| 15 24 | 
             
                  def initialize
         | 
| 16 25 | 
             
                  end
         | 
| 17 26 |  | 
| 18 | 
            -
                  def  | 
| 27 | 
            +
                  def resource_name
         | 
| 19 28 | 
             
                  end
         | 
| 20 29 |  | 
| 21 30 | 
             
                  def description
         | 
| @@ -24,6 +33,17 @@ module ActiveMcp | |
| 24 33 | 
             
                  def visible?(context: {})
         | 
| 25 34 | 
             
                    true
         | 
| 26 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
         | 
| 27 47 | 
             
                end
         | 
| 28 48 | 
             
              end
         | 
| 29 49 | 
             
            end
         | 
| @@ -2,7 +2,7 @@ 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 ||= []
         | 
| @@ -18,6 +18,11 @@ module ActiveMcp | |
| 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 | 
| 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 | 
| 39 | 
            +
                    self.class.resource_templates&.filter do |template|
         | 
| 35 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 | 
| 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
         | 
| @@ -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)
         | 
| @@ -60,6 +50,10 @@ module ActiveMcp | |
| 60 50 | 
             
                      handle_read_resource(request)
         | 
| 61 51 | 
             
                    when Method::COMPLETION_COMPLETE
         | 
| 62 52 | 
             
                      handle_complete(request)
         | 
| 53 | 
            +
                    when Method::PROMPTS_LIST
         | 
| 54 | 
            +
                      handle_list_prompts(request)
         | 
| 55 | 
            +
                    when Method::PROMPTS_GET
         | 
| 56 | 
            +
                      handle_get_prompt(request)
         | 
| 63 57 | 
             
                    else
         | 
| 64 58 | 
             
                      error_response(request[:id], ErrorCode::METHOD_NOT_FOUND, "Unknown method: #{request[:method]}")
         | 
| 65 59 | 
             
                    end
         | 
| @@ -88,13 +82,9 @@ module ActiveMcp | |
| 88 82 | 
             
                      result: {
         | 
| 89 83 | 
             
                        protocolVersion: PROTOCOL_VERSION,
         | 
| 90 84 | 
             
                        capabilities: {
         | 
| 91 | 
            -
                          resources: {
         | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
                          },
         | 
| 95 | 
            -
                          tools: {
         | 
| 96 | 
            -
                            listChanged: false
         | 
| 97 | 
            -
                          }
         | 
| 85 | 
            +
                          resources: {},
         | 
| 86 | 
            +
                          tools: {},
         | 
| 87 | 
            +
                          prompts: {},
         | 
| 98 88 | 
             
                        },
         | 
| 99 89 | 
             
                        serverInfo: {
         | 
| 100 90 | 
             
                          name: @server.name,
         | 
| @@ -191,7 +181,7 @@ module ActiveMcp | |
| 191 181 |  | 
| 192 182 | 
             
                      success_response(request[:id], result)
         | 
| 193 183 | 
             
                    rescue => e
         | 
| 194 | 
            -
                      Server.("Error reading resource #{uri}", e)
         | 
| 184 | 
            +
                      Server.log_error("Error reading resource #{uri}", e)
         | 
| 195 185 | 
             
                      error_response(request[:id], ErrorCode::INTERNAL_ERROR, "An error occurred while reading the resource")
         | 
| 196 186 | 
             
                    end
         | 
| 197 187 | 
             
                  end
         | 
| @@ -206,7 +196,38 @@ module ActiveMcp | |
| 206 196 | 
             
                      )
         | 
| 207 197 | 
             
                      success_response(request[:id], { completion: result[:result] })
         | 
| 208 198 | 
             
                    rescue => e
         | 
| 209 | 
            -
                      Server.("Error reading resource #{uri}", 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)
         | 
| 210 231 | 
             
                      error_response(request[:id], ErrorCode::INTERNAL_ERROR, "An error occurred while reading the resource")
         | 
| 211 232 | 
             
                    end
         | 
| 212 233 | 
             
                  end
         | 
    
        data/lib/active_mcp/tool/base.rb
    CHANGED
    
    
    
        data/lib/active_mcp/version.rb
    CHANGED
    
    
    
        data/lib/active_mcp.rb
    CHANGED
    
    | @@ -6,12 +6,16 @@ require_relative "active_mcp/configuration" | |
| 6 6 | 
             
            require_relative "active_mcp/schema/base"
         | 
| 7 7 | 
             
            require_relative "active_mcp/tool/base"
         | 
| 8 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"
         | 
| 9 14 | 
             
            require_relative "active_mcp/server"
         | 
| 10 15 | 
             
            require_relative "active_mcp/completion"
         | 
| 11 16 |  | 
| 12 17 | 
             
            if defined? ::Rails
         | 
| 13 18 | 
             
              require_relative "active_mcp/engine"
         | 
| 14 | 
            -
              require_relative "active_mcp/controller/base"
         | 
| 15 19 | 
             
            end
         | 
| 16 20 |  | 
| 17 21 | 
             
            module ActiveMcp
         | 
    
        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.9.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Moeki Kawakami
         | 
| @@ -77,6 +77,8 @@ files: | |
| 77 77 | 
             
            - app/views/active_mcp/initialize.json.jbuilder
         | 
| 78 78 | 
             
            - app/views/active_mcp/initialized.json.jbuilder
         | 
| 79 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
         | 
| 80 82 | 
             
            - app/views/active_mcp/resource_templates_list.json.jbuilder
         | 
| 81 83 | 
             
            - app/views/active_mcp/resources_list.json.jbuilder
         | 
| 82 84 | 
             
            - app/views/active_mcp/resources_read.json.jbuilder
         | 
| @@ -86,8 +88,12 @@ files: | |
| 86 88 | 
             
            - lib/active_mcp.rb
         | 
| 87 89 | 
             
            - lib/active_mcp/completion.rb
         | 
| 88 90 | 
             
            - lib/active_mcp/configuration.rb
         | 
| 89 | 
            -
            - lib/active_mcp/controller/base.rb
         | 
| 90 91 | 
             
            - lib/active_mcp/engine.rb
         | 
| 92 | 
            +
            - lib/active_mcp/message/audio.rb
         | 
| 93 | 
            +
            - lib/active_mcp/message/image.rb
         | 
| 94 | 
            +
            - lib/active_mcp/message/resource.rb
         | 
| 95 | 
            +
            - lib/active_mcp/message/text.rb
         | 
| 96 | 
            +
            - lib/active_mcp/prompt/base.rb
         | 
| 91 97 | 
             
            - lib/active_mcp/resource/base.rb
         | 
| 92 98 | 
             
            - lib/active_mcp/schema/base.rb
         | 
| 93 99 | 
             
            - lib/active_mcp/server.rb
         |