Цей документ фіксує обовʼязкові технічні контракти для реалізації eMCP.
Status markers:
SPEC Version:1.0-contractRuntime Status:Gate C baseline validated in demo runtime; RC-1 hardening pending
Normative hierarchy:
SPEC.mddefines platform-wide MUST/SHOULD rules.TOOLSET.mddefines canonical public tool contract (evo.content.*,evo.model.*).DOCS*.mdare implementation and usage guides; if conflict occurs,SPEC.md+TOOLSET.mdwin.
Current validation snapshot (2026-03-03):
make demo-allPASS (install + smoke + runtime integration).php artisan emcp:testPASS forinitializeandtools/list.- Runtime HTTP integration PASS for
/api/v1/mcp/{server}. - Verified content-read tool flow in runtime (
evo.content.search,evo.content.root_tree,evo.content.get). - One-click verification writes
demo/logs.mdwith request/response evidence, negative transport/security probes (401/403/413/415/409/429) and model-safety sanity (evo.model.get(User)without sensitive fields). demo/logs.mdalso captures localsTasklifecycle proof (queued -> completed) in demo runtime.- CI/local test baseline now includes:
ScopePolicy/ServerRegistryunit checks, async failover behavior checks, SiteContent tree/TV contract checks, security guardrail checks, docs/config/commands consistency checks, upstream adapter smoke checks. - Optional advanced tree tools (
neighbors,prev/next siblings,children/siblings range) are implemented and covered by contract/runtime integration tests. - Streaming policy now enforces proxy/FPM-safe SSE headers (
Content-Type: text/event-stream,Cache-Control: no-cache, no-transform,X-Accel-Buffering: no) with dedicated tests. - CI includes migration matrix automation for
sqlite/mysql/pgsql. - CI/check flow now produces reproducible benchmark and leaderboard artifacts.
- Live runtime integration harness now supports secret-controlled hardening probes: negative transport checks, model-safety sanity, and optional
sTasklifecycle verification with external-worker mode.
Open RC-1 validation scope:
- live CI runtime integration jobs are wired for
release/*pushes (demo-runtime-proof,runtime-integration), but branch-protection required-check enforcement must be configured in repository settings; - live async
sTaske2e checks (queue lifecycle/progress/failover) are not yet enforced in CI; - live stream/rate-limit infra checks on external target env remain pending as RC evidence;
- first RC tag is pending manual release approval flow.
/Users/dmi3yy/PhpstormProjects/Extras/LaravelMcp— upstreamlaravel/mcp./Users/dmi3yy/PhpstormProjects/Extras/LaravelAi— upstream SDK packaging patterns consumed via Evo thin wrappers./Users/dmi3yy/PhpstormProjects/Extras/eAi— Evo thin-wrapper pattern для Laravel package./Users/dmi3yy/PhpstormProjects/Extras/sApi— API kernel, JWT middleware, route provider discovery./Users/dmi3yy/PhpstormProjects/Extras/sTask— async worker/task model./Users/dmi3yy/PhpstormProjects/Extras/dAi— manager-side AI/orchestration consumer./Users/dmi3yy/PhpstormProjects/Extras/ColimaOpenclaw— contract discipline patterns (deterministic gates, doc sync, operability checks)./Users/dmi3yy/PhpstormProjects/Extras/ePasskeys— permissions migrations + publish flatten pattern./Users/dmi3yy/PhpstormProjects/Extras/eFilemanager— publish + shim/autoload.files pattern./Users/dmi3yy/PhpstormProjects/Extras/sLang— lang/multilingual package pattern./Users/dmi3yy/PhpstormProjects/Extras/evolution/core/src/Models— Evo domain models (SiteContent, TVs, users, ACL entities)./Users/dmi3yy/PhpstormProjects/Extras/eMCP/TOOLSET.md— canonical public tool contract v1.
eMCP = thin integration layer:
- Upstream protocol/runtime:
laravel/mcp. - Evo adapter layer: provider, routes, config paths, permissions, logging.
- Access layer:
- manager mode (ACL Evo).
- API mode через
sApi(JWT scopes). - Async layer:
sTaskworker (emcp_dispatch) для long-running викликів.
- Не форкати upstream
laravel/mcpкод. - Не тягнути
laravel/framework/illuminate/foundation. - Evo-specific сумісність реалізується тільки адаптерами
eMCP. - Безпека за замовчуванням: deny-by-default.
У чистому Evo (без Passport) пакет MUST:
- встановлюватися і завантажуватись без fatals/warnings на boot;
- реєструвати щонайменше один
webMCP server зconfig('mcp.servers'); - обробляти
POSTJSON-RPCinitialize; - обробляти
POSTJSON-RPCtools/list; - повертати
405наGETдля MCP transport route; - коректно передавати
MCP-Session-Id; - enforce manager ACL: запит без permission
emcpMUST отримувати403.
Реалізація виконується по гейтах:
- Gate A:
webtransport + manager route + manager ACL (emcp) +initialize/tools:list+405. - Gate B: API access layer (scope engine, basic rate limit,
sApiintegration). - Gate C: async layer (
sTask, payload contract, failover, idempotency). - Gate D: hardening (audit/redaction/denylist) + DX commands.
Архітектурний профіль реалізації фіксується як data-first pipeline:
DATA DIVISION-> contracts/data schemas (TOOLSET.md+ runtime validators).PROCEDURE DIVISION-> tool handlers (1 tool = 1 explicit procedure).ENVIRONMENT DIVISION-> runtime guards (ACL/scopes/rate/limits/redaction/idempotency).FILE SECTION-> projection/mapping layer для стабільних response layouts.
Execution pipeline for every tool call (MUST):
validate -> authorize -> query -> map -> paginate -> respond -> audit.- Hidden side effects in transport/controller layer are forbidden.
- Canonical tools MUST implement explicit stage separation (
validate,authorize,query,map,paginate,respond,audit) so each stage is independently testable.
eMCP is a neutral platform layer and MUST remain concept-agnostic.
Interop boundaries:
LaravelMcpcompatibility: preserve upstream transport/protocol semantics.sApicompatibility: external MCP routes viaRouteProviderInterface+ JWT attributes/scopes.sTaskcompatibility: async execution through worker/task model without custom queue framework coupling.eAi/dAicompatibility: consumers use eMCP tool contracts; core eMCP MUST NOT depend on one orchestration concept.
Design rule:
- eMCP defines execution contracts and policy boundaries.
- packages above eMCP implement orchestration strategies (planner/guild/workflow/etc.) independently.
- Runtime MUST expose no orchestration-specific persistence entities (
Intent,PolicyCheck,EvidenceTrace, etc.) unless extension profile is explicitly enabled.
eMCP/
├─ composer.json
├─ PRD.md
├─ SPEC.md
├─ config/
│ ├─ mcp.php
│ └─ eMCPSettings.php
├─ database/
│ └─ migrations/
│ ├─ *_add_emcp_permissions.php
│ └─ *_add_emcp_role_permissions.php (optional if split)
├─ lang/
│ ├─ en/global.php
│ ├─ uk/global.php
│ └─ ru/global.php
├─ plugins/
│ └─ eMCPPlugin.php
├─ src/
│ ├─ eMCPServiceProvider.php
│ ├─ LaravelMcp/
│ │ └─ McpServiceProvider.php
│ ├─ Http/
│ │ ├─ mgrRoutes.php
│ │ └─ Controllers/
│ ├─ Api/
│ │ └─ Routes/McpRouteProvider.php
│ ├─ Middleware/
│ │ ├─ EnsureMcpPermission.php
│ │ ├─ EnsureMcpScopes.php
│ │ ├─ ResolveMcpActor.php
│ │ └─ RateLimitMcpRequests.php
│ ├─ Services/
│ │ ├─ ServerRegistry.php
│ │ ├─ McpExecutionService.php
│ │ ├─ ScopePolicy.php
│ │ └─ AuditLogger.php
│ ├─ Support/
│ │ ├─ AutoloadShims.php
│ │ ├─ ConfigPath.php
│ │ └─ Redactor.php
│ └─ sTask/
│ └─ McpDispatchWorker.php
├─ stubs/
│ ├─ mcp-server.stub
│ ├─ mcp-tool.stub
│ ├─ mcp-resource.stub
│ └─ mcp-prompt.stub
└─ README.md
name:evolution-cms/emcptype:evolutioncms-pluginrequire:php: ^8.3evolution-cms/evolution: ^3.5.2laravel/mcp: ^0.5.9
seiger/sapi(^1.0, для API mode).seiger/stask(^1.0, для async mode).
- PSR-4 namespace
EvolutionCMS\eMCP\. autoload.filesMUST includesrc/Support/AutoloadShims.php.
- Provider:
EvolutionCMS\eMCP\eMCPServiceProvider. - For sApi discovery (if package installed in environment),
extra.sapi.route_providersSHOULD exposeMcpRouteProviderdescriptor.
Example descriptor:
{
"extra": {
"sapi": {
"route_providers": [
{
"class": "EvolutionCMS\\eMCP\\Api\\Routes\\McpRouteProvider",
"endpoint": "mcp",
"version": "v1"
}
]
}
}
}Required keys:
enable: bool, defaulttrue.mode.internal: bool, defaulttrue.mode.api: bool, defaulttrue.route.manager_prefix: string, defaultemcp.route.api_prefix: string, defaultmcp.auth.mode:sapi_jwt|none, defaultsapi_jwt.auth.require_scopes: bool, defaulttrue.auth.scope_map: array (див. 7.2).acl.permission: string, defaultemcp.queue.driver:stask|sync, defaultstask.queue.failover:sync|fail, defaultsync.rate_limit.enabled: bool, defaulttrue.rate_limit.per_minute: int, default60.limits.max_payload_kb: int, default256.limits.max_result_items: int, default100.limits.max_result_bytes: int, default1048576.stream.enabled: bool, defaultfalse(Gate B+).stream.max_stream_seconds: int, default120.stream.heartbeat_seconds: int, default15.stream.abort_on_disconnect: bool, defaulttrue.logging.channel: string, defaultemcp.logging.audit_enabled: bool, defaulttrue.logging.redact_keys: array.security.allow_servers: array, default['*'].security.deny_tools: array, default[].security.enable_write_tools: bool, defaultfalse.actor.mode:initiator|service, defaultinitiator.actor.service_username: string, defaultMCP.actor.service_role: string, defaultMCP.actor.block_login: bool, defaulttrue.trace.header: string, defaultX-Trace-Id.trace.generate_if_missing: bool, defaulttrue.idempotency.header: string, defaultIdempotency-Key.idempotency.ttl_seconds: int, default86400.idempotency.storage:stask_meta|cache, defaultstask_meta.domain.content.max_depth: int, default6.domain.content.max_limit: int, default100.domain.content.max_offset: int, default5000.domain.models.max_offset: int, default5000.domain.models.allow: array, default allowlist (див. 10.4).
Базовий upstream config + eMCP extensions:
redirect_domains(upstream).servers(eMCP extension):
'servers' => [
[
'handle' => 'content',
'transport' => 'web',
'route' => '/mcp/content',
'class' => EvolutionCMS\eMCP\Servers\ContentServer::class,
'enabled' => true,
'auth' => 'sapi_jwt',
'scopes' => ['mcp:read', 'mcp:call'],
'scope_map' => [
'mcp:read' => ['initialize', 'tools/list', 'resources/read'],
'mcp:call' => ['tools/call'],
],
'limits' => [
'max_payload_kb' => 128,
'max_result_items' => 50,
],
'rate_limit' => [
'per_minute' => 30,
],
'security' => [
'deny_tools' => ['evo.write.*'],
],
],
[
'handle' => 'content-local',
'transport' => 'local',
'class' => EvolutionCMS\eMCP\Servers\ContentServer::class,
'enabled' => false,
],
],Note:
content-localis disabled by default to keep global tool-name uniqueness.- If you enable a local server with the same toolset, disable conflicting server entries first.
Per-server override policy:
scope_mapMAY override global scope mapping.limits.*MAY override global payload/result limits.rate_limit.per_minuteMAY override global rate limit.security.deny_toolsMAY extend per-server deny policy.
loadPluginsFrom().- Merge
eMCPSettingsуcms.settings.eMCP. - Register logging channel
emcp(daily) if missing. - Register middleware aliases (
emcp.permission,emcp.scope,emcp.actor,emcp.rate). - Register adapter provider (
EvolutionCMS\eMCP\LaravelMcp\McpServiceProvider).
- Merge
mcp.phpintomcpnamespace. - Load migrations.
- Load translations.
- Load manager routes.
- Publish resources (config/stubs/lang/views as needed).
- Run
flattenPublishDirectories()after boot (as ineAi/ePasskeys/eFilemanager). - Auto-register sTask worker when
sTaskis present.
Purpose: Evo-safe replacement for upstream Laravel\Mcp\Server\McpServiceProvider.
Mandatory behavior:
- Register
Registrarsingleton. - Register container callbacks for
Laravel\Mcp\Request(mcp.requestbridging). - Register MCP commands (
mcp:start,mcp:inspector,make:mcp-*). - Register publish groups without Laravel app skeleton assumptions.
- Register routes from eMCP registry instead of requiring
routes/ai.php. - Guard calls that may not exist in Evo runtime (
routesAreCached, path helpers) with safe fallbacks.
src/Support/AutoloadShims.php MUST alias upstream provider to adapter before provider registration:
Laravel\Mcp\Server\McpServiceProvider->EvolutionCMS\eMCP\LaravelMcp\McpServiceProvider.
Alias safety contract:
- Aliasing is allowed only for provider interception, not for runtime MCP primitives.
- Intercepted contract is strictly:
- provider boot lifecycle
- route source (
configinstead ofroutes/ai.php) - publish paths and Evo-safe helper usage
- Before enabling alias in production, a spike MUST verify whether container/provider replacement without alias is viable.
- If non-alias replacement is not viable, keep alias and document the reason in
DOCS.md+ changelog. - CI MUST include a regression test that fails if upstream provider FQCN changes or is no longer aliasable.
- Boot MUST fail fast with
RuntimeExceptionif expected upstream provider FQCN does not exist orclass_aliasinterception cannot be applied. - Exception message MUST include actionable hint: verify installed
laravel/mcpversion and update eMCP adapter/alias map.
Under middleware mgr + emcp.permission:
POST /{manager_prefix}/{server}-> JSON-RPC MCP endpoint.POST /{manager_prefix}/{server}/dispatch-> async submit to sTask (Gate C+).GET /{manager_prefix}/servers-> registry diagnostics (optional but recommended).
Rules:
GETto MCP transport route returns405.POSTaccepts JSON only.- Request must support
MCP-Session-Idheader passthrough.
McpRouteProvider MUST implement Seiger\sApi\Contracts\RouteProviderInterface.
Registered routes (inside sApi group):
POST /mcp/{server}-> JSON-RPC endpoint.POST /mcp/{server}/dispatch-> async submit (Gate C+).
Middleware chain:
emcp.jwt(adapter oversApiJWT middleware).emcp.scope.emcp.actor.emcp.rate.
Route-level policy:
McpRouteProviderMUST remove upstreamsapi.jwtmiddleware from MCP routes and useemcp.jwtas the single JWT/auth normalization middleware.
ServerRegistry resolves active servers from config('mcp.servers').
Validation rules:
- unique
handle. classexists and extendsLaravel\Mcp\Server.transportinweb|local.enabled=trueonly gets registered.- duplicate
handleis forbidden: fail-fast in debug, warning + reject conflicting registration in production.
Registration mapping:
transport=web->Mcp::web(route, class).transport=local->Mcp::local(handle, class).
Runtime policy resolution order:
- rate limit:
mcp.servers[*].rate_limit.per_minute-> globalrate_limit.per_minute. - limits:
mcp.servers[*].limits.*-> globallimits.*. - deny tools: per-server
security.deny_tools+ globalsecurity.deny_tools(union).
- Tool names are globally unique within one MCP runtime.
- Duplicate tool name is a configuration error.
- In debug mode, duplicate tool registration MUST fail-fast.
- In production, duplicate tool registration MUST emit warning and reject second registration.
- Re-registration of existing tool name or server
handleis forbidden. - ServerRegistry MUST perform global tool-name uniqueness validation before boot completes.
- Namespace
evo.*is reserved exclusively for Evolution CMS core toolset. - Third-party ecosystem packages MUST NOT register tools in
evo.*. - Third-party ecosystem packages MUST use
vendor.domain.*naming (for exampleshop.catalog.*,crm.contact.*). - Name collision with existing tool name is a configuration error.
- In debug mode, name collision MUST fail-fast.
- In production, name collision MUST emit warning and reject conflicting registration.
- Namespace enforcement on registration:
- if tool name starts with
evo.and source package is not core, registration MUST be rejected. - debug mode: throw exception.
- production mode: warning + reject registration.
Migration MUST create:
- Permission group:
eMCP(or existingsPackagesby project decision; default in this SPEC:eMCP). - Permissions:
emcp(Access eMCP Interface)emcp_manage(Manage MCP servers)emcp_dispatch(Run async MCP tasks)
Delivery gates for permissions:
- Gate A (MVP) MUST include
emcp. - Gate B+ SHOULD include
emcp_manage. - Gate C+ SHOULD include
emcp_dispatch.
Role assignment defaults:
- role_id
1gets allemcp*permissions.
Migration style MUST follow robust idempotent pattern from sTask/ePasskeys:
- safe firstOrCreate/upsert.
- PostgreSQL sequence fix helper.
- reversible down().
EnsureMcpScopes MUST map JSON-RPC method -> scope by policy table:
initialize,ping,tools/list,resources/list,resources/read,prompts/list,prompts/get,completion/complete-> requiremcp:read.tools/call-> requiremcp:call.- server admin/service endpoints -> require
mcp:admin. - wildcard
*bypasses specific checks.
Resolution order (MUST):
- per-server override from
mcp.servers[*].scope_map(if defined) - global
auth.scope_map - built-in default table (above)
This keeps secure defaults while preserving extensibility for ecosystem packages with custom methods.
sapi_jwtis the only external API auth mode.- If API auth is disabled by config, fallback mode is
nonefor restricted/internal scenarios.
- Supported upstream window:
laravel/mcp ^0.5.x. - If upstream provider FQCN/signature changes, eMCP MUST ship adapter update before recommending upstream upgrade.
- Upgrade protocol remains mandatory: spike + smoke + alias regression.
ResolveMcpActor middleware responsibility:
- manager mode: actor = logged manager user.
- sApi mode: actor from
request->attributes['sapi.jwt.user_id']if available. - service mode: actor = service account user (auto-create optional).
Context fields propagated to logs/task payload:
actor_user_idinitiated_by_user_idcontext(mgr|api|cli)trace_id
- Trace ID source priority: request header
trace.header-> generated value. - If
trace.generate_if_missing=true, missing trace id MUST be generated as UUID v4. - Generated or forwarded trace id MUST be returned in response header
trace.header. - Trace ID MUST be propagated to audit events and async payloads unchanged.
Rate limit identity key resolution order:
- manager mode:
actor_user_id. - API mode: JWT subject/user id (for example
sapi.jwt.user_id). - fallback when identity is missing: client IP.
This policy MUST be consistent across middleware and async dispatch entrypoints.
Implementation MUST use one shared resolver function (for example resolveRateLimitIdentity()); duplicated logic is forbidden.
Plugin MUST auto-register worker in s_workers if sTask exists:
identifier:emcp_dispatchscope:eMCPclass:EvolutionCMS\eMCP\sTask\McpDispatchWorkeractive: true
Async payload (meta) MUST include:
server_handlejsonrpc_methodjsonrpc_paramsrequest_idsession_idtrace_ididempotency_keyactor_user_idinitiated_by_user_idcontextattemptsmax_attempts
Idempotency policy (MUST):
- Source priority: request header
idempotency.header-> payload fieldidempotency_key-> generated hash of (server_handle,method,params,actor_user_id) for sync fallback only. - Storage backend from
idempotency.storage. - TTL from
idempotency.ttl_seconds. - Persisted idempotency record MUST include payload hash.
- If the same
idempotency_keyis reused with identical payload hash, system MUST return existing task/result reference. - If the same
idempotency_keyis reused with different payload hash, system MUST reject with HTTP409 Conflict. - Conflict error body MUST follow non-JSON-RPC transport error format from section
10.1. - Conflict path MUST never create a new async task.
McpDispatchWorker:
- validates payload and server allowlist.
- executes MCP call via
McpExecutionService. - writes progress (
TaskProgress) and final result. - marks task failed with sanitized error (no secrets).
If sTask unavailable:
- obey
queue.failover. sync-> execute immediately.fail-> return controlled error.
McpExecutionService::call(...) MUST:
- accept JSON-RPC message + server handle.
- resolve registered server.
- execute request and return raw JSON-RPC response structure.
- preserve/return
MCP-Session-Idwhen applicable. - support streaming mode when method returns iterable stream.
- for
initialize, include mandatory platform metadata (see10.0).
Safety:
- payload size limit check.
- tool denylist enforcement pre-call.
initialize response MUST include:
serverInfo.platform = "eMCP".serverInfo.platformVersion = <current-package-version>.capabilities.evo.toolsetVersion = "1.0"(or current toolset version).- This metadata is part of the public contract and required for version-aware ecosystem integrations.
- Missing metadata is a breaking contract change.
Error handling is split into two layers with a single fixed rule:
- Transport/auth/middleware errors return HTTP status codes and non-JSON-RPC error body.
- JSON-RPC dispatch errors return HTTP
200with JSON-RPCerrorobject.
Non-JSON-RPC error body (MUST):
{
"error": {
"code": "forbidden",
"message": "Forbidden",
"trace_id": "..."
}
}Mapping table:
- invalid JSON body -> JSON-RPC
-32700(HTTP 200). - invalid JSON-RPC envelope -> JSON-RPC
-32600(HTTP 200). - method/server/tool not found -> JSON-RPC
-32601(HTTP 200). - invalid params/validation -> JSON-RPC
-32602(HTTP 200). - internal execution failure -> JSON-RPC
-32603(HTTP 200, sanitized message). - unauthenticated API request -> HTTP
401(middleware layer, non-JSON-RPC). - forbidden by ACL/scope -> HTTP
403(middleware layer, non-JSON-RPC). - idempotency key conflict (same key, different payload) -> HTTP
409(non-JSON-RPC). - method not allowed (GET on MCP route) -> HTTP
405. - payload too large -> HTTP
413. - unsupported media type -> HTTP
415.
The project MUST NOT mix styles for the same failure class.
- A single error formatter MUST be used for
401,403,409,413,415. - Every non-JSON-RPC error body MUST include
trace_id. - Raw Laravel exception responses are forbidden.
eMCP MUST ship (or provide canonical stubs for) tool names:
evo.content.searchevo.content.getevo.content.root_treeevo.content.descendantsevo.content.ancestorsevo.content.childrenevo.content.siblings
Behavior mapping to Evo model API:
evo.content.search->SiteContentquery + optionalscopeActive|scopePublished|scopeWithoutProtected|scopeWithTVs|scopeTvFilter|scopeTvOrderBy|scopeTagsData|scopeOrderByDate.evo.content.root_tree->scopeGetRootTree(depth)+withTVs(..., ':', true)+toTree.evo.content.descendants->scopeDescendantsOf(id)(optional depth cap viawhere('depth', '<', maxDepth + 1)).evo.content.ancestors->scopeAncestorsOf(id)+ defaultorderBy('depth', 'desc').evo.content.children-> direct children (parent = id) orgetChildren().evo.content.siblings->scopeSiblingsOf(id)and related sibling selectors when requested.
Advanced optional tools (implemented, non-canonical):
evo.content.neighbors->scopeNeighborsOf(id).evo.content.prev_siblings->scopePrevSiblingsOf(id).evo.content.next_siblings->scopeNextSiblingsOf(id).evo.content.children_range->scopeChildrenRangeOf(id, from, to).evo.content.siblings_range->scopeSiblingsRangeOf(id, from, to).
Input for evo.content.* MUST be structured JSON (not raw SQL fragments):
idoridsfor point lookups.parent,depth,published,deleted,template,hidemenu.with_tvs: array of TV names, supports default marker (name:d).tv_filters: array of objects{tv, op, value, cast?, use_default?}.tv_order: array of objects{tv, dir, cast?, use_default?}.tags_data: object{tv_id, tags[]}(maps toscopeTagsData).order_by_date:asc|desc(maps toscopeOrderByDate).limit,offset.
Validation rules (MUST):
- Reject raw
tvFilterstrings from client payload. - Allowed operators:
=,!=,>,>=,<,<=,in,not_in,like,like-l,like-r,null,!null. - Allowed casts:
UNSIGNED,SIGNED,DECIMAL(p,s). - Enforce
depth <= domain.content.max_depth,limit <= domain.content.max_limit,offset <= domain.content.max_offset. - Enforce allowlist for sortable base columns (
id,pagetitle,menuindex,createdon,pub_date). - Large responses MUST be paginated (
limit/offset) and bounded bylimits.max_result_items. - Serialized response size MUST be bounded by
limits.max_result_bytes; oversized responses return413.
Read-only canonical tools:
evo.model.listevo.model.get
domain.models.allow default allowlist:
SiteTemplateSiteTmplvarSiteTmplvarContentvalueSiteSnippetSitePluginSiteModuleCategoryUserUserAttributeUserRolePermissionsPermissionsGroupsRolePermissions
Allowlist governance (MUST):
- Any extension of default
domain.models.allowrequires security checklist review and explicit release-note approval. - Any change that increases exposed fields for an allowlisted model requires allowlist test updates and BC/SemVer impact review before merge.
Model field exposure policy (MUST):
- Each allowlisted model MUST have explicit allowlist of public fields.
evo.model.listandevo.model.getMUST return only allowlisted fields.- Sensitive-field blacklist remains additional defense-in-depth and MUST still be enforced.
- Direct
model->toArray()exposure without sanitizer/allowlist projection is forbidden.
Default model field allowlists (MUST):
SiteTemplate:id,templatename,description,editor_type,icon,category,locked.SiteTmplvar:id,name,caption,description,type,default_text,display,elements,rank,category,locked.SiteTmplvarContentvalue:id,contentid,tmplvarid,value.SiteSnippet:id,name,description,category,locked,disabled,createdon,editedon.SitePlugin:id,name,description,category,locked,disabled,createdon,editedon.SiteModule:id,name,description,category,disabled,createdon,editedon.Category:id,category.User:id,username,isfrontend,createdon,editedon,blocked,blockeduntil,blockedafter.UserAttribute:id,internalKey,fullname,email,phone,mobilephone,blocked,blockeduntil,blockedafter,failedlogincount,logincount,lastlogin.UserRole:id,name,description,frames,home,rank,locked.Permissions:id,name,description.PermissionsGroups:id,name.RolePermissions:id,role_id,permission.
evo.model.list validation policy (MUST):
filtersaccepts only structured JSON format:{ "where": [ { "field": "...", "op": "...", "value": ... } ] }.filters.where[].fieldMUST be allowlisted for the selected model.- Allowed operators:
=,!=,>,>=,<,<=,in,not_in,like,like-l,like-r,null,!null. - Raw SQL fragments or raw query DSL MUST be rejected with JSON-RPC
-32602. offsetfor model list MUST be bounded bydomain.models.max_offset.
Sensitive-field policy (MUST):
- Never return raw fields:
password,cachepwd,verified_key,refresh_token,access_token,sessionid. - Redaction applies in API payloads and audit logs.
- Model writes are out of default profile.
- List endpoints MUST require pagination and obey
limits.max_result_items.
evo.content.*andevo.model.*read tools requireemcp(manager mode).evo.content.*andevo.model.*read tools requiremcp:read(API mode).- Mutating tools (if introduced later) MUST live under explicit write namespace (e.g.
evo.write.*). - Mutating tools require
security.enable_write_tools=true. - Mutating tools require
emcp_manage(manager mode). - Mutating tools require
mcp:admin(API mode).
Default behavior remains deny-by-default.
- Streaming responses are out of MVP Gate A scope.
- Streaming is disabled by default.
- Streaming MUST be explicitly enabled via
stream.enabled=true. - Per-server streaming restrictions MAY apply and MUST be honored.
- Without explicit streaming enablement, streaming behavior is forbidden.
- When
stream.enabled=false, any streaming response attempt MUST be rejected before dispatch. - Runtime MUST enforce
stream.max_stream_secondsandstream.heartbeat_secondsfor active streams. - Streaming enablement requires deployment notes for runtime environment:
- Nginx: disable response buffering for MCP routes (
proxy_buffering off/X-Accel-Buffering: no). - PHP-FPM/FastCGI: ensure flush path is enabled and output buffering is controlled.
- Reverse proxy timeouts MUST be configured to tolerate long-running streams.
- Backpressure and liveness policy (MUST):
- hard timeout via
stream.max_stream_seconds. - heartbeat event every
stream.heartbeat_seconds. - abort stream immediately when
stream.abort_on_disconnect=trueand client disconnect is detected.
Для розширених сценаріїв execution beyond plain tool-calls, eMCP SHOULD підтримувати нейтральний envelope:
Intent(what should be executed).PolicyCheck(which rules allowed/blocked action).Task(s)(materialized workflow steps).EvidenceTrace(context/facts used).ApprovalGate(approve/reject/escalate where required).
Rules:
- Any orchestrator/planner MUST act only within policy-valid action sets.
- Materialized tasks SHOULD keep traceable linkage to intent/policy/evidence IDs.
- Execution trace MUST stay auditable end-to-end.
Tags:
emcp-config:eMCPSettings.php->core/custom/config/cms/settings/eMCP.php.emcp-mcp-config:mcp.php->core/custom/config/mcp.php.emcp-stubs: MCP stubs ->core/stubs/.emcp-lang(optional): lang overrides.
All publish dirs MUST support flattened output (recursive files mapping).
- Package-to-package server registration:
- ecosystem package adds server definition into
core/custom/config/mcp.phpusing uniquehandle. - package may provide
scope_map,limits,rate_limit,security.deny_toolsper server. - Tool/resource/prompt onboarding:
- use
make:mcp-*generators and publish stubs as canonical path. - package-specific tools MUST follow
vendor.domain.*naming. - Policy extension:
- scopes and deny rules can be extended per server without changing global defaults.
- Third-party package MAY add server via config registration.
- Third-party package MAY add tools/resources/prompts via generators/stubs under its own namespace.
- Third-party package MUST NOT modify global scope namespace
mcp:*. - Per-server overrides are limited to
scope_map,limits,rate_limit,security.deny_tools. - Third-party package MUST NOT globally override behavior of
evo.content.*orevo.model.*.
From upstream (must be available):
mcp:startmcp:inspectormake:mcp-servermake:mcp-toolmake:mcp-resourcemake:mcp-prompt
eMCP custom commands (recommended/MUST for operability):
emcp:test(smoke test: initialize + one list call).emcp:sync-workers(repair worker registration).emcp:list-servers(runtime diagnostics of registry).
Documentation SHOULD include one canonical "internal + external in 5 minutes" path:
- generate one server + one tool via
make:mcp-*, - register in
core/custom/config/mcp.php, - verify manager endpoint,
- verify
sApiendpoint with JWT scopes, - optional async dispatch verification with
sTask.
logging.channels.emcp daily file:
- path:
core/storage/logs/emcp.log - rotation days from env (
LOG_DAILY_DAYS).
Required fields:
timestamprequest_idtrace_idserver_handlemethodstatusactor_user_idcontextduration_mstask_id(if async)
Recommended orchestration fields (Post-MVP SHOULD):
intent_idevidence_idpolicy_result(allow|deny)approval_status(approved|rejected|escalated|n/a)
Redactor MUST mask keys:
authorization,token,jwt,secret,cookie,password,api_key.
Mandatory translation files:
lang/en/global.phplang/uk/global.phplang/ru/global.php
Required keys:
titlepermissions_grouppermission_accesspermission_managepermission_dispatcherrors.forbiddenerrors.scope_deniederrors.server_not_founderrors.invalid_payload
- Missing
sApi: API mode disabled, manager mode still works. - Missing
sTask: async disabled or sync failover per config. - Missing registered server: return JSON-RPC error
-32601/domain error with safe message. - Unsupported protocol version: upstream
Initializebehavior preserved. - Invalid scope: 403 with structured error.
- Alias interception failure: fail fast on boot with
RuntimeExceptionand actionable message. - Idempotency key conflict (same key, different payload): HTTP 409.
- ServerRegistry validation (duplicates, invalid class, disabled server).
- ScopePolicy mapping by JSON-RPC method.
- Redactor masks secret fields.
SiteContenttool input validator (operators/casts/depth/limit caps).- Model catalog field sanitizer (sensitive fields excluded).
- Error mapper: JSON-RPC code + HTTP status consistency.
- Alias shim guard: explicit failure when upstream provider FQCN changes.
- Provider boot in Evo without Laravel skeleton files.
- Manager MCP route handshake (
initialize) and session header. - GET returns 405.
tools/list,resources/read,tools/callcontracts.- Streaming response headers/content type.
evo.content.search/get/root_tree/descendants/ancestorson realSiteContent.- TV filters/order and default TV fallback (
:d) on controlled fixtures. - Trace id propagation from request -> response -> audit.
- Golden fixtures for canonical tools (
initialize,tools/list,evo.content.search,evo.content.get) MUST pass in CI. - Golden fixtures MUST be versioned by
toolsetVersion; fixture schema change requires version bump and changelog entry.
- ACL deny without
emcp. - Scope deny without
mcp:callfortools/call. - Log output contains no raw tokens.
- Reject raw
tvFilterDSL payload from clients. evo.model.get Userdoes not expose hidden/token credentials.
emcp_dispatchworker auto-registration.- Task create -> execute -> result persistence.
- Failover behavior when
sTaskis absent. - Idempotency key deduplication within TTL.
- Policy contract tests: invalid action MUST be rejected before task materialization.
- Intent-to-task traceability tests: every created task has intent/evidence linkage.
- Deterministic replay tests for control scenarios (same input -> same policy verdict class).
- Benchmark suite smoke: baseline strategy vs at least one planner strategy on fixed episode set.
- Architecture freeze artifacts approved before Phase 1:
PLATFORM_AUDIT.mdTHREAT_MODEL.mdARCHITECTURE_FREEZE_CHECKLIST.md- All AC з PRD закриті.
- Smoke command
emcp:testgreen. - Migration up/down idempotent across MySQL/PostgreSQL/SQLite.
- README installation flow verified in clean Evo instance.
- Upstream update protocol observed (
laravel/mcpspike + smoke + alias regression).
- Passes Minimal Boot Contract (section 1.2).
- Includes only Gate A functionality.
- Enforces manager ACL (
emcp) for MCP manager route. - Does not require
Passportto be functional.
- eMCP follows SemVer for public MCP behavior.
evo.content.*andevo.model.*are stable public namespaces.- Rename/removal of public namespaces or canonical tool names is allowed only in
MAJOR. - Changing error semantics requires
MAJOR. - Removing mandatory
initializeplatform metadata requiresMAJOR. - Adding new optional params/capabilities is allowed in
MINOR. - Making existing optional params mandatory in
MINORis forbidden. - Breaking change rollout requires deprecation notice for at least one
MINOR.