Skip to main content
Glama

๐Ÿ“š mcp-kb

An MCP knowledge base. Skills, prompts and agent material collected from git, WebDAV and folders into one catalogue, served as resources โ€” or as tools, for clients without them. ๐Ÿ—‚๏ธ

๐Ÿงช Test ๐Ÿ›ก๏ธ Quality ๐Ÿ“ธ Image Builder ๐Ÿ“– Wiki License: MIT Docker FastMCP


The whole idea, in one breath

Your Agent Skills and prompts are scattered โ€” a plugin marketplace on GitHub, a folder somebody edits in Nextcloud, a ConfigMap in the cluster. Name them in one config file and every agent reads them from one address space, over MCP.

sources (git ยท WebDAV ยท folders)  โ”€โ”€โ–ถ  plugins  โ”€โ”€โ–ถ  libraries  โ”€โ”€โ–ถ  agents (resources, or tools)

The image bakes nothing. A source is a dependency โ€” a URL and a credential that is a reference to an environment variable rather than a value โ€” and a plugin is a folder under one, pinned to a commit, fetched at container start into a cache volume. Point a library at somebody's marketplace.json and its plugins arrive already split, with the categories and tags they publish. ๐Ÿช„


Related MCP server: skillsmcp

๐Ÿ”ค One address space

Everything served is a skill:// URI, and reading one is the only operation there is. The grammar follows the MCP Skills extension: the library and folder are a prefix, and the last segment before the file is always the skill's name.

skill://<library>/_index.md                  an index: the library's folders and skills
skill://<library>/<folder>/_index.md         the same, one folder down
skill://<library>/<folder>/<skill>/SKILL.md  that skill's instructions
skill://<library>/<folder>/<skill>/_manifest what else it ships
skill://<library>/<folder>/<skill>/<path>    one of those files
skill://<library>/_files.md                  what the library ships outside its skills
skill://<library>/<path>                     one of those files

A library, a folder or a skill's own directory is not a file โ€” reading one is not found, and the error names the file to read instead. Progressive disclosure lives in the addresses rather than in a tool list, so a listing is a dozen index rows whether the catalogue holds nine skills or ninety.

Skills-aware clients can also use skills/list and skills/get. The server advertises io.modelcontextprotocol/skills and returns each skill's frontmatter and complete file manifest, with SHA-256 digests of the bytes served by resources/read. The same library, category and tag scopes apply; ordinary resource listings stay small. Connect to /mcp and enable Skills in your client โ€” no ?skills=full is needed for this extension.

๐Ÿ“– Skills


๐Ÿชž Resources first, tools as a mirror

MCP already has a primitive for material an agent reads, and it is the resource. So resources are the interface, and a client that speaks them sees no tools at all.

Many clients only implement tools โ€” n8n's MCP Client Tool above all โ€” and to those a resource-only server looks empty. They get the same interface as four tools, revealed with ?resources=off, ?prompts=off, or both:

Tool

Mirrors

list_resources()

resources/list โ€” the same uri/name/description rows

read_resource(uri)

resources/read โ€” the same URI

list_prompts()

prompts/list โ€” each prompt's name, description and arguments

get_prompt(name, arguments)

prompts/get โ€” the rendered, role-tagged messages

The vocabulary is the protocol's own, so an agent that can drive MCP resources can drive these with nothing new to learn. Each pair is hidden from a client that has the real feature, and stays callable either way.

๐Ÿ“– Tools ยท Installing


๐ŸŽฏ One server, many narrow agents

A client can be given part of the catalogue and no more, and the narrowing is a ceiling the model cannot widen past โ€” enforced on resources, prompts and every mirror tool alike:

On the MCP URL

Header

Sees

?library=grafana

X-Skill-Library

that whole library

?categories=observability

X-Skill-Categories

every plugin declaring that category, across libraries

?tags=oncall,runbooks

X-Skill-Tags

what carries both tags; repeat the parameter for any-of

?library=grafana&tags=oncall

all three

the tagged part of that one library

A header beats the URL, which is what makes a scope pinned inside a credential something the caller cannot edit away. It is a plumbed constant, never a tool argument โ€” there is no way for a model to ask for material it was not given.

๐Ÿ“– Scoping


๐Ÿ”Œ Sources, plugins, libraries

Three lists. A source is a backend named once. A plugin is a folder of skills and prompts at a path under it. A library is the first segment of every URI, and is a marketplace, a list of plugins, or a query:

sources:
- name: github
  url: git+https://github.com

plugins:
- name: house-prompts
  category: operations
  source: file:///srv/prompts
  prompts: ["*.md"]

libraries:
- name: grafana
  source: github://grafana/skills?ref=51d33e71e191b409bbd25fc7be2684c610d18166
  plugins: [house-prompts]

Scheme

Backend

file:///path

a directory on this machine, served in place โ€” ConfigMap mounts included

git+https://, git+http://, git+file://

a git remote, cloned bare โ€” shallow over the network, whole for git+file:// (libgit2's local transport refuses a shallow fetch) โ€” and exported at a commit

webdav+https://, webdav+http://

a WebDAV folder โ€” Nextcloud above all โ€” copied into the cache

A plugin's address is <source>://<path>[//<subdir>][?ref=<ref>], go-getter's grammar, the one kustomize reads. Pin a ?ref= and the image serves the same bytes in a year; track a branch, give the source a refresh:, and something goes and looks. cache: live on a WebDAV source revalidates a file by ETag as it is read, so a file edited in Nextcloud is served on the next read. A credential lives on the source and is always an {env: NAME} reference โ€” a URL carrying its own user:token@ is refused.

Nothing about a marketplace's plugin is editable here: to change one, declare your own plugin against the same repository, which costs no second clone.

๐Ÿ“– Plugins ยท Sources ยท Configuration


๐Ÿ’ฌ Prompts

A skill is read by the model when it decides to. A prompt is picked by a person, who fills in a few arguments first โ€” Claude Code lists them as slash commands. Point the server at a Claude Code command, a Copilot .prompt.md or one of its own files and the same MCP prompt comes out, with the same libraries, tags and scopes as everything else:

---
description: Debug a workload's recent logs in Loki with the Grafana MCP server.
arguments:
- name: app
  required: true
- name: since
  default: 1h
---
Investigate the logs of **{{ app }}** over the last {{ since }}.

Double braces, because prompt bodies here are full of LogQL and JSON. A Claude command's $ARGUMENTS and a Copilot file's ${input:name:hint} become arguments the same way. ${CLAUDE_PLUGIN_ROOT} is rendered as an address you can read; ${selection} and friends name things only a client has, so they are served exactly as written, and nothing fetched is ever executed.

๐Ÿ“– Prompts


๐Ÿš€ Running it

docker run -p 8000:8000 \
  -v mcp-kb-cache:/var/cache/mcp-kb \
  -v "$PWD/examples/config.yaml:/etc/mcp-kb/config.yaml:ro" \
  kubed/mcp-kb:latest

Two mounts and that is the deployment. examples/config.yaml serves four pinned GitHub libraries out of the box. Then point an MCP client at http://localhost:8000/mcp.

The cache wants to be writable by uid 65534, which the container runs as: a named Docker volume needs nothing, a Kubernetes emptyDir needs fsGroup: 65534.

๐Ÿ“– Deployment ยท Installing


โš™๏ธ Configuration

Every flag has an environment fallback: containers are configured with env vars, developers reach for flags. These say how to run; the config file says what to serve.

Env

Flag

Default

Notes

CONFIG

--config

/etc/mcp-kb/config.yaml

The config file listing the sources to serve

CACHE_DIR

--cache-dir

/var/cache/mcp-kb

Where a non-file:// source materialises, and where index.json lives

TRANSPORT

--transport

http

http or stdio

HOST

--host

0.0.0.0

Bind address, http only

PORT

--port

8000

Port, http only

Per request, on the MCP URL โ€” each with a header form that beats it:

Parameter

Header

Does

?resources=off

X-MCP-Resources

Reveals the resource mirror tools

?prompts=off

X-MCP-Prompts

Reveals the prompt mirror tools

?library=<name>

X-Skill-Library

Restricts this client to one library, by name

?categories=<name>

X-Skill-Categories

Restricts it to the plugins declaring that category

?tags=a,b

X-Skill-Tags

Restricts it to what carries all of those tags; repeat for any-of

?skills=full

X-Skill-Listing

Lists every skill, for clients that sync them to disk

mcp-kb schema prints the config JSON Schema, which is also committed as config.schema.json for an editor to validate against live.

๐Ÿ“– Configuration


๐Ÿฉบ Operations

GET /health reports the generation being served, the skill and prompt counts, and three maps: the libraries and what each serves, the plugins and what each yielded, and the fetches โ€” one per materialised tree โ€” each ok, stale or failed. It needs no credentials and answers 200 whenever the process is serving, including when a fetch failed to load: one unreachable remote must not take a working catalogue down.

POST /reindex rebuilds every fetch now and answers with the same body plus what it rebuilt. GET /openapi.yaml is the machine-readable contract for both.

๐Ÿ“– Operations ยท Endpoints


๐Ÿ›  Contributing

Setup, the layout, what is generated and what is not, and what CI will say about it: CONTRIBUTING.md. The rules worth reading before changing behaviour are AGENTS.md, and why they are what they are is saga/.


๐Ÿ”— References


๐Ÿ“œ Licence

MIT. See LICENSE.

Related MCP Connectors

Related MCP Servers