k8s-dr-mcp
Provides network diagnostics tools for Cilium CNI, including status checks, endpoint inspection, and Hubble observability.
Provides container runtime inspection and management tools using crictl, such as listing containers and images.
Provides tools for etcd cluster management including snapshots, restore, member listing, defragmentation, and alarm handling.
Provides disaster recovery tools for Kubernetes clusters via SSH, enabling diagnosis and repair when the API server is unreachable.
Supports clusters deployed with Kubespray, including host-mode etcd, PEM certificate layouts, and script-based certificate renewal.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@k8s-dr-mcpcheck etcd health on control plane nodes"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
k8s-dr-mcp
!!! This is MVP version only !!! Running on production on your own risk and responsibility! !!!
Kubernetes disaster recovery for AI agents — over SSH, for when the API server is already dead.
Every Kubernetes MCP server on the market talks to the Kubernetes API. That is exactly the thing that is gone when you actually need help at 3am.
k8s-dr-mcp gives an AI agent a way in through the back door: SSH to the nodes themselves.
systemctl, etcdctl, kubeadm, crictl, journalctl, openssl — the tools you'd reach for by
hand, wrapped in 63 MCP tools with a three-gate safety model so the agent can't wreck a cluster
that's already having a bad day.
┌───────────────────────────── CLUSTER HEALTHY ──────────────────────────────┐
│ │
│ LLM ──► kubectl MCP ──► kube-apiserver ──► etcd ✓ works │
│ │
└────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────── CONTROL PLANE IS DOWN ───────────────────────────┐
│ │
│ LLM ──► kubectl MCP ──► kube-apiserver ✗ ✗ dead │
│ nothing left to talk to │
│ │
│ LLM ──► k8s-dr-mcp ──► SSH ──► the node itself ✓ works │
│ systemctl · etcdctl · │
│ kubeadm · crictl · journalctl │
│ │
└────────────────────────────────────────────────────────────────────────────┘Run it alongside a normal kubectl-based MCP server. The agent picks the right one on its own: API for healthy clusters, SSH for broken ones.
Contents
Related MCP server: MCP Server Kubernetes
Highlights
63 tools, 8 groups — diagnostics, etcd, certificates, control plane, kubelet, runtime, network, and orchestrated recovery workflows
Works when nothing else does — no API server, no kubeconfig, no CNI required
Read-only by default — 43 of the 63 tools can't change anything; the other 20 need two explicit unlocks
Three-gate safety — mode gate → confirmation gate → automated pre-flight checks, then an auto-backup
Parallel by design — AsyncSSH connection pool fans out across every control-plane node at once
Bastion-native — tunnels into private node networks through a jump host
Audit trail — one structured JSON line per operation, to stderr or a file
Kubespray-aware — host-mode etcd,
.pemcert layouts,/etc/etcd.env, script-based cert renewal
Quick start
1. Install
pip install -e .2. Describe your cluster
Copy the example that matches your topology:
cp config.example.yaml cluster.yaml # generic kubeadm cluster
cp cluster.stacked.example.yaml cluster.yaml # Kubespray: stacked etcd + Calico
cp cluster.host-etcd.example.yaml cluster.yaml # Kubespray: host etcd + CiliumMinimum viable config — node names, addresses, and how to SSH in:
cluster:
name: my-cluster
etcd_deployment: stacked # stacked | external | host
container_runtime: containerd # containerd | crio
cni_plugin: calico
ssh_defaults:
user: root
key_path: ~/.ssh/id_ed25519
sudo: true
# Optional — only if the nodes live on a private network
# bastion:
# host: bastion.example.com
# user: ubuntu
# key_path: ~/.ssh/bastion_key
control_plane:
- { name: cp-1, host: 10.0.0.11, etcd_member_name: cp-1 }
- { name: cp-2, host: 10.0.0.12, etcd_member_name: cp-2 }
- { name: cp-3, host: 10.0.0.13, etcd_member_name: cp-3 }
workers:
- { name: worker-1, host: 10.0.0.31 }
- { name: worker-2, host: 10.0.0.32 }
cluster.yaml,.env,.mcp.jsonand everything underssh/are gitignored — they hold your real hostnames, addresses and keys. Only the*.example.*files are meant to be committed.
3. Wire it into your agent
// .mcp.json in your project root
{
"mcpServers": {
"k8s-dr": {
"type": "stdio",
"command": "k8s-dr-mcp",
"env": {
"K8S_DR_MODE": "readonly",
"K8S_DR_CLUSTER_CONFIG": "./cluster.yaml"
}
}
}
}Other clients (Claude Desktop, Cursor, Windsurf, VS Code, Docker) → MCP client setup.
4. Ask
You The cluster is down — kubectl just hangs.
Claude → dr_cluster_overview()
→ dr_check_etcd_health()
→ dr_check_etcd_alarms()
etcd on all three nodes has a NOSPACE alarm — the database hit the
2 GiB default quota. Fix is defrag + disarm. That's destructive and
the server is read-only. Want me to walk you through, or restart in
readwrite mode?
You Restart in readwrite. Go ahead.
Claude → dr_etcd_defrag(node="cp-1", confirm=True) ↳ backup first
→ dr_etcd_alarm_disarm(node="cp-1", confirm=True)
→ …cp-2, cp-3…
→ dr_check_etcd_health()
All three members healthy, quorum restored, API server answering.Disaster playbook
What to say, and what the agent will reach for.
Symptom | Say something like | Tools it uses |
| "Certs look expired — check and renew across the control plane." |
|
| "etcd is out of space." |
|
2 of 3 control-plane nodes gone | "We lost quorum. One survivor left." |
|
API server won't come back after a manifest edit | "apiserver won't start — compare cp-1 against the others." |
|
Node stuck | "worker-3 is NotReady, find out why." |
|
Pods stuck | "CNI looks broken on this node." |
|
Restored from a snapshot, unsure what's next | "Give me a recovery plan before we touch anything." |
|
Total loss, need it back now | "Diagnose and recover the whole cluster." |
|
⚠ = requires K8S_DR_MODE=readwrite and confirm=True.
Tool reference
63 tools across 8 groups. 43 are read-only. 20 are destructive (⚠) and pass all three safety
gates before running. Every tool is prefixed dr_.
Group | Tools | Destructive | Purpose |
10 | 0 | Health, logs, ports, disk — always loaded | |
10 | 7 | Snapshots, restore, members, defrag, alarms | |
7 | 2 | Expiry, inspection, renewal, PKI backup | |
7 | 2 | Static pod manifests, component restarts | |
8 | 3 | Kubelet state, config, systemd services | |
6 | 1 | containerd / CRI-O, containers, images | |
9 | 0 | CNI, interfaces, firewall, DNS, Cilium, Hubble | |
6 | 5 | Multi-step orchestrated recovery workflows |
Always registered, even if you leave it out of K8S_DR_TOOL_GROUPS.
Tool | What it does |
| Unified health snapshot of every control-plane node |
| Detailed system health for one node |
| etcd cluster health, member status, quorum |
| Active etcd alarms ( |
| kube-apiserver health on a node |
| Whether critical Kubernetes ports are listening |
| systemd unit status |
| journald logs for a specific unit |
| List and validate |
| Disk usage on the paths that matter |
Tool | What it does |
| Take a snapshot and verify its integrity |
| Inspect an existing snapshot file |
| List cluster members |
| Restore from a snapshot via |
| Bootstrap a single-member cluster from a survivor — total quorum loss |
| Add a member |
| Remove a member |
| Disarm active alarms |
| Defragment storage to reclaim space |
| Compact history to a revision |
Tool | What it does |
| Expiry dates for all Kubernetes certificates |
|
|
| Inspect everything under the PKI directories |
| Verify the TLS chain to a service |
| Tarball the whole PKI directory |
|
|
| Renew one certificate |
Tool | What it does |
| Read a static pod manifest |
| YAML-validate every manifest |
| Back up the manifests directory |
| Are the referenced images present locally? |
| Diff manifests between two nodes — finds the odd one out |
| Restart one component by cycling its manifest |
| Restart all components in dependency order |
Tool | What it does |
| systemd status for kubelet |
| Read the kubelet config file |
| Read startup flags ( |
| Read the kubelet kubeconfig, secrets masked |
| kubelet journal logs |
| Restart kubelet |
| Start any systemd unit |
| Stop any systemd unit |
Tool | What it does |
| containerd / CRI-O service status |
| Read the runtime config file |
| List containers via |
| Logs from one container |
| Cached images on the node |
| Restart the runtime |
Tool | What it does |
| CNI configuration files |
| Installed CNI plugin binaries |
| Interfaces and routing table |
| TCP reachability between two nodes |
| Firewall rules |
| DNS config and resolution |
| Cilium agent health |
| Quick Cilium connectivity test |
| Hubble relay health and recent flows |
Each of these chains many low-level tools into one guarded, multi-step procedure.
Tool | What it does |
| Diagnose every node and produce a plan — read-only, start here |
| Back up PKI → renew on all CP nodes in parallel → restart → verify |
| Restore etcd from a snapshot on one node |
| Restore etcd from a snapshot across all CP nodes |
| Rebuild the cluster from a surviving member |
| Auto-diagnose, then run whichever recovery fits |
Safety model
The server is read-only until you say otherwise, and even then every destructive tool has to clear three gates and take a backup before it touches anything.
Claude calls: dr_etcd_defrag(node="cp-1", confirm=True)
│
▼
┌────────────────────┐
│ GATE 1 · MODE │ K8S_DR_MODE == "readwrite"?
│ │
│ readonly ───────►│──► BLOCKED "Set K8S_DR_MODE=readwrite"
│ readwrite ───────►│──► pass
└─────────┬──────────┘
▼
┌────────────────────┐
│ GATE 2 · CONFIRM │ confirm == True?
│ │
│ confirm=False ───►│──► PREVIEW what it would do + pre-flight results,
│ │ nothing executed
│ confirm=True ───►│──► pass
└─────────┬──────────┘
▼
┌────────────────────┐
│ GATE 3 · PREFLIGHT│ ✓ SSH reachable ✓ required binary present
│ │ ✓ service in the ✓ target dir writable
│ │ expected state ✓ enough disk space
│ any check failed ►│──► BLOCKED with the specific failure
│ all passed ───►│──► pass
└─────────┬──────────┘
▼
┌────────────────────┐
│ AUTO-BACKUP │ etcd data dir · PKI · static pod manifests
└─────────┬──────────┘ (path returned in the result, and logged)
▼
┌────────────────────┐
│ EXECUTE │ run it over SSH
└─────────┬──────────┘
▼
┌────────────────────┐
│ AUDIT │ one JSON line → stderr and/or K8S_DR_AUDIT_LOG
└────────────────────┘Gate 2 is the useful one in practice. Calling a destructive tool with confirm=False is a
dry run: you get a description of the operation plus the pre-flight results, and nothing happens.
Agents can safely explore what a fix would involve before proposing it.
Audit entries look like this:
{"timestamp":"2026-07-28T09:14:22.881Z","tool":"dr_etcd_defrag","node":"cp-1",
"mode":"readwrite","result":"success","confirm":true,
"backup_path":"/var/lib/etcd-backup/etcd-20260728-091422.tar.gz","duration_ms":4130}Configuration
Cluster file
Full schema, with defaults shown. Only name and control_plane are required.
cluster:
name: my-cluster
etcd_deployment: stacked # stacked | external | host
container_runtime: containerd # containerd | crio
cni_plugin: calico
cert_management: kubeadm # kubeadm | script
ssh_defaults:
user: root
key_path: ~/.ssh/id_ed25519
port: 22
sudo: true
connect_timeout: 30
command_timeout: 120
bastion: # omit entirely for direct access
host: ""
user: ""
key_path: ""
port: 22
control_plane: # required, at least one
- name: cp-1
host: 10.0.0.11
ip: 10.0.0.11
etcd_member_name: cp-1
ssh_user: ubuntu # optional per-node override
ssh_key_path: ~/.ssh/cp1 # optional per-node override
etcd_nodes: [] # only when etcd_deployment: external
workers: [] # optional, for kubelet/runtime/network tools
paths: # override only what differs
etcd_data_dir: /var/lib/etcd
etcd_backup_dir: /var/lib/etcd-backup
k8s_manifests: /etc/kubernetes/manifests
k8s_pki: /etc/kubernetes/pki
k8s_pki_etcd: /etc/kubernetes/pki/etcd
kubelet_config: /var/lib/kubelet/config.yaml
kubelet_kubeconfig: /etc/kubernetes/kubelet.conf
kubelet_flags: /var/lib/kubelet/kubeadm-flags.env
kubelet_service: /etc/systemd/system/kubelet.service
kubelet_dropin: /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
containerd_config: /etc/containerd/config.toml
crio_config: /etc/crio/crio.conf
cni_bin: /opt/cni/bin
cni_config: /etc/cni/net.d
etcd_cert_dir: /etc/kubernetes/pki/etcd
etcd_ca_cert: ca.crt
etcd_client_cert: server.crt # supports a {node} placeholder
etcd_client_key: server.key # supports a {node} placeholder
etcd_env_file: "" # Kubespray: /etc/etcd.envEnvironment variables
All prefixed K8S_DR_. Also readable from a .env file in the working directory.
Variable | Default | Description |
|
|
|
|
| Path to the cluster file |
| all 8 groups | Comma-separated groups to load |
|
|
|
| (stderr only) | Also append audit JSON to this file |
|
| Default SSH user |
|
| Default private key |
|
| Default SSH port |
|
| Connect timeout, seconds |
|
|
|
| (none) | Jump host address |
| (none) | Jump host user |
| (none) | Jump host key |
|
| Jump host port |
Values in cluster.yaml win over the environment for anything both can express — the env vars are
there for container deployments where mounting one file is easier than templating two.
Kubespray clusters
Kubespray doesn't lay things out the way kubeadm does: etcd runs as a systemd unit rather than
a static pod, certificates are .pem files in /etc/ssl/etcd/ssl, and renewal goes through a
script instead of kubeadm certs renew. Tell the server about it and every tool adapts:
cluster:
etcd_deployment: host # systemctl, not a static pod
cert_management: script # not kubeadm
paths:
k8s_pki: /etc/kubernetes/ssl
k8s_pki_etcd: /etc/ssl/etcd/ssl
etcd_cert_dir: /etc/ssl/etcd/ssl
etcd_ca_cert: ca.pem
etcd_client_cert: "admin-{node}.pem" # {node} → the node's name
etcd_client_key: "admin-{node}-key.pem"
etcd_env_file: /etc/etcd.env
kubelet_config: /etc/kubernetes/kubelet-config.yaml
kubelet_flags: /etc/kubernetes/kubelet.envTwo ready-made starting points ship with the repo: cluster.stacked.example.yaml (stacked etcd +
Calico) and cluster.host-etcd.example.yaml (host etcd + Cilium).
MCP client setup
.mcp.json in the project root, or ~/.claude.json for every project:
{
"mcpServers": {
"k8s-dr": {
"type": "stdio",
"command": "k8s-dr-mcp",
"env": {
"K8S_DR_MODE": "readonly",
"K8S_DR_CLUSTER_CONFIG": "/absolute/path/to/cluster.yaml"
}
}
}
}Without installing the entry point, use "command": "python", "args": ["-m", "k8s_dr_mcp"].
Verify with claude mcp list. A copyable starting point lives in .mcp.json.example, including a
second server for the healthy-cluster case.
macOS ~/Library/Application Support/Claude/claude_desktop_config.json,
Windows %APPDATA%\Claude\claude_desktop_config.json:
{
"mcpServers": {
"k8s-dr": {
"command": "python",
"args": ["-m", "k8s_dr_mcp"],
"env": {
"K8S_DR_MODE": "readonly",
"K8S_DR_CLUSTER_CONFIG": "/absolute/path/to/cluster.yaml"
}
}
}
}Cursor — .cursor/mcp.json; Windsurf — ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"k8s-dr": {
"command": "python",
"args": ["-m", "k8s_dr_mcp"],
"env": {
"K8S_DR_MODE": "readonly",
"K8S_DR_CLUSTER_CONFIG": "/absolute/path/to/cluster.yaml"
}
}
}
}VS Code — .vscode/mcp.json, same shape but the top-level key is "servers" and each entry needs
"type": "stdio".
The agent chooses based on what's actually broken: kubectl while the API answers, SSH once it doesn't.
┌──────────┐
│ Claude │ sees every tool from both servers
└────┬─────┘
│
┌─────┴───────────────────┐
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ k8s-dr-mcp │ │ kubectl MCP │
│ 63 DR tools │ │ server │
│ │ │ │
│ SSH → nodes │ │ K8s API │
│ │ │ │
│ when the │ │ when the │
│ API is DOWN │ │ cluster is OK │
└──────────────┘ └──────────────────┘See .mcp.json.example for a working two-server config, including the SSH port-forward wrapper for
reaching a private API endpoint through a bastion.
Enabling write operations
"env": { "K8S_DR_MODE": "readwrite" }That only opens gate 1. Every destructive tool still needs confirm=True and still has to pass
pre-flight checks. Running read-only day to day and restarting in readwrite when you've decided on a
fix is a reasonable habit.
Docker
docker build -t k8s-dr-mcp .docker run --rm -i \
-v ./cluster.yaml:/config/cluster.yaml:ro \
-v ./ssh:/config/ssh:ro \
-e K8S_DR_MODE=readonly \
k8s-dr-mcpNote: the image sets
K8S_DR_MODE=readwritein its ownENVso it works out of the box for recovery. Pass-e K8S_DR_MODE=readonly(as above) — or edit theDockerfile— if you'd rather the container default to safe.
The image runs as a non-root user, expects the cluster file at /config/cluster.yaml, and reads SSH
keys from wherever your config points — /config/ssh/<key> if you mount the repo's ssh/
directory. From an MCP client:
{
"mcpServers": {
"k8s-dr": {
"type": "stdio",
"command": "docker",
"args": [
"run", "--rm", "-i",
"-v", "./cluster.yaml:/config/cluster.yaml:ro",
"-v", "./ssh:/config/ssh:ro",
"-e", "K8S_DR_MODE=readonly",
"k8s-dr-mcp:latest"
]
}
}
}Context budget
63 tool definitions is a lot of context to hand an LLM before it has done anything. Load only the groups the situation calls for:
K8S_DR_TOOL_GROUPS=diagnostics,etcd,certificates all groups · 63 tools selected · 27 tools
┌───────────────────────┐ ┌───────────────────────┐
│ diagnostics 10 │ │ diagnostics 10 │ ✓ always on
│ etcd 10 │ │ etcd 10 │ ✓
│ certificates 7 │ ────► │ certificates 7 │ ✓
│ control-plane 7 │ │ │
│ kubelet 8 │ │ ~57% fewer tool │
│ runtime 6 │ │ definitions in the │
│ network 9 │ │ context window │
│ recovery 6 │ └───────────────────────┘
└───────────────────────┘diagnostics is always registered whether you list it or not — you can't triage without it.
How it works
┌──────────────────────────┐
│ MCP client │
│ Claude Code · Desktop │
│ Cursor · Windsurf │
└────────────┬─────────────┘
│ JSON-RPC over stdio
┌────────────▼─────────────┐
│ k8s-dr-mcp (FastMCP) │
│ │
│ tool router │ ← K8S_DR_TOOL_GROUPS
│ safety gates │ ← mode · confirm · preflight
│ auto-backup │
│ audit logger │ → JSON, stderr or file
└────────────┬─────────────┘
│ AsyncSSH connection pool
│ cached · auto-reconnect · parallel
┌────────────▼─────────────┐
│ bastion / jump host │ optional
└────────────┬─────────────┘
│ tunneled SSH
┌──────────────────────┼──────────────────────┐
│ │ │
┌────────▼────────┐ ┌────────▼────────┐ ┌────────▼────────┐
│ cp-1 │ │ cp-2 │ │ cp-3 │
│ etcd │ │ etcd │ │ etcd │
│ kube-apiserver │ │ kube-apiserver │ │ kube-apiserver │
│ scheduler │ │ scheduler │ │ scheduler │
│ controller-mgr │ │ controller-mgr │ │ controller-mgr │
│ kubelet │ │ kubelet │ │ kubelet │
└─────────────────┘ └─────────────────┘ └─────────────────┘
systemctl · etcdctl · kubeadm · crictl · journalctl · openssl · ss · ipThe SSH pool keeps one connection per node, opens the bastion tunnel once and reuses it, reconnects
on drop, and fans out across all control-plane nodes concurrently — so dr_cluster_overview on a
three-node cluster costs about as long as querying one node.
Everything the server does is an ordinary shell command over SSH. Nothing is installed on the nodes, and nothing depends on the cluster being able to schedule a pod.
Layout
src/k8s_dr_mcp/
├── server.py FastMCP instance, SSH pool lifespan
├── config.py env settings + cluster YAML loading
├── models/ pydantic models — cluster topology, tool results
├── ssh/ AsyncSSH connection pool, command executor
├── safety/ gates · preflight checks · auto-backup · audit log
└── tools/ the 8 tool groupsDevelopment
pip install -e ".[dev]"
pytest tests/ -v # test suite
ruff check src/ tests/ # lint
mypy src/ # type check (advisory — see below)Adding a tool: write it in the right tools/*.py module, register it in that module's register(),
and if it changes anything on a node, route it through run_safety_gates() with a confirm: bool
parameter and a pre-flight check.
What CI enforces
.github/workflows/ci.yml runs on every push and pull request:
Job | Gate |
|
|
|
|
| wheel + sdist, |
| image builds and still defaults to |
| no private-key material, no tracked |
|
|
Type-checking is advisory on purpose. FastMCP tools receive their context as ctx: Context = None
— the framework injects it at call time, but mypy reads the None default as an implicit Optional
and flags every such signature. Annotating Context | None just relocates the complaint to each
ctx. use site, so the honest options are a large signature rework or an advisory job. If you make
the type errors go away without weakening the checks, that's a very welcome PR.
Tech
Python 3.10+, asyncio throughout
FastMCP — MCP server framework
AsyncSSH — non-blocking SSH; parallel node fan-out without a thread pool
Pydantic — config validation and structured tool results
Security
This server executes privileged shell commands on your Kubernetes nodes, usually with sudo. Treat
it as the sensitive thing it is:
Keep it read-only unless you are actively recovering something.
Use a dedicated SSH key for the server rather than your personal one, and scope it as tightly as your setup allows.
Never commit keys or real inventories.
cluster.yaml,.env,.mcp.jsonandssh/*are gitignored for exactly this reason — if one slips through, rotate the key; deleting the file isn't enough.Turn on host key verification in production.
K8S_DR_SSH_KNOWN_HOSTS=nonedisables it, which is convenient for a lab and a bad idea anywhere else.Point
K8S_DR_AUDIT_LOGat a real file when running against production, so there's a record of what ran and when.
Found a vulnerability? Please report it privately rather than opening a public issue.
Status
Alpha. The tooling is complete and covered by tests, but it has been exercised against a limited set of cluster topologies (kubeadm and Kubespray, containerd, Calico and Cilium). Try it on a throwaway cluster before you trust it with a real outage — ideally rehearse your recovery with it, so the first time you use it in anger isn't the first time you use it.
Reports about other topologies — CRI-O, external etcd, flannel, k3s — are especially welcome.
Contributing
Issues and pull requests are welcome. Useful contributions, roughly in order of value:
Support for cluster layouts that don't work yet
New recovery workflows for failure modes you've actually hit
Pre-flight checks that would have caught something the hard way
Please run ruff and the tests before opening a PR, and keep destructive tools behind the
three gates.
License
MIT.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/kulibins/k8s-dr-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server