Skip to content

Add settings page, destructive and code scope limitations - #6

Open
elcreator wants to merge 6 commits into
evolution-cms:mainfrom
elcreator:add-settings-page
Open

elcreator wants to merge 6 commits into
evolution-cms:mainfrom
elcreator:add-settings-page

Conversation

@elcreator

@elcreator elcreator commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Settings page, a contract for tools contributed by other extras, and the gates that contract needs: per-tool permissions, destructive tools and writing backend code. Also fixes the model catalog on Evolution CMS 3.x and a
fail-open in tool registration.

Manager settings page

  • core/custom/config/cms/settings/eMCP.php is published on first boot when it is missing. A plain composer require never runs vendor:publish, so before this write tools could not be enabled without shell access.
  • Tools → MCP settings ({manager_url}/emcp/settings, needs Evo's settings permission on top of emcp) shows the boolean switches as checkboxes: endpoints, scopes, write / destructive / code tools, rate limit, audit,
    streaming. Values are rewritten token by token, so the file keeps its comments. Missing keys are inserted, and a result that does not parse is refused.
  • After publishing or saving, Evo 3.5.9+'s configuration cache (env.php) is invalidated so the change applies on the next request.
  • Manager pages return a 403 instead of calling abort(), which Evo does not define (it was a 500), and follow manager_language.

Extension tool contract (DOCS 7.0.1)

Extras add tools through a ToolProvider or mcp.servers[].extra_tools. New optional contracts in EvolutionCMS\eMCP\Contracts:

Contract Gate
WritesSite (existing) security.enable_write_tools + mcp:write
DestructiveSite also security.enable_destructive_tools (off by default) + mcp:destructive; optional reversible()
ExecutesCode also security.enable_code_tools (off by default) + mcp:code + a permission that already grants PHP in the manager (save_snippet / save_plugin / save_module)
RequiresPermission an Evo permission (or any of several), checked for the token owner
  • All checks run centrally in SecurityPolicy. The HTTP endpoint, /dispatch and the async worker path call it, and ContentServer filters its tool list with it, so the local (stdio) transport is covered too. Refusals: 403 permission_denied, tool_denied or scope_denied.
  • tools/list hides tools the caller cannot use: missing permission, a disabled destructive or code tool, deny_tools. Disabled evo.write.* tools stay listed as before.
  • Destructive calls and calls that store code are marked in the audit log (extra.source = mcp, destructive, reversible, executes_code).
  • Support\ToolError::make($code, $message, $status, $extra) returns an isError result carrying one JSON object {ok:false, error, message, status[, errors, candidates]}. Codes: forbidden, not_found, ambiguous,
    invalid, refused, store_failure, or a prefixed extension code. candidates keeps only id and title, at most 10.
  • Docs cover identity rules for extension authors: the actor comes only from evo()->getLoginUserID('mgr'), nothing acts as a fallback user under auth.mode=none, and other users' resources are answered as not_found.

Backend code gate for built-in tools

The gate looks at the arguments of each call. evo.write.elements.save for a snippet or plugin, a TV definition with @EVAL / @SELECT / @INCLUDE (in elements, default_text or display_params), and document
fields or TV values starting with a code binding (also behind @INHERIT) all need the code gate. Templates and chunks stay ordinary writes. The rules follow Evo's BackendCodePermissions / TvBindingGuard when the core has
them, with an identical fallback for older cores. So save_template or save_document alone can no longer store @EVAL through MCP.

Tool annotations

readOnlyHint / destructiveHint are derived from the markers, and hints a tool sets itself are kept. evo.write.content.update and evo.write.elements.save are destructiveHint: true because they overwrite without a
revision. WritesSite tools only get readOnlyHint: false: MCP reads a missing destructiveHint as true, so a tool that only adds data declares #[IsDestructive(false)] itself.

Fixes

  • Fail-open in tool registration: a contributed tool without #[Name] was served under its class-derived name while the policy layer looked only at the attribute, so it skipped the write gates. ToolRegistry now resolves
    names the way Laravel MCP does, and rejects tools it cannot name, evo.* names, duplicates and non-Tool classes (SPEC 6.4/6.5: throws in debug mode, logs otherwise).
  • evo.model.list|get for User, UserRole and Permissions selected columns that do not exist in Evolution CMS 3.x and failed with an SQL error. The allowlists now match the 3.x schema.
  • tools/list post-processing keeps empty JSON objects ({} schemas and annotations) instead of turning them into [].

Housekeeping

  • The JSON-RPC body is decoded once per request and shared by the scope, rate-limit and permission checks (Support\JsonRpcPayload).
  • seiger/sapi ^1.0 and seiger/stask ^1.0.10 || ^2.0 replace *, so composer validate --strict is clean again. sTask 2.x needs PHP 8.4, and the APIs eMCP uses are identical in 1.x and 2.x.
  • composer run governance:update-lock no longer rewrites .ci/governance-lock.json when only updated_at would change.
  • docker: drop Evo's configuration cache after installing the package, otherwise Evo 3.5.9 does not load the eMCP provider.
  • New strings are translated into all 22 locales; DOCS.uk.md / README.uk.md are updated.

Upgrade notes

  • New scopes mcp:destructive and mcp:code. Each implies mcp:write, mcp:call and mcp:read when a token is issued. Existing tokens keep working, but they can no longer save snippets or plugins, or write code bindings,
    until security.enable_code_tools is on and the token has mcp:code.
  • New settings: security.enable_destructive_tools, security.enable_code_tools (both false), auth.destructive_scope, auth.code_scope.
  • A contributed tool that overrides name() without a #[Name] attribute, or uses an evo.* name, is no longer served.

elcreator and others added 5 commits September 29, 2026 13:59
evo.model.list|get selected columns that no longer exist in Evolution CMS 3.x
(users.isfrontend/createdon/blocked..., user_roles.frames/home/rank/locked,
permissions.description) and failed with "Unknown column". The allowlists now
list only real columns; block state and timestamps stay available through
UserAttribute.

A new unit test checks every allowlisted field against the column listing of a
stock Evo 3.5 install, so a projection can no longer point at a missing column.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The site copy of the settings (core/custom/config/cms/settings/eMCP.php) is
  created on first boot when missing: a plain `composer require` never runs
  vendor:publish, so write tools could not be enabled without shell access.
- Tools -> MCP settings (`{manager_url}/emcp/settings`) shows the boolean
  switches (write tools, endpoints, scopes, rate limit, audit, streaming) as
  checkboxes. It needs Evo's `settings` permission on top of `emcp`. Values are
  rewritten token by token so the file keeps its comments; missing keys are
  inserted, a result that does not parse is refused.
- After publishing or saving, Evo 3.5.9+'s configuration cache (env.php) is
  invalidated so the change applies on the next request.
- Manager pages return a 403 response instead of calling abort(), which Evo
  does not define (was a 500), and follow manager_language instead of always
  rendering English.
- Page and menu strings translated into all 22 locales.
- docker: drop the configuration cache after installing the package, otherwise
  Evo 3.5.9 does not load the eMCP provider at all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@elcreator elcreator changed the title Add settings page Add settings page, destructive and code scope limitations Oct 17, 2026
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.

1 participant