Skip to main content
Glama
thekk1
by thekk1

ssh-mcp

LLM이 SSH를 통해 셸 명령을 실행할 수 있게 해주는 MCP 서버로, 단일 공유 서비스 계정이 아닌 각 사용자 고유의 개인 SSH 키로 인증합니다. 서버는 공유되지만 요청별 SSH 신원은 공유되지 않아야 하는 다중 사용자 채팅 플랫폼(예: LibreChat)을 위해 제작되었습니다.

도구는 하나뿐입니다: ssh_exec(host, port, username, command). 호스트 허용 목록도, 명령어 화이트리스트도 없습니다. 그 이유와 이를 배포하는 모든 사람에게 어떤 의미인지는 아래 "보안 모델"을 참조하세요.

왜 만들어졌나

이 서버를 작성하기 전에 몇 가지 기존 오픈소스 SSH MCP 서버를 검토했습니다(vignitin/multi-ssh-mcp, giuliolibrando/ssh-mcp-server, tufantunc/ssh-mcp). 그중 어느 것도 요청별 자격 증명을 지원하지 않습니다. 모두 시작 시 단일 호스트/사용자/자격 증명을 환경 변수나 구성 파일에 고정해 넣는데, 이는 단일 사용자 배포 또는 공유 서비스 계정에서만 작동합니다. 각자 자신의 SSH 키를 가진 많은 사람들이 하나의 실행 중인 MCP 서버를 공유하는 구성에는 그 어느 것도 맞지 않습니다.

그래서 이 프로젝트는 기존 도구를 감싸는 래퍼가 아니라 asyncssh를 기반으로 한 작고 특수 목적의 서버입니다. 감쌀 만한 적합한 것이 없었기 때문입니다.

Related MCP server: terminal-mcp-server

작동 방식

MCP client --(streamable-http, /mcp, per-request headers)--> ssh-mcp
                                                                  |
                                                                  | asyncssh,
                                                                  | one connection
                                                                  | per tool call
                                                                  v
                                                            arbitrary target host

자격 증명은 서버 구성이 아니라 요청별 HTTP 헤더로 전달됩니다:

  • x-ssh-private-key -- 인증에 사용할 개인 키, base64 인코딩됨 (원시 여러 줄 PEM 블록은 HTTP 헤더 값으로 유지될 수 없음)

  • x-ssh-key-passphrase -- 선택 사항, 해당 키가 암호문구로 보호되는 경우

둘 다 모든 도구 호출 시 새로 읽혀서 디코딩된 후 asyncssh에 직접 전달되고 폐기됩니다. 디스크에 기록되는 것도, 요청 간에 캐시되는 것도 없습니다. 올바른 사용자에게 올바른 헤더를 첨부하는 것은 호출하는 클라이언트의 몫입니다. 한 가지 방법은 아래 "LibreChat에서 사용하기"를 참조하세요.

호스트 키는 "무조건 수락"이 아닌 진정한 TOFU(trust-on-first-use)를 사용합니다: 특정 host:port에 대한 첫 연결은 해당 키 지문을 디스크의 JSON 파일(hostkeys.py)에 고정합니다. 이후의 모든 연결은 그 고정값과 정확히 일치해야 하며, 그렇지 않으면 host_key_mismatch로 거부됩니다. 이는 호스트와의 최초 접촉 시 중간자 공격을 막을 수는 없지만, 이후에 예고 없이 키가 변경되는 경우(교체든 실제 공격이든) 조용한 구멍이 아니라 명확하고 분명한 실패로 바꿔줍니다.

MCP 연결 자체에는 API 키나 베어러 토큰 게이트가 없습니다. 이는 특정 배포 형태를 위한 의도적인 단순성 선택입니다: 신뢰할 수 있는 내부 네트워크에서만 접근 가능한 서버로, 클라이언트가 사용자별 SSH 자격 증명을 직접 첨부하고(아래 참조) 네트워크 배치가 실제 접근 경계가 되는 형태입니다. 덜 신뢰할 수 있는 곳에 노출한다면 앞에 게이트를 두세요. 이 프로젝트에는 게이트가 포함되어 있지 않습니다.

보안 모델

ssh_exec은 허용되는 호스트, 명령어, 사용자를 필터링하지 않습니다. 호출자가 전달하는 호스트/포트/사용자 이름/명령어는 무엇이든 시도됩니다. 그게 전부입니다. 이는 실수가 아니라 의도적인 절충입니다. MCP 서버 내부에서 호스트나 명령어로 필터링하는 것은 보안 극장에 불과합니다. 유효한 키를 가진 호출자는 이 도구를 통하지 않고도 직접 SSH로 접속할 수 있기 때문입니다. 요청과 실제 셸 사이에 실제로 존재하는 두 가지 장벽은 다음과 같습니다:

  1. 이 서버에 도달하고 자격 증명 헤더를 설정할 수 있는 사람 -- 전적으로 이 코드의 통제 밖입니다. 다중 테넌트 클라이언트 뒤에 배포하는 경우, 어떤 사용자가 이 도구를 볼 수 있거나 사용할 수 있는지 제한하는 것은 그 클라이언트의 몫입니다(구체적인 방법은 "LibreChat에서 사용하기" 참조).

  2. 사용되는 키에 연결된 실제 Unix 권한. ssh_exec은 해당 키의 대상 계정이 가진 권한과 정확히 동일한 권한으로 실행됩니다. 그 이상도 그 이하도 아닙니다.

특정 배포에서 이 두 가지 중 어느 것도 실제로 적용되지 않는다면, 이 도구는 모든 호출자에게 그들의 키가 도달할 수 있는 모든 호스트에서 빈 터미널을 쥐어주는 것과 똑같이 위험합니다. 이것이 의도된 모델입니다. SSH 자체의 권한 부여를 다시 구현한 것이 아니라 SSH의 권한 부여를 그대로 사용하는 것입니다. 그러니 배포하기 전에 이것이 실제로 원하는 모델인지 확인하세요.

host/username 누락: 모델이 추측하지 않고 MCP elicitation으로 질문

hostusername은 의도적으로 도구의 required 스키마 필드에 없습니다 (command는 필수로 유지됩니다. 무엇을 실행할지 결정하는 것은 사람이 아니라 모델의 몫이기 때문입니다). host/username을 필수로 만들면 스펙을 준수하는 모델은 그 값 없이는 도구 호출을 아예 거부하고 대신 스스로 일반 텍스트 후속 질문을 즉흥적으로 만들어 내게 됩니다. 이 도구가 피하는 UX가 바로 그것입니다. 둘 중 하나가 없으면 ssh_mcp/app.pyelicit_missing_ssh_args()MCP elicitation (elicitation/create, 폼 모드)을 통해 사람에게 하나의 결합된 폼으로 직접 질문합니다. 모델이 스스로 문구를 만들어야 하는 것도 아니고, 필드별로 별도의 왕복이 필요한 것도 아닙니다. port는 같은 폼에 함께 포함되며, 스키마의 default를 통해 일반적인 기본값(22)이 미리 채워져 있습니다. 편집은 가능하지만, 유일하게 설정되지 않은 값일 때 자체적으로 중단을 유발하지는 않습니다.

elicitation을 지원하지 않는 클라이언트에서는 안전하게 저하됩니다. elicit_missing_ssh_args()는 요청을 보내기 전에 클라이언트의 선언된 기능(session.check_client_capability(...))을 확인하고, 호출 자체의 어떤 실패도 포착합니다. 어느 쪽이든 도구 호출이 오류로 끝나거나 멈추는 대신, 모델이 여전히 텍스트 질문으로 전달할 수 있는 일반 missing_host/missing_username 오류로 대체됩니다. Elicitation 지원은 클라이언트마다 다릅니다. 작성 시점 기준으로 여러 인기 MCP 클라이언트(LibreChat 포함)는 아직 구현하지 않았으므로, 이 기능은 오늘날 대부분 미래 호환을 위한 기반 작업 역할을 합니다. 지원되지 않을 때는 아무 비용이 들지 않으며, 나중에 실제 elicitation 지원을 추가하는 클라이언트에서는 여기서 변경할 것 없이 자동으로 활성화됩니다.

mcp.shared.memory의 인메모리 전송을 통해 실제 ClientSession으로 검증했습니다: elicitation_callback을 등록한 경우와 그렇지 않은 경우, 수락/거부/취소 분기, 그리고 완전한 결합 폼(host + username 누락, port 기본값 재정의) -- 세 값 모두 스펙을 읽고 추정한 것이 아니라 elicitation된 그대로 실제로 SSH 호출에 도달하는 것을 확인했습니다.

LibreChat에서 사용하기

LibreChat은 customUserVars를 통해 MCP 요청 헤더에 사용자별 값을 첨부할 수 있습니다. 각 사용자는 Settings에서 자신의 키를 한 번 입력하면, LibreChat이 그 사용자의 모든 요청에 대해 구성된 헤더에 그 값을 주입합니다. librechat.yaml:

mcpServers:
  ssh:
    type: streamable-http
    url: http://ssh-mcp:8080/mcp
    serverInstructions: true
    headers:
      X-SSH-Private-Key: '{{SSH_PRIVATE_KEY}}'
      X-SSH-Key-Passphrase: '{{SSH_KEY_PASSPHRASE}}'
    customUserVars:
      SSH_PRIVATE_KEY:
        title: "SSH Private Key (Base64)"
        description: "Your personal SSH private key, base64-encoded: `base64 -w0 ~/.ssh/id_ed25519`"
      SSH_KEY_PASSPHRASE:
        title: "SSH Key Passphrase (optional)"
        description: "Only fill in if your private key is passphrase-protected"

customUserVars 항목 모두 title description 둘 다 필요합니다. title만 있는 항목은 시작 시 LibreChat의 구성 검증을 ZodError로 실패시키는데, 혼란스럽게도 관련 없어 보이는 필드에 대해 보고됩니다(LibreChat은 전체 mcpServers 블록을 전송 유형의 하나의 유니온으로 검증하므로, 필드 하나가 누락되면 겉보기에 서로 관련 없는 여러 오류로 한꺼번에 나타납니다). librechat.yaml은 컨테이너 시작 시에만 읽히므로, 편집 후에는 LibreChat을 재시작하세요.

이 서버를 볼 수 있는 사용자 제한

이 프로젝트에는 누가 사용할 수 있는지를 제한하는 것이 없습니다. SSH_PRIVATE_KEY 헤더를 설정할 수 있는 모든 사용자가 ssh_exec을 호출할 수 있습니다. 이를 사용자 하위 집합으로 제한해야 한다면, 여기서가 아니라 LibreChat(또는 사용 중인 클라이언트)에서 처리해야 합니다. LibreChat 0.8.5+부터 관리자 패널에는 추가 mcpServers 항목을 특정 역할이나 그룹으로 범위를 한정할 수 있는 구성 재정의 시스템(Configuration Management)이 있습니다. 해당 그룹 밖의 사용자는 해석된 구성에 ssh 항목이 아예 없습니다. 숨겨진 항목조차도 아닙니다. 이 기능에 의존하기 전에 추측하지 말고 자신의 LibreChat 버전에서 확인할 가치가 있는 두 가지:

  • 작성 시점 기준으로 GA가 아닌 "미리 보기" 로 문서화되어 있습니다.

  • 역할 범위 재정의는 적용되는데 그룹 범위 재정의는 조용히 적용되지 않는 알려진 이력이 있습니다 (danny-avila/LibreChat#13172). 직접 테스트하여 실행 중인 버전에 수정 사항이 있는지 확인하세요. 사용자를 그룹에 넣거나 빼고 서버가 실제로 그 사용자에게 나타나거나 사라지는지 확인하세요.

실행

docker build -t ssh-mcp .
docker run --rm -p 8080:8080 -v ssh-mcp-hostkeys:/data ssh-mcp

/data 볼륨이 TOFU 호스트 키 고정값이 컨테이너 재생성 후에도 유지되게 만듭니다. 이 볼륨이 없으면 재배포할 때마다 이전에 본 모든 호스트 키를 잊어버리고 다음 접촉 시 다시 고정합니다(보안 허점은 아니며, 각 호스트에 한 번씩 다시 접촉할 때까지 "이후 변경 감지" 속성이 일시적으로 사라질 뿐입니다).

로컬 클론에서 빌드하는 예제 docker-compose.yml 서비스:

services:
  ssh-mcp:
    build: .
    container_name: ssh-mcp
    volumes:
      - ssh-mcp-hostkeys:/data
    restart: always

volumes:
  ssh-mcp-hostkeys:

검증

curl -s http://127.0.0.1:8080/readyz   # "ok" once the session manager is up

단위 테스트만이 아니라 수동으로 엔드투엔드 검증했습니다: 이미지를 빌드하고 실행한 다음, 자격 증명 헤더와 함께 streamable-http를 통해 실제 MCP 클라이언트를 연결했고, tools/listssh_exec이 표시되었으며, 실제 asyncssh 기반 임시 SSH 서버에 대한 tools/call이 실제 SSH 핸드셰이크를 통해 실제 명령을 실행하고 실제 stdout을 반환했습니다. 또한 HTTP 계층을 우회하여 같은 임시 서버에 직접 실행해 검증했습니다: 첫 접촉 시 TOFU 고정, 일치하는 두 번째 접촉 시 수락, 변경/불일치 호스트 키에 대한 강력한 거부, 잘못된 개인 키의 invalid_key 거부, 권한 없는 키의 connection_failed 거부, 그리고 0이 아닌 원격 종료 코드가 해당 종료 코드와 함께 ok: true로 전달되는 것(도구 실패로 처리되지 않음).

테스트

pip install -e '.[dev]'
pytest

단위 테스트(자격 증명 헤더 파싱, TOFU 고정/수락/거부 로직, 도구 스키마, 가짜 세션에 대한 elicit_missing_ssh_args의 기능 확인/필드 선택/수락/거부/취소/실패 분기) -- 실제 네트워크, 하위 프로세스, MCP 전송 없음. 실제 핸드셰이크 시나리오와 실제 ClientSession elicitation 왕복(위 참조)은 자동화된 스위트의 일부가 아니라 수동으로 실행되었습니다.

F
license - not found
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    Enables secure SSH connections to remote servers for executing shell commands and managing active sessions. It supports authentication via passwords or private keys and provides optional host-based access control.
    4
    207
    MIT

View all related MCP servers

Related MCP Connectors

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

  • Issue, rotate and revoke scoped API-key passes for 25+ providers — the agent never sees a real key

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

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

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