FastWebMCPpara WordPress

REFERENCIA PARA DESARROLLADORES · 0.6.0

Construye sobre
las herramientas de tu sitio.

API, ejemplos y decisiones de implementación para integrar WebMCP en themes, bloques y plugins de WordPress.

JavaScript + PHPREST · Zod · AbortSignalVer el código fuente ↗

01

Arquitectura y alcance

Esta referencia describe la implementación 0.6.0. El frontend publica herramientas en el navegador a través de FastWebMCP; las operaciones PHP se ejecutan mediante REST de WordPress. Exponer una ruta REST por sí solo no registra una herramienta WebMCP.

  1. Theme o bloqueMarkup, controles y estado visible.
  2. Runtime compartidowindow.FastWebMCP · Zod · registro nativo · ciclo de vida.
  3. WordPress RESTSesión · esquema · saneamiento · permisos · callback PHP.

runtime.ts contiene la API; frontend.ts, los adaptadores de búsqueda y filtros; frontend-api.php, el registro de operaciones y las rutas. Los bundles administrativos se cargan en sus pantallas correspondientes. El frontend no necesita el panel técnico.

Requisitos del plugin: WordPress 6.5+, PHP 8.0+. Para ejecutar herramientas desde un agente se necesita un navegador con WebMCP nativo y un agente capaz de invocarlas. El runtime utiliza MutationObserver, AbortController y AbortSignal.any; no incorpora un polyfill de WebMCP ni un cliente de IA.

Implementación: runtime.ts, frontend-api.php. La API nativa se describe en la especificación WebMCP.

02

Integración mínima en un theme

Con el plugin activo, carga tu extensión después de fwmcp-runtime. Este ejemplo añade un shortcode de contador. La herramienta y el botón comparten la misma función, y cada instancia mantiene su estado.

functions.php del theme hijoPHP
<?php
add_action('wp_enqueue_scripts', function () {
    if (!function_exists('fwmcp_enqueue_frontend')) {
        return;
    }
    fwmcp_enqueue_frontend();
    wp_enqueue_script(
        'mytheme-webmcp',
        get_stylesheet_directory_uri() . '/webmcp.js',
        array('fwmcp-runtime'),
        '1.0.0',
        true
    );
});

add_shortcode('webmcp_counter', function () {
    return '<section data-fwmcp-component="counter">
        <button type="button">Sumar</button>
        <output aria-live="polite">0</output>
        <p data-status role="status"></p>
    </section>';
});
webmcp.js del theme hijoJavaScript
function setupCounter(api) {
  api.registerComponent(
    'counter',
    '[data-fwmcp-component="counter"]',
    ({ id, root, signal, registerTool }) => {
      const output = root.querySelector('output');
      const status = root.querySelector('[data-status]');
      let count = 0;

      function increment() {
        output.textContent = String(++count);
        status.textContent = 'Contador actualizado.';
        return { id, count };
      }

      root.querySelector('button').addEventListener(
        'click', increment, { signal }
      );

      void registerTool({
        name: 'increment',
        description: 'Increment this visible counter by one.',
        inputSchema: api.z.object({}).strict(),
        execute: increment
      }).catch(error => {
        status.textContent = error.message;
      });

      return () => { /* limpiar recursos adicionales */ };
    }
  );
}

if (window.FastWebMCP) {
  setupCounter(window.FastWebMCP);
} else {
  document.addEventListener('fastwebmcp:ready', event => {
    setupCounter(event.detail);
  }, { once: true });
}

Coloca [webmcp_counter] en un bloque Shortcode. Prueba dos instancias: sus herramientas reciben nombres como counter_5_increment y counter_6_increment. Los números son ilustrativos; descúbrelos en cada página.

Registro síncrono del adaptador.

El callback de montaje de registerComponent debe ser síncrono. Puede iniciar registros asíncronos y devolver una función de limpieza. Sin WebMCP, el runtime aún monta el componente y su botón manual.

03

Referencia JavaScript

Método / propiedadEntrada y resultadoUso
registerTool(spec, options?)Promise<boolean>true confirma registro nativo; false indica registro desactivado/no disponible o una señal ya cancelada. Los errores rechazan la Promise.
registerComponent(type, selector, mount)Devuelve dispose()Asigna prefijo y señal por instancia. dispose retira el adaptador y sus instancias.
invokeServer(name, input, options?)Promise<JSON>Llama a una operación PHP con validación REST y sesión. No registra herramientas.
registerServerTool(name, options?)Promise<boolean>Descubre el descriptor y registra una herramienta global server_NAME.
schemaFromJSON(schema)Devuelve un esquema ZodConvierte el subconjunto documentado; rechaza restricciones desconocidas.
components()Array<{id, type}>Lista las instancias que conoce el runtime. No es un inventario de todas las herramientas del navegador.
reconcile()Sin resultado de negocioRevisa raíces actuales y adaptadores; útil cuando cambian atributos fuera de la observación automática.
supported()booleanComprueba la configuración y la presencia de document.modelContext. No certifica la conexión del agente.
z / versionZod compartido / "0.6.0"Evita importar un segundo Zod para tus herramientas.
Contrato de una herramientaTypeScript
type ToolSpec = {
  name: string;
  description: string;
  inputSchema: ZodType;
  readOnly?: boolean;
  execute: (
    input: any,
    context: { signal: AbortSignal }
  ) => unknown | Promise<unknown>;
};

// registerTool: options = { signal?: AbortSignal }
// registerServerTool: options = {
//   signal?: AbortSignal,
//   onResult?: (result: any) => unknown
// }
// invokeServer: options = { signal?: AbortSignal }

Usa esquemas Zod convertibles a JSON Schema: el SDK llama a toJSONSchema() antes del registro y a parse() antes de ejecutar. Un esquema Zod aceptado por JavaScript puede fallar al convertirse, por ejemplo si incluye transformaciones no representables. readOnly se convierte en una anotación, no en un permiso.

Herramientas JS: ^[a-z][a-z0-9_]{0,99}$. Tipos de componente: ^[a-z][a-z0-9_]{0,29}$. El nombre final con prefijo también debe caber en 100 caracteres. Nombres duplicados se rechazan; no registres un mismo puente global dentro de cada instancia.

04

Ciclo de vida y cancelación

El callback de montaje recibe { id, root, signal, registerTool }. Busca controles dentro de root, no mediante IDs globales. Usa {signal} en listeners y propaga la señal a peticiones y tareas largas.

  • El runtime observa inserciones, retiradas y cambios de data-fwmcp-component.
  • Al reconciliar una raíz retirada, cancela sus herramientas y ejecuta su función de limpieza.
  • Si después se reinserta, se monta como una instancia nueva. Retirar y reinsertar en el mismo turno sin una reconciliación intermedia puede conservar la instancia.
  • Una raíz pertenece a un adaptador. Para sustituir controles internos, reemplaza la raíz completa.
  • Otros cambios de atributos usados en tu selector requieren api.reconcile().
  • Un error de montaje se registra en consola; no se reintenta mientras la raíz siga montada. Retírala y reconcilia antes de volver a insertarla.
Herramienta global con vida explícitaJavaScript
const api = window.FastWebMCP;
const lifetime = new AbortController();

const registered = await api.registerTool({
  name: 'mytheme_component_count',
  description: 'Read the number of mounted frontend components.',
  inputSchema: api.z.object({}).strict(),
  readOnly: true,
  execute: () => ({ count: api.components().length })
}, { signal: lifetime.signal });

// Al desmontar tu integración:
lifetime.abort();

Este fragmento usa await de nivel superior: ejecútalo en un módulo o una función async después de que la API esté disponible. Sin una señal, la herramienta global permanece durante la vida de la página. La señal del executor combina la cancelación de la llamada con la de su registro; no cancela mágicamente trabajo que ignore esa señal.

05

Operaciones PHP extensibles

Registra en init, antes de rest_api_init. Usa un nombre único y un esquema raíz de tipo object con additionalProperties:false. Este ejemplo público solo devuelve una etiqueta saneada; no modifica WordPress.

Registrar una operación públicaPHP
<?php
add_action('init', function () {
    if (!function_exists('fwmcp_register_frontend_tool')) {
        return;
    }
    fwmcp_register_frontend_tool('theme_echo', array(
        'description' => 'Echo a short label in the visible theme.',
        'public' => true,
        'readOnly' => true,
        'schema' => array(
            'type' => 'object',
            'additionalProperties' => false,
            'properties' => array(
                'label' => array('type' => 'string', 'maxLength' => 80)
            ),
            'required' => array('label')
        ),
        'permission_callback' => '__return_true',
        'callback' => function ($input, $request) {
            return array('label' => sanitize_text_field($input['label']));
        }
    ));
});
Mostrar el resultado en el themeHTML + JavaScript
<!-- En el markup del theme -->
<p id="theme-status" role="status"></p>

<!-- En tu script de extensión, después de que exista la API -->
<script>
async function setupEcho(api) {
  const lifetime = new AbortController();
  const output = document.querySelector('#theme-status');
  if (!output) return;

  try {
    await api.registerServerTool('theme_echo', {
      signal: lifetime.signal,
      onResult: result => { output.textContent = result.label; }
    });
  } catch (error) {
    output.textContent = error.message;
  }
  // En una integración dinámica, guarda lifetime para llamar abort()
  // cuando retires este widget. En navegación completa termina la página.
}

if (window.FastWebMCP) {
  void setupEcho(window.FastWebMCP);
} else {
  document.addEventListener('fastwebmcp:ready', event => {
    void setupEcho(event.detail);
  }, { once: true });
}
</script>
CampoObligatorio / valor inicialContrato
descriptionObligatorioString no vacío; describe entradas, efecto y alcance.
schemaObligatorioArray PHP con JSON Schema raíz estricto.
callbackObligatorioCallable: recibe ($input, $request); devuelve datos, WP_Error o respuesta REST.
permission_callbackObligatorioCallable con WP_REST_Request; devuelve bool o WP_Error.
publicfalseCuando es false se exige sesión. true no anula capability ni permission_callback.
capabilityString vacíoCapacidad adicional comprobada con current_user_can.
readOnlyfalseAnotación publicada para el agente; no restringe el callback.

Nombres PHP: ^[a-z][a-z0-9_.-]{0,59}$. El puente genera server_NAME y sustituye puntos/guiones por guiones bajos: theme.echo y theme_echo colisionan. Registrar demasiado tarde lanza LogicException; especificaciones o nombres inválidos/duplicados lanzan InvalidArgumentException.

06

REST, sesión y caché

PeticiónContratoResultado
GET /wp-json/fastwebmcp/v1/frontend-toolsDescubre operaciones para la sesión{version:1, tools:[{name,description,inputSchema,readOnly}]}
POST /wp-json/fastwebmcp/v1/frontend-tools/NAMEBody: {"input":{...}}Datos devueltos por el callback; no añade un wrapper success.
GET /wp-admin/admin-ajax.php?action=fwmcp_frontend_sessionBootstrap de sesión same-origin sin caché{success:true,data:{nonce,authenticated}}; nonce vacío si no hay sesión.

Las URLs REST de la tabla muestran el formato habitual con enlaces permanentes. Usa rest_url() o invokeServer() en integraciones para respetar la configuración real, subdirectorios y formato de rutas. WordPress también admite el parámetro rest_route cuando no usa ese formato. Referencia de rutas WordPress.

Usar una operación desde la interfaz manualJavaScript
const result = await window.FastWebMCP.invokeServer('filter_content', {
  query: 'diseño',
  type: 'post',
  category: 0,
  limit: 6
});
// result = { query, category, type, results: [{ id, title, url }] }
Petición pública de diagnósticoShell
curl -X POST 'https://example.com/wp-json/fastwebmcp/v1/frontend-tools/filter_content' \
  -H 'Content-Type: application/json' \
  --data '{"input":{"query":"diseño","type":"post","category":0,"limit":6}}'

Reemplaza example.com por un WordPress con el plugin activo. La petición curl verifica REST; no demuestra registro ni ejecución nativa WebMCP.

Autenticación del runtime

Obtiene el nonce mediante Ajax con cookies same-origin y lo envía como X-WP-Nonce. Los scripts frontend no incrustan un nonce de usuario en HTML cacheable. La sesión se mantiene en memoria durante la página. Ante rest_cookie_invalid_nonce, la llamada falla y se invalida ese bootstrap; la siguiente llamada pide una sesión nueva. No hay reintento automático de escrituras.

Respeta respuestas sin caché en el CDN para la sesión y el manifiesto, y evita cachear operaciones autenticadas. Si cambias de cuenta sin recargar la página, redescubre tras recargar para renovar sesión, permisos y herramientas.

07

Permisos, saneamiento y escrituras

El manifiesto filtra por sesión y capability. La comprobación específica de permission_callback ocurre al ejecutar: una operación descubierta puede seguir rechazando una entrada. El plugin usa validación/saneamiento de esquema de WordPress para el argumento input, además de la comprobación de permisos.

Permiso de edición por objetoPHP
// Dentro del spec de una operación privada con schema post_id:
'public' => false,
'capability' => 'edit_posts',
'permission_callback' => function ($request) {
    $input = $request->get_param('input');
    $id = isset($input['post_id']) ? absint($input['post_id']) : 0;
    return $id > 0 && current_user_can('edit_post', $id);
},

Es un fragmento de spec, no un registro completo. Declara post_id como integer requerido con mínimo 1 en el esquema de tu operación. Para un recurso diferente, utiliza su capacidad correspondiente y verifica propiedad/alcance según el caso.

  • Validar tipos y rangos no sustituye el saneamiento de negocio: usa, por ejemplo, sanitize_text_field o wp_kses_post cuando corresponda.
  • No confíes en ocultar una herramienta ni en readOnly para autorizar acceso. REST debe denegar peticiones directas no autorizadas.
  • Para escrituras propias, implementa preparación, revisión y confirmación con estado de servidor. La API de extensiones no añade ese protocolo automáticamente.
  • Una revisión optimista no constituye por sí sola una transacción ni elimina todas las carreras. Evalúa atomicidad, reintentos e idempotencia del caso de uso.
  • Usa textContent para resultados de texto en la interfaz y valida URLs antes de convertirlas en enlaces.

Modelo WordPress: permission_callback y endpoints personalizados. Las decisiones de permisos por objeto pertenecen a tu extensión.

08

Subconjunto de JSON Schema

Tipo / claveAdmitido por el puente
objectproperties, required y additionalProperties:false; también para objetos anidados.
stringminLength, maxLength.
number / integerminimum, maximum; integer impone valor entero.
booleanTipo boolean.
arrayitems, minItems, maxItems.
Comunestype, description, enum.

El puente rechaza keywords no compatibles, incluidas pattern, format, $ref, oneOf, anyOf, allOf, default, $schema y tipos nullable en arrays de tipos. Usa este subconjunto para descriptores destinados a registerServerTool.

El registro PHP comprueba el contrato raíz; la conversión del puente se realiza al registrar en el navegador. Que PHP acepte un esquema no garantiza que el puente lo pueda convertir. Para necesidades más amplias registra una herramienta JS con Zod convertible a JSON Schema y mantén validación equivalente en tu operación REST.

Schema compatibleJSON
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "query": { "type": "string", "maxLength": 200 },
    "type": { "type": "string", "enum": ["post", "page"] },
    "category": { "type": "integer", "minimum": 0 },
    "limit": { "type": "integer", "minimum": 1, "maximum": 12 }
  },
  "required": ["query", "type", "category", "limit"]
}

09

Bloques, herramientas y flujos incluidos

InstanciaHerramientasFlujo
search_Nstate({}), prepare({query}), submit({revision})prepare rellena y devuelve revisión. submit exige revisión válida, valida el formulario y programa requestSubmit a 300 ms.
filter_Nstate({}), prepare({query,category}), apply({revision})apply invoca filter_content y renderiza resultados dentro de esa instancia.
Puente de servidorserver_NAMEregisterServerTool descubre schema, valida con Zod y ejecuta REST.

N es un número asignado durante el montaje. Editar manualmente un campo invalida la preparación anterior. Los filtros cancelan búsquedas en curso cuando cambian sus campos. Después de una navegación completa, vuelve a descubrir las herramientas.

Bloque fastwebmcp/content-filter

Atributos: label (por defecto «Explorar contenido»), type (post o page, post por defecto) y limit (6 por defecto; PHP lo limita a 1–12). Solo consulta contenido publicado sin contraseña. En la UI de entradas, prepare permite categorías presentes en el select; en páginas exige category:0.

Sin JavaScript, GET usa fwmcp_query y fwmcp_category, compartidos por las instancias de la página. Con JavaScript, cada bloque conserva campos y resultados propios; con WebMCP ausente/desactivado, siguen disponibles los controles manuales.

Un bloque personalizado

El render PHP de un bloque dinámico puede llamar fwmcp_enqueue_frontend(), emitir una raíz con data-fwmcp-component y cargar su adaptador como un script clásico dependiente de fwmcp-runtime. Usa viewScript y su archivo de assets para el JavaScript frontend de tu bloque. Los módulos ESM pueden consumir la API global una vez disponible; no hay export ESM público del runtime en esta versión.

Campos y carga de assets de bloques: referencia oficial de block.json.

Herramientas administrativas

El editor, biblioteca de medios, panel FastWebMCP y pantallas de ajustes/gestión registran sus propias herramientas según contexto y capacidades. No existe un catálogo universal invariable para toda página. Consulta readme.txt y los adaptadores editor.ts, media-library.ts y admin-tools.ts para nombres y flujos concretos.

10

Eventos, diagnóstico y errores

Disponibilidad y errores del executorJavaScript
const api = window.FastWebMCP;
console.log({ version: api?.version, supported: api?.supported() });
console.table(api?.components() ?? []);

document.addEventListener('fastwebmcp:error', event => {
  const { name, error } = event.detail;
  console.error('WebMCP executor:', name, error);
});

fastwebmcp:ready se emite en document con la API en detail. fastwebmcp:error informa errores capturados dentro del executor. La validación Zod anterior al executor y los fallos de registro no pasan por este evento: maneja también las Promises de registro y los errores que recibe el agente.

SíntomaComprobar
window.FastWebMCP no existeQue el render/helper haya cargado fwmcp-runtime; orden/dependencias del script y errores de consola.
supported() devuelve falseFiltro frontend_enabled, presencia de document.modelContext y compatibilidad del navegador/agente.
Nombre inválido o duplicadoPrefijo final, longitud, nombres globales y conversión . / - a _ en puentes.
Operación no disponible para esta sesiónRegistro PHP anterior a rest_api_init, sesión y capacidad del manifiesto.
Revisión desactualizadaPrepara de nuevo después de editar manualmente o navegar; no reutilices revisiones entre instancias.
Esquema JSON no compatibleKeywords fuera del subconjunto, additionalProperties o required inválidos.
REST deniega accesoNonce y cookies, capability y permission_callback; la visibilidad del descriptor no concede permiso.
Resultados manuales fallan detrás de cachéRutas same-origin, nonce vigente y exclusión de respuestas autenticadas del caché.

REST puede responder 400 por entradas inválidas, 401/403 por acceso o autenticación, y 404 por una operación inexistente. Tus callbacks pueden devolver otros estados mediante WP_Error/REST. invokeServer transforma errores HTTP en Error(message); no conserva status/code como propiedades públicas del Error. Para diagnosticar esos datos usa la pestaña Network.

Filtros PHP de configuraciónPHP
// Desactivar únicamente el registro WebMCP del frontend:
add_filter('fwmcp_frontend_enabled', '__return_false');

// Mostrar el panel de laboratorio opcional en el frontend:
// El plugin exige además manage_options para mostrarlo/cargarlo.
add_filter('fwmcp_frontend_debug_panel', '__return_true');

11

Compilación, pruebas y distribución

Clonar y construirShell
git clone https://github.com/MauricioPerera/fastwebmcp-wordpress.git
cd fastwebmcp-wordpress
npm ci
npm run typecheck
npm run build
npm test
npm run package

Desarrollo: Node.js 22+ y Python 3 para el empaquetado. PHP 8+ permite comprobar sintaxis localmente. Las dependencias están fijadas en package-lock.json: Zod 4.5.4, esbuild 0.28.2 y TypeScript 7.0.2. vendor/fastwebmcp incluye el subconjunto MIT de registro imperativo del SDK; no requiere un checkout adyacente.

  • npm run build: genera los siete bundles IIFE de assets/.
  • npm test: prueba el runtime compilado en VM con un mock de document.modelContext; esquemas, duplicados, cancelación, retirada/remontaje, disposal y ausencia de WebMCP.
  • npm run package: genera dist/fastwebmcp-wordpress.zip, con carpeta raíz fastwebmcp-wordpress, assets, bloques, fuentes y licencias.
  • CI comprueba tipos, compilación, pruebas, sincronización de bundles, sintaxis PHP y ZIP. Los datos y fixtures WordPress del laboratorio externo no se distribuyen.
Qué demuestra cada prueba.

La VM valida el contrato local del runtime, no la integración del navegador. La versión 0.6.0 se validó también con WebMCP real en un WordPress local: dos búsquedas, dos filtros, retirada/remontaje, extensión PHP, rechazos y uso manual; 27 comprobaciones REST nuevas y 105 de regresión en ese laboratorio.

Cuando cambies fuentes TypeScript, reconstruye assets y ejecuta pruebas antes de empaquetar. La instalación del ZIP compilado no requiere Node, Python ni descargar scripts remotos.

12

Límites y referencias

  • Runtime compartido: aproximadamente 429 KiB sin compresión; adaptadores frontend, 4.2 KiB; editor del filtro, menos de 1 KiB. No se ha optimizado aún el tamaño de Zod.
  • La navegación dinámica admite sustituir raíces completas, no incluye un router SPA ni garantiza compatibilidad con cualquier theme, CDN o plugin.
  • Los esquemas tienen el subconjunto indicado; el protocolo de preparación/revisión de una extensión debe implementarse expresamente.
  • El comportamiento está documentado para 0.6.0. Consulta la release y el código antes de asumir compatibilidad con versiones posteriores.