muster

Muster n. German: pattern, sample.Here: an aggregating MCP server.

One endpoint.
Every MCP server your platform runs.

muster connects the MCP servers a platform team runs — Kubernetes, Prometheus, GitHub, Flux, your own — and serves their tools through a single authenticated endpoint. Agents connect once, find tools without flooding their context window, run procedures as deterministic workflows, and make every call as the person they work for.

agent session · /mcp
▸ filter_tools { "query": "pods not running" }
  workflow_pod-triage   Find the pods of a workload that are not running and gather their evidence
  x_kubernetes_list     List resources of a kind in a namespace
  x_kubernetes_logs     Read the logs of a container
▸ call_tool { "name": "workflow_pod-triage", "arguments": { "namespace": "payments", "selector": "app=checkout" } }
  status: completed · 7 steps, 1 allowed to fail · as dana@example.com · trace 3f9c…a1
x_<server>_<tool> workflow_<name> core_*
Workflow engine kind: Workflow · muster.giantswarm.io/v1alpha1

Procedures become tools.

Operational work is a sequence: find the pods, read their logs, query the metric, compare. Left to an agent, the sequence is rediscovered on every run, differently each time, at full token cost. A workflow sews the sequence into one tool. It is a list of steps — tool calls, forEach loops and parallel groups — gated by conditions and joined by templates. muster exposes it as workflow_<name> with the arguments you declared; the steps run the same way every time, and every run leaves a durable execution record.

workflow.yamlkubectl apply -f · or core_workflow_create
apiVersion: muster.giantswarm.io/v1alpha1
kind: Workflow
metadata:
  name: pod-triage
  namespace: muster
spec:
  description: Find the pods of a workload that are not running and gather their evidence
  args:
    namespace: { type: string, required: true }
    selector:  { type: string, required: true }
  steps:
    - id: pods
      tool: x_kubernetes_list
      args: { kind: Pod, namespace: "{{ .input.namespace }}", labelSelector: "{{ .input.selector }}" }
    - id: events
      tool: x_kubernetes_list
      args: { kind: Event, namespace: "{{ .input.namespace }}" }
      condition:
        template: "{{ gt (len .results.pods.items) 0 }}"
    - id: logs
      forEach:
        items: "{{ .results.pods.items }}"
        as: pod
        steps:
          - id: tail
            tool: x_kubernetes_logs
            args: { name: "{{ .vars.pod.metadata.name }}", namespace: "{{ .input.namespace }}", tailLines: 50 }
            allowFailure: true
    - id: signals
      parallel:
        - id: restarts
          tool: x_prometheus_query
          args: { query: 'increase(kube_pod_container_status_restarts_total{namespace="{{ .input.namespace }}"}[1h])' }
        - id: alerts
          tool: x_prometheus_alerts
          args: { namespace: "{{ .input.namespace }}" }
  onFailure:
    - id: note
      tool: x_slack_post
      args: { channel: "#oncall", text: "pod-triage failed for {{ .input.namespace }}/{{ .input.selector }}" }
  output:
    pods:     "{{ .results.pods.items }}"
    restarts: "{{ .results.restarts.data.result }}"
    alerts:   "{{ len .results.alerts.alerts }}"
WorkflowExecution pod-triage-… idle
stepstatustook
waiting for a run…

Workflows compose: one workflow calls another's workflow_<name> tool, a toolset selects them with workflow:<name>, and an agent finds them by intent through filter_tools. Create workflows · Workflow reference

Kubernetes-native helm install muster giantswarm/muster

Servers and workflows are resources.

On a cluster, muster runs in Kubernetes mode: MCPServer, Workflow and WorkflowExecution are custom resources reconciled by muster itself. Status, conditions and events say which servers are reachable now and which workflows have every tool they need. A server is registered in a pull request, reviewed like any other manifest and rolled out by Flux; nothing is configured in a client.

MCPServerkubectl get mcpservers
spec:
  type: streamable-http
  url: https://mcp-kubernetes.muster.svc/mcp
  toolPrefix: k8s
  auth:
    type: oauth
    forwardToken: true
    requiredAudiences: [dex-k8s-authenticator]
status:
  state: Running
  conditions: [Connected, ToolsDiscovered]

Connected, health-checked and reconnected with backoff. The tool list is followed as it changes; the status and the events tell the story.

Workflowkubectl get wf
spec:
  args: { … }
  steps: [ … ]
  onFailure: [ … ]
  output: { … }
status:
  available: true
  requiredTools:
    - x_kubernetes_list
    - x_kubernetes_logs
    - x_prometheus_query

Validated at admission by CEL rules on the CRD. The workflow's tool appears in a session as soon as every tool it references is available there.

WorkflowExecutionkubectl get wfe
spec:
  workflowName: pod-triage
  status: completed
  input: { namespace: payments, … }
  steps:
    - { id: pods,    status: completed, duration: 412ms }
    - { id: events,  status: completed, duration: 231ms }
    - { id: tail_1,  status: failed,    allowFailure: true }
    - …

One record per run, labelled by workflow and status, kept under a retention you set. Readable with kubectl, the CLI, or core_workflow_execution_get.

$ kubectl -n muster get mcpservers
NAME          TYPE              URL                                       AUTOSTART   STATUS    AGE
kubernetes    streamable-http   https://mcp-kubernetes.muster.svc/mcp     true        Running   14d
prometheus    streamable-http   https://mcp-prometheus.muster.svc/mcp     true        Running   14d
github        streamable-http   https://api.githubcopilot.com/mcp/        true        Running   3d
$ kubectl -n muster get wf,wfe
NAME                                       AGE
workflow.muster.giantswarm.io/pod-triage   9d

NAME                                                          WORKFLOW     STATUS
workflowexecution.muster.giantswarm.io/pod-triage-7f3k2       pod-triage   completed
workflowexecution.muster.giantswarm.io/pod-triage-b1q8x       pod-triage   failed

What the chart ships

  • The three CRDs, and a separate muster-crds chart when their lifecycle should be its own release
  • RBAC, with editor roles for MCPServer and Workflow so teams register their own servers
  • NetworkPolicy and a Cilium variant restricting ingress to the aggregator and metrics ports
  • ServiceMonitor, a PrometheusRule set and a Grafana dashboard
  • Ingress, or Gateway API HTTPRoute and BackendTrafficPolicy
  • HPA and PDB, and Valkey-backed sessions and grants for more than one replica
  • An extra CA bundle for MCP servers behind a private CA

In Kubernetes mode stdio servers are refused: a child process of the muster pod would run under its service account. Servers run as their own workloads and are registered by URL. Installation · Custom resources

Identity and single sign-on OAuth 2.1 · RFC 9728 · RFC 8693 · PKCE

Every call is made as the person.

A shared muster is an OAuth 2.1 resource server in front of Dex. A person logs in once and gets one session; muster carries that identity to every MCP server behind it and records who called what. Nothing muster adds widens access: a toolset bounds what the model can see, and the backends' own authorization stays the boundary.

$ muster auth status

muster: authenticated
  Endpoint: https://muster.example.com
  Identity: dana@example.com
  Expires:  in 29 minutes · session 30 days

MCP Servers:
  mcp-kubernetes    Connected  [SSO: Forwarded]
  mcp-prometheus    Connected  [SSO: Forwarded]
  remote-cluster    Connected  [SSO: Exchanged]
  github            Not authenticated   Run: muster auth login --server github

Protect the endpoint with Dex · Authenticate the CLI · Toolsets · Why token forwarding

Discovery 13 meta-tools · x_<server>_<tool>

A catalogue that fits a context window.

An MCP client loads every tool definition of every attached server before the first question is asked; a platform team's servers contribute hundreds. muster's clients see thirteen meta-tools instead. filter_tools ranks the catalogue against a query and returns a short, summarised page; describe_tool returns one schema; call_tool runs it. The catalogue can be hundreds of tools wide and still cost a few hundred tokens a turn. Servers that serve the same tools for different targets form a family and appear as one surface with an instance argument.

MCP tools reference · How the catalogue is built

  • list_tools
  • filter_tools
  • describe_tool
  • call_tool
  • list_resources
  • filter_resources
  • describe_resource
  • get_resource
  • list_prompts
  • filter_prompts
  • describe_prompt
  • get_prompt
  • list_core_tools
Get started brew · curl · helm · go

Ten minutes to an IDE calling tools through muster.

muster is a single static binary for Linux, macOS and Windows, a container image and a Helm chart. Binaries are signed in CI and verified by muster self-update.

brew trust giantswarm/muster
brew install giantswarm/muster/muster

Serve, register, call

$ muster serve                       # http://localhost:8090/mcp
$ muster create mcpserver files --type=stdio --command=npx \
    --args="-y,@modelcontextprotocol/server-filesystem,$HOME" --autoStart=true
$ muster list mcpserver              # files   Running   stdio
$ muster call x_files_list_allowed_directories

Connect an IDE

muster standalone runs the aggregator and a stdio bridge in one process. Against a running or remote muster, use muster agent --mcp-server; the bridge performs the browser login for the IDE.

{
  "mcpServers": {
    "muster": { "command": "muster", "args": ["standalone"] }
  }
}

The quick start continues from here: exploring the catalogue in the REPL, toolsets and the first workflow. On a cluster, the installation guide covers Dex, Valkey-backed sessions, network policies and metrics.

Documentation tutorials · how-to · reference · explanation