Tu primer plugin paso a paso
Tu primer plugin paso a paso
En el capítulo 1 vimos qué problema resuelve Agent Plugins: un formato de paquete portable, neutral respecto del proveedor, para empaquetar Agent Skills y servidores MCP. Ahora lo construimos.
Este capítulo hace tres cosas, en este orden:
- Reproduce el plugin mínimo útil que publica el
README.mddel repositorio oficial, y lo explica archivo por archivo y campo por campo. - Lo hace crecer de forma incremental: un segundo skill, metadatos en el manifiesto y un servidor MCP.
- Muestra cómo probarlo localmente y qué errores aparecen la primera vez.
Un aviso de método que vamos a repetir en todo el curso: el README.md es material informativo, no normativo. Lo dice él mismo: “This README is a non-normative introduction. The versioned specification defines the portable contract.” Cada vez que un ejemplo del README se apoye en una regla, citaremos la sección de spec/1.0.0.md que la fija.
El plugin mínimo útil
El README lo introduce así: “The smallest useful plugin is a directory with one skill”. La estructura es esta, transcrita literalmente:
hello-plugin/
├── plugin.json
└── skills/
└── greet/
└── SKILL.md
Tres cosas que conviene fijar antes de escribir nada:
- El paquete es un directorio. La especificación dice en §4.1, punto 1: “A plugin is a directory rooted at a single filesystem location.” No es un
.zip, no es un.tar.gz, no es un bundle descargado de un registro. Es una carpeta. plugin.jsones lo único obligatorio. §4.1, punto 2: “A plugin MUST include a manifest atplugin.jsonin the plugin root.” DEBE (MUST). El resto del árbol es opcional.skills/no es una convención tuya, es una ubicación fija. §6.1 la fija por tabla y añade que “plugin.jsoncannot override these locations or contain inline component configuration.”
Crea el esqueleto:
mkdir -p hello-plugin/skills/greet
cd hello-plugin
plugin.json, línea por línea
Contenido exacto del ejemplo del README:
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "hello-plugin"
}
Dos campos. Ni uno más. Son exactamente los dos campos obligatorios que lista §5.3, y coinciden con el "required": ["$schema", "name"] de schemas/1.0.0/plugin.schema.json.
$schema
No es decoración para que el editor te autocomplete. Es el selector de versión del paquete. §5.2 lo define así: “The required $schema field identifies the Agent Plugins specification version targeted by the plugin and its corresponding manifest schema. For Agent Plugins 1.0.0, its value MUST be the canonical identifier https://agent-plugins.org/schemas/1.0.0/plugin.schema.json.”
En el esquema JSON el campo está declarado con const, no con type: solo esa cadena exacta pasa la validación. Y hay una regla que sorprende a todo el mundo la primera vez:
“Clients MUST NOT retrieve a schema while loading a plugin.” (§5.2)
El cliente NO DEBE (MUST NOT) descargar esa URL al cargar tu plugin. La usa como identificador para elegir qué reglas de validación locales aplicar. Si el cliente no soporta la versión declarada, DEBE (MUST) rechazar el plugin y DEBERÍA (SHOULD) reportar la versión no soportada.
name
§5.3 lo describe como “Human-readable plugin name”, y §5.5 le impone cuatro restricciones que DEBE (MUST) cumplir:
| Restricción | Requisito | Detalle |
|---|---|---|
| Longitud | 1 a 64 caracteres | Inclusive en ambos extremos |
| Juego de caracteres | a-z, 0-9, -, . | Solo minúsculas alfanuméricas, guiones y puntos |
| Inicio y fin | Alfanumérico | El primer y el último carácter |
| Repetición | Ni -- ni .. | Sin guiones ni puntos consecutivos |
La especificación aclara además que “Periods are allowed in plugin names”, y da ejemplos. Válidos: my-plugin, acme.tools, lint3r, a. Inválidos: My-Plugin por la mayúscula, -start por el guion inicial, has--double por los guiones consecutivos, too.many..dots por los puntos consecutivos, y la cadena vacía.
Ese conjunto de reglas está condensado en el esquema en un solo patrón:
^(?!.*(?:--|\.\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$
Fíjate en el lookahead negativo del principio: es lo que prohíbe -- y ... Guárdalo en la cabeza, porque en el capítulo de validación condiciona qué validador puedes usar.
Un detalle importante sobre la gravedad de los errores. §5.2 establece que “Any schema violation other than an unknown top-level field or a non-object extensions field is fatal: the client MUST reject the plugin and MUST NOT discover or execute any of its components.” Un name mal formado no degrada el plugin: lo tumba entero, skills incluidos.
skills/greet/SKILL.md, línea por línea
Contenido exacto del ejemplo del README:
---
name: greet
description: Greet the user and offer help.
---
Greet the user and offer help.
Aquí hay una frontera que tienes que tener clarísima desde el primer minuto: este archivo no lo define Agent Plugins. §7.1 es explícita:
“Agent Skills MUST conform to the Agent Skills specification. That specification is the source of truth for the
SKILL.mdformat, frontmatter fields, and directory layout (scripts/,references/,assets/).”
Y remata: “This specification defines how Agent Skills are discovered within a plugin, not the skill format itself or how clients expose skills to users or models.”
Es decir: el frontmatter con name y description, el cuerpo en Markdown, los subdirectorios scripts/, references/ y assets/ son terreno de la especificación de Agent Skills. Si necesitas dominar ese formato, ese es el tema del curso hermano: Agent Skills. Lo que Agent Plugins aporta es únicamente la regla de dónde se busca ese archivo.
Y la regla de búsqueda es estricta (§7.1):
“The fixed discovery location is
skills/. Each immediate child directory containing a path named exactlySKILL.mdthat resolves to a regular file is treated as one skill. Clients MUST NOT recursively search deeper descendants for additional skills.”
Desglosemos:
- Hijo inmediato.
skills/greet/SKILL.mdse descubre.skills/saludos/greet/SKILL.mdno: está un nivel más abajo y el cliente NO DEBE (MUST NOT) buscar recursivamente. - Nombre exacto.
SKILL.md, con esas mayúsculas.skill.mdoSkill.mdno son ese nombre. - Archivo regular. Debe resolver a un archivo, no a un directorio.
Y si el skill existe pero está mal escrito según la especificación de Agent Skills, §7.1 dice que el cliente DEBE (MUST) saltarse ese skill y seguir cargando los demás, y DEBERÍA (SHOULD) reportarlo. Un skill roto no rompe el plugin.
Qué hace un cliente al cargarlo
El README lo resume en una frase: “A client that supports skills can load the plugin by reading plugin.json and discovering skills/greet/SKILL.md.”
Y a continuación pone el límite que hay que respetar al enseñar esto: “How the client exposes the skill to users or models is outside the Agent Plugins specification.” La especificación no dice si el skill aparece en un menú, si se inyecta en el prompt del sistema, si se activa por descripción o si el usuario lo invoca con un comando. Eso es decisión de cada producto.
Lo que sí está especificado es la secuencia de carga:
sequenceDiagram
participant C as Cliente
participant FS as Directorio del plugin
C->>FS: leer plugin.json en la raiz
FS-->>C: manifiesto JSON
C->>C: seleccionar reglas locales segun $schema
C->>C: validar el esquema cerrado del manifiesto
C->>FS: listar hijos inmediatos de skills/
FS-->>C: directorio greet con SKILL.md
C->>C: registrar el skill greet
C->>FS: buscar mcp.json en la raiz
FS-->>C: no existe
C->>C: continuar sin error segun 6.2
Tres reglas normativas sostienen ese diagrama:
- §5.1: “Clients MUST check for a manifest at
plugin.jsonin the plugin root”, y “A client loads and validates rootplugin.jsonbefore discovering components”. El orden importa: primero el manifiesto, después los componentes. - §6.1: el cliente DEBE (MUST) descubrir cada tipo de componente soportado desde su ubicación fija.
- §6.2: “If a fixed component location is absent, the client MUST NOT treat that as an error.” Que
hello-pluginno tengamcp.jsonno es un fallo, es simplemente un plugin sin servidores MCP.
Un matiz de conformidad que evita malentendidos: §11.2 permite que un cliente solo soporte skills y siga siendo conformante. “A skills-only client can conform without supporting MCP servers, provided it satisfies all applicable requirements.” Y §7 obliga a que los clientes ignoren los tipos de componente que no soportan. Tu plugin con mcp.json cargado en un cliente solo-skills no falla: sus skills se cargan y su MCP se ignora.
Hacerlo crecer: de hello-plugin a algo real
Vamos a construir un plugin propio, docs-toolkit, en tres incrementos. Cada uno añade exactamente una cosa.
flowchart LR
A["Paso 0 - plugin minimo<br/>plugin.json + un skill"] --> B["Paso 1 - segundo skill<br/>skills/changelog/"]
B --> C["Paso 2 - metadatos<br/>version, description, author, license"]
C --> D["Paso 3 - servidor MCP<br/>mcp.json en la raiz"]
Paso 1: un segundo skill
Añadir skills es añadir directorios. No se declaran en ninguna parte: la ubicación fija hace el trabajo.
mkdir -p docs-toolkit/skills/resumir
mkdir -p docs-toolkit/skills/changelog
Estructura resultante:
docs-toolkit/
├── plugin.json
└── skills/
├── resumir/
│ ├── SKILL.md
│ ├── scripts/
│ │ └── extraer.sh
│ └── references/
│ └── criterios.md
└── changelog/
└── SKILL.md
Ese anidamiento con scripts/ y references/ no me lo estoy inventando: es exactamente la forma del ejemplo de §7.1, que muestra un skill deploy con scripts/rollback.sh y references/runbook.md. Pero cuidado con la atribución: esos subdirectorios los define la especificación de Agent Skills, no Agent Plugins. Agent Plugins solo garantiza que skills/resumir/ y skills/changelog/ serán inspeccionados.
Nota de contención de rutas para cuando empieces a usar enlaces simbólicos: §4.1, punto 3, dice que las rutas resueltas DEBEN (MUST) permanecer dentro de la raíz del plugin, y que los symlinks PUEDEN (MAY) apuntar a destinos dentro de esa raíz, pero el cliente DEBE (MUST) rechazar rutas del paquete que resuelvan fuera. Si enlazas skills/resumir/SKILL.md a un archivo que vive fuera del plugin, manda el tercer nivel de la lista de fronteras de fallo del final de §4.1: ese skill se salta.
Paso 2: metadatos
Hasta aquí el manifiesto tenía dos campos. §5.4 define siete campos opcionales de metadatos: version, description, author, homepage, repository, license y keywords. Con eso, el manifiesto queda así:
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "docs-toolkit",
"version": "0.2.0",
"description": "Skills para resumir documentacion y redactar changelogs",
"author": {
"name": "Artiko",
"email": "[email protected]",
"url": "https://example.com"
},
"homepage": "https://docs.example.com/docs-toolkit",
"repository": "https://github.com/example/docs-toolkit",
"license": "MIT",
"keywords": ["documentacion", "changelog"]
}
Ese es el ejemplo de manifiesto completo de §5.2 con los valores cambiados. Lo único que falta respecto del ejemplo oficial es extensions, que veremos en el capítulo plugin.json: el manifiesto al detalle.
Cuatro reglas que hay que internalizar sobre estos metadatos:
- El esquema es cerrado. §5.2 lista los diez únicos campos raíz permitidos:
$schema,name,version,description,author,homepage,repository,license,keywordsyextensions. El esquema JSON lo refuerza con"additionalProperties": false. authortambién es cerrado. §5.4: “Theauthorobject MAY contain only thename,email, andurlfields, each with a string value. Any other field or value type makes the manifest invalid.” Añadirauthor.githubinvalida el manifiesto y eso es fatal.- La validación de metadatos es solo por tipo JSON. §5.4 es tajante: “Clients MUST NOT reject a manifest solely because
versionis not valid Semantic Versioning;homepage,repository, orauthor.urlis not a recognized URL;author.emailis not a recognized email address; orlicenseis not an SPDX identifier.” SemVer y SPDX son RECOMENDADOS (RECOMMENDED), no exigidos. Queversionsea"beta"es feo, pero no es motivo de rechazo. versiontiene un uso definido. §5.4 dice que se usa “for update checks and cache freshness”, y §10.2 añade que los clientes PUEDEN (MAY) usarlo para decidir si hay actualizaciones o si una caché está obsoleta. Es un dato para el cliente, no un simple adorno.
Paso 3: un servidor MCP
Ahora el paso que cambia la naturaleza del plugin: pasar de “paquete de instrucciones” a “paquete que además aporta herramientas”.
Primera regla, y la que más gente rompe: la configuración MCP no va en plugin.json. §7.2.1 lo prohíbe con MUST NOT:
“The MCP configuration path is
mcp.jsonat the plugin root. MCP configuration MUST NOT be declared inline inplugin.jsonor loaded from any alternative core path.”
Crea docs-toolkit/mcp.json:
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
"mcpServers": {
"indexador": {
"type": "stdio",
"command": "./bin/indexador",
"args": ["--data", "${PLUGIN_DATA}/indice"],
"env": {
"CONFIG": "${PLUGIN_ROOT}/config.json"
},
"cwd": "${PLUGIN_ROOT}"
}
}
}
Estructura del archivo, según §7.2.1: “mcp.json MUST be a JSON object containing the required $schema and mcpServers fields, with no other top-level fields.” Dos campos raíz, ninguno más. Y mcpServers DEBE (MUST) ser un objeto cuyos nombres de miembro identifican servidores; un mcpServers vacío es válido.
Fíjate en el $schema: apunta a mcp.schema.json, no a plugin.schema.json, y comparte versión con el manifiesto. §10.1 lo exige: “When mcp.json is present, the version in its $schema value MUST match the version declared by plugin.json.”
Sobre los campos de la entrada indexador:
typeselecciona el transporte. §7.2.1 define una unión cerrada de tres variantes:stdio,streamable-httpysse. “Each server configuration MUST contain atypefield and match exactly one of the closed variants below. An unknown field, an unknowntypevalue, or a field belonging to another variant makes that server entry invalid.” Ponerurlen una entradastdiola invalida.commandDEBE (MUST) ser “a single executable token, not a shell command string”, y DEBE (MUST) ser un nombre desnudo o una ruta relativa al plugin que empiece por./. Como aquí empaquetamos el ejecutable dentro del plugin, la especificación obliga a la forma relativa: “A plugin that bundles an executable in the package MUST use a plugin-relativecommand.”args,envycwdson los tres únicos sitios donde se expanden${PLUGIN_ROOT}y${PLUGIN_DATA}. §9.2: “Expansion applies to every string element ofargs, every string value inenv, and thecwdstring. It does not apply toenvkeys,command, or fixed component locations.”${PLUGIN_ROOT}y${PLUGIN_DATA}los provee el cliente, no tú. §9.1 obliga a los clientes que lanzan subprocesos a proporcionar ambas variables, y describe el reparto de responsabilidades:PLUGIN_DATApara dependencias instaladas, código generado, cachés y estado que debe sobrevivir a las actualizaciones;PLUGIN_ROOTpara scripts, binarios y archivos de configuración que viajan con el paquete.
Un ejemplo literal de §9.1 de cómo se ven esas variables en la práctica: un cliente que carga el plugin devtools desde /home/alex/.agents/plugins/devtools establece
PLUGIN_ROOT=/home/alex/.agents/plugins/devtools
PLUGIN_DATA=/home/alex/.agents/plugins/data/devtools
Y si el servidor fuera remoto en vez de local:
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
"mcpServers": {
"api-docs": {
"type": "streamable-http",
"url": "https://docs.example.com/mcp",
"headers": {
"X-Tenant": "public-tenant"
}
}
}
}
Con dos límites de §7.2.1: la url DEBE (MUST) ser absoluta HTTP o HTTPS, sin información de usuario ni fragmento, y los destinos que no sean loopback DEBEN (MUST) usar HTTPS; y “Plugins MUST NOT embed credentials or other secrets in headers.” La misma prohibición existe para env en §9.2. Agent Plugins 1.0.0 no tiene mecanismo portable de secretos, así que no hay dónde esconder una API key: simplemente no la pongas ahí. El detalle completo de MCP está en Servidores MCP dentro de un plugin.
El plugin terminado
docs-toolkit/
├── plugin.json
├── mcp.json
├── bin/
│ └── indexador
├── config.json
└── skills/
├── resumir/
│ ├── SKILL.md
│ ├── scripts/
│ │ └── extraer.sh
│ └── references/
│ └── criterios.md
└── changelog/
└── SKILL.md
Compáralo con el layout estándar de §4.2, que además incluye un directorio de extensión de cliente, LICENSE y CHANGELOG.md. La frase que introduce ese layout en la especificación es “A plugin that has skills, MCP servers, and a client extension can have the following layout”: es un ejemplo posible, no una lista de archivos obligatorios. El único archivo obligatorio sigue siendo plugin.json.
Cómo probarlo localmente
Aquí hay que ser honesto sobre el estado del ecosistema. FUTURE_CONSIDERATIONS.md, que es material explícitamente no normativo, dice literalmente: “No test harness or validation tool is specified.” Agent Plugins 1.0.0 no publica ningún linter ni validador oficial.
Lo que sí publica son dos JSON Schema draft 2020-12 reutilizables con cualquier validador genérico: schemas/1.0.0/plugin.schema.json y schemas/1.0.0/mcp.schema.json.
Requisito no negociable del validador que elijas: debe soportar draft 2020-12 y lookahead negativo en pattern, porque el patrón de name usa (?!.*(?:--|\.\.)).
Validar el manifiesto
Con check-jsonschema, que se ejecuta sin instalar nada vía uvx:
uvx check-jsonschema \
--schemafile /ruta/a/schemas/1.0.0/plugin.schema.json \
docs-toolkit/plugin.json
Salida cuando todo está bien:
ok -- validation done
Salida real con un manifiesto que tiene el nombre mal formado y un campo raíz que no existe:
Schema validation errors were encountered.
plugin-malo.json::$.name: 'Hello--Plugin' does not match '^(?!.*(?:--|\\.\\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$'
plugin-malo.json::$: Additional properties are not allowed ('mcpServers' was unexpected)
Ese par de errores es didácticamente perfecto: el primero es la violación de §5.5, el segundo es la violación del esquema cerrado de §5.2 y, de paso, el error clásico de meter la configuración MCP dentro de plugin.json.
La alternativa en Node es ajv-cli, con dos banderas obligatorias en la práctica:
npx --yes ajv-cli@5 validate \
--spec=draft2020 \
--all-errors \
-s /ruta/a/schemas/1.0.0/plugin.schema.json \
-d docs-toolkit/plugin.json
Sin --spec=draft2020 asume draft-07 y falla. Sin --all-errors solo reporta el primer problema y esconde el resto.
Validar el mcp.json
uvx check-jsonschema \
--schemafile /ruta/a/schemas/1.0.0/mcp.schema.json \
docs-toolkit/mcp.json
Aviso sobre la lectura de errores aquí: como las tres variantes de transporte están unidas por oneOf, los mensajes son ruidosos. Un env que contiene PLUGIN_ROOT produce esta salida real:
Best Match:
$.mcpServers.s: 'url' is a required property
Best Deep Match:
$.mcpServers.s.env: 'PLUGIN_ROOT' should not be valid under {'enum': ['PLUGIN_ROOT', 'PLUGIN_DATA']}
El 'url' is a required property es un falso amigo: viene de que el validador intentó casar tu entrada stdio contra la variante HTTP y falló. El error real está en el Best Deep Match. Aprende a leer esa segunda línea.
Un guion para el laboratorio
#!/usr/bin/env bash
set -euo pipefail
PLUGIN_DIR="${1:?uso: validar-plugin.sh <dir-del-plugin>}"
SCHEMAS="${APS_SCHEMAS:?exporta APS_SCHEMAS=/ruta/a/schemas/1.0.0}"
# 1. plugin.json es obligatorio (spec 4.1.2)
[ -f "$PLUGIN_DIR/plugin.json" ] || { echo "FALTA plugin.json en la raiz"; exit 1; }
uvx check-jsonschema --schemafile "$SCHEMAS/plugin.schema.json" "$PLUGIN_DIR/plugin.json"
# 2. mcp.json es opcional (spec 6.2: su ausencia NO es error)
if [ -f "$PLUGIN_DIR/mcp.json" ]; then
uvx check-jsonschema --schemafile "$SCHEMAS/mcp.schema.json" "$PLUGIN_DIR/mcp.json"
fi
# 3. skills/ es opcional; solo hijos INMEDIATOS con SKILL.md cuentan (spec 7.1)
if [ -d "$PLUGIN_DIR/skills" ]; then
find "$PLUGIN_DIR/skills" -mindepth 2 -maxdepth 2 -name SKILL.md -printf 'skill: %h\n'
fi
El -mindepth 2 -maxdepth 2 no es capricho: reproduce exactamente la regla de §7.1 de hijos inmediatos. Si un SKILL.md no sale en esa lista, ningún cliente conformante lo va a descubrir.
Lo que ningún validador de esquema comprueba
El propio plugin.schema.json se cubre las espaldas en su description: “The Agent Plugins specification defines additional semantic and operational requirements.” Y §5.2 zanja la jerarquía: “The specification text is authoritative if it conflicts with the schema.”
Quedan fuera del alcance de la validación por esquema, entre otras cosas: que mcp.json declare la misma versión que plugin.json (§10.1); la contención real de rutas tras seguir symlinks (§4.1); que command sea un único token y no una cadena de shell (§7.2.1); que la url sea absoluta, sin userinfo ni fragmento y HTTPS fuera de loopback (§7.2.1); que no haya cabeceras duplicadas con distinto casing (§7.2.1); y que cada SKILL.md cumpla la especificación de Agent Skills (§7.1). El capítulo Validar un plugin automatiza todo eso.
Cargarlo en un cliente real
La especificación fija el formato en disco, no el procedimiento de instalación. Cada producto tiene el suyo, y verás ejemplos reales en el curso hermano Google Skills y en el capítulo Distribuir e instalar plugins. Lo único que la especificación garantiza (§11.1 punto 1) es que un cliente conformante “can load a plugin from a directory path”. Tu directorio es la unidad; cómo lo apunta cada CLI es asunto de ese CLI.
Errores típicos de la primera vez
| Error | Qué pasa realmente | Regla |
|---|---|---|
name con mayúsculas o -- | El plugin entero se rechaza, skills incluidos | §5.5 + §5.2 fatal |
Olvidar $schema | Campo requerido ausente: rechazo del plugin | §5.3 |
Meter mcpServers en plugin.json | Campo raíz desconocido; además §7.2.1 lo prohíbe explícitamente | §5.2, §7.2.1 |
skills/area/mi-skill/SKILL.md | El skill nunca se descubre; sin error visible | §7.1 sin recursión |
Llamar al archivo skill.md | No se descubre: el nombre debe ser exactamente SKILL.md | §7.1 |
command: "node server.js" | Entrada de servidor inválida: debe ser un único token | §7.2.1 |
command: "${PLUGIN_ROOT}/bin/x" | Entrada inválida: command solo admite nombre desnudo o ruta ./, y ahí no hay expansión de placeholders | §7.2.1, §9.2 |
cwd: "data" | Inválido: falta el prefijo ./ | §4.1.4, §7.2.1 |
PLUGIN_ROOT dentro de env | Esa entrada de servidor queda inválida | §9.2 |
mcp.json con $schema de otra versión | MCP deshabilitado para ese plugin; los skills siguen cargando | §10.1, §7.2.2 |
Campo raíz inventado, por ejemplo category | No es fatal: el cliente lo reporta e ignora, y sigue cargando | §5.2 |
Esperar que falte mcp.json sea un error | No lo es: ubicación ausente no es error | §6.2 |
Los dos últimos merecen desarrollo porque son contraintuitivos en direcciones opuestas.
Un campo raíz desconocido NO tumba el plugin. §5.2: “Clients MUST report and ignore each unknown field and MUST continue loading the plugin if the manifest otherwise satisfies this section. Clients MUST NOT assign semantics to unknown fields.” Es la única excepción no fatal junto con un extensions que no sea objeto. Todo lo demás sí es fatal. Esa asimetría es deliberada: permite que un manifiesto escrito para una versión futura no se vuelva ilegible de golpe, sin abrir la puerta a que cada cliente invente semántica propia en campos raíz.
Los fallos de componente están aislados. §11.3 punto 3: “A failure isolated to a component type, component entry, or component process MUST NOT prevent the client from loading independently valid components.” Traducido a la práctica: un skill malformado se salta y los otros cargan; una entrada de servidor MCP inválida se salta y las otras cargan; un servidor MCP que no arranca, no conecta, no autentica o no completa el handshake tampoco impide que carguen los demás componentes (§7.2.2 punto 5). El único fallo que se lleva todo por delante es el del manifiesto.
Un mapa de gravedad, para tenerlo a mano:
flowchart TD
A["Fallo detectado"] --> B{"Esta en plugin.json?"}
B -->|"Campo raiz desconocido o extensions no objeto"| C["No fatal - se reporta y se ignora"]
B -->|"Cualquier otra violacion del esquema"| D["FATAL - se rechaza el plugin completo"]
B -->|No| E{"Que componente falla?"}
E -->|"Un SKILL.md invalido"| F["Se salta ese skill - siguen los demas"]
E -->|"mcp.json invalido o de otra version"| G["MCP deshabilitado - siguen los skills"]
E -->|"Una entrada de servidor invalida"| H["Se salta ese servidor - siguen los otros"]
E -->|"Un servidor no arranca o no conecta"| I["Se reporta - sigue todo lo demas"]
Un último error de encuadre, más conceptual que técnico: buscar el manifiesto del plugin dentro de .claude-plugin/ o de .agents/. Esos directorios existen en repositorios reales, pero son convenciones de productos y catálogos concretos. .claude-plugin no aparece ni una sola vez en spec/1.0.0.md; .agents aparece exactamente tres veces, y las tres dentro del mismo ejemplo ilustrativo de §9.1 sobre dónde podría instalar un cliente el plugin devtools (/home/alex/.agents/plugins/devtools), que es una ruta elegida por ese cliente hipotético, no una ubicación normativa. El manifiesto portable vive en la raíz del plugin y se llama plugin.json. Punto.
Resumen
- El plugin mínimo útil son dos archivos:
plugin.jsonen la raíz yskills/greet/SKILL.md. El paquete es un directorio, no un archivo comprimido. plugin.jsonsolo exige$schemayname.$schemaes un selector de versión y el cliente NO DEBE (MUST NOT) descargarlo al cargar el plugin.nametiene cuatro restricciones que DEBE (MUST) cumplir: 1 a 64 caracteres, soloa-z0-9.-, extremos alfanuméricos y sin--ni...- El formato de
SKILL.mdlo define la especificación de Agent Skills, no Agent Plugins. Agent Plugins solo define dónde se descubre: hijos inmediatos deskills/con unSKILL.mdque sea archivo regular, sin búsqueda recursiva. - Cómo el cliente expone el skill al usuario o al modelo queda fuera del alcance de la especificación, por decisión explícita.
- Crecer es añadir directorios y archivos en ubicaciones fijas: un segundo skill es un directorio más; los metadatos son siete campos opcionales validados solo por tipo JSON; el MCP es un
mcp.jsonen la raíz, nunca inline enplugin.json. ${PLUGIN_ROOT}y${PLUGIN_DATA}los provee el cliente y se expanden solo enargs, en los valores deenvy encwd. Nunca encommand, nunca en las claves deenv, nunca enurlni en cabeceras.- No existe validador oficial: se usan los dos JSON Schema draft 2020-12 con
check-jsonschemaoajv-cli, sabiendo que el esquema no cubre las reglas semánticas y que el texto de la especificación prevalece sobre él. - Gravedad de los fallos: solo el manifiesto es fatal. Los fallos de componente se aíslan, y un campo raíz desconocido se reporta y se ignora sin tumbar nada.
Siguiente: plugin.json: el manifiesto al detalle