
서론
Claude AI와 MCP를 활용한 자연어 기반 SSH 서버 관리 도구
나는 코딩을 할 때 AI를 적극적으로 활용하고 있다.
특히 퇴근후에는 집에서 MCP나 Claude Code 등을 이용해서 바이브코딩 가깝게 서비스를 만들어내기도 한다.
이걸 서버작업에도 적용하고 싶었다. 그래서 Claude AI를 활용한 자연어 기반 SSH 서버 관리 도구 SpeakSH를 개발했다.
*** 거의 대부분 Claude 를 이용해서 개발했다.
이 글에서는 MCP를 활용하여 Claude와 SSH를 연동한 과정과 기술적 구현 내용을 공유한다.
다만 최근 PC에 대한 제어권을 가지고 모든 것에 대한 작업을 진행하는 에이전트가 대두되고 있는 것 같은데 곧 MCP를 활용하는 서비스는 퇴물이 되지 않을까싶긴하다.
내 기억상 작년에 나온 따끈따끈한 기술이었는데..
프로젝트 개요
SpeakSH는 Claude AI를 통해 SSH 서버를 자연어로 관리할 수 있는 도구다.
사용자는 "Dev 서버에 연결해서 LAMP 스택 설치해줘"와 같이 자연어로 요청하면, Claude가 이를 해석하여 필요한 SSH 명령어를 순차적으로 실행한다.
핵심 기능
- GUI 기반 실시간 콘솔 모니터링
- SSH 서버 연결 및 명령 실행
- MCP를 통한 Claude AI 연동
- 자연어 기반 서버 관리
기술 스택
- Python 3.x
- CustomTkinter - GUI 프레임워크
- Paramiko - SSH 연결 라이브러리
- MCP - Model Context Protocol
- asyncio - 비동기 처리
아키텍처 설계
SpeakSH의 전체 아키텍처는 다음과 같이 구성했다.
사용자 (Claude Desktop/claude.ai)
↓
Claude AI (자연어 해석)
↓
MCP 서버 (도구 제공)
↓
SSH Manager (연결 관리)
↓
Linux 서버 (명령 실행)
↓
GUI 콘솔 (실시간 출력)
각 계층은 명확한 책임을 가지며 독립적으로 동작하도록 설계했다.
핵심 구현 내용
1. MCP 서버 구현
Claude AI와의 통신을 위해 MCP 서버를 구현했다. MCP는 Anthropic에서 제공하는 프로토콜로, AI 모델이 외부 도구를 사용할 수 있게 해준다.
class SpeakSHMCPServer:
def __init__(self, ssh_manager: SSHManager):
self.ssh_manager = ssh_manager
self.server = Server("speaksh")
self._register_tools()
MCP 서버는 다음 5가지 도구를 제공한다.
list_servers - 등록된 SSH 서버 목록 조회
@self.server.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="list_servers",
description="등록된 SSH 서버 목록을 조회합니다.",
inputSchema={
"type": "object",
"properties": {},
"required": []
}
),
# ... 나머지 도구들
]
select_server - 서버 선택 및 연결
async def _select_server(self, name: str, host: str) -> list[TextContent]:
# 최신 서버 목록 로드
self.ssh_manager.load_servers()
# 비동기로 연결 시도
loop = asyncio.get_event_loop()
success, message = await loop.run_in_executor(
None,
self.ssh_manager.connect_server,
name,
host
)
return [TextContent(
type="text",
text=json.dumps(result, indent=2, ensure_ascii=False)
)]
execute_ssh_command - SSH 명령 실행
async def _execute_ssh_command(self, command: str) -> list[TextContent]:
current_server = self.ssh_manager.get_current_server_info()
if not current_server:
return [TextContent(
type="text",
text="선택된 서버가 없습니다. 먼저 select_server를 사용하여 서버를 선택하세요."
)]
# 명령 실행 로그
self._log(f"[{current_server['host']}] $ {command}", "command")
# 비동기로 명령 실행
loop = asyncio.get_event_loop()
exit_code, stdout, stderr = await loop.run_in_executor(
None,
self.ssh_manager.execute_command,
command,
None,
None
)
get_current_server - 현재 선택된 서버 정보 조회
read_console_log - GUI 콘솔 로그 읽기 (최대 500줄)
2. SSH 연결 관리
Paramiko 라이브러리를 사용하여 SSH 연결을 관리했다. 여러 서버에 동시에 연결할 수 있도록 설계했으며, 연결 상태를 파일로 저장하여 재시작 시에도 복원할 수 있다.
class SSHManager:
def __init__(self, config_file: str = "servers.json"):
# 애플리케이션 데이터 디렉토리
app_data_dir = self.get_app_data_dir()
# 설정 파일 경로
self.config_file = os.path.join(app_data_dir, config_file)
# 현재 연결 상태 파일
self.connection_state_file = os.path.join(app_data_dir, "current_connection.json")
# 로그 파일 경로
self.log_file = os.path.join(app_data_dir, "mcp_activity.log")
self.servers: Dict[str, dict] = {}
self.connections: Dict[str, SSHConnection] = {}
self.current_server: Optional[str] = None
서버 정보는 JSON 형식으로 저장된다.
{
"Dev Server_192.168.1.100": {
"name": "Dev Server",
"host": "192.168.1.100",
"port": 22,
"username": "root",
"password": "encrypted_password",
"key_filename": null
}
}
보안을 위해 servers.json 파일은 .gitignore에 포함시켰다.
3. 비동기 처리 전략
MCP 서버는 비동기로 동작하지만, SSH 명령 실행은 동기적으로 처리해야 했다. 이를 위해 run_in_executor를 활용하여 동기 함수를 비동기 컨텍스트에서 실행했다.
# 비동기 컨텍스트에서 동기 함수 실행
loop = asyncio.get_event_loop()
exit_code, stdout, stderr = await loop.run_in_executor(
None,
self.ssh_manager.execute_command,
command,
None,
None
)
이 방식으로 MCP 서버의 비동기 특성을 유지하면서도 SSH 연결의 안정성을 확보했다.
4. GUI 구현
CustomTkinter를 사용하여 모던한 다크모드 GUI를 구현했다. GUI는 크게 세 부분으로 구성된다.
서버 사이드바 - 등록된 서버 목록 표시 및 연결 관리
class ServerSidebar(ctk.CTkFrame):
def __init__(self, parent, ssh_manager: SSHManager):
super().__init__(parent)
self.ssh_manager = ssh_manager
self.server_buttons = {}
콘솔 패널 - 실시간 명령 출력 표시
class ConsolePanel(ctk.CTkFrame):
def __init__(self, parent):
super().__init__(parent)
# 터미널 스타일 텍스트 위젯
self.console_text = tk.Text(
self,
bg="#1e1e1e",
fg="#d4d4d4",
font=("Consolas", 10),
wrap="word"
)
입력 패널 - 직접 명령어 입력 지원
5. 로그 파일 모니터링
MCP 서버와 GUI 간의 실시간 통신을 위해 로그 파일을 중간 매체로 활용했다. MCP 서버가 로그 파일에 기록하면 GUI는 이를 주기적으로 읽어 화면에 표시한다.
def start_log_monitoring(self):
self.log_monitor_running = True
def monitor_log():
# 로그 파일을 모니터링하고 새로운 줄을 GUI에 표시
last_position = 0
while self.log_monitor_running:
if os.path.exists(self.log_file):
try:
with open(self.log_file, 'r', encoding='utf-8') as f:
f.seek(last_position)
new_lines = f.readlines()
last_position = f.tell()
for line in new_lines:
self.console_panel.add_log(line.rstrip())
except Exception as e:
pass
time.sleep(0.5)
threading.Thread(target=monitor_log, daemon=True).start()
이 방식은 프로세스 간 통신 없이도 실시간 동기화를 구현할 수 있어 구조를 단순하게 유지할 수 있었다.
Claude Desktop 연동
Claude Desktop과의 연동은 설정 파일을 통해 이루어진다.
{
"mcpServers": {
"speaksh": {
"command": "python",
"args": [
"D:/_BANG/05_Study/SpeakSH/run_mcp_server.py"
]
}
}
}
사용자는 Claude Desktop에서 자연어로 요청하면, Claude가 적절한 MCP 도구를 선택하여 실행한다.
사용 예시
사용자: "등록된 서버 목록을 보여줘"
Claude: [list_servers 호출]
"등록된 서버는 3개입니다.
1. Dev Server (192.168.1.100) - 연결 안됨
2. Production (192.168.1.200) - 연결됨
3. Staging (192.168.1.150) - 연결 안됨"
사용자: "Dev Server에 연결해서 nginx 설치해줘"
Claude: [select_server 호출]
"Dev Server에 연결했습니다."
[execute_ssh_command: "apt update"]
[execute_ssh_command: "apt install -y nginx"]
[execute_ssh_command: "systemctl start nginx"]
"nginx 설치가 완료되었습니다."
개발 과정에서의 고민
1. 프로세스 간 통신 방식
처음에는 MCP 서버와 GUI를 하나의 프로세스로 실행하려 했다. 하지만 MCP 서버는 stdio를 통해 Claude와 통신하기 때문에, GUI와 함께 실행하면 표준 입출력이 충돌하는 문제가 있었다.
이를 해결하기 위해 MCP 서버와 GUI를 별도 프로세스로 분리하고, 로그 파일을 중간 매체로 사용하는 방식을 채택했다. 이 방식은 구현이 간단하고 안정적이었다.
2. 연결 상태 유지
SSH 연결은 일정 시간 동안 활동이 없으면 끊어질 수 있다. 이를 방지하기 위해 연결 상태를 파일로 저장하고, 재시작 시 자동으로 재연결하는 기능을 구현했다.
def save_connection_state(self):
state = {
"current_server": self.current_server,
"connected_servers": list(self.connections.keys())
}
with open(self.connection_state_file, 'w', encoding='utf-8') as f:
json.dump(state, f, indent=2, ensure_ascii=False)
def load_connection_state(self):
# 저장된 연결 상태 로드 및 자동 재연결
pass
3. 크로스 플랫폼 지원
Windows, macOS, Linux에서 모두 동작하도록 설정 파일 경로를 플랫폼별로 다르게 지정했다.
향후 개선 계획
파일 전송 기능
SFTP를 활용하여 파일 업로드/다운로드 기능을 추가할 예정이다.
결론
SpeakSH를 개발하면서 MCP를 활용한 AI 도구 통합의 가능성을 확인했다.
정확히는 기존 시스템이 AI를 만나면서 더 많은 가능성이 확장됨을 피부로 느끼게 되는 경험이었다.
자연어로 서버를 관리한다는 아이디어가 실제로 구현 가능하며, 실용적으로 활용할 수 있음을 경험했다.
특히 MCP의 도구 기반 접근 방식은 기존 시스템과의 통합을 매우 간단하게 만들어준다. SSH뿐만 아니라 데이터베이스 관리, 클라우드 인프라 제어 등 다양한 영역에 동일한 패턴을 적용할 수 있을 것이다.
앞으로도 AI를 기존 서비스, 시스템에 붙여서 활용하는 방안에 대해 계속 고민할 것이다.
참고 자료
- 개발 철학: https://egg-stone.tistory.com/39
- MCP Documentation: https://modelcontextprotocol.io
- Paramiko Documentation: https://www.paramiko.org
- CustomTkinter Documentation: https://customtkinter.tomschimansky.com
'AI' 카테고리의 다른 글
| Docker 환경에서 Ollama + OpenClaw(Clawdbot) 설치하기 (0) | 2026.02.23 |
|---|---|
| sQuiry: AI 서버와 통신하는 프론트엔드 개발 (0) | 2026.02.10 |
| sQuiry: 오픈소스 LLM과 RAG 기반 자연어-SQL 변환 서비스 (0) | 2026.02.09 |
| SpeakSH: Claude AI로 서버 관리 (시나리오) (0) | 2026.02.07 |
| SpeakSH: Claude Desktop 연동 (0) | 2026.02.06 |