Skip to main content
Glama

skill-mcp

Un servidor MCP que sirve un directorio de Agent Skills. Apúntalo a skills; las lista, entrega sus instrucciones y sus archivos empaquetados, y ejecuta solo los scripts que una skill declara.

Es un adaptador genérico, no un conjunto curado: las skills son contenido que lee, y la misma compilación sirve para aquello a lo que se apunte.

npx @chrischall/skill-mcp                       # serves the example skill bundled here
SKILLS_DIR=~/my-skills npx @chrischall/skill-mcp

El paquete npm es @chrischall/skill-mcp (skill-mcp sin scope está ocupado por otra persona en npm). Todo lo demás — el repositorio, el binario, la identidad de registro io.github.chrischall/skill-mcp — no tiene scope.

Qué es una skill

Un directorio que contiene SKILL.md: frontmatter YAML (name, description y opcionalmente el bloque mcp-host: más abajo) seguido de instrucciones, más los archivos a los que esas instrucciones hagan referencia. Bajo cada raíz se encuentran tres estructuras:

<root>/SKILL.md              # the root IS one skill
<root>/<name>/SKILL.md       # a directory of skills
<root>/skills/<name>/SKILL.md

Una skill se sirve bajo el nombre de su DIRECTORIO, nunca bajo el name de su propio frontmatter (un name de frontmatter que discrepa se notifica y por lo demás se ignora; cuando la raíz misma es la skill, el directorio raíz le da nombre). Es una regla de seguridad, no de orden: la concesión del propietario nombra una skill, así que un paquete que pudiera elegir su propio nombre podría reclamar el de su vecina y recibir el script y las variables concedidos a la vecina. Por la misma razón, dos directorios que de verdad contribuyen con un mismo nombre — solo posible entre dos raíces — son ambos rechazados y notificados, en lugar de que uno gane por orden de escaneo.

Las herramientas

herramienta

argumentos

devuelve

skill_list

todas las skills encontradas: nombre, descripción, cuándo usarla, número de archivos, si declara scripts ejecutables y exactamente cuáles; además de problems, para que una lista vacía nunca sea un misterio

skill_load

name

el cuerpo de SKILL.md textualmente, más un manifiesto de los archivos del paquete. Los archivos referenciados no se incrustan — para eso está skill_file

skill_file

name, path

un archivo del directorio de esa skill: texto, o base64 con su tipo de medio. Como máximo 1 MiB, con truncated: true en lugar de un corte silencioso

skill_run

name, script, args[], confirm

{exitCode, stdout, stderr, truncated, durationMs}

skill_file acepta una ruta. El diseño de referencia especifica un lote paths[] (8 rutas por llamada, 1 MiB por entrada, 4 MiB por llamada, una ruta mala falla solo en su propio slot); publicar la forma singular es un aplazamiento deliberado, no un descuido, y esos tres límites son los que un cambio posterior de procesamiento por lotes debe respetar. Lee los topes como límites del propio heap de este servidor: limitan la asignación, no solo la respuesta, porque un hijo alojado tiene un límite duro de datos de 256 MiB y un paquete puede ser más grande que eso.

Cada skill se registra también como prompt MCP (su cuerpo es el mensaje) y cada archivo empaquetado como recurso (skill://<name>/<path>), porque un cliente que soporta esas superficies presenta una skill mejor de lo que lo hace una llamada a herramienta. Es una segunda puerta, nunca la única: cuando este servidor se ejecuta en mcp-host y un registro restringe enabledTools, prompts/list y resources/list vuelven vacías y el handshake deja de anunciar esas capacidades — así que las herramientas sostienen toda la experiencia.

El descubrimiento informa, nunca se calla

Cualquier cosa que impida que un directorio sea servido aparece en problems de skill_list, con la ruta y el motivo: falta SKILL.md, frontmatter que no se puede parsear, un nombre que dos directorios reclaman (ambos rechazados), un script declarado que no está en el paquete, un symlink que sale de la raíz o de una skill, un nombre de archivo al que las herramientas de lectura no pudieron dirigirse. Una skill mala solo se perjudica a sí misma y nunca al listado, y no hay un tercer resultado en el que algo se descarte en silencio — un directorio de skill con symlink se sirve cuando permanece dentro de la raíz (así que skills/foo -> ../shared/foo funciona) y se notifica cuando no.

La valla de ejecución

skill_run ejecuta código de terceros. Cada regla siguiente restringe qué código se ejecuta y qué se le entrega; cada una tiene su propia prueba.

  • Solo un script que la skill DECLARA. No "cualquier archivo bajo scripts/", no "cualquier cosa ejecutable". Una ruta no declarada se rechaza, diciendo que debe ser declarada y enumerando las que lo están.

  • Solo dentro del propio directorio de esa skill. La ruta se comprueba primero como cadena (segmentos simples; sin / inicial, sin . ni .., sin barra invertida, sin escape de porcentaje, sin NUL) y luego de nuevo tras la resolución: la ruta real, con los symlinks seguidos, debe seguir estando dentro del directorio real de la skill, y debe ser un archivo regular. Ambas comprobaciones, porque una comprobación de cadena por sí sola no detecta un symlink plantado dentro del paquete, y una comprobación resuelta por sí sola acepta formas que nunca deberían haberse unido. La misma disciplina rige skill_file: una lectura fuera del directorio de una skill es, en el mejor de los casos, el paquete de otra skill.

  • Un array argv, nunca una cadena de shell. spawn con shell: false, sin interpolación, sin sh -c. Los argumentos se pasan tal cual.

  • Un intérprete de un conjunto cerrado, nombrado por la declaración — nunca inferido de la extensión y nunca tomado del shebang del propio archivo, ya que un archivo que puede elegir su propio intérprete ya ha elegido su propio programa. v1 ejecuta node y nada más (ver Lo que v1 no puede ejecutar).

  • Acotado, y la llamada siempre retorna. Un timeout de tiempo real (60 s por defecto, anulable por script, con un techo duro de 300 s), 1 MiB capturado por flujo con truncated: true en lugar de un corte silencioso, y un solo skill_run a la vez. Al agotarse el timeout se señala al grupo de procesos, lo que alcanza al script y a cualquier hijo que haya permanecido en su grupo. No alcanza a un nieto que se haya separado a un grupo propio, y ese nieto también mantiene abiertos los pipes de stdio — así que la ejecución se resuelve con la salida del proceso más un drenaje corto, bajo un plazo duro, en lugar de esperar a que los pipes se cierren. Eso es lo que garantiza que la llamada a la herramienta retorne dentro de su presupuesto y libere el bloqueo de uno a la vez; no es una garantía de que un nieto deliberadamente separado esté muerto. Acotar eso es trabajo del nivel (un uid sin privilegios, prlimit NPROC y una máquina que se detiene), no de este adaptador.

  • Una lista blanca de entorno. Un script recibe PATH, HOME, LANG, TZ, TMPDIR, MCP_DATA_DIR cuando el host ha establecido una, y exactamente las variables que ese script pidió y que el propietario concedió — nunca el entorno propio de este servidor. La mitad fija refleja INSTALL_ALLOWLIST de mcp-host (packages/runner-node/src/spawn-env.ts), por la razón que da ese archivo: una constante del host que una declaración alojada no puede ampliar ni en un nombre.

  • Una salida distinta de cero es un resultado normal y notificado — el código de salida, stdout y stderr vuelven todos. Nunca es una excepción que pierda la salida.

  • skill_run está sujeto a confirmación. Sin confirm: true no inicia ningún proceso y devuelve una vista previa de dry-run de exactamente lo que se ejecutaría: el intérprete, el argv, el directorio de trabajo, el timeout y los nombres de las variables que recibiría el script.

Por qué la compuerta de confirmación es general

La convención de la flota pone una compuerta a las herramientas mutadoras. Que un script dado mute algo es algo que este servidor no puede saber: nunca lee un script, y deliberadamente no lo analiza — un veredicto generado por máquina sobre el código de otro se acepta de una manera en que no se acepta la declaración de un autor. Por tanto, los efectos desconocidos se tratan como mutadores.

El suavizado obvio — permitir que una skill marque un script como de solo lectura y omitir la compuerta para él — se rechaza porque es circular: el mismo autor escribió el script y la frase que lo describe, así que un «read-only» autodeclarado no autoriza nada. Eso deja una compuerta general. Su coste es un viaje de ida y vuelta extra en un helper de solo lectura; su beneficio es que la vista previa es el único lugar donde quien llama ve la llamada exacta antes de que ocurra nada.

Lo que la valla NO garantiza

Un script declarado sigue siendo código arbitrario. Estas reglas restringen qué código se ejecuta y con qué; ninguna hace que el código sea seguro. Un script que permitas puede leer todo el árbol de skills, gastar la CPU de la máquina y enviar lo que tenga a cualquier lugar que su red permita. Los topes de salida de skill_run son truncamiento, no confidencialidad: nada censura el stdout de un script, y nada podría.

Esto no es un sandbox. Ejecútalo contra skills que hayas leído, o ejecútalo en algún sitio que lo cerque — bajo mcp-host eso significa el nivel aislado (fly-machine): una microVM por registro, un uid sin privilegios, límites prlimit y nftables con denegación por defecto y una lista blanca de salida declarada. Este servidor es una restricción encima de esa valla, no un sustituto de una.

Lo que v1 no puede ejecutar

El conjunto de intérpretes es node, una sola entrada, y es una decisión meditada más que un descuido: la imagen de runner de mcp-host es Node + git + tar + util-linux + nftables, sin python3, curl ni jq, mientras que las skills reales son abrumadoramente Python (70 .py frente a 1 .js en anthropics/skills en 3b3fad96).

Así que una skill que declara un script Python es notificada por skill_list en unavailableScripts, con el intérprete y el conjunto de este despliegue nombrados, y skill_run la rechaza con las mismas palabras. Sus instrucciones siguen sirviendo — una skill solo de instrucciones es una skill útil, y la mayoría de las skills publicadas son exactamente eso. Un intérprete fijado es un seguimiento que llega como dependencia, nunca como un cambio de imagen.

El bloque de declaración mcp-host:

Opcional, dentro del frontmatter de SKILL.md:

---
name: weather
description: Forecasts and geocoding.
mcp-host:
  version: 1
  run:
    - script: scripts/forecast.js
      interpreter: node
      env: [WEATHER_API_KEY]     # variables this SCRIPT asks for
      timeout: 30                # seconds; clamped to 300
  env:                           # fields proposed for the SERVER's environment
    - name: WEATHER_API_KEY
      secret: true
  egress: [api.weather.example]  # hosts this skill reaches; a proposal
---

Una declaración restringe; nunca concede. El autor de los scripts también escribió el bloque que los nombra, así que nada en él es una autorización — dice qué archivos son puntos de entrada y qué quiere cada uno. Lo que hace que un script sea ejecutable, y que una variable llegue a él, es que otra persona lo acepte.

Se lee estrictamente: esquema central YAML 1.2, anclas y alias rechazados, un tope de 64 KiB, una versión MAJOR desconocida rechazada en bloque, claves desconocidas ignoradas y notificadas por nombre, y un bloque que no parsea se notifica con la posición del parser en lugar de tratarse como ausente. Un bloque roto le cuesta a una skill sus scripts, nunca sus instrucciones, y nunca el resto del listado.

Configuración

variable

significado

MCP_SKILLS_PATH

raíces de slots separadas por :, inyectadas por el runner de mcp-host. Gana sobre todo

SKILLS_DIR

lo mismo para uso local. Se lee solo cuando MCP_SKILLS_PATH no está definido

MCP_SKILL_RUN

JSON opcional [{skill, script, env?}] — la concesión del propietario. Solo restringe

(ninguno definido)

el directorio skills/ propio de este paquete

MCP_SKILL_RUN merece el énfasis. Cuando está presente, lo que puede ejecutarse es la declaración intersecada con él — una fila que nombre un script que la skill no declaró no concede nada (y se notifica), y una fila que nombre una variable que el script no pidió no concede nada. No hay ninguna forma de escribirlo que haga ejecutable algo que una skill no declaró, que es lo que hace seguro leerlo de un entorno que también lleva las variables propias de un registro.

Cuando está ausente, el valor predeterminado depende de si un host inició este proceso hijo, y la mitad alojada es de cierre ante fallos.

  • Alojado — cualquier variable que el runner de mcp-host inyecta está presente (MCP_SKILLS_PATH, MCP_HOST_METER_FILE, MCP_DATA_DIR, MCP_BLOB_BASE_URL): no se concede nada y no se ejecuta nada. Las instrucciones y los archivos de cada skill se siguen sirviendo — eso es un conector funcional y útil. La razón es que un proceso hijo contiene un único entorno con todas las credenciales que el propietario estableció, así que una skill cuyo frontmatter nombrara la variable de su vecino recibiría la credencial del vecino sin que nadie hubiera decidido entregársela. Deliberadamente no se basa únicamente en MCP_SKILLS_PATH: mcp-host aún no inyecta esa variable, así que hoy el único canal alojado es SKILLS_DIR en el env simple de un registro, y eso no debe caer en el valor predeterminado abierto. La comprobación del marcador solo puede mover el valor predeterminado en la dirección de cierre ante fallos.

  • Independiente — ningún marcador inyectado en absoluto: la declaración de la propia skill se mantiene. Nada está inyectando nada, y la persona que apuntó el servidor a un directorio es el propietario.

skill_list informa de qué caso es (grantFrom, más un grantNote en el alojado) y enumera los scripts declarados pero no concedidos de una skill, de modo que «no se ejecuta nada» nunca sea indistinguible de «no se declaró nada».

Postura de confianza

  • El código de este servidor es del operador; las skills son tuyas. Lee un conjunto fijo de directorios que se le entregan, no obtiene nada, no instala nada y no tiene ninguna herramienta que acepte una ruta fuera del directorio de la propia skill.

  • No examina nada. No hay insignia, ni lista de permitidos de editores, ni escaneo. Las instrucciones de una skill y sus scripts son exactamente tan fiables como quien los escribió.

  • Una lectura se trata con el mismo peligro que una ejecución, porque el directorio del que lee está junto a todo lo demás en la máquina.

  • No guarda nada en caché ni almacena nada. El catálogo se escanea una vez al arrancar y se mantiene en memoria; no se escribe ningún archivo en ningún sitio.

Alojamiento en mcp-host

mint.yaml en la raíz del repositorio indica cómo quiere registrarse este MCP. Cuatro cosas que deliberadamente no propone, porque solo un registro puede decidirlas:

  • El runtime — y no podría, por regla. Un manifiesto nunca puede nombrarlo (docs/MINT-MANIFEST.md §5): en qué nivel aterriza un registro lo decide quién lo solicita, no el paquete, ya que un archivo que pudiera pedir fly-shared sería el paquete de un desconocido solicitando un asiento en la propia máquina del operador. Un servidor de skills alojado pertenece al nivel aislado (fly-machine) con una política de egreso declarada, y esa es la decisión que debe tomar el registro.

  • Las propias skills. Llegan como una dependencia fijada (un github-archive que nombra un repositorio y un commit exacto) y aterrizan en un espacio de solo lectura que el runner nombra mediante MCP_SKILLS_PATH. Este paquete no puede saber cuáles lleva un registro determinado.

  • state.dataDir. El adaptador no necesita persistencia. Actívalo cuando las skills de un registro tengan scripts que necesiten un lugar donde escribir — el espacio es de solo lectura, así que MCP_DATA_DIR (con él activado) o TMPDIR (sin él) es donde va la salida de un script — y da la razón allí.

  • La lista de permitidos de egreso real. mint.yaml propone allow: [], que es lo que el propio adaptador necesita: no alcanza nada. Los hosts que necesita un registro son los que declaran sus SKILLS, mostrados en la vista previa con quién los declaró y aceptados por el propietario. En el nivel aislado, un host que no está en la lista aparece como un HTTP 403 del proxy de bucle local o como un simple timeout — los dos son indistinguibles desde dentro de un script, así que skill_run adjunta una nota que lo dice siempre que una llamada falla en una máquina que parece cercada.

Reducir enabledTools a [skill_list, skill_load, skill_file] es el interruptor no ejecutable, aplicado en la puerta de enlace en lugar de aquí — una declaración más contundente que el rechazo de skill_run por parte de este servidor, y sacrifica por completo las superficies de prompt y de recursos.

Desarrollo

npm install
npm run build      # tsc → dist/, esbuild → dist/bundle.js
npm test           # tsc typecheck + vitest

Este proyecto fue desarrollado y es mantenido por IA. Úsalo bajo tu propio criterio.

-
license - not tested
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.

  • A registry of 5,900+ peer-authored skills any MCP agent can search and load on demand.

  • Hosted AgentLux MCP server for marketplace, identity, creator, services, and social flows.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/chrischall/skill-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server