rolemodel-rails 1.1.0 → 2.0.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -7
  3. data/lib/generators/rolemodel/all_generator.rb +0 -1
  4. data/lib/generators/rolemodel/optics/icons/README.md +6 -1
  5. data/lib/generators/rolemodel/optics/icons/icons_generator.rb +14 -9
  6. data/lib/generators/rolemodel/optics/icons/templates/app/helpers/icon_helper.rb.tt +4 -2
  7. data/lib/generators/rolemodel/testing/README.md +4 -1
  8. data/lib/generators/rolemodel/testing/all_generator.rb +3 -3
  9. data/lib/generators/rolemodel/testing/jasmine_playwright/jasmine_playwright_generator.rb +1 -1
  10. data/lib/generators/rolemodel/testing/rspec/README.md +1 -1
  11. data/lib/generators/rolemodel/testing/rspec/USAGE +1 -1
  12. data/lib/generators/rolemodel/testing/rspec/rspec_generator.rb +15 -14
  13. data/lib/generators/rolemodel/testing/rspec/templates/spec/support/capybara_config.rb.tt +9 -0
  14. data/lib/{generators/rolemodel/optics/icons/templates/app/icon_builders → rolemodel/optics}/custom_icon_builder.rb +1 -1
  15. data/lib/{generators/rolemodel/optics/icons/templates/app/icon_builders → rolemodel/optics}/feather_icon_builder.rb +1 -1
  16. data/lib/{generators/rolemodel/optics/icons/templates/app/icon_builders → rolemodel/optics}/icon_builder.rb +10 -19
  17. data/lib/{generators/rolemodel/optics/icons/templates/app/icon_builders → rolemodel/optics}/lucide_icon_builder.rb +1 -1
  18. data/lib/{generators/rolemodel/optics/icons/templates/app/icon_builders → rolemodel/optics}/material_icon_builder.rb +1 -1
  19. data/lib/{generators/rolemodel/optics/icons/templates/app/icon_builders → rolemodel/optics}/phosphor_icon_builder.rb +1 -1
  20. data/lib/{generators/rolemodel/optics/icons/templates/app/icon_builders → rolemodel/optics}/tabler_icon_builder.rb +1 -1
  21. data/lib/rolemodel/optics.rb +9 -0
  22. data/lib/rolemodel/utility/task_tools.rb +42 -0
  23. data/lib/rolemodel/utility.rb +3 -0
  24. data/lib/rolemodel/version.rb +1 -1
  25. data/lib/rolemodel-rails.rb +2 -0
  26. metadata +35 -42
  27. data/lib/generators/rolemodel/mcp/README.md +0 -13
  28. data/lib/generators/rolemodel/mcp/USAGE +0 -8
  29. data/lib/generators/rolemodel/mcp/mcp_generator.rb +0 -110
  30. data/lib/generators/rolemodel/mcp/templates/app/assets/stylesheets/components/doorkeeper.css +0 -140
  31. data/lib/generators/rolemodel/mcp/templates/app/controllers/doorkeeper/base_controller.rb +0 -7
  32. data/lib/generators/rolemodel/mcp/templates/app/controllers/mcp_controller.rb.tt +0 -91
  33. data/lib/generators/rolemodel/mcp/templates/app/controllers/oauth_registrations_controller.rb +0 -46
  34. data/lib/generators/rolemodel/mcp/templates/app/controllers/well_known_controller.rb +0 -39
  35. data/lib/generators/rolemodel/mcp/templates/app/mcp/prompts/sample.rb +0 -36
  36. data/lib/generators/rolemodel/mcp/templates/app/mcp/resources/controller.rb +0 -57
  37. data/lib/generators/rolemodel/mcp/templates/app/mcp/resources/docs/SAMPLE_DOC.md +0 -4
  38. data/lib/generators/rolemodel/mcp/templates/app/mcp/resources/docs_controller.rb +0 -46
  39. data/lib/generators/rolemodel/mcp/templates/app/mcp/tools/sample.rb +0 -42
  40. data/lib/generators/rolemodel/mcp/templates/app/views/doorkeeper/authorizations/error.html.slim.tt +0 -13
  41. data/lib/generators/rolemodel/mcp/templates/app/views/doorkeeper/authorizations/new.html.slim.tt +0 -41
  42. data/lib/generators/rolemodel/mcp/templates/app/views/layouts/doorkeeper.html.slim +0 -7
  43. data/lib/generators/rolemodel/mcp/templates/config/initializers/doorkeeper.rb +0 -537
  44. data/lib/generators/rolemodel/mcp/templates/spec/mcp/prompts/sample_spec.rb +0 -15
  45. data/lib/generators/rolemodel/mcp/templates/spec/mcp/resources/controller_spec.rb +0 -16
  46. data/lib/generators/rolemodel/mcp/templates/spec/mcp/resources/docs_controller_spec.rb +0 -55
  47. data/lib/generators/rolemodel/mcp/templates/spec/mcp/tools/sample_spec.rb +0 -15
  48. data/lib/generators/rolemodel/mcp/templates/spec/requests/mcp_controller_spec.rb +0 -84
  49. data/lib/generators/rolemodel/mcp/templates/spec/requests/oauth_registrations_controller_spec.rb +0 -62
  50. data/lib/generators/rolemodel/mcp/templates/spec/requests/well_known_controller_spec.rb +0 -30
  51. data/lib/generators/rolemodel/testing/rspec/templates/support/capybara_testid.rb +0 -5
  52. /data/lib/generators/rolemodel/testing/rspec/templates/{rails_helper.rb → spec/rails_helper.rb.tt} +0 -0
  53. /data/lib/generators/rolemodel/testing/rspec/templates/{spec_helper.rb → spec/spec_helper.rb.tt} +0 -0
  54. /data/lib/generators/rolemodel/testing/rspec/templates/{support/capybara_drivers.rb → spec/support/capybara_drivers.rb.tt} +0 -0
  55. /data/lib/generators/rolemodel/testing/rspec/templates/{support → spec/support}/helpers/action_cable_helper.rb +0 -0
  56. /data/lib/generators/rolemodel/testing/rspec/templates/{support → spec/support}/helpers/capybara_helper.rb +0 -0
  57. /data/lib/generators/rolemodel/testing/rspec/templates/{support → spec/support}/helpers/playwright_helper.rb +0 -0
  58. /data/lib/generators/rolemodel/testing/rspec/templates/{support → spec/support}/helpers/select_helper.rb +0 -0
  59. /data/lib/generators/rolemodel/testing/rspec/templates/{support → spec/support}/helpers/test_element_helper.rb +0 -0
  60. /data/lib/generators/rolemodel/testing/rspec/templates/{support/helpers.rb → spec/support/helpers.rb.tt} +0 -0
@@ -1,110 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Rolemodel
4
- class McpGenerator < GeneratorBase
5
- source_root File.expand_path('templates', __dir__)
6
-
7
- def update_inflections
8
- inflections_path = File.join(destination_root, 'config/initializers/inflections.rb')
9
- block_start = "\nActiveSupport::Inflector.inflections(:en) do |inflect|\n"
10
-
11
- return if File.read(inflections_path).include?("inflect.acronym 'MCP'")
12
-
13
- if File.read(inflections_path).include?(block_start)
14
- inject_into_file inflections_path, " inflect.acronym 'MCP'\n", after: block_start
15
- else
16
- append_to_file inflections_path, <<~RUBY
17
-
18
- ActiveSupport::Inflector.inflections(:en) do |inflect|
19
- inflect.acronym 'MCP'
20
- end
21
- RUBY
22
- end
23
- end
24
-
25
- def install_mcp
26
- bundle_command 'add mcp'
27
- template 'app/controllers/mcp_controller.rb'
28
- copy_file 'spec/requests/mcp_controller_spec.rb'
29
-
30
- route <<~RUBY
31
- match '/mcp', to: 'mcp#handle', via: %i[get post delete]
32
- RUBY
33
- end
34
-
35
- def add_sample_mcp_resource
36
- copy_file 'app/mcp/resources/controller.rb'
37
- copy_file 'spec/mcp/resources/controller_spec.rb'
38
-
39
- copy_file 'app/mcp/resources/docs/SAMPLE_DOC.md'
40
- copy_file 'app/mcp/resources/docs_controller.rb'
41
- copy_file 'spec/mcp/resources/docs_controller_spec.rb'
42
- end
43
-
44
- def add_sample_mcp_prompt
45
- copy_file 'app/mcp/prompts/sample.rb'
46
- copy_file 'spec/mcp/prompts/sample_spec.rb'
47
- end
48
-
49
- def add_sample_mcp_tool
50
- copy_file 'app/mcp/tools/sample.rb'
51
- copy_file 'spec/mcp/tools/sample_spec.rb'
52
- end
53
-
54
- def install_doorkeeper
55
- bundle_command 'add doorkeeper'
56
- run_bundle
57
- generate 'doorkeeper:install'
58
- end
59
-
60
- def configure_doorkeeper
61
- copy_file 'config/initializers/doorkeeper.rb', force: true
62
- copy_file 'app/controllers/doorkeeper/base_controller.rb'
63
-
64
- copy_file 'app/views/layouts/doorkeeper.html.slim'
65
- template 'app/views/doorkeeper/authorizations/new.html.slim'
66
- template 'app/views/doorkeeper/authorizations/error.html.slim'
67
-
68
- copy_file 'app/assets/stylesheets/components/doorkeeper.css'
69
-
70
- route 'use_doorkeeper'
71
- end
72
-
73
- def apply_doorkeeper_css
74
- css_manifest = if File.exist?(File.join(destination_root, 'app/assets/stylesheets/application.scss'))
75
- 'app/assets/stylesheets/application.scss'
76
- else
77
- 'app/assets/stylesheets/application.css'
78
- end
79
-
80
- return if File.read(File.join(destination_root, css_manifest)).include?("@import 'components/doorkeeper.css';")
81
-
82
- append_to_file css_manifest, <<~CSS
83
- @import 'components/doorkeeper.css';
84
- CSS
85
- end
86
-
87
- def add_oauth_dynamic_registrations
88
- copy_file 'app/controllers/oauth_registrations_controller.rb'
89
- copy_file 'spec/requests/oauth_registrations_controller_spec.rb'
90
- route <<~RUBY
91
- post '/oauth/register', to: 'oauth_registrations#create'
92
- RUBY
93
- end
94
-
95
- def add_well_known_route
96
- copy_file 'app/controllers/well_known_controller.rb'
97
- copy_file 'spec/requests/well_known_controller_spec.rb'
98
- route <<~RUBY
99
- get '/.well-known/oauth-protected-resource', to: 'well_known#oauth_protected_resource'
100
- get '/.well-known/oauth-authorization-server', to: 'well_known#oauth_authorization_server'
101
- RUBY
102
- end
103
-
104
- private
105
-
106
- def application_name
107
- Rails.application.name
108
- end
109
- end
110
- end
@@ -1,140 +0,0 @@
1
- .doorkeeper {
2
- position: fixed;
3
- inset: 0;
4
- box-sizing: border-box;
5
- display: flex;
6
- align-items: center;
7
- justify-content: center;
8
- padding: var(--op-space-x-large) var(--op-space-large);
9
- overflow: auto;
10
- background-color: var(--op-color-neutral-plus-eight);
11
- color: var(--op-color-neutral-minus-max);
12
- font-family: var(--op-font-family);
13
- }
14
-
15
- .doorkeeper__card {
16
- width: min(100%, 48rem);
17
- background-color: var(--op-color-white);
18
- border: var(--op-border-width) solid var(--op-color-neutral-plus-six);
19
- border-radius: var(--op-radius-x-large);
20
- box-shadow: var(--op-shadow-large);
21
- display: flex;
22
- flex-direction: column;
23
- gap: var(--op-space-large);
24
- padding: var(--op-space-2x-large);
25
- }
26
-
27
- .doorkeeper__brand {
28
- display: flex;
29
- flex-direction: column;
30
- align-items: center;
31
- gap: var(--op-space-large);
32
- margin-bottom: var(--op-space-small);
33
- }
34
-
35
- .doorkeeper__logo {
36
- width: 180px;
37
- height: auto;
38
- }
39
-
40
- .doorkeeper__title {
41
- margin: 0;
42
- font-size: var(--op-font-2x-large);
43
- font-weight: var(--op-font-weight-bold);
44
- color: var(--op-color-primary-minus-two);
45
- text-align: center;
46
- letter-spacing: -0.04em;
47
- }
48
-
49
- .doorkeeper__prompt {
50
- margin: 0;
51
- font-size: var(--op-font-large);
52
- line-height: var(--op-line-height-loose);
53
- color: var(--op-color-neutral-minus-three);
54
- text-align: center;
55
- }
56
-
57
- .doorkeeper__client-name {
58
- color: var(--op-color-primary-base);
59
- font-weight: var(--op-font-weight-bold);
60
- }
61
-
62
- .doorkeeper__permissions {
63
- background-color: var(--op-color-primary-plus-eight);
64
- border: var(--op-border-width) solid var(--op-color-primary-plus-six);
65
- border-radius: var(--op-radius-medium);
66
- padding: var(--op-space-large);
67
- display: flex;
68
- flex-direction: column;
69
- gap: var(--op-space-medium);
70
- }
71
-
72
- .doorkeeper__permissions-label {
73
- margin: 0;
74
- font-size: var(--op-font-small);
75
- text-transform: uppercase;
76
- letter-spacing: 0.05em;
77
- font-weight: var(--op-font-weight-bold);
78
- color: var(--op-color-primary-minus-three);
79
- }
80
-
81
- .doorkeeper__scope-list {
82
- margin: 0;
83
- padding-left: var(--op-space-large);
84
- display: grid;
85
- gap: var(--op-space-small);
86
- color: var(--op-color-primary-minus-max);
87
- }
88
-
89
- .doorkeeper__scope-item {
90
- line-height: var(--op-line-height-base);
91
- font-weight: var(--op-font-weight-medium);
92
- }
93
-
94
- .doorkeeper__actions {
95
- display: flex;
96
- flex-direction: column;
97
- gap: var(--op-space-medium);
98
- margin-top: var(--op-space-medium);
99
- }
100
-
101
- .doorkeeper__error {
102
- align-self: stretch;
103
- }
104
-
105
- .doorkeeper__error-description {
106
- margin: 0;
107
- font-size: var(--op-font-medium);
108
- line-height: var(--op-line-height-base);
109
- white-space: pre-wrap;
110
- overflow-wrap: anywhere;
111
- }
112
-
113
- .doorkeeper__form {
114
- margin: 0;
115
- }
116
-
117
- .doorkeeper__button {
118
- width: 100%;
119
- justify-content: center;
120
- font-weight: var(--op-font-weight-semi-bold);
121
- }
122
-
123
- @media (max-width: 640px) {
124
- .doorkeeper {
125
- padding: var(--op-space-large) var(--op-space-medium);
126
- }
127
-
128
- .doorkeeper__card {
129
- padding: var(--op-space-large);
130
- border-radius: var(--op-radius-large);
131
- }
132
-
133
- .doorkeeper__logo {
134
- width: 140px;
135
- }
136
-
137
- .doorkeeper__title {
138
- font-size: var(--op-font-x-large);
139
- }
140
- }
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Doorkeeper
4
- class BaseController < ::ApplicationController
5
- skip_forgery_protection
6
- end
7
- end
@@ -1,91 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class MCPController < ApplicationController
4
- # skip_before_action :authenticate_user!
5
- skip_forgery_protection
6
-
7
- before_action :authorize_mcp
8
- before_action :set_current_user
9
-
10
- def handle
11
- server = build_server
12
- transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, stateless: true)
13
- server.transport = transport
14
-
15
- status, response_headers, body = transport.handle_request(request)
16
- respond_with(status, response_headers, body)
17
- end
18
-
19
- private
20
-
21
- def respond_with(status, headers, body)
22
- headers.each { |key, value| response.set_header(key, value) }
23
- response.status = status
24
- self.response_body = body
25
- end
26
-
27
- def authorize_mcp
28
- doorkeeper_authorize! :mcp
29
- set_mcp_resource_metadata_header if response.status == 401
30
- end
31
-
32
- def set_current_user
33
- @current_user = User.find_by(id: doorkeeper_token&.resource_owner_id)
34
- unauthorized_request if @current_user.blank?
35
- end
36
-
37
- def unauthorized_request
38
- set_mcp_resource_metadata_header
39
- render json: { error: 'Unauthorized' }, status: :unauthorized
40
- end
41
-
42
- def set_mcp_resource_metadata_header
43
- metadata = %(resource_metadata="#{request.base_url}/.well-known/oauth-protected-resource")
44
- response.set_header('WWW-Authenticate', %(Bearer realm="<%= application_name.titleize %>", #{metadata}))
45
- end
46
-
47
- def build_server
48
- server = MCP::Server.new(**mcp_server_config)
49
- handle_resources(server)
50
-
51
- server
52
- end
53
-
54
- def handle_resources(server) # rubocop:disable Metrics/MethodLength
55
- controllers = [
56
- Resources::DocsController
57
- ]
58
-
59
- server.resources_read_handler do |params|
60
- uri = params[:uri].to_s
61
- controller = controllers.find { |h| h.serves?(uri) }
62
-
63
- unless controller
64
- raise MCP::Server::RequestHandlerError.new(
65
- "Unable to serve resource for URI: #{uri}. Supported schemas: #{controllers.map(&:schema).join(', ')}",
66
- params,
67
- error_type: :invalid_params
68
- )
69
- end
70
-
71
- controller.call(params, server_context)
72
- end
73
- end
74
-
75
- def mcp_server_config # rubocop:disable Metrics/MethodLength
76
- {
77
- name: '<%= application_name.underscore %>_mcp',
78
- version: '1.0.0',
79
- tools: [Tools::Sample],
80
- prompts: [Prompts::Sample],
81
- server_context:,
82
- resources: [
83
- *Resources::DocsController.resource_list,
84
- ],
85
- }
86
- end
87
-
88
- def server_context
89
- @server_context ||= { current_user: @current_user }
90
- end
91
- end
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class OauthRegistrationsController < ApplicationController
4
- # skip_before_action :authenticate_user!
5
- skip_forgery_protection
6
-
7
- def create
8
- app = Doorkeeper::Application.new(doorkeeper_params)
9
- return client_metadata_error('redirect_uris is required') if app.redirect_uri.blank?
10
-
11
- if app.save
12
- render json: base_response(app), status: :created
13
- else
14
- client_metadata_error(app.errors.full_messages.join(', '))
15
- end
16
- end
17
-
18
- private
19
-
20
- def base_response(app)
21
- {
22
- client_id: app.uid,
23
- client_name: app.name,
24
- redirect_uris: app.redirect_uri.split("\n"),
25
- grant_types: %w[authorization_code refresh_token],
26
- response_types: ['code'],
27
- token_endpoint_auth_method: app.confidential? ? 'client_secret_basic' : 'none',
28
- client_id_issued_at: app.created_at.to_i,
29
- scope: 'mcp',
30
- client_secret: app.confidential? ? app.secret : nil,
31
- }.compact
32
- end
33
-
34
- def client_metadata_error(description)
35
- render json: { error: 'invalid_client_metadata', error_description: description }, status: :bad_request
36
- end
37
-
38
- def doorkeeper_params
39
- {
40
- name: params[:client_name].presence || 'MCP Client',
41
- redirect_uri: params[:redirect_uris].is_a?(Array) ? params[:redirect_uris].join("\n") : params[:redirect_uris],
42
- scopes: 'mcp',
43
- confidential: params[:token_endpoint_auth_method] != 'none',
44
- }
45
- end
46
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class WellKnownController < ApplicationController
4
- # skip_before_action :authenticate_user!
5
- before_action :set_base_url
6
-
7
- def oauth_protected_resource
8
- render json: {
9
- resource: "#{@base_url}/mcp",
10
- authorization_servers: [@base_url],
11
- }
12
- end
13
-
14
- def oauth_authorization_server
15
- render json: authorization_server_metadata
16
- end
17
-
18
- private
19
-
20
- def authorization_server_metadata # rubocop:disable Metrics/MethodLength
21
- {
22
- issuer: @base_url,
23
- authorization_endpoint: "#{@base_url}/oauth/authorize",
24
- token_endpoint: "#{@base_url}/oauth/token",
25
- registration_endpoint: "#{@base_url}/oauth/register",
26
- revocation_endpoint: "#{@base_url}/oauth/revoke",
27
- introspection_endpoint: "#{@base_url}/oauth/introspect",
28
- scopes_supported: ['mcp'],
29
- response_types_supported: ['code'],
30
- grant_types_supported: %w[authorization_code client_credentials refresh_token],
31
- token_endpoint_auth_methods_supported: %w[none client_secret_basic client_secret_post],
32
- code_challenge_methods_supported: ['S256'],
33
- }
34
- end
35
-
36
- def set_base_url
37
- @base_url = request.base_url
38
- end
39
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Prompts
4
- class Sample < ::MCP::Prompt
5
- prompt_name 'sample_prompt'
6
- title 'Sample Prompt'
7
- description 'Sample prompt description'
8
-
9
- class << self
10
- def template(_args, _server_context: nil)
11
- ::MCP::Prompt::Result.new(
12
- description: 'Sample prompt result description',
13
- messages: [
14
- ::MCP::Prompt::Message.new(
15
- role: 'assistant',
16
- content: ::MCP::Content::Text.new(instructions_text),
17
- ),
18
- ],
19
- )
20
- end
21
-
22
- private
23
-
24
- def instructions_text
25
- <<~TEXT
26
- This is a sample prompt.
27
-
28
- MCP prompts can return instructions for the agent, which can be used to guide the agent's behavior.
29
- For example, you might include instructions on how to query a specific resource or use a specific tool.
30
- Think of it like a system prompt in a conversational agent, but it can be dynamically generated based on the
31
- context of the request.
32
- TEXT
33
- end
34
- end
35
- end
36
- end
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Resources
4
- class Controller
5
- include ActiveModel::Attributes
6
- include ActiveModel::API
7
-
8
- attribute :server_context
9
- attribute :path, :string
10
-
11
- validates :path, presence: { message: 'is required' } # rubocop:disable Rails/I18nLocaleTexts
12
-
13
- class << self
14
- def mime_type(mime_type = nil)
15
- @mime_type = mime_type if mime_type
16
- @mime_type
17
- end
18
-
19
- def schema(schema = nil)
20
- @schema = schema if schema
21
- @schema
22
- end
23
-
24
- def serves?(uri)
25
- uri.start_with?(schema)
26
- end
27
-
28
- def call(params, server_context)
29
- controller = new(params[:uri].sub(schema, ''), server_context)
30
-
31
- unless controller.valid?
32
- raise ::MCP::Server::RequestHandlerError.new(
33
- controller.errors.full_messages.join(', '),
34
- params,
35
- error_type: :invalid_params
36
- )
37
- end
38
-
39
- [{ uri: params[:uri], mimeType: mime_type, text: controller.serve }]
40
- end
41
- end
42
-
43
- def initialize(path, server_context = nil)
44
- super()
45
- self.path = path
46
- self.server_context = server_context
47
- end
48
-
49
- private
50
-
51
- def no_extra_path_parts
52
- return if @extra.blank?
53
-
54
- errors.add(:base, "Too many uri parts: #{@extra.join('/')}.")
55
- end
56
- end
57
- end
@@ -1,4 +0,0 @@
1
- # Hello World
2
-
3
- This is a sample doc that the MCP can return as a resource.
4
- It's URI will be doc://SAMPLE_DOC.md as set in the handler.
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Resources
4
- class DocsController < Controller
5
- FILES = {
6
- 'SAMPLE_DOC.md' => Rails.root.join('app/mcp/resources/docs/blazer-documentation.md'),
7
- }.freeze
8
-
9
- mime_type 'text/markdown'
10
- schema 'docs://'
11
-
12
- attribute :file_path
13
-
14
- validates :file_path, presence: { message: ->(controller, _) { "Unknown docs resource: #{controller.path}" } }
15
- validate :file_exists
16
-
17
- def initialize(path, _server_context = nil)
18
- super
19
- self.file_path = FILES[path]
20
- end
21
-
22
- def self.resource_list
23
- [
24
- ::MCP::Resource.new(
25
- uri: 'docs://SAMPLE_DOC.md',
26
- name: 'sample_doc',
27
- title: 'Sample Resource',
28
- description: 'Sample resource',
29
- mime_type: mime_type,
30
- ),
31
- ]
32
- end
33
-
34
- def serve
35
- file_path.read
36
- end
37
-
38
- private
39
-
40
- def file_exists
41
- return if file_path.blank? || file_path.exist?
42
-
43
- errors.add(:file_path, "Missing docs file for #{path}")
44
- end
45
- end
46
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Tools
4
- class Sample < ::MCP::Tool
5
- tool_name 'sample_tool'
6
- title 'Sample Tool'
7
- description 'Sample tool description'
8
- input_schema(
9
- properties: {
10
- name: { type: 'string', minLength: 1 },
11
- },
12
- required: ['name'],
13
- )
14
- annotations(
15
- read_only_hint: true,
16
- destructive_hint: false,
17
- idempotent_hint: true,
18
- open_world_hint: false,
19
- title: 'Sample Tool',
20
- )
21
-
22
- class << self
23
- def call(name:, server_context:)
24
- payload = payload_for(name)
25
-
26
- ::MCP::Tool::Response.new(
27
- [{ type: 'text', text: payload.to_json }],
28
- structured_content: { sample: payload },
29
- )
30
- end
31
-
32
- private
33
-
34
- def payload_for(name)
35
- {
36
- time: Time.current.iso8601,
37
- user_count: User.count
38
- }
39
- end
40
- end
41
- end
42
- end
@@ -1,13 +0,0 @@
1
- - content_for :page_title do
2
- = '<%= application_name.titleize %> - Error Authorizing Application'
3
-
4
- main.doorkeeper role="main"
5
- .doorkeeper__card.card.card--padded.card--shadow-medium
6
- .doorkeeper__brand
7
- = image_tag 'logo.svg', alt: '<%= application_name.titleize %>', class: 'doorkeeper__logo'
8
- h1.doorkeeper__title= t('doorkeeper.authorizations.error.title')
9
-
10
- .doorkeeper__error.alert.alert--danger role="alert"
11
- .alert__messages
12
- p.doorkeeper__error-description.alert__description
13
- = (local_assigns[:error_response] ? error_response : @pre_auth.error_response).body[:error_description]
@@ -1,41 +0,0 @@
1
- - content_for :page_title do
2
- = '<%= application_name.titleize %> - Authorize Application'
3
-
4
- main.doorkeeper role="main"
5
- .doorkeeper__card.card.card--padded.card--shadow-medium
6
- .doorkeeper__brand
7
- = image_tag 'logo.svg', alt: '<%= application_name.titleize %>', class: 'doorkeeper__logo'
8
- h1.doorkeeper__title= t('.title')
9
-
10
- p.doorkeeper__prompt
11
- == t('.prompt', client_name: content_tag(:strong, @pre_auth.client.name, class: 'doorkeeper__client-name'))
12
-
13
- - if @pre_auth.scopes.count > 0
14
- #oauth-permissions.doorkeeper__permissions
15
- p.doorkeeper__permissions-label= t('.able_to') + ":"
16
- ul.doorkeeper__scope-list
17
- - @pre_auth.scopes.each do |scope|
18
- li.doorkeeper__scope-item= t scope, scope: [:doorkeeper, :scopes]
19
-
20
- .doorkeeper__actions
21
- = form_tag oauth_authorization_path, method: :post, class: 'doorkeeper__form', data: { turbo: false } do
22
- = hidden_field_tag :client_id, @pre_auth.client.uid, id: nil
23
- = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri, id: nil
24
- = hidden_field_tag :state, @pre_auth.state, id: nil
25
- = hidden_field_tag :response_type, @pre_auth.response_type, id: nil
26
- = hidden_field_tag :response_mode, @pre_auth.response_mode, id: nil
27
- = hidden_field_tag :scope, @pre_auth.scope, id: nil
28
- = hidden_field_tag :code_challenge, @pre_auth.code_challenge, id: nil
29
- = hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method, id: nil
30
- = submit_tag t('doorkeeper.authorizations.buttons.authorize'), class: 'btn btn--primary doorkeeper__button'
31
-
32
- = form_tag oauth_authorization_path, method: :delete, class: 'doorkeeper__form' do
33
- = hidden_field_tag :client_id, @pre_auth.client.uid, id: nil
34
- = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri, id: nil
35
- = hidden_field_tag :state, @pre_auth.state, id: nil
36
- = hidden_field_tag :response_type, @pre_auth.response_type, id: nil
37
- = hidden_field_tag :response_mode, @pre_auth.response_mode, id: nil
38
- = hidden_field_tag :scope, @pre_auth.scope, id: nil
39
- = hidden_field_tag :code_challenge, @pre_auth.code_challenge, id: nil
40
- = hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method, id: nil
41
- = submit_tag t('doorkeeper.authorizations.buttons.deny'), class: 'btn btn--destructive doorkeeper__button'
@@ -1,7 +0,0 @@
1
- doctype html
2
- html
3
- / Use whatever partial you have for the head
4
- = render 'head'
5
-
6
- body
7
- = yield