Empaquetar Agent Skills dentro de un plugin

Por: Artiko
agent-skillsplugins-de-agentesia-agentesskill-mdempaquetadodescubrimiento

Empaquetar Agent Skills dentro de un plugin

En el capítulo 3 recorriste plugin.json campo por campo y viste una regla que a mucha gente le sorprende: el manifiesto no declara componentes. No hay un array skills, no hay rutas configurables, no hay nada que apunte a dónde viven tus skills.

Eso no es un olvido. Es la decisión de diseño central del formato: los componentes se descubren en ubicaciones fijas. Este capítulo trata la primera de las dos ubicaciones que define Agent Plugins 1.0.0: el directorio skills/.

Y trata algo igual de importante: dónde termina Agent Plugins y dónde empieza Agent Skills. Son dos especificaciones distintas, publicadas por proyectos distintos, que se componen en un punto muy concreto. Confundirlas es la fuente número uno de errores al empaquetar.

Dos especificaciones que se componen

La sección §7.1 de la especificación lo dice con una frase que conviene leer despacio:

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.

Traducido: Agent Plugins responde dónde buscar dentro del paquete. Agent Skills responde qué es un skill válido. Y cómo el cliente expone ese skill al usuario o al modelo queda explícitamente fuera del alcance de Agent Plugins.

Precisión que conviene no perder: fuera de Agent Plugins no significa fuera de todo. La especificación de Agent Skills sí describe un modelo de carga progresiva en tres etapas —metadatos (name y description) al arranque, cuerpo completo de SKILL.md al activarse, y recursos de scripts/, references/ o assets/ solo cuando hacen falta—. Lo que ninguna de las dos especificaciones fija es la interfaz: si el skill aparece en un menú, si se invoca con un comando, si se lista en una paleta. Eso es decisión de cada producto.

La misma sección remite a la otra spec de forma normativa:

Agent Skills MUST conform to the Agent Skills specification. That specification is the source of truth for the SKILL.md format, frontmatter fields, and directory layout (scripts/, references/, assets/).

Los skills DEBEN (MUST) conformar la especificación Agent Skills. Esa spec es la fuente de verdad para el formato de SKILL.md, los campos del frontmatter y la disposición de directorios.

flowchart TD
    subgraph AP["Agent Plugins 1.0.0 — alcance"]
        A1["plugin.json valido"]
        A2["skills/ es la ubicacion fija"]
        A3["cada hijo inmediato con SKILL.md es un skill"]
        A4["contencion dentro del plugin root"]
        A5["que hacer si un skill no conforma"]
    end
    subgraph AS["Agent Skills — alcance"]
        B1["formato de SKILL.md"]
        B2["frontmatter: name, description y opcionales"]
        B3["scripts/ references/ assets/"]
        B4["rutas relativas dentro del skill"]
        B5["carga progresiva en tres etapas"]
    end
    subgraph FUERA["Fuera de ambas specs"]
        C1["la interfaz con la que el cliente muestra el skill"]
        C2["cuando y como el cliente decide activarlo"]
        C3["que pasa entre plugins distintos"]
    end
    A3 --> B1

La consecuencia práctica: este curso no te va a enseñar a escribir un SKILL.md. Eso es materia del curso hermano Agent Skills, donde se cubren el frontmatter, los límites de caracteres, la divulgación progresiva y cómo redactar una description que el agente sepa activar. Aquí nos concentramos en el envoltorio.

La ubicación fija: skills/

La tabla de §6.1 de la especificación tiene exactamente dos filas:

Component typeFixed locationPattern
Skillsskills/Subdirectories containing SKILL.md
MCP serversmcp.jsonJSON configuration

Y la regla que la acompaña:

Clients MUST discover each supported component type from its fixed location. plugin.json cannot override these locations or contain inline component configuration.

Los clientes DEBEN (MUST) descubrir cada tipo de componente soportado en su ubicación fija. plugin.json no puede sobrescribir esas ubicaciones ni contener configuración de componentes en línea.

Esto significa que no existe forma conforme de poner tus skills en mis-skills/, en src/skills/ ni en packages/skills/. Si un cliente encuentra skills ahí, no está siguiendo la spec. La ruta es literal, en la raíz del plugin, y se llama skills.

El apartado de decisiones de diseño explica el porqué:

Fixed root-level locations such as skills/ and mcp.json eliminate discovery indirection, alternate-source precedence, and manifest configuration that every client would otherwise need to implement.

Cero indirección, cero precedencia entre fuentes alternativas, cero configuración de descubrimiento. Todo cliente que quiera soportar skills implementa exactamente el mismo algoritmo.

Las tres reglas de descubrimiento

La regla operativa de §7.1 cabe en dos frases, pero encierra tres condiciones independientes:

Each immediate child directory containing a path named exactly SKILL.md that resolves to a regular file is treated as one skill. Clients MUST NOT recursively search deeper descendants for additional skills.

Regla 1: hijo inmediato

Solo cuentan los hijos inmediatos de skills/. Un directorio a dos niveles de profundidad no es un skill, por mucho que contenga un SKILL.md perfectamente válido.

mi-plugin/
├── plugin.json
└── skills/
    ├── desplegar/
    │   └── SKILL.md          # SI es un skill
    └── backend/
        └── migrar/
            └── SKILL.md      # NO es un skill: segundo nivel

Esta es la trampa más común al empaquetar. La intuición de quien viene de organizar código dice “agrupo por tema en subcarpetas”. Ese instinto rompe el descubrimiento en silencio: el cliente no ve migrar y tampoco tiene por qué reportar nada, porque backend/ simplemente no contiene un SKILL.md en su nivel.

Más abajo verás cómo organizar por tema sin anidar.

Regla 2: el nombre es exactamente SKILL.md

La spec dice “a path named exactly SKILL.md”. Exactamente. No skill.md, no Skill.md, no SKILL.markdown, no SKILL.MD.

En un sistema de archivos insensible a mayúsculas —macOS por defecto, Windows— un skill.md puede funcionarte localmente y romperse en el CI de Linux o en la máquina de quien instale tu plugin. Es un fallo clásico y sin diagnóstico obvio.

Regla 3: DEBE resolver a un archivo regular

“That resolves to a regular file”. Si SKILL.md es un directorio, ese hijo no es un skill. Si es un enlace simbólico, tiene que resolver a un archivo regular — y, además, cumplir la contención que veremos enseguida.

Y la prohibición: nada de búsqueda recursiva

Clients MUST NOT recursively search deeper descendants for additional skills.

Los clientes NO DEBEN (MUST NOT) buscar recursivamente en descendientes más profundos. Es una prohibición normativa, no una recomendación de rendimiento. Un cliente que descubriera skills/backend/migrar/SKILL.md estaría violando la spec.

El motivo es doble. Primero, hace el descubrimiento determinista: el conjunto de skills de un plugin es exactamente el conjunto de hijos inmediatos que califican, sin ambigüedad. Segundo, deja libre el interior de cada skill: un skill puede tener references/casos/ejemplo/SKILL.md como material de ejemplo sin que eso se convierta accidentalmente en un segundo skill.

flowchart TD
    A["Cliente abre el plugin root"] --> B{"existe skills/ ?"}
    B -->|no| C["No es error. Continua con otros componentes"]
    B -->|si, pero no es directorio| D["Tipo de componente invalido. Continua con los demas"]
    B -->|si, es directorio| E["Listar hijos inmediatos"]
    E --> F{"el hijo contiene una ruta llamada SKILL.md ?"}
    F -->|no| G["Ignorar ese hijo"]
    F -->|si| H{"SKILL.md resuelve a archivo regular ?"}
    H -->|no| G
    H -->|si| I{"resuelve dentro del plugin root ?"}
    I -->|no| J["Saltar ese skill"]
    I -->|si| K{"conforma Agent Skills ?"}
    K -->|no| L["Saltar ese skill y reportarlo"]
    K -->|si| M["Skill cargado"]

Qué pasa si skills/ no existe

La sección §6.2 cubre los dos casos degenerados y en ninguno de los dos el plugin muere.

Ausente. La regla es literal:

If a fixed component location is absent, the client MUST NOT treat that as an error.

Si una ubicación fija está ausente, el cliente NO DEBE (MUST NOT) tratarlo como error. Un plugin que solo trae un servidor MCP no tiene skills/ y es perfectamente válido. Un plugin que solo tiene plugin.json y nada más también lo es: no aporta nada, pero conforma.

Presente con el tipo de archivo equivocado.

If a fixed component location is present but does not resolve to the expected filesystem kind — for example, skills does not resolve to a directory or mcp.json does not resolve to a regular file — the client MUST treat that component type as invalid and continue loading other supported component types.

Si creas un archivo llamado skills en vez de un directorio, el cliente DEBE (MUST) tratar el tipo de componente “skills” como inválido y seguir cargando los demás. Tu mcp.json se carga igual. El plugin no se rechaza.

Esta es la filosofía de aislamiento de fallos que recorre toda la spec, y que §11.3 formaliza:

A failure isolated to a component type, component entry, or component process MUST NOT prevent the client from loading independently valid components.

Contención: el skill no puede salirse del plugin

La regla general de §4.1 aplica a todo archivo que el plugin suministre:

When a client discovers, reads, or executes a file or directory supplied by the plugin package, the filesystem-resolved path MUST remain within the filesystem-resolved plugin root. Symlinks, junctions, reparse points, and equivalent filesystem mechanisms MAY resolve to targets within the plugin root, but clients MUST reject package paths that resolve outside it.

La ruta resuelta DEBE permanecer dentro de la raíz resuelta del plugin. Los enlaces simbólicos PUEDEN (MAY) apuntar a destinos dentro del plugin root, pero el cliente DEBE rechazar rutas del paquete que resuelvan fuera.

Lo interesante para este capítulo es la frontera de fallo que la spec asigna al caso de los skills. §4.1 enumera cinco niveles, del más amplio al más estrecho, y el tercero dice:

If a discovered SKILL.md does not resolve within the plugin root, the client MUST skip that skill under §7.1.

Es decir: un SKILL.md que se escapa del plugin no rechaza el plugin. Solo se salta ese skill. Compáralo con el primer nivel de la lista —si plugin.json no resuelve dentro del plugin root, el cliente DEBE rechazar el plugin entero— y verás la escala.

flowchart LR
    A["plugin.json fuera del root"] --> A2["Rechazar el plugin completo"]
    B["skills/ fuera del root"] --> B2["Tipo de componente invalido"]
    C["SKILL.md fuera del root"] --> C2["Saltar solo ese skill"]
    D["command o cwd de MCP fuera"] --> D2["Entrada de servidor invalida"]
    E["cualquier otra ruta fuera"] --> E2["Denegar acceso a esa ruta"]

La spec llama a esto “the narrowest applicable failure boundary”: aplicar siempre la frontera de fallo más estrecha que corresponda.

Un detalle que conviene subrayar: estas reglas de contención no son un sandbox. La propia spec lo aclara:

These containment rules govern access to files supplied by the plugin package. They do not sandbox a plugin subprocess or restrict paths supplied at runtime.

Gobiernan el acceso a los archivos que el paquete suministra. No encierran un subproceso ni restringen rutas que aparezcan en tiempo de ejecución. El modelo de confianza y permisos queda para futuras versiones, como registra FUTURE_CONSIDERATIONS.md.

Skill no conforme: se salta, no se aborta

La otra regla de fallo de §7.1:

If a discovered skill does not conform to the Agent Skills specification, the client MUST skip that skill and continue loading other skills and component types. The client SHOULD report the invalid skill.

El cliente DEBE (MUST) saltar ese skill y continuar con los demás skills y tipos de componente. Y DEBERÍA (SHOULD) reportar el skill inválido.

Fíjate en el reparto de fuerza: saltar es obligatorio, reportar es recomendado. Como autor de plugins, esto te deja en una posición incómoda: un skill con un name inválido en el frontmatter puede desaparecer sin que ningún cliente te avise, si ese cliente decidió no implementar el SHOULD. Por eso la validación en tu propio pipeline no es opcional en la práctica, aunque lo sea en la letra. El capítulo 8 entra en eso.

¿Y qué cuenta como “no conforma la especificación Agent Skills”? Resumiendo lo que esa spec exige y que se cubre a fondo en el curso de Agent Skills:

  • SKILL.md DEBE contener frontmatter YAML seguido de Markdown.
  • name es obligatorio, 1–64 caracteres, minúsculas alfanuméricas y guiones, sin guion inicial ni final, sin guiones consecutivos.
  • name DEBE coincidir con el nombre del directorio padre.
  • description es obligatorio, 1–1024 caracteres, no vacío.
  • Los campos opcionales son license, compatibility, metadata y allowed-tools, este último marcado como experimental por la propia especificación de Agent Skills. Seis campos en total, ni uno más.

Esa última regla —name coincide con el directorio padre— es la que cose las dos especificaciones. Agent Plugins identifica el skill por su directorio; Agent Skills obliga a que el name del frontmatter sea ese mismo nombre. El resultado es que en un plugin conforme el nombre del directorio y el name del skill son siempre el mismo string.

skills/
└── revisar-migraciones/
    └── SKILL.md          # name: revisar-migraciones  ← obligatorio que coincida

Rutas relativas y recursos empaquetados

Dentro de un skill, la organización la define Agent Skills, no Agent Plugins. La spec de plugins solo nombra las tres convenciones al remitir a la otra: scripts/, references/, assets/.

El ejemplo literal de §7.1 es este:

skills/
└── deploy/
    ├── SKILL.md          # name: deploy
    ├── scripts/
    │   └── rollback.sh
    └── references/
        └── runbook.md

Y el layout estándar de §4.2 muestra el mismo patrón integrado en un plugin completo:

my-plugin/
├── plugin.json
├── skills/
│   └── summarize/
│       ├── SKILL.md
│       ├── scripts/
│       │   └── analyze.sh
│       └── references/
│           └── checklist.md
├── mcp.json
├── com.example.client/
│   └── hooks/
├── LICENSE
└── CHANGELOG.md

Dentro de SKILL.md, las referencias a esos archivos se escriben relativas a la raíz del skill, no a la raíz del plugin:

Consulta la [checklist](references/checklist.md) antes de resumir.

Para analizar el documento, ejecuta:
scripts/analyze.sh

Esto merece una advertencia porque es donde la gente se equivoca al migrar un skill suelto a un plugin: la raíz de referencia no cambia. Un skill que funcionaba instalado suelto sigue funcionando dentro de un plugin sin tocar una sola ruta, porque el directorio del skill es el mismo en ambos casos. Lo que cambia es dónde vive ese directorio, y eso lo resuelve el cliente.

Los placeholders de plugin no son para los skills

Aquí hay una asimetría que conviene tener clarísima. Agent Plugins define dos variables, PLUGIN_ROOT y PLUGIN_DATA, y dos placeholders correspondientes, ${PLUGIN_ROOT} y ${PLUGIN_DATA}. Pero §9.2 acota dónde se expanden:

Expansion applies to every string element of args, every string value in env, and the cwd string. It does not apply to env keys, command, or fixed component locations.

Esos tres campos —args, env, cwd— son campos de mcp.json. La expansión de placeholders no toca SKILL.md ni ningún archivo de un skill. Si escribes ${PLUGIN_ROOT}/datos.csv dentro de tu SKILL.md, Agent Plugins no define ninguna sustitución para eso.

Del mismo modo, §9.1 obliga a proveer esas variables solo en un contexto:

Clients that launch plugin subprocesses (i.e., stdio MCP servers) MUST provide PLUGIN_ROOT and PLUGIN_DATA in each subprocess environment.

“Plugin subprocesses (i.e., stdio MCP servers)”. El paréntesis es de la spec y es restrictivo. Agent Plugins 1.0.0 no define un entorno de ejecución para los scripts de un skill: ni qué variables reciben, ni cuál es su directorio de trabajo, ni si el cliente los ejecuta siquiera. Eso queda del lado del cliente y del formato de skills.

La conclusión práctica: escribe los scripts de tus skills de forma que funcionen con rutas relativas a su propia ubicación, sin depender de variables de entorno que la spec no te garantiza. El capítulo 5 cubre PLUGIN_ROOT y PLUGIN_DATA a fondo, donde sí aplican.

Nombres duplicados y colisiones

Esta es la pregunta que aparece siempre y merece una respuesta en tres niveles, porque la spec responde solo uno.

Dentro de un mismo plugin: imposible por construcción

Dos skills del mismo plugin no pueden llamarse igual, y no porque la spec lo prohíba sino porque el sistema de archivos lo impide: son directorios hermanos dentro de skills/, y no puede haber dos hermanos con el mismo nombre. Sumado a la regla de Agent Skills de que name coincide con el directorio padre, la unicidad dentro del plugin es automática.

No hace falta ninguna regla normativa para esto. El diseño la hace innecesaria.

Entre plugins distintos: fuera del alcance de la spec

Aquí hay que ser preciso: Agent Plugins 1.0.0 no dice nada sobre qué pasa si dos plugins instalados aportan un skill con el mismo name. No hay reglas de precedencia, ni de prefijado automático, ni de espacio de nombres por plugin, ni de conflicto fatal. No es que la spec lo resuelva de una forma que no te guste: sencillamente no lo aborda.

Y es coherente con su alcance declarado. §7.1 dice que la spec no define “how clients expose skills to users or models”. Si el cliente no está obligado a exponer los skills de ninguna forma concreta, tampoco puede estar obligado a resolver colisiones en esa exposición.

Tampoco aparece en FUTURE_CONSIDERATIONS.md como área pendiente: ese documento lista permisos, procedencia, secretos, controles empresariales, auditoría, resolución de dependencias y validación, pero no colisión de nombres entre plugins.

Lo único adyacente que la spec sí resuelve es la colisión entre clientes, con los namespaces de dominio invertido de §8. Y lo explica así:

Reverse-domain identifiers provide a decentralized convention for avoiding collisions without requiring a central client-name registry.

Ese mecanismo es para extensions y para los directorios de extensión. No se aplica a los nombres de skill.

Qué hacer como autor

Como la spec no te protege, la protección es de diseño. Tres tácticas que funcionan:

  1. Prefija por dominio del plugin. Si tu plugin se llama acme-deploy, nombra los skills acme-deploy-rollback en vez de rollback. Feo, pero determinista.
  2. Usa nombres específicos, no genéricos. revisar-migraciones-postgres colisiona mucho menos que revisar. Además ayuda al modelo a decidir cuándo activarlo, que es el criterio que manda en la optimización de descriptions.
  3. Un plugin, un dominio. Si tu plugin cubre un solo producto o una sola área, tus nombres tienden a la unicidad de forma natural.

Y en el lado del consumo: si instalas dos plugins que compiten por el mismo nombre, el comportamiento depende del cliente. Puede ganar el primero, el último, puede reportarlo o puede no hacer nada. Verifica en la documentación de tu cliente concreto. El capítulo 6 profundiza en lo que la spec sí obliga al cliente durante la carga.

Un plugin con varios skills organizados por tema

Volvamos a la trampa de la regla del hijo inmediato. Quieres agrupar quince skills por tema y no puedes anidar. ¿Cómo se organiza entonces?

La respuesta es: el tema va en el nombre, no en el árbol. El árbol es plano por obligación; la agrupación es una convención de nomenclatura.

Este es un plugin de plataforma con nueve skills en tres temas:

plataforma-tools/
├── plugin.json
├── skills/
│   ├── db-migrar/
│   │   ├── SKILL.md
│   │   ├── scripts/
│   │   │   └── plan.sh
│   │   └── references/
│   │       └── convenciones.md
│   ├── db-revisar-indices/
│   │   ├── SKILL.md
│   │   └── references/
│   │       └── heuristicas.md
│   ├── db-restaurar-backup/
│   │   ├── SKILL.md
│   │   └── scripts/
│   │       └── verificar.sh
│   ├── deploy-canary/
│   │   ├── SKILL.md
│   │   └── references/
│   │       └── runbook.md
│   ├── deploy-rollback/
│   │   ├── SKILL.md
│   │   └── scripts/
│   │       └── rollback.sh
│   ├── deploy-verificar-salud/
│   │   └── SKILL.md
│   ├── obs-consultar-logs/
│   │   └── SKILL.md
│   ├── obs-crear-alerta/
│   │   ├── SKILL.md
│   │   └── assets/
│   │       └── alerta.tmpl.yaml
│   └── obs-diagnosticar-latencia/
│       ├── SKILL.md
│       └── references/
│           └── arbol-decision.md
├── mcp.json
├── LICENSE
└── CHANGELOG.md

Nueve hijos inmediatos, nueve skills descubiertos. Los prefijos db-, deploy- y obs- agrupan visualmente y en el listado ordenado del sistema de archivos, sin romper el descubrimiento.

Su plugin.json no menciona ni uno solo de esos skills:

{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
  "name": "plataforma-tools",
  "version": "2.1.0",
  "description": "Skills de base de datos, despliegue y observabilidad para la plataforma interna.",
  "author": {
    "name": "Equipo Plataforma",
    "url": "https://example.com/plataforma"
  },
  "license": "Apache-2.0",
  "keywords": ["database", "deploy", "observability"]
}

Eso es todo el acoplamiento que existe entre el manifiesto y los skills: ninguno. Añades un directorio a skills/ y el skill aparece; lo borras y desaparece. No hay un índice que mantener sincronizado ni que se pueda desincronizar.

flowchart LR
    A["Autor crea skills/obs-nueva/SKILL.md"] --> B["No toca plugin.json"]
    B --> C["Cliente lista hijos inmediatos de skills/"]
    C --> D["Descubre obs-nueva automaticamente"]

Contraste: el mismo plugin mal organizado

Para que la diferencia quede grabada, este es el mismo contenido con la intuición equivocada:

plataforma-tools/
├── plugin.json
└── skills/
    ├── db/
    │   ├── migrar/
    │   │   └── SKILL.md
    │   └── revisar-indices/
    │       └── SKILL.md
    ├── deploy/
    │   ├── canary/
    │   │   └── SKILL.md
    │   └── rollback/
    │       └── SKILL.md
    └── obs/
        └── consultar-logs/
            └── SKILL.md

Un cliente conforme descubre aquí cero skills. Ni db/, ni deploy/, ni obs/ contienen un SKILL.md en su nivel, así que ninguno califica. Y la prohibición de búsqueda recursiva impide que el cliente baje un nivel más. El plugin carga sin error —recuerda: ubicación presente y del tipo correcto, simplemente sin hijos que califiquen— y no aporta absolutamente nada.

Peor aún: no hay ninguna regla que obligue al cliente a avisarte. El SHOULD de reportar aplica a skills descubiertos que no conforman Agent Skills; aquí no se descubrió ninguno, así que no hay nada que reportar.

El plugin mínimo con un skill

El README.md del proyecto presenta el caso más pequeño posible, y sirve como plantilla mental:

hello-plugin/
├── plugin.json
└── skills/
    └── greet/
        └── SKILL.md

Con este plugin.json:

{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
  "name": "hello-plugin"
}

Y este SKILL.md:

---
name: greet
description: Greet the user and offer help.
---

Greet the user and offer help.

Tres archivos. El README cierra con la aclaración de siempre:

A client that supports skills can load the plugin by reading plugin.json and discovering skills/greet/SKILL.md. How the client exposes the skill to users or models is outside the Agent Plugins specification.

Ten presente que el README es material no normativo: se presenta a sí mismo como “a non-normative introduction”. Los ejemplos son ilustrativos; la autoridad está en spec/1.0.0.md.

Clientes solo-skills

Una consecuencia de §11.2 que afecta directamente a cómo empaquetas:

A client is not required to support every component type. For example, a skills-only client can conform without supporting MCP servers, provided it satisfies all applicable requirements.

Un cliente solo-skills es plenamente conforme. Y §11.3 obliga a que ignore lo que no soporta:

Clients MUST ignore unsupported component types.

Así que si tu plugin trae skills y un servidor MCP, un cliente solo-skills cargará los skills e ignorará mcp.json sin quejarse. La spec añade que eso no es un error:

Clients MAY report partially unsupported plugins, but lack of support for a component type, MCP transport, or client extension is not itself an error.

Para ti, esto tiene una implicación de diseño: si un skill depende funcionalmente del servidor MCP del mismo plugin, ese skill se romperá en un cliente solo-skills. La spec no ofrece ningún mecanismo para declarar esa dependencia. Lo que sí tienes es el campo compatibility del frontmatter de Agent Skills, pensado exactamente para señalar requisitos de entorno. No es un mecanismo de aplicación, es documentación para el agente y para quien lea el skill, pero es lo que hay.

En un catálogo real como el de Google Skills verás esa convivencia: skills que funcionan por sí solas y skills que asumen un servidor MCP configurado.

Lista de verificación al empaquetar

Antes de publicar, repasa esto:

  • El directorio se llama exactamente skills, en minúsculas, en la raíz del plugin.
  • Cada skill es un hijo inmediato de skills/. Ningún skill anidado a dos niveles.
  • Cada skill tiene un archivo llamado exactamente SKILL.md, con esa capitalización.
  • SKILL.md es un archivo regular, no un directorio.
  • El name del frontmatter coincide con el nombre del directorio padre.
  • Ningún enlace simbólico dentro del plugin resuelve fuera del plugin root.
  • Las rutas dentro de SKILL.md son relativas a la raíz del skill; Agent Skills recomienda además mantener las referencias a un solo nivel de profundidad.
  • Ningún archivo de skill depende de ${PLUGIN_ROOT} ni ${PLUGIN_DATA}.
  • Los nombres de skill son suficientemente específicos como para no colisionar con otros plugins.
  • Cada SKILL.md valida contra la especificación Agent Skills, no solo contra tu intuición.

Resumen

  • Agent Plugins y Agent Skills son dos especificaciones que se componen: la primera define dónde se descubren los skills, la segunda define qué es un skill válido y cómo se cargan progresivamente. Que el cliente exponga el skill al usuario o al modelo de una forma u otra queda fuera del alcance de Agent Plugins, y la interfaz concreta no la fija ninguna de las dos.
  • Los skills DEBEN (MUST) conformar la especificación Agent Skills, que es la fuente de verdad para el formato de SKILL.md, el frontmatter y los directorios scripts/, references/ y assets/.
  • La ubicación fija es skills/ en la raíz del plugin. plugin.json no puede sobrescribirla ni declarar componentes en línea.
  • Cada hijo inmediato de skills/ que contenga una ruta llamada exactamente SKILL.md que resuelva a un archivo regular es un skill. Los clientes NO DEBEN (MUST NOT) buscar recursivamente más profundo.
  • Si skills/ está ausente, el cliente NO DEBE tratarlo como error. Si está presente pero no es un directorio, ese tipo de componente es inválido y los demás siguen cargándose.
  • Un SKILL.md que no resuelve dentro del plugin root hace que se salte solo ese skill; es la frontera de fallo más estrecha aplicable.
  • Un skill que no conforma Agent Skills DEBE saltarse y el cliente DEBERÍA (SHOULD) reportarlo. Saltar es obligatorio, avisar es solo recomendado.
  • Las rutas dentro de un skill son relativas a la raíz del skill, no a la del plugin. Un skill suelto migra a un plugin sin tocar rutas.
  • ${PLUGIN_ROOT} y ${PLUGIN_DATA} se expanden únicamente en args, env y cwd de mcp.json. No aplican a los archivos de un skill, y la spec no define entorno de ejecución para los scripts de un skill.
  • Los nombres duplicados dentro de un plugin son imposibles por construcción. Entre plugins distintos, la spec no define nada: es comportamiento del cliente. Protégete con nombres específicos o prefijados.
  • Para agrupar por tema, usa prefijos en el nombre, nunca subdirectorios: anidar hace que el cliente no descubra ningún skill, en silencio y sin error.
  • Un cliente solo-skills es conforme e ignorará tu mcp.json. Si un skill depende del servidor MCP del plugin, dilo en compatibility.

Siguiente: Servidores MCP dentro de un plugin