Validar un plugin: JSON Schema y automatización

Por: Artiko
agent-skillsplugins-de-agentesia-agentesjson-schemavalidaciongithub-actionsci-cd

Validar un plugin: JSON Schema y automatización

La especificación Agent Plugins 1.0.0 publica dos esquemas legibles por máquina bajo https://agent-plugins.org/schemas/1.0.0/:

Documento del pluginIdentificador canónico del esquema
plugin.jsonhttps://agent-plugins.org/schemas/1.0.0/plugin.schema.json
mcp.jsonhttps://agent-plugins.org/schemas/1.0.0/mcp.schema.json

Ambos declaran el meta-esquema https://json-schema.org/draft/2020-12/schema. Ese detalle determina qué validador puedes usar y con qué opción, y es la causa del primer error que te vas a encontrar.

Dos advertencias antes de escribir un comando, ambas textuales de las fuentes.

La spec no define ninguna herramienta de validación. FUTURE_CONSIDERATIONS.md, que es explícitamente no normativo, lo dice sin rodeos: “No test harness or validation tool is specified.” Todo lo que montes aquí es tooling tuyo; no existe un agent-plugins lint oficial.

El texto manda sobre el esquema. La spec repite la misma frase en §5.2 y en §7.2.1: “The specification text is authoritative if it conflicts with the schema.” Un plugin que pasa el validador puede seguir siendo inválido. Por eso la validación real tiene capas.

flowchart TD
    A["Capa 1 - el archivo es JSON bien formado"] --> B["Capa 2 - conforma el JSON Schema 1.0.0"]
    B --> C["Capa 3 - cumple las reglas de prosa que el schema no expresa"]
    C --> D["Capa 4 - el cliente concreto lo carga y lo ejecuta"]
    B -.- E["plugin.schema.json y mcp.schema.json"]
    C -.- F["contencion de rutas, command como token unico, HTTPS, versiones coherentes"]
    D -.- G["fuera del alcance de la especificacion"]

Este capítulo cubre y automatiza las capas 2 y 3. La capa 4 depende de cada cliente y la especificación la deja fuera de su alcance a propósito.

Los esquemas viven en tu repositorio

La especificación prohíbe al cliente descargar un esquema mientras carga un plugin. §5.2 y §7.2.1 usan la misma frase: “Clients MUST NOT retrieve a schema while loading a plugin.” El identificador canónico es una clave de selección, no una URL que se resuelva en tiempo de carga.

Tu banco de trabajo no es un cliente, así que puedes descargarlos. Pero conviene la misma disciplina: descárgalos una vez, versiónalos y valida contra la copia local. Tu CI deja de depender de la red, y el día que aparezca una versión nueva de la spec el cambio queda registrado en un commit.

mkdir -p schemas/1.0.0
for f in plugin mcp; do
  curl -fsSL -o "schemas/1.0.0/$f.schema.json" \
    "https://agent-plugins.org/schemas/1.0.0/$f.schema.json"
done

El repositorio del plugin queda así:

release-notes/
├── plugin.json
├── mcp.json
├── skills/
│   └── changelog/
│       └── SKILL.md
├── schemas/
│   └── 1.0.0/
│       ├── plugin.schema.json
│       └── mcp.schema.json
└── scripts/checks.sh

Un detalle de licencias: LICENSE.md del repositorio oficial separa el material. El texto de la especificación es CC-BY-4.0, pero los esquemas son Apache 2.0, porque cuentan como “schemas, source code, scripts, and other software material”.

Validar plugin.json con ajv-cli

ajv-cli es el frontend de línea de comandos de Ajv. Requiere Node.js.

npx --yes ajv-cli@5 validate \
  --spec=draft2020 \
  -s schemas/1.0.0/plugin.schema.json \
  -d plugin.json

Con este manifiesto:

{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
  "name": "release-notes",
  "version": "1.2.0",
  "description": "Genera notas de version a partir del historial de git.",
  "license": "MIT",
  "keywords": ["release", "git"]
}

la salida es una línea, plugin.json valid, y el código de salida es 0. Si omites --spec=draft2020, Ajv arranca en draft-07 y ni siquiera llega a mirar tus datos:

schema schemas/1.0.0/plugin.schema.json is invalid
error: no schema with key or ref "https://json-schema.org/draft/2020-12/schema"

Es el error número uno con estos esquemas: no es tu manifiesto, es el validador apuntando a la versión equivocada de JSON Schema.

Ver todos los errores, no solo el primero

Por defecto Ajv se detiene en el primero; --all-errors revisa el manifiesto entero de una pasada. Con este archivo deliberadamente roto:

{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
  "name": "Release-Notes",
  "author": "Artiko <[email protected]>",
  "main": "index.js"
}

el comando:

npx --yes ajv-cli@5 validate --spec=draft2020 --all-errors --errors=text \
  -s schemas/1.0.0/plugin.schema.json -d plugin.json

devuelve:

plugin.json invalid
data must NOT have additional properties, data/name must match pattern "^(?!.*(?:--|\.\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$", data/author must be object

Tres defectos con lectura normativa distinta. main es un campo top-level desconocido: el esquema es cerrado (additionalProperties: false), pero §5.2 dice que el cliente DEBE (MUST) reportarlo e ignorarlo y DEBE seguir cargando el plugin. Es el único caso, junto con un extensions que no sea objeto, en que una violación del esquema no es fatal. Release-Notes viola §5.5 — solo minúsculas, dígitos, guiones y puntos — y author como string viola §5.4, que lo define como objeto que PUEDE (MAY) contener solo name, email y url. Esos dos sí son fatales.

Con --errors=json obtienes la estructura completa: el campo keyword permite distinguir por script el caso no fatal, additionalProperties con instancePath vacío, del resto.

Validar mcp.json con ajv-cli

Mismo comando, otro esquema:

npx --yes ajv-cli@5 validate --spec=draft2020 \
  -s schemas/1.0.0/mcp.schema.json -d mcp.json

Con la configuración canónica de la spec la salida es mcp.json valid. El problema aparece cuando algo falla, porque #/$defs/server es un oneOf de tres variantes cerradas: stdio, streamable-http y sse. Un solo defecto genera errores de las tres ramas.

{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
  "mcpServers": {
    "malcwd": { "type": "stdio", "command": "node", "cwd": "data" }
  }
}

Un único defecto real produce una avalancha de diez errores en la que el fragmento útil se pierde entre repeticiones de las tres ramas. Con --errors=text Ajv los emite todos separados por comas en una sola línea; aquí van partidos para que se lean:

mcp.json invalid
data/mcpServers/malcwd/cwd must match pattern "^(?:\./|\$\{PLUGIN_ROOT\}(?:/|$)|...", ...
data/mcpServers/malcwd must have required property 'url', ...
data/mcpServers/malcwd must NOT have additional properties, ...
data/mcpServers/malcwd/type must be equal to constant, ...
data/mcpServers/malcwd must match exactly one schema in oneOf

Técnica: validar cada servidor contra su variante

La propia especificación anticipa el problema en §7.2.1: “The schema exposes #/$defs/server so that clients can validate each server independently and preserve the failure boundaries in §7.2.2.” Puedes ir un paso más allá y apuntar a la variante que declara el type, con un esquema puente de tres líneas:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.local/stdio-only.schema.json",
  "$ref": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json#/$defs/stdioServer"
}

Extrae el servidor y valídalo contra él, pasando el esquema oficial como referencia con -r:

jq '.mcpServers.malcwd' mcp.json > /tmp/srv.json
npx --yes ajv-cli@5 validate --spec=draft2020 --all-errors --errors=text \
  -s stdio-only.schema.json -r schemas/1.0.0/mcp.schema.json -d /tmp/srv.json

El ruido desaparece:

/tmp/srv.json invalid
data/cwd must match pattern "^(?:\./|\$\{PLUGIN_ROOT\}(?:/|$)|\$\{PLUGIN_DATA\}(?:/|$))"

Eso es exactamente §7.2.1: cwd DEBE (MUST) tener una de tres formas — ruta plugin-relativa que empiece por ./, exactamente ${PLUGIN_ROOT} o algo bajo ${PLUGIN_ROOT}/, o exactamente ${PLUGIN_DATA} o algo bajo ${PLUGIN_DATA}/. data no es ninguna.

Alternativa en Python: check-jsonschema

Si no quieres Node.js en el pipeline, check-jsonschema cubre lo mismo y detecta el draft 2020-12 solo, sin flag extra. Se ejecuta con uvx check-jsonschema --schemafile schemas/1.0.0/plugin.schema.json plugin.json o se instala con pipx. Sobre el manifiesto roto de antes:

Schema validation errors were encountered.
  plugin.json::$.name: 'Release-Notes' does not match '^(?!.*(?:--|\\.\\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$'
  plugin.json::$.author: 'Artiko <[email protected]>' is not of type 'object'
  plugin.json::$: Additional properties are not allowed ('main' was unexpected)

Usa rutas JSONPath ($.name) en lugar de JSON Pointer, muestra todos los errores por defecto y, cuando todo pasa, imprime ok -- validation done. Para mcp.json maneja el oneOf mejor que Ajv: agrupa por servidor y elige la rama más probable.

  mcp.json::$.mcpServers.malcwd: {'type': 'stdio', 'command': 'node', 'cwd': 'data'} is not valid under any of the given schemas
  Underlying errors caused this.

  Best Match:
    $.mcpServers.malcwd: 'url' is a required property
  Best Deep Match:
    $.mcpServers.malcwd.cwd: 'data' does not match '^(?:\\./|\\$\\{PLUGIN_ROOT\\}(?:/|$)|...'

  5 other errors were produced. Use '--verbose' to see all errors.

Regla de lectura: ignora el Best Match y quédate con el Best Deep Match. El Best Match casi siempre dice 'url' is a required property, que es lo que falla al comparar tu servidor stdio contra la rama streamableHttpServer. El Best Deep Match apunta al defecto real dentro de la variante correcta.

Lo que el esquema no puede validar

Aquí está el valor de la capa 3: estas reglas son normativas y ningún JSON Schema las expresa.

Regla de la specSecciónPor qué el esquema no la ve
command DEBE ser un token ejecutable único, no una línea de shell§7.2.1El esquema solo pide minLength: 1
Un command plugin-relativo DEBE empezar por ./ y quedar dentro del plugin root§4.1, §7.2.1Requiere resolver el sistema de archivos
Un plugin que empaqueta un ejecutable DEBE usar command plugin-relativo§7.2.1Requiere mirar el contenido del paquete
cwd resuelto DEBE seguir dentro del plugin root o del directorio de datos§7.2.1El pattern valida la forma, no el resultado
Un endpoint no loopback DEBE usar HTTPS; la url NO DEBE llevar user-info ni fragmento§7.2.1El esquema solo pide minLength: 1
Nombres de header repetidos con distinto casing invalidan la entrada§7.2.1JSON Schema no compara claves entre sí
Los plugins NO DEBEN embeber credenciales en env ni en headers§7.2.1, §9.2Un secreto es un string como cualquier otro
Si mcp.json existe, la versión de su $schema DEBE coincidir con la de plugin.json§10.1Son dos archivos distintos
Los skills se descubren solo en hijos inmediatos de skills/ con SKILL.md§7.1Es una regla del sistema de archivos

La coherencia de versiones merece énfasis porque su consecuencia es quirúrgica: §10.1 dice que el desajuste “makes the MCP configuration invalid under §7.2.2 but does not invalidate other component types”. El cliente deshabilita MCP para ese plugin y sigue cargando los skills. Nadie te avisa a gritos; tus servidores MCP simplemente dejan de existir.

Script de comprobaciones semánticas

Cubre las reglas anteriores que sí son automatizables; solo necesita jq.

#!/usr/bin/env bash
# scripts/checks.sh - comprobaciones que el JSON Schema no puede expresar.
set -uo pipefail
root="${1:-.}"
fail=0
err()  { printf 'ERROR  %s\n' "$1"; fail=1; }
warn() { printf 'AVISO  %s\n' "$1"; }
sver() { sed -E 's#.*/schemas/([^/]+)/.*#\1#'; }

[ -f "$root/plugin.json" ] || { err "falta plugin.json en el plugin root (4.1)"; exit 1; }
ver=$(jq -r '."$schema" // ""' "$root/plugin.json" | sver)

if [ -f "$root/mcp.json" ]; then
  mver=$(jq -r '."$schema" // ""' "$root/mcp.json" | sver)
  [ "$ver" = "$mver" ] || err "mcp.json apunta a $mver y plugin.json a $ver (10.1)"

  while IFS=$'\t' read -r name cmd; do
    case "$cmd" in
      *[[:space:]]*) err "$name: command debe ser un token unico, no una linea de shell (7.2.1)" ;;
      /*|../*)       err "$name: command absoluto o fuera del plugin root (4.1)" ;;
      ./*)           [ -e "$root/${cmd#./}" ] || warn "$name: command $cmd no existe en el paquete" ;;
    esac
    case "$cmd" in *'${PLUGIN_'*) err "$name: command no admite expansion de placeholders (9.2)" ;; esac
  done < <(jq -r '.mcpServers // {} | to_entries[]
    | select(.value.type=="stdio") | "\(.key)\t\(.value.command // "")"' "$root/mcp.json")

  while IFS=$'\t' read -r name url; do
    case "$url" in
      https://*) ;;
      http://localhost|http://localhost[:/]*|http://127.*|http://\[::1\]*) ;;
      http://*)  err "$name: un endpoint no loopback debe usar HTTPS (7.2.1)" ;;
      *)         err "$name: url debe ser absoluta HTTP o HTTPS (7.2.1)" ;;
    esac
    case "$url" in *@*|*\#*) err "$name: url con user information o fragmento (7.2.1)" ;; esac
  done < <(jq -r '.mcpServers // {} | to_entries[]
    | select(.value.type=="streamable-http" or .value.type=="sse")
    | "\(.key)\t\(.value.url // "")"' "$root/mcp.json")

  jq -r '.mcpServers // {} | to_entries[] | .key as $s
    | ((.value.env // {}) + (.value.headers // {}) | keys[]) | "\($s): \(.)"' "$root/mcp.json" \
    | grep -Ei '(token|secret|passwo?rd|api[_-]?key|authorization)' \
    | while read -r hit; do warn "$hit parece un secreto; la spec prohibe empaquetarlos (7.2.1, 9.2)"; done
fi

for d in "$root"/skills/*/; do
  [ -d "$d" ] && [ ! -f "$d/SKILL.md" ] && \
    warn "skills/$(basename "$d") no tiene SKILL.md y no se descubre (7.1)"
done

[ "$fail" -eq 0 ] && printf 'OK     comprobaciones semanticas superadas\n'
exit "$fail"

Sobre un plugin con argumentos incrustados en command y un endpoint remoto en HTTP plano:

ERROR  notas: command debe ser un token unico, no una linea de shell (7.2.1)
ERROR  remoto: un endpoint no loopback debe usar HTTPS (7.2.1)

Los avisos no rompen el build a propósito. Un SKILL.md ausente no es un error del plugin: §6.2 dice que una ubicación fija ausente NO DEBE (MUST NOT) tratarse como error, y §7.1 solo dice que ese directorio no se descubre como skill. La validez del propio SKILL.md la define la Agent Skills specification, no Agent Plugins: esta spec define cómo se descubren los skills, no su formato.

Hook de pre-commit

La vía sin dependencias es un .githooks/pre-commit que corra los dos comandos de ajv y scripts/checks.sh, activado con git config core.hooksPath .githooks. La vía con más recorrido es el framework pre-commit, que trae un hook oficial de check-jsonschema:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/python-jsonschema/check-jsonschema
    rev: 0.37.4
    hooks:
      - id: check-jsonschema
        name: Validar plugin.json contra Agent Plugins 1.0.0
        files: ^plugin\.json$
        args: ["--schemafile", "schemas/1.0.0/plugin.schema.json"]
      - id: check-jsonschema
        name: Validar mcp.json contra Agent Plugins 1.0.0
        files: ^mcp\.json$
        args: ["--schemafile", "schemas/1.0.0/mcp.schema.json"]

  - repo: local
    hooks:
      - id: agent-plugins-semantica
        name: Comprobaciones semanticas de Agent Plugins
        entry: scripts/checks.sh
        language: script
        pass_filenames: false
        files: ^(plugin\.json|mcp\.json|skills/.*)$

Se instala con uvx pre-commit install y se prueba con uvx pre-commit run --all-files. El id es check-jsonschema en las dos entradas porque es un hook genérico: cambia files y --schemafile; name solo hace legible el log.

CI en GitHub Actions

Workflow completo: valida con los dos validadores, ejecuta las comprobaciones semánticas y vigila que tus copias locales de los esquemas no hayan derivado.

# .github/workflows/validate.yml
name: Validar plugin

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

permissions:
  contents: read

jobs:
  schema:
    name: JSON Schema 1.0.0
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: "22" }

      - name: Instalar ajv-cli
        run: npm install --no-save ajv@8 ajv-cli@5

      - name: Validar plugin.json
        run: |
          npx ajv validate --spec=draft2020 --all-errors --errors=text \
            -s schemas/1.0.0/plugin.schema.json -d plugin.json

      - name: Validar mcp.json
        if: hashFiles('mcp.json') != ''
        run: |
          npx ajv validate --spec=draft2020 --all-errors --errors=text \
            -s schemas/1.0.0/mcp.schema.json -d mcp.json

      - name: Segunda opinion con check-jsonschema
        run: pipx run check-jsonschema
          --schemafile schemas/1.0.0/plugin.schema.json plugin.json

  semantica:
    name: Reglas no expresables en el schema
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: bash scripts/checks.sh .

  deriva:
    name: Los esquemas locales siguen siendo los publicados
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: |
          for f in plugin mcp; do
            curl -fsSL -o "/tmp/$f.json" \
              "https://agent-plugins.org/schemas/1.0.0/$f.schema.json"
            diff -u "schemas/1.0.0/$f.schema.json" "/tmp/$f.json"
          done

El tercer job merece explicación. §10.1 establece que “Published canonical schema identifiers MUST NOT be reassigned to different schema contents”: un identificador publicado nunca cambia de contenido, así que ese diff debería dar siempre vacío. Si un día falla, o cambió algo que no debía, o tu copia local se corrompió. Es el único job que necesita red: los otros dos validan contra la copia versionada, igual que un cliente.

Un beneficio gratuito de $schema: como es obligatorio y su valor es el identificador canónico, cualquier editor con soporte de JSON Schema te da autocompletado y errores en vivo mientras escribes el manifiesto. Es justamente el motivo por el que la especificación eligió esquemas cerrados; el AGENTS.md del repositorio lo declara como principio de diseño: “Use closed schemas where typo detection and completion are valuable”.

Interpretar los errores más comunes

MensajeQué pasa de verdadArreglo
no schema with key or ref "https://json-schema.org/draft/2020-12/schema"Ajv arrancó en draft-07Añade --spec=draft2020
data must have required property '$schema'Falta $schema; es obligatorio en plugin.json y en mcp.jsonNo se puede omitir “en desarrollo”
data/$schema must be equal to constantEl valor no es el identificador canónico exacto de 1.0.0Cópialo literal; §5.2 dice que su valor DEBE ser esa cadena
data/name must match pattern "^(?!.*(?:--|\.\.))..."Mayúsculas, _, @, /, guion o punto en un extremo, o -- o ..Solo a-z, 0-9, - y ., con extremos alfanuméricos
data must NOT have additional properties en la raíz de plugin.jsonCampo top-level desconocidoEs no fatal según §5.2, pero el esquema lo marca; muévelo a extensions bajo un namespace de dominio inverso
data/author must be objectauthor como string tipo "Nombre <email>"author es un objeto con name, email y url opcionales
data/extensions/<ns> must be objectUn namespace con valor no objetoCada miembro de extensions DEBE (MUST) ser un objeto; esto sí es fatal, a diferencia de un extensions que no sea objeto. Su contenido no lo valida el esquema portable
must match exactly one schema in oneOf en mcp.jsonUn servidor no encaja en ninguna de las tres variantes cerradasValida ese servidor solo, contra #/$defs/stdioServer, #/$defs/streamableHttpServer o #/$defs/sseServer
data/mcpServers/<s> must NOT have additional properties con un url en un servidor stdioMezclaste campos de dos variantesUn campo de otra variante invalida esa entrada; elige una
data/mcpServers/<s>/env must NOT be validDefiniste PLUGIN_ROOT o PLUGIN_DATA en envProhibido por §9.2; el cliente los provee y los fija después del overlay de env
data/mcpServers/<s>/cwd must match pattern "^(?:\./|..."cwd absoluto, relativo sin ./, o con un placeholder no reconocidoSolo ./algo, ${PLUGIN_ROOT} o ${PLUGIN_DATA}, con o sin subruta
Best Match: 'url' is a required property sobre un servidor stdiocheck-jsonschema eligió la rama HTTP para el informeLee el Best Deep Match, no el Best Match

Dos errores frecuentes que ningún validador de esquema detecta, y que el script cubre:

  • "command": "node server.js". Pasa el esquema porque es un string no vacío. Viola §7.2.1: DEBE ser un token único. Lo correcto es "command": "node" con "args": ["server.js"].
  • "command": "${PLUGIN_ROOT}/bin/tool". Pasa el esquema. Viola §7.2.1 y §9.2: el cliente NO DEBE expandir placeholders en command. Lo correcto es "command": "./bin/tool".

Lista de verificación antes de publicar

Trabajada sobre el Apéndice A de la especificación, que es no normativo y lo advierte: “when it conflicts with the spec text above, the spec governs”.

  • plugin.json existe en la raíz, es el único manifiesto y su $schema es exactamente https://agent-plugins.org/schemas/1.0.0/plugin.schema.json.
  • name cumple las cuatro restricciones de §5.5: 1 a 64 caracteres; solo a-z, 0-9, - y .; extremos alfanuméricos; sin -- ni ...
  • No hay campos top-level fuera de los diez permitidos; lo específico de cliente vive en extensions bajo un namespace de dominio inverso.
  • version sigue Semantic Versioning y license es un identificador SPDX. Ambos son RECOMENDADOS (RECOMMENDED), no obligatorios: §5.4 dice que los clientes NO DEBEN (MUST NOT) rechazar un manifiesto solo porque version no sea SemVer válido o license no sea un identificador SPDX.
  • Cada skill es un hijo inmediato de skills/ con un SKILL.md real que conforma la Agent Skills specification; no hay skills anidados más abajo esperando descubrirse.
  • No declaraste componentes que v1 no define: la especificación define exactamente dos tipos, skills y servidores MCP.
  • mcp.json está en la raíz, su $schema declara la misma versión que plugin.json, y no tiene más campos top-level que $schema y mcpServers.
  • Cada command es un token único, nombre desnudo o ruta ./, y todo ejecutable empaquetado se referencia con ruta plugin-relativa.
  • Ningún cwd escapa del plugin root ni del directorio de datos tras resolverse, y ninguna ruta del paquete sale del plugin root, ni siquiera vía symlink.
  • Todo endpoint no loopback usa HTTPS, sin user-info ni fragmento en la URL, y ningún nombre de header se repite con distinto casing.
  • No hay credenciales ni secretos en env ni en headers: v1 no define ningún mecanismo portable de credenciales.
  • El plugin no depende de heredar variables del entorno base: solo PLUGIN_ROOT y PLUGIN_DATA están garantizados.
  • Los dos comandos de validación pasan y scripts/checks.sh sale con 0.
  • Cargaste el plugin en un cliente real y viste sus componentes. Cómo el cliente los expone al usuario o al modelo queda fuera del alcance de la especificación, así que esta es la única comprobación no portable de la lista.

Para un banco de pruebas con plugins publicados de verdad sobre el que correr estos mismos comandos, el catálogo del curso de Google Skills da material real.

Resumen

  • Los dos esquemas de 1.0.0 son draft 2020-12. Con ajv-cli necesitas --spec=draft2020; con check-jsonschema no hace falta flag.
  • Descarga los esquemas una vez y versiónalos. Un cliente conformante NO DEBE descargar un esquema al cargar un plugin, y tu CI tampoco debería depender de la red para validar.
  • El texto de la especificación manda sobre el esquema: pasar el validador es necesario, no suficiente.
  • El oneOf de mcp.json genera errores ruidosos. Valida cada servidor por separado contra su variante, o quédate con el Best Deep Match de check-jsonschema.
  • Nueve reglas normativas no son expresables en JSON Schema — command como token único, contención de rutas, HTTPS remoto, headers duplicados por casing, ausencia de secretos, coincidencia de versión entre los dos archivos — y hay que automatizarlas aparte.
  • Un desajuste de versión entre los dos archivos no rompe el plugin: deshabilita MCP y deja los skills cargando. Es un fallo silencioso, y por eso conviene automatizarlo primero.
  • Monta la validación en tres puntos: el editor gracias a $schema, el hook de pre-commit, y un workflow de CI que además vigile la deriva de tus copias locales.

Siguiente: Distribuir e instalar plugins