Spec Driven Development
MCP Server that facilitates spec-driven development workflows, not just Vibe Coding.
作者提供的说明(英文原文)
Serves a directory of Agent Skills over MCP: list, load, read files, run declared scripts
这三项永远不会合并成一个分数:意见、测量和人气是不同的东西。
我们还没有为它写通俗的介绍。下面是我们从注册中心读取到的事实。
选择你使用的 AI 应用,然后复制设置。
SKILLS_DIR你需要
你用的是哪个应用?
在上方选择你的应用。我们会记住它,下次按钮会显示“安装到 …”。
打开 Claude Desktop → Settings → Developer → Edit Config,把下面的内容粘贴到 claude_desktop_config.json
{
"mcpServers": {
"skill": {
"command": "npx",
"args": [
"-y",
"@chrischall/skill-mcp"
],
"env": {
"SKILLS_DIR": "your-value"
}
}
}
}在终端中运行这条命令
claude mcp add skill --env SKILLS_DIR=your-value -- npx -y @chrischall/skill-mcp运行前请先填写空着的值。
粘贴到项目中的 .cursor/mcp.json,或 ~/.cursor/mcp.json
{
"mcpServers": {
"skill": {
"command": "npx",
"args": [
"-y",
"@chrischall/skill-mcp"
],
"env": {
"SKILLS_DIR": "your-value"
}
}
}
}浏览器会先询问是否打开应用,然后应用会显示设置并请你确认。密钥的值会留空,由你自己填写。
粘贴到项目中的 .vscode/mcp.json
{
"servers": {
"skill": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@chrischall/skill-mcp"
],
"env": {
"SKILLS_DIR": "your-value"
}
}
}
}浏览器会先询问是否打开应用,然后应用会显示设置并请你确认。密钥的值会留空,由你自己填写。
这个服务器在你的电脑上运行,而 ChatGPT 只能连接互联网上的服务器。请选择其他应用。
你的选择通过 Cookie 保存在这台设备上,无需账户。
SKILLS_DIR env这些设置是根据作者在注册中心的条目生成的。如果 README 的写法不同,请以 README 为准。
它在你的电脑上运行;安装后,工具列表会显示在你的 AI 应用中。
我们检查的是任何人都能看到的信息;我们没有阅读代码。不适用的检查项不计入总数。 · 检查于 2026年11月7日
注册名称已通过 GitHub 账户 chrischall 验证,该账户拥有此仓库
我们还没有从 GitHub 读取这个仓库
我们还没有从 GitHub 读取这个仓库
它在你的电脑上运行,没有互联网端点
在你的电脑上运行,改用本地设置
它在你的电脑上运行;我们只能读取互联网端点的工具列表
与注册中心里这个分类中所有活跃的服务器比较。只有当发布者拥有该仓库时,星标数才计入。
它在 1 项适用的基础检查中通过了 1 项。我们没有阅读代码。在添加密钥或令牌之前,请先看看这个服务器要求哪些权限,并使用只具备所需权限的密钥。
注册中心没有列出必需的密钥,不过有些服务器会在首次使用时要求你登录。
它在你的电脑上运行。你的 AI 应用会在需要时启动它,数据不会经过作者的服务器(除非它自己调用在线服务)。
GitHub 仓库最后更新于–,最新版本(1.2.3)于9天前进入注册中心。
支持 MCP 的应用,例如 Claude Desktop、Claude Code、Cursor 和 VS Code。你的电脑上还需要有 Node.js。
同一分类中有:Spec Driven Development, Remote Desktop Commander, Aiterm MCP
同一分类中其他作者的服务器,按星标数排序。
如何看卡片:★ GitHub 星标i三大支柱:评价 · 检查 · 使用情况i更新 = 最新版本发布到注册中心的时间i
MCP Server that facilitates spec-driven development workflows, not just Vibe Coding.
Remote Desktop Commander: use files, terminal and processes on your own computers from any AI app.
Persistent terminals and one launcher for Claude, Codex, Grok, Cursor, and Antigravity harnesses.
按原文从作者的仓库复制。PopNx MCP 没有审查或背书其内容。HTML、图片和脚本已被移除。链接会离开本站。
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.
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.
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
/ /SKILL.md # a directory of skills /skills/ /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.
| 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, paths[] | one entry per path, in request order: text, or base64 with its media type, or that path's own error. At most 1 MiB per entry, truncated: true rather than a silent cut |
skill_run | name, script, args[], confirmToken | {exitCode, stdout, stderr, truncated, durationMs} |
skill_file takes a paths[] batch, so the files a SKILL.md points at come back in one round trip: at most 8 paths per call, 1 MiB per entry and 4 MiB per call (summed from file sizes before a byte is read, so the answer never depends on the order the paths were listed in). One bad path fails only its own entry — the rest of the batch is still served. 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:// / ), 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 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.
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. It is served under its *target* directory's name — skills/foo -> ../bar is the skill bar, reported as a name-mismatch so a grant is written against the right name — and a directory reached twice (a link to a skill that is also listed directly) is one skill, not a duplicate.
Each entry in problems is {path, reason, detail}. detail is prose for a person; reason is the stable value to switch on.
| reason | meaning |
|---|---|
root-unreadable | a configured root could not be read at all |
no-skill-md | a candidate directory holds no SKILL.md |
unreadable-skill-md | SKILL.md is not a regular file, or could not be read |
malformed-frontmatter | the frontmatter does not parse |
declaration | the mcp-host: block was refused, or carries an unknown key (named in detail); the skill's scripts are affected, its instructions are not |
duplicate-name | two directories contribute one skill name; both are refused |
name-mismatch | the frontmatter name disagrees with the directory name, which wins |
unusable-name | the directory's own name is not addressable, so the skill is skipped |
declared-script-missing | a declared script is not in the bundle |
symlink-escape | a symlink leads out of the root or out of a skill |
unusable-path | a filename the read tools could not address, left out of the manifest |
file-limit | a skill holds more files or directories than the per-skill cap (2000 each); the manifest lists what was walked |
skill-limit | more skills than MAX_SKILLS (32) were found; the rest are ignored |
grant | MCP_SKILL_RUN names a script the skill does not declare, so that row grants nothing |
environ-unscrubbed | hosted, and the exec-time environment block could not be wiped, so no script runs (see the env allowlist note below) |
The set is additive. New reasons arrive without a major version bump (environ-unscrubbed is the most recent), so a client that switches on reason must treat a value it does not recognise as a generic problem and show its detail, never fail on it.
skill_run executes third-party code. Every rule below narrows which code runs and what it is handed; each has its own test.
scripts/", not "anything executable". An undeclared path is refused, saying it must be declared and listing the ones that are./, 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.spawn with shell: false, no interpolation, no sh -c. Arguments are passed through verbatim.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.PATH, HOME, LANG, TZ, TMPDIR, MCP_DATA_DIR when the host set one, the egress proxy variables (HTTP_PROXY, HTTPS_PROXY, http_proxy, https_proxy, NO_PROXY, no_proxy, NODE_USE_ENV_PROXY) when the host set them — on mcp-host's isolated tier the network is fenced and a script without them cannot reach even an allowlisted host — 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. This decides what a script is handed, not what a hostile script can reach. A script runs as the same uid as this server. On Linux a same-uid process can read /proc/ /environ, so at boot the server wipes that exec-time environment block (src/scrub-environ.ts; process.env keeps every value) and that one-line read comes back empty. If a kernel or sandbox refuses that wipe, a hosted server runs no script at all — skill_run refuses and skill_list reports environ-unscrubbed — unless the owner sets MCP_SKILL_ALLOW_UNSCRUBBED=1; standalone it only warns on stderr. The values still live in the server's memory, reachable through ptrace or /proc/ /mem on a kernel at Yama ptrace_scope = 0. Real isolation between skills that do not trust each other needs the tier (a distinct uid for scripts, /proc mounted hidepid=2, or a non-dumpable server); until then, only put skills in one registration if you would give each of them every credential it holds.MCP_CONFIRM_ELICITATION=off. Otherwise the first call starts no process and returns status: "confirmation-required" with a 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 (field by field in the preview) — plus a confirmToken. Only a repeat call with the same arguments and that token runs the script, once. A token is bound to that exact run: changing the skill, script or argv is refused as DRAFT_CHANGED, and a used token as TOKEN_REUSED. See Confirmations.skill_run previewThe first call's preview.willRun is what the user approves, and it is the confirmToken's payload: the token is bound to it. Names only — never a value.
| field | |
|---|---|
skill | the skill, by its directory name |
interpreter | the declared interpreter (node) |
argv | the script path followed by the arguments, exactly as they will be passed |
cwd | the skill's own directory, where the script runs |
timeoutMs | the wall-clock budget, after the 300 s clamp |
| envNames | every variable the script will be handed, sorted — not onl
只显示了开头部分;其余内容请在仓库中阅读