mcp_authorization 0.1.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +552 -0
- data/app/controllers/mcp_authorization/mcp_controller.rb +37 -0
- data/lib/mcp_authorization/configuration.rb +81 -0
- data/lib/mcp_authorization/dsl.rb +64 -0
- data/lib/mcp_authorization/engine.rb +68 -0
- data/lib/mcp_authorization/rbs_schema_compiler.rb +1015 -0
- data/lib/mcp_authorization/tool.rb +245 -0
- data/lib/mcp_authorization/tool_registry.rb +89 -0
- data/lib/mcp_authorization/version.rb +3 -0
- data/lib/mcp_authorization.rb +57 -0
- data/lib/tasks/mcp_authorization.rake +99 -0
- metadata +112 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module McpAuthorization
|
|
2
|
+
# Mixin for handler classes — the plain Ruby objects that implement the
|
|
3
|
+
# business logic behind each MCP tool.
|
|
4
|
+
#
|
|
5
|
+
# Include this module instead of hand-rolling +initialize+, permission
|
|
6
|
+
# checks, and MCP notification plumbing:
|
|
7
|
+
#
|
|
8
|
+
# class Handlers::ListOrders
|
|
9
|
+
# include McpAuthorization::DSL
|
|
10
|
+
#
|
|
11
|
+
# def description
|
|
12
|
+
# "List recent orders"
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# #: (status: String, ?limit: Integer @requires(:admin)) -> Hash[Symbol, untyped]
|
|
16
|
+
# def call(status:, limit: 25)
|
|
17
|
+
# orders = Order.where(status: status).limit(limit)
|
|
18
|
+
# { orders: orders.map(&:as_json) }
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# == What it provides
|
|
23
|
+
#
|
|
24
|
+
# * +server_context+ — the per-request context object built by the host
|
|
25
|
+
# app's +context_builder+.
|
|
26
|
+
# * +can?(flag)+ — convenience delegation to +current_user.can?+.
|
|
27
|
+
# * +report_progress+ / +notify_log_message+ — thin wrappers around MCP
|
|
28
|
+
# session notifications, safe to call even when the context doesn't
|
|
29
|
+
# support them (e.g. during +tools/list+).
|
|
30
|
+
#
|
|
31
|
+
module DSL
|
|
32
|
+
# The per-request context built by the host app's +context_builder+.
|
|
33
|
+
#: untyped
|
|
34
|
+
attr_reader :server_context
|
|
35
|
+
|
|
36
|
+
#: (server_context: untyped) -> void
|
|
37
|
+
def initialize(server_context:)
|
|
38
|
+
@server_context = server_context
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Convenience check — delegates to +server_context.current_user.can?+.
|
|
42
|
+
# Use inside +#call+ for runtime branching beyond +@requires+ filtering.
|
|
43
|
+
#: (Symbol) -> bool
|
|
44
|
+
def can?(flag)
|
|
45
|
+
server_context.current_user.can?(flag)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Send a progress notification to the MCP client (MCP 0.10+).
|
|
49
|
+
# Safe to call unconditionally — no-ops when context lacks support.
|
|
50
|
+
#: (Numeric, ?total: Numeric?, ?message: String?) -> void
|
|
51
|
+
def report_progress(progress, total: nil, message: nil)
|
|
52
|
+
return unless server_context.respond_to?(:report_progress)
|
|
53
|
+
server_context.report_progress(progress, total: total, message: message)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Send a log message notification to the MCP client (MCP 0.10+).
|
|
57
|
+
# Safe to call unconditionally — no-ops when context lacks support.
|
|
58
|
+
#: (data: untyped, level: String | Symbol, ?logger: String?) -> void
|
|
59
|
+
def notify_log_message(data:, level:, logger: nil)
|
|
60
|
+
return unless server_context.respond_to?(:notify_log_message)
|
|
61
|
+
server_context.notify_log_message(data: data, level: level, logger: logger)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module McpAuthorization
|
|
2
|
+
# Rails Engine that wires the gem into a host application.
|
|
3
|
+
#
|
|
4
|
+
# The engine handles three things automatically so the host app doesn't
|
|
5
|
+
# have to:
|
|
6
|
+
#
|
|
7
|
+
# 1. *Autoload paths* — tool directories (default +app/mcp+) are added to
|
|
8
|
+
# the Rails autoloader so tool and handler classes are discovered
|
|
9
|
+
# without explicit requires.
|
|
10
|
+
#
|
|
11
|
+
# 2. *Cache invalidation* — on code reload (development mode) the
|
|
12
|
+
# ToolRegistry and RbsSchemaCompiler caches are cleared. Tools
|
|
13
|
+
# re-register themselves via the +inherited+ hook when their classes
|
|
14
|
+
# are reloaded.
|
|
15
|
+
#
|
|
16
|
+
# 3. *Route mounting* — MCP endpoints are prepended to the host app's
|
|
17
|
+
# router at +config.mount_path+ (default +/mcp+). The routes support
|
|
18
|
+
# multi-domain routing via an optional +:domain+ segment:
|
|
19
|
+
#
|
|
20
|
+
# POST /mcp → default domain
|
|
21
|
+
# POST /mcp/operator → "operator" domain
|
|
22
|
+
# POST /mcp/recruiting → "recruiting" domain
|
|
23
|
+
#
|
|
24
|
+
# All three HTTP methods required by the MCP StreamableHTTP transport
|
|
25
|
+
# (GET, POST, DELETE) are accepted.
|
|
26
|
+
#
|
|
27
|
+
class Engine < ::Rails::Engine
|
|
28
|
+
isolate_namespace McpAuthorization
|
|
29
|
+
|
|
30
|
+
# Add configured tool_paths to the Rails autoloader so tool classes
|
|
31
|
+
# (and their handler classes) are discovered without manual requires.
|
|
32
|
+
initializer "mcp_authorization.autoload_paths", before: :set_autoload_paths do |app|
|
|
33
|
+
McpAuthorization.config.tool_paths.each do |path|
|
|
34
|
+
full_path = Rails.root.join(path).to_s
|
|
35
|
+
if File.directory?(full_path)
|
|
36
|
+
app.config.autoload_paths << full_path
|
|
37
|
+
app.config.eager_load_paths << full_path
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Clear caches on code reload so stale class references and parsed
|
|
43
|
+
# schemas are dropped. Tools re-register via the inherited hook when
|
|
44
|
+
# their classes are reloaded by Zeitwerk.
|
|
45
|
+
initializer "mcp_authorization.reloader" do |app|
|
|
46
|
+
app.reloader.to_prepare do
|
|
47
|
+
McpAuthorization::ToolRegistry.reset!
|
|
48
|
+
McpAuthorization::RbsSchemaCompiler.reset_cache!
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Prepend MCP routes into the host app's router. Uses +prepend+ so the
|
|
53
|
+
# MCP endpoint is available before any catch-all routes the host may
|
|
54
|
+
# define. Supports both domain-scoped and bare paths.
|
|
55
|
+
initializer "mcp_authorization.routes", before: :finisher_hook do |app|
|
|
56
|
+
default_domain = McpAuthorization.config.default_domain
|
|
57
|
+
mount_path = McpAuthorization.config.mount_path
|
|
58
|
+
|
|
59
|
+
app.routes.prepend do
|
|
60
|
+
scope mount_path, module: :mcp_authorization do
|
|
61
|
+
match ":domain", to: "mcp#handle", via: [ :get, :post, :delete ]
|
|
62
|
+
match "/", to: "mcp#handle", via: [ :get, :post, :delete ],
|
|
63
|
+
defaults: { domain: default_domain }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|