Skip to content

Scoping

Kelly Ferrone edited this page Sep 21, 2026 · 8 revisions

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.

Three selectors, and they narrow together

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.

A scope names a library, not a folder

?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.

One comma rule

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 that names nothing is refused

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.

A header beats the URL

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.

What it reaches

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.

Serving less than you have

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

Clone this wiki locally