Toolsets Reference¶
A toolset is the selector list an agent declares to say which of the gateway's tools it is
composed with. muster evaluates it per request, statelessly, as the third filter on the
caller's catalogue — after server-authentication visibility and workflow availability — so
list_tools, filter_tools, describe_tool, list_core_tools, the resource and prompt
accessors and call_tool all answer within it.
A toolset never widens access. The catalogue it filters is already the caller's own: a tool from a server the session has not signed in to is not in the catalogue, so a selector naming it matches nothing. The invoking human's identity and the backends' own authorization remain the boundary; a toolset bounds what the model can discover and call.
Declaring a toolset¶
The X-muster-Toolset request header¶
A comma-separated list of inline selectors; whitespace around each selector is trimmed. The header is read on every request. Nothing is bound to the MCP session: two requests on the same session with different headers get different catalogues, and a request without the header is unscoped — exactly today's behaviour. muster's session for a forwarded token is derived from the token, so all the agents one person drives share one session; per-request evaluation is what keeps agent A's toolset from applying to agent B.
The platform's Generic agent chart renders this header from its toolset value onto the
agent's muster tool entry (headersFrom), so an agent declares its toolset once, on its own
release.
The toolset bound to a Session¶
A request that authenticates with Substrate's actor JWT (the actor bearer)
is served as the person its Session binding names, and within the toolset that binding carries:
the toolset the component starting the Session sent with PUT /session-bindings, the agent's
toolset at the Session's start. It applies to every such request, with or without the header:
- without
X-muster-Toolset, the request resolves within the bound toolset; - with it, the header resolves within the bound toolset: it narrows it and never widens it, and a header asking for more gets the bound toolset and no error;
- a binding without a
toolsetbounds nothing: the request is scoped by its header alone, as for a request that authenticates as the person; preset:noneresolves to nothing; an unknown preset in a binding fails every meta-tool call of the Session with the error naming it, as in a header.
A refusal of a name outside the bound toolset names it, for example tool "x_k8s_delete" is
outside the toolset [preset:read-only] bound to the session; filter_tools echoes it as
toolset_bound, and the actor bearer's audit line carries it (toolset=[...]). When a header
and the bound toolset differ, muster logs toolset header resolved within the bound toolset
with both. A request that authenticates with a person's bearer is never bound.
Inline selector grammar¶
| Selector | Selects |
|---|---|
preset:<name> |
A preset from toolsetPresets or a built-in (below). |
server:<name> |
Every tool of the MCPServer <name>. For a family, the family name selects the family surface on every member; a member's name selects it on that member only, so a call naming another member with the instance argument is refused. |
workflow:<name> |
The workflow's execution tool (workflow_<name>). |
tool:<name> |
One tool by its exposed name (x_<server>_<tool>, workflow_<name>, core_*). |
Names are exact and case-sensitive (regex ^(preset|server|workflow|tool):[^\s,]+$). At most
32 selectors inline — larger selections are presets. Core tools (core_*) are selectable
by name (tool:core_workflow_list inline, pattern: core_* in a preset) and through their
annotations (the built-in read-only preset includes the read-only core tools). There is no
core pseudo-server.
Rejected inline, with an error naming the toolset and the selector:
| Input | Error |
|---|---|
| empty header / empty list | toolset [] is empty; use "preset:none" for an agent without tools |
toolset:<name> |
… selector "toolset:x" is reserved for shared toolsets |
label:<k>=<v> |
… selector "label:…" is allowed inside presets only |
| anything else | … selector "…" is malformed; expected preset:<name>, server:<name>, workflow:<name> or tool:<name> |
| more than 32 | toolset [...] has 33 selectors, more than the 32 allowed inline; define a preset |
| unknown preset | toolset [preset:foo] names unknown preset "foo"; known presets: read-only, none, full, … |
These are error results on every meta-tool call — including get_resource and
get_prompt — never a silent fall-back to the unscoped catalogue and never silent degradation
to the selectors that did parse. They are not sticky: the next request without the header is
unscoped again.
Presets¶
Presets are muster configuration (toolsetPresets: in config.yaml; chart value
muster.toolsetPresets, rendered into the ConfigMap). They are GitOps-managed by
construction and evaluated against the live catalogue on each request, so a preset selecting
by server or by annotation stays correct when a server adds tools.
toolsetPresets:
infrastructure:
description: Every infrastructure server, without deletes
include:
- server: mcp-kubernetes
- server: mcp-prometheus
exclude:
- pattern: "*_delete"
safe-ops:
description: The infrastructure preset without its writes, plus one workflow
include:
- preset: infrastructure
- workflow: incident-triage
exclude:
- pattern: "*_apply"
- pattern: "*_patch"
Each preset has an optional description (shown by filter_tools with include_presets),
an include list and an optional exclude list. A preset resolves to the union of its
includes minus its excludes. include rules are a union, never an intersection: a preset
that includes preset: infrastructure and readOnly: true resolves to every infrastructure tool
plus every read-only tool in the catalogue, not to the infrastructure's read-only tools. To
narrow a server set, include it and exclude the tools you do not want by pattern: or tool:,
as safe-ops does above (readOnly: false is not a rule; muster rejects it at startup). Every
rule sets exactly one key:
| Rule | Selects |
|---|---|
tool: <name> |
One tool by exposed name. |
pattern: <glob> |
Tools whose exposed name matches the glob (core_*, x_mcp-kubernetes_*; Go path.Match syntax). |
server: <name> |
Every tool of that server (family name for every member, member name for that member alone). |
workflow: <name> |
The workflow's execution tool. |
readOnly: true |
Every tool annotated readOnlyHint: true, including workflows carrying the derived hint (below). |
preset: <name> |
Composition: everything another preset selects. include only. |
label: <key>=<value> / label: <key> |
Tools of every MCPServer whose resource carries that label — metadata.labels in both storage modes, read live on each request, so a server gaining or losing the label changes the resolution on the next request without a restart. <key>=<value> matches the value, <key> alone matches presence, <key>= matches an empty value. A family tool joins on the members providing it that carry the label. Presets only — inline label: is rejected. |
The two platform presets are defined by the tool-group label every platform-shipped MCPServer
carries (agent-platform.giantswarm.io/tool-group: infrastructure | agent-platform; see the
fleet chart), so a new manager or a fourth infrastructure family joins its preset with no values
change:
toolsetPresets:
infrastructure:
description: The servers for the infrastructure underneath the platform
include:
- label: agent-platform.giantswarm.io/tool-group=infrastructure
agent-platform:
description: The platform's own management surface, including muster's core tools
include:
- label: agent-platform.giantswarm.io/tool-group=agent-platform
- pattern: core_*
Built-in presets¶
Three presets are built in and cannot be redefined — configuration that tries fails muster's startup naming the preset:
| Preset | Resolves to |
|---|---|
read-only |
Every tool its server annotates readOnlyHint: true — muster's own read-only core tools included, see Core tool annotations — plus every workflow whose step tools are all read-only. |
none |
Nothing. A preset:none header hides and refuses every tool; the Generic chart omits the muster tool entry altogether for an agent whose toolset is exactly ["preset:none"]. |
full |
The whole catalogue, core tools included (pattern: "*"). |
The platform charts ship infrastructure and agent-platform as values (by the
agent-platform.giantswarm.io/tool-group label); installations add their own the same way.
Validation at startup¶
muster serve refuses to start when toolsetPresets redefines a built-in preset, when a rule
sets no key or more than one, when a pattern does not compile, when readOnly is false,
when exclude composes a preset, when a composition names an unknown preset, or when
compositions cycle. Every error names the preset and the rule position.
Workflow read-only derivation¶
A workflow is read-only when every tool its steps reference — conditions, forEach (nested
loops included) and parallel sub-steps and onFailure handlers included, nested workflows followed — resolves in
the caller's catalogue to a tool annotated read-only — a downstream server's tool or a core tool
by its declared annotation. A step calling a core tool that writes (core_workflow_delete,
core_service_stop, …), an unknown tool, or a cycle makes the workflow not read-only. The derived hint
fills the workflow tool's readOnlyHint annotation slot, so describe_tool shows it and
preset:read-only includes the pure-query workflows.
What the meta-tools do with a toolset¶
list_tools,filter_tools,describe_tool,list_core_toolsread the filtered catalogue.list_toolspages it (limit, default 50, andoffset;totalis the size of the filtered catalogue andtruncatedsays whether more pages exist), so a 450-toolpreset:read-onlyis nine pages of summaries rather than one 400 KB answer;preset:noneistotal: 0and no tools. Likefilter_tools, it echoes the header's selectors intoolset.describe_toolof a tool the session can see but the toolset excludes answerstool "<name>" is outside the toolset [<selectors>]; an unknown name is stillTool not found. A family tool is shown with the members the toolset selects it on alone, in its instance argument's enum and its description.call_tool— including workflow execution (workflow_<name>) — of a name outside the toolset is refused withtool "<name>" is outside the toolset [<selectors>]and logged once at info level with the tool, the toolset and the session. A family tool runs on the member its instance argument selects, and the toolset is checked on that member: a call naming a member the toolset does not select the tool on is refused withtool "<name>" on server "<member>" is outside the toolset [<selectors>]. Rules are evaluated per member, so a preset excludingserver: <member>keeps the tool on the other members. The tools a workflow's steps call internally are the workflow author's composition and are not re-checked. A tool of a server the session has not signed in to is not in the catalogue, so no selector resolves to it; when the toolset could select it once listed, the call answersauth_requiredwith the sign-in link instead of refusing the name, and runs nothing. The server is the one the call would be routed to: in a family's name space, the member the instance argument selects; elsewhere, the server whose tool prefix is the longest the name carries. The toolset could select it when atool:<name>orserver:<name>selector names it (server:also takes the family name), or a preset's rules could:tool,pattern,serverandlabelrules are decided on the name, the server and its labels, andreadOnly, whose annotation is not known before sign-in, counts as a possible include and never as an exclude. When a stored grant connects the session instead, the call is checked against the tool as listed, with its annotations, like any other.- Resources and prompts follow the servers: a server is inside the toolset when at least one
of its tools is selected.
list_resources,filter_resources,describe_resource,list_prompts,filter_prompts,describe_prompthide the others;get_resourceandget_promptrefuse them (resource "<uri>" is outside the toolset […]). servers_requiring_auth(list_tools;filter_toolsfor the servers itspatterncould match) is neither narrowed by the toolset nor paged: it tells the caller which sign-in would make more of the toolset resolve.tools/list— the meta-tools themselves — is unchanged.
filter_tools and toolsets¶
filter_tools accepts toolset (an array of inline selectors) and include_presets (bool):
{"name": "filter_tools", "arguments": {"toolset": ["preset:read-only", "server:pro"], "include_presets": true}}
The response carries the tools the toolset resolves to for the caller, toolset (the
selectors as given), toolset_unmatched (the selectors that selected nothing for the caller —
for example a server the caller has not signed in to; preset:none is never reported),
toolset_requiring_auth (the servers awaiting the caller's sign-in some of whose tools the
toolset could select once listed, by the same rules as call_tool, sorted by name, each with
name, auth_tool, tool_prefix and, for a family member, family: the part of the toolset a
sign-in would unlock) and presets ([{name, description, built_in}], built-ins
first). When the request also carries X-muster-Toolset or a bound toolset, the argument
resolves within them and never widens them. Argument errors use the header's texts.
toolset names the toolset the returned tools were resolved within, whichever way it was
declared. Without a toolset argument, a request carrying X-muster-Toolset gets the header's
selectors echoed (as declared, e.g. ["preset:read-only"]), with toolset_unmatched computed
against them — the one place an agent whose toolset is set by its deployment can learn what
bounds it, and how an operator reading a transcript sees which toolset a discovery call ran
under. With both header and argument the argument is echoed, as it is the narrower of the two.
Without either, a bound Session's request gets the bound toolset echoed. A bound Session's
response also carries toolset_bound. An unscoped request carries no toolset. presets accompanies the argument or
include_presets only: a header-declared toolset alone does not add them, so the discovery
calls of an agent scoped by header stay as small as unscoped ones.
Tool information¶
Every entry of list_tools / filter_tools, and describe_tool, carries:
server— the owning MCPServer (the family name for a family tool); omitted for workflows and core tools,kind—tool(served by an MCPServer),workfloworcore,annotations— the hints the server declared (readOnlyHint,destructiveHint,idempotentHint,openWorldHint), or the derivedreadOnlyHintof a workflow; omitted when the tool carries none.
Composition, not authorization¶
A toolset narrows what an agent reaches within the invoking person's access; it never grants more. Where the request authenticates as the person (a forwarded token), the toolset is declared by the request: a client that omits the header reaches everything the person may reach, which is what it could reach before toolsets existed. Where it authenticates with the actor JWT of a bound Session, the toolset is bound to the Session at its start and no process in the sandbox can widen it; the person's own access and the backend's authorization remain the boundary.