-
Notifications
You must be signed in to change notification settings - Fork 1
Scoping
One server can serve several clients, each a different slice of the catalogue. The slice is set once, in the client's connection config, and it is a ceiling the model cannot widen past.
| 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 |
everything carrying both of those tags |
?library=grafana&tags=oncall |
all three | the tagged part of that one library |
Nothing is set by default, and a request that sets nothing sees everything the server has.
A library is the first segment of every URI, so a scope names one and only one — several would be a different request. It is matched exactly, by name.
A category and a tag are the plugin's, and they reach whatever that
plugin serves: its skills, its prompts and its library-level files alike. What
counts as a tag is a plugin's tags and its keywords together.
Given more than one, they AND: ?library=grafana&categories=observability is
the observability part of grafana, never grafana plus everything else
observability.
?library=grafana/grafana-lgtm is refused:
A scope names a library, not a folder: use ?categories= or ?tags= to narrow
inside it.
Folders keep existing in URIs and in indexes — they are simply not a selector. A folder name repeats across libraries and is not an address on its own, so "the folder, wherever it is" would have to guess which library it meant. What a client wants inside a library is a subject, and a subject is a category or a tag.
A comma inside one value means all of. A repeated parameter means any of.
The same rule in the URL and in a library's pluginSelector:
?tags=oncall,runbooks&tags=lgtm (oncall AND runbooks) OR lgtm
?categories=design&categories=ops design OR ops
Categories are the exception that proves it. A plugin has exactly one, so a list can only mean any of them — and a comma in one can never match anything, so it is refused rather than quietly returning an empty catalogue:
A plugin has one category, so 'a,b' matches nothing; repeat ?categories= to
mean any of them.
A scope is checked against what it names before anything is served. A library the config does not declare, a category no plugin declares, a tag nothing carries, or a combination nothing satisfies: every request fails with invalid params (-32602), and the message says what there is instead.
?library=grafna The scope names library 'grafna', and there is no such
library. The libraries are: grafana, kubed, n8n, penpot,
superpowers.
?categories=obs The scope names category 'obs', which no plugin declares.
The categories are: automation, design, observability.
?library=penpot&tags=oncall
The scope names things that exist, but nothing in library
'penpot' carries the combination: tags oncall.
A client configured with a typo then fails to connect and says why, rather than connecting to what looks like a server with no skills. The names in that message are the operator's own config, and only a scope that selects nothing gets them; a working scope still learns nothing about what is outside it.
A scope is refused for what it names, never for what happens to be served
this minute. A library whose plugins are all down right now, or up with nothing
in them yet, is not refused: it exists, it is only empty, and /health says
why.
Where both are present for the same setting, the header wins. That is the mechanism that makes a scope a ceiling rather than a suggestion:
- The URL is where a caller names itself. Good when one credential is shared and each consumer wants a different slice.
- The header is where an administrator pins a setting. Good when one credential should mean one slice no matter who holds it — in n8n, a Header Auth credential on the MCP Client Tool node.
Either way it is a plumbed constant, not something the model fills in. There is no tool argument that takes a library, a category or a tag, and no way for a model to ask for material it was not given: a URI outside the scope resolves to nothing, and so does a name that never existed. A scoped client cannot confirm another library's contents from the shape of an error.
Everything. Resources, prompts, and all four mirror tools go through the same scope on every listing and every read — there is no method in the catalogue that can be called without deciding about it, which is what keeps a second code path from quietly forgetting.
/health is the deliberate exception: it reports the catalogue as loaded,
ignoring any scope headers, because an operator asking what a pod serves wants
the real answer rather than one client's view of it.
A scope narrows what one client sees of a catalogue the server still holds and still refreshes. When a deployment should not hold something at all, the answer is not a scope — it is a config that lists fewer plugins. A scope is about attention; the config is about reach.
A third thing sits between them: a library of your own, assembled with a
pluginSelector, is a saved query somebody can then pin a client to by name.
See Libraries.
Home · Installing · Libraries · Skills
Configuration, Tools and Endpoints are generated by scripts/generate_wiki.py from the config models, a live server's tool list and the OpenAPI document — so they describe the server that shipped, not the one someone remembered. Prose for a generated page goes in wiki/notes/<page>.notes.md.
mcp-kb · MIT
Getting started
The config file
What clients get
Reference