Skip to main content
Glama
cubicecho

MCP Skills Manager

by cubicecho

MCP Skills Manager

An MCP server that serves skills — reusable markdown documents (instructions, playbooks, references) — to agents. Write and organize skills in a web UI; agents load them over MCP.

Every skill is exposed two ways at once:

  • as an MCP tool — calling it returns the skill's markdown body (plus a note listing any bundled supporting files), so an agent can pull a skill on demand;

  • as an MCP resource at skill://<name> — for clients that browse and attach resources. Bundled supporting files are resources too (skill://<name>/<path>), the endpoint advertises RFC 6570 resource templates with argument completion (skill names and file paths), and resources/list is paginated. Clients that subscribe get live updates (resources/list_changed + resources/updated) when skills change on disk — always over stdio, and over HTTP when httpLiveUpdates is enabled.

Every endpoint also serves two meta-tools for discovery: list_skills returns a text catalogue of the available skills, one line each — name, tags, supporting files, last change and description — with no bodies, and search_skills filters that catalogue by a free-text query and/or tags, so an agent can find what's relevant by intent before loading anything.

Skills are advertised as tools in one of two modes (a global default, optionally overridden per workspace): per-skill (the default — one no-arg tool per skill) or loader (a single load_skill(name) tool that keeps the tool footprint fixed no matter how many skills exist).

Agents can also author skills over MCP: every endpoint exposes authoring tools (create_skill, update_skill, edit_skill, rename_skill, delete_skill, plus supporting-file tools) so an agent can write and refine its own skills. edit_skill swaps exact passages of a skill's body or of one of its supporting files, so a small change does not resend the whole text. These are gated on a setting (on by default), and disabledAuthoringTools in settings.json leaves named tools out while the rest stay. To protect an individual skill, mark it read-only (the toggle in the skill editor, or readonly: true in its frontmatter): agents can still load it, but every authoring tool refuses to edit, rename or delete it — or, for a directory skill, anything in its folder.

The root endpoint /mcp serves all skills. Workspaces are named subsets served at their own endpoint /mcp/w/<slug>, so you can hand a specific agent just the skills it needs. Everything is also available over stdio for local clients.

Features

  • 📝 Markdown editor in the web UI with live split-pane preview

  • 🗂️ Skill CRUD — create, rename, edit, delete, import (.md/dir/.zip), and export skills; drag-and-drop supporting files for directory-format skills

  • 🌿 Git-linked skills — point a skill at a folder in a git repo and pull the newest version with Sync now

  • 🧩 Workspaces — group skills into filtered endpoints

  • 🔍 Discovery meta-tools — list_skills and search_skills on every endpoint, plus tags on skills for organising and filtering

  • 🤖 MCP authoring — agents can create and refine their own skills over MCP

  • ⌨️ Skill prompts — a skill's prompts/*.md files are served as MCP prompts, so a client can start the skill from a slash command

  • 🔧 Tool modes — advertise skills as one tool each (per-skill) or a single load_skill loader, globally or per workspace

  • 📁 Two skill formats — a flat <name>.md file, or a <name>/SKILL.md directory (Claude Code convention) with supporting files

  • 🔌 HTTP and stdio transports

  • 📡 Live resource updates — subscribers are notified when skills change on disk (always over stdio; over HTTP with httpLiveUpdates)

  • 🗃️ Flat-file config — hand-editable on disk, watched and hot-reloaded

  • 🔐 Bearer-token auth guarding the API and MCP endpoints

Related MCP server: MCP Skill Registry

Quick start

npm install
npm run dev          # server on :3001, web UI on :3000 (proxies to the server)

Open http://localhost:3000. On first run a bearer token is generated into data/config/settings.json and printed to the server logs; paste it into the web UI when prompted.

Production

npm run build        # shared → server → app
npm run start        # HTTP server on :3000, also serving the built web UI

Or with Docker:

docker compose up --build      # mounts ./data at /data, serves on :3000

Connecting an agent

HTTP

Point an MCP client at the streamable-HTTP endpoint:

  • All skills: http://localhost:3000/mcp

  • A workspace: http://localhost:3000/mcp/w/<slug>

Send the bearer token as Authorization: Bearer <token>.

stdio

Run the packaged stdio entry (installed as the mcp-skills-stdio bin):

# all skills
mcp-skills-stdio --data-dir /path/to/data

# only a workspace's skills
mcp-skills-stdio --data-dir /path/to/data --workspace <slug>

Example Claude Desktop / MCP client config:

{
  "mcpServers": {
    "skills": {
      "command": "mcp-skills-stdio",
      "args": ["--data-dir", "/path/to/data", "--workspace", "backend"]
    }
  }
}

Skills on disk

Skills live under DATA_DIR/skills/ in either of two shapes:

Flat file — DATA_DIR/skills/commit-messages.md:

---
name: commit-messages
description: Write clear, conventional git commit messages.
---

# Writing good commit messages

...the skill body...

Directory (Claude Code convention) — DATA_DIR/skills/pdf-forms/SKILL.md plus any supporting files (reference.md, scripts, templates). Supporting files are listed on the skill and referenced in the rendered tool output so the agent knows they exist.

The frontmatter name must be a slug: lowercase letters, digits, ., _, - (max 64 chars). description is surfaced as the MCP tool/resource description. An optional tags key (a comma-separated string or a YAML list) organises skills and feeds the search_skills filter. readonly: true stops agents from modifying the skill over MCP (the web UI and REST API can still edit it). Unknown frontmatter keys are preserved across round-trips.

Skill prompts

A directory skill can carry prompts: each Markdown file directly inside its prompts/ folder is served as an MCP prompt named after the file, on every endpoint that serves the skill. Clients that support MCP prompts offer them as slash commands (Claude Code shows prompts/refactor.md as /mcp__<server>__refactor), so a prompt is the way to start a skill by name instead of asking the agent to go and find it.

DATA_DIR/skills/coding-standards/prompts/refactor.md:

---
description: Refactor a scope with the coding-standards workflow
arguments:
  - name: scope
    description: Files, directory or concept to refactor
    required: false
---
Refactor: {{scope}}

Load the `coding-standards` skill and follow its refactor workflow.
  • The frontmatter is optional. description is shown in the client's prompt list; arguments declares what the prompt takes (name, and optionally description and required).

  • {{name}} in the body is replaced by the argument of that name. An optional argument that is left out becomes an empty string; a required one that is left out fails the request. A placeholder that names no declared argument is left as written.

  • The body is returned as a single user message.

  • The file name (without .md) is the prompt name, so it must be letters, digits, - and _. Other files in prompts/, and files in its sub-folders, are ordinary supporting files.

  • When two served skills have a prompt of the same name, the first skill by sorted name keeps the bare name and each later one is served as <skill>__<name>.

  • Prompt files are written and read like any other supporting file (write_skill_file, the web UI, or on disk). They are left out of the "bundled supporting files" list an agent sees when it loads the skill.

Git-linked skills

A skill can be linked to a folder in a git repo — a published skill you want this server to track rather than copy once. In the web UI use New skill → From Git, or the Link to Git source button on an existing skill (which replaces its content). Give a clone URL, optionally a branch or tag (blank follows the default branch) and the folder holding the SKILL.md (blank is the repo root); pasting a GitHub/GitLab folder link such as https://github.com/owner/repo/tree/main/skills/my-skill fills all three.

The link is stored in the skill's frontmatter, with the commit the last sync fetched:

---
name: my-skill
description: …
source:
  repo: https://github.com/owner/repo
  ref: main            # optional
  path: skills/my-skill  # optional
  commit: 4f2a9c1…     # written by sync
  syncedAt: 2026-10-04T12:00:00.000Z
---
  • Sync is manual. Sync now (or POST /api/skills/:name/sync) replaces the skill's whole folder with the repo's copy; files removed upstream are removed here. Nothing syncs on a timer.

  • Linked skills are locked. Their content cannot be edited in the web UI, over the REST API or by agents over MCP. Unlink keeps the current content and makes the skill a normal, editable one again.

  • Local settings survive a sync: the skill's id here (even when the repo's SKILL.md names it differently), global, readonly and workspace membership. The skill can still be renamed and deleted.

  • Repo URLs must be https://…, ssh://… or user@host:path. An https URL may not embed a username or token — the URL is written into SKILL.md, which agents read. Symlinks in the repo folder are skipped, and a folder may hold at most 500 files.

  • Private repos use the credentials of the machine (or container) running the server: an SSH key the server's user can read, or a git credential helper for https. git never prompts — a repo it cannot read fails the sync with git's own error. With Docker, mount a key and known_hosts (e.g. -v ~/.ssh:/root/.ssh:ro).

  • git must be on the server's PATH. The Docker image ships it.

Workspaces

A workspace is a JSON file at DATA_DIR/config/workspaces/<slug>.json:

{
  "name": "Backend",
  "slug": "backend",
  "enabled": true,
  "description": "Skills for backend work.",
  "skills": ["commit-messages", "pdf-forms"]
}

It is served at /mcp/w/backend (HTTP) and via --workspace backend (stdio). Disabled workspaces return 404. An optional skillToolMode (per-skill or loader) overrides the global default for this workspace only. Manage workspaces from the Workspaces page in the web UI, or edit the files directly — changes are picked up automatically.

Configuration

Everything lives under DATA_DIR (default ./data):

data/
├── config/
│   ├── settings.json          # port, auth token, auth/authoring toggles, tool mode, httpLiveUpdates
│   └── workspaces/
│       └── <slug>.json        # one file per workspace
└── skills/
    ├── <name>.md              # flat-file skill
    └── <name>/                # directory-format skill
        ├── SKILL.md
        └── <supporting files>

Edits on disk are watched (debounced) and hot-reloaded; you can also force a re-read with the Reload button on the Settings page or POST /api/reload.

Environment variables

Variable

Default

Description

DATA_DIR

./data

Root directory for skills and config

PORT

3000 (or settings.json)

HTTP listen port

MCP_SKILLS_TOKEN

—

Bearer token; overrides the one in settings.json

SECURE_LOCAL_NET

false

Set true to disable auth entirely (trusted networks only)

HTTP_KEEP_ALIVE_TIMEOUT_MS

75000

How long an idle client connection stays open (Node's own default is 5 s). Keep it above the idle timeout of any reverse proxy in front; 0 never closes one

If no token is configured and SECURE_LOCAL_NET is not set, a random token is generated into settings.json on first run and logged.

HTTP API

All routes require the bearer token (unless SECURE_LOCAL_NET=true).

Method

Path

Description

GET

/api/status

Version, uptime, skill/workspace counts, auth mode, port

GET

/api/settings

Read settings (auth/authoring toggles, tool mode, live updates)

PATCH

/api/settings

Update authoringEnabled / disabledAuthoringTools / skillToolMode / httpLiveUpdates

GET

/api/skills

List skills (summaries)

POST

/api/skills

Create a skill

POST

/api/skills/import

Import an uploaded .md / directory / .zip

POST

/api/skills/import-git

Create a skill linked to a folder in a git repo

GET

/api/skills/:name

Get a skill (with body)

PATCH

/api/skills/:name

Update body/description/tags/global/readOnly, or rename

DELETE

/api/skills/:name

Delete a skill

PUT

/api/skills/:name/source

Link a skill to a git source and replace its content from it

DELETE

/api/skills/:name/source

Unlink it, keeping its content

POST

/api/skills/:name/sync

Re-fetch a linked skill → { skill, changed }

GET

/api/skills/:name/export

Download the skill as a .zip

GET

/api/skills/:name/files/content?path=

Read one supporting file

PUT

/api/skills/:name/files

Add/overwrite a supporting file (promotes to a dir)

POST

/api/skills/:name/folders

Create an empty sub-folder

POST

/api/skills/:name/files/move

Rename / move a file or folder

DELETE

/api/skills/:name/files?path=

Delete a file or folder

GET

/api/workspaces

List workspaces

POST

/api/workspaces

Create a workspace

GET

/api/workspaces/:slug

Get a workspace

PATCH

/api/workspaces/:slug

Update a workspace

DELETE

/api/workspaces/:slug

Delete a workspace

POST

/api/reload

Re-read all config from disk

ALL

/mcp

MCP endpoint — all skills

ALL

/mcp/w/:slug

MCP endpoint — a workspace's skills

Architecture

Monorepo with npm workspaces:

  • shared/ — zod schemas and inferred types; the single source of truth for config-file shapes and REST DTOs.

  • server/ — Express 5 + the MCP TypeScript SDK. A ConfigStore owns the flat-file state (atomic writes, chokidar watching); the gateway builds an MCP Server exposing skills as tools and resources over HTTP (StreamableHTTPServerTransport — stateless by default, or stateful sessions with SSE push when httpLiveUpdates is on) or stdio.

  • app/ — React 19 + Vite + shadcn/ui + TanStack Router/Query. The markdown editor uses react-markdown + remark-gfm.

See AGENTS.md for development conventions.

License

MIT

Related MCP Connectors

Related MCP Servers