Skip to main content
Glama
raphyduck

ssh-mcp-gateway

by raphyduck
README.md
# ssh-mcp-gateway

Serveur MCP qui donne accès SSH à une machine, avec **approbation manuelle obligatoire**
de chaque session et **terminaison automatique** de la session précédente quand une
nouvelle est demandée (+ timeouts d'inactivité et de durée max).

## 1. Génération d'une clé SSH dédiée (PAS ta clé perso)

```bash
ssh-keygen -t ed25519 -f ./keys/gateway_key -C "claude-mcp-gateway" -N ""
```

Sur la machine cible (`SSH_USER@SSH_HOST`), ajoute la clé publique dans
`~/.ssh/authorized_keys` avec des restrictions :

```
restrict,command="/bin/bash",pty no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAA... claude-mcp-gateway
```

`restrict` désactive tout par défaut. Adapte `command=` si tu veux limiter
encore plus (ex: un script wrapper qui filtre les commandes autorisées).
Crée idéalement un utilisateur dédié non-root avec sudo désactivé.

## 2. Configuration

```bash
cp .env.example .env
```

Remplis :
- `MCP_BEARER_TOKEN` : `openssl rand -hex 32`
- `APPROVAL_TOKEN` : `openssl rand -hex 32` (différent du précédent)
- `SSH_HOST`, `SSH_USER`, `SSH_PRIVATE_KEY_PATH`
- `SESSION_MAX_MINUTES`, `SESSION_IDLE_MINUTES` selon ta tolérance

## 3. Installation et lancement

```bash
npm install
npm start
```

Tourne par défaut sur `:8787`. Lance-le sous systemd/Docker avec
`Restart=always`, et **ne l'expose jamais directement sur internet** : passe
par un Cloudflare Tunnel.

## 4. Exposition via Cloudflare Tunnel (recommandé)

```bash
cloudflared tunnel create ssh-mcp-gateway
cloudflared tunnel route dns ssh-mcp-gateway mcp.tondomaine.fr
```

`config.yml` :

```yaml
tunnel: <TUNNEL_ID>
credentials-file: /root/.cloudflared/<TUNNEL_ID>.json
ingress:
  - hostname: mcp.tondomaine.fr
    service: http://localhost:8787
  - service: http_status:404
```

Optionnel mais recommandé : active **Cloudflare Access** sur ce hostname
pour ajouter une couche d'auth supplémentaire (ex: email OTP) avant même
d'atteindre le serveur MCP.

```bash
cloudflared tunnel run ssh-mcp-gateway
```

## 5. Ajout du Custom Connector dans Claude.ai

Settings > Connectors > Add custom connector :
- URL : `https://mcp.tondomaine.fr/mcp`
- Auth : configure le `MCP_BEARER_TOKEN` comme credential (selon ce que
  propose l'UI au moment où tu le fais — Bearer/API key ou champ "secret"
  dans Advanced settings).

## 6. Utilisation depuis une conversation Claude

1. Claude appelle `session_request` → reçoit deux liens (`approve` / `deny`).
2. Tu ouvres le lien `approve` sur ton téléphone (uniquement protégé par
   `APPROVAL_TOKEN`, garde-le secret — ne le partage jamais avec Claude
   lui-même autrement que via ces liens générés serveur-side).
3. Claude vérifie `session_status` → `approved`.
4. Claude utilise `ssh_exec` autant que nécessaire ; chaque appel repousse
   le timeout d'inactivité.
5. Claude appelle `session_end` en fin de tâche, ou la session expire
   automatiquement (`SESSION_IDLE_MINUTES` / `SESSION_MAX_MINUTES`), ou tu
   la termines toi-même via le lien "Terminer la session maintenant".
6. Toute nouvelle `session_request` termine immédiatement la session en
   cours, donc une seule session active à la fois — garanti.

## Limites et points à durcir toi-même

- Le serveur ne whitelist pas les commandes : `ssh_exec` exécute ce que
  Claude lui envoie, dans les limites de ce que ton `authorized_keys`
  autorise. Si tu veux une whitelist de commandes, mets-la dans le
  `command=` forcé côté `authorized_keys` plutôt que dans ce serveur.
- Pas de logging persistant des commandes exécutées par défaut — ajoute un
  `fs.appendFile` dans `ssh_exec` si tu veux un audit trail.
- `APPROVAL_TOKEN` et `MCP_BEARER_TOKEN` sont des secrets statiques : si
  l'un fuite, change-le et redémarre.
- Pas de notification push native ici : les liens d'approbation, c'est toi
  qui dois aller les chercher dans la conversation et les ouvrir. Si tu
  veux du push (ntfy.sh, Pushover), ajoute un appel HTTP dans
  `session_request`.