Sign inSign up

kubed/mcp-kb

By kubed

โ€ขUpdated 3 days ago

Image
0

885

kubed/mcp-kb repository overview

โ ๐Ÿ“š 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. ๐Ÿช„


โ ๐Ÿ”ค 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:

ToolMirrors
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 URLHeaderSees
?library=grafanaX-Skill-Librarythat whole library
?categories=observabilityX-Skill-Categoriesevery plugin declaring that category, across libraries
?tags=oncall,runbooksX-Skill-Tagswhat carries both tags; repeat the parameter for any-of
?library=grafana&tags=oncallall threethe 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]
SchemeBackend
file:///patha 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.

EnvFlagDefaultNotes
CONFIG--config/etc/mcp-kb/config.yamlThe config file listing the sources to serve
CACHE_DIR--cache-dir/var/cache/mcp-kbWhere a non-file:// source materialises, and where index.json lives
TRANSPORT--transporthttphttp or stdio
HOST--host0.0.0.0Bind address, http only
PORT--port8000Port, http only

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

ParameterHeaderDoes
?resources=offX-MCP-ResourcesReveals the resource mirror tools
?prompts=offX-MCP-PromptsReveals the prompt mirror tools
?library=<name>X-Skill-LibraryRestricts this client to one library, by name
?categories=<name>X-Skill-CategoriesRestricts it to the plugins declaring that category
?tags=a,bX-Skill-TagsRestricts it to what carries all of those tags; repeat for any-of
?skills=fullX-Skill-ListingLists 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โ .

Tag summary

Content type

Image

Digest

sha256:9517f8e00โ€ฆ

Size

78.5 MB

Last updated

3 days ago

docker pull kubed/mcp-kb