An MCP server that serves a directory of Agent Skills — lists them, hands out their instructions and bundled files, and runs only the scripts a skill declares.
- ✓Open-source license (MIT)
- ✓Actively maintained (<30d)
- ✓Clear description
- ✓Documented (README)
claude mcp add skill-mcp -- npx -y @chrischall/skill-mcp{
"mcpServers": {
"skill-mcp": {
"command": "npx",
"args": ["-y", "@chrischall/skill-mcp"]
}
}
}Resumen de MCP Servers
# skill-mcp
An MCP server that serves a directory of **Agent Skills**. Point it at skills;
it lists them, hands out their instructions and their bundled files, and runs
**only the scripts a skill declares**.
It is a generic adapter, not a curated set: the skills are content it reads, and
the same build serves whatever it is pointed at.
```bash
npx @chrischall/skill-mcp # serves the example skill bundled here
SKILLS_DIR=~/my-skills npx @chrischall/skill-mcp
```
The npm package is **`@chrischall/skill-mcp`** (unscoped `skill-mcp` is taken by
someone else on npm). Everything else — the repo, the binary, the registry
identity `io.github.chrischall/skill-mcp` — is unscoped.
## What a skill is
A directory holding `SKILL.md`: YAML frontmatter (`name`, `description`, and
optionally the `mcp-host:` block below) followed by instructions, plus whatever
files those instructions refer to. Three layouts are found under each root:
```
<root>/SKILL.md # the root IS one skill
<root>/<name>/SKILL.md # a directory of skills
<root>/skills/<name>/SKILL.md
```
**A skill is served under its DIRECTORY name**, never under the `name` in its
own frontmatter (a frontmatter `name` that disagrees is reported and otherwise
ignored; where the root itself is the skill, the root directory names it). That
is a security rule, not a tidiness one: the owner's grant names a skill, so a
bundle that could choose its own name could claim its neighbour's and be handed
the neighbour's granted script and granted variables. For the same reason, two
directories that really do contribute one name — only possible across two roots
— have **both** refused and reported, rather than one of them winning by scan
order.
## The tools
| tool | arguments | returns |
| --- | --- | --- |
| `skill_list` | — | every skill found: name, description, when to use it, file count, whether it declares runnable scripts and **exactly which**; plus `problems`, so an empty list is never a mystery |
| `skill_load` | `name` | the SKILL.md body **verbatim**, plus a manifest of the bundle's files. Referenced files are not inlined — that is what `skill_file` is for |
| `skill_file` | `name`, `path` | one file from that skill's directory: text, or base64 with its media type. At most 1 MiB, `truncated: true` rather than a silent cut |
| `skill_run` | `name`, `script`, `args[]`, `confirm` | `{exitCode, stdout, stderr, truncated, durationMs}` |
`skill_file` takes **one** path. The design of record specifies a `paths[]`
batch (8 paths per call, 1 MiB per entry, 4 MiB per call, one bad path failing
only its own slot); shipping the singular form is a deliberate deferral, not an
oversight, and those three bounds are what a later batching change has to
honour. Read the caps as bounds on this server's own heap: they cap the
**allocation**, not only the answer, because a hosted child has a hard 256 MiB
data limit and a bundle may be larger than that.
Each skill is **also** registered as an MCP prompt (its body is the message) and
each bundled file as a resource (`skill://<name>/<path>`), because a client that
supports those surfaces presents a skill better than a tool call does. It is a
second door, never the only one: when this server runs on
[mcp-host](https://github.com/chrischall/mcp-host) and a registration narrows
`enabledTools`, `prompts/list` and `resources/list` come back empty and the
handshake stops advertising those capabilities — so **the tools carry the whole
experience**.
## Discovery reports, it never goes quiet
Anything that keeps a directory from being served comes back in `skill_list`'s
`problems`, with the path and the reason: no `SKILL.md`, frontmatter that will
not parse, a name two directories both claim (both refused), a declared script that is not in
the bundle, a symlink leading out of the root or out of a skill, a filename the
read tools could not address. One bad skill costs itself and never the listing,
and there is no third outcome where something is dropped in silence — a
symlinked skill directory is **served** when it stays inside the root (so
`skills/foo -> ../shared/foo` works) and **reported** when it does not.
## The execution fence
`skill_run` executes third-party code. Every rule below narrows **which** code
runs and **what it is handed**; each has its own test.
- **Only a script the skill DECLARES.** Not "any file under `scripts/`", not
"anything executable". An undeclared path is refused, saying it must be
declared and listing the ones that are.
- **Only inside that skill's own directory.** The path is checked as a string
first (plain segments; no leading `/`, no `.` or `..`, no backslash, no
percent escape, no NUL) and then again after resolution: the **real** path,
with symlinks followed, must still be inside the skill's real directory, and
it must be a regular file. Both checks, because a string check alone misses a
symlink planted inside the bundle and a resolved check alone accepts shapes
that should never have been joined. The same discipline governs `skill_file`:
a read out of a skill directory is another skill's bundle at best.
- **An argv array, never a shell string.** `spawn` with `shell: false`, no
interpolation, no `sh -c`. Arguments are passed through verbatim.
- **An interpreter from a closed set**, named by the declaration — never
inferred from the extension and never taken from the file's own shebang, since
a file that can choose its own interpreter has already chosen its own program.
**v1 runs `node` and nothing else** (see *What v1 cannot run*).
- **Bounded, and the call always returns.** A wall-clock timeout (60 s default,
per-script override, hard 300 s ceiling), 1 MiB captured per stream with
`truncated: true` rather than a silent cut, and one `skill_run` at a time. On
timeout the process **group** is signalled, which reaches the script and any
child that stayed in its group. It does **not** reach a grandchild that
detached into a group of its own, and such a grandchild also holds the stdio
pipes open — so the run settles on the process exiting plus a short drain,
under a hard deadline, rather than on the pipes closing. That is what
guarantees the tool call returns within its budget and frees the
one-at-a-time lock; it is not a guarantee that a deliberately detached
grandchild is dead. Bounding *that* is the tier's job (an unprivileged uid,
`prlimit` NPROC, and a machine that stops), not this adapter's.
- **An env allowlist.** A script gets `PATH`, `HOME`, `LANG`, `TZ`, `TMPDIR`,
`MCP_DATA_DIR` when the host set one, and **exactly the variables that script
asked for and the owner granted** — never this server's own environment. The
fixed half mirrors mcp-host's `INSTALL_ALLOWLIST`
(`packages/runner-node/src/spawn-env.ts`), for the reason that file gives: a
host constant a hosted declaration cannot widen by one name.
- **A non-zero exit is a normal, reported outcome** — exit code, stdout and
stderr all come back. It is never an exception that loses the output.
- **`skill_run` is confirm-gated.** Without `confirm: true` it starts no process
and returns a dry-run preview of exactly what would run: the interpreter, the
argv, the working directory, the timeout, and the **names** of the variables
the script would be handed.
### Why the confirm gate is blanket
The fleet convention gates mutating tools. Whether a given script mutates
anything is something this server cannot know: it never reads a script, and it
deliberately does not analyse one — a machine-generated verdict about somebody
else's code gets trusted in a way an author's declaration does not. Unknown
effects are therefore treated as mutating.
The obvious softening — let a skill mark a script read-only and skip the gate
for it — is refused because it is circular: the same author wrote the script and
the sentence describing it, so a self-declared "read-only" authorizes nothing.
That leaves a blanket gate. Its cost is one extra round-trip on a read-only
helper; its benefit is that the preview is the one place a caller sees the exact
call before any of it happens.
### What the fence does NOT buy
**A declared script is still arbitrary code.** These rules narrow which code
runs and with what; none of them makes the code safe. A script you allow can
read the whole skills tree, spend the machine's CPU, and send whatever it holds
anywhere its network permits. `skill_run`'s output caps are truncation, not
confidentiality: nothing redacts a script's stdout, and nothing could.
**This is not a sandbox.** Run it against skills you have read, or run it
somewhere that fences it — under mcp-host that means the isolated tier
(`fly-machine`): a microVM per registration, an unprivileged uid, `prlimit`
bounds, and nftables default-deny with a declared egress allowlist. This server
is a narrowing on top of such a fence, not a replacement for one.
## What v1 cannot run
The set of interpreters is `node`, one entry, and that is a measured decision
rather than an oversight: mcp-host's runner image is Node + git + tar +
util-linux + nftables, with no `python3`, `curl` or `jq`, while real skills are
overwhelmingly Python (70 `.py` against 1 `.js` in `anthropics/skills` at
`3b3fad96`).
So a skill declaring a Python script is reported by `skill_list` under
`unavailableScripts`, with the interpreter and this deployment's set named, and
`skill_run` refuses it in the same words. **Its instructions still serve** —
an instructions-only skill is a useful skill, and most published skills are
exactly that. A pinned interpreter is a follow-up that arrives as a dependency,
never as an image change.
## The `mcp-host:` declaration block
Optional, inside SKILL.md's frontmatter:
```yaml
---
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 forLo que la gente pregunta sobre skill-mcp
¿Qué es chrischall/skill-mcp?
+
chrischall/skill-mcp es mcp servers para el ecosistema de Claude AI. An MCP server that serves a directory of Agent Skills — lists them, hands out their instructions and bundled files, and runs only the scripts a skill declares. Tiene 0 estrellas en GitHub y su última actualización registrada es del 2026-09-10.
¿Cómo se instala skill-mcp?
+
Puedes instalar skill-mcp clonando el repositorio (https://github.com/chrischall/skill-mcp) o siguiendo las instrucciones del README en GitHub. ClaudeWave también te ofrece bloques de instalación rápida en esta misma página.
¿Es seguro usar chrischall/skill-mcp?
+
Nuestro agente de seguridad ha analizado chrischall/skill-mcp y le ha asignado un Trust Score de 87/100 (tier: Trusted). Revisa el desglose completo de comprobaciones superadas y flags en esta página.
¿Quién mantiene chrischall/skill-mcp?
+
chrischall/skill-mcp es mantenido por chrischall. La última actividad registrada en GitHub es del 2026-09-10, con 0 issues abiertos.
¿Hay alternativas a skill-mcp?
+
Sí. En ClaudeWave puedes explorar mcp servers similares en /categories/mcp, ordenados por popularidad o actividad reciente.
Despliega skill-mcp en tu cloud
Lleva este repo a producción en minutos. Cada plataforma genera su propio entorno con variables de entorno editables.
¿Mantienes este repo? Añade un badge a tu README
Pega el badge en tu README de GitHub para mostrar que está auditado por ClaudeWave. Cada badge enlaza de vuelta a esta página y muestra el Trust Score actual.
[](https://claudewave.com/repo/chrischall-skill-mcp)<a href="https://claudewave.com/repo/chrischall-skill-mcp"><img src="https://claudewave.com/api/badge/chrischall-skill-mcp" alt="Featured on ClaudeWave: chrischall/skill-mcp" width="320" height="64" /></a>Más MCP Servers
Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.
User-friendly AI Interface (Supports Ollama, OpenAI API, ...)
An open-source AI agent that brings the power of Gemini directly into your terminal.
Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface
The fastest path to AI-powered full stack observability, even for lean teams.
🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl!