Skip to main content
Glama

ThirdBrain skills-mcp

Library-agnostic MCP server that serves one or many directories of Agent Skills (SKILL.md folders, flat or nested) over the Model Context Protocol, each library under its own namespace. Any folder of SKILL.md directories works: your own skills, a vendor pack, a git checkout, a zip, or a GitHub release asset. Optionally it also serves playbooks and value chains when a library ships them. Includes a pull client that syncs skills from any SEP-2640 server to disk with digest verification, and a pack command that builds a distributable zip.

It implements the MCP Skills extension (SEP-2640, Final since 2026-09-13) for hosts that understand skill:// resources, and a small set of discovery tools for hosts that do not yet, so skills are loaded on demand instead of being copied into every agent's skills folder. It speaks both protocol revisions from one binary: 2025-11-25 (the initialize handshake current hosts such as Claude Code use) and 2026-07-28 (server/discover, stateless per-request envelope), and passes the official conformance suite's skills scenarios on both (see Spec conformance). The agent flow is progressive disclosure: search → get_skill → read_skill_file. Only the skill you pick enters context.

It was built for, and is exercised daily against, the ThirdBrain Business Operating Brain (BOB) library (~400 skills). Nothing in the server depends on BOB: a library needs only SKILL.md folders, and the playbook and value-chain features switch on only when the matching folders and files are present. BOB-specific material (vault layout, measured numbers, this repository's own MCP config) is collected in the last section.

Quick start

git clone https://github.com/cbruyndoncx/ThirdBrain-skills-mcp.git
cd ThirdBrain-skills-mcp
npm install && npm run build
node dist/index.js --root /path/to/my-skills --name myskills     # serve one folder over stdio
node dist/index.js --root /path/to/my-skills --stats             # what would be served, as JSON
node dist/index.js --root test/fixtures/libA --stats             # try the included public fixture

Register it with Claude Code (any other MCP host takes the same command line):

claude mcp add --scope user myskills -- node /path/to/ThirdBrain-skills-mcp/dist/index.js --root /path/to/my-skills --name myskills

The agent then has myskills_search_skills, myskills_get_skill, myskills_read_skill_file and friends. npx github:cbruyndoncx/ThirdBrain-skills-mcp --root DIR builds and runs it without a checkout (the package exposes the skills-mcp binary).

Registry metadata

server.json describes the stdio package for the official MCP Registry. It asks clients for SKILLS_MCP_ROOT, the library directory or ZIP to serve. The registry entry can be published after version 1.5.1 is available as a public thirdbrain-skills-mcp npm package; the package's mcpName and the registry name must match. This checkout does not itself publish a package or registry entry.

The Skills extension working group keeps a separate implementations list. Adding this server there is a separate documentation contribution.

Related MCP server: Taproom

Ways to use it

Every form below takes the same flags; the difference is only where the content comes from.

1. One folder of skills

skills-mcp --root /path/to/skills --name acme       # URIs skill://<skill>/SKILL.md, tools acme_*

--name sets the MCP server name and the default tool prefix. Discovery finds every folder that holds a SKILL.md, up to four levels deep, so both flat (skills/refunds/SKILL.md) and nested (skills/billing/refunds/SKILL.md) layouts work; the nested path becomes the URI (skill://billing/refunds/SKILL.md) and the folder path becomes the category.

2. Several libraries in one server

skills-mcp --lib acme=/path/to/acme-skills --lib vendor=/path/to/vendor-skills --name skills

Each --lib NS=DIR is a namespace: URIs are skill://acme/refunds/SKILL.md, tools accept a bare name when it is unique across libraries, or vendor/refunds when both ship one. skills_list_libraries shows what is served; library=acme scopes search and listing. --root and --lib cannot be mixed.

3. A zip instead of a folder

skills-mcp --lib vendor=/srv/packs/vendor-skills-2.7.0.zip

The zip is a transport container: it is verified, extracted once into a digest-keyed cache, and served like a directory. A wrapper folder inside (as git archive and GitHub "Download ZIP" produce) is fine. See Archive libraries for what is refused.

4. A published release, by URL

skills-mcp --lib vendor='https://host/vendor-skills-2.7.0.zip#sha256=fac65d24…'      # pinned
export GH_TOKEN=$(gh auth token)                                                     # private asset
skills-mcp --lib vendor='https://api.github.com/repos/OWNER/REPO/releases/assets/<id>#sha256=<hex>'

Consumers need no checkout and no vault. Pin the digest; a mismatch is refused. Fetched once per process and cached by digest, so the second start is instant and offline. See Remote libraries.

5. Skills plus playbooks and value chains

skills-mcp --lib acme=/path/to/acme-vault          # a vault root: skills, playbooks, value chains
skills-mcp --lib acme=/path/to/acme-vault.zip      # the same, packed

When the root you give contains 00-CORE/Agents/skills, and next to it 00-CORE/Playbooks or a value-chains.md, the server also serves playbooks (multi-step workflows that chain skills into an outcome) and value chains (business journeys with ordered stages that skills and playbooks map to). The root is the boundary: nothing above or beside it is read, so pointing at the skills folder alone serves skills only. Conventions and tools in Playbooks and value chains.

6. Runtime config file, hot reload, HTTP

skills-mcp --config skills.json              # libraries re-read on every rescan and on SIGHUP
skills-mcp --lib acme=DIR --http 3939        # Streamable HTTP at http://127.0.0.1:3939/mcp

The config file adds, removes or re-points libraries while the server runs and carries the descriptive metadata (title, source, version) that clients see instead of paths. See Runtime config file and hardening.

7. Pull skills to disk from any SEP-2640 server

skills-mcp pull --url http://127.0.0.1:3939/mcp --list
skills-mcp pull --url http://127.0.0.1:3939/mcp refunds vendor/onboarding --to ./skills
skills-mcp pull --all --keep-path --to ./skills --command node dist/index.js --lib acme=DIR
skills-mcp pull --with-deps --keep-path --sync --to ~/.cache/skills-mcp-client/acme acme/refunds --url …

A client, not a server: walks skills/list, reads every file with resources/read, verifies each sha256 digest, refuses paths outside the skill, writes atomically. Works against this server or any other SEP-2640 server. Details in Pull.

8. Build a distributable pack

skills-mcp pack --vault /path/to/acme-vault --out acme-2026.09.zip
skills-mcp --lib acme=acme-2026.09.zip                 # serve it; or upload it and use form 4

Copies exactly the public subset the server serves (skills, active core playbooks with their embedded files, the value-chains file), as a deterministic zip with a .sha256 sidecar, and prints the pinned --lib line for consumers. Details in Pack.

What it exposes

Surface

Purpose

capabilities.extensions["io.modelcontextprotocol/skills"]

Declares the extension (directoryRead: true).

skills/list (paginated) / skills/get

skills/get returns {skill: <entry>} (the final spec's shape). On 2026-07-28 both also carry ttlMs (the rescan interval) and cacheScope: "private". Skill entries: uri, verbatim frontmatter, resources[] with sha256 digests and sizes, and _meta with io.modelcontextprotocol.skills/dependencies ({required, optional}: other skills this one runs code from, see Runtime dependencies) and .../library.

resources/list

One text/markdown resource per skill (skill://<name>/SKILL.md), extra frontmatter under _meta with the io.modelcontextprotocol.skills/ prefix. Plus one per served playbook (playbook://<ns>/<name>) and value chain (value-chain://<ns>/<id>), _meta under io.thirdbrain.vault/.

resources/templates/list

skill://{+skillPath}/SKILL.md and skill://{+skillPath}/{+path}; playbook://{+playbookPath}, playbook://{+playbookPath}/{+file} and value-chain://{+library}/{id} unless switched off.

resources/read

Any bundled file (text, or base64 blob for binaries); a playbook note or an attachment embedded by it; a value chain rendered as a markdown stage table.

resources/directory/read

Children of a skill directory (inode/directory).

notifications/resources/list_changed

Sent after a rescan detects changes.

Tools

<prefix>_search_skills, _list_skills, _list_libraries, _list_categories, _get_skill, _read_skill_file, _catalog_status, and _list_playbooks, _get_playbook, _list_value_chains, _get_value_chain (listed unless --no-playbooks / --no-value-chains; they answer empty for a library that ships none). All declare outputSchema and return structuredContent; search/list/categories take a library filter.

Prompts

use-skill(skill, task?) injects a skill's instructions into the conversation; run-playbook(playbook, inputs?) injects the playbook-runner skill plus a playbook.

Tool names are the prefix (default: the server name, - → _) plus the suffix, so a server named acme has acme_search_skills. Tools accept the bare skill name (when unique across all libraries), <namespace>/<name>, or the full path from search results.

Browsing: _list_categories gives the taxonomy (from frontmatter category, else the folder path between namespace and skill, else uncategorized) with counts; _list_skills(category) and _search_skills(query, category) filter by it. Skills stay a flat list at the resource layer; there is no category tree in skill:// URIs.

Configuration

Flag / env

Default

Meaning

--root / SKILLS_MCP_ROOT

one of these required

Single un-namespaced library: a directory searched recursively for SKILL.md folders, or a .zip of one. SKILLS_ROOT is not read (it belongs to the skills' own contract).

--lib NS=DIR / SKILLS_LIBS="a=/x,b=/y.zip"

Namespaced library; repeatable. Cannot be mixed with --root. A .zip path or an https URL of a zip (optionally #sha256=<hex>) is allowed.

--config FILE / SKILLS_CONFIG

JSON file with libraries and global switches, re-read on every rescan and on SIGHUP. See Runtime config file.

--name / SKILLS_NAME

skills, or the namespace when there is exactly one

MCP server name; also the default tool prefix.

--prefix / SKILLS_TOOL_PREFIX

name with -→_

Tool-name prefix.

--title / SKILLS_TITLE

derived

Human title in initialize.

--exclude / SKILLS_EXCLUDE

_archive,_audit

Directory names skipped during discovery.

--depth / SKILLS_DEPTH

4

Max discovery depth below root.

--show-disabled / SKILLS_HIDE_DISABLED=false

hidden

Serve skills with disable-model-invocation: true, and playbooks whose status is not active.

--no-scripts / SKILLS_NO_SCRIPTS=true

served

Withhold executable files from every library (per library: "noScripts": true in the config file).

--no-lint / SKILLS_LINT=false

on

Disable the scan-time risk linter.

--vault NS=DIR / SKILLS_VAULTS="acme=/vault"

the root itself

Explicit vault directory holding a library's playbooks and value chains. By default only the library root is inspected; nothing above or beside it is read.

--no-playbooks / SKILLS_PLAYBOOKS=false

served

Never serve playbooks (default: served when found and a playbook-runner skill is served).

--no-value-chains / SKILLS_VALUE_CHAINS=false

served

Never serve value chains (default: served when found).

--exclude-tiers T1,T2 / SKILLS_EXCLUDE_TIERS / "excludeTiers" in the config file

none

Skills and playbooks whose frontmatter pricing-tier is in the list (case-insensitive) are not served at all (not even to skills/get) and are counted in hidden / playbooksHidden; catalog_status and --stats report tierExcluded {tiers, skills, playbooks}. The config file value, when set, replaces the flag.

--dev-status S1,S2 / SKILLS_DEV_STATUS / "devStatus" in the config file

all

Serve only skills whose frontmatter dev-status is in the list (case-insensitive), e.g. integrated. A skill whose dev-status is set to anything else is not served at all (not even to skills/get) and is counted in hidden. A skill without a dev-status field is served (the library does not track it) and counted as untracked. catalog_status and --stats report devStatusExcluded {statuses, skills, untracked}. A skill withheld by both filters is counted under tierExcluded only. Playbooks are not affected. The config file value, when set, replaces the flag.

--http PORT / SKILLS_HOST

stdio; 127.0.0.1, port 3939

Streamable HTTP instead of stdio; SKILLS_HOST sets the bind address. HTTP validates Host and Origin.

SKILLS_ALLOWED_HOSTS

empty

Additional comma-separated hostnames accepted by HTTP. Required when SKILLS_HOST binds all interfaces. Place remote deployments behind authentication.

SKILLS_MAX_FILE_BYTES

4 MiB

Files above this are not served.

SKILLS_CACHE_DIR

~/.cache/skills-mcp

Where .zip libraries are extracted and downloaded, keyed by content digest.

SKILLS_MAX_DOWNLOAD_BYTES

256 MiB

Ceiling on bytes read from the network for a remote library.

SKILLS_FETCH_TIMEOUT_MS

60000

Timeout for a single HTTP request when fetching a remote library.

SKILLS_FETCH_TOKEN

Explicit bearer token for remote archive assets; sent to the initial library host and same-host redirects.

GH_TOKEN / GITHUB_TOKEN

Used automatically only for initial github.com or api.github.com archive URLs.

SKILLS_MAX_ARCHIVE_BYTES

256 MiB

Total uncompressed size an archive library may extract to.

SKILLS_MAX_ARCHIVE_ENTRIES

8192

Max entries in an archive library.

SKILLS_RESCAN_SECONDS

60

Background rescan interval (0 disables). <prefix>_catalog_status refresh=true forces one.

--stats

Print catalog statistics as JSON and exit.

Nested libraries: a skill's URI path is its directory path below root, prefixed by the namespace when there is one (skill://acme/billing/refunds/SKILL.md).

Always ignored inside skills, silently: tool and build folders (.venv, venv, node_modules, __pycache__, site-packages, .git, .pytest_cache, .mypy_cache, .ruff_cache, *.dist-info, *.egg-info), compiled artefacts (.pyc, .pyo, .so, .dylib, .dll, .whl) and type stubs (.pyi). Also not served, but reported as one catalog_status warning per skill (<skill>: N file(s) skipped (… over SKILLS_MAX_FILE_BYTES: …; … symlink: …; … dotfile: …), up to three names each): files over SKILLS_MAX_FILE_BYTES, symlinks, and dotfiles or dot-folders. Skills with invalid required frontmatter, an incomplete manifest, or more than 512 files or 16 MiB of served content are withheld with a catalog warning.

Playbooks and value chains

Optional. A library may ship two more things next to its skills: playbooks (multi-step workflows that chain skills into an outcome: trigger → numbered AGENT/HUMAN steps → outcome) and value chains (end-to-end business journeys such as lead-to-cash with ordered stages, to which skills and playbooks are mapped). The layout and note formats below are the ThirdBrain BOB vault conventions; any library that follows them gets the same features, and a library without them is served as skills only. The four tools, the run-playbook prompt and the resource templates are always listed (turn them off with --no-playbooks / --no-value-chains), so a host that fetches the tool list once, before the first scan finishes, still sees them; when no library ships the content they answer with an empty list and a hint that says so.

The root is the boundary. The server reads only what is inside the directory, zip or URL you give it, never folders above or beside it. So --lib acme=<vault>/00-CORE/Agents/skills serves skills only; to serve playbooks and value chains, give the vault root (or a release zip of it). The layout is recognised from what is inside the root, so the same pack works as a checked-out folder, a local zip, or a GitHub release asset:

Root given (directory, zip, or URL)

Skills root

Vault root

A skills folder (any folder without the markers below)

as given

none: skills only

<vault> (contains 00-CORE/Agents/skills)

that subfolder

as given

One wrapper folder containing 00-CORE/Agents/skills (GitHub release zips are shaped this way)

inside the wrapper

the wrapper

Anything else that holds 00-CORE/Playbooks or a value-chains.md within four levels

as given

as given

--vault NS=DIR or "vault" in the config file names a vault directory explicitly, the one case where content outside the root is served, by your choice; "vault": false turns the extras off for one library. Inside the vault root only these locations are read:

Information

Source

Served as

Playbooks

type: playbook notes under 00-CORE/Playbooks/ only, the vault-shipped library. Company, personal and client playbooks (20-COMPANY/03-PROCESSES/Playbooks/, 10-ME/Playbooks/, 30-CLIENTS/<id>/Playbooks/) are private and never served; notes found in the first two are counted in a catalog_status warning so the operator knows. Recursive; _archive/ and UPGRADE/ skipped; AGENTS.md, CLAUDE.md, _local.md ignored.

playbook://<ns>/<file stem>; files embedded with ![[name]] from the same folder (sequence diagrams) as playbook://<ns>/<stem>/<file>

Value chains

20-COMPANY/03-PROCESSES/value-chains.md, the canonical file: ### <id> with SME Label, Description, Stages fields (bulleted - **Stages:** … or not), plus the ## Cross-Chain: … / ## Meta-Chain: … sections and the ids in its "Valid chain IDs" list that have no ### block, which are served as buckets (unstaged groups such as operating-controls and infrastructure: no stages, no gaps). Else VALUE-CHAINS.md at the root, else any value-chains.md (case-insensitive) found by walking. Without one, chains are derived from the value-chains / chain-stage frontmatter of skills and the value-chain / chain-coverage frontmatter of playbooks.

value-chain://<ns>/<id>

Paths never leave the server: vaultPath in playbook output is relative to the vault root, and archive extraction directories are not shown.

How steps are read. Steps are the numbered items of the ## Steps section, in the playbook-runner grammar: N (countdown). head → action (AGENT|HUMAN[ — note]), where the head is skill, skill (route), [[Playbook]] or script:file.py, and bold or code decoration around it is ignored. An item runs over its indented continuation lines up to the next item or heading (numbered lines inside code fences or indented deeper are not items). The actor is the parenthesis ending the item's first paragraph ((AGENT), (HUMAN + AGENT), (HUMAN, 5 min), (HUMAN decides, AGENT drafts)), else the first one inside it, else the one ending a later paragraph. A step's skill is the head when it names a skill, else the first served skill the step links to — [[skill]], [[skill/SKILL.md|…]], {skills.root}/skill/… or skill/scripts/…; links to other notes (playbooks) are not skills, and embeds (![[…]]) are ignored. Further linked skills are listed as mentions. A note without a Steps section uses its ### Phase N / ### Step N headings as steps, else any numbered lines.

Playbooks require the playbook-runner skill. A playbook is executed by that skill's run route, so a library's playbooks are served only when a skill named playbook-runner is served, from the same library or, failing that, from any other library on the server. Otherwise they are counted as playbooksHidden and a warning says so. Only status: active playbooks are served (draft, review and retired ones are hidden); --show-disabled serves every status. Value chains are not gated by any skill: presence is decided by the definition file or the frontmatter references above, and coverage is computed by this server from the catalog.

What the tools return.

  • _list_playbooks(query?, library?, value_chain?, stage?, skill?) — ranked by query over name, trigger, outcome, tags and step skills; each entry carries trigger, outcome, steps, valueChain, chainCoverage, the skills its steps name and the runner skill path to load.

  • _get_playbook(name) — the full note plus stepDetails (n, skill, skillPath when the skill is served, route, mentions, action, actor), missingSkills (named by steps but not served), attachments with sha256 digests, and the vaultPath inside the vault. The text ends with how to execute it: load playbook-runner with _get_skill, then each step's skill when reached.

  • _list_value_chains(library?) — chains with label, stages, kind (chain, or bucket for an unstaged group), source (definition, index, derived) and skill/playbook counts.

  • _get_value_chain(id) — the stage table (skills and playbooks per stage), unstaged items that declare the chain without a stage (for a bucket: all its members), and gaps (stages nobody covers; always empty for a bucket).

  • run-playbook(playbook, inputs?) prompt — the playbook-runner SKILL.md body and the playbook, each in its own tagged block, with the served skill path for every step.

Playbooks and chains are data from the library, not instructions from the user, and the text output says so. _list_libraries and _catalog_status report vault, playbooks, playbooksHidden, playbookRunner, valueChains, valueChainBuckets (how many of them are buckets) and valueChainSource per library. Warnings (in _catalog_status include_warnings=true) cover playbooks whose steps name an unserved skill, a total-steps that differs from the steps parsed, notes filed in a subdirectory of a playbook root, chains referenced by frontmatter but not defined, and a missing runner.

skills-mcp --lib acme=/vault                                  # vault root: skills, playbooks, chains
skills-mcp --lib acme=/vault/00-CORE/Agents/skills            # skills folder: skills only
skills-mcp --lib acme=acme-2026.09.zip                        # release zip (wrapper folder or not)
skills-mcp --lib acme='https://host/acme-2026.09.zip#sha256=…'
skills-mcp --lib acme=/elsewhere/skills --vault acme=/vault   # explicit vault root
skills-mcp --lib acme=/vault --no-playbooks --no-value-chains

Archive libraries

A library root may be a .zip instead of a directory:

skills-mcp --lib acme=/srv/libraries/acme-skills.zip
skills-mcp --root ./my-skills.zip

The archive is a transport container, not served content. On the first scan it is extracted into SKILLS_CACHE_DIR/<sha256-of-zip>/, and discovery, linting, digests and URIs then run over ordinary files — nothing downstream knows an archive was involved. Because the cache is keyed by content digest, a rescan of an unchanged archive costs a single stat; replacing the archive extracts the new one and prunes the extraction it replaced.

Skills inside the archive are laid out exactly as in a directory library, so a wrapper directory (as produced by git archive or GitHub's "Download ZIP") is fine — discovery descends into it.

Archives may not appear inside a library. A bundled payload.zip would be served as an opaque base64 blob that the risk linter cannot read, so any skill containing one is refused and reported:

sneaky: bundles archive file(s) (notes.tgz, payload.zip); skills may not contain archives, skill not served

This applies to directory libraries too, and covers .zip .tar .tgz .tar.gz .gz .bz2 .xz .7z .rar .jar .war .apk .iso .dmg .cab — renaming a zip to .tgz does not get past it. The count appears in --stats as archiveSkillsRejected. One bad skill is withheld; the rest of the library still serves.

Remote (URL) libraries

A library may also be an https URL of a .zip, so a published release can be served without checking anything out:

# pinned to a digest (recommended)
skills-mcp --lib sales='https://host/pack-sales-free-v2.7.0.zip#sha256=fac65d24...'

# or in the config file
{"libraries": [{"namespace": "sales", "url": "https://host/pack.zip", "sha256": "fac65d24..."}]}

GitHub release assets work directly, including private ones — export GH_TOKEN and use the asset API URL:

export GH_TOKEN=$(gh auth token)
skills-mcp --lib sales="https://api.github.com/repos/OWNER/REPO/releases/assets/<id>#sha256=<hex>"

Integrity. The archive is verified before extraction: against the sha256 pinned in config, or failing that a <url>.sha256 sidecar next to the asset. A mismatch is refused outright rather than served. Pinning is stronger than the sidecar, since the sidecar travels the same wire as the zip.

Caching. Downloads are keyed by content digest, the same key the extractor uses. Cached downloads are hashed again before reuse, and extracted files are checked against a local integrity record before each scan. A changed extraction is refused until that cache entry is removed. A pinned URL with a valid cached download does not need a network request. A remote library is fetched once per process; change the URL or the pin to pick up a new version. The shared cache is not pruned by a running server, because another server may be using those files. Operators can remove unused files when no server is running.

Failure handling. If a refresh fails but a previous extraction is still cached, the cached copy keeps being served and a warning is recorded — a transient DNS blip during a background rescan will not empty a live library.

What is refused:

Check

Why

Any scheme but https, on the initial URL and every redirect

No plaintext downgrade mid-chain

More than 5 redirects

Redirect loops

Authorization on a cross-host redirect

A token must not follow a redirect to another host. GitHub relies on this: it redirects asset URLs to a signed object store that must be called without the header

content-length over the cap, or the body exceeding it mid-stream

A lying or absent content-length cannot smuggle a huge body onto disk

A digest that does not match the pin or sidecar

Tampering or corruption

Query strings and fragments are stripped from anything logged, so credentials in a signed URL do not reach a log line.

What is rejected in an archive

The archive is treated as untrusted input. Extraction refuses, before writing anything to disk:

Check

Why

Entry paths containing .., absolute paths, drive letters, backslashes

Path traversal ("zip slip")

Symlink entries

A link to ~/.ssh/id_rsa would otherwise be served verbatim

Encrypted entries, compression methods other than stored/deflate, zip64

Unparseable or unsupported

Nested archives

Hide content from the linter

More than SKILLS_MAX_ARCHIVE_ENTRIES entries

Resource exhaustion

Entries over SKILLS_MAX_FILE_BYTES × 16, or a total over SKILLS_MAX_ARCHIVE_BYTES

Decompression bombs

Compression ratio over 100:1 for entries above 1 MiB

Bombs, without flagging ordinary repetitive content

The byte budget is enforced while inflating, not from the sizes declared in the central directory, since those are attacker-controlled and may lie. A failed extraction leaves no partial tree behind, and extracted files are always written non-executable (0644).

Build a distributable pack from a vault

skills-mcp pack --vault /path/to/vault --out acme-2026.09.zip [--wrapper acme-2026.09] [--all-statuses] [--dry-run]

Copies exactly the public content the server serves: 00-CORE/Agents/skills (with the server's ignore rules, skills that bundle an archive left out), the status: active playbooks in 00-CORE/Playbooks together with the files they embed, and the canonical 20-COMPANY/03-PROCESSES/value-chains.md. Company, personal and client folders are never read, and the generated VALUE-CHAINS.md is not shipped because it names private playbooks. The zip is deterministic (sorted entries, fixed timestamps) and gets a .sha256 sidecar; the command prints the pinned --lib line to give consumers. pack.json inside records what was packed. Serving the pack gives the same catalog as serving the vault root, minus the private-folder warning.

Pull skills from any SEP-2640 server

skills-mcp pull --url http://127.0.0.1:3939/mcp --list
skills-mcp pull --url http://127.0.0.1:3939/mcp refunds vendor/onboarding --to ./skills
skills-mcp pull --all --keep-path --to ./skills --command node dist/index.js --lib acme=DIR
skills-mcp pull --with-deps --keep-path --sync --to ~/.cache/skills-mcp-client/acme acme/refunds \
  --command node dist/index.js --lib acme=/path/to/acme-skills

pull walks skills/list, reads every file with resources/read, verifies its byte count and sha256 digest against the manifest, refuses unsafe paths and symlinked destinations, and writes each file through a temporary file. Skills with dynamic manifests cannot be synced with content verification and are rejected. Colliding destination names require --keep-path. It negotiates the protocol era (probing with server/discover, falling back to the 2025 handshake), so it works against servers on either revision, including ones that expose skills only on 2026-07-28; --legacy-protocol skips the probe. It accepts both the final {skill} and the draft bare-entry shape of skills/get. --keep-path keeps the namespace as a folder; --force overwrites; --dry-run verifies without writing. --sync updates an existing folder in place: files whose sha256 already matches are kept without a network read, changed or missing ones are fetched, and files no longer in the skill are deleted, so a re-run on an unchanged skill costs one skills/list and some local hashing. Without the CLI installed, npx github:cbruyndoncx/ThirdBrain-skills-mcp pull ... builds and runs it. It copies skills only; playbooks and value chains travel as a pack (above).

--with-deps also pulls every skill a selected skill declares as a required runtime dependency (the _meta["io.modelcontextprotocol.skills/dependencies"].required list, see Runtime dependencies), transitively and from the same library, and writes each next to it (<to>/<ns>/<dep>/ with --keep-path), which is where a script importing a sibling skill looks for it. Each added skill is logged (deps acme/refunds → acme/context-pack). Optional dependencies are not pulled. A required dependency the server does not serve in the same library stops the pull with an error rather than leaving a closure that cannot run; pull without --with-deps to fetch the skill alone. The flag is off by default, so existing invocations behave as before.

Runtime config file and hardening

skills-mcp --config skills.json          # re-read on every rescan (default 60 s) and on SIGHUP
{ "libraries": [
    { "namespace": "acme", "root": "/path/to/acme-vault",
      "title": "Acme skills", "source": "acme-vault", "version": "2026.09",
      "metadata": { "channel": "stable" } },
    { "namespace": "vendor", "url": "https://host/vendor-2.7.0.zip", "sha256": "fac65d24...", "noScripts": true, "vault": false },
    { "namespace": "team", "root": "../team/skills", "vault": "../team" } ],
  "noScripts": false, "lint": true, "playbooks": true, "valueChains": true, "excludeTiers": [],
  "devStatus": [] }

vault is optional: a path (relative to the file) naming a vault directory explicitly when the library root is only the skills folder, or false to serve that library's skills only. playbooks and valueChains are global switches (default true); excludeTiers (an array or a comma string, default none) is the --exclude-tiers list, and devStatus (same forms, default none) the --dev-status list.

Libraries from the file can be added, removed or re-pointed while the server runs; libraries given on the command line stay fixed. Relative roots resolve against the file's directory. CLI and environment restrictions such as --no-scripts remain active across reloads. A broken edit is logged and the previous set is kept. Changes trigger resources, tools and prompts list_changed notifications. kill -HUP <pid> forces an immediate reload.

Library paths never leave the server. list_libraries, catalog_status, the initialize instructions and skill output show what is loaded, not where it is: the optional title, source (the vault, repository or team the library comes from), version and free-form string metadata from the config file, the kind (directory, archive, url), and for archive and url libraries the sha256 of the archive currently extracted. Warnings returned by catalog_status have root paths replaced by <namespace>. Roots still appear in the server log and in --stats, which are for the operator. Metadata fields are only available in the config file; a library given as --lib NS=DIR shows its namespace and kind.

The server never executes anything. Three additional layers label or withhold risky content:

Layer

What it does

Where it shows up

Provenance

Lifts origin, origin-repo, risk, outbound, outbound_targets, gate_required, dev-status, pricing-tier, license, allowed-tools from frontmatter

_meta["io.modelcontextprotocol.skills/<field>"] on resources, trust in search/list/get_skill output, Provenance: line in get_skill text

--no-scripts (global) / "noScripts": true (per library)

Drops executable files (.sh .bash .zsh .ps1 .psm1 .bat .cmd .py .js .mjs .cjs .ts .rb .pl .php) from skill manifests and playbook attachments

scriptsWithheld count for skills, _meta[".../scripts-withheld"], catalog status

Scan-time linter (--no-lint to disable)

Regex rules over text files ≤ 512 KiB: pipe-to-shell, remote-exec, eval-decode, base64-blob, destructive-rm, world-writable, sensitive-path, credential-literal, env-exfil, prompt-injection, reverse-shell

Catalog warnings, _meta[".../risk-flags"], riskFlags in search/list results, per-finding file:line in get_skill plus a ⚠ Risk flags banner in its text

The linter labels, it does not block. Typical hits are curl … | sh install instructions for third-party tools: legitimate, but worth knowing before a host runs them. Rules live in src/lint.ts.

Bundled scripts

Skills may ship scripts (scripts/extract.py), and this server serves them like any other file. What happens on the receiving side is up to the host; SEP-2640 sets the rules:

  • Fetched on demand, verified, cached. Hosts SHOULD cache skill files locally as they are read, not in bulk, and MUST verify each file against its sha256 digest from skills/list / skills/get. The cache must be writable only by the host (or re-hashed on every use), live outside every local skill-discovery path, and be separated per server.

  • Cached is not local. A cached script keeps its MCP origin, even after a restart or after the server is disconnected. It never gains the trust of a filesystem skill.

  • No execution without approval. Hosts MUST NOT run a script from an MCP-served skill, or any command its instructions direct the model to run, without explicit per-skill user approval. That approval is bound to the skill's file set and digests; any change revokes it.

  • No executable bits. Files travel one by one as resources, without mode bits or symlinks, so a script is run through its interpreter (uv run scripts/extract.py, python scripts/extract.py), never as ./extract.py.

Hosts without SEP-2640 support reach skills through the tools, so this server repeats those rules to the model. The server instructions, get_skill and the use-skill prompt label every skill with its source server (Source: <name> (MCP-served skill, not installed locally)). Every file in get_skill carries its digest; every script also carries its interpreter (uv run for a Python file with a PEP 723 # /// script header, else python, bash, node, ... by extension) and, for Python, pep723: true|false, in the structured output and in the text file list (run with: uv run (PEP 723)). read_skill_file returns the same two fields for a script. When a skill or its dependency closure bundles scripts, the text output also lists the digests and tells the model to run scripts only from a verified local copy of the skill and the skills it depends on, because scripts import or read their sibling files and those skills:

  1. Copy the skill and its dependency closure into a cache folder that no host scans for skills, preferably with skills-mcp pull --sync --keep-path --with-deps --to ~/.cache/skills-mcp-client/<server> <skill-path> (--url or --command as the client is configured). No file content passes through the conversation, digests are verified, and re-running it each session is cheap thanks to --sync. Only when the CLI cannot run: read_skill_file per needed file of the skill and of each skill in its closure (the guidance names them), written byte-for-byte at its relative path and checked against the digests, never retyped.

  2. Show the user what will run and get their approval. The approval covers the skill and its dependency closure, whose code runs too, and the guidance lists the closure (acme/refunds, acme/context-pack). A declared dependency the server does not serve is named with a warning.

  3. Run each script with the full command the guidance renders, from the cached skill folder:

    cd ~/.cache/skills-mcp-client/acme/acme/refunds && SKILLS_ROOT=~/.cache/skills-mcp-client/acme/acme VAULT_PATH=<workspace> uv run scripts/refunds.py … --vault <workspace>
    • The interpreter is per file: uv run when the script has a PEP 723 header (it installs the inline dependencies; plain python does not), otherwise the one for its extension.

    • SKILLS_ROOT is always set, to the cached library (<cache>/<ns>), so a command written as {skills.root}/<other>/scripts/x.py runs the verified cache copy rather than a local install. It is library-agnostic; this server itself does not read SKILLS_ROOT (see Configuration).

    • VAULT_PATH=<workspace> is added when the library is vault-shaped (a vault was detected) or the skill's text or scripts mention VAULT_PATH / --vault; --vault <workspace> is appended to a script whose source takes a --vault option. <workspace> is the user's vault or workspace, never the cache folder, which a script would otherwise take as its workspace.

    • Up to 8 commands are rendered (the script named after the skill first, test files left out); the rest follow the same form. … stands for the script's own arguments.

read_skill_file attaches a short version of these steps, with the digest and the interpreter, to every executable file. Skill output (get_skill, read_skill_file, use-skill) only shows paths relative to the skill folder, never where the skill lives on the server's disk.

Runtime dependencies

A skill that runs another skill's code declares it in its SKILL.md body with a marker line, which the server parses at scan time:

- [[context-pack/SKILL.md|context-pack]] — **runtime dependency.** Filed tasks use the pack's output.tasks.
- [[skillsmith/SKILL.md]] — **optional runtime dependency.** Used when present.

The link may carry an alias or not. Required dependencies form the closure: transitive, cycles followed once, the skill itself excluded. Optional ones are reported but never pulled. They are exposed as _meta["io.modelcontextprotocol.skills/dependencies"] = {required, optional} in skills/list, skills/get and resources/list, and in get_skill as dependencies, dependencyClosure (skill paths) and missingDependencies (required but not served), plus a Runtime dependencies: line in its text. A required dependency that the library does not serve is also a catalog_status warning.

Dependencies resolve inside the skill's own library only; cross-library dependencies are not supported. An acme skill depending on context-pack is satisfied by acme/context-pack, never by a context-pack in another namespace.

requires is not a dependency list. Frontmatter requires names external setup (tools, API keys). It is still published as _meta["io.modelcontextprotocol.skills/requires"] for compatibility, and get_skill returns it as setup with a Setup (… not skills): text line.

For skill authors this means:

  • Reference scripts by path relative to the skill root, and show the interpreter in the command: uv run scripts/extract.py input.pdf (or python scripts/extract.py), not ./scripts/extract.py or an absolute path.

  • Do not rely on a script's executable bit, on symlinks, or on files outside the skill directory other than declared runtime dependencies, reached through {skills.root}/<other>/ or as a sibling folder.

  • Declare Python dependencies inline with a PEP 723 # /// script header, which uv run installs; a requirements.txt in the skill is optional. .venv and node_modules are never served.

  • Declare every skill whose code yours runs with the runtime-dependency marker line above.

  • Expect a host to ask the user before running anything, and write instructions that still make sense if the user says no.

pull into a cache folder, as above, keeps skills out of the host's reach as local skills. Pulled with --to into a folder the host scans for skills, they become local skills instead: an explicit install, not a cache. Pull only from servers you trust, or use --no-scripts on the serving side to withhold scripts entirely.

Spec conformance notes

Measured with the official suite (modelcontextprotocol/conformance, commit 7169291, 2026-09-11), SEP-2640 server scenarios, by npm run test:conformance:

Protocol

enumeration

manifest

directory

2026-07-28

32/32

6/6

7/7

2025-11-25

30/30

6/6

7/7

  • URIs are skill://<skill-path>/<relative-path>; the last skill-path segment equals frontmatter name. A warning is recorded when a directory name and its frontmatter name differ.

  • playbook:// and value-chain:// are custom schemes, which the MCP resources spec allows provided they follow RFC 3986; path segments are percent-encoded and templates follow RFC 6570. They are separate from the SEP-2640 skill:// scheme, and skills/list returns skills only.

  • Digests are computed lazily and cached per file mtime, so skills/list is cheap after the first call.

  • Skills with more than 512 files or 16 MiB of served content are withheld and listed in <prefix>_catalog_status include_warnings=true; manifests are never truncated.

  • skills/get answers for hidden (disabled) skills too, as the spec requires, and wraps the entry as {skill}.

  • Cache fields (ttlMs, cacheScope) are emitted on 2026-07-28 only: by us on skills/list, skills/get and resources/directory/read, and by the SDK (from the same policy) on the spec's cacheable results. cacheScope is private because libraries may be private.

  • The SKILL.md resource's name and description are the frontmatter values verbatim.

  • A missing resource is a -32602 error, as the current resources spec requires.

Development

npm run test:unit   # in-memory MCP client, fixtures under test/fixtures
npm run test:nested # nested-path fixture (skill://acme/billing/refunds/...)
npm run test:eras   # stdio with a 2025 client, a pinned 2026-07-28 client and auto negotiation
npm run test:conformance # official SEP-2640 conformance scenarios over HTTP, both revisions
npm test            # smoke test against a real library at $BOB_VAULT (prints SKIP when unset)
npm run test:gbl    # same smoke test against $GBL_VAULT
npm run test:startup # time initialize + first tool call against $SKILLS_CONFIG or $BOB_VAULT (SKIP when unset);
                     # or pass server args: node test/startup-timing.mjs --lib bob=/path/to/vault
npm run test:all    # build + all of the above

Requires Node.js 20+. Built on the TypeScript MCP SDK v2 (@modelcontextprotocol/server, client, node). test:conformance fetches the conformance suite at a pinned commit and builds it once into ~/.cache/skills-mcp (no npm release contains the skills scenarios yet); set SKILLS_CONFORMANCE_DIR to use an existing checkout instead.

The vault fixtures under test/fixtures/vault and test/fixtures/vault2 are minimal vaults (three skills, playbooks of every status, a private playbook that must never be served, a chain file in each of the two formats); the unit tests also zip them on the fly to cover the release layout.

src/config.ts       env/CLI configuration
src/frontmatter.ts  SKILL.md frontmatter parsing
src/catalog.ts      directory scan, file inventory, digests, change detection
src/archive.ts      zip-backed libraries: strict extraction, path/bomb/symlink defences, digest cache
src/remote.ts       remote libraries: https fetch, redirect/credential rules, digest verification
src/search.ts       ranked keyword search over name/description/tags/body
src/vault.ts        vault extras: playbook discovery/step parsing, value-chain definitions and coverage
src/server.ts       MCP wiring: extension methods, resources, tools, prompts
src/lint.ts         scan-time risk linter (labels, never blocks)
src/pull.ts         client: sync skills from a SEP-2640 server with digest verification
src/pack.ts         `pack`: build a distributable zip (skills, active core playbooks, chain file) from a vault
src/index.ts        entrypoint: `serve` (default; stdio via serveStdio, --http via createMcpHandler; both eras), `pull` and `pack`
test/unit/*.test.ts unit tests (config, frontmatter, catalog, search, server via InMemoryTransport, pull, hardening, archive, remote, vault, deps, tiers, pack)
test/smoke.ts       end-to-end test via a real MCP client (parametrised by root/name)
test/nested.mjs     nested-path + duplicate-name test against test/fixtures/nested
test/eras.mjs       stdio serving of both protocol eras
test/startup-timing.mjs time to initialize and first tool call against a real library
test/conformance.mjs official SEP-2640 conformance scenarios, both revisions

ThirdBrain BOB specifics

Everything above applies to any skill library. This section holds what is particular to the ThirdBrain Business Operating Brain (BOB) vault the server was built for.

Vault layout. A BOB vault keeps its skills at 00-CORE/Agents/skills/, its shipped playbooks at 00-CORE/Playbooks/, company, personal and client playbooks at 20-COMPANY/03-PROCESSES/Playbooks/, 10-ME/Playbooks/ and 30-CLIENTS/<id>/Playbooks/, and the canonical chain definitions at 20-COMPANY/03-PROCESSES/value-chains.md, with a generated VALUE-CHAINS.md and PLAYBOOKS.md at the root. The playbook step grammar, the playbook-runner skill, the runtime-dependency marker line and the {skills.root} / VAULT_PATH conventions in the sections above come from BOB. The vault's own vault-release skill builds the published packs; skills-mcp pack builds the MCP-served subset of one.

Serving the live vault. Point at the vault root, not the skills folder, to get playbooks and value chains. To serve only what is ready and integrated, and nothing private, withhold the internal and private pricing tiers and every skill whose dev-status is not integrated:

claude mcp add --scope user skills -- node /path/to/ThirdBrain-skills-mcp/dist/index.js \
  --lib bob=/path/to/brncx-skills \
  --lib gbl=/path/to/gbl-skills \
  --exclude-tiers internal,private --dev-status integrated

Both filters apply to every library. A library whose skills carry no dev-status field is served in full and shows up as untracked in devStatusExcluded.

This repository's .mcp.json serves the bundled fixture library (test/fixtures/libA) under the demo namespace, so it works on a fresh clone: run npm ci && npm run build, then start the MCP client in the repository folder. Point --lib at your own library to serve real skills. The smoke tests read BOB_VAULT and GBL_VAULT, and test:startup reads SKILLS_CONFIG or BOB_VAULT.

Serving a release pack in a new vault. Build the pack from the live vault, then reference it from the new vault's .mcp.json:

skills-mcp pack --vault /path/to/brncx-skills --out bob-full-2026-09-25.zip
{ "mcpServers": { "bob": { "command": "node",
  "args": ["/path/to/ThirdBrain-skills-mcp/dist/index.js", "--lib", "bob=/path/to/bob-full-2026-09-25.zip"] } } }

Environment aliases. BOB_SKILLS_* environment variables are still accepted as aliases of SKILLS_*, and BOB_SKILLS_ROOT of SKILLS_MCP_ROOT (warned once as deprecated).

Breaking change (after 1.3.0): the root variable is SKILLS_MCP_ROOT; SKILLS_ROOT is no longer read. BOB skills use SKILLS_ROOT as their own contract (the folder their scripts resolve {skills.root} from), so a host exporting it for the skills and passing its environment to this server gave the server a second, un-namespaced library: next to --lib it refused to start (--root cannot be combined with other libraries), alone it silently served that folder.

Measured on the live vault (2026-09-23 unless stated; --lib bob=<vault> --stats):

What

Figure

Skills served / hidden (disable-model-invocation)

390 / 8

Playbooks served / hidden (draft)

86 / 4 (2026-09-25)

Value chains, from the canonical file

14: 12 staged chains and the 2 buckets operating-controls and infrastructure

Skills declaring at least one required runtime dependency

142

--exclude-tiers internal,private

withholds 13 skills and 3 playbooks

--dev-status integrated (2026-09-26)

withholds 1 skill (beancount, dev-status: dev); 0 untracked. With --exclude-tiers internal,private too it serves 377 skills (21 hidden: 8 disabled, 13 tier) and 83 playbooks; beancount is also private, so devStatusExcluded.skills is 0

Linter

15 served skills flagged: 11 curl … | sh install instructions, 2 that document prompt-injection phrasing, 1 test fixture with a sensitive path, 1 env-exfil match; a 16th, hidden skill for test fixtures

SEP-2640 limits

none exceeded: largest skill nl-accounts-review at 3.8 MiB, most files 166

Files skipped (over size, symlink, dotfile)

none

Pack of the full vault (2026-09-25)

398 skills, 86 playbooks, 1 chain file: 4427 files, 49 MiB, 24.9 MiB zipped; serves identically to the vault root

pull --with-deps --keep-path bob/bob-marker-sweep

writes bob-marker-sweep and context-pack; the rendered command runs from the cache with exit 0

Available Tools

7 tools
skills_catalog_statusskills catalog statusA
Read-only

Catalog statistics (libraries, skill count, files, last scan, frontmatter warnings). Pass refresh=true to force a rescan of the skills directories.

ParametersJSON Schema
NameRequiredDescriptionDefault
refreshNo
include_warningsNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
rootNo
bytesYes
filesYes
hiddenYes
scanMsNo
skillsYes
warningsNo
librariesNo
scannedAtYes
categoriesNo
warningCountYes

TDQS

A3.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare readOnlyHint=true and idempotentHint=false, and the description adds useful behavioral context: refresh=true forces a rescan of skills directories. This goes beyond the schema and annotations by disclosing a potentially expensive or state-changing behavior within an otherwise read-only operation. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two brief sentences with no filler. The first sentence front-loads the core purpose and returned data, and the second adds the key behavioral option. Every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present and annotations covering read-only behavior, the description is sufficient for an agent to understand the tool's role. The main gap is the undocumented include_warnings parameter, but given the low complexity and existing output schema, the description is still reasonably complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry parameter meaning. It explicitly explains refresh=true, but it does not explain include_warnings, which is only vaguely implied by the mention of frontmatter warnings. With two parameters and one undocumented, the description only partially compensates for the lack of schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the tool as providing catalog statistics, listing specific data points (libraries, skill count, files, last scan, frontmatter warnings). It is easily distinguished from sibling tools that search, list, or fetch specific catalog items, though it does not explicitly name the verb 'get' or 'retrieve'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies this tool is for inspecting catalog health and statistics rather than searching or listing resources, but it does not explicitly state when to prefer it over siblings or provide exclusions. The refresh hint gives one concrete usage scenario, but overall guidance is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

skills_get_skillLoad a skills skillA
Read-onlyIdempotent

Load a skill's full SKILL.md instructions and its bundled file list. Follow the instructions as if the skill were installed. Use skills_read_skill_file for referenced files.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYesSkill name (when unique), '<library>/<name>', or the full skill path from search results
include_frontmatterNoInclude the raw frontmatter metadata block

Output Schema

ParametersJSON Schema
NameRequiredDescription
uriYes
nameYes
pathYes
filesYes
trustNo
libraryNo
categoryNo
riskFlagsYes
rootOnDiskNo
descriptionYes
frontmatterNo
instructionsYesSKILL.md body without frontmatter
scriptsWithheldNo

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already mark the tool read-only and idempotent, so the description's main behavioral addition is the directive to 'follow the instructions as if the skill were installed,' which is important context for how the returned content should be treated. It also discloses that the tool returns both SKILL.md content and a bundled file list.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, each earning its place: the core action, the behavioral directive to follow the instructions, and the sibling-tool routing. No filler or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-parameter tool with a full output schema and read-only/idempotent annotations, the description covers the essential behavior, the instruction-following expectation, and the only likely workflow question (how to read referenced files). Nothing critical is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%: both name formats and include_frontmatter semantics are documented in the schema. The description adds no extra parameter detail beyond naming the bundled file list, so the baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Load a skill's full SKILL.md instructions and its bundled file list.' This clearly identifies the tool's function and distinguishes it from siblings like skills_read_skill_file by noting that referenced files are handled separately.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear usage context—load the full skill content and follow it as if installed—and explicitly routes referenced-file access to skills_read_skill_file. It does not spell out when to prefer this over search or list tools, but the distinction is implied well enough.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

skills_list_categoriesList skills skill categoriesA
Read-onlyIdempotent

All categories with skill counts, plus value chains. Optionally scoped to one library.

ParametersJSON Schema
NameRequiredDescriptionDefault
libraryNoOptional library filter (single-library server: ignored).

Output Schema

ParametersJSON Schema
NameRequiredDescription
categoriesYes
totalSkillsYes
valueChainsYes

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description adds useful behavioral detail beyond annotations: it returns all categories, includes skill counts, and also includes value chains. The read-only and idempotent annotations already cover safety, so the description's additional output-scope details are valuable without contradicting annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and front-loaded: it states the primary output immediately, then adds the optional scoping. Every sentence earns its place with no redundancy or filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple list operation with one optional parameter, a strong output schema, and annotations covering read-only/idempotent behavior, the description fully covers what an agent needs to select and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% for the only parameter, library, and the schema already explains it as an optional filter with a single-library-server caveat. The description's mention of optional library scoping adds nothing beyond the schema, so baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool lists all categories with skill counts and value chains, with optional library scoping. This distinguishes it from sibling tools like skills_list_skills or skills_search_skills by focusing on category-level aggregation rather than individual skill retrieval.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description makes the context clear: use this tool when you need a category overview with counts and value chains. It does not explicitly name alternatives or provide exclusionary guidance, but the distinction from siblings is evident from the resource type it targets.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

skills_list_librariesList skills librariesA
Read-onlyIdempotent

Skill libraries served by this server, with namespace, root directory and skill counts. Namespaces are the first segment of skill:// URIs.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
librariesYes

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and openWorldHint: false. The description adds useful context by specifying the output contents (namespace, root directory, skill counts) and explaining that namespaces are the first segment of skill:// URIs. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single sentence conveys the resource type, scope, output details, and a useful naming convention. There is no filler, and every phrase adds meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a parameterless, read-only tool with an output schema, the description fully covers what the tool returns and how namespaces relate to URIs. An agent has enough information to select and invoke this tool correctly without additional details.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With zero parameters and 100% schema description coverage, the baseline is 4. There are no parameter semantics to document, so the description does not need to compensate for any parameter gaps.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('List') and resource ('skill libraries') and clearly indicates what the result contains: namespace, root directory, and skill counts. This distinguishes it from sibling tools like skills_list_skills, which would list individual skills rather than libraries.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The intended use is implied: call this when you need an inventory of skill libraries and their namespaces/root directories. However, there is no explicit statement of when to prefer this over alternatives such as skills_list_skills or skills_list_categories, nor any when-not-to-use guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

skills_list_skillsList skills skillsA
Read-onlyIdempotent

Compact catalog listing (name + one-line description), optionally filtered by library and/or category (see skills_list_categories). Paginated with cursor.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
cursorNoOpaque cursor from a previous call
libraryNoOptional library filter (single-library server: ignored).
categoryNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
totalYes
skillsYes
nextCursorNo

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint and idempotentHint, so the safety profile is covered. The description adds useful behavioral context: results are compact name+description entries, pagination uses a cursor, and filtering is optional.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single, tight sentence that front-loads the core purpose, then states filters and pagination. Every clause adds useful information with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only listing tool with an output schema, annotations, and a simple parameter set, the description covers output shape, filtering options, and pagination. It is sufficient for an agent to invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With only 50% schema description coverage, the description compensates by clarifying that library and category are optional filters and that pagination uses a cursor. It does not add meaning for the limit parameter, though the schema's default/min/max constraints provide partial guidance.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it lists skills as a compact catalog, with name and one-line description, and that filters by library/category are available. It distinguishes itself from get_skill and read_skill_file through the 'catalog listing' framing, though it does not explicitly contrast itself with skills_search_skills.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for browsing/enumerating skills and points to skills_list_categories for category context, which is helpful. However, it never explicitly says when to use this list tool instead of skills_search_skills or get_skill, leaving the routing decision partly to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

skills_read_skill_fileRead a skills skill fileA
Read-onlyIdempotent

Read a file bundled with a skill, by skill name/path and relative path (e.g. 'references/troubleshooting.md', 'scripts/run.py'). Binary files are returned base64-encoded.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYes
pathYesPath relative to the skill directory

Output Schema

ParametersJSON Schema
NameRequiredDescription
uriYes
pathYes
sizeYes
textNo
base64No
digestYes
mimeTypeYes

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already declare readOnlyHint, idempotentHint, and openWorldHint, covering safety. The description adds useful behavioral detail beyond annotations: binary files are returned base64-encoded, which materially affects how an agent consumes the response.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with no filler: purpose, parameter behavior, examples, and special return encoding are all included efficiently. Information is front-loaded and every clause earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple two-parameter read tool with strong annotations and an output schema, the description is nearly complete. The only gap is the exact format or scope of the 'name' param, which keeps it from being fully self-contained.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is only 50% and the description partially compensates by explaining relative paths with examples. However, the 'name' parameter remains somewhat ambiguous ('skill name/path'), and the description does not clearly specify whether it accepts a name, a path, or both.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Read') and a clear resource ('a file bundled with a skill'), and explains the addressing scheme with concrete examples. This clearly differentiates it from sibling tools like skills_get_skill, skills_search_skills, and skills_list_skills.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The intended use is implied by the description and examples, but there is no explicit guidance on when to use this tool versus alternatives like skills_get_skill. For a small sibling group, an explicit 'when to use' statement would strengthen the definition.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

skills_search_skillsSearch skills skillsA
Read-onlyIdempotent

Ranked search over the skills skill library by task, keyword, or trigger phrase. Returns name, description, category. Call this first, then skills_get_skill to load one.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
queryYesWhat you need to do, e.g. 'A/B test sample size', 'level 10 meeting', 'invoice reconciliation'
libraryNoOptional library filter (single-library server: ignored).
categoryNoOptional exact category filter (see skills_list_categories)
value_chainNoOptional value-chain filter, e.g. 'infrastructure'

Output Schema

ParametersJSON Schema
NameRequiredDescription
hintNo
resultsYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and idempotentHint=true, so the safety profile is covered. The description adds meaningful behavioral detail: results are ranked, and the return surface is name/description/category. This is useful context beyond the structured annotations and schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences carry the core purpose, query semantics, return fields, and the recommended workflow. There is no filler, and the most important information is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only search tool with an output schema, rich parameter descriptions, and readOnly/idempotent annotations, the description is complete. It tells the agent what to search on, what will be returned, and how to proceed to the next step. No critical missing context prevents correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is high (80%), so the schema already documents parameters well. The description adds value by clarifying what kinds of query terms are expected ('task, keyword, or trigger phrase'), which is a semantic cue not fully captured by the schema's 'What you need to do' phrasing. It does not need to repeat every parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Ranked search'), a clear resource ('the skills skill library'), and the input modalities ('by task, keyword, or trigger phrase'). It also differentiates from siblings by prescribing the follow-up flow to skills_get_skill. The awkward title does not undermine the clarity of the description.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly tells the agent when to use this tool first in a workflow ('Call this first, then skills_get_skill to load one'). It provides clear context for using search as an entry point, but it does not explicitly state when to prefer a sibling like skills_list_skills instead, so it stops short of full when/when-not guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 7 tool updatesv1.2.0
    • First observedskills_catalog_status
    • First observedskills_get_skill
    • First observedskills_list_categories
    • First observedskills_list_libraries
    • First observedskills_list_skills
    • First observedskills_read_skill_file
    • First observedskills_search_skills

TDQS

A4.2/5.0

Scored across 7 tools

Disambiguation5/5

Each tool has a distinct responsibility: search, list skills, list libraries, list categories, load a skill, read a bundled file, and check catalog status. Search and list are clearly differentiated by ranked querying versus catalog enumeration, and get vs read file are separated by skill-level vs file-level access.

Naming Consistency4/5

Tools consistently use a skills_ prefix with snake_case and a mostly verb_noun pattern (search_skills, list_skills, get_skill, read_skill_file). The only minor deviation is skills_catalog_status, which is noun-based rather than verb-based, but the overall pattern remains predictable.

Tool Count5/5

Seven tools is well-scoped for a skills library server: discovery, retrieval, browsing metadata, and status management are all covered without unnecessary redundancy. Each tool earns its place in the workflow.

Completeness5/5

The tool surface covers the full skill-consumption workflow: discover skills via search or listing, navigate libraries and categories, load the full SKILL.md, read referenced files, and verify catalog health. No significant gaps are apparent for the stated purpose of serving skills.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to discover, install, and manage SKILL.md skills from a Git-backed registry via MCP tools for search, install, and list operations.
    8 npm
    2
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Discovers and manages portable agent capabilities (skills and MCP servers) from configurable collections, providing search, inspection, and local installation via CLI and MCP tools.
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables MCP-capable agents to search, inspect, lint, and safely install Agent Skills from the skillmd registry mid-conversation.
    2 npm
    34
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables MCP clients to discover, search, and load agent skills from many sources, including other agents' skill directories, Claude Code plugin marketplaces, and remote git repositories, with no copying or syncing required.
    1,013 npm
    6
    MIT