본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 21. 08:16

MCP 테스트 및 디버깅: Curl 우선 접근 방식

요약

MCP(Model Context Protocol) 통합 과정에서 발생하는 오류를 효율적으로 디버깅하기 위해 curl을 활용한 '도구 우선 테스트' 접근 방식을 제안합니다. 에이전트나 프롬프트를 수정하기 전에 SSE 세션 연결, 초기화, 도구 목록 조회, 직접 호출 단계를 통해 MCP 서버 자체의 데이터 형식이나 스키마 문제를 먼저 검증하는 것이 핵심입니다.

핵심 포인트

  • 에이전트 디버깅 전, curl을 사용하여 MCP 도구의 독립적인 동작을 먼저 검증해야 함
  • SSE(Server-Sent Events) 세션을 통해 sessionId를 확보하는 것이 디버깅의 첫 단계임
  • tools/list 명령을 통해 도구의 설명과 파라미터 스키마가 정확히 등록되었는지 확인해야 함
  • tools/call을 직접 실행하여 응답 데이터의 형식이 올바른지 확인하면 LLM 문제인지 서버 문제인지 명확히 구분 가능함

MCP 테스트 및 디버깅: Curl 우선 접근 방식

이전 포스트에서 저는 12개 이상의 도구(tools)를 가진 4개의 MCP 서버에 AI 에이전트를 연결했습니다. 잘 작동했습니다. 하지만 작동하지 않을 때가 있습니다. 에이전트가 잘못된 답변을 내놓으면, 질문은 항상 동일합니다. LLM의 문제인가, 프롬프트(prompt)의 문제인가, 아니면 도구(tool)의 문제인가?

이 포스트에서는 문제의 90%를 잡아낼 수 있는 가장 간단한 접근 방식부터 시작하여, 제가 MCP 통합을 디버깅하는 방법을 다룹니다.

에이전트를 테스트하기 전에 도구를 먼저 테스트하세요

제가 초기에 저지른 가장 큰 실수는 문제는 도구에 있는데 에이전트를 디버깅하는 것이었습니다. 몇 시간 동안 시스템 프롬프트(system prompt)를 수정하다가, 나중에야 MCP 서버가 잘못된 형식의 데이터(malformed data)를 반환하고 있다는 사실을 깨닫곤 했습니다. 이제 저는 항상 curl을 사용하여 도구를 먼저 테스트합니다. MCP는 단순한 HTTP이므로, AI 에이전트나 LangChain4j 없이도 어떤 도구든 호출할 수 있습니다.

1단계: SSE 세션 열기
curl -N http://localhost:8092/sse

이 명령은 Server-Sent Events (SSE) 연결을 엽니다. 서버는 첫 번째 이벤트에서 sessionId를 반환합니다. 이를 복사해 두세요. 이후의 모든 요청에 필요합니다.

2단계: 연결 초기화
curl -X POST "http://localhost:8092/mcp/message?sessionId=YOUR_SESSION_ID"
-H "Content-Type: application/json"
-d '{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "clientInfo": { "name": "debug-client", "version": "1.0.0" }, "capabilities": {} } }'

3단계: 사용 가능한 도구 목록 조회
curl -X POST "http://localhost:8092/mcp/message?sessionId=YOUR_SESSION_ID"
-H "Content-Type: application/json"
-d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {} }'

이 명령은 설명(descriptions) 및 스키마(schemas)와 함께 등록된 모든 도구를 반환합니다. 저는 여기서 세 가지를 확인합니다: 예상되는 모든 도구가 나열되어 있는가? 설명이 정확한가? 파라미터 스키마(parameter schemas)가 올바른가?

한번은 실패하는 에이전트를 디버깅하느라 한 시간을 허비한 적이 있습니다. tools/list 응답 결과, 도구가 3개가 아닌 2개만 표시되었습니다. .tools(...) 호출에서 getFraudRiskScore를 등록하는 것을 잊었던 것입니다. LLM은 존재하지 않는 도구를 사용하려고 시도하고 있었습니다.

4단계: 도구를 직접 호출하기
curl -X POST "http://localhost:8092/mcp/message?sessionId=YOUR_SESSION_ID"
-H "Content-Type: application/json"
-d '{ "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "getStockByProduct", "arguments": { "productCode": "COMIC_BOOKS" } } }'
이것은 에이전트가 트리거할 것과 정확히 동일한 코드 경로를 실행합니다. 여기서 응답이 잘못되었다면, 문제는 LLM에 있는 것이 아니라 사용자 서비스 코드에 있다는 의미입니다.

일반적인 MCP 버그 (및 찾는 방법)

  1. 도구 이름 불일치
    시스템 프롬프트에는 getTransactionStatus라고 되어 있습니다. 하지만 MCP 서버는 getPaymentStatus를 노출합니다. LLM은 존재하지 않는 도구를 호출하려고 시도합니다. 에이전트는 실제 데이터가 없는 모호한 답변을 제공합니다.
    찾는 방법: tools/list를 실행하고 모든 도구 이름을 시스템 프롬프트와 비교하세요. 정확히 일치해야 합니다.
  2. 필수 매개변수 누락
    도구 스키마에는 transactionId가 필수라고 되어 있습니다. 하지만 LLM은 orderId만으로 호출합니다. 도구는 null pointer로 실패하거나 "not found"를 반환합니다.
    찾는 방법: tools/list에서 스키마를 확인하세요. 매개변수가 필수라면, 설명에 어디서 가져와야 하는지 명확하게 기재해야 합니다. 저는 도구 설명에 "이벤트의 transactionId 필드에서 추출하세요"와 같은 메모를 추가합니다.
  3. 잘못된 매개변수 유형
    스키마에는 threshold가 정수(integer)라고 되어 있습니다. 하지만 LLM은 이를 문자열 "3"로 전송합니다. 핸들러는 args.get("threshold")를 (Integer)로 캐스팅하고 ClassCastException을 발생시킵니다.
    찾는 방법: 올바른 유형과 잘못된 유형 모두를 사용하여 curl로 테스트하세요. MCP SDK가 일부 타입 강제 변환(type coercion)을 수행하지만, 모든 경우에 해당하지는 않습니다. 저는 중요한 도구의 핸들러에 명시적인 타입 검사를 추가합니다.
  4. 지나치게 광범위한 도구 설명
    설명에 "데이터를 가져옵니다"라고만 되어 있습니다. LLM은 다른 도구가 더 적절함에도 불구하고 모든 것에 대해 이 도구를 호출합니다.
    찾는 방법: 다른 도구를 사용해야 하는 질문을 에이전트에게 해보세요. 만약 잘못된 도구를 선택한다면, 설명이 너무 모호하다는 뜻입니다. 저는 설명을 수정하여 "다음 경우에 사용하세요..."와 "사용하지 마세요..." 조항을 포함시킵니다.

요청/응답 로깅 활성화 (Enabling Request/Response Logging)

LangChain4j의 MCP 전송(transport)은 로깅을 지원합니다. 저는 개발 중에 이를 활성화합니다:

private McpClient buildClient ( String sseUrl ) {
    return new DefaultMcpClient . Builder () 
        . transport ( new HttpMcpTransport . Builder () 
            . sseUrl ( sseUrl ) 
            . logResponses ( true ) 
            . logRequests ( true ) 
            . build ()) 
        . build (); 
}

이렇게 하면 모든 JSON-RPC 요청과 응답이 콘솔에 출력됩니다. 에이전트가 어떤 도구(tool)를 어떤 인자(argument)와 함께 호출하는지, 그리고 무엇이 반환되는지를 정확히 확인할 수 있습니다. 운영 환경에서는 소음이 심하겠지만, 개발 중에는 매우 귀중한 정보가 됩니다.

서버 측에서도 동일한 채팅 모델 로깅이 작동합니다:

GoogleAiGeminiChatModel . builder () 
    . logRequests ( true ) 
    . logResponses ( true ) 
    . build ();

이를 통해 Gemini로 전송된 함수 선언(functionDeclaration)과 Gemini가 생성한 함수 호출(functionCall)을 볼 수 있습니다. 만약 LLM이 잘못된 도구를 선택한다면, 요청/응답 로그를 통해 그 추론 과정을 확인할 수 있습니다.

도구 응답 테스트 (Testing Tool Responses)

LLM에게는 도구 응답의 형식이 매우 중요합니다. 저는 초기에 key=value 문자열 방식에서 JSON 방식으로 전환했습니다.

이전: status=SUCCESS | totalAmount=150.00 | totalItems=3
이후: return jsonUtil . toJson ( paymentService . findByTransactionId ( txId )) . orElse ( "No payment found" );

ObjectMapper.writeValueAsString()은 LLM이 안정적으로 파싱할 수 있는 깔끔한 JSON을 생성합니다. key=value 형식은 약 10%의 사례에서 파싱 오류를 일으켰는데, LLM이 파이프(|) 문자를 값의 일부로 취급했기 때문입니다.

디버깅 체크리스트 (The Debugging Checklist)

에이전트가 잘못된 답변을 하거나 빈 답변을 내놓을 때, 저는 다음 체크리스트를 실행합니다:

  • curl로 도구 테스트하기: 예상된 데이터를 반환하는가?
  • tools/list 확인하기: 모든 도구가 등록되어 있는가? 이름이 시스템 프롬프트와 일치하는가?
  • 도구 설명(tool descriptions) 확인하기: LLM이 올바른 도구를 선택할 수 있을 만큼 충분히 구체적인가?
  • 매개변수 스키마(parameter schemas) 확인하기: 필수 필드가 LLM이 컨텍스트에서 추출할 수 있는 내용과 일치하는가?
  • maxSequentialToolsInvocations 확인하기: 워크플로우에 비해 설정값이 충분히 높은가? 5-사가(5-saga) 분석에는 최소 11번의 도구 호출이 필요합니다.
  • maxOutputTokens 확인하기: 응답이 잘리고(truncated) 있지는 않은가?

로깅 (logging)을 활성화하세요. LLM이 생성하는 실제 함수 호출 (functionCall)을 살펴보세요. 잘못된 도구인가요? 잘못된 파라미터 (params)인가요? 대부분의 버그는 1~3번 범주에 속합니다. 도구 자체가 고장 났거나, 도구 이름이 일치하지 않거나, 설명 (description)이 잘못된 경우입니다.

다음에 추가할 사항
아직 MCP를 위한 자동화된 통합 테스트 (integration tests)를 갖추고 있지 않습니다. 각 도구는 서비스의 단위 테스트 (unit tests)를 통해 개별적으로 테스트됩니다. 하지만 전체 체인 (agent → MCP client → HTTP → MCP server → database)은 수동으로만 테스트됩니다. 적절한 통합 테스트는 다음과 같을 것입니다: Testcontainers를 사용하여 서비스를 시작하고, MCP 클라이언트를 연결하며, 각 도구를 호출하고, 응답을 검증하는 방식입니다. 이는 로드맵에 포함되어 있습니다. 현재로서는 curl 접근 방식이 대부분의 문제를 잡아내며, 도구당 30초 정도가 소요됩니다.

리포지토리: github.com/pedrop3/saga-orchestration

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0