에이전틱스
도메인 8. 툴과 MCP

8.2MCP 서버 개발

예상 학습 시간 25

MCP 서버를 만들면 여러 클라이언트가 같은 도구를 공유할 수 있습니다. 이 수업에서는 서버가 제공하는 것들과 통신 방식, 그리고 stdio에서 자주 겪는 함정을 다룹니다.

서버가 제공하는 세 가지

MCP 서버는 세 종류를 노출할 수 있습니다. 이름이 비슷해 시험에서 섞어 냅니다.

  • — 모델이 호출해 무언가를 하는 것. 조회, 생성, 실행.
  • 리소스 — 읽을 수 있는 데이터. 파일이나 레코드처럼 식별자로 지목해 가져옵니다.
  • 프롬프트 — 미리 만들어 둔 프롬프트 템플릿. 사용자가 고르면 채워집니다.

구분 기준은 누가 주도하는가입니다. 툴은 모델이 판단해 부르고, 리소스는 클라이언트가 가져와 컨텍스트에 넣고, 프롬프트는 사람이 고릅니다.

셋을 다 만들 필요는 없습니다. 대부분의 서버는 툴만 제공해도 충분합니다. 리소스는 참조 자료가 많을 때, 프롬프트는 정형화된 작업이 반복될 때 값을 합니다.

통신 방식

서버와 클라이언트가 붙는 방식은 크게 둘입니다.

stdio — 클라이언트가 서버를 자식 프로세스로 띄우고 표준 입출력으로 주고받습니다. 로컬에서 쓰기 간편하고 설정이 적습니다.

HTTP 계열 — 원격 서버에 네트워크로 붙습니다. 여러 사용자가 공유하거나 다른 기기에서 쓸 때 필요합니다. 대신 인증과 접근 제어를 갖춰야 합니다.

stdio에서 가장 자주 겪는 사고가 하나 있습니다. 서버 코드에서 print나 표준 출력으로 로그를 쓰면 프로토콜 메시지와 섞여 통신이 깨집니다. 표준 출력은 프로토콜 전용이므로, 로그는 표준 오류로 보내야 합니다.

import sys

# 나쁨 — 프로토콜을 오염시킨다
print("서버 시작됨")

# 나음 — 표준 오류로
print("서버 시작됨", file=sys.stderr)

이 증상은 "서버가 응답하지 않는다"거나 "파싱 오류"로 나타나서 원인을 찾기 어렵습니다. 로그를 넣은 직후부터 깨졌다면 여기를 의심해야 합니다.

서버 설계

도구를 만들 때의 원칙이 그대로 적용됩니다. 다만 서버는 여러 클라이언트가 공유하므로 몇 가지가 더 붙습니다.

  • 도구를 너무 많이 노출하지 않습니다. 붙이는 쪽의 컨텍스트를 먹습니다. 관련 있는 것끼리 서버를 나누는 편이 낫습니다.
  • 설명은 클라이언트를 모른다는 전제로 씁니다. 우리 프로젝트 맥락을 아는 사람만 이해하는 표현은 피합니다.
  • 버전과 호환을 신경 씁니다. 도구 이름이나 인자를 바꾸면 그 서버를 쓰던 모든 곳이 영향을 받습니다.

권한도 서버 쪽에서 정해야 합니다. 클라이언트가 알아서 제한해 주리라 기대할 수 없습니다.

@server.tool()
def read_record(record_id: str) -> dict:
    rec = db.get(record_id)
    if not rec or not caller_can_read(rec):
        raise PermissionError("해당 레코드에 접근할 수 없습니다")
    return to_safe_dict(rec)      # 필요한 필드만

반환값도 다듬습니다. 서버가 전체 레코드를 그대로 돌려주면 클라이언트 쪽 컨텍스트가 그만큼 소모됩니다.

연결하고 확인하기

서버를 만들었으면 실제로 붙여 확인합니다. 확인할 것은 셋입니다.

  1. 도구가 목록에 보이는가. 안 보이면 연결이나 선언 문제입니다.
  2. 호출이 되는가. 인자가 기대한 형태로 도착하는지 봅니다.
  3. 오류가 제대로 전달되는가. 실패했을 때 클라이언트가 그 사실을 아는지 확인합니다.

프로젝트 단위로 서버 설정을 공유하면 팀이 같은 환경을 씁니다. 개인 설정에만 두면 다른 사람 환경에서는 도구가 없어 동작이 달라집니다.

서버가 죽었을 때의 동작도 확인해 둡니다. 클라이언트가 그대로 멈추는지, 도구 없이 진행하는지에 따라 사용자 경험이 크게 달라집니다. 죽은 서버를 계속 재시작하려다 루프에 빠지는 구성도 실제로 나옵니다.

시험 함정

  • MCP의 툴·리소스·프롬프트를 같은 것으로 보는 선택지 — 주도하는 주체가 각각 다릅니다.
  • stdio 서버에서 표준 출력으로 로그를 쓰는 구현 — 프로토콜 메시지와 섞여 통신이 깨집니다.
  • 도구를 많이 노출할수록 유용하다는 서술 — 붙이는 쪽의 컨텍스트를 소모합니다.
  • 권한 검사를 클라이언트가 해 줄 것이라는 가정 — 서버에서 강제해야 합니다.
  • 서버가 전체 레코드를 그대로 반환하는 설계 — 클라이언트 컨텍스트를 낭비합니다.
  • 서버 설정을 개인 환경에만 두는 관리 — 팀원마다 도구 구성이 달라집니다.

실습 시나리오

실제 시험과 같은 형식의 시나리오 문제입니다.

stdio로 붙인 MCP 서버가 어제까지 잘 되다가 오늘부터 클라이언트에서 응답을 받지 못합니다. 어제 서버 코드에 진행 상황을 출력하는 로그를 추가했습니다. 원인으로 가장 적절한 것은 무엇입니까?

빌드 연습 · 최소 MCP 서버를 만들어 붙이기

30
  1. 1.서버 뼈대

    도구 하나를 노출하는 stdio 서버를 만듭니다.

    기대 결과 · 클라이언트 도구 목록에 나타납니다.

  2. 2.로그 분리

    모든 로그를 표준 오류로 보냅니다.

    기대 결과 · 로그를 켜도 통신이 유지됩니다.

  3. 3.권한 검사

    호출자가 접근할 수 없는 레코드는 거부합니다.

    기대 결과 · 거부가 오류로 클라이언트에 전달됩니다.

  4. 4.반환값 다듬기

    필요한 필드만 남겨 반환합니다.

    기대 결과 · 응답 크기가 줄어든 것이 확인됩니다.

  5. 5.장애 동작 확인

    서버를 강제 종료하고 클라이언트 동작을 관찰합니다.

    기대 결과 · 멈추는지 진행하는지가 기록됩니다.

출처 및 더 읽기