360 lines
12 KiB
Markdown
360 lines
12 KiB
Markdown
|
|
# mcpctl
|
||
|
|
|
||
|
|
**kubectl for MCP servers.** A management system for [Model Context Protocol](https://modelcontextprotocol.io) 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
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 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
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 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.
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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.
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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.
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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`:
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
# 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
|
||
|
|
```
|
||
|
|
|
||
|
|
```bash
|
||
|
|
mcpctl apply -f infrastructure.yaml
|
||
|
|
```
|
||
|
|
|
||
|
|
Round-trip works too — export, edit, re-apply:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
mcpctl get all --project monitoring -o yaml > backup.yaml
|
||
|
|
# edit backup.yaml...
|
||
|
|
mcpctl apply -f backup.yaml
|
||
|
|
```
|
||
|
|
|
||
|
|
## 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
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 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.
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 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).
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
mcpctl console monitoring
|
||
|
|
```
|
||
|
|
|
||
|
|
The traffic inspector watches MCP traffic from other clients in real-time:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
mcpctl console --inspect
|
||
|
|
```
|
||
|
|
|
||
|
|
## Architecture
|
||
|
|
|
||
|
|
```
|
||
|
|
┌─────────────────────────────────────────┐
|
||
|
|
│ mcpd (daemon) │
|
||
|
|
│ │
|
||
|
|
│ REST API (/api/v1/*) │
|
||
|
|
│ PostgreSQL (Prisma ORM) │
|
||
|
|
│ Docker/Podman container management │
|
||
|
|
│ Health probes (STDIO, SSE, HTTP) │
|
||
|
|
│ RBAC enforcement │
|
||
|
|
└──────────────┬──────────────────────────┘
|
||
|
|
│ HTTP
|
||
|
|
│
|
||
|
|
┌──────────────┐ STDIO ┌──────────────┴──────────────────────────┐
|
||
|
|
│ Claude Code │◄─────────►│ mcplocal (proxy) │
|
||
|
|
│ │ │ │
|
||
|
|
│ (or any MCP │ │ Namespace-merging MCP proxy │
|
||
|
|
│ client) │ │ Gated sessions + prompt delivery │
|
||
|
|
│ │ │ Per-project endpoints │
|
||
|
|
└──────────────┘ │ Traffic inspection │
|
||
|
|
└──────────────┬──────────────────────────┘
|
||
|
|
│ STDIO/SSE/HTTP
|
||
|
|
│
|
||
|
|
┌──────────────┴──────────────────────────┐
|
||
|
|
│ MCP Server Containers │
|
||
|
|
│ │
|
||
|
|
│ grafana/ home-assistant/ docmost/ │
|
||
|
|
│ (tools are namespaced by server name) │
|
||
|
|
└─────────────────────────────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
**Tool namespacing**: When Claude connects to a project with servers `grafana` and `slack`, it sees tools like `grafana/search_dashboards` and `slack/send_message`. The proxy routes each call 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
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 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
|