Michal 5d859ca7d8 feat: audit console TUI, system prompt management, and CLI improvements
Audit Console Phase 1: tool_call_trace emission from mcplocal router,
session_bind/rbac_decision event kinds, GET /audit/sessions endpoint,
full Ink TUI with session sidebar, event timeline, and detail view
(mcpctl console --audit).

System prompts: move 6 hardcoded LLM prompts to mcpctl-system project
with extensible ResourceRuleRegistry validation framework, template
variable enforcement ({{maxTokens}}, {{pageCount}}), and delete-resets-
to-default behavior. All consumers fetch via SystemPromptFetcher with
hardcoded fallbacks.

CLI: -p shorthand for --project across get/create/delete/config commands,
console auto-scroll improvements, shell completions regenerated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 23:50:54 +00:00
2026-02-21 03:10:39 +00:00
2026-02-21 03:10:39 +00:00
2026-02-21 03:10:39 +00:00
2026-02-21 03:10:39 +00:00
2026-02-21 03:10:39 +00:00
2026-02-21 03:10:39 +00:00
2026-02-21 03:10:39 +00:00
2026-02-21 03:10:39 +00:00
2026-02-21 03:10:39 +00:00

mcpctl

kubectl for MCP servers. A management system for Model Context Protocol servers — define, deploy, and connect MCP servers to Claude using familiar kubectl-style commands.

mcpctl get servers
NAME             TRANSPORT   REPLICAS   DOCKER IMAGE                              DESCRIPTION
grafana          STDIO       1          grafana/mcp-grafana:latest                Grafana MCP server
home-assistant   SSE         1          ghcr.io/homeassistant-ai/ha-mcp:latest    Home Assistant MCP
docmost          SSE         1          10.0.0.194:3012/michal/docmost-mcp:latest Docmost wiki MCP

What is this?

mcpctl manages MCP servers the same way kubectl manages Kubernetes pods. You define servers declaratively in YAML, group them into projects, and connect them to Claude Code or any MCP client through a local proxy.

The architecture:

Claude Code  <--STDIO-->  mcplocal (local proxy)  <--HTTP-->  mcpd (daemon)  <--Docker-->  MCP servers
  • mcpd — the daemon. Runs on a server, manages MCP server containers (Docker/Podman), stores configuration in PostgreSQL.
  • mcplocal — local proxy. Runs on your machine, presents a single MCP endpoint to Claude that merges tools from all your servers. Handles namespacing (grafana/search_dashboards), gated sessions, and prompt delivery.
  • mcpctl — the CLI. Talks to mcpd (via mcplocal or directly) to manage everything.

Quick Start

1. Install

# From RPM repository
sudo dnf config-manager --add-repo https://your-registry/api/packages/mcpctl/rpm.repo
sudo dnf install mcpctl

# Or build from source
git clone https://github.com/your-org/mcpctl.git
cd mcpctl
pnpm install
pnpm build
pnpm rpm:build   # requires bun and nfpm

2. Connect to a daemon

# Login to an mcpd instance
mcpctl login --mcpd-url http://your-server:3000

# Check connectivity
mcpctl status

3. Create your first secret

Secrets store credentials that servers need — API tokens, passwords, etc.

mcpctl create secret grafana-token \
  --data TOKEN=glsa_xxxxxxxxxxxx

4. Create your first server

A server is an MCP server definition — what Docker image to run, what transport it speaks, what environment it needs.

mcpctl create server grafana \
  --docker-image grafana/mcp-grafana:latest \
  --transport STDIO \
  --env GRAFANA_URL=http://grafana.local:3000 \
  --env GRAFANA_AUTH_TOKEN=secretRef:grafana-token:TOKEN

mcpd pulls the image, starts a container, and keeps it running. Check on it:

mcpctl get instances         # See running containers
mcpctl logs grafana          # View server logs
mcpctl describe server grafana  # Full details

5. Create a project

A project groups servers together and configures how Claude interacts with them.

mcpctl create project monitoring \
  --description "Grafana dashboards and alerting" \
  --server grafana \
  --no-gated

6. Connect Claude Code

Generate the .mcp.json config for Claude Code:

mcpctl config claude --project monitoring

This writes a .mcp.json that tells Claude Code to connect through mcplocal. Restart Claude Code and your Grafana tools appear:

mcpctl console monitoring   # Preview what Claude sees

Declarative Configuration

Everything can be defined in YAML and applied with mcpctl apply:

# infrastructure.yaml
secrets:
  - name: grafana-token
    data:
      TOKEN: "glsa_xxxxxxxxxxxx"

servers:
  - name: grafana
    description: "Grafana dashboards and alerting"
    dockerImage: grafana/mcp-grafana:latest
    transport: STDIO
    env:
      - name: GRAFANA_URL
        value: "http://grafana.local:3000"
      - name: GRAFANA_AUTH_TOKEN
        valueFrom:
          secretRef:
            name: grafana-token
            key: TOKEN

projects:
  - name: monitoring
    description: "Infrastructure monitoring"
    gated: false
    servers:
      - grafana
mcpctl apply -f infrastructure.yaml

Round-trip works too — export, edit, re-apply:

mcpctl get all --project monitoring -o yaml > backup.yaml
# edit backup.yaml...
mcpctl apply -f backup.yaml

Content Pipeline (ProxyModel)

ProxyModel defines a content transformation pipeline that runs between upstream MCP servers and the client (e.g., Claude). It processes tool results, prompts, and resources through ordered stages before delivering them.

Built-in Models

Model Stages Use case
default passthroughpaginate (8KB pages) Safe pass-through with pagination for large responses
subindex section-splitsummarize-tree Splits large content into sections, returns a summary index. Client drills down with _resultId/_section params

How subindex Works

  1. Upstream returns a large tool result (e.g., 50KB of device states)
  2. section-split divides content into logical sections (2KB15KB each)
  3. summarize-tree generates a compact index with section summaries (~200 tokens each)
  4. Client receives the index and can request specific sections via _section parameter

Configuration

Set per-project (all servers use the same model):

kind: Project
metadata:
  name: home-automation
spec:
  servers: [home-assistant, node-red]
  proxyModel: subindex

Override per-server within a project:

kind: Project
metadata:
  name: monitoring
spec:
  servers: [grafana, prometheus]
  proxyModel: default
  serverOverrides:
    grafana:
      proxyModel: subindex

Via CLI:

mcpctl create project monitoring --server grafana --server prometheus --proxy-model subindex

Custom ProxyModels

Place YAML files in ~/.mcpctl/proxymodels/ to define custom pipelines:

kind: ProxyModel
metadata:
  name: my-pipeline
spec:
  stages:
    - type: section-split
      config:
        minSectionSize: 1000
        maxSectionSize: 10000
    - type: summarize-tree
      config:
        maxTokens: 150
        maxDepth: 2
  appliesTo: [toolResult, prompt]
  cacheable: true

Inspect available models: mcpctl get proxymodels / mcpctl describe proxymodel subindex

Resources

Resource What it is Example
server MCP server definition Docker image + transport + env vars
instance Running container (immutable) Auto-created from server replicas
secret Key-value credentials API tokens, passwords
template Reusable server blueprint Community server configs
project Workspace grouping servers "monitoring", "home-automation"
prompt Curated content for Claude Instructions, docs, guides
promptrequest Pending prompt proposal LLM-submitted, needs approval
rbac Access control bindings Who can do what
serverattachment Server-to-project link Virtual resource for apply

Commands

# List resources
mcpctl get servers
mcpctl get instances
mcpctl get projects
mcpctl get prompts --project myproject

# Detailed view
mcpctl describe server grafana
mcpctl describe project monitoring

# Create resources
mcpctl create server <name> [flags]
mcpctl create secret <name> --data KEY=value
mcpctl create project <name> --server <srv> [--gated]
mcpctl create prompt <name> --project <proj> --content "..."

# Modify resources
mcpctl edit server grafana          # Opens in $EDITOR
mcpctl patch project myproj gated=true
mcpctl apply -f config.yaml         # Declarative create/update

# Delete resources
mcpctl delete server grafana

# Logs and debugging
mcpctl logs grafana                 # Container logs
mcpctl console monitoring           # Interactive MCP console
mcpctl console --inspect            # Traffic inspector

# Backup and restore
mcpctl backup -o backup.json
mcpctl restore -i backup.json

# Project management
mcpctl --project monitoring get servers       # Project-scoped listing
mcpctl --project monitoring attach-server grafana
mcpctl --project monitoring detach-server grafana

Templates

Templates are reusable server configurations. Create a server from a template without repeating all the config:

# Register a template
mcpctl create template home-assistant \
  --docker-image "ghcr.io/homeassistant-ai/ha-mcp:latest" \
  --transport SSE \
  --container-port 8086

# Create a server from it
mcpctl create server my-ha \
  --from-template home-assistant \
  --env-from-secret ha-secrets

Gated Sessions

Projects are gated by default. When Claude connects to a gated project:

  1. Claude sees only a begin_session tool initially
  2. Claude calls begin_session with a description of its task
  3. mcplocal matches relevant prompts and delivers them
  4. The full tool list is revealed

This keeps Claude's context focused — instead of dumping 100+ tools and pages of docs upfront, only the relevant ones are delivered based on the task at hand.

# Enable/disable gating
mcpctl patch project monitoring gated=true
mcpctl patch project monitoring gated=false

Prompts

Prompts are curated content delivered to Claude through the MCP protocol. They can be plain text or linked to external MCP resources (like wiki pages).

# Create a text prompt
mcpctl create prompt deployment-guide \
  --project monitoring \
  --content-file docs/deployment.md \
  --priority 7

# Create a linked prompt (content fetched live from an MCP resource)
mcpctl create prompt wiki-page \
  --project monitoring \
  --link "monitoring/docmost:docmost://pages/abc123" \
  --priority 5

Claude can also propose prompts during a session. These appear as prompt requests that you can review and approve:

mcpctl get promptrequests
mcpctl approve promptrequest proposed-guide

Interactive Console

The console lets you see exactly what Claude sees — tools, resources, prompts — and call tools interactively:

mcpctl console monitoring

The traffic inspector watches MCP traffic from other clients in real-time:

mcpctl console --inspect

Architecture

┌──────────────┐           ┌─────────────────────────────────────────┐
│ Claude Code  │   STDIO   │           mcplocal (proxy)              │
│              │◄─────────►│                                         │
│ (or any MCP  │           │  Namespace-merging MCP proxy            │
│  client)     │           │  Gated sessions + prompt delivery       │
│              │           │  Per-project endpoints                  │
└──────────────┘           │  Traffic inspection                     │
                           └──────────────┬──────────────────────────┘
                                          │ HTTP (REST + MCP proxy)
                                          │
                           ┌──────────────┴──────────────────────────┐
                           │              mcpd (daemon)              │
                           │                                         │
                           │  REST API (/api/v1/*)                   │
                           │  MCP proxy (routes tool calls)          │
                           │  PostgreSQL (Prisma ORM)                │
                           │  Docker/Podman container management     │
                           │  Health probes (STDIO, SSE, HTTP)       │
                           │  RBAC enforcement                       │
                           │                                         │
                           │  ┌───────────────────────────────────┐  │
                           │  │     MCP Server Containers         │  │
                           │  │                                   │  │
                           │  │  grafana/  home-assistant/  ...   │  │
                           │  │  (managed + proxied by mcpd)      │  │
                           │  └───────────────────────────────────┘  │
                           └─────────────────────────────────────────┘

Clients never connect to MCP server containers directly — all tool calls go through mcplocal → mcpd, which proxies them to the right container via STDIO/SSE/HTTP. This keeps containers unexposed and lets mcpd enforce RBAC and health checks.

Tool namespacing: When Claude connects to a project with servers grafana and slack, it sees tools like grafana/search_dashboards and slack/send_message. mcplocal routes each call through mcpd to the correct upstream server.

Project Structure

mcpctl/
├── src/
│   ├── cli/          # mcpctl command-line interface (Commander.js)
│   ├── mcpd/         # Daemon server (Fastify 5, REST API)
│   ├── mcplocal/     # Local MCP proxy (namespace merging, gating)
│   ├── db/           # Database schema (Prisma) and migrations
│   └── shared/       # Shared types and utilities
├── deploy/           # Docker Compose for local development
├── stack/            # Production deployment (Portainer)
├── scripts/          # Build, release, and deploy scripts
├── examples/         # Example YAML configurations
└── completions/      # Shell completions (fish, bash)

Development

# Prerequisites: Node.js 20+, pnpm 9+, Docker/Podman

# Install dependencies
pnpm install

# Start local database
pnpm db:up

# Generate Prisma client
cd src/db && npx prisma generate && cd ../..

# Build all packages
pnpm build

# Run tests
pnpm test:run

# Development mode (mcpd with hot-reload)
cd src/mcpd && pnpm dev

License

MIT

Description
No description provided
Readme 4.7 MiB
Languages
TypeScript 96.4%
Shell 2.9%
JavaScript 0.7%