activeadmin_mcp 0.0.4 → 0.2.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 +4 -4
- data/README.md +373 -32
- data/app/controllers/activeadmin_mcp/mcp_controller.rb +0 -2
- data/app/models/activeadmin_mcp/api_token.rb +0 -2
- data/config/routes.rb +0 -2
- data/lib/activeadmin_mcp/action_catalog.rb +165 -0
- data/lib/activeadmin_mcp/action_definition.rb +297 -0
- data/lib/activeadmin_mcp/action_params.rb +78 -0
- data/lib/activeadmin_mcp/action_runner.rb +184 -0
- data/lib/activeadmin_mcp/action_schema.rb +66 -0
- data/lib/activeadmin_mcp/active_admin_ext.rb +83 -0
- data/lib/activeadmin_mcp/authorization.rb +0 -2
- data/lib/activeadmin_mcp/configuration.rb +0 -2
- data/lib/activeadmin_mcp/controller_dispatcher.rb +138 -0
- data/lib/activeadmin_mcp/engine.rb +12 -2
- data/lib/activeadmin_mcp/form_description.rb +157 -0
- data/lib/activeadmin_mcp/form_field_collector.rb +84 -17
- data/lib/activeadmin_mcp/record_writer.rb +167 -0
- data/lib/activeadmin_mcp/request_handler.rb +167 -38
- data/lib/activeadmin_mcp/resource_registry.rb +0 -2
- data/lib/activeadmin_mcp/version.rb +1 -3
- data/lib/activeadmin_mcp.rb +9 -3
- data/lib/generators/activeadmin_mcp/install/install_generator.rb +0 -2
- data/lib/generators/activeadmin_mcp/install/templates/initializer.rb +5 -2
- data/lib/generators/activeadmin_mcp/install/templates/mcp_api_tokens.rb +0 -2
- data/lib/generators/activeadmin_mcp/install/templates/migration.rb.erb +0 -2
- metadata +18 -10
- data/lib/activeadmin_mcp/record_updater.rb +0 -108
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
module ActiveadminMcp
|
|
2
|
+
# Creates and updates ActiveAdmin-managed records by dispatching the
|
|
3
|
+
# resource's own create and update actions, so a write from an MCP client
|
|
4
|
+
# goes through the same permitted params, callbacks, before_action chain and
|
|
5
|
+
# authorization as the same write made by clicking Save in the admin UI.
|
|
6
|
+
#
|
|
7
|
+
# Three gates run before anything is dispatched: the resource must expose the
|
|
8
|
+
# action, the authorization adapter must allow it, and the resource must
|
|
9
|
+
# declare what may be written. The controller then applies all three again,
|
|
10
|
+
# against the record it builds or loads.
|
|
11
|
+
class RecordWriter
|
|
12
|
+
CREATE = :create
|
|
13
|
+
UPDATE = :update
|
|
14
|
+
|
|
15
|
+
def initialize(resource:, current_user:)
|
|
16
|
+
@resource = resource
|
|
17
|
+
@current_user = current_user
|
|
18
|
+
@config = resource[:config]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def create(attributes:)
|
|
22
|
+
return error("Resource is not creatable: #{resource_name}") unless exposes?(CREATE)
|
|
23
|
+
|
|
24
|
+
permitted = resolve_permitted(attributes)
|
|
25
|
+
refusal = permit_refusal(permitted)
|
|
26
|
+
return refusal if refusal
|
|
27
|
+
return error("Not authorized to create #{resource_name}") unless authorized?(CREATE, @config.resource_class)
|
|
28
|
+
|
|
29
|
+
write(
|
|
30
|
+
action: CREATE,
|
|
31
|
+
verb: :post,
|
|
32
|
+
path: @config.route_collection_path,
|
|
33
|
+
attributes: attributes,
|
|
34
|
+
written: permitted.keys,
|
|
35
|
+
written_key: :created,
|
|
36
|
+
description: "Create #{resource_name}"
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def update(id:, attributes:)
|
|
41
|
+
return error("Resource is not editable: #{resource_name}") unless exposes?(UPDATE)
|
|
42
|
+
|
|
43
|
+
record = @resource[:model].find_by(id: id)
|
|
44
|
+
return error("Record not found: #{resource_name}##{id}") unless record
|
|
45
|
+
|
|
46
|
+
permitted = resolve_permitted(attributes)
|
|
47
|
+
refusal = permit_refusal(permitted)
|
|
48
|
+
return refusal if refusal
|
|
49
|
+
return error("Not authorized to update #{resource_name}##{id}") unless authorized?(UPDATE, record)
|
|
50
|
+
|
|
51
|
+
write(
|
|
52
|
+
action: UPDATE,
|
|
53
|
+
verb: :patch,
|
|
54
|
+
path: @config.route_instance_path(record),
|
|
55
|
+
attributes: attributes,
|
|
56
|
+
written: permitted.keys,
|
|
57
|
+
written_key: :updated,
|
|
58
|
+
path_params: { id: record.to_param },
|
|
59
|
+
description: "Update #{resource_name}##{id}"
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def write(action:, verb:, path:, attributes:, written:, written_key:, description:, path_params: {})
|
|
66
|
+
record = nil
|
|
67
|
+
|
|
68
|
+
outcome = ControllerDispatcher.new(config: @config, current_user: @current_user).call(
|
|
69
|
+
action: action,
|
|
70
|
+
verb: verb,
|
|
71
|
+
path: path,
|
|
72
|
+
params: { @config.param_key => attributes },
|
|
73
|
+
path_params: path_params
|
|
74
|
+
) { |controller| record = controller.send(:get_resource_ivar) }
|
|
75
|
+
|
|
76
|
+
return rejection(record, description, outcome) unless saved?(record)
|
|
77
|
+
|
|
78
|
+
{
|
|
79
|
+
resource: resource_name,
|
|
80
|
+
id: record.id,
|
|
81
|
+
written_key => written,
|
|
82
|
+
record: without_sensitive_attributes(record),
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def saved?(record)
|
|
87
|
+
!record.nil? && record.persisted? && record.errors.empty?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# The controller declined to save. Validation messages are the usual
|
|
91
|
+
# reason, and they are read off the record rather than the response
|
|
92
|
+
# because a rejected write re-renders the admin form — a render the
|
|
93
|
+
# synthesized request may well not survive, leaving the dispatch itself
|
|
94
|
+
# reported as a failure even though the record knows exactly what was
|
|
95
|
+
# wrong with it.
|
|
96
|
+
#
|
|
97
|
+
# With no record and no validation messages, anything the controller put
|
|
98
|
+
# in the flash is the best account left of what the admin UI would have
|
|
99
|
+
# told the user.
|
|
100
|
+
def rejection(record, description, outcome)
|
|
101
|
+
messages = record ? record.errors.full_messages : []
|
|
102
|
+
return error("Validation failed", details: messages) if messages.any?
|
|
103
|
+
return outcome if outcome[:error]
|
|
104
|
+
|
|
105
|
+
error("#{description} failed", details: Array(outcome[:flash]&.values).presence)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def exposes?(action)
|
|
109
|
+
@config.defined_actions.include?(action)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def authorized?(action, subject)
|
|
113
|
+
Authorization.for(@config, @current_user).authorized?(action, subject)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Turns what the controller says it would permit into a refusal, or nil to
|
|
117
|
+
# go ahead. The answer is not used to filter the write — dispatch means
|
|
118
|
+
# ActiveAdmin applies permit_params itself — but to refuse early, and with
|
|
119
|
+
# a reason, in the two cases where dispatching would otherwise fail
|
|
120
|
+
# obscurely: a resource that never declared permit_params, and a call whose
|
|
121
|
+
# every attribute would be dropped.
|
|
122
|
+
#
|
|
123
|
+
# A resource with no permit_params cannot be written through ActiveAdmin's
|
|
124
|
+
# own forms either — Rails raises ForbiddenAttributesError on the
|
|
125
|
+
# unpermitted params — so refusing it says what the admin UI would.
|
|
126
|
+
def permit_refusal(permitted)
|
|
127
|
+
if permitted.nil?
|
|
128
|
+
return error("Resource declares no permit_params, so nothing may be written: #{resource_name}")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
error("No permitted attributes to write") if permitted.empty?
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Asks the resource's own controller what it would permit, or nil when it
|
|
135
|
+
# has nothing to say because permit_params was never declared.
|
|
136
|
+
#
|
|
137
|
+
# The controller carries the MCP user, because ActiveAdmin instance_execs a
|
|
138
|
+
# block-form permit_params on it: a block reading current_admin_user raises
|
|
139
|
+
# on a bare instance, and the rescue below would then report a resource
|
|
140
|
+
# that permits plenty as one that permits nothing.
|
|
141
|
+
def resolve_permitted(attributes)
|
|
142
|
+
param_key = @config.param_key.to_sym
|
|
143
|
+
controller = ControllerDispatcher.new(config: @config, current_user: @current_user)
|
|
144
|
+
.controller_with_mcp_user
|
|
145
|
+
controller.params = ActionController::Parameters.new(param_key => attributes)
|
|
146
|
+
permitted = controller.send(:permitted_params)
|
|
147
|
+
scoped = permitted && permitted[param_key]
|
|
148
|
+
scoped&.to_h&.symbolize_keys
|
|
149
|
+
rescue StandardError
|
|
150
|
+
nil
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def without_sensitive_attributes(record)
|
|
154
|
+
record.as_json.except(*ResourceRegistry.sensitive_attributes)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def resource_name
|
|
158
|
+
@resource[:name]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def error(message, details: nil)
|
|
162
|
+
result = { error: message }
|
|
163
|
+
result[:details] = details if details
|
|
164
|
+
result
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
1
|
module ActiveadminMcp
|
|
4
2
|
class RequestHandler
|
|
5
3
|
PROTOCOL_VERSION = "2025-06-18"
|
|
@@ -40,45 +38,149 @@ module ActiveadminMcp
|
|
|
40
38
|
end
|
|
41
39
|
|
|
42
40
|
def tools_list
|
|
43
|
-
{
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
{ tools: built_in_tools + action_tools }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Both gates run before the schema is built, never after: ActionSchema calls
|
|
45
|
+
# the application's `suggestions:` procs, which read the database. Filtering
|
|
46
|
+
# an already-assembled list would mean those procs had already run — and
|
|
47
|
+
# their values already been read — for a user authorized for none of it.
|
|
48
|
+
def action_tools
|
|
49
|
+
ActionCatalog.all(current_user: @current_user).filter_map do |definition|
|
|
50
|
+
next unless authorized_to_run?(definition)
|
|
51
|
+
next unless offered_by_active_admin?(definition)
|
|
52
|
+
next unless authorized_to_list?(definition)
|
|
53
|
+
|
|
54
|
+
{
|
|
55
|
+
name: definition.tool_name,
|
|
56
|
+
description: definition.description,
|
|
57
|
+
inputSchema: ActionSchema.new(definition).to_h,
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The same authorization adapter check ActionRunner makes before dispatch,
|
|
63
|
+
# and the one list_resources makes for reads. There is no record at listing
|
|
64
|
+
# time, so the subject is the resource class.
|
|
65
|
+
def authorized_to_run?(definition)
|
|
66
|
+
Authorization.for(definition.config, @current_user)
|
|
67
|
+
.authorized?(definition.action_name, definition.config.resource_class)
|
|
68
|
+
rescue StandardError => e
|
|
69
|
+
warn("[activeadmin_mcp] hiding #{definition.tool_name}: authorization check raised #{e.class}: #{e.message}")
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# ActiveAdmin's own `:if` proc on a batch action decides whether the admin
|
|
74
|
+
# UI offers it. Evaluated in controller context, as ActiveAdmin evaluates
|
|
75
|
+
# it, so `authorized?` and `current_admin_user` are in scope. A proc
|
|
76
|
+
# reaching for request state it cannot have here raises, and the tool is
|
|
77
|
+
# hidden rather than offered past a gate we could not read.
|
|
78
|
+
def offered_by_active_admin?(definition)
|
|
79
|
+
block = definition.display_if
|
|
80
|
+
return true unless block
|
|
81
|
+
|
|
82
|
+
controller = ControllerDispatcher.new(config: definition.config, current_user: @current_user)
|
|
83
|
+
.controller_with_mcp_user
|
|
84
|
+
!!::MethodOrProcHelper.render_in_context(controller, block)
|
|
85
|
+
rescue StandardError => e
|
|
86
|
+
warn("[activeadmin_mcp] hiding #{definition.tool_name}: if: proc raised #{e.class}: #{e.message}")
|
|
87
|
+
false
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Collection and batch actions whose permission proc takes no record can be
|
|
91
|
+
# resolved now, so the tool is simply hidden. A member action's proc needs a
|
|
92
|
+
# record, so its tool stays listed and refusal happens at call time.
|
|
93
|
+
#
|
|
94
|
+
# The proc is evaluated in controller context, exactly as ActionRunner
|
|
95
|
+
# evaluates it at call time, so `current_admin_user`, `can?` and the rest of
|
|
96
|
+
# the admin helpers are in scope. Evaluating it bare would make every such
|
|
97
|
+
# proc raise NameError and hide its tool from everybody.
|
|
98
|
+
def authorized_to_list?(definition)
|
|
99
|
+
permission = definition.permission
|
|
100
|
+
return true unless permission
|
|
101
|
+
return true unless permission.respond_to?(:arity) && permission.arity.zero?
|
|
102
|
+
|
|
103
|
+
controller = ControllerDispatcher.new(config: definition.config, current_user: @current_user)
|
|
104
|
+
.controller_with_mcp_user
|
|
105
|
+
!!::MethodOrProcHelper.render_in_context(controller, permission)
|
|
106
|
+
rescue StandardError => e
|
|
107
|
+
# One broken proc hides its own tool and nothing else, but it says so.
|
|
108
|
+
warn("[activeadmin_mcp] hiding #{definition.tool_name}: permission proc raised #{e.class}: #{e.message}")
|
|
109
|
+
false
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def built_in_tools
|
|
113
|
+
[
|
|
114
|
+
{
|
|
115
|
+
name: "list_resources",
|
|
116
|
+
description: "List the ActiveAdmin resources the authenticated user is authorized " \
|
|
117
|
+
"to read, with their attributes",
|
|
118
|
+
inputSchema: { type: "object", properties: {} },
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "query",
|
|
122
|
+
description: "Query an ActiveAdmin resource using Ransack syntax. Respects ActiveAdmin " \
|
|
123
|
+
"authorization: the resource must be readable by the authenticated user, " \
|
|
124
|
+
"and results are scoped to the records they may access.",
|
|
125
|
+
inputSchema: {
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: {
|
|
128
|
+
resource: { type: "string", description: "Resource name (e.g., 'User', 'Post')" },
|
|
129
|
+
q: { type: "object", description: "Ransack query (e.g., {name_cont: 'john'})" },
|
|
130
|
+
limit: { type: "integer", description: "Max records (default: 25)" },
|
|
131
|
+
},
|
|
132
|
+
required: ["resource"],
|
|
50
133
|
},
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "describe_form",
|
|
137
|
+
description: "Describe the fields of a resource's ActiveAdmin form — their input " \
|
|
138
|
+
"types, labels, hints, allowed values, column types and which are " \
|
|
139
|
+
"required — so a create or update call need not guess them.",
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {
|
|
143
|
+
resource: { type: "string", description: "Resource name (e.g., 'User', 'Post')" },
|
|
144
|
+
action: {
|
|
145
|
+
type: "string",
|
|
146
|
+
enum: %w[new edit],
|
|
147
|
+
description: "Which form to describe: 'new' for creating, 'edit' for updating " \
|
|
148
|
+
"(default: 'new')",
|
|
62
149
|
},
|
|
63
|
-
required: ["resource"],
|
|
64
150
|
},
|
|
151
|
+
required: ["resource"],
|
|
65
152
|
},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
},
|
|
77
|
-
required: %w[resource id attributes],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "create",
|
|
156
|
+
description: "Create a new record. The write runs through the resource's real " \
|
|
157
|
+
"ActiveAdmin create action, so only fields its permit_params accepts " \
|
|
158
|
+
"are written and its callbacks and authorization all apply.",
|
|
159
|
+
inputSchema: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
resource: { type: "string", description: "Resource name (e.g., 'User', 'Post')" },
|
|
163
|
+
attributes: { type: "object", description: "Attributes for the new record (e.g., {name: 'Ada'})" },
|
|
78
164
|
},
|
|
165
|
+
required: %w[resource attributes],
|
|
79
166
|
},
|
|
80
|
-
|
|
81
|
-
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "update",
|
|
170
|
+
description: "Update an existing record. The write runs through the resource's real " \
|
|
171
|
+
"ActiveAdmin update action, so only fields its permit_params accepts " \
|
|
172
|
+
"are written and its callbacks and authorization all apply.",
|
|
173
|
+
inputSchema: {
|
|
174
|
+
type: "object",
|
|
175
|
+
properties: {
|
|
176
|
+
resource: { type: "string", description: "Resource name (e.g., 'User', 'Post')" },
|
|
177
|
+
id: { type: ["integer", "string"], description: "Primary key of the record to update" },
|
|
178
|
+
attributes: { type: "object", description: "Attributes to update (e.g., {name: 'New name'})" },
|
|
179
|
+
},
|
|
180
|
+
required: %w[resource id attributes],
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
]
|
|
82
184
|
end
|
|
83
185
|
|
|
84
186
|
def call_tool(params)
|
|
@@ -88,13 +190,22 @@ module ActiveadminMcp
|
|
|
88
190
|
result = case name
|
|
89
191
|
when "list_resources" then tool_list_resources
|
|
90
192
|
when "query" then tool_query(args)
|
|
193
|
+
when "describe_form" then tool_describe_form(args)
|
|
194
|
+
when "create" then tool_create(args)
|
|
91
195
|
when "update" then tool_update(args)
|
|
92
|
-
else
|
|
196
|
+
else tool_action(name, args)
|
|
93
197
|
end
|
|
94
198
|
|
|
95
199
|
{ content: [{ type: "text", text: JSON.pretty_generate(result) }] }
|
|
96
200
|
end
|
|
97
201
|
|
|
202
|
+
def tool_action(name, args)
|
|
203
|
+
definition = ActionCatalog.find(name, current_user: @current_user)
|
|
204
|
+
return { error: "Unknown tool: #{name}" } unless definition
|
|
205
|
+
|
|
206
|
+
ActionRunner.new(definition: definition, current_user: @current_user).call(args)
|
|
207
|
+
end
|
|
208
|
+
|
|
98
209
|
def tool_list_resources
|
|
99
210
|
entries = ResourceRegistry.resources.select { |entry| authorized_to_read?(entry) }
|
|
100
211
|
{ resources: entries.map { |entry| ResourceRegistry.resource_info(entry) } }
|
|
@@ -113,6 +224,24 @@ module ActiveadminMcp
|
|
|
113
224
|
{ resource: resource[:name], count: records.size, records: filter_sensitive(records.as_json) }
|
|
114
225
|
end
|
|
115
226
|
|
|
227
|
+
def tool_describe_form(args)
|
|
228
|
+
resource = ResourceRegistry.find(args["resource"])
|
|
229
|
+
return { error: "Resource not found: #{args['resource']}" } unless resource
|
|
230
|
+
|
|
231
|
+
FormDescription.new(resource: resource, current_user: @current_user)
|
|
232
|
+
.call(action: args["action"] || "new")
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def tool_create(args)
|
|
236
|
+
resource = ResourceRegistry.find(args["resource"])
|
|
237
|
+
return { error: "Resource not found: #{args['resource']}" } unless resource
|
|
238
|
+
|
|
239
|
+
attributes = args["attributes"] || {}
|
|
240
|
+
return { error: "attributes are required" } if attributes.empty?
|
|
241
|
+
|
|
242
|
+
RecordWriter.new(resource: resource, current_user: @current_user).create(attributes: attributes)
|
|
243
|
+
end
|
|
244
|
+
|
|
116
245
|
def tool_update(args)
|
|
117
246
|
resource = ResourceRegistry.find(args["resource"])
|
|
118
247
|
return { error: "Resource not found: #{args['resource']}" } unless resource
|
|
@@ -121,8 +250,8 @@ module ActiveadminMcp
|
|
|
121
250
|
attributes = args["attributes"] || {}
|
|
122
251
|
return { error: "attributes are required" } if attributes.empty?
|
|
123
252
|
|
|
124
|
-
|
|
125
|
-
|
|
253
|
+
RecordWriter.new(resource: resource, current_user: @current_user)
|
|
254
|
+
.update(id: args["id"], attributes: attributes)
|
|
126
255
|
end
|
|
127
256
|
|
|
128
257
|
def authorized_to_read?(resource)
|
data/lib/activeadmin_mcp.rb
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
1
|
require_relative "activeadmin_mcp/version"
|
|
4
2
|
require_relative "activeadmin_mcp/configuration"
|
|
5
3
|
require_relative "activeadmin_mcp/authorization"
|
|
4
|
+
require_relative "activeadmin_mcp/active_admin_ext"
|
|
5
|
+
require_relative "activeadmin_mcp/action_definition"
|
|
6
|
+
require_relative "activeadmin_mcp/action_schema"
|
|
7
|
+
require_relative "activeadmin_mcp/action_params"
|
|
8
|
+
require_relative "activeadmin_mcp/action_catalog"
|
|
9
|
+
require_relative "activeadmin_mcp/controller_dispatcher"
|
|
10
|
+
require_relative "activeadmin_mcp/action_runner"
|
|
6
11
|
require_relative "activeadmin_mcp/resource_registry"
|
|
7
12
|
require_relative "activeadmin_mcp/form_field_collector"
|
|
8
|
-
require_relative "activeadmin_mcp/
|
|
13
|
+
require_relative "activeadmin_mcp/form_description"
|
|
14
|
+
require_relative "activeadmin_mcp/record_writer"
|
|
9
15
|
require_relative "activeadmin_mcp/request_handler"
|
|
10
16
|
require_relative "activeadmin_mcp/engine"
|
|
11
17
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
1
|
ActiveadminMcp.configure do |config|
|
|
4
2
|
# Uncomment to enable API token authentication.
|
|
5
3
|
# Requires running the auth migration first:
|
|
@@ -19,6 +17,11 @@ ActiveadminMcp.configure do |config|
|
|
|
19
17
|
# Path where the MCP server is mounted.
|
|
20
18
|
# config.mount_path = "/mcp"
|
|
21
19
|
|
|
20
|
+
# How the engine adds its route: :prepend (default), :append, or :none.
|
|
21
|
+
# Set it to :none when you want to mount the engine yourself, for instance
|
|
22
|
+
# inside a constraints block your admin routes already sit in.
|
|
23
|
+
# config.mount_strategy = :prepend
|
|
24
|
+
|
|
22
25
|
# HTTP header used to read the Bearer token from.
|
|
23
26
|
# Useful when a reverse proxy (e.g. AWS Verified Access) strips the
|
|
24
27
|
# standard Authorization header.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activeadmin_mcp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- harunkumars
|
|
@@ -16,28 +16,28 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '7.2'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '7.2'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: activeadmin
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
|
-
- - "
|
|
31
|
+
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
33
|
+
version: '3.5'
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
|
-
- - "
|
|
38
|
+
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
40
|
+
version: '3.5'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: rake
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -95,11 +95,19 @@ files:
|
|
|
95
95
|
- app/models/activeadmin_mcp/api_token.rb
|
|
96
96
|
- config/routes.rb
|
|
97
97
|
- lib/activeadmin_mcp.rb
|
|
98
|
+
- lib/activeadmin_mcp/action_catalog.rb
|
|
99
|
+
- lib/activeadmin_mcp/action_definition.rb
|
|
100
|
+
- lib/activeadmin_mcp/action_params.rb
|
|
101
|
+
- lib/activeadmin_mcp/action_runner.rb
|
|
102
|
+
- lib/activeadmin_mcp/action_schema.rb
|
|
103
|
+
- lib/activeadmin_mcp/active_admin_ext.rb
|
|
98
104
|
- lib/activeadmin_mcp/authorization.rb
|
|
99
105
|
- lib/activeadmin_mcp/configuration.rb
|
|
106
|
+
- lib/activeadmin_mcp/controller_dispatcher.rb
|
|
100
107
|
- lib/activeadmin_mcp/engine.rb
|
|
108
|
+
- lib/activeadmin_mcp/form_description.rb
|
|
101
109
|
- lib/activeadmin_mcp/form_field_collector.rb
|
|
102
|
-
- lib/activeadmin_mcp/
|
|
110
|
+
- lib/activeadmin_mcp/record_writer.rb
|
|
103
111
|
- lib/activeadmin_mcp/request_handler.rb
|
|
104
112
|
- lib/activeadmin_mcp/resource_registry.rb
|
|
105
113
|
- lib/activeadmin_mcp/version.rb
|
|
@@ -122,14 +130,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
122
130
|
requirements:
|
|
123
131
|
- - ">="
|
|
124
132
|
- !ruby/object:Gem::Version
|
|
125
|
-
version:
|
|
133
|
+
version: 4.0.0
|
|
126
134
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
135
|
requirements:
|
|
128
136
|
- - ">="
|
|
129
137
|
- !ruby/object:Gem::Version
|
|
130
138
|
version: '0'
|
|
131
139
|
requirements: []
|
|
132
|
-
rubygems_version:
|
|
140
|
+
rubygems_version: 4.0.20
|
|
133
141
|
specification_version: 4
|
|
134
142
|
summary: MCP server for Rails apps with ActiveAdmin
|
|
135
143
|
test_files: []
|