Skip to content

MCP Guards [TAN-7975] and destroy_resource tool [TAN-7979]#14164

Merged
adessy merged 6 commits into
masterfrom
TAN-7975-mcp-create-update-guards
Jun 29, 2026
Merged

MCP Guards [TAN-7975] and destroy_resource tool [TAN-7979]#14164
adessy merged 6 commits into
masterfrom
TAN-7975-mcp-create-update-guards

Conversation

@adessy

@adessy adessy commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Changelog

For translators

@notion-workspace

Copy link
Copy Markdown

@adessy adessy changed the title [TAN-7975] Route MCP write tools through Pundit MCP Guards [TAN-7975] and destroy_resource tool [TAN-7979] Jun 24, 2026
@notion-workspace

Copy link
Copy Markdown

@cl-dev-bot

cl-dev-bot commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator
Warnings
⚠️

The changelog is empty. What should I put in the changelog?

Messages
📖 Notion issue: TAN-7975
📖 Notion issue: TAN-7979
📖

Run the e2e tests

📖 Check translation progress

Generated by 🚫 dangerJS against 5641805

@adessy adessy requested a review from amanda-anderson June 24, 2026 09:17
@adessy

adessy commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

@kogre @amanda-anderson, if that's okay with you, I've assigned Amanda to the review so we don't pile too much onto Koen's plate. But your input is of course still welcome if you'd like to chime in, Koen.

adessy added 6 commits June 24, 2026 13:53
Wires every MCP write tool through Pundit's `authorize(record, :verb)` so the core app policies decide what the current user can do. `BaseTool.for` now rescues `Pundit::NotAuthorizedError` and converts it to an LLM-friendly `isError` MCP response via `unauthorized_message`.

Today the MCP channel is super_admin-only (gated at the controller via `McpServer::McpPolicy`), so core policies already return true for every action. The wiring becomes load-bearing when moderators/admins are added to the channel.
Adds an MCP-channel guard on top of the user-level Pundit authorization: write tools that mutate a project (or anything inside one) call `authorize_project!(project)` at the top of `#run`, which refuses any operation whose target project isn't in `draft`. The check raises a `Pundit::NotAuthorizedErrorWithReason` and `BaseTool.for`'s existing rescue converts it to an LLM-friendly `isError` MCP response.

Channel-level rules live on the runner (project state), user-level rules live in Pundit policies (who can do what). Two layers, two call sites, no extra abstraction.
Generic destroy tool that dispatches on `resource_type`. It uses the same two-layer authorization as the create tools: channel-level `authorize_project!(...)` (the target project has to be in draft) plus user-level Pundit `authorize(record, :destroy?)`. Each branch mirrors the corresponding core controller's `#destroy` action through the shared `destroy_with_sidefx!` helper, except where the MCP channel needs to be stricter:

- You can't destroy a project if any of its phases contain inputs.
- You can't destroy a phase if its participation method cascades to inputs (`native_survey`, `community_monitor_survey`, `common_ground`, `proposals`) and the phase has any. Other participation methods keep their ideas via the `IdeasPhase` join, so destroying the phase is safe.
@adessy adessy force-pushed the TAN-7975-mcp-create-update-guards branch from 406f976 to 5641805 Compare June 24, 2026 11:54
input_count = Idea.where(project: project).count
return if input_count.zero?

message = 'Cannot destroy project: it has inputs that would be deleted along with it.'

@amanda-anderson amanda-anderson Jun 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not so relevant now, but I wonder about other phase types too - e.g. If there are votes submitted (baskets)?

@adessy adessy Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amanda-anderson amanda-anderson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code LGTM! 🚀 Just one small comment - not blocking.

@adessy adessy merged commit 1ea3901 into master Jun 29, 2026
17 checks passed
@adessy adessy deleted the TAN-7975-mcp-create-update-guards branch June 29, 2026 08:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants