Skip to main content
Glama

ssh-mcp

Streamable HTTP를 통해 AI 에이전트가 SSH 인프라에 통제된 액세스 권한을 얻을 수 있게 해주는 중앙 집중형 MCP 게이트웨이입니다.

ssh-mcp는 단일 HTTP 서비스로 실행됩니다. 여러 AI 클라이언트 — 에이전트, CI 파이프라인, 대시보드 — 하나의 게이트웨이에 연결됩니다. SSH 자격 증명은 게이트웨이에 유지됩니다. 권한 부여 정책, 감사 로깅, 속도 제한은 SSH 명령이 실행되기 전에 중앙에서 적용됩니다.

License: MIT Docker MCP Security M8ven Live Monitored


목차


Related MCP server: MCP SSH Orchestrator

아키텍처

로컬 stdio MCP (일반적인 패턴)

각 에이전트는 자체 프로세스를 실행합니다. SSH 자격 증명은 모든 머신에 있습니다. 중앙 집중식 제어가 없습니다.

AI client
   │
   ▼
local MCP process ──► SSH target

ssh-mcp (중앙 집중형 HTTP 게이트웨이)

단일 배포가 모든 클라이언트에 서비스를 제공합니다. 자격 증명, 정책, 로그는 한곳에 있습니다.

AI clients ───────┐
CI agents ────────┼──► ssh-mcp ──► SSH targets
Dashboards ───────┘      │
                         ├─ API-key authentication
                         ├─ per-client authorization
                         ├─ rate limiting
                         ├─ audit logging
                         └─ connection pooling

ssh-mcp가 필요한 이유

  • 중앙 집중형 HTTP 게이트웨이 — 단일 배포가 Streamable HTTP를 통해 모든 AI 에이전트, CI 파이프라인, 대시보드에 서비스를 제공합니다

  • 클라이언트별 권한 부여 — 서로 다른 API 키가 서로 다른 서버에서 서로 다른 명령 세트를 허용합니다

  • 계층형 명령 정책 — 차단 패턴, 위험 셸 감지, 대상별 허용 목록이 함께 작동합니다

  • 중앙 집중형 SSH 액세스 — SSH 자격 증명은 게이트웨이에 위치하며 각 에이전트 머신이 아닌 곳에 있습니다

  • 감사 추적 — 모든 명령, 모든 클라이언트, 모든 결과 — 요청 추적이 포함된 구조화된 JSONL 로그

  • 운영 복원력 — 연결 풀링, 서킷 브레이커, 지수 백오프 재시도

  • 관측 가능성 — 모니터링을 위한 Prometheus 메트릭 및 상태 엔드포인트


멀티 에이전트 액세스 제어

에이전트마다 서로 다른 권한이 필요합니다. ssh-mcp는 게이트웨이에서 이를 강제합니다:

monitoring agent  →  API key A  →  read-only commands  →  all servers
deployment agent  →  API key B  →  deploy commands      →  web servers only
database agent    →  API key C  →  db commands           →  database server only
                  ┌─ monitoring agent (read-only, all servers)
                  ├─ deployment agent (deploy commands, web only)
MCP clients ──────┼─ database agent (db commands, db server only)
                  └─ ...
                         │
                         ▼
                      ssh-mcp
                         │
                  centralized policies
                         │
              ┌──────────┼──────────┐
              ▼          ▼          ▼
             web         db      monitoring
           servers    servers     servers

이 구성을 보여주는 최소 구성:

{
  "version": 1,
  "ssh_targets": {
    "web-1": { "host": "10.0.1.10", "username": "deploy" },
    "db-1":  { "host": "10.0.1.20", "username": "dbadmin" }
  },
  "allowed_commands": {
    "default": {
      "web-1": { "allow": ["uptime", "df -h", "free -m"] }
    },
    "api_keys": {
      "deploy-key": {
        "web-1": { "allow": ["systemctl restart app", "deploy *"] }
      },
      "db-key": {
        "db-1": { "allow": ["systemctl restart postgres", "pg_dump *"] }
      }
    }
  }
}

문제

대부분의 MCP SSH 서버는 로컬 stdio 프로세스로 실행됩니다 — 클라이언트당 하나씩, 공유 상태도, 중앙 집중식 권한 부여도, 감사 추적도 없습니다. 여러 AI 에이전트, CI 파이프라인 또는 대시보드가 SSH 액세스를 필요로 할 때, 각각은 자체 SSH 키를 독립적으로 관리하고 자체 MCP 프로세스를 실행합니다. 그 결과 다음과 같은 문제가 발생합니다:

  • 중앙 집중식 액세스 제어 없음 — 모든 클라이언트가 실행할 수 있는 명령을 스스로 결정합니다

  • 감사 추적 없음 — 운영팀이 명령을 볼 수 없습니다

  • SSH 키 확산 — 에이전트를 실행하는 모든 머신에 키가 분산되어 있습니다

  • 속도 제한 없음 — 통제 불능 에이전트가 대상을 압도할 수 있습니다

  • 연결 풀링 없음 — 각 클라이언트가 SSH 세션을 독립적으로 열고 닫습니다

ssh-mcp는 단일 MCP 서버를 HTTP 게이트웨이로 배포하여 이 문제를 해결합니다. 모든 클라이언트는 게이트웨이에 연결되고, 게이트웨이는 SSH 대상에 연결됩니다. 권한 부여, 인증, 속도 제한, 연결 풀링, 감사 로깅이 한곳에서 이루어집니다.


사용 사례

멀티 에이전트 서버 관리

서로 다른 액세스 수준을 가진 AI 에이전트 팀을 실행합니다. 배포 에이전트는 웹 서버에서 systemctl restart nginx를 실행할 수 있고, 모니터링 에이전트는 어디서나 journalctl을 실행할 수 있으며, 데이터베이스 에이전트는 DB 서버에서 psql만 실행할 수 있습니다. 각 에이전트는 자체 API 키로 인증하며, 각 키에는 고유한 권한 집합이 있습니다.

CI/CD 파이프라인 통합

모든 러너에서 SSH 키를 관리하는 대신 CI 파이프라인을 ssh-mcp에 연결하세요. 파이프라인당 하나의 API 키, CI 서브넷에 대한 네트워크 기반 규칙, 명령 허용 목록을 통해 배포 스크립트가 정확히 필요한 명령만 실행하도록 보장합니다 — 그 이상은 아닙니다.

중앙 집중식 로그 및 구성 검색

ssh_download_file을 사용하면 MCP 클라이언트에서 벗어나지 않고 원격 서버에서 로그, 구성 파일 또는 데이터베이스 덤프를 가져올 수 있습니다. 8계층 경로 검증과 샌드박스 루트 설정은 파일 전송이 안전한 경계 내에 유지되도록 보장합니다.

서버 상태 대시보드

서버 전체에서 uptime, free, df, ps를 조회하는 MCP 기반 대시보드를 구축하세요. 연결 풀은 SSH 세션을 재사용하고, 서킷 브레이커는 실패 대상을 격리하며, /metrics의 Prometheus 메트릭이 기존 모니터링 스택에 공급됩니다.

규정 준수 및 감사

모든 명령은 구조화된 JSONL로 기록됩니다: 누가 무엇을, 어느 서버에서, 어떤 IP에서 실행했는지, 허용되었는지, 얼마나 걸렸는지. matched_via 필드는 어떤 권한 부여 계층이 결정을 내렸는지 정확히 추적합니다. 구성 변경은 변경 전/후 상태와 함께 별도로 기록됩니다.


보안 모델

ssh-mcp는 모든 계층에 심층 방어를 적용합니다. 전체 보안 모델은 docs/SECURITY.md에 문서화되어 있습니다.

보안 경계: ssh-mcp는 SSH 앞에 권한 부여, 인증, 감사 계층을 추가합니다. 기본 SSH 계정의 권한을 대체하지 않습니다. 명령이 허용되면 SSH 사용자는 해당 계정이 가진 권한으로 명령을 실행합니다. 게이트웨이 자체는 TLS 및 네트워크 액세스 제어로 보호해야 합니다. 로그에는 명령 출력이 포함될 수 있으므로 그에 따라 취급해야 합니다.

명령 권한 부여 체인

명령은 순서가 지정된 계층형 체인을 통해 평가됩니다. 어떤 계층에서 거부하면 요청은 거기서 중단됩니다:

계층

검사 내용

1. 대상 검증

서버 이름이 알려진 이름인가?

2. block_patterns

명령이 차단된 정규식과 일치하는가?

3. 위험한 패턴

$(), 백틱 또는 줄바꿈을 포함하는가?

4. 리다이렉션 가드

셸 리다이렉션이 /dev/, /proc/, /sys/를 대상으로 하는가?

5. 분할

리다이렉션을 제거하고 &&, ` 기준으로 분할

, ;, \|`, 각 세그먼트는 전체 체인을 실행합니다

6. default 규칙

모든 클라이언트 허용/거부 규칙

7. api_keys 규칙

키별 허용/거부 규칙

8. networks 규칙

CIDR별 허용/거부 규칙

9. 거부

암시적 폴백

인증

API 키는 X-API-Key 또는 Authorization: Bearer 헤더를 통해 전송됩니다. 키는 PBKDF2-HMAC-SHA256(100,000회 반복, 무작위 16바이트 솔트)으로 해시되며 상수 시간 비교로 검증됩니다. 원시 키는 절대 저장되지 않습니다.

입력 정화

명령, 대상 이름, 로그 문자열은 처리 전에 정화됩니다: 널 바이트 제거, 제어 문자 제거, NFKC 정규화, block_patterns에 대한 ReDoS 보호 적용.

경로 탐색 방지

SFTP 전송은 널 바이트 검사, 제어 문자 제거, 점 세그먼트 정규화, 심볼릭 링크 확인, 샌드박스 루트 적용을 포함한 8계층 경로 검증을 거칩니다.

속도 제한

클라이언트 IP당 슬라이딩 윈도우 속도 제한기(60초당 60개 요청, /health 면제). 위반 시 Retry-After와 함께 HTTP 429를 반환합니다.

속도 제한은 settings.rate_limit에서 구성할 수 있습니다:

"settings": {
  "rate_limit": {
    "enabled": true,                        // set false to disable entirely
    "max_requests_per_minute": 60,          // max requests per client IP in the window
    "window_seconds": 60.0,                 // sliding-window duration
    "cleanup_interval_seconds": 300.0       // expired-entry GC interval
  }
}

참고: 속도 제한기는 컨테이너 시작 시 초기 구성으로 한 번만 생성되며 구성 핫 리로드 시 다시 생성되지 않습니다. 속도 제한을 비활성화하려면 부팅 시 존재하는 구성(예: 마운트된 볼륨의 config/ssh-mcp-config.json)에서 settings.rate_limit.enabledfalse로 설정해야 합니다. 이는 대량 요청 클라이언트 또는 단일 IP에서 많은 요청을 보내는 테스트 스위트에 유용합니다.


빠른 시작

사전 요구 사항

  • Docker 및 Docker Compose

  • 연결하려는 서버에 대한 SSH 키 페어(또는 대상별 비밀번호)

1. 디렉터리 설정

mkdir -p config logs
ssh-keygen -t ed25519 -f ssh_key -N ""
cp default-config.json config/ssh-mcp-config.json

2. SSH 대상 추가

config/ssh-mcp-config.json을 열고 대상 하나를 추가합니다:

{
  "version": 1,
  "ssh_targets": {
    "web-server": {
      "host": "192.168.1.10",
      "port": 22,
      "username": "deploy",
      "private_key": "/app/ssh_key"
    }
  },
  "block_patterns": [ "\\brm\\s+-rf\\b", "\\bdd\\s+if=" ],
  "allowed_commands": {
    "default": [
      { "targets": ["*"], "commands": ["hostname", "uptime", "free", "df", "ps", "ls", "cat"] }
    ]
  },
  "settings": {}
}

3. 서버 시작

docker compose up -d --build

4. 실행 확인

curl http://localhost:9080/health
# {"status": "ok", "connection_pool": {...}}

5. MCP 클라이언트 연결

Streamable HTTP를 지원하는 모든 MCP 클라이언트가 연결할 수 있습니다. API 키 헤더와 함께 http://localhost:9080/mcp를 가리키면 됩니다. 자세한 내용은 MCP 클라이언트 구성을 참조하세요.

6. 서버 목록 확인 및 명령 실행

curl -X POST http://localhost:9080/mcp \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "ssh_list_servers",
      "arguments": {}
    }
  }'

curl -X POST http://localhost:9080/mcp \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "ssh_execute_command",
      "arguments": {"server_name": "web-server", "command": "uptime"}
    }
  }'

MCP 클라이언트 구성

Streamable HTTP 전송을 지원하는 모든 MCP 클라이언트가 연결할 수 있습니다. 구성 형식은 클라이언트에 따라 다르므로 아래 URL과 헤더를 사용하세요.

설정

전송

Streamable HTTP

URL

https://ssh-mcp.example.com/mcp

인증

X-API-Key 헤더 또는 Authorization: Bearer

일반적인 Streamable HTTP 구성

{
  "mcpServers": {
    "ssh": {
      "url": "http://localhost:9080/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key>"
      }
    }
  }
}

Python 클라이언트

import requests

MCP_URL = "https://ssh-mcp.example.com/mcp"
API_KEY = "your-api-key"


def call_tool(name: str, arguments: dict) -> dict:
    response = requests.post(
        MCP_URL,
        headers={
            "Content-Type": "application/json",
            "X-API-Key": API_KEY,
        },
        json={
            "jsonrpc": "2.0",
            "id": 1,
            "method": "tools/call",
            "params": {"name": name, "arguments": arguments},
        },
    )
    response.raise_for_status()
    return response.json()


print(call_tool("ssh_list_servers", {}))
print(call_tool("ssh_execute_command", {
    "server_name": "web-server",
    "command": "uptime",
}))

원시 JSON-RPC

도구 호출을 JSON-RPC tools/call 요청으로 /mcp에 보냅니다:

curl -X POST http://localhost:9080/mcp \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "ssh_execute_command",
      "arguments": {"server_name": "web-server", "command": "uptime"}
    }
  }'

도구

모든 도구 호출은 /mcp에 대한 JSON-RPC tools/call 요청입니다. 모든 도구는 문자열(JSON 또는 일반 텍스트)을 반환합니다.

도구

매개변수

설명

ssh_list_servers

(없음)

구성된 SSH 대상을 나열합니다(호스트, 포트, 사용자 이름 — 비밀 정보 없음)

ssh_list_allowed_commands

server_name (str)

현재 클라이언트가 대상에서 실행할 수 있는 명령을 나열합니다(기본 규칙 + api_key 규칙 + 네트워크 규칙의 합집합)

ssh_execute_command

server_name (str), command (str), timeout (int, 기본값 30), sudo (bool, 기본값 false)

SSH를 통해 명령을 실행합니다. stdout을 반환합니다(stderr는 [STDERR]로, 종료 코드는 [EXIT: n]으로 추가됨)

ssh_download_file

server_name (str), remote_path (str)

SFTP로 파일을 다운로드합니다. 인증은 cat <path>와 동일합니다

ssh_upload_file

server_name (str), remote_path (str), content (str), permissions (str, 기본값 "0644")

SFTP로 파일을 업로드합니다. 인증은 tee <path>와 동일합니다

ssh_check_connection

server_name (str), timeout (int, 기본값 10)

대상의 checkcommand를 실행하여 SSH 연결을 확인합니다. 성공 플래그, 출력, 종료 코드를 반환합니다

예제

# List available servers
call_tool("ssh_list_servers", {})
# {"web-server": {"host": "192.168.1.10", "port": 22, "username": "deploy"}}

# List what this client can run on web-server
call_tool("ssh_list_allowed_commands", {"server_name": "web-server"})
# ["cat", "df", "du", "free", "grep", "head", "hostname", ...]

# Execute a command
call_tool("ssh_execute_command", {
    "server_name": "web-server",
    "command": "uptime",
})
# " 07:12:33 up 10 days,  2:15,  1 user,  load average: 0.08, 0.03, 0.01"

# Download a file
call_tool("ssh_download_file", {
    "server_name": "web-server",
    "remote_path": "/etc/hostname",
})
# "web-server\n"

# Upload a file
call_tool("ssh_upload_file", {
    "server_name": "web-server",
    "remote_path": "/tmp/backup.sql",
    "content": "CREATE TABLE ...;\n",
    "permissions": "0640",
})
# "OK: Uploaded 19 bytes to /tmp/backup.sql"

# Check SSH connectivity
call_tool("ssh_check_connection", {"server_name": "web-server"})
# {"success": true, "output": "ping", "error": null, "exit_code": 0, "checkcommand": "echo ping"}

# Check with custom timeout
call_tool("ssh_check_connection", {"server_name": "web-server", "timeout": 5})

sudo 참고: sudo_password 매개변수는 없습니다. sudo에 암호가 필요한 경우 구성의 대상 password 필드에서 가져옵니다. sudo 플래그는 명령을 sudo -S -p ''(구성의 암호 사용) 또는 sudo -n(암호 없이)으로 감쌉니다.

오류 응답

실패 시 도구는 다음을 반환합니다:

{
  "error": true,
  "error_type": "AuthorizationError",
  "message": "Command rejected: target 'foo' not found",
  "retryable": false,
  "request_id": "abc-123"
}

일반적인 error_type 값: AuthorizationError, PathValidationError, FileTransferError, SSHAuthenticationError, SSHTimeoutError, MCPSSHError. retryable 플래그는 SSHTimeoutError의 경우 true입니다. 속도 제한 위반 시 대신 HTTP 429를 반환합니다.


구성

구성 파일 위치

서버는 <config_dir>/ssh-mcp-config.json을 읽습니다. config_dir--config CLI 플래그 또는 MCP_SSH_CONFIG_PATH 환경 변수로 설정합니다(기본값: /config). 파일이 없으면 서버는 번들된 default-config.json을 작성합니다.

최상위 구조

{
  "version": 1,
  "ssh_targets": { ... },
  "block_patterns": [ ... ],
  "allowed_commands": {
    "default": [ ... ],
    "api_keys": [ ... ],
    "networks": [ ... ]
  },
  "settings": { ... }
}

구성은 로드 시 config.schema.json(JSON Schema Draft 2020-12)에 대해 검증됩니다. 알 수 없는 키는 하드 오류를 발생시킵니다.

ssh_targets

서버 식별자로 키가 지정된 객체입니다. 각 대상은 host, port, usernameprivate_key 또는 password 중 하나 이상이 필요합니다.

"ssh_targets": {
  "web-server": {
    "host": "192.168.1.10",
    "port": 22,
    "username": "deploy",
    "private_key": "/app/ssh_key",
    "checkcommand": "echo ping"
  }
}

필드

필수

기본값

설명

host

호스트 이름 또는 IP 주소

port

아니요

22

SSH 포트

username

SSH 사용자 이름

private_key

*

서버 파일시스템의 SSH 개인 키 파일 경로

password

*

SSH 암호(secrets.json 또는 환경 변수로도 설정 가능)

checkcommand

아니요

"echo ping"

연결 확인을 위해 ssh_check_connection이 실행하는 명령

* private_key 또는 password 중 하나 이상이 필요합니다.

private_key는 인라인 키가 아니라 서버 파일시스템의 경로(Docker에서는 컨테이너에 마운트됨)입니다.

block_patterns

정규식 패턴 목록입니다. 패턴과 일치하는 모든 명령은 다른 허용 목록 계층과 관계없이 거부됩니다. 패턴은 로드 시 치명적 역추적(catastrophic backtracking) 구조가 있는지 검사되고(ReDoS 보호) 런타임에는 타임아웃 가드와 함께 컴파일됩니다.

allowed_commands

세 개의 하위 객체가 각 클라이언트가 실행할 수 있는 명령을 제어합니다:

  • default — 모든 클라이언트에 대한 규칙(더 구체적인 계층이 먼저 결정하지 않는 한)

  • api_keys — 키별 규칙, key_hash로 매칭

  • networks — CIDR별 규칙, 클라이언트 소스 IP로 매칭

각 규칙에는 targets 목록(서버 ID 또는 모든 서버에 대한 "*")과 commands 목록(기본 명령 이름 또는 모든 명령에 대한 "*")이 있습니다.

"allowed_commands": {
  "default": [
    { "targets": ["*"], "commands": ["hostname", "uptime", "free", "df", "ps"] }
  ],
  "api_keys": [
    {
      "name": "ci-bot",
      "key_hash": "pbkdf2:sha256:100000$<salt>$<hash>",
      "rules": [
        { "targets": ["web-server"], "commands": ["systemctl", "journalctl"] }
      ]
    }
  ],
  "networks": [
    {
      "name": "home-lan",
      "range": "192.168.1.0/24",
      "rules": [
        { "targets": ["*"], "commands": ["*"] }
      ]
    }
  ]
}

settings

설정

기본값

설명

max_output_length

50000

클라이언트에 반환되는 명령 출력의 최대 바이트(int 또는 크기 문자열)

command_timeout_max

120

명령 타임아웃의 상한(초)

retry_max_attempts

3

일시적 SSH 실패에 대한 재시도 횟수

retry_backoff_base_seconds

1.0

기본 지수 백오프(초)

circuit_breaker_failure_threshold

5

대상별 회로가 열리기 전 실패 횟수

circuit_breaker_timeout_seconds

60.0

열린 회로의 복구 타임아웃(초)

log_level

"INFO"

로그 수준: DEBUG, INFO, WARNING, ERROR

max_log_output

4096

로그 항목에 저장되는 출력의 최대 문자 수

compress_rotated

true

순환된 로그 파일을 Gzip으로 압축

pool_max_connections_per_target

5

대상별 최대 풀 SSH 연결 수

pool_idle_timeout_seconds

300.0

유휴 연결 타임아웃(초)

pool_cleanup_interval_seconds

60.0

풀 정리 간격(초)

max_concurrent_ssh_connections

20

모든 대상에 대한 전역 상한. 초과 시 HTTP 503 반환

watcher_debounce_seconds

2.0

구성 리로드 사이 최소 간격. 0이면 비활성화

trusted_proxies

[]

신뢰할 수 있는 리버스 프록시 IP(IPv4/IPv6)

SFTP 설정 (settings.sftp)

설정

기본값

설명

sftp.sandbox_root

"/"

SFTP 경로 검증을 위한 루트 디렉터리

sftp.max_path_length

4096

허용되는 최대 SFTP 경로 길이(바이트). 0이면 비활성화

비밀 정보

SSH 대상 암호와 API 키 해시는 기본 구성에서 분리하여 <config_dir>/secrets.json 또는 MCP_SSH_SECRET_* 환경 변수에 넣을 수 있습니다. 우선순위:

environment variables  >  secrets.json  >  ssh-mcp-config.json

비밀 정보 소스

효과

secrets.json

대상별 password 및 키별 key_hash 재정의(이름으로 매칭)

MCP_SSH_SECRET_PASSWORD_<TARGET_ID>

ssh_targets[<TARGET_ID>].password 재정의

MCP_SSH_SECRET_API_KEY_<KEY_NAME>

api_keys 항목 <KEY_NAME>key_hash 재정의

<TARGET_ID><KEY_NAME>은 대문자로 변환되고 -_로 바뀝니다. API 키 값은 원시 키가 아닌 해시 문자열이어야 합니다.

환경 변수 및 CLI 플래그

환경 변수

CLI 플래그

기본값

레거시 대체

MCP_SSH_CONFIG_PATH

--config

/config

CONFIG_DIR

MCP_SSH_SSH_KEY

--ssh-key

ssh_key

SSH_KEY_PATH

MCP_SSH_LOG_DIR

--log-dir

/logs

LOG_DIR

MAX_OUTPUT_LENGTH

--max-output

50000

CONFIG_API_ENABLED

false

CONFIG_API_TOKEN

(API 활성화 시 필수)

--fix-permissions

False

--print-default-config

CLI 플래그는 환경 변수보다 우선합니다. 모든 settings 키는 런타임에 MCP_SSH_SETTING_<KEY>(대문자, -_)로 재정의할 수 있습니다.

핫 리로드

서버는 구성 파일의 변경 사항을 폴링합니다(15초 간격, 2초 디바운스). 변경이 감지되면 리로드하고 검증한 후 새 구성을 원자적으로 교체합니다. 구성 변경 콜백(권한 규칙 재구축, 연결 풀 새로고침)은 교체가 성공한 후 실행됩니다. 가능한 경우 워치독 기반 파일 모니터링이 사용됩니다.


관찰 가능성

상태 확인

GET /health{"status": "ok"}와 연결 풀 통계를 반환합니다. 컨테이너의 HEALTHCHECK는 이 엔드포인트를 사용합니다.

Prometheus 지표

GET /metrics는 전용 레지스트리에 지표를 노출하며 모두 mcpssh_ 접두사가 붙습니다:

지표

유형

레이블

mcpssh_requests_total

Counter

tool, status(success/error/denied)

mcpssh_ssh_connections_total

Counter

target

mcpssh_ssh_connection_duration_seconds

Histogram

target

mcpssh_auth_denials_total

Counter

reason

mcpssh_command_duration_seconds

Histogram

target

mcpssh_pool_active_connections

Gauge

target

mcpssh_pool_idle_connections

Gauge

target

mcpssh_pool_created_total

Counter

target

구조화된 로깅

mcp-ssh 서버는 구성 파일의 settings.logging.log_targets를 통해 구성되는 플러그형 로그 대상을 지원합니다. 각 대상은 모든 로그 항목을 수신하는 독립적인 드라이버입니다.

기본 동작

기본적으로 로그 항목은 사람이 읽을 수 있는 텍스트 형식으로 stdout에 기록됩니다. 이는 컨테이너 로그를 런타임이 수집하는 Docker 환경에 적합합니다.

로그 대상 유형

대상

설정 값

형식

설명

Stdout

"stdout"

텍스트

stdout에 씁니다. 기본 대상입니다.

JSON 파일

"jsonfile"

JSONL

파일에 줄마다 JSON 객체 하나를 씁니다.

텍스트 파일

"file"

텍스트

파일에 사람이 읽을 수 있는 텍스트를 씁니다.

구성

{
  "settings": {
    "log_level": "INFO",
    "logging": {
      "log_targets": [
        { "target": "stdout" },
        { "target": "jsonfile", "filepath": "logs/ssh-mcp.log" }
      ],
      "max_log_output": 4096,
      "compress_rotated": true
    }
  }
}

로그 수준

  • 설정 파일: 기본 수준을 제어하려면 settings.log_level을 설정합니다.

  • 환경 변수: 구성 파일 기본값을 재정의하려면 MCP_SSH_LOG_LEVEL을 설정합니다(예: MCP_SSH_LOG_LEVEL=DEBUG).

  • 대상별: 각 로그 대상은 기본값을 재정의하는 자체 log_level을 가질 수 있습니다.

레거시 구성

settings.logging이 없으면 서버는 로그 디렉터리(기본값 /logs)의 단일 JSONL 파일 대상으로 대체됩니다. 이는 기존 구성과의 하위 호환성을 유지합니다.

텍스트 형식

stdout 및 텍스트 파일 대상은 다음 형식을 사용합니다.

2025-01-15 10:30:00 INFO ssh_execute_command: Command executed on server1

JSON 형식

JSON 파일 대상은 줄마다 JSON 객체 하나를 씁니다.

{"timestamp": "2025-01-15T10:30:00+00:00", "event": "ssh_execute_command", "level": "INFO", "message": "Command executed on server1", "request_id": "abc-123", "log_level": "INFO", "log_format_version": 1}

파일 순환

파일 기반 대상은 max_file_size_mb(기본값: 10MiB)를 초과하면 순환되며, backup_count(기본값: 5)개의 백업을 유지합니다. 순환된 파일은 compress_rotatedtrue이면 gzip으로 압축됩니다.

구성 변경 이벤트

이벤트

의미

config.load

시작 시 초기 구성 로드

config.reload

디스크에서 구성 재읽기(success, changed_keys, targets_added, targets_removed 포함)

config.migrated

스키마 마이그레이션 적용됨(from_version, to_version)

config.default_created

번들된 기본 구성이 복사됨

config.fallback

메모리 내 기본값으로 대체됨

config.callback_error

구성 변경 콜백이 예외를 발생시킴


구성 API 및 웹 대시보드

통합 컨테이너에는 선택적 구성 API 및 웹 대시보드가 포함되어 있습니다. SSH 정책, 대상, 명령 규칙 및 백업을 위한 완전한 관리 플레인입니다. 구성 파일 편집이 필요하지 않습니다. 이 기능은 기본적으로 비활성화되어 있습니다.

제공 기능

  • 웹 대시보드 — 5개 페이지(SSH 대상, 차단 패턴, 명령 규칙, 설정, 백업)가 있는 반응형 단일 페이지 애플리케이션입니다. API 토큰으로 로그인하고 브라우저에서 모든 것을 관리하세요.

  • REST API — 모든 구성 섹션에 대한 전체 CRUD와 구성 검증, API 키 해싱, 백업 관리 및 인라인 SSH 연결 테스트를 제공합니다.

  • API 키 해싱 유틸리티 — 평문 API 키를 구성에 바로 사용할 수 있는 PBKDF2 문자열로 해시합니다. 더 이상 해시 형식을 추측할 필요가 없습니다.

  • 백업 및 복원 — 모든 쓰기 시 자동 구성 백업; 대시보드 또는 API에서 백업을 나열, 복원 또는 삭제합니다.

  • 원자적, 스레드 안전 쓰기 — 모든 구성 쓰기는 검증되고 스레딩 잠금으로 직렬화되며 디스크에 원자적으로 기록됩니다.

  • Swagger UI 및 ReDoc/api/docs/api/redoc에서 자동 생성된 대화형 API 문서.

구성 API 활성화

compose.yaml 또는 .env 파일에 다음 환경 변수를 설정합니다:

변수

기본값

설명

CONFIG_API_ENABLED

false

구성 API를 활성화하려면 true로 설정합니다

CONFIG_API_TOKEN

(활성화 시 필수)

API 요청 인증을 위한 Bearer 토큰

services:
  mcp-ssh:
    environment:
      CONFIG_API_ENABLED: "true"
      CONFIG_API_TOKEN: "your-secret-token-here"

API 엔드포인트

모든 엔드포인트는 MCP 서버와 동일한 Starlette ASGI 애플리케이션의 /api에 마운트됩니다.

상태 확인 및 유틸리티

메서드

경로

설명

GET

/api/health

구성 API에 대한 상태 확인(인증 불필요)

POST

/api/hash-key

평문 API 키를 PBKDF2-HMAC-SHA256 문자열로 해시

GET

/api/config/schema

구성 JSON 스키마 반환(인증 불필요)

POST

/api/config/validate

디스크에 쓰지 않고 구성 dict 검증

구성

메서드

경로

설명

GET

/api/config

전체 구성 가져오기(비밀 값은 마스킹됨)

PUT

/api/config

전체 구성 교체

GET

/api/config/{section}

단일 구성 섹션 가져오기(settings, ssh_targets, allowed_commands, block_patterns)

PUT

/api/config/{section}

단일 구성 섹션 교체

SSH 대상

메서드

경로

설명

GET

/api/config/ssh_targets/{name}

특정 SSH 대상 가져오기(비밀 값은 제거됨)

PUT

/api/config/ssh_targets/{name}

SSH 대상 생성 또는 교체

DELETE

/api/config/ssh_targets/{name}

SSH 대상 삭제

POST

/api/config/ssh_targets/{name}/check

대상의 checkcommand를 통해 SSH 연결 테스트

명령 규칙

메서드

경로

설명

GET

/api/config/allowed_commands

허용 명령 규칙 나열(GET /api/config/{section}을 통해)

PUT

/api/config/allowed_commands

허용 명령 규칙 교체(PUT /api/config/{section}을 통해)

차단 패턴

메서드

경로

설명

GET

/api/config/block_patterns

차단 패턴 나열(GET /api/config/{section}을 통해)

PUT

/api/config/block_patterns

모든 차단 패턴 교체

POST

/api/config/block_patterns

차단 패턴 추가

PUT

/api/config/block_patterns/{index}

인덱스로 단일 차단 패턴 교체

DELETE

/api/config/block_patterns/{index}

인덱스로 단일 차단 패턴 제거

백업

메서드

경로

설명

GET

/api/backups

구성 백업 나열(최신순)

POST

/api/backups/{name}/restore

백업에서 구성 복원

DELETE

/api/backups/{name}

백업 파일 삭제

인증

모든 API 요청(/api/health/api/config/schema 제외)에는 Authorization 헤더에 Bearer 토큰이 필요합니다:

curl -H "Authorization: Bearer your-secret-token-here" http://localhost:9080/api/config

웹 대시보드

활성화하면 http://localhost:9080/ui/에서 반응형 단일 페이지 애플리케이션을 사용할 수 있습니다. Tailwind CSS로 구축된 완전한 관리 UI로, 페이지 새로고침 없이 모든 작업에 대한 토스트 알림과 편집용 모달 대화상자를 제공합니다.

페이지

기능

SSH 대상

대상 보기, 추가, 편집, 삭제; checkcommand를 통한 인라인 연결 테스트; 호스트/포트/사용자 이름이 포함된 테이블 보기

차단 패턴

개별 패턴 추가, 편집(인덱스별), 삭제; 전체 패턴 목록 보기

명령 규칙

기본, API 키 및 네트워크 규칙 편집; 대상 및 명령 목록이 포함된 전체 규칙 편집기

설정

모든 서버 설정 편집: SFTP 샌드박스, 속도 제한, 로깅, 연결 풀링, 회로 차단기 등

백업

구성 백업 나열, 복원, 삭제; 각 백업의 타임스탬프 및 크기

추가 기능:

  • 토큰 기반 로그인 — 세션 관리 포함(sessionStorage에 저장)

  • 구성 검증 — 변경 사항은 기록되기 전에 검증됩니다

  • API 키 해싱 — 대시보드에서 직접 평문 키를 해시

  • 반응형 디자인 — 데스크톱 및 모바일에서 작동

  • 토스트 알림 — 모든 작업에 대한 성공/오류 피드백

Swagger / ReDoc

대화형 API 문서는 FastAPI에 의해 자동 생성됩니다:

  • Swagger UI: http://localhost:9080/api/docs

  • ReDoc: http://localhost:9080/api/redoc


배포

Docker Compose

compose.yaml는 MCP 서버와 선택적으로 구성 API 및 웹 대시보드를 모두 호스팅하는 단일 mcp-ssh 서비스를 정의합니다. 구성 API는 CONFIG_API_ENABLED 환경 변수(기본값: false)를 통해 활성화됩니다.

mcp-ssh — MCP SSH 게이트웨이 + 구성 API

호스트 경로

컨테이너 경로

모드

./config

/config

rw

./logs

/logs

rw

./ssh_key

/app/ssh_key

ro

./ssh_key.pub

/app/ssh_key.pub

ro

호스트 포트 9080에 노출됩니다(컨테이너 포트 8080에 매핑). 런타임 이미지는 해시 고정 다이제스트를 사용하는 python:3.13-alpine입니다. 비루트 mcpssh 사용자가 프로세스를 실행합니다. CycloneDX SBOM은 빌드 시 sbom 단계에서 생성됩니다.

구성 API 및 웹 대시보드(선택 사항)

.env 파일 또는 환경에서 CONFIG_API_ENABLED=true를 설정하여 구성 API를 활성화합니다:

# Generate an auth token
openssl rand -hex 32
CONFIG_API_ENABLED=true
CONFIG_API_TOKEN=<your-token>

활성화하면 구성 API는 MCP 게이트웨이와 동일한 HTTP 서버의 /api에 마운트됩니다. 다음을 제공합니다:

  • REST APIhttp://localhost:9080/api/...에서 SSH 대상, 차단 패턴, 명령 규칙, 백업 및 설정에 대한 전체 CRUD

  • 웹 대시보드(GUI)http://localhost:9080/ui/에서 시각적 정책 관리를 위한 단일 페이지 애플리케이션(SSH 대상, 차단 패턴, 명령 규칙, 설정, 백업)

  • API 문서http://localhost:9080/api/docs(Swagger UI) 및 http://localhost:9080/api/redoc(ReDoc)

Makefile

Command

Description

make build

Docker 이미지 빌드 (ghcr.io/gelse/ssh-mcp:latest)

make up

docker compose up -d

make down

docker compose down

make test

단위 테스트 실행

make config-test

config-api 단위 테스트 실행

make integrationtest

테스트 이미지 빌드 및 통합 테스트 실행

make clean-test

테스트 산출물 및 컨테이너 제거

GHCR에서 이미지 가져오기

Docker 이미지는 자동으로 빌드되어 GitHub Container Registry에 게시됩니다:

docker pull ghcr.io/gelse/ssh-mcp:latest

제한 사항 및 위협 모델

ssh-mcp가 아닌 것

  • 셸이 아닙니다. 대화형 터미널 세션을 얻을 수 없습니다. 모든 실행은 일회성 명령 호출입니다.

  • 파일 관리자가 아닙니다. SFTP는 경로 검증과 샌드박스 적용이 포함된 단일 파일 업로드/다운로드로 제한됩니다. 디렉터리 목록 조회나 재귀 작업은 없습니다.

  • 네트워크 방화벽이 아닙니다. 속도 제한은 IP별로 고정 기본값이 적용됩니다. 폭주 클라이언트를 보호할 뿐, 끈질긴 공격자까지 막지는 못합니다.

위협 모델

위협

완화 조치

명령 체이닝을 통한 명령 삽입 (cmd1 && cmd2)

명령 분할 — 각 세그먼트는 전체 인증 체인을 실행합니다

민감한 경로로의 셸 리다이렉션 (> /etc/passwd)

리다이렉션 대상 가드가 /dev/, /proc/, /sys/로의 리다이렉션을 거부합니다

SFTP의 경로 탐색

8계층 경로 검증: 널 바이트 검사, 제어 문자 제거, 점-세그먼트 정규화, 심볼릭 링크 해석, 샌드박스 루트 강제

block_patterns를 통한 ReDoS

로드 시 정적 검사 + 런타임 타임아웃 가드

API 키 무차별 대입

상수 시간 검증이 포함된 PBKDF2-HMAC-SHA256, IP별 속도 제한

로그 주입

로깅 전 모든 사용자 제어 필드에 개행 문자 정리

구성 파일의 시크릿

secrets.json 분리, MCP_SSH_SECRET_* 환경 변수, 0600 파일 권한

범위 제외

  • TLS 종료 (리버스 프록시에서 처리)

  • API 키 외의 사용자 인증 (OAuth 없음, 애플리케이션 계층에서 mTLS 없음)

  • SSH 세션 다중화 (tmux/screen 패스스루 없음)

  • 감사 로그 변조 방지 (로그는 로컬 파일입니다. 변경 불가성을 위해 자체 로그 전송을 사용하세요)


개발

프로젝트 구조

  • server.py — FastMCP 앱 팩토리 + CLI 진입점

  • lib/ — 단일 책임 모듈 30개 (인증, 구성, SSH 클라이언트, 파일 전송, 로깅 등)

  • config-api/ — 구성 API + 웹 대시보드 (FastAPI, CONFIG_API_ENABLED=true일 때 /api에 마운트됨)

  • tests/ — 단위 테스트 파일 36개 + 실제 Docker 컨테이너를 사용한 통합 테스트

기술 스택

Python 3.13, FastMCP 3.4.x, paramiko 5.0, Starlette 1.4, FastAPI 0.115+, Pydantic 2.10+, httpx 0.28+, uvicorn 0.34+

테스트 실행

# Unit tests (fast inner loop)
source .venv/bin/activate
python -m pytest tests/test_<module>.py -x

# Full unit test suite
make test

# Integration tests (requires Docker)
make integrationtest

새 도구 추가

AGENTS.md의 실제 예제가 새 @mcp.tool() 핸들러를 끝에서 끝까지 추가하는 과정을 안내합니다: 상수, 타입, 재내보내기, 핸들러, 테스트, 커밋.

린트/타입 검사 도구 없음

이 프로젝트에는 ruff, mypy, pyright, flake8 구성이 없습니다. 서식은 .editorconfig 기본값(Python은 4칸 들여쓰기, 88자 줄)을 따릅니다.


로드맵

  • 시각적 정책 관리를 위한 구성 GUI


라이선스

MIT 라이선스 — 자세한 내용은 LICENSE를 참조하세요.

A
license - permissive license
Not graded
quality - not tested
A
maintenance

Maintenance

Maintainers
<1hResponse time
Release cycle
1Releases (12mo)
Commit activity

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables secure remote access operations through SSH, SFTP, rsync, VPN, and tunneling with enterprise-grade policy enforcement and audit logging. Provides AI assistants with secure, policy-driven access to remote systems while maintaining comprehensive audit trails and zero-trust security.
    1
    Apache 2.0
  • A
    license
    B
    quality
    A
    maintenance
    Provides policy-driven, auditable SSH access to server fleets for AI assistants with zero-trust security controls, command whitelisting, and comprehensive audit logging to safely manage infrastructure.
    13
    27
    Apache 2.0
  • A
    license
    A
    quality
    C
    maintenance
    Enables AI assistants to securely execute remote SSH commands, perform file transfers, and monitor system status through a standardized interface. It features robust security controls including command whitelisting, blacklisting, and credential isolation to prevent unauthorized operations.
    10
    22
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to securely execute SSH commands on remote servers with connection pooling, session isolation, and a web audit panel.
    3
    MIT

View all related MCP servers

Related MCP Connectors

  • Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.

  • Let AI operate servers without SSH. Choose actions, approve risky changes, and audit every step.

  • Agent payments, API key vaulting, and governed mandates. Agents spend within user-defined limits.

View all MCP Connectors

Latest Blog Posts

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/gelse/ssh-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server