Validar un plugin: JSON Schema y automatización
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 plugin | Identificador canónico del esquema |
|---|---|
plugin.json | https://agent-plugins.org/schemas/1.0.0/plugin.schema.json |
mcp.json | https://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 spec | Sección | Por qué el esquema no la ve |
|---|---|---|
command DEBE ser un token ejecutable único, no una línea de shell | §7.2.1 | El esquema solo pide minLength: 1 |
Un command plugin-relativo DEBE empezar por ./ y quedar dentro del plugin root | §4.1, §7.2.1 | Requiere resolver el sistema de archivos |
Un plugin que empaqueta un ejecutable DEBE usar command plugin-relativo | §7.2.1 | Requiere mirar el contenido del paquete |
cwd resuelto DEBE seguir dentro del plugin root o del directorio de datos | §7.2.1 | El 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.1 | El esquema solo pide minLength: 1 |
| Nombres de header repetidos con distinto casing invalidan la entrada | §7.2.1 | JSON Schema no compara claves entre sí |
Los plugins NO DEBEN embeber credenciales en env ni en headers | §7.2.1, §9.2 | Un 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.1 | Son dos archivos distintos |
Los skills se descubren solo en hijos inmediatos de skills/ con SKILL.md | §7.1 | Es 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
| Mensaje | Qué pasa de verdad | Arreglo |
|---|---|---|
no schema with key or ref "https://json-schema.org/draft/2020-12/schema" | Ajv arrancó en draft-07 | Añade --spec=draft2020 |
data must have required property '$schema' | Falta $schema; es obligatorio en plugin.json y en mcp.json | No se puede omitir “en desarrollo” |
data/$schema must be equal to constant | El valor no es el identificador canónico exacto de 1.0.0 | Có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.json | Campo top-level desconocido | Es 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 object | author como string tipo "Nombre <email>" | author es un objeto con name, email y url opcionales |
data/extensions/<ns> must be object | Un namespace con valor no objeto | Cada 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.json | Un servidor no encaja en ninguna de las tres variantes cerradas | Valida 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 stdio | Mezclaste campos de dos variantes | Un campo de otra variante invalida esa entrada; elige una |
data/mcpServers/<s>/env must NOT be valid | Definiste PLUGIN_ROOT o PLUGIN_DATA en env | Prohibido 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 reconocido | Solo ./algo, ${PLUGIN_ROOT} o ${PLUGIN_DATA}, con o sin subruta |
Best Match: 'url' is a required property sobre un servidor stdio | check-jsonschema eligió la rama HTTP para el informe | Lee 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 encommand. 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.jsonexiste en la raíz, es el único manifiesto y su$schemaes exactamentehttps://agent-plugins.org/schemas/1.0.0/plugin.schema.json. -
namecumple las cuatro restricciones de §5.5: 1 a 64 caracteres; soloa-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
extensionsbajo un namespace de dominio inverso. -
versionsigue Semantic Versioning ylicensees un identificador SPDX. Ambos son RECOMENDADOS (RECOMMENDED), no obligatorios: §5.4 dice que los clientes NO DEBEN (MUST NOT) rechazar un manifiesto solo porqueversionno sea SemVer válido olicenseno sea un identificador SPDX. - Cada skill es un hijo inmediato de
skills/con unSKILL.mdreal 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.jsonestá en la raíz, su$schemadeclara la misma versión queplugin.json, y no tiene más campos top-level que$schemaymcpServers. - Cada
commandes un token único, nombre desnudo o ruta./, y todo ejecutable empaquetado se referencia con ruta plugin-relativa. - Ningún
cwdescapa 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
envni enheaders: v1 no define ningún mecanismo portable de credenciales. - El plugin no depende de heredar variables del entorno base: solo
PLUGIN_ROOTyPLUGIN_DATAestán garantizados. - Los dos comandos de validación pasan y
scripts/checks.shsale con0. - 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-clinecesitas--spec=draft2020; concheck-jsonschemano 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
oneOfdemcp.jsongenera errores ruidosos. Valida cada servidor por separado contra su variante, o quédate con elBest Deep Matchde check-jsonschema. - Nueve reglas normativas no son expresables en JSON Schema —
commandcomo 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