Skip to main content
Glama
eMCP for Evolution CMS

eMCP for Evolution CMS

Official

eMCP for Evolution CMS

eMCP is the Evolution CMS integration layer for laravel/mcp.

It adapts Laravel MCP to Evo runtime with:

  • Evo-native config publishing

  • manager ACL and sApi scope controls

  • optional async dispatch through sTask

  • no Laravel app skeleton requirement

  • Evo domain MCP tools for document tree (SiteContent + TVs)

Implementation starts with a strict MVP gate:

  • web transport

  • manager mode

  • initialize + tools/list

Design style:

  • contract-first (TOOLSET.md + validators)

  • declarative config-first server registry (config/mcp.php)

  • explicit handler pipeline (validate -> authorize -> query -> map -> paginate)

If you need full architecture and contracts, see DOCS.md (EN) or DOCS.uk.md (UA). Public canonical tool contract: TOOLSET.md. Versioning and BC policy: PRD.md (API Stability Policy section). Operations runbook: OPERATIONS.md.

Requirements

  • Evolution CMS 3.5.2+

  • PHP 8.3+

  • Composer 2.2+

  • seiger/sapi 1.x (installed as dependency; only used when auth.mode = sapi_jwt)

  • seiger/stask 1.x (from 1.0.10) or 2.x, installed as dependency (2.x needs PHP 8.4)

Related MCP server: terminus-mcp

Install

From your Evo core directory:

cd core
php artisan package:installrequire evolution-cms/emcp "*"
php artisan migrate

Publish Config and Stubs

core/custom/config/cms/settings/eMCP.php is created automatically on the first request after install when it is missing, so emcp-config is only needed to restore the defaults. emcp-mcp-config changes the MCP server list, emcp-stubs scaffolds your own servers and tools.

Site-wide switches (write tools, manager/API endpoints, token scopes, rate limit, audit log, streaming) are also available as checkboxes in Tools → MCP settings ({manager_url}/emcp/settings, requires the settings permission). The page edits the same file and keeps its comments.

php artisan vendor:publish --provider="EvolutionCMS\\eMCP\\eMCPServiceProvider" --tag=emcp-config
php artisan vendor:publish --provider="EvolutionCMS\\eMCP\\eMCPServiceProvider" --tag=emcp-mcp-config
php artisan vendor:publish --provider="EvolutionCMS\\eMCP\\eMCPServiceProvider" --tag=emcp-stubs

Published files:

  • core/custom/config/cms/settings/eMCP.php

  • core/custom/config/mcp.php

  • core/stubs/mcp-*.stub

Quick Start (Internal + External)

The default contract is concept-agnostic and follows Laravel MCP behavior first.

  1. Create your MCP server/tool classes:

php artisan make:mcp-server ContentServer
php artisan make:mcp-tool HealthTool

Generated classes are placed in core/custom/app/Mcp/....

  1. Register server in core/custom/config/mcp.php (servers[]).

  2. Test manager/internal route:

  • POST /{manager_prefix}/{handle} with manager session + permission emcp.

  1. Enable external API mode (if sApi installed):

  • keep mode.api=true in core/custom/config/cms/settings/eMCP.php

  • call POST /{SAPI_BASE_PATH}/{SAPI_VERSION}/mcp/{handle} with Bearer JWT and required mcp:* scopes.

  • get JWT from POST /{SAPI_BASE_PATH}/{SAPI_VERSION}/token (sApi token endpoint).

  1. Optional async:

  • set queue.driver=stask, ensure sTask installed, use dispatch endpoint for long-running jobs.

Personal access tokens (default API auth)

Since this version the API endpoint needs no extra packages: a manager user creates a personal access token and every request runs as that user, with that user's role, permissions and document groups — exactly as if they were logged into the manager.

  1. Give the role the emcp permission (admins have it after migrate).

  2. Open Tools → MCP tokens in the manager ({manager_url}/emcp/tokens), or run php artisan emcp:token:create <username> --scopes=mcp:read,mcp:call --expires=90.

  3. Connect the agent:

claude mcp add --transport http evo https://example.com/mcp/content   --header "Authorization: Bearer emcp_..."
# Codex ~/.codex/config.toml
[mcp_servers.evo]
url = "https://example.com/mcp/content"
bearer_token_env_var = "EVO_MCP_TOKEN"

Scopes narrow a token, never widen the user: mcp:read (list/read), mcp:call (read-only tools), mcp:write (evo.write.* tools, also gated by security.enable_write_tools), mcp:destructive (extension tools that delete data, also gated by security.enable_destructive_tools), mcp:code (saving snippets, plugins and @EVAL-style bindings, also gated by security.enable_code_tools and a snippet/plugin/module permission), mcp:admin. Tokens are stored hashed, can expire, and are revoked from the same page or with emcp:token:revoke. emcp:token:list shows what exists.

auth.mode in core/custom/config/cms/settings/eMCP.php selects pat (default), sapi_jwt (seiger/sapi JWT, now also impersonating the JWT user) or none.

Write tools

evo.write.content.update|create|publish, evo.write.elements.save, evo.write.cache.clear plus the read tools evo.elements.list|get. Each one re-checks the manager permission of the matching manager action (save_document, publish_document, save_chunk, new_snippet, ...), document-group access, element locks, fires the same OnBefore*FormSave/On*FormSave events and writes a manager_log row, so an admin sees API changes next to browser ones.

Tools from other extras

An extra contributes tools by implementing EvolutionCMS\eMCP\Contracts\ToolProvider and registering it in its service provider's boot():

if (class_exists(\EvolutionCMS\eMCP\Services\ToolRegistry::class)) {
    app(\EvolutionCMS\eMCP\Services\ToolRegistry::class)->register(new MyToolProvider());
}

Tools are plain Laravel\Mcp\Server\Tool classes with a #[Name('vendor.domain.action')]. Mark the ones that change the site with EvolutionCMS\eMCP\Contracts\WritesSite so security.enable_write_tools and the mcp:write scope apply to them, the ones that delete data with DestructiveSite, the ones that store server-side code with ExecutesCode, and declare a required Evo permission with RequiresPermission. Tool code runs as the impersonated manager, so evo()->hasPermission() and the extra's own guards behave as on the page; Support\ToolError builds structured error results. Alternatively list classes under mcp.servers[].extra_tools. Example: elcreator/aimage ships aimage.* tools this way. The full contract (naming, identity rules, error format) is in DOCS.md.

Try it in Docker

cd docker && docker compose up --build      # prints the site URL and a ready-made token
EVO_EXTRAS=elcreator/aimage AIMAGE_API_KEY=... docker compose up --build   # with extras
docker/smoke.sh                             # runs an end-to-end check against it

Claude Code skills

The repository is a Claude Code plugin marketplace. Its skills teach an agent how to use the tools: emcp (documents, elements, users, cache, extending) and the extras' own plugins.

claude plugin marketplace add evolution-cms/evo-mcp
claude plugin install emcp@evolution-cms
claude plugin install aimage@evolution-cms   # example - optional, with elcreator/aimage

Other agents can use skills/evolution-cms-mcp/SKILL.md directly. An extra that adds tools ships its skill in its own skills/ folder with a .claude-plugin/plugin.json, and is listed in .claude-plugin/marketplace.json.

Design Philosophy (Optional Reading)

Why This Product Exists (4 Core Questions, Aristotle)

This is the shortest way to understand eMCP as a product, not just a package.

  1. Material cause: what it consists of (hard boundaries):

  • protocol/runtime from laravel/mcp

  • Evo adapter layer (ServiceProvider, registry, routes, middleware, publish)

  • optional access/async integrations (sApi, sTask)

  • canonical contracts (SPEC.md, TOOLSET.md)

  1. Formal cause: what form makes it a product (not components):

  • one execution contract from request to audited response

  • one policy model for manager/API access (ACL + scopes + limits)

  • one versioned public tool contract for ecosystem consumers

  • one stable extension model for third-party packages

  1. Efficient cause: what puts it in motion (workflows + triggers):

  • internal trigger: manager MCP call (/{manager_prefix}/{handle})

  • external trigger: API MCP call (/{SAPI_BASE_PATH}/{SAPI_VERSION}/mcp/{handle})

  • async trigger: dispatch into sTask worker for long operations

  • lifecycle trigger: package install/publish/register/test

  1. Final cause: why it is built this way:

  • keep Laravel MCP semantics intact

  • keep Evo integration explicit and operable

  • support both internal and external MCP usage

  • allow multiple orchestration strategies on top of one neutral MCP foundation

Conceptual Model (Design Lens)

This lens helps explain architecture decisions:

  • set theory: CMS data is structured sets (site -> nodes -> attributes)

  • Peano sequence: workflows are ordered state transitions

  • Godel limits: self-referential rule systems need strict boundaries

Practical implication:

  • eMCP stays as a contract/runtime layer

  • orchestration logic stays in consuming packages

  • policy/audit/human gates prevent recursive rule loops from becoming unsafe

Install Verification (1 minute)

For Gate A, use manager endpoint /{manager_prefix}/{server_handle} (default: /emcp/content). Gate A is manager ACL protected, so run checks as a logged-in manager with emcp permission (session cookie required).

  1. Verify GET returns 405 on MCP endpoint:

curl -i -X GET http://localhost/<MANAGER_PREFIX>/<SERVER_HANDLE> \
  -H 'Cookie: evo_session=<MANAGER_SESSION_COOKIE>'
  1. Verify JSON-RPC initialize:

curl -i -X POST http://localhost/<MANAGER_PREFIX>/<SERVER_HANDLE> \
  -H 'Cookie: evo_session=<MANAGER_SESSION_COOKIE>' \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":"init-1","method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"smoke","version":"1.0.0"}}}'

Expected:

  • HTTP 200 for valid initialize.

  • MCP-Session-Id present in response headers.

  • stable HTTP 405 on GET.

Register MCP Servers (Evo style)

Unlike Laravel default routes/ai.php, eMCP registers servers from config.

Example in core/custom/config/mcp.php:

return [
    'redirect_domains' => ['*'],

    'servers' => [
        [
            'handle' => 'content',
            'transport' => 'web',
            'route' => '/mcp/content',
            'class' => EvolutionCMS\eMCP\Servers\ContentServer::class,
            'enabled' => true,
            'auth' => 'sapi_jwt',
            'scopes' => ['mcp:read', 'mcp:call'],
        ],
        [
            'handle' => 'content-local',
            'transport' => 'local',
            'class' => EvolutionCMS\eMCP\Servers\ContentServer::class,
            'enabled' => false,
        ],
    ],
];

Notes:

  • Gate A manager endpoint is still /{manager_prefix}/{handle} (for example /emcp/content).

  • servers[*].route is used by web transport registration and becomes externally relevant in API mode (Gate B+).

  • content-local is disabled by default to avoid duplicate tool-name registration conflict with content.

Access Model

  • Manager/internal access: Evo permission emcp

  • API access (via sApi): JWT scopes (mcp:read, mcp:call, mcp:admin)

  • Domain reads (evo.content.*, evo.model.*) are read-only by default

Ecosystem Interop

eMCP is the MCP platform layer for the Evo ecosystem:

  • LaravelMcp: upstream protocol/runtime contract (kept intact).

  • sApi: external API kernel + JWT route-provider discovery.

  • sTask: async worker/task execution and progress.

  • eAi: AI runtime can call MCP tools through manager or API mode.

  • dAi: manager-side orchestration UI can consume eMCP tools as a stable contract.

This keeps the core declarative and neutral: one MCP foundation for multiple orchestration concepts.

Evo Domain Tools

  • Implemented now: evo.content.search|get|root_tree|descendants|ancestors|children|siblings

  • Optional (implemented): evo.content.neighbors|prev_siblings|next_siblings|children_range|siblings_range

  • TV-aware queries via structured with_tvs, tv_filters, tv_order

  • evo.model.list|get implemented with per-model explicit allowlist projection and sensitive-field defense-in-depth blacklist

Artisan Commands

From Laravel MCP (available via eMCP adapter):

php artisan make:mcp-server ContentServer
php artisan make:mcp-tool ListResourcesTool
php artisan make:mcp-resource DocsResource
php artisan make:mcp-prompt SummaryPrompt
php artisan mcp:start content-local

For mcp:start content-local, first enable content-local in core/custom/config/mcp.php and disable conflicting server entries if they expose identical tool names.

Third-party servers

Register project-specific tools on a separate Laravel\Mcp\Server class and add that class as its own entry in core/custom/config/mcp.php. Third-party tool names must not use the reserved evo.* namespace. Do not subclass the package ContentServer only to append project tools, because the inherited evo.* toolset is package-owned and the registry will reject that external server.

php artisan emcp:test --server=<handle> runs generic initialize and tools/list checks for third-party servers. When the selected class is the canonical eMCP ContentServer, it also verifies the required Evolution toolset. Use php artisan emcp:list-servers to see both accepted servers and concrete reasons why configured entries were rejected.

eMCP operational commands:

  • php artisan emcp:test

  • php artisan emcp:list-servers

  • php artisan emcp:sync-workers

  • composer run governance:update-lock

  • composer run ci:check

  • composer run benchmark:run

  • composer run benchmark:leaderboard

  • composer run test:integration:clean-install

Repository Checks (for first run in package workspace)

If you are validating this repository directly:

composer run check
make test
composer run ci:check
make benchmark
make leaderboard

These checks validate composer.json and run PHP syntax lint across package sources.

One-click demo + full MCP verification:

make demo-all

This target installs demo Evo, starts php -S, issues sApi JWT, runs php artisan emcp:test, then runs composer run test with HTTP runtime integration enabled. After run, detailed evidence is written to:

  • demo/logs.md (token/masked auth info, MCP request payloads, HTTP statuses, responses, manual verification commands, plus negative probes: 401/403/413/415/409/429 and evo.model.get(User) sanity)

  • demo/logs.md also includes local sTask lifecycle proof (queued -> completed) via php artisan stask:worker in demo runtime.

  • /tmp/emcp-demo-php-server.log (php built-in server log)

If GitHub API auth is needed during install, pass token via ENV (same pattern as evolution):

GITHUB_PAT=ghp_xxx make demo-all

Fallback ENV names are also supported: GITHUB_TOKEN, GH_TOKEN.

Manual content-read MCP examples (same calls used in demo/logs.md):

# list tools
curl -sS -H 'Content-Type: application/json' -H 'Authorization: Bearer <TOKEN>' \
  -d '{"jsonrpc":"2.0","id":"tools-1","method":"tools/list","params":{}}' \
  'http://127.0.0.1:8787/api/v1/mcp/content'

# read content slice from DB
curl -sS -H 'Content-Type: application/json' -H 'Authorization: Bearer <TOKEN>' \
  -d '{"jsonrpc":"2.0","id":"search-1","method":"tools/call","params":{"name":"evo.content.search","arguments":{"limit":3,"offset":0}}}' \
  'http://127.0.0.1:8787/api/v1/mcp/content'

# read one document
curl -sS -H 'Content-Type: application/json' -H 'Authorization: Bearer <TOKEN>' \
  -d '{"jsonrpc":"2.0","id":"get-1","method":"tools/call","params":{"name":"evo.content.get","arguments":{"id":1}}}' \
  'http://127.0.0.1:8787/api/v1/mcp/content'

Optional runtime integration check (against deployed environment):

EMCP_INTEGRATION_ENABLED=1 \
EMCP_BASE_URL="https://example.org" \
EMCP_API_PATH="/api/v1/mcp/{server}" \
EMCP_API_TOKEN="<jwt>" \
EMCP_SERVER_HANDLE="content" \
EMCP_DISPATCH_CHECK=1 \
composer run test:integration:runtime

CI release note:

  • .github/workflows/ci.yml runs demo-runtime-proof, runtime-integration, and migration-matrix (sqlite/mysql/pgsql) on release/* pushes.

  • Configure branch protection to make these jobs required for RC/release merges.

Async (sTask-first)

If queue.driver=stask and sTask is installed, eMCP can run long MCP calls via worker emcp_dispatch. If sTask is missing, fallback behavior follows queue.failover (sync or fail).

Security Notes

  • Keep secrets in .env or core/custom/config/*.

  • Audit logs must redact tokens/secrets.

  • Use tool denylist and server allowlist for production hardening.

Security Defaults

  • deny-by-default for manager/API without explicit access.

  • security.enable_write_tools=false by default.

  • sensitive key redaction in logs is mandatory.

  • API scope checks (mcp:read|call|admin) are required in Gate B+.

  • depth/limit/payload limits should stay enabled.

Security release checklist: SECURITY_CHECKLIST.md. Threat model: THREAT_MODEL.md. Architecture freeze: ARCHITECTURE_FREEZE_CHECKLIST.md.

License

MIT (LICENSE).

Available Tools

21 tools
evo.content.ancestorsContent Ancestors ToolB
Read-only

Read ancestors of a content node with optional depth cap and pagination.

Inputs: id (required), limit (required), depth, offset.

evo.content.childrenContent Children ToolB
Read-only

Read direct children of a content node with pagination.

Inputs: id (required), limit (required), offset.

evo.content.children_rangeContent Children Range ToolB
Read-only

Read child nodes by menuindex position range for a parent node.

Inputs: id (required), from (required), limit (required), to, +1 more.

evo.content.descendantsContent Descendants ToolC
Read-only

Read descendants of a content node with bounded depth and pagination.

Inputs: id (required), limit (required), depth, offset.

evo.content.getContent Get ToolB
Read-only

Get one content document by id, including its body (content, introtext, menutitle) and optional TVs projection.

Inputs: id (required).

evo.content.neighborsContent Neighbors ToolC
Read-only

Read immediate previous/next neighbor nodes for a content item with pagination.

Inputs: id (required), limit (required), offset.

evo.content.next_siblingsContent Next Siblings ToolC
Read-only

Read next siblings of a content node (higher menuindex in same parent).

Inputs: id (required), limit (required), offset.

evo.content.prev_siblingsContent Prev Siblings ToolB
Read-only

Read previous siblings of a content node (lower menuindex in same parent).

Inputs: id (required), limit (required), offset.

evo.content.root_treeContent Root Tree ToolB
Read-only

Read root tree content (from root nodes) with bounded depth and pagination.

Inputs: limit (required), depth, offset.

evo.content.searchContent Search ToolB
Read-only

Search Evolution CMS content with pagination, depth caps, and structured TV filters/order.

Inputs: depth, limit, offset, parent, +3 more.

evo.content.siblingsContent Siblings ToolC
Read-only

Read sibling documents of a content node with pagination.

Inputs: id (required), limit (required), offset.

evo.content.siblings_rangeContent Siblings Range ToolC
Read-only

Read sibling nodes by menuindex position range for a node parent set.

Inputs: id (required), from (required), limit (required), to, +1 more.

evo.elements.getElements Get ToolB
Read-only

Read one template, TV, chunk, snippet or plugin including its code, by id or name.

Inputs: type (required), id, name.

evo.elements.listElements List ToolB
Read-only

List templates, TVs, chunks, snippets or plugins (without code) the acting user may edit.

Inputs: type (required), limit, offset, search, +1 more.

evo.model.getModel Get ToolB
Read-only

Get one allowlisted Evolution CMS model record by id with allowlist projection.

Inputs: id (required), model (required).

evo.model.listModel List ToolC
Read-only

List allowlisted Evolution CMS models with explicit allowlist projection.

Inputs: limit (required), model (required), offset, order_by, +1 more.

evo.write.cache.clearCache Clear ToolA

Clear the site cache (same as "Refresh site" in the manager). Requires empty_cache.

Inputs: no named parameters.

evo.write.content.createContent Create ToolA

Create a document under a parent. Requires new_document (and publish_document to create it published).

Inputs: fields (required), parent (required), tvs.

evo.write.content.publishContent Publish ToolA

Publish, unpublish, delete (to trash) or restore a document. Requires publish_document or delete_document.

Inputs: id (required), action (required).

evo.write.content.updateContent Update ToolB
Destructive

Update fields and TVs of an existing document. Requires save_document; publish state changes need publish_document.

Inputs: id (required), tvs, fields.

evo.write.elements.saveElements Save ToolA
Destructive

Create or update a template, TV, chunk, snippet or plugin. Requires new_*/save_* permission for the type; fires the same OnBeforeFormSave/OnFormSave events as the manager.

Inputs: type (required), fields (required), id.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.1/5.0

    Scored across 21 tools

    Disambiguation3/5

    Several content-tree readers overlap heavily: prev_siblings/next_siblings, siblings, siblings_range, neighbors, children, and children_range all navigate related node sets. The descriptions help distinguish them, but an agent can easily choose the wrong granularity or variant. Model, elements, and write tools are otherwise fairly distinct.

    Naming Consistency4/5

    The dotted namespace pattern is mostly predictable: evo.content.*, evo.model.*, evo.elements.*, and evo.write.*. Minor inconsistencies exist in verb placement and noun-oriented names such as root_tree, children, and ancestors, but the convention remains readable.

    Tool Count3/5

    At 21 tools, the surface is at the heavy end for a CMS integration, driven by many fine-grained content traversal variants. The set is functional but could be consolidated by combining range/neighbor/sibling navigation options.

    Completeness4/5

    Core content operations are well covered: search, get, tree traversal, create, update, publish/unpublish, delete/restore, plus elements list/get/save and cache clearing. Notable gaps are element deletion and any model write lifecycle, though these may be intentional for an allowlisted/read-only model surface.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables external AI agents to edit and manage a self-hosted website via a token-gated MCP server, including page content, data, media, and sandboxed functions with validation and rollback.
      3
      Apache 2.0
    • F
      license
      Not graded
      quality
      B
      maintenance
      Connects MCP-capable agents to an authenticated ELEMS account, enabling website creation and inventory management, page and element editing, media and template handling, structured content authoring, draft previews, and explicit publishing.
      -
    • A
      license
      Not graded
      quality
      C
      maintenance
      Connects MCP-compatible agents to company files, spreadsheets, ERP exports, and records with per-person permissions, enabling document search, SQL workbook queries, versioned spreadsheet edits, file management, and role administration with history.
      MIT