원격 MCP 서버가 '연결됨'으로 표시되지만 여전히 401
모든 원격 MCP 배포에서 나타나는 특정 종류의 버그 보고서가 있는데, 내용은 항상 똑같습니다:
서버를 추가했습니다. 연결된 것으로 표시됩니다. 도구 목록도 나옵니다. 그런데 첫 실제 호출에서 401 오류가 발생합니다.
아무것도 고장 난 것이 아닙니다. 서버는 정상이고, 클라이언트도 정상이며, 자격 증명(credential)도 정상입니다. 문제가 있는 것은 정신 모델(mental model)입니다 — 우리 대부분이 로컬 stdio 서버에서 가져온 모델로, 여기서 '연결됨'은 '준비됨'을 의미했습니다.
원격 MCP 서버의 경우, 접근 가능성(reachability)과 권한 부여(authorization)는 두 가지 다른 것이며, 전자가 후자를 함축하지 않습니다. 디스커버리(Discovery)는 의도적으로 공개되어 있습니다. 실행(Execution)은 그렇지 않습니다.
디스커버리는 공용 엔드포인트이고, 실행은 그렇지 않다
호스팅되는 MCP 엔드포인트는 일반적으로 어떤 자격 증명 없이 프로토콜의 디스커버리 절반만 노출합니다. 왜냐하면 이것이 디렉터리 크롤러(directory crawlers), 활성 상태 확인(liveness probes) 및 브라우저를 가진 사람들에게 필요한 것이기 때문입니다:
| 클라이언트가 하는 작업 | 필요한 자격 증명 | 자격 증명 없이 일반적인 결과 |
|---|
initialize | 없음 | 200 |
| ... | | |
이러한 비대칭성이 실패를 마치 클라이언트 버그처럼 느끼게 만듭니다. 클라이언트의 '연결됨' 표시기는 자격 증명 없이도 성공하는 initialize와 tools/list에 의해 구동됩니다. 불은 초록색이지만, 그 대상은 여전히 아무 작업도 할 수 없습니다. | | |
따라서 첫 번째 진단 질문은 결코
- 스키마 접두사(The scheme prefix). 헤더 값은 반드시
Bearer <비밀번호> 형식이어야 하며, 단어와 공백이 포함되어야 합니다. headers를 불투명한 맵(opaque map)으로 받아들이는 클라이언트 설정은 단순히 비밀번호만 보내도 문제가 없다고 생각할 수 있지만, 서버는 이를 올바르게 거부합니다. 정확한 철자는 계약의 일부이며, 모델 프롬프트가 아닌 설정에 포함되어야 합니다.
- 호스트가 실제로 협상한 인증 모드(Which auth mode your host actually negotiated). 사용자님의 설정에 반환된
401과 인증되지 않은 프로브(unauthenticated probe)에 대해 서버가 반환하는 401은 같은 사건이 아닙니다. 저는 누군가가 기본적인 npx 명령을 실행하여 401 응답을 보고, 이를 "인증이 필요하므로 내 인증이 작동하고 있다"라고 해석하며 깨진 설정을 "검증"하는 것을 목격했습니다. 이는 아무것도 배우지 못하게 하는 매우 정확한 방법입니다.
여기에 함정이 있습니다. 그리고 제가 이 글을 작성하면서 빠졌기 때문에 정확히 언급할 가치가 있습니다: tools/list는 결코 자격 증명 테스트가 아닙니다. 저는 의도적으로 잘못된 비밀번호를 사용하여 tools/list를 실행했고, 401 응답을 예상했지만, 헤더가 단순히 참조되지 않았기 때문에 전체 도구 목록이 담긴 200 응답을 받았습니다. 만약 사용자님의 "인증 확인"이 리스팅 호출(listing call)이라면, 헤더에 무엇을 넣든 통과할 것입니다.
자격 증명은 실제로 필요한 곳에서만 사용됩니다: tools/call에서요. 따라서 세 가지 상태는 리스팅 경로가 아닌 호출 경로(calling path)에서 테스트되어야 합니다.
상태를 실제로 구별하는 세 가지 curl 명령어
비밀번호는 명령줄에 절대 설정하지 마십시오 (셸 히스토리와 기록에 남습니다). 환경 변수에 설정하세요:
# 1) 자격 증명 없이 발견(discovery) 시도 -> 반드시 200이어야 합니다. 이는 연결성 그 이상도 이하도 아닙니다.
curl -s -o /dev/null -w 'open %{http_code}\n' -X POST "$MCP_URL" \
-H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \
...
세 개의 줄, 세 가지 다른 질문이 있으며, 그중 오직 세 번째 질문만이 설정(setup)에 대한 합격/불합격 여부를 판단합니다. 1번째 줄의 결과가 200이 아니라면 전송 문제(transport problem)입니다. 2번째 줄이 200을 반환한다면, 호출 자체가 서버에서 보호되고 있지 않거나 (혹은 생각하는 것과 다른 경로를 타고 있는 경우), 이는 설정에 대한 문제가 아니라 서버 자체에 대한 발견 사항입니다. 만약 2번째 줄이 401이고 3번째 줄도 401이라면, 합격 여부가 실제로 잘못되었거나 만료된 것이며, 이 경우가 유일하게 재시행(re-rolling)을 하는 것이 올바른 경우입니다.
이러한 속성을 위키 페이지에 기록하기보다는 자체 테스트 스위트(test suite)에 포함하는 것이 좋습니다. 여기서 주장해야 할 속성은 "서버가 작동한다"가 아니라, **"호출 경로에 자격 증명(credential)이 필요하며, 우리의 클라이언트 설정이 작동하는 것을 제공한다"**입니다.
연결되어 보일 수 있는 두 번째 이유: 전송 구문(transport dialects)
HTTP를 통한 원격 MCP는 실제 환경에서 여러 개의 방언(dialect)을 가지고 있으며, 클라이언트는 이를 다르게 표기합니다. 설정을 작성하는 사람은 사양 요약본(spec summary)이 아니라 클라이언트와 일치시켜야 합니다:
| 클라이언트 측 차이점 | 기재할 내용 | 잘못했을 때의 실패 사례 |
|---|
| 서버 맵을 담는 키 | 대부분의 JSON 설정 클라이언트에서는 mcpServers, VS Code의 mcp.json에서는 servers | 서버가 조용히 무시됨 — "연결됨(connected)"이 절대 발생하지 않거나, 잘못된 항목이 로드됨 |
| ... | | |
이 중 얼마나 많은 것이 동일한 사용자 가시적 증상(user-visible symptom)을 만들어내는지 주목하십시오. 이것이 함정입니다: 하나의 오류 메시지, 네 개의 관련 없는 원인, 그리고 "서버가 불안정하다(flaky)"는 쪽으로 강하게 기울어지는 경향이 있습니다.
해결책은 자체 서버에 대한 설정 행렬(config matrix)을 명시적으로 만들고 이를 코드 옆에 저장소(repo)에 커밋하는 것입니다. 위의 모든 행들은 독자가 복사할 수 있는 항목이며, 그 모든 것은 이전에 우리가 실수했던 것들입니다.
자격 증명을 모델의 컨텍스트에서 분리하기
이는 설정만큼이나 호출 패턴에 영향을 미치기 때문에 명확하게 언급할 가치가 있는 또 다른 설계 포인트입니다.
비밀번호는 **호스트(host)**의 설정에 넣으세요. 절대 도구 인자(tool argument)로 받지 마세요. 인자로 전달되는 자격 증명은 프롬프트, 기록(transcript), 그리고 해당 로그 파이프라인 어디든 남게 되는 자격 증명이 됩니다. 이것이 바로 검색 스타일의 도구가 REST 엔드포인트보다는 도구 호출(tool call)로 설계되어야 하는 이유입니다. 호스트가 헤더를 첨부하고, 에이전트는 그 값을 절대 보유하지 않으며, 중단된 호출은 누군가가 토큰을 복사/붙여넣기 하지 않아도 가져올 수 있습니다.
만약 이 글에서 한 가지를 얻어 간다면, 세 줄 점검(three-line check)을 기억하세요. '연결됨(Connected)'은 tools/list에 대한 주장일 뿐입니다. 에이전트가 실제로 무언가를 호출할 수 있는지 여부는 tools/call에 대한 주장이며, 이를 알 수 있는 유일하게 정직한 방법은 세 가지 상태를 의도적으로, 한 번씩 분리하여 테스트하는 것입니다.
동일한 세 가지 상태와 함께 도구의 응답 대신 계약(contract)을 단언하는 작업 예시가 저희 공개 쿡북에 있습니다: https://mcp.turingcorp.net/은 레포지토리를 가리키며, 관련 섹션은 docs/COOKBOOK.md §7–§10입니다. 7개 필드 구성 매트릭스는 동일한 저장소의 llms-install.md에 있으며, 위에서 설명된 모든 실수로부터 나온 문서입니다.
부록: 실패 원인표 (failure-to-cause table)
이 표를 복사하여 자체 운영 매뉴얼(runbook)에 붙여넣고 서버에 맞게 오른쪽 열을 채우세요:
| 증상 | 가장 가능성 높은 원인 | 먼저 확인할 것 |
|---|
| 패널이 연결됨이라고 표시되나, 첫 호출에서 401 반환 | 클라이언트 설정에 자격 증명이 첨부되지 않음 | headers 블록, 특히 Bearer 접두사 포함 |
| ... | | 이전 호출의 검색(retrieval) 시도에서 401 에러 발생 |