Files
Claude Code 8ada367bd0 docs: mapeamento completo do EMCP Tools (11 docs, ~6070 linhas)
Especificação funcional do plugin emcp-tools v3.12.1 (msrbuilds/elementor-mcp,
GPL-2.0-or-later) via leitura directa do código-fonte em emanuelalmeida.pt.

- 00: arquitectura (bootstrap, ability registrar, dispatcher, MCP adapter)
- 01: Elementor classico (paginas, layout, widgets, templates, globals)
- 02: Elementor Atomic v4 + Gutenberg
- 03: WordPress core (conteudo, media, settings, temas)
- 04: Themer (CPT, condicoes, render, PHP templates)
- 05: Redirects + change ledger unificado (rollback)
- 06: Sandbox PHP snippets + custom widgets
- 07: Filesystem/DB/WP-CLI/Security/Performance (maior risco)
- 08: Integracoes terceiros (ACF, Meta Box, forms, SEO)
- 09: Stock images + Cloud + OAuth
- 10: Sistema de modulos + inventario Pro-only (30 classes)
- INDEX: sintese, sequencia de construcao, tabela de risco

Produzido por 10 subagentes code-explorer em paralelo + revisao cruzada de
consistencia. Cada doc inclui blueprint de replica (copiar/simplificar/omitir).
2026-08-19 06:41:04 +01:00

20 KiB

00 — Arquitectura do EMCP Tools e blueprint da réplica

Fonte: leitura directa do código-fonte emcp-tools v3.12.1 (build Free), instalado em emanuelalmeida.pt (/home/ealmeida/emanuelalmeida.pt/wp-content/plugins/emcp-tools/), 19-08-2026. Autor terceiro: Mian Shahzad Raza (msrbuilds.com), repo público github.com/msrbuilds/elementor-mcp, licença GPL-2.0-or-later.

0. Achado mais importante: a peça MCP não é do fornecedor — é do WordPress core team

O plugin não implementa o protocolo MCP. Depende de duas bibliotecas Composer, ambas projectos oficiais da equipa de IA do WordPress.org (WordPress AI Team, make.wordpress.org/ai), GPL-2.0-or-later, públicas:

Pacote Namespace PHP Repo O que faz
wordpress/mcp-adapter WP\MCP\ github.com/wordpress/mcp-adapter Expõe abilities da Abilities API como servidor MCP (McpAdapter, McpServer, HttpTransport, SessionManager, RequestRouter, HttpSessionValidator, McpErrorFactory) — cria o endpoint REST /wp-json/mcp/{route}
wordpress/php-mcp-schema WP\McpSchema\ github.com/WordPress/php-mcp-schema DTOs PHP para o schema oficial do protocolo MCP

A Abilities API em si é núcleo do WordPress 6.9+/7.0 (wp_register_ability(), wp_get_ability(), wp_get_abilities(), hooks wp_abilities_api_categories_init / wp_abilities_api_init) — não é código do plugin nem do adapter.

Consequência directa para a réplica: não há nada de protocolo para reimplementar. composer require wordpress/mcp-adapter (ou vendorizar uma cópia, como o EMCP faz — ver §1) dá-nos a mesma máquina JSON-RPC/sessão/transporte. O trabalho real do "clone" é: (a) registar as nossas próprias abilities via wp_register_ability(), com o nosso próprio prefixo (ex. descomplicar-tools/*), e (b) $mcp_adapter->create_server(...) com o nosso server_id. Tudo o resto — catálogo de ~165 tools, módulos, sandbox, change-ledger, content-mirror — é trabalho de aplicação, não de protocolo.

Nota de licenciamento: por ser GPL-2.0-or-later, nada impede legalmente copiar/adaptar o código-fonte do EMCP Tools directamente para um plugin próprio (fork), mantendo a licença GPL a jusante. "Réplica 100% nossa" pode ser lida como (a) fork com rebranding + remoção do Freemius + namespace próprio (muito menos esforço, reutiliza ~78k linhas já testadas), ou (b) reimplementação limpa a partir desta especificação (mais esforço, zero dependência de código de terceiros, liberdade total de redesenho). Esta série de documentos serve ambos os caminhos — é a especificação funcional; a decisão de fork-vs-reescrita é independente dela.

1. Estrutura de directórios do plugin

emcp-tools/
├── emcp-tools.php                    # bootstrap: header, guarda Free⇄Pro, Freemius, handoff
├── includes/
│   ├── class-bootstrap.php           # boot(): dependências, carrega classes, arranca Plugin
│   ├── class-plugin.php              # EMCP_Tools_Plugin (singleton orquestrador) — ver §3
│   ├── class-mcp-adapter-bootstrap.php  # preload do WP\MCP\ vendorizado — ver §2
│   ├── class-schema-compat.php       # emcp_tools_register_ability() + normalização de schema
│   ├── abilities/
│   │   ├── class-ability-registrar.php   # regista TODOS os grupos de abilities — índice mestre
│   │   ├── class-dispatcher-abilities.php # compact tool mode (list-tools/get-tool-schema/call-tool)
│   │   ├── class-{grupo}-abilities.php    # ~40 classes de abilities (uma por grupo funcional)
│   │   ├── forms/class-{plugin}-integration.php  # CF7 (free) + dispatcher genérico
│   │   └── seo/class-{plugin}-integration.php    # SlimSEO (free) + dispatcher genérico
│   ├── modules/          # os 9 módulos opcionais + registry — ver doc 10
│   ├── themer/            # Themer CPT/render/condições/blocos/widgets/PHP — ver doc 04
│   ├── sandbox/           # armazenamento de artefactos sandbox (bundle/paths/store) — ver doc 06
│   ├── security/          # 4 auditores + finding — ver doc 07
│   ├── performance/       # 3 auditores + finding — ver doc 07
│   ├── redirects/         # handler + store — ver doc 05
│   ├── cloud/              # ligação/sync EMCP Cloud — ver doc 09
│   ├── oauth/              # servidor OAuth para autenticação MCP remota — ver doc 09
│   ├── wpcli/              # runner/validator/jobs para run-wp-cli — ver doc 07
│   ├── widgets/            # catálogos de widgets Elementor (free/woo/pro) — ver doc 02
│   ├── blocks-catalog/     # catálogos Kadence Blocks / Spectra — ver doc 03
│   ├── schemas/            # gerador de JSON Schema a partir de controls Elementor
│   ├── validators/         # validador de settings/elementos
│   ├── admin/              # UI de admin: class-admin.php (6115 linhas!), views/*, mcpb-builder
│   └── vendors/fremius/    # SDK Freemius (licenciamento/billing) — ~50k linhas, IRRELEVANTE p/ réplica
├── vendor/wordpress/       # mcp-adapter + php-mcp-schema vendorizados (ver §0)
├── vendor/enshrined/svg-sanitize/  # sanitização SVG (módulo svg-support)
├── vendor/automattic/jetpack-autoloader/  # autoloader partilhado entre plugins que usam o adapter
└── prompts/, assets/, bin/, languages/

Total: ~128 465 linhas PHP fora de vendor//languages/ (medido 19-08-2026); ~50k dessas são o SDK Freemius bundled (irrelevante para a réplica — ver §6). Ficheiro maior de longe: includes/admin/class-admin.php, 6115 linhas (catálogo de tools para a UI, aplicação do deny-list incremental de 34 versões, todas as views de admin — ver doc dedicado se necessário).

2. Cadeia de arranque (ordem real de hooks)

emcp-tools.php carrega no arquivo (topo do request)
  ↓ guarda Free⇄Pro (§7), depois Freemius init
  ↓ require class-bootstrap.php
  add_action('plugins_loaded', [Bootstrap, 'boot'], 20)
    ↓
plugins_loaded:20 → EMCP_Tools_Bootstrap::boot()
  ↓ EMCP_Tools_Adapter_Bootstrap::ensure() — preload do WP\MCP\ vendorizado (ver §2.1)
  ↓ require de todas as classes includes/**/*.php
  ↓ EMCP_Tools_Plugin::instance() → init()
      add_action('wp_abilities_api_categories_init', register_category)
      add_action('wp_abilities_api_init', register_abilities)       # regista ~165 abilities
      add_action('mcp_adapter_init', register_mcp_server, 20)        # cria o servidor MCP
      add_filter('emcp_tools_ability_names', filter_disabled_tools)  # aplica o deny-list
      add_filter('rest_pre_dispatch', MCP_Host_Guard::guard, 5)      # valida Host header
      add_filter('rest_pre_dispatch'/'rest_post_dispatch', mcp_log_*) # log de pedidos MCP
    ↓
(Abilities API é lazy — só inicializa no primeiro wp_get_ability())
O PRÓPRIO adapter, ao arrancar (mcp_adapter_init prioridade 10), chama wp_get_ability()
internamente para descobrir tools → dispara wp_abilities_api_init → register_abilities() corre
ANTES do nosso register_mcp_server (prioridade 20) — por isso $this->ability_names já está
preenchido quando register_mcp_server lê $this->ability_names.
    ↓
mcp_adapter_init:20 → EMCP_Tools_Plugin::register_mcp_server($mcp_adapter)
  → $mcp_adapter->create_server('emcp-tools-server', 'mcp', 'emcp-tools-server', …, $tools, …)
  → endpoint fica disponível em /wp-json/mcp/emcp-tools-server

2.1 O preload "authoritative namespace" (class-mcp-adapter-bootstrap.php)

Problema real documentado no código (issue #99): quando vários plugins activos vendorizam wordpress/mcp-adapter (ex. WooCommerce + EMCP Tools) em versões diferentes, e nem todos usam o mesmo autoloader partilhado (Jetpack Autoloader), PHP carrega classes individuais de cópias diferentes consoante qual plugin referencia cada classe primeiro — resultado: um McpAdapter da v0.5.0 a par de um HttpTransport da v0.4.1 no mesmo request ("sheared namespace"), que falha com McpServerError: Session terminated (JSON-RPC -32600).

Mitigação do EMCP: regista um autoloader spl_autoload_register($cb, true, true) (throw, prepend) logo no arranque, servindo TODO o namespace WP\MCP\ a partir da SUA cópia vendorizada — antes que qualquer outro plugin possa referenciar uma classe do adapter. Reafirma-se depois de carregar o Jetpack Autoloader do próprio adapter (que também se regista com prepend).

Implicação para a réplica: se formos "mais um plugin" a vendorizar wordpress/mcp-adapter lado a lado com EMCP Tools/WooCommerce no mesmo WordPress, herdamos este mesmo risco de colisão. Duas opções limpas: (a) usar o Jetpack Autoloader nós também (para participar na arbitragem de versão-mais-alta em vez de competir com autoloaders simples), ou (b) não vendorizar — depender de uma cópia wordpress/mcp-adapter partilhada ao nível do site (plugin dedicado só ao adapter, todos os outros plugins declaram a dependência) — mais correcto a prazo mas exige coordenação com todos os plugins que hoje bundlam a sua própria cópia.

3. EMCP_Tools_Plugin — orquestrador singleton

Ficheiro: includes/class-plugin.php. Contrato mínimo para a réplica:

  • register_category() — wp_register_ability_category('<prefixo>', ['label'=>…, 'description'=>…]), chamado em wp_abilities_api_categories_init.
  • register_abilities() — $this->ability_names = $registrar->register_all($elementor_active), chamado em wp_abilities_api_init. $elementor_active vem de EMCP_Tools_Bootstrap::elementor_active() (guarda de dependência — grupos Elementor-dependentes ficam todos atrás desta flag, ver doc 01/02 e a lista completa em class-ability-registrar.php reproduzida no doc 10).
  • get_active_ability_names() — força a inicialização lazy da Abilities API (wp_get_ability('emcp-tools/list-pages') como gatilho) e devolve o array já filtrado pelo deny-list; usado pelo dispatcher (compact mode) e por qualquer superfície externa (ex. um chat de admin) que precise de saber "o que está mesmo disponível agora".
  • filter_disabled_tools($names) — array_values(array_diff($names, get_option('emcp_tools_disabled_tools', []))), hook em emcp_tools_ability_names. Este é o único ponto de aplicação do deny-list — mecanismo do deny-list incremental (34 migrações) já documentado em skill://emcp-tools §8; aqui confirma-se o hook exacto que o aplica.
  • register_mcp_server($mcp_adapter) — chamado em mcp_adapter_init prioridade 20 (depois da Abilities API já ter corrido). Comportamento condicional:
    • Server gate (emcp_tools_server_enabled, on por omissão): se off, abilities continuam registadas no core mas nenhum endpoint MCP é criado — kill-switch total sem desregistar nada.
    • Compact tool mode (emcp_tools_dispatcher_mode, off por omissão): quando ligado, o $tools passado a create_server() é só os 3 nomes do dispatcher (emcp-tools/{list-tools,get-tool-schema,call-tool}) — o resto continua registado e invocável via call-tool, só não aparece em tools/list. Quando desligado, $tools é a lista cheia (+ as 3 abilities de contexto do core: core/get-site-info, core/get-user-info, core/get-environment-info, sempre incluídas quando existirem).
    • create_server() recebe: server_id='emcp-tools-server', route_namespace='mcp', route='emcp-tools-server' (→ endpoint final /wp-json/mcp/emcp-tools-server), nome/descrição (descrição compõe um resumo do ambiente do site via EMCP_Tools_Site_Context), versão, transports=[HttpTransport::class], tools=$tools, resources=[], prompts=[], e um transport_permission_callback opcional: se o módulo OAuth (EMCP_Tools_OAuth_Server) está activo, usa Bearer OAuth; senão cai no default do adapter (Application Password / cookie admin).

4. Compact tool mode — o dispatcher de 3 tools

includes/abilities/class-dispatcher-abilities.php (ver excerto completo lido nesta sessão). Sempre registado (para wp_get_ability() resolver os 3 nomes), mas só exposto no servidor quando emcp_tools_dispatcher_mode=1 (§3). Este é exactamente o padrão que os 3 servidores emcp-tools/emcp-emanuelalmeida/emcp-descomplicar deste ecossistema já usam pelo lado do cliente (system prompt: "Compact tool mode... Discover tools with list-tools, fetch inputs with get-tool-schema, run with call-tool").

Tool O que faz Enforcement
list-tools Devolve {name, description, category, destructive} para cada ability activa (pós-deny-list), filtrável por search/category. Inclui context (resumo do ambiente via Site_Context). permission_callback: current_user_can('edit_posts') — é só metadata/routing
get-tool-schema Devolve {description, inputSchema} por nome, em lote (names: string[]). Nomes fora do set activo entram em unavailable. idem
call-tool Resolve wp_get_ability($name), corre check_permissions($args) da ability alvo (nunca do dispatcher), depois validate_input() se existir, depois execute($args). A gate real é sempre a da tool alvo — o dispatcher nunca contorna permission_callback individual

Blueprint para a réplica: replicar este padrão exactamente — um dispatcher de 3 tools que só resolve/chama wp_get_ability(), nunca duplica lógica de permissão. É ~250 linhas triviais de reescrever do zero (schema simples, sem estado) — não vale a pena tentar simplificar mais.

5. emcp_tools_register_ability() — o único ponto de entrada de registo

includes/class-schema-compat.php. Toda classe de abilities chama esta função global (nunca wp_register_ability() directamente). Faz 3 coisas antes de delegar:

  1. sanitize() — normaliza o JSON Schema para clientes exigentes: remove valores vazios de enum, força properties: {} (objecto, nunca array vazio) — Gemini/Antigravity rejeitam a forma "errada". Recursivo em items/allOf/oneOf/anyOf.
  2. strictify() (opt-in, emcp_tools_strict_schemas, off por omissão) — reescreve o schema para o modo "strict function calling" da OpenAI: toda a propriedade em required, as originalmente opcionais tornam-se nullable, additionalProperties: false em objectos com propriedades declaradas. Existe porque CrewAI e stacks OpenAI-compatíveis exigem esta forma; um schema "normal" (opcionais fora de required) é rejeitado por eles.
  3. wrap_execute_callback() — envolve o callback da ability em dois cuidados:
    • Veto de escrita (emcp_tools_before_write filter, só para abilities não-read-only): um listener pode devolver WP_Error para bloquear a escrita antes de correr — mecanismo real de enforcement de guardrails (usado pela feature Pro "Memory Enforcer"). Seam útil para nós: dá um ponto único onde qualquer política de governança (ex. CARL) pode vetar uma escrita MCP sem tocar em cada ability individualmente.
    • normalize_result() — garante que o retorno cabe em structuredContent do MCP (que o schema tipa como objecto {[key:string]:unknown}, nunca lista). Arrays associativos e objectos passam直; listas/escalares/null são embrulhados em {data: …}; array() vazio TAMBÉM é embrulhado (PHP não distingue lista vazia de mapa vazio — desembrulhado serializa para [], forma inválida). Bug real que isto evita: a integração WooCommerce devolve WP_REST_Response::get_data() verbatim, e várias rotas wc/v3 respondem com array de topo (ex. report-products-totals) — sem este wrapper, clientes MCP estritos rejeitam a resposta.

Blueprint para a réplica: replicar exactamente este envelope (sanitize + normalize_result no mínimo; strictify só se formos alvo de clientes OpenAI-strict). É a peça que evita uma classe inteira de bugs "funciona no Claude, parte no CrewAI" — não pular esta camada achando-a acessória.

6. Guardas de infraestrutura (fora do fluxo funcional, mas obrigatórias)

  • EMCP_Tools_MCP_Host_Guard::guard (rest_pre_dispatch, prioridade 5) — recusa pedidos MCP cujo header Host já não bate com o home do site (conector apontado para um domínio antigo/temporário). no_store_headers (rest_pre_serve_request) força Cache-Control: no-store nas respostas MCP — proxies como LiteSpeed/QUIC descartam respostas suspeitas de cache sem isto (issue nomeada no código).
  • EMCP_Tools_MCP_Request_Log — grava tool/status/duração/x-request-id de cada pedido MCP via rest_pre_dispatch(6)/rest_post_dispatch(10), só para rotas que começam por /mcp/emcp-tools-server — alimenta a tab "MCP Log" do admin.

7. Guarda Free⇄Pro (single-instance)

emcp-tools.php, topo do ficheiro (antes de qualquer require). Free e Pro são o mesmo código em duas pastas de plugin (emcp-tools/ vs emcp-pro/), distinguidas só por um marcador .emcp-pro no directório. Como WordPress trata as duas pastas como plugins distintos, require_once não deduplica entre elas — activar ambas redeclara todas as classes → fatal error. A guarda corre antes de qualquer require, detecta se já correu neste request (defined('EMCP_TOOLS_VERSION')) e decide: Premium sempre ganha — a cópia Free cede (mostra aviso, return antes de declarar nada) se a Premium estiver activa; a cópia Premium desactiva a Free se esta já tiver arrancado primeiro. Não relevante para a réplica (é housekeeping específico do modelo de distribuição Free/Pro deste vendor) — mas explica porque é seguro assumir que só UMA "visão" do código corre por request, mesmo no build Free.

8. Superfície total (dimensionamento)

Ver class-ability-registrar.php::register_groups() para a lista EXACTA de classes/condições — reproduzida integralmente no doc 10 (módulos) como referência cruzada, porque a ordem de registo ali É a fonte de verdade de que grupo depende de que módulo/plugin/licença. Resumo por doc:

Doc Assunto Nº aprox. de abilities
01 Elementor clássico (páginas, layout, widgets, templates, globals, custom code, composite) ~35
02 Elementor Atomic v4 + Gutenberg ~25
03 WordPress core (conteúdo, media, settings, plugins, temas, users, menus) + temas/frameworks ~30
04 Themer (CPT header/footer/single/archive/search/404 + Themer PHP) ~13
05 Redirects + Search + Snapshot + Change-ledger + Content-mirror ~14
06 Sandbox (PHP snippets, custom widgets/blocks, cloud bundle) ~20
07 Filesystem + DB + WP-CLI + Security + Performance ~15
08 Integrações terceiros (ACF, Meta Box, forms, SEO) ~30
09 Stock images + Cloud sync + OAuth ~17
10 Módulos (lifecycle) + inventário Pro-only (metadata) n/a (estrutural)

Total bate aproximadamente com os 162-165 confirmados ao vivo em skill://emcp-tools §7 — a diferença entre sites reflecte quais integrações de terceiros estão de facto instaladas.

Fonte

Leitura directa (19-08-2026) de: emcp-tools.php, includes/class-mcp-adapter-bootstrap.php, includes/class-plugin.php, includes/class-schema-compat.php, includes/abilities/class-ability-registrar.php, includes/abilities/class-dispatcher-abilities.php, vendor/wordpress/mcp-adapter/composer.json, vendor/wordpress/php-mcp-schema/composer.json; inventário completo de ficheiros (find … -name '*.php' -exec wc -l, 128 465 linhas). Cruzado com skill://emcp-tools (auditoria de postura de segurança, 16-08-2026) para os números de abilities activas/desligadas por site.