네트워크 장비를 위한 첫 번째 MCP 서버 구축 - 파트 2
요약
MCP(Model Context Protocol)를 활용하여 네트워크 장비를 제어하고 관리하는 AI 에이전트용 서버 구축 방법을 다룹니다. 장치 인벤토리 관리, SSH 연결, 읽기 전용 명령 실행 및 설정 백업 등 가장 기초적이고 안전한 MCP 서버의 핵심 기능을 구현하는 과정을 설명합니다.
핵심 포인트
- MCP 서버를 통한 네트워크 장비의 AI 에이전트 접근 방식 제안
- 안전성을 위해 쓰기 작업이 제외된 읽기 전용 기초 서버 구축
- 장치 인벤토리 관리 및 Netmiko를 활용한 SSH 연결 구현
- 화이트리스트 기반의 제한된 명령 실행을 통한 보안 확보
이 글은 MCP 및 에이전트 기술 (Agent Skills)을 활용한 AI 지원 네트워크 운영에 관한 7부작 시리즈 중 파트 2입니다. 파트 1에서는 왜 네트워크 장비에 MCP 서버가 필요한지 설명했습니다. 이 글에서는 첫 번째 유용한 버전을 구축합니다.
목차
- 가장 작은 유용한 MCP 서버
- 도구(Tools) 이전의 인벤토리
- 드라이버 추상화 (Driver Abstraction)
- Netmiko 백엔드
- 첫 번째 도구들
- 가공되지 않은 덤프가 아닌 제한된 데이터
- 제품군별 특정 예시
- 이 서버가 아직 수행하지 않는 것
- 첫 번째 버전은 씨앗입니다
- 시리즈 로드맵
파트 1에서 우리는 개인용 AI에서 팀용 AI로의 전환에는 회사의 자산인 공유 인프라 MCP 서버와 기술 (Skills)이 필요하다고 주장했습니다. 우리는 팀 AI, 토큰 효율성 (token efficiency), 진화, 필터링, 레고 블록, 그리고 미래 대비라는 여섯 가지 원칙을 다루었습니다.
이제 기초를 구축합니다. 아직 전체 툴킷을 만드는 것은 아닙니다. 가장 작은 유용한 MCP 서버: 장치 인벤토리 (device inventory), 자격 증명 분리, 드라이버 추상화, 안전한 읽기 도구, 상태 확인 (health check), 그리고 설정 백업입니다. 쓰기 작업, 지식 카탈로그, 화려한 검색 기능은 없습니다. 다른 모든 것들이 그 위에 구축될 읽기 전용 기초일 뿐입니다.
이 글을 마칠 때쯤이면, 여러분은 장치 인벤토리를 관리하고, SSH를 통해 장치에 연결하며, 화이트리스트에 등록된 읽기 명령을 실행하고, 장치 상태를 확인하며, 모든 AI 클라이언트가 호출할 수 있는 타입화되고 제한된 MCP 도구를 통해 설정을 백업할 수 있는 서버를 갖게 될 것입니다.
가장 작은 유용한 MCP 서버
코드를 작성하기 전에, 첫 번째 마일스톤이 무엇인지, 그리고 무엇이 아닌지를 정의하십시오.
수행하는 작업:
- 장치 인벤토리 관리 (추가, 목록, 업데이트, 제거)
- SSH를 통한 장치 연결 (SSH를 사용할 수 없는 경우 telnet 사용)
- 화이트리스트에 등록된 읽기 명령 실행 (
show명령,info덤프) - 장치 상태 확인 (식별 정보, 활성 알람)
- 로컬 아카이브로 설정 백업
아직 수행하지 않는 작업:
- 설정 쓰기 없음 (그것은 안전 모델을 다루는 제3편입니다)
- 지식 카탈로그 없음 (그것은 CLI 수집, 매뉴얼, MIB를 다루는 제5편입니다)
- SNMP 작업 없음 (이 또한 제5편입니다)
- 멀티 클라이언트 배포 없음 (그것은 제6편입니다)
- 모든 계층을 아우르는 퓨전 프롬프트(fusion prompts) 없음 (그것은 제7편입니다)
이것은 씨앗입니다. 이것은 사용을 통해 성장하며, 엔지니어가 현재의 도구로 답할 수 없는 질문을 던질 때마다 누락된 각 기능이 발견됩니다.
도구 도입 전의 인벤토리 (Inventory)
첫 번째 도구는 CLI 명령어가 아니라 장치 인벤토리 (device inventory)입니다. AI가 장치에 접근하기 전에, 해당 장치가 존재한다는 사실을 먼저 알아야 합니다.
6가지 사실 기반의 입력 게이트 (Six-Fact Intake Gate)
장치를 추가하려면 이름(name), 호스트(host), 제품군(family), 그룹(group), 사용자(user), 비밀번호(password)라는 6가지 사실이 필요합니다. 필드를 하나라도 누락하면, 에이전트는 누락된 항목을 하나씩 묻는 것이 아니라 한 번의 질문으로 모두 요청합니다:
"rad agent, add my device: name lab-etx2, host 172.17.163.205, family etx2, group lab, user su, password 1234"
입력 게이트는 무언가가 기록되기 전에 이 6가지 요소를 모두 강제합니다. 왜 6가지일까요?
- 이름 (Name): 팀에서 사용하는 인간용 식별자 ("lab-etx2", "marks-mp4", "sf-163-187")
- 호스트 (Host): IP 주소 또는 호스트 이름 (hostname)
- 제품군 (Family): CLI 방언 (CLI dialect)을 선택하는 장치 제품군 식별자 ("etx2", "secflow", "mp4100", "minid")
- 그룹 (Group): 필터링을 위한 조직 태그 ("lab", "production", "site-A")
- 사용자 (User): SSH 사용자 이름 (username)
- 비밀번호 (Password): SSH 비밀번호
자격 증명 분리 (Credential Separation)
이것은 첫 번째 안전 결정이며, 타협할 수 없는 원칙입니다: 자격 증명 (credentials)은 절대로 인벤토리 파일에 저장하지 않습니다. 인벤토리는 이름, 호스트, 제품군, 그룹을 저장합니다. 자격 증명은 .gitignore 처리된 .env 파일이나 운영체제 키체인 (OS keychain)으로 보냅니다. 인벤토리 파일은 Git에 커밋하거나, 팀과 공유하거나, 도구 응답에 표시해도 안전합니다. 자격 증명은 도구 인자 (arguments), 응답, 또는 감사 로그 (audit logs)에 절대 노출되지 않습니다.
inventory.yaml ← name, host, family, group (커밋 가능)
server/.env ← user, password (gitignored, chmod 600)
장치 목록 표시 (Listing Devices)
장치가 등록되면, 인증 정보 없이도 목록을 조회할 수 있습니다:
"noam, 장치 목록을 보여줘"
이름, 호스트(host), 제품군(family), 그룹(groups)을 인증 정보 없이 반환합니다. 필터링도 가능합니다: "mp1 제품군만 목록에 표시해"라고 하면 제품군별로 좁혀서 보여주고, "lab 그룹을 목록에 표시해"라고 하면 그룹별로 좁혀서 보여줍니다.
장치 업데이트 및 삭제 (Updating and Removing Devices)
"abayev, marks-mp4의 호스트를 172.17.161.95로 업데이트해"
부분 업데이트(Partial update): 지정된 필드만 변경됩니다. family를 변경하는 것은 의심스러운 동작(보통 잘못된 등록인 경우가 많음)으로 간주되어 재확인을 요청합니다.
"rad agent, 목록에서 lab-etx2를 삭제해"
명시적인 확인이 필요합니다. 장치를 삭제하면 인벤토리(inventory) 항목만 잊을 뿐, 실제 장치나 백업, 또는 감사 이력(audit history)에는 전혀 영향을 주지 않습니다. 해당 정보들은 그대로 유지됩니다.
드라이버 추상화 (The Driver Abstraction)
이 부분이 서버의 기술적 핵심입니다. 핵심 통찰은 다음과 같습니다: 전송 방식(transport)과 CLI 방언(dialect)은 독립적으로 변한다는 것입니다. SSH와 telnet은 전송 방식(transports)입니다. CLI 방언(프롬프트 형식, 컨텍스트 탐색, 커밋 동작, 포트 명명 규칙 등)은 장치 제품군(device family)마다 다릅니다. 이 두 차원은 반드시 분리되어야 합니다.
백엔드 × 드라이버 (Backend × Driver)
도구 (제품 중립적) run_show, health_check, get_config, backup_config
│
백엔드 (전송 방식) ssh / telnet (Netmiko)
...
도구는 제품에 종속되지 않는 동사(verb) 형태를 유지합니다. 장치의 family 필드가 드라이버(driver)를 선택하고, 인벤토리의 transport 필드가 백엔드(backend)를 선택합니다. SSH 세션이 SecFlow를 구동할 수 있고, telnet 세션이 ETX-2를 구동할 수 있습니다. 도구는 자신이 드라이버를 호출한다는 사실을 신경 쓰지 않으며, 드라이버가 백엔드를 호출할 뿐입니다.
이것이 중요한 이유
대부분의 네트워크 자동화 코드는 전송 방식과 방언을 결합(couple)합니다. 즉, 특정 장치 유형에 SSH로 접속하여 특정 명령어를 실행하는 하나의 스크립트 형태입니다. 이는 하나의 제품군에는 작동합니다. 하지만 두 번째 제품군을 추가할 때, 스크립트를 복사하여 방언 관련 부분만 수정하게 됩니다. 세 번째 제품군에 이르면, 서로 동기화되지 않고 파편화된 세 개의 복사본을 갖게 됩니다.
드라이버 추상화는 이를 방지합니다. 공유되는 방언은 하나의 기본 클래스(base class)에 존재하며, 제품군별 차이점은 변경된 부분만 오버라이드(override)하는 하위 클래스(subclasses)에 존재합니다.
class RadCLIDriver:
"""공유 컨텍스트-CLI 방언 프롬프트 형식, 컨텍스트 탐색,
show-command 화이트리스트, 설정 내보내기."""
...
제품군별 차이점
차이점은 실재하며 사용을 통해 발견됩니다:
| 측면 | SecFlow (SF-1p) | ETX-2 | Megaplex-4100 | MiNID |
|---|---|---|---|---|
| SSH 동작 | 표준 (Standard) | 표준 (Standard) | 표준 (Standard) | 취약한 프로파일(Fragile patient profile) 필요 |
| ... |
이러한 차이점을 고려하지 않는 도구는 한 제품군에서는 작동하지만, 다른 제품군에서는 소리 없이 실패할 것입니다. 드라이버 추상화(driver abstraction)는 이러한 차이점을 코드베이스 전반에 흩어져 있는 if/else 분기 속에 숨기는 대신, 명시적이고 테스트 가능하게 만듭니다.
Netmiko 백엔드
Netmiko는 SSH 세션 관리(connection), 인증(authentication), 명령 실행(command execution), 출력 읽기(output reading)를 처리합니다. RAD 장비를 위한 rad_etx 디바이스 타입을 지원하며, cisco_ios, junos, arista_eos, huawei_vrp 및 수십 가지 이상의 타입을 지원합니다. 이미 Netmiko 또는 NAPALM을 사용 중이라면, MCP 서버는 기존의 자동화 기본 요소(automation primitives)를 타입이 지정된(typed), AI 호출 가능한 도구(AI-callable tools)로 감싸줍니다.
하지만 Netmiko는 전송 계층(transport)이지 정책(policy)이 아닙니다. MCP 서버가 정책을 소유합니다: 어떤 명령이 화이트리스트(whitelisted)에 포함될지, 출력이 어떻게 제한될지, 무엇이 확인(confirmation)을 필요로 하는지, 무엇이 로그에 기록될지를 결정합니다. Netmiko는 장치에 접속하게 해주지만, 서버는 접속한 후 무엇을 하는 것이 안전할지를 결정합니다.
장치당 하나의 지속적인 세션 (One Persistent Session Per Device)
SSH 연결에는 5~7초가 소요되며, 많은 네트워크 장비는 기존 세션이 종료되는 동안 새로운 세션 연결을 거부합니다. 따라서 세션은 캐싱됩니다: 장치당 하나의 지속적인 CLI 세션을 유지하며, 각 호출 전에 exit all로 다시 초기화(re-grounded)하고, 60초 동안 유휴(idle) 상태인 경우 생존 여부를 확인(liveness-probed)하며, 세션이 끊기면 투명하게(transparently) 교체합니다.
이는 성능을 위한 결정이기도 하지만, 필터링을 위한 결정이기도 합니다. 에이전트(agent)는 도구 호출마다 새로운 SSH 세션을 열지 않습니다. 기존 세션을 재사용하므로 장치의 세션 테이블이 범람(flooded)되지 않으며, 에이전트는 재연결 오버헤드 없이 단일 대화 내에서 여러 번의 읽기 작업을 체이닝(chain)할 수 있습니다.
프롬프트 기반 읽기, 절대 조용한 타이머를 사용하지 말 것 (Prompt-Anchored Reads, Never Quiet Timers)
모든 읽기 작업은 무음 간격 타임아웃 (quiet-gap timeout)이 아니라, 장치의 프롬프트 (prompt)가 다시 나타나는 즉시 종료됩니다. 이는 생각보다 훨씬 중요합니다. SecFlow-1p는 info 덤프 (dump) 도중 결정론적으로 3초 이상 일시 중지됩니다. 짧은 무음 임계값 (quiet threshold)을 사용하면 출력이 조용히 잘려 나가게 되는데, 이로 인해 장치가 전송을 마치기 전에 읽기가 종료되어 CLI 수집기 (harvester)에서 router 1 서브트리 (subtree) 전체가 누락된 적이 있습니다.
프롬프트 기반 읽기 (Prompt-anchored reads)는 결정론적 (deterministic)입니다. 무음 타이머 (quiet-timer) 기반 읽기는 확률적 (probabilistic)입니다. 네트워크 운영에서는 항상 결정론적인 방식이 승리합니다.
첫 번째 도구들 (The First Tools)
각 도구는 동일한 패턴을 따릅니다: 사용자가 일상 언어로 요청하면, 에이전트 (agent)가 도구를 호출하고, 도구는 장치에 대해 실행하며 (장치에 접속하는 읽기 작업의 경우 확인 절차를 거침), 결과는 출처 (provenance)와 함께 반환됩니다.
add_device 새로운 장치 등록
"rad agent, add my device: name lab-etx2, host 172.17.163.205, family etx2, group lab, user su, password 1234"
6가지 사실 기반의 입력 게이트 (intake gate)입니다. 무언가가 기록되기 전에 필요한 모든 사실을 요구합니다. 자격 증명 (credentials)은 .env로 가고, 인벤토리 (inventory) 정보는 inventory.yaml로 저장됩니다. 필드를 누락하면 에이전트가 누락된 모든 항목을 한 번의 질문으로 요청합니다.
가드레일 (Guardrail): 자격 증명은 인벤토리 파일에 절대 저장되지 않습니다. 인벤토리는 안전하게 공유할 수 있습니다.
list_devices 인벤토리 표시
"noam, show the list of devices"
모든 장치에 대해 이름 (name), 호스트 (host), 제품군 (family), 그룹 (groups)을 반환합니다. 자격 증명이 포함되지 않은 출력입니다. 제품군 또는 그룹별로 필터링할 수 있습니다.
가드레일 (Guardrail): 응답에 자격 증명을 포함하지 않습니다. 절대 금지입니다.
test_connectivity SSH 도달 가능성 확인
"rad agent, can you reach lab-etx2?"
SSH (또는 장치의 전송 필드에 따라 telnet) 도달 가능성 및 인증 (auth) 확인을 수행합니다. 명령어를 실행하지는 않으며, 세션이 수립될 수 있는지만 확인합니다.
가드레일 (Guardrail): 읽기 전용 (Read-only)입니다. 장치의 상태를 변경하지 않습니다.
run_show 화이트리스트 기반 읽기 명령어
"abayev, show the active alarms on sf-163-187"
"noam, show the ports status on ehud1p"
에이전트는 적절한 컨텍스트(context)(configure reporting → show active-alarms, 또는 configure port → show summary)를 탐색하고 명령어를 실행합니다. 출력 결과는 다음과 같이 해석됩니다: 알람 심각도(severity) (주요(major) 또는 심각(critical) 알람은 정책에 따라 설정 작업을 차단함), 포트 상태(port status) (up/down/errors).
포트 명명 규칙(Port naming)은 제품군(family)마다 다르며, 에이전트는 대상 제품군의 관례를 사용합니다. SecFlow는 ethernet 3을 사용하고, ETX-1p는 ethernet lan1을, ETX-2는 ethernet 0/2를 사용합니다. 드라이버(driver)가 이를 알고 있으며, 도구(tool)에 하드코딩되어 있지 않습니다.
가드레일(Guardrail): 화이트리스트(whitelisted)에 등록된 읽기 접두사(read prefixes)만 허용됩니다. 가공되지 않은 쉘 문자열(raw shell strings)은 허용되지 않습니다. 컨텍스트는 알려진 컨텍스트와 대조하여 검증됩니다. 엄격한 토큰 문자 집합(token charset)을 통해 커맨드 인젝션(command injection)을 방지합니다.
run_show_in_context 범위 제한 읽기 (Scoped Reads)
일부 장치 제품군은 show 명령어를 특정 컨텍스트로 제한합니다. 루트 프롬프트(root prompt)에서 show active-alarms를 실행할 수 없으며, 먼저 configure reporting 상태여야 합니다. run_show_in_context는 컨텍스트 진입, 명령어 실행, 루트로의 복귀와 같은 탐색 과정을 처리합니다.
가드레일(Guardrail): 컨텍스트 경로(Context path)는 드라이버의 알려진 컨텍스트 목록(known-context list)과 대조하여 검증됩니다. 알 수 없는 컨텍스트는 추측하지 않고 거부됩니다.
cli_help ? 도움말 트리 전달
"rad agent, ETX-2의 configure protection erp 하위에는 어떤 명령어들이 있나요?"
cli_help는 장치의 CLI에 <prefix>?를 입력하고 도움말 출력을 캡처한 다음, Ctrl-U로 대기 중인 라인을 지웁니다. 이는 어떠한 것도 실행하지 않으며, 장치 자체의 도움말 시스템을 읽는 작업일 뿐입니다.
가드레일(Guardrail): 접두사(prefix)에 포함된 줄바꿈(newlines) 및 제어 문자(control characters)는 거부됩니다. 도구는 ?를 입력하고 라인을 지울 뿐, 아무것도 실행하지 않습니다. 이것이 CLI 수집기(CLI harvester, 제5조)가 명령어 트리(command tree)를 발견하는 방식입니다.
get_config 전체 설정 내보내기 (Full Config Export)
"noam, minid-1의 설정을 백업하고 이전 백업과 차이점(diff)을 비교해줘"
전체 info를 로컬 아카이브(server/backups/)로 내보내며, 이전 스냅샷(snapshot)과 차이점(diff)을 비교합니다. 백업은 타임스탬프(timestamp)가 찍히고 해시(hash) 처리되어 저장됩니다. 차이점 비교는 컨텍스트를 포함하여 라인 단위(line-by-line)로 수행됩니다.
가드레일(Guardrail): 읽기 전용(Read-only). 설정 변경 없음. 백업 아카이브는 추가 전용(append-only)이며, 이전 스냅샷은 절대 덮어쓰지 않습니다.
health_check 드라이버 정의 건강 검사
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기