MCP 스크린샷이 안 보일 때: image Base64·MIME·모델 전달 경로 진단

“MCP 도구가 스크린샷을 반환했는데 왜 이미지가 안 보이거나, AI는 못 봤다고 할까?” 먼저 응답에 실제 이미지 바이트가 있는지 확인한다. 그다음 Base64와 MIME 형식, 호스트의 화면 표시, 모델 입력 전달을 따로 점검한다. 이미지 생성 성공과 화면 표시 성공, 모델의 이미지 판독 성공은 서로 다른 단계다.

기준은 2026년 10월 13일 KST에 확인한 MCP revision 2026-07-28이다. 공식 버전 안내를 대조했다. 특정 앱이나 SDK의 구현을 보증하는 글은 아니다. 아래는 진단 절차와 가상 데이터 예제이며, 실제 MCP 서버 장애를 재현한 기록이 아니다.

1. URL, 텍스트, 이미지 블록을 먼저 구분한다

공식 Image Content 예시는 type: "image", Base64 문자열인 data, mimeType을 사용한다. data 자리에 파일 경로나 이미지 URL을 넣는 것은 이 형태와 다르다. Base64 문장을 type: "text"에 넣었다고 이미지 블록이 되지도 않는다.

받은 형태진단의 첫 질문
image + data + mimeTypedata를 디코딩하면 의도한 이미지 바이트인가?
text 안의 URL 또는 Base64호스트가 일반 문자열로 처리하고 있는가?
resource_link이미지 위치만 받았는가? 별도 읽기가 필요한가?
resource 안의 blob리소스 바이너리 처리 경로를 거치는가?

이 글은 첫 번째 형태에 집중한다. 자료의 위치만 반환된 경우에는 resource_link와 본문 읽기 구분을 먼저 적용한다. 도구 목록의 작은 아이콘이 없는 문제라면 결과 이미지와 무관할 수 있으므로 도구 icons 표시 진단으로 분리한다.

2. Base64 검사와 이미지 검사는 다르다

권장 순서는 길이 제한 → Base64 디코딩 → 실제 이미지 형식 확인 → 제한된 이미지 디코더로 열기다. 선언한 image/png와 실제 JPEG 바이트가 다르면 MIME만 고치거나 변환 결과를 다시 인코딩해야 한다. 확장자를 바꾸는 것으로 파일 형식이 변하지는 않는다.

다음 Python 함수는 PNG 후보의 전송 형식만 검사하는 작은 예제다. 2MiB 상한은 이 예제에서 정한 운영 제한이며 MCP 공통 제한이 아니다. JSON 파싱 전의 HTTP 본문 크기 제한을 대신하지 못한다. 서버에서 이미 읽은 바이트를 다시 온라인 도구에 업로드할 필요도 없다.

import base64
import binascii

MAX_BYTES = 2 * 1024 * 1024  # Example policy, not an MCP limit.
MAX_CHARS = 4 * ((MAX_BYTES + 2) // 3)

def check_png_candidate(block):
    if not isinstance(block, dict):
        raise ValueError("Expected a content object")
    if block.get("type") != "image":
        raise ValueError("Not an image block")
    if block.get("mimeType") != "image/png":
        raise ValueError("This example checks PNG only")
    data = block.get("data")
    if not isinstance(data, str) or len(data) > MAX_CHARS:
        raise ValueError("Missing data or encoded size limit exceeded")
    try:
        raw = base64.b64decode(data, validate=True)
    except (binascii.Error, ValueError) as exc:
        raise ValueError("Invalid Base64") from exc
    if not raw or len(raw) > MAX_BYTES:
        raise ValueError("Decoded size limit exceeded or empty data")
    if not raw.startswith(b"\x89PNG\r\n\x1a\n"):
        raise ValueError("PNG signature mismatch")
    return raw  # Still needs a bounded image decoder.

Python 3.12 공식 base64 문서에 따라 validate=True를 사용했다. 기본 디코딩은 일부 비알파벳 문자를 버릴 수 있어, 형식 오류를 찾는 단계에서는 엄격 검사가 유용하다. data:image/png;base64,라는 Data URL 접두어를 포함한 문자열도 이 함수에서는 거부한다. 생산 코드에서 무조건 접두어를 잘라 오류를 숨기기보다, 어느 계층이 잘못된 형식을 만들었는지 먼저 확인한다.

PNG 시그니처가 맞는다는 것은 완전한 이미지이거나 안전하다는 증명이 아니다. 잘린 파일, 손상된 청크, 지나치게 큰 픽셀 수 등은 별도 검사 대상이다. 실제 디코더에는 너비·높이·픽셀 수·메모리·처리 시간 제한을 두고, 제한을 넘기면 조용히 성공 처리하지 않는다. 이 안전 절차는 구현 권고다.

3. 최소 응답과 실제 스크린샷을 비교한다

복잡한 화면 캡처 대신 작은 비민감 테스트 이미지로 먼저 전송 경로를 확인한다. 아래 코드는 이미 확보한 PNG 바이트 png_bytes로 만드는 가상 완료 응답이다. 독립 실행 코드가 아니며, 실제 캡처·권한 확인·네트워크 전송은 포함하지 않는다.

result = {
    "resultType": "complete",
    "content": [
        {"type": "text", "text": "A synthetic test image."},
        {
            "type": "image",
            "data": base64.b64encode(png_bytes).decode("ascii"),
            "mimeType": "image/png",
            "annotations": {"audience": ["user", "assistant"]}
        }
    ],
    "isError": False
}

이것은 JSON-RPC 전체 메시지의 result에 넣을 객체다. 텍스트 설명이 보인다고 바로 뒤 이미지 항목까지 처리됐다고 판단하지 않는다. 배열 첫 항목만 읽는 변환 코드, 이미지 항목을 제거하는 직렬화 코드, 지나친 크기 축소·잘림 여부를 확인한다.

4. 화면에 보이는 것과 모델이 받은 것을 나눈다

공식 annotations 설명에서 audience는 의도한 대상에 관한 힌트다. ["user"]만 있다면 사용자 표시를 위한 내용인지 확인할 이유가 있다. 하지만 ["user", "assistant"]로 바꾸는 것만으로 모델에 이미지가 전달되거나 해석된다고 보장하지 않는다. 접근 권한을 부여하는 설정도 아니다.

관찰다음 확인
엄격 Base64 디코딩부터 실패문자열 잘림, 접두어, 잘못된 인코딩
디코딩은 되지만 이미지로 열리지 않음실제 파일 형식, 손상, 디코더 제한
작은 이미지만 표시됨해당 호스트·모델·전송 계층의 크기 제한
사용자는 보지만 AI는 내용을 모름audience 처리, 모델의 이미지 입력 지원, 변환 후 입력
AI 답변은 있지만 화면은 빈칸UI 렌더러, 결과 항목 필터, 이미지 표시 지원

표는 원인을 확정하는 목록이 아니라 경계를 좁히는 순서다. 호스트와 모델 이름·버전을 기록하고 동일한 테스트 이미지를 같은 조건으로 비교한다. 모델이 텍스트 설명을 반복한 것만으로 이미지를 봤다고 판정하지 않는다. 테스트 이미지에만 있는 무해한 시각 요소를 확인하되, 한 번 맞힌 답을 모든 이미지 처리의 성공으로 일반화하지 않는다.

검사 기록은 바이트 전체 대신 최소 정보로 남긴다

확인 시각 / MCP revision:
호스트·모델·서버·SDK 실제 버전:
content 항목 수 / 각 type:
선언 MIME / 디코딩된 형식:
인코딩 길이 / 디코딩 바이트 수:
디코더 결과 / 너비·높이 / 적용한 상한:
사용자 화면 표시: 확인 · 실패 · 미확인
모델 입력 전달: 확인 · 실패 · 미확인
판독한 범위 / 아직 확인하지 못한 부분:

스크린샷에는 로그인 정보, 고객 문서, 알림, 위치가 섞일 수 있다. 승인된 테스트 화면을 사용하고 Base64 전체를 일반 로그에 남기지 않는다. Base64는 암호화가 아니다. 이미지 안의 명령문도 자료의 일부이며 새 작업 권한으로 취급하지 않는다. 표시 오류를 고치려고 권한을 넓히거나 민감한 캡처를 외부 변환 사이트로 보내는 조치는 진단에 필요하지 않다.

이번 예제에서 확인한 범위

검사 함수는 Python 3.12.14 표준 라이브러리에서 합성 입력으로 실행했다. 정상적인 작은 PNG 후보는 통과하고, 잘못된 type·MIME·Base64·접두어·빈 데이터·시그니처·크기 초과 입력은 거부하는지 확인했다. 시그니처만 있는 불완전한 데이터도 이 함수는 통과한다는 한계를 함께 검사했다. 이미지 디코더, 실제 MCP 통신, 제품 UI나 모델의 판독 성공을 검증한 결과는 아니다.

수정 완료 기준은 “도구 호출이 성공했다”가 아니라 필요한 이미지가 올바른 형식으로 수신되고, 의도한 화면과 모델 입력 경로에서 각각 확인되는 것이다. 어느 단계까지 확인했는지 남기면 재연결이나 무작정 재시도보다 다음 조치가 분명해진다.


다른 글 보기 · 주제 탐색 · 작성자와 편집 기준 · 문의·정정 요청

RUDA DIRECTOR에서 더 알아보기

지금 구독하여 계속 읽고 전체 아카이브에 액세스하세요.

계속 읽기