Claude Code가 MCP 서버의 지침을 시스템 프롬프트가 아닌 사용자 턴에 배치하는 방식
요약
Claude Code는 MCP(Model Communication Protocol) 서버와 상호작용할 때, 시스템 프롬프트가 아닌 사용자 턴에서 지침을 전달하는 경향이 있습니다. 이는 `initialize` 응답의 선택적 필드인 `instructions`를 통해 이루어지며, 이 지침은 최대 2,048자로 제한됩니다. 또한, MCP 사양에 따르면 초기화 단계는 클라이언트와 서버 간의 첫 번째 상호작용이어야 합니다.
핵심 포인트
- MCP 서버 지침은 시스템 프롬프트가 아닌 사용자 턴에서 전달될 수 있습니다.
- 지침을 포함하는 `instructions` 필드는 `initialize` 응답의 선택적 필드입니다.
- 서버 지침은 최대 2,048자로 제한되며, 간결하게 작성해야 합니다.
- MCP 프로토콜 네고시에이션(`auto`) 설정을 통해 다양한 서버와 연결할 수 있습니다.
20회 중 20회의
claude -p실행에서 Claude Code 2.1.285는server/discover를 통해 우리의 stdio MCP 서버를 열었습니다. 이는 문서에 따르면 stdio 서버가MCP_PROTOCOL_NEGOTIATION=auto가 설정될 때만 받는 탐색(probe)입니다. 그리고 서버가 응답한 2회 실행에서는,initialize가 한 번도 전송되지 않았기 때문에, 우리가initialize응답에 넣었던 지침은 Claude에게 도달하지 못했습니다. 나머지 경우는 문서와 일치했습니다. 즉, 지침이 첫 번째 사용자 턴에서<system-reminder>로 도착했으며 (시스템 프롬프트에는 절대 포함되지 않음), 이는 2,048자로 잘렸고, 이 제한된 블록은 영어로 첫 요청에 721 토큰을, 일본어로 1,727 토큰을 추가했습니다.
MCP 서버는 연결 시 클라이언트에게 일반 텍스트 단락(paragraph of plain text)을 전달할 수 있는데, 이것이 바로 instructions 필드입니다. 이 필드는 서버 작성자가 모델에게 서버의 목적과 언제 이를 사용해야 하는지 산문으로 알려줄 수 있는 유일한 장소입니다. Claude Code의 MCP 문서는
MCP 사양의 2025-11-25 개정판에서, instructions는 initialize 응답의 선택적 필드이며, 라이프사이클 페이지는 순서에 엄격합니다. "초기화 단계는 클라이언트와 서버 간의 첫 번째 상호작용이어야 합니다(MUST)." 예시 응답은 `
같은 날 가져온 Claude Code의 MCP 페이지는 해당 필드에 대해 세 가지 진술을 합니다. 도구 검색(tool search)과 관련하여 "도구 이름과 서버 지침만 세션 시작 시 로드됩니다". 그리고 다음과 같습니다: "Claude Code는 기본적으로 각 도구 설명과 각 서버의 지침을 2,048자로 자릅니다. 간결하게 유지하고 중요한 세부 사항은 시작 부분 근처에 배치하십시오." 이 제한은 환경 변수인 CLAUDE_CODE_MAX_MCP_DESCRIPTION_LENGTH로 변경할 수 있으며, 해당 참조 항목에는 이것이 "순수 숫자의 양의 정수를 받습니다. 그 외의 것은 무시되고 기본값이 적용됩니다"라고 명시되어 있습니다.
같은 페이지에는 두 가지 MCP 클라이언트 런타임(client runtimes)에 대한 섹션도 있습니다. v2 런타임은 2026-07-28 개정판을 추가하며, v2 Claude Code는 "HTTP 서버가 새로운 개정판을 지원하는지 묻고, 지원하는 경우 그것을 사용합니다. 또한 기능 플래그를 가져오는 세션에서는 claude.ai 커넥터 서버에도 문의합니다. 모든 세션에서 stdio 서버 또는 커넥터 서버에 문의하려면 MCP_PROTOCOL_NEGOTIATION을 auto로 설정하십시오. 이는 다른 모든 서버와 v1과 동일하게 연결됩니다."라고 설명합니다. 해당 환경 변수 자체의 항목에는 기본값이 반복되어 있습니다: "변수가 없으면 Claude Code는 HTTP 서버를 조사하고, 기능 플래그를 가져오는 세션에서는 claude.ai 커넥터 서버도 조사합니다."
따라서 문서에 따르면, 사용자가 옵트인하지 않은 한 stdio 서버는 initialize를 먼저 보게 되어야 합니다. 이 기사의 나머지 부분은 그 문장에 달려 있습니다.
실험실 (The lab)
이 서버는 의존성이 없는 Node 스크립트로, stdio를 통해 줄 바꿈으로 구분된 JSON-RPC로 통신합니다. 세 개의 도구(lookup_part, list_bins, lab_ping)를 가지고 있어 도구 검색이 처리할 내용이 있고, 수신하는 모든 메시지를 rpc-log.jsonl에 추가합니다. MCP 설정의 환경 변수는 지침을 선택합니다: 없음(필드에서 생략), 주어진 길이의 영어 텍스트, 또는 주어진 길이의 일본어 텍스트입니다. 두 개의 스위치가 더 있어 서버가 2026-07-28 서버처럼 server/discover에 응답하도록 하거나, initialize 응답을 8초 동안 보류하게 합니다.
이 텍스트는 Claude가 실제로 받은 내용만을 보고할 수 있도록 구성되어 있습니다. 이 텍스트는 BEGIN codeword: <단어>-<네 자리 숫자>.로 시작하고 END codeword: <단어>-<네 자리 숫자>.로 끝나며, 그 사이에는 약 250자마다 [at N] 형식의 마커와 함께 부품 재고에 대한 몇 문장이 반복됩니다. 여기서 N은 해당 마커 자체의 문자 오프셋입니다. 각 텍스트는 길이와 언어로부터 파생된 고유한 코드를 한 쌍씩 가지며, 이 이중 시대(dual-era) 설정에서는 두 응답이 서로 다른 코드 쌍을 전달하므로, 답변을 통해 Claude가 어떤 텍스트를 받았는지 알 수 있습니다.
각 서버 설정은 자체 구성 파일(config file)을 가지고 있었고, --strict-mcp-config 플래그와 함께 전달되어 다른 MCP 서버나 해당 계정의 claude.ai 커넥터가 세션에 참여하는 것을 막았습니다. Claude Code 환경 변수를 변경하는 구성들은 이 파일 중 하나를 재사용하고 claude 프로세스에서 해당 변수를 설정합니다. 20,000자 분량의 텍스트에 대한 파일은 다음과 같습니다:
{
"mcpServers": {
"lab": {
...
18번의 실행(run)에서 이 명령어와 프롬프트를 사용했으며, 빈 디렉토리에서 진행되었습니다 (두 개의 후기 서버 실행은 아래 섹션에서 설명하는 것처럼 프롬프트와 두 개의 플래그를 변경했습니다):
claude -p "$PROMPT" --output-format stream-json --verbose --max-turns 1 --model opus \
--settings '{"disableAllHooks": true}' --strict-mcp-config --mcp-config cfg/a20k.json \
--debug-file runs/R05.debug.log
도구는 호출하지 말고, 컨텍스트에 이미 있는 내용으로부터만 답변하세요. lab이라는 이름의 MCP 서버가 지침을 제공했을 수 있습니다. 이 형식으로 정확히 한 줄로 응답하세요: BEGIN=<"BEGIN codeword:" 뒤의 코드> END=<"END codeword:" 뒤의 코드> LAST=<[at N] 형태로 작성된 모든 마커 중 가장 큰 숫자 N>. 찾을 수 없는 값은 NONE이라고 작성하세요.
우리는 다른 Claude Code 세션 내부에서 실행을 시작했기 때문에, 래퍼(wrapper)가 해당 세션이 내보낸 환경 변수(CLAUDECODE와 그 주변 변수들)를 제거한 후에 claude를 호출했습니다. --settings 오버라이드는 후크(hooks)를 비활성화하여 사용자 레벨의 알림 후크는 조용히 유지되었습니다.
숫자의 경우, 우리는 트랜스크립트를 읽습니다. 첫 번째 어시스턴트 기록의 usage는 첫 번째 요청의 크기를 제공합니다: input_tokens와 cache_read_input_tokens, 그리고 cache_creation_input_tokens를 합한 값입니다. 2.1.285 버전에서는 트랜스크립트가 Claude Code가 첫 사용자 턴에 추가하는 모든 컨텍스트 블록을 그 렌더링된 텍스트와 함께 attachment 기록으로, 시스템 프롬프트와 도구 정의를 담는 prompt_snapshot 기록 옆에 저장합니다. 이 기록들은 모델이 말한 내용에 의존하지 않고도 지침이 어디로 갔는지, 그리고 얼마나 많은 문자로 이루어져 있었는지를 알려줍니다.
우리는 10가지 구성을 각각 두 번씩, 총 20번 실행했습니다. 모든 쌍에서 첫 번째 요청은 토큰 수와 일치했습니다.
하나의 표에 담긴 결과
| 구성 | 첫 번째 요청 (토큰) | vs. 지침 없음 | Claude에게 도달한 내용 | Claude의 답변 |
|---|---|---|---|---|
instructions 필드 없음 | 20,472 | 아무것도 아님 | 세 경우 모두 NONE | |
| ... |
제한 변수는 CLAUDE_CODE_MAX_MCP_DESCRIPTION_LENGTH입니다. 총 20번의 실행에서 Claude의 답변은 트랜스크립트가 제공되었다고 말하는 내용과 일치했습니다: 텍스트가 전달된 올바른 코드워드, 전달되지 않은 곳에 대한 NONE, 그리고 잘린 지점 직전의 마지막 마커였습니다. 단 한 번도 임의로 코드워드를 만들어내지 않았습니다. 이 20번의 실행을 합친 비용은 total_cost_usd에서 $1.02로 보고되었습니다.
이 기사의 나머지 부분에서는 표를 열별로 나누어 분석합니다.
핸드셰이크: server/discover가 20회 중 20회 먼저 발생
서버의 로그가 첫 번째 이야기를 전했습니다. 모든 실행에서 Claude Code가 서버의 stdin에 작성한 첫 메시지는 initialize가 아니라 항상 ID가 server-discover-probe-1인 server/discover였습니다. 두 번째 실행부터는 서버도 각 요청의 _meta를 기록했으며, 이 프로브는 19번의 모든 실행에서 다음과 같이 나타났습니다 (축약됨; _meta에는 클라이언트의 기능도 포함됩니다):
18번의 실행에서 서버는 해당 메서드를 알지 못하여 JSON-RPC 오류 -32601로 응답했습니다. 이후 Claude Code는 protocolVersion: "2025-11-25"를 포함한 initialize 메시지를 전송했고, 이어서 notifications/initialized와 tools/list가 이어졌으며, 디버그 로그에는 `
두 가지 동작 모두 2026-07-28 사양을 따릅니다. 일치하지 않는 부분은 Claude Code가 언제 프로브(probe)하는지에 대한 자체 설명입니다. MCP_PROTOCOL_NEGOTIATION이 설정되지 않았습니다. 이는 셸 환경이나 ~/.claude/settings.json의 env 블록에 없었으며, 해당 기계에는 관리되는 설정 파일이 없습니다. 문서에 따르면, 표준 입출력(stdio) 서버는
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기