Skills Registry
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Skills Registrysearch for a skill that helps write API documentation"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Skills Registry
A personal, read-only MCP skill registry: one GitHub repository, one Cloudflare Worker, four stable MCP tools. Every approved push builds and deploys a validated snapshot; every client keeps the same endpoint URL.
Architecture
Deployment-time publishing, not runtime GitHub fetching:
flowchart TD
G["GitHub repository"] --> V["CI validation and snapshot build"]
V --> D["Deploy Worker at stable URL"]
C["AI clients"] --> A["MCP authentication"]
A --> W["Worker: four registry tools"]
D --> W
W --> S["Bundled index and skill files"]
V --> F["Validation failure: retain existing deployment"]The Worker embeds a generated catalog (src/generated/catalog.json) built
from one exact Git commit. It answers MCP requests with no GitHub API
calls, no database, and no session state — see src/worker.ts.
Related MCP server: skillet
Repository layout
Path | Purpose |
| Metadata (YAML frontmatter) + primary instructions |
| Supporting Markdown/text, explicitly allowlisted per skill |
| Supporting shell/Python/JS/TS source, same allowlist rules, never executed by the registry |
| Schema, allowed file types, size limits, search/pagination defaults |
| Worker, GitHub token check, MCP dispatch, tools, search, registry loader |
|
|
| Contract, search, retrieval, revision, security, auth tests |
| Per-client connection steps and the shared bootstrap instruction |
|
|
src/generated/ is a build output (gitignored), regenerated by CI from the
exact checked-out commit. It is never a second source of truth.
Authoring a skill
Create
skills/<id>/SKILL.mdwith frontmatter:--- schema_version: 1 id: your-skill-id name: Human Readable Name description: One sentence, used in search and listings. version: 1.0.0 tags: [development] keywords: [relevant, terms] requires: [] # capabilities the client needs; does not grant them files: [] # explicit retrieval allowlist, relative to the skill dir required_zdr: false # true = only served to callers cleared for Zero Data Retention (see "Data handling") --- # When to use ... # Instructions ...idmust be a stable lowercase slug matching the directory name (^[a-z0-9]+(-[a-z0-9]+)*$) — treat renaming it as a breaking change.List every supporting file under
files; anything on disk that isn't listed fails validation, and anything listed that doesn't exist also fails.Bump
version(semver) whenever the body or a supporting file changes — CI computes a content hash against the base branch and fails if the version didn't move.Run
npm run validatelocally before pushing.If a skill's instructions genuinely need a local script, put it under
scripts/(.sh,.py,.js,.ts,.rb— seeregistry.config.jsonallowed_script_extensions) and declare it infileslike any other supporting file. It is served as plain text viaskills_get_filewithexecutable: true; the registry never runs it. State plainly in the skill's own instructions that the client must read the script before running it under its own local execution permissions.If a skill's content must never leave a Zero-Data-Retention-cleared provider, set
required_zdr: true. See "Data handling" below — this is an access-control decision, not a content-type decision, so it applies regardless of file type.
Local development
npm install
npm run validate # metadata, duplicate IDs, file allowlist, size, version bumps
npm run build # generates src/generated/catalog.json from the working tree
npm run typecheck
npm test # unit tests against fixture catalogs, independent of build output
npm run dev # wrangler dev, serves the just-built snapshot locallyMCP interface
Four tools, all read-only/non-destructive, all returning api_version,
registry_sha, and published_at:
Tool | Purpose |
| Paginated metadata, optional |
| Ranked keyword/tag search with match reasons and excerpts |
| Metadata, primary instructions, supporting-file manifest for one skill |
| Bounded, paginated text from one allowlisted supporting file |
revision means the registry's Git SHA, not a skill's semantic version. An
omitted revision targets the active snapshot; a stale one returns
REVISION_UNAVAILABLE naming the active SHA so a client restarts discovery
instead of mixing revisions. Application errors (SKILL_NOT_FOUND,
FILE_NOT_FOUND, INVALID_PATH, REVISION_UNAVAILABLE, INVALID_CURSOR)
come back as a normal tool result with isError: true, never disguised as
an empty search result. Malformed arguments are JSON-RPC -32602 errors.
Search is deterministic keyword/tag matching (src/search.ts): exact
id/name match ranks highest, then tag, then keyword, then description
substring, tie-broken by id. No embeddings, no invented relevance.
Every skill's metadata carries required_zdr (see "Data handling"), and
every file manifest entry carries executable (true for anything under
scripts/). A caller not cleared for required_zdr skills doesn't see
them at all — skills_list/skills_search omit them, skills_get/
skills_get_file return SKILL_NOT_FOUND rather than a permission error,
so their existence isn't leaked to callers who can't read them.
Authentication
Decision: direct GitHub personal-access-token validation, no OAuth App,
no redirect flow. A client sends a GitHub PAT as its bearer token; the
Worker asks GitHub who it belongs to (one call to api.github.com/user,
see src/github.ts) and checks the returned login against
ALLOWED_GITHUB_LOGINS. See src/worker.ts's resolveCaller.
This replaced an earlier full OAuth-provider design (GitHub OAuth App +
/authorize + /github/callback + @cloudflare/workers-oauth-provider
issuing its own per-client tokens). For a single-operator personal registry
where every client can be configured with a static bearer/API-key header
(Claude Code, Codex, OpenCode all support this), that was more machinery
than the problem needed: no OAuth App to register, no callback URL, no KV
namespace for grant storage, no DCR/CIMD/PKCE surface to maintain. The
tradeoff is Claude Web / ChatGPT Web connectors, which are generally built
around an interactive "Authorize" redirect button rather than a pasted
token field — verify each one actually has a usable auth field before
relying on it (see "Open questions still unresolved").
Setup required before this works (not done yet — Cloudflare account secrets were just added, GitHub PAT/allowlist still need setting):
Create a GitHub personal access token (github.com → Settings → Developer settings → Personal access tokens). No scopes are required — this only reads the token owner's public identity via
/user.wrangler secret put ALLOWED_GITHUB_LOGINS— your GitHub username(s), comma-separated. OptionallyZDR_GITHUB_LOGINS(see "Data handling").Configure each client with
Authorization: Bearer <your PAT>— seedocs/client-setup.md.
Optional service-token path. A static bearer token
(SKILLS_READ_TOKENS) is checked first, before the GitHub lookup, so
automation (the deploy workflow's smoke test, a script) doesn't need to
hold your actual GitHub credential. It's equivalent in privilege to a
GitHub-authenticated call, not a separate tier.
Cloudflare deployment credentials (CLOUDFLARE_API_TOKEN,
CLOUDFLARE_ACCOUNT_ID) live only in CI secrets — never in client
configuration or tool responses. Your GitHub PAT lives only in your own
client configs and is never sent anywhere but GitHub's API and this Worker.
Data handling: required_zdr
Some skills may describe workflows unsuitable for disclosure to every
connected AI provider (per your answer to the open question below). Those
skills set required_zdr: true in frontmatter. A caller — a GitHub
identity or a static token — is cleared to see them only if it's in
ZDR_GITHUB_LOGINS or SKILLS_ZDR_TOKENS respectively; both default to
empty, so clearance is opt-in and fails closed. This is configured per
client, exactly as asked: a client whose connected provider has no ZDR
agreement simply never gets a login/token added to those lists, and the
skill doesn't exist as far as it can tell (see "MCP interface" above for
why that's SKILL_NOT_FOUND, not a permission error).
required_zdr is an access-control flag, not a content-type flag — it
applies the same way to a Markdown instruction file or a scripts/ file.
Publishing
Edit a skill, bump its version.
Push/merge to
main.validate.ymlruns on PRs and pushes;deploy.ymlruns only on push tomain: validate → build snapshot from the exact commit → typecheck → test →wrangler deploy→ smoke test.A failed step leaves the previous deployment live. Deploys are serialized (
concurrency: production-deploy) so an older build can never overwrite a newer one.Rollback: re-run
deploy.ymlfrom a previous green commit (orwrangler rollbackif using Cloudflare's built-in version history).
"Latest" means latest successfully deployed commit, not necessarily branch HEAD.
Security notes
Skill files are restricted to
.md/.txt(or, underscripts/,.sh/.py/.js/.ts/.rb) under configured size limits; retrieval rejects path traversal, absolute paths, and anything outside a skill's declaredfilesallowlist (src/tools.ts, tested intests/tools.test.ts).No skill script is ever executed by validation, the build, or retrieval —
scripts/files are served as inert text with anexecutableflag; a client chooses whether to run one, under its own local permission model.requiresin frontmatter documents what a skill's instructions assume a client has access to — it grants nothing by itself.Retrieved content still enters the connected AI provider's context. Don't put secrets or incident-specific sensitive detail into a skill.
Registry instructions are not higher priority than system instructions, user authorization, or local access controls — the bootstrap text in
docs/client-setup.mdsays this explicitly so it travels with every client's standing instructions.
Resolved decisions
Auth mechanism: direct GitHub PAT validation against
api.github.com, no OAuth App, no redirect flow — see "Authentication" above. (An earlier pass built a full OAuth-provider design; it was replaced once a simpler option was pointed out — see git history if you want the OAuth version.)Client surfaces: all of them — Claude Web, Claude Code, Codex CLI and hosted surfaces, OpenCode, and ChatGPT Web. Claude Code, Codex, and OpenCode all take a static bearer/API-key header in their MCP config, so a pasted GitHub PAT works directly. Claude Web and ChatGPT Web connectors are generally built around an interactive OAuth "Authorize" redirect rather than a pasted-token field — confirm each actually has a usable auth field for this before relying on it (see "Open questions still unresolved"). If one doesn't, the OAuth-provider design is the fallback.
Publish freshness: "publish after CI finishes" is fine as-is; no change made.
Local scripts/binaries: text scripts (
.sh/.py/.js/.ts/.rb) are now supported underscripts/, served as plain text with anexecutableflag, never run by the registry — see "Authoring a skill" step 6. Compiled/binary assets remain out of scope. A binary can't be reviewed by a human or model before a client runs it, which defeats the trust model this whole registry depends on (retrieval never grants execution); it also adds integrity/signing and storage concerns this registry doesn't currently handle. If a real skill turns out to need a binary, the right shape is a separate, explicitly-confirmed release channel with signed artifacts — notskills_get_file— and should be designed against a concrete skill that needs it, not speculatively.Long-running tasks spanning a deploy: staying with active-snapshot- only for now. The existing
REVISION_UNAVAILABLEbehavior already keeps a client from silently mixing revisions mid-task — it just has to restart discovery. No evidence yet that this is a real problem for any actual task; revisit with historical-revision serving only if one shows up.ZDR-sensitive skills: yes, some may need this — see
required_zdrunder "Data handling" above.
Open questions still unresolved
Does the Claude Web and ChatGPT Web connector UI actually expose a field for a static bearer token / API key, or only an OAuth "Authorize" button? Not yet verified — no live deployment exists yet to test against.
Explicitly out of scope for now
Embeddings/full-text search, a database, an admin UI, remote execution, automatic native-skill installation, write tools, one-tool-per-skill registration, runtime GitHub sync, historical-revision retrieval, and compiled/binary skill assets.
This server cannot be deployed
Maintenance
Related MCP Connectors
Read-only MCP tools for AI agent discovery, structured resources, and NIULAI information.
A registry of 5,900+ peer-authored skills any MCP agent can search and load on demand.
Governed AI agent skills — one library, distributed to devs and exposed to remote agents over MCP.
Search and discover Agent Skills from the skills.sh registry. Powered by HAPI MCP server.
Related MCP Servers
- AlicenseAqualityCmaintenanceConverts AI Skills (following Claude Skills format) into MCP server resources, enabling LLM applications to discover, access, and utilize self-contained skill directories through the Model Context Protocol. Provides tools to list available skills, retrieve skill details and content, and read supporting files with security protections.328Apache 2.0
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to discover, install, and manage SKILL.md skills from a Git-backed registry via MCP tools for search, install, and list operations.4 npm1MIT
- AlicenseNot gradedqualityBmaintenanceAn MCP server that enables AI agents to interact with the SkillShare registry, including searching, reading, creating, and managing resources like skills, MCP configurations, and notes.MIT
- AlicenseNot gradedqualityCmaintenanceEnables MCP clients to infer and resolve skills from text, normalize skill names, perform semantic taxonomy searches, and inspect lifecycle governance.MIT