Official MCP platform layer for Evolution CMS

Maintainers

Package info

github.com/evolution-cms/eMCP

Type:evolutioncms-plugin

pkg:composer/evolution-cms/emcp

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-03-04 04:18 UTC

This package is auto-updated.

Last update: 2026-03-04 04:18:10 UTC


README

Total Downloads Latest Stable Version License

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.4+
  • Composer 2.2+
  • seiger/sapi 1.x (installed as dependency)
  • seiger/stask 1.x (installed as dependency)

Install

From your Evo core directory:

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

Publish Config and Stubs

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.

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.

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).