Repository navigation
Route writes through the real ActiveAdmin controller, and add a create tool - #13
Merged
Merged
Conversation
…e tool Phase 2 of the actions-and-forms design (#8). `update` no longer calls `record.update` behind ActiveAdmin's back. Both it and the new `create` tool dispatch the resource's own controller action, so permit_params, the before_action chain, ActiveAdmin's build/create/update/save callbacks and its controller-level authorization all apply to an MCP write exactly as they do to a click on Save in the admin UI. ControllerDispatcher gains a block that hands the processed controller back to its caller. A write needs the record the controller built or loaded, and its validation errors, and it needs them even when processing raised: a rejected write re-renders the admin form, which is exactly the kind of render a synthesized request does not always survive. Resolves the permitted-params question the design left open. The fallback that derived writable fields from a `form do ... end` block does not earn its place: it was granting MCP clients a write ActiveAdmin does not grant, since a resource with no permit_params raises ForbiddenAttributesError through its own forms. It is removed, and such a resource is now refused by name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…se-2 # Conflicts: # CHANGELOG.md
This was referenced Sep 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 Phase 2 of the design in #8: writes through dispatch.
What this does
createtool (new). Creates a record by dispatching the resource's own ActiveAdmincreateaction. Resources registered without that action are refused,permit_paramsdecides what may be written, the namespace's authorization adapter is consulted before dispatch and again inside the controller, and every ActiveAdmin callback (before_build,before_create,before_save, …) fires.updatemoved onto dispatch. It no longer callsrecord.updatebehind ActiveAdmin's back. This is a behaviour change, called out in the CHANGELOG: the controller'sbefore_actionchain, ActiveAdmin'sbefore_update/after_update/before_save/after_savecallbacks and the controller's own authorization now all run on an MCP update where they were previously skipped. Applications whose callbacks have side effects — auditing, notifications, derived columns, background jobs — will see those fire for MCP updates for the first time.Two smaller consequences of the same change: a rejected write now reports
Validation failedwith the model's own messages indetails, and the record echoed back has the same sensitive attributes stripped (encrypted_password,password_digest, …) thatlist_resourcesandqueryalready omit.ControllerDispatchergained a block that hands the processed controller back to its caller, called whether processing succeeded or raised. That last part matters: a rejected write re-renders the admin form, and a synthesized request does not always survive that render — the unit harness proves it, blowing up ondestroy_admin_user_session_path. Reading validation errors off the record rather than off the response is what keeps failure reporting honest.The permitted-params question, resolved
The design left this open for phase 2 to decide. The
from_formfallback does not earn its place, and is removed along withFormFieldCollector.A resource that declares no
permit_paramscannot be saved through ActiveAdmin's own forms either: InheritedResources hands the raw unpermitted params toassign_attributesand Rails raisesActiveModel::ForbiddenAttributesError. The fallback was therefore granting MCP clients a write the admin UI itself refuses. Such a resource is now refused by name, with a message sayingpermit_paramsis missing.A slimmed permit-params resolution survives as a pre-flight gate only — it decides whether to refuse, and reports which fields were written. The filtering itself is ActiveAdmin's job now.
FormFieldCollectoris deleted rather than left as unused production code; phase 3 rewrites it substantially anyway (recording input options, not bare names) fordescribe_form.Testing
record_updater_specis replaced byrecord_writer_spec, running against the real ActiveAdmin harness rather than doubles — dispatch cannot honestly be tested against mocks. The harness gainedpermit_paramsand abefore_saveonVolunteer, plusNote(registered read-only) andSighting(registered writable with nopermit_params).Tagresource with nopermit_params,before_create/before_updatecallbacks onPostwhose effects prove a write was dispatched rather than written straight to the model, and atitlepresence validation.Postcallbacks and addingpermit_paramstoTagfailed 5 examples; removingAuthor'sactions :index, :showandPost's validation failed the other 4. Fixtures restored and both suites re-run green.README's tool table and writes section, and the CHANGELOG, are updated.
🤖 Generated with Claude Code