This document describes how eMCP integrates laravel/mcp into Evolution CMS.
It is the implementation-oriented guide aligned with PRD.md and SPEC.md.
Contract boundary:
SPEC.mdandTOOLSET.mdare normative.DOCS.mddescribes how to implement and operate those contracts.OPERATIONS.mdis the day-2 operator runbook (profiles, health checks, triage).
Status marker:
- Contract target:
SPEC 1.0-contract. - Runtime baseline in current repository:
Gate C baseline implemented (full validation pending).
eMCP is a thin Evo-native adapter around laravel/mcp.
Core goals:
- keep upstream MCP runtime and protocol behavior
- adapt registration/config/routes for Evo architecture
- enforce ACL/scopes and enterprise security controls
- support both internal manager usage and external API clients
Integration layers:
- Protocol layer:
laravel/mcp(Server, Registrar, transports, JSON-RPC methods) - Adapter layer:
eMCPprovider, registry, middleware, publishing - API layer: optional
sApiroute provider with JWT scopes - Async layer: optional
sTaskworker (emcp_dispatch)
Implementation order is mandatory:
- Gate A: web transport + manager route + manager ACL (
emcp) +initialize+tools/list+GET=405 - Gate B: API access layer (scope engine, basic rate limit,
sApiprovider) - Gate C: async (
sTaskworker, payload contract, failover, idempotency) - Gate D: security hardening + DX commands
Minimal first release is Gate A only.
eMCP keeps upstream laravel/mcp behavior as the baseline:
GETon MCP transport route returns405.POSTprocesses JSON-RPC messages.MCP-Session-Idis passed through request/response.202is preserved for no-reply notification flows.- SSE transport response uses
text/event-streamwhen streaming is enabled. - Upstream command surface (
make:mcp-*,mcp:start,mcp:inspector) remains available.
eMCP-specific logic is additive (ACL/scopes/policies), not a protocol rewrite.
sApi: external exposure for MCP endpoints through route providers and JWT scopes.sTask: async dispatch for long-running MCP calls (emcp_dispatchworker).eAi: AI runtime can consume eMCP tools in manager or API mode.dAi: manager orchestration UI can consume stable eMCP tool contracts.
Boundary rule:
- eMCP provides protocol/runtime/policy contracts.
- orchestration concepts live in consuming packages, not in eMCP core.
- Generate primitives:
php artisan make:mcp-server ContentServer
php artisan make:mcp-tool HealthToolGenerated classes are placed under core/custom/app/Mcp/....
2. Register server entry in core/custom/config/mcp.php.
3. Verify manager/internal call:
POST /{manager_prefix}/{handle}with manager session andemcppermission.
- Verify external API call (with
sApiinstalled):
POST /{SAPI_BASE_PATH}/{SAPI_VERSION}/mcp/{handle}Authorization: Bearer <jwt>with requiredmcp:*scopes.
- Optional async mode:
queue.driver=stask+ dispatch endpoint + task progress tracking.
Mandatory:
- Evolution CMS 3.5.2+
- PHP 8.4+
- Composer 2.2+
seiger/sapi1.xseiger/stask1.x
From Evo core directory:
cd core
php artisan package:installrequire evolution-cms/emcp "*"
php artisan migrateAuto-publish may be enabled by installer flows, but explicit publish is recommended:
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-stubsPublished targets:
core/custom/config/cms/settings/eMCP.phpcore/custom/config/mcp.phpcore/stubs/mcp-server.stubcore/stubs/mcp-tool.stubcore/stubs/mcp-resource.stubcore/stubs/mcp-prompt.stub
Recommended baseline:
Phase labels:
[MVP]enable,mode.internal,route.manager_prefix,acl.permission.[Gate B+]mode.api,auth.*,rate_limit.*,limits.*,security.allow_servers,security.deny_tools,domain.*,actor.*.[Gate C+]queue.*, async/idempotency behavior.[Gate E]logging.audit_enabled, hardening/redaction tuning.
return [
'enable' => true,
'mode' => [
'internal' => true,
'api' => true,
],
'route' => [
'manager_prefix' => 'emcp',
'api_prefix' => 'mcp',
],
'auth' => [
'mode' => 'sapi_jwt',
'require_scopes' => true,
'scope_map' => [
'mcp:read' => ['initialize', 'ping', 'tools/list', 'resources/list', 'resources/read', 'prompts/list', 'prompts/get', 'completion/complete'],
'mcp:call' => ['tools/call'],
'mcp:admin' => ['admin/*'],
],
],
'acl' => [
'permission' => 'emcp',
],
'queue' => [
'driver' => 'stask',
'failover' => 'sync',
],
'rate_limit' => [
'enabled' => true,
'per_minute' => 60,
],
'limits' => [
'max_payload_kb' => 256,
'max_result_items' => 100,
'max_result_bytes' => 1048576,
],
'logging' => [
'channel' => 'emcp',
'audit_enabled' => true,
'redact_keys' => ['authorization', 'token', 'jwt', 'secret', 'cookie', 'password', 'api_key'],
],
'security' => [
'allow_servers' => ['*'],
'deny_tools' => [],
'enable_write_tools' => false,
],
'domain' => [
'content' => [
'max_depth' => 6,
'max_limit' => 100,
'max_offset' => 5000,
],
'models' => [
'max_offset' => 5000,
'allow' => [
'SiteTemplate', 'SiteTmplvar', 'SiteTmplvarContentvalue',
'SiteSnippet', 'SitePlugin', 'SiteModule', 'Category',
'User', 'UserAttribute', 'UserRole', 'Permissions', 'PermissionsGroups', 'RolePermissions',
],
],
],
'actor' => [
'mode' => 'initiator',
'service_username' => 'MCP',
'service_role' => 'MCP',
'block_login' => true,
],
];eMCP uses this file as the MCP server registry.
Example:
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,
],
],
];Note:
content-localis disabled by default to avoid duplicate tool-name registration conflicts withcontent.- Enable local transport only when conflicting server entries are disabled or use non-overlapping tool names.
Validation rules:
handlemust be uniqueclassmust exist and extendLaravel\Mcp\Servertransportmust beweborlocalenabled=trueto register
Optional per-server overrides:
scope_maplimits.max_payload_kblimits.max_result_itemsrate_limit.per_minutesecurity.deny_tools
Route semantics:
- Gate A manager endpoint is
/{manager_prefix}/{handle}. servers[*].routeis the web transport route binding and is externally relevant in API mode (Gate B+).
Use these presets to reduce setup friction:
manager-only:mode.internal=true,mode.api=false- use manager endpoint only (
/{manager_prefix}/{handle})
api-only:mode.internal=false,mode.api=true- require
sApi+ JWT scopes
hybrid(recommended default):mode.internal=true,mode.api=true- same tool contract available for manager and API consumers
Async add-on (any preset):
queue.driver=staskfor long-running calls.queue.failover=syncfor resilient fallback on installations withoutsTask.
Upstream Laravel MCP expects routes/ai.php.
eMCP replaces this with config-first registration for Evo.
Mapping:
transport=web->Mcp::web(route, class)transport=local->Mcp::local(handle, class)
This keeps upstream runtime behavior while fitting Evo package architecture.
- Add server entries in
core/custom/config/mcp.phpwith uniquehandle. - Extend per-server policy via
scope_map,limits,rate_limit,security.deny_tools. - Add tools/resources/prompts through generators (
make:mcp-*) and shared stubs.
Canonical source for tool names/params/examples/errors is TOOLSET.md.
Primary domain profile is document tree access via SiteContent:
evo.content.searchevo.content.getevo.content.root_treeevo.content.descendantsevo.content.ancestorsevo.content.childrenevo.content.siblings
Optional (implemented) tree tools:
evo.content.neighborsevo.content.prev_siblingsevo.content.next_siblingsevo.content.children_rangeevo.content.siblings_range
TV support is part of the contract:
with_tvsmaps towithTVs- structured
tv_filtersmaps totvFilter - structured
tv_ordermaps totvOrderBy tags_datamaps totagsDataorder_by_datemaps toorderByDate
Safety constraints:
- reject raw
tvFilterDSL strings from client payloads - allow only approved operators/casts
- enforce
depth/limit/offsetcaps from config
Contract-first execution style:
- each tool call follows
validate -> authorize -> query -> map -> paginate - one tool should map to one explicit handler/procedure
- hidden query/policy side effects outside the pipeline are discouraged
Orchestration execution profile (post-MVP):
Intent -> PolicyCheck -> Task(s) -> EvidenceTrace -> ApprovalGate- planner actions should be constrained by policy-valid action sets
- intent/task/evidence linkage should be auditable end-to-end
Model catalog profile (read-only by default):
evo.model.listevo.model.get
Default allowed models:
SiteTemplate,SiteTmplvar,SiteTmplvarContentvalueSiteSnippet,SitePlugin,SiteModule,CategoryUser,UserAttribute,UserRole,Permissions,PermissionsGroups,RolePermissions
Sensitive fields are always excluded/redacted:
password,cachepwd,verified_key,refresh_token,access_token,sessionid
Migrations should create:
- permission group:
eMCP(or project-selected shared group) - permissions:
emcp(access)emcp_manage(management actions)emcp_dispatch(async dispatch)
Delivery gates:
- Gate A (MVP):
emcpis required. - Gate B+: add
emcp_manage. - Gate C+: add
emcp_dispatch.
Default assignment:
- role
1(admin) receives allemcp*permissions
Minimum scope policy:
mcp:read:initialize,ping, list/read/get methodsmcp:call:tools/callmcp:admin: server admin/service actions
Rules:
- If
auth.require_scopes=true, scope check is mandatory. *in token scopes grants full MCP access.
Supported modes:
sapi_jwt(default): usessApiJWT middleware attributesnone(restricted/internal scenarios only)
Behavior contract:
- Missing auth configuration must not break package boot.
Under mgr middleware and emcp permission:
POST /{manager_prefix}/{server}POST /{manager_prefix}/{server}/dispatch
Rules:
GETon MCP endpoint returns405POSTexpects JSON-RPC body- pass through
MCP-Session-Id
Via McpRouteProvider (RouteProviderInterface):
POST /mcp/{server}POST /mcp/{server}/dispatch
Recommended middleware chain:
emcp.jwtemcp.scopeemcp.actoremcp.rate
McpRouteProvider removes upstream sapi.jwt from MCP routes and uses emcp.jwt as the single JWT middleware.
Error handling policy:
- transport/auth/middleware failures -> HTTP status (
401/403/405/413/415) with non-JSON-RPC error body. - JSON-RPC dispatch failures -> HTTP
200+ JSON-RPCerror(-32700,-32600,-32601,-32602,-32603).
For exact normative mapping and error body shape, use SPEC.md.
When MCP method streams iterable responses, response must use:
Content-Type: text/event-stream- optional
MCP-Session-Idresponse header
Environment notes (Gate B+):
- Nginx/Proxy: disable buffering for MCP streaming routes.
- PHP-FPM/FastCGI: configure output buffering/flush for incremental event delivery.
- Proxy/FPM timeouts must be tuned for long-running streams.
If sTask is installed, register worker:
identifier:emcp_dispatchscope:eMCPclass:EvolutionCMS\eMCP\sTask\McpDispatchWorkeractive:true
Async payload should include:
server_handlejsonrpc_methodjsonrpc_paramsrequest_idsession_idtrace_ididempotency_keyactor_user_idinitiated_by_user_idcontext(mgr|api|cli)attemptsmax_attempts
If sTask is unavailable:
queue.failover=sync-> execute synchronouslyqueue.failover=fail-> return controlled error
Context identity fields:
actor_user_idinitiated_by_user_idcontexttrace_id
Resolution strategy:
- manager mode -> current manager user
- sApi mode -> JWT user (
sapi.jwt.user_id) when available - service mode -> dedicated service account (
actor.mode=service)
logging.channels.emcp daily channel:
- file:
core/storage/logs/emcp-YYYY-MM-DD.log - rotation:
LOG_DAILY_DAYS
Audit events should include:
timestamprequest_idtrace_idserver_handlemethodstatusactor_user_idcontextduration_mstask_id(if async)
Never log raw values of:
authorizationtokenjwtsecretcookiepasswordapi_key
Required language files:
lang/en/global.phplang/uk/global.phplang/ru/global.php
Minimum keys:
titlepermissions_grouppermission_accesspermission_managepermission_dispatcherrors.forbiddenerrors.scope_deniederrors.server_not_founderrors.invalid_payload
Upstream MCP commands expected through 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
php artisan mcp:inspector content-localBefore mcp:start content-local, enable content-local in core/custom/config/mcp.php and disable conflicting server entries if they expose identical tool names.
Available 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
EMCP_INTEGRATION_ENABLED=1 EMCP_BASE_URL="https://example.org" EMCP_API_PATH="/api/v1/mcp/{server}" EMCP_API_TOKEN="<jwt>" composer run test:integration:runtime-
401/403 on API calls: check JWT scopes and
auth.require_scopes. -
403 in manager: verify role has
emcppermission. -
Server not found: verify
mcp.phpentry (enabled,handle, class path). -
Streaming not working: check server/client supports SSE and proxy buffering settings.
-
Async dispatch not running: verify
sTaskinstalled, worker registered, and worker process active.
- Product requirements:
PRD.md - Technical contract:
SPEC.md - Canonical tool contract:
TOOLSET.md - Quick start:
README.md,README.uk.md - Deep docs:
DOCS.md,DOCS.uk.md - Execution plan:
TASKS.md - Security review:
SECURITY_CHECKLIST.md - Threat model:
THREAT_MODEL.md - Formal audit:
PLATFORM_AUDIT.md - Architecture freeze gate:
ARCHITECTURE_FREEZE_CHECKLIST.md