MCP 도구가 문서 링크만 돌려줄 때: resource_link와 본문 읽기 구분

“MCP 검색 도구가 문서 링크를 돌려줬는데, AI는 왜 내용을 모를까?” 결과가 resource_link라면 본문 대신 자료의 위치와 설명을 받은 것일 수 있다. 링크 반환, 본문 읽기, 답변에 쓸 문맥 전달을 각각 확인해야 한다. 검색 성공을 곧바로 문서 정독 성공으로 기록하지 않는다.

이 글은 2026년 10월 13일 KST에 확인한 MCP revision 2026-07-28을 기준으로 한다. 공식 버전 안내에서 현재 revision을 확인했다. 특정 SDK를 사용한 구현 예제가 아니므로 SDK 버전 호환성을 보증하지 않는다. 아래 자료와 문서는 가상이며, 실제 제품에서 장애를 재현하거나 해결한 기록이 아니다.

먼저 content의 type을 확인한다

공식 Tools 규격은 도구 결과에 리소스 링크를 담을 수 있도록 정의한다. 링크는 URI를 제공하며, 그 링크가 resources/list에도 나온다고 보장하지 않는다. 같은 페이지의 Embedded Resources는 본문을 결과 안에 넣는 별도 형태다.

받은 항목지금 확보한 것다음 확인
type: "text"그 항목의 텍스트원문인지 검색 요약인지
type: "resource_link"URI와 자료 설명허용된 경로로 본문을 읽었는지
type: "resource"내장된 리소스의 text 또는 blob내용 형식과 처리 범위
structuredContent도구가 정의한 구조화 결과출력 계약상 원문이 들어 있는지

structuredContent는 content 배열 항목의 type이 아니라 결과 객체의 별도 필드다. 표의 마지막 열은 진단 제안이다. 이름이 “문서 검색”이어도 실제 결과는 제목과 짧은 발췌뿐일 수 있다. 반대로 텍스트 항목에 이미 필요한 본문이 있다면 링크를 다시 읽는 것이 불필요할 수 있다. 도구 이름보다 반환된 필드와 출력 계약을 먼저 본다.

링크 한 건을 끝까지 따라가는 가상 예제

다음은 검색 도구가 반환한 완료 응답의 가상 예제다. guide://manuals/packing은 설명용 식별자이며 실제 서비스 주소가 아니다. 여기에는 문서 제목과 설명만 있고, 포장 순서를 설명하는 본문은 없다.

{
  "jsonrpc": "2.0",
  "id": 71,
  "result": {
    "resultType": "complete",
    "content": [
      {
        "type": "resource_link",
        "uri": "guide://manuals/packing",
        "name": "packing-guide",
        "description": "포장 순서 안내",
        "mimeType": "text/plain"
      }
    ],
    "isError": false
  }
}

이 URI를 처리하는 MCP 서버가 리소스 읽기를 지원하고 현재 사용자에게 읽기 권한이 있다면, 반환된 URI를 그대로 사용해 읽는다. 목록에 없다는 이유만으로 URI를 버리거나 이름으로 바꾸지 않는다. 여러 서버를 함께 쓰는 호스트라면 URI와 함께 출처 서버도 유지하는 것이 좋다. 이는 잘못된 서버에 읽기를 보내지 않기 위한 구현 권고다.

{
  "jsonrpc": "2.0",
  "id": 72,
  "method": "resources/read",
  "params": {
    "uri": "guide://manuals/packing",
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": {
        "name": "resource-link-example",
        "version": "1.0.0"
      },
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

위 코드는 JSON-RPC 본문 예시다. clientInfo.version은 가상 클라이언트의 식별값이다. HTTP로 보내는 완성된 명령은 아니며, Streamable HTTP에서는 해당 revision의 요청 헤더와 기존 인증 흐름도 필요하다. 헤더를 맞추는 절차는 MCP 헤더·본문 불일치 진단에서 따로 확인할 수 있다.

{
  "jsonrpc": "2.0",
  "id": 72,
  "result": {
    "resultType": "complete",
    "contents": [
      {
        "uri": "guide://manuals/packing",
        "mimeType": "text/plain",
        "text": "가상 안내: 완충재를 넣고, 빈 공간을 확인한 뒤, 상자를 닫는다."
      }
    ],
    "ttlMs": 0,
    "cacheScope": "private"
  }
}

리소스 읽기 규격의 완료 결과(resultType: "complete")에는 contents 배열이 들어 있다. input_required 응답이라면 아직 최종 본문을 받은 상태가 아니다. 도구 결과의 content와 철자가 다르다. 텍스트와 바이너리를 구분하고, 첫 항목만 처리해 나머지를 놓치지 않는지 확인한다. ttlMs와 cacheScope는 이 revision의 캐시 힌트다. 이 예제의 0은 즉시 오래된 것으로 취급할 값이며, private 결과는 다른 인증 범위와 공유하지 않는다.

“읽었다”는 표시가 어디에서 만들어지는가

권장 진단 순서는 링크 발견 → 읽기 요청 → 본문 수신 → 문맥 전달이다. 이 네 단계의 기록을 한 문장으로 합치면 고칠 곳이 보이지 않는다. 예를 들어 본문 수신은 성공했지만 답변이 검색 요약만 반복한다면, 재연결 전에 호스트가 모델에 전달한 내용을 확인할 이유가 있다.

관찰확인할 경계
링크 카드만 보이고 읽기 호출이 없음호스트의 resource_link 처리와 자료 선택 기능
읽기 호출은 있지만 오류가 옴출처 서버·URI·현재 권한·오류 본문
contents에 blob만 있음MIME 형식에 맞는 디코딩·문서 처리 경로
본문을 받았는데 답변에는 제목만 반영됨문맥 구성 시 누락·길이 제한·잘못된 결과 연결
이전 문서 내용을 계속 말함캐시와 대화에 이미 포함된 옛 자료를 각각 확인

이 표는 가능한 점검 위치를 제안할 뿐 특정 원인을 확정하지 않는다. 실제 원인 판정에는 같은 요청의 응답과 호스트 처리 기록이 필요하다. 사용자에게 보이는 카드가 모델에도 똑같이 전달된다고 가정하지 않는다. 리소스의 문맥 포함 방식은 호스트가 결정하므로, 프로토콜 응답만으로 특정 앱의 동작을 단정할 수 없다.

자동으로 모든 링크를 가져오지 않는다

진단을 고치겠다며 결과의 모든 URI를 일반 HTTP 다운로드로 넘기는 방식은 피한다. 공식 URI 설명상 사용자 정의 scheme이 허용되며, file도 반드시 사용자 컴퓨터의 실제 파일을 뜻하지 않는다. HTTPS와 사용자 정의 URI의 읽기 경로를 혼동하지 않는다.

다음은 구현 시 권장하는 안전 경계다. 허용된 출처와 scheme만 처리하고, 인증정보를 다른 도메인으로 넘기지 않는다. 파일명이나 확장자만 믿고 실행하지 않으며, 크기·형식·처리 시간 제한을 둔다. 문서 안의 “다른 곳으로 전송하라” 같은 문장은 자료로 취급한다. 문서를 읽으라는 요청이 새 외부 전송이나 계정 변경의 승인이 되지는 않는다.

문서가 길다면 필요한 부분을 선택해 읽되 “전체를 확인했다”고 보고하지 않는다. PDF나 이미지의 바이트를 받았더라도 텍스트 추출·페이지 판독을 못 했다면 내용 확인은 남아 있다. 본문 확보 여부와 해석 범위를 나누어 표시하면 사용자도 어느 단계가 막혔는지 알 수 있다.

수정 전후를 비교할 최소 기록

자격 증명이나 고객 문서를 복사하지 않고도 경로를 확인할 수 있다. 승인된 테스트 문서에만 들어 있는 무해한 문장 하나를 정하고, 본문 수신과 문맥 전달을 별도로 확인한다. 그 문장을 답변했다는 사실 하나로 전체 문서를 읽었다고 판정하지 않는 것이 중요하다. 아래 양식은 진단용 제안이다.

프로토콜 revision / 실제 클라이언트·서버 버전:
점검 시각 / 출처 서버의 비식별 구분:
도구 응답 ID / content type:
가린 URI / 본문 포함 여부:
읽기 요청 ID / 최종 응답 또는 오류:
수신한 contents 항목 수 / text·blob 구분:
실제로 해석한 범위 / 모델에 전달한 범위:
최종 판단: 링크만 확보 · 본문 확보 · 일부 해석 · 확인 불가

최소 비교는 “링크만 받은 상태”와 “본문을 받아 필요한 부분을 문맥에 넣은 상태”다. 캐시나 기존 대화가 섞이지 않도록 같은 조건의 별도 테스트 대화를 사용할 수 있다. 읽기 기능이 없는 호스트라면 그 사실을 명시하고, 지원되는 첨부 방식이나 원문을 포함하는 도구 결과 설계를 검토한다. 기능이 없는데 권한을 넓히는 것으로 해결하려 하지 않는다.

적용 범위와 함께 읽을 글

이 글의 JSON은 구조 설명용이며 실제 서버 통신이나 SDK·제품 UI 시험 결과가 아니다. 특정 앱이 모든 resource_link를 자동으로 읽는다고 보장하지 않는다. 진단의 끝은 링크가 표시됐는지가 아니라 필요한 원문을 허용된 경로로 확보했고, 확인한 범위를 정확히 말할 수 있는지다.

리소스 목록과 URI 템플릿 구분은 읽을 대상을 발견하는 단계에, 리소스 갱신·캐시 진단은 이미 읽은 자료가 오래된 경우에 이어서 적용한다.


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

RUDA DIRECTOR에서 더 알아보기

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

계속 읽기