Skip to content

Claude Code MCP 리소스와 Tool Search — `@server:protocol://path` 멘션·`/mcp__server__prompt` 프롬프트·도구 지연 로딩·`alwaysLoad`·관리형 MCP

Published: at 04:58 PM

들어가며

35차시까지 우리는 MCP 서버를 붙이고(32·33차시), 관리하고(34차시), 설정했다(35차시). 32차시에서 전송 방식과 claude mcp add를, 33차시에서 인기 서버를 인증 패턴별로, 34차시에서 스코프·우선순위·승인 게이트·OAuth를, 35차시에서 .mcp.json을 손으로 쓰고 ${VAR}로 시크릿을 분리하는 법을 다뤘다. 즉 연결의 기계장치는 끝났다.

그런데 한 가지를 줄곧 한쪽으로만 봤다. 32~35차시 내내 “서버가 주는 것”은 사실상 도구(tools) 하나였다 — GitHub의 PR 생성, DB의 쿼리 실행, Sentry의 에러 조회. 하지만 MCP 표준에서 서버가 노출하는 능력은 셋이다.

tools(도구) · resources(리소스) · prompts(프롬프트).

36차시는 두 갈래다. 먼저 붙인 뒤 잘 쓰는 법 — 지금까지 안 본 나머지 둘, 리소스를 @로 참조하는 법과 프롬프트를 / 명령으로 실행하는 법. 그다음이 이 차시의 심장이다.

연결된 서버는 공짜가 아니다. 35차시까지 우리는 서버를 자꾸 붙이라고만 했지만, 서버 하나하나의 도구 이름과 서버 instructions가 매 세션 컨텍스트에 로드된다. 서버가 많아질수록 정작 대화에 쓸 컨텍스트가 줄어든다. 이 비용 곡선을 꺾는 것이 Tool Search이고, 그 미세 조정 손잡이가 35차시에서 미뤄 둔 alwaysLoad다.

마지막으로 시점을 한 단계 올린다 — 개인이 아니라 조직이 MCP를 통제하는 관리형(managed) 설정. 이걸로 섹션 7(MCP 서버 연동)이 끝난다.

MCP 서버가 주는 세 가지 — tools·resources·prompts

개념부터 정리한다. MCP 서버는 클라이언트(여기서는 Claude Code)에 세 종류의 능력을 노출한다.

도구는 Claude가 알아서 고른다. 반면 리소스와 프롬프트는 내가 명시적으로 꺼내 쓴다 — 어떤 데이터를 볼지, 어떤 명령을 실행할지 사용자가 지목하는 것이다. 이 차시의 앞 절반이 바로 그 “꺼내 쓰기”다.

@ 멘션으로 리소스 참조하기

리소스는 파일을 참조하듯 @ 멘션으로 끌어온다. 23차시에서 @로 프로젝트 파일을 참조했던 그 동작이, 연결된 MCP 서버의 리소스로 확장된다고 보면 된다.

목록 — @를 치면 파일과 나란히 뜬다

프롬프트에서 @를 입력하면, 연결된 모든 MCP 서버의 리소스가 자동완성 메뉴에 파일과 나란히 나타난다. 따로 명령을 외울 필요 없이, 파일을 고르던 그 자리에서 서버 리소스를 고른다.

참조 — @server:protocol://resource/path

특정 리소스는 다음 형식으로 가리킨다.

@server:protocol://resource/path

server는 34차시에서 정한 서버 이름, 그 뒤가 서버가 노출하는 리소스의 프로토콜과 경로다. 실제 프롬프트에 섞어 쓰면 이렇다.

Can you analyze @github:issue://123 and suggest a fix?
Please review the API documentation at @docs:file://api/authentication

앞쪽은 github 서버의 issue://123(123번 이슈)을, 뒤쪽은 docs 서버의 file://api/authentication(인증 문서)을 끌어온다.

여러 개를 한 번에

한 프롬프트에 리소스를 여러 개 섞을 수 있다. 서로 다른 서버의 리소스를 나란히 놓고 비교시키는 식이다.

Compare @postgres:schema://users with @docs:file://database/user-model

postgres 서버의 users 테이블 스키마와 docs 서버의 데이터 모델 문서를 함께 읽혀 둘이 어긋나는 곳을 찾게 한다.

알아 둘 점

MCP 프롬프트를 슬래시 명령으로

리소스가 “읽어 들이는 데이터”라면, 프롬프트는 서버가 미리 정의해 둔 명령이다. 24~26차시에서 만든 커스텀 스킬이 /로 떴던 것처럼, MCP 서버가 노출하는 프롬프트도 슬래시 명령으로 나타난다.

발견 — /를 치면 보인다

프롬프트에서 /를 입력하면, MCP 서버가 제공하는 것까지 포함해 모든 명령이 뜬다. MCP 프롬프트는 다음 형식이다.

/mcp__servername__promptname

mcp__ 접두사 뒤에 서버 이름, __, 프롬프트 이름이 붙는다. 서버·프롬프트 이름은 정규화되어 공백이 언더스코어로 바뀐다.

실행 — 인자 없이, 또는 인자와 함께

인자 없이 그대로 실행하거나,

/mcp__github__list_prs

프롬프트가 인자를 받으면 명령 뒤에 공백으로 구분해 넘긴다.

/mcp__github__pr_review 456
/mcp__jira__create_issue "Bug in login flow" high

인자는 그 프롬프트가 정의한 파라미터에 따라 파싱되고, 실행 결과는 대화에 직접 주입된다. 프롬프트 목록은 연결된 서버에서 동적으로 발견되므로, 서버가 프롬프트를 추가하면 재연결 없이 새 명령이 뜬다.

정리하면 — 도구는 Claude가 고르고, 리소스(@)와 프롬프트(/)는 내가 지목한다. 이 셋이 MCP 서버가 노출하는 능력의 전부다. 32~35차시가 도구를, 이 절이 나머지 둘을 채웠다. 이제 그 모든 능력에 깔린 비용 문제로 넘어간다.

핵심 — Tool Search로 컨텍스트를 지킨다

문제 — 서버를 붙일수록 컨텍스트가 줄어든다

먼저 33차시가 권한 것을 떠올린다 — 필요한 서버를 자꾸 붙여라. GitHub, Sentry, PostgreSQL, Notion, Slack…. 그런데 공식 문서가 콕 짚는 비용이 있다.

연결된 서버 하나하나는 Claude의 컨텍스트 윈도우를 얼마간 차지한다 — 서버의 도구 이름과 서버 instructions가 매 세션 로드되기 때문이다.

즉 서버를 10개 붙이면, 그 10개의 도구 정의가 모든 대화의 시작점에서 컨텍스트를 먹고 들어간다. 정작 코드를 읽고 작업할 공간이 그만큼 줄어든다. 35차시까지의 “많이 붙여라”와 이 비용은 정면으로 충돌한다.

기본 동작 — 도구를 필요할 때까지 미룬다

이 충돌을 푸는 것이 Tool Search이고, 기본값으로 켜져 있다. 작동 방식은 한 줄로 요약된다.

도구 정의를 Claude가 필요로 할 때까지 미룬다(defer). 세션 시작 때는 도구 이름과 서버 instructions만 로드하고, 작업이 특정 도구를 필요로 하면 Claude가 검색 도구로 그것을 찾아 불러온다.

그래서 실제로 쓰는 도구만 컨텍스트에 들어온다. 서버를 더 붙여도 컨텍스트 영향이 최소로 유지된다. 사용자 입장에서 MCP 도구는 전과 똑같이 동작한다 — Claude가 필요한 순간 알아서 검색해 쓰기 때문이다. 덕분에 Claude Code는 서버당 도구 개수에 고정 상한을 두지 않는다. 실질적 한계는 컨텍스트 예산뿐이다.

34·35차시에서 본 “백그라운드로 연결 중인 서버를 기다리는” 동작을 기억한다면 — Tool Search가 켜져 있을 때 그 대기는 ToolSearch 호출 안에서 일어난다. (Tool Search가 꺼진 구성에서는 WaitForMcpServers라는 별도 도구를 쓴다.)

ENABLE_TOOL_SEARCH — 다섯 가지 동작

동작은 ENABLE_TOOL_SEARCH 환경 변수로 제어한다.

값동작
(미설정)모든 MCP 도구를 미루고 필요 시 로드(기본값). 단 Vertex AI거나 ANTHROPIC_BASE_URL이 비-1차 호스트면 전부 미리 로드로 폴백
true모든 도구를 미룬다. Vertex AI·프록시에서도 beta 헤더를 보낸다. Sonnet 4.5/Opus 4.5 미만의 Vertex 모델이나 tool_reference 미지원 프록시에선 요청이 실패
auto임계값 모드 — 도구가 컨텍스트 윈도우의 10% 안에 들면 미리 로드하고, 넘치는 부분만 미룬다
auto:N임계값 모드, 사용자 지정 퍼센트(N은 0~100). 예: auto:5는 5%
false모든 도구를 미리 로드, 지연 없음
# 5% 임계값으로 켜기
ENABLE_TOOL_SEARCH=auto:5 claude

# 완전히 끄기
ENABLE_TOOL_SEARCH=false claude

매번 환경 변수를 주기 번거로우면, 43차시에서 다룰 settings.json의 env 필드에 박아 둘 수 있다. 또 검색 동작 자체가 거슬리면 ToolSearch 도구만 콕 집어 끌 수도 있다.

{
  "permissions": {
    "deny": ["ToolSearch"]
  }
}

모델 요구사항 — Haiku는 안 된다

한 가지 못 박아 둘 제약. Tool Search는 tool_reference 블록을 지원하는 모델에서만 동작한다.

alwaysLoad — 35차시에서 미룬 손잡이

Tool Search는 모든 도구를 미루는 게 기본이다. 그런데 어떤 서버의 도구는 매 턴 반드시 필요해서 검색 단계를 거치는 것조차 아깝다. 이때 그 서버만 지연에서 빼는 것이 alwaysLoad다 — 35차시에서 “Tool Search 관련이라 36차시로 미룬다”고 한 바로 그 필드다.

서버 설정에 alwaysLoad: true를 주면, 그 서버의 모든 도구가 ENABLE_TOOL_SEARCH 설정과 무관하게 세션 시작 시 컨텍스트로 로드된다.

{
  "mcpServers": {
    "core-tools": {
      "type": "http",
      "url": "https://mcp.example.com/mcp",
      "alwaysLoad": true
    }
  }
}

위 예시는 core-tools 한 서버만 항상 로드하고, 나머지 서버는 그대로 지연시킨다. 쓸 때 지켜야 할 선이 있다.

한 가지 부작용도 알아 둔다. alwaysLoad: true는 그 서버가 연결될 때까지 startup을 막는다(표준 5초 connect timeout 한도 내에서). MCP 서버 연결은 평소 non-blocking인데도 그렇다 — 첫 프롬프트를 만들 시점에 그 서버의 도구가 이미 있어야 하기 때문이다. 다른 서버들은 그동안에도 백그라운드로 계속 연결된다.

서버를 만드는 쪽이라면 — Tool Search가 켜져 있을 때 서버 instructions 필드가 한층 중요해진다. “내 도구가 어떤 작업을 다루고, Claude가 언제 그것을 검색해야 하는지”를 적어 두면, 스킬(24차시)이 동작하듯 Claude가 적절한 때에 도구를 찾는다. 단 도구 설명과 서버 instructions는 각각 2KB에서 잘리므로 핵심을 앞쪽에 간결히 둔다.

조직 단위 통제 — 관리형(managed) MCP

여기서 시점을 바꾼다. 지금까지는 개발자 개인이 자기 서버를 붙이고 컨텍스트를 관리하는 이야기였다. 마지막 축은 조직이다 — 기본적으로 누구나 원하는 MCP 서버를 붙일 수 있는데, 회사가 이를 통제하고 싶을 때 쓰는 것이 관리형 MCP 설정이다. 59차시(팀 표준 공유)의 MCP 판이라고 보면 된다.

managed-mcp.json — 독점 제어

관리자가 시스템 경로에 managed-mcp.json을 배포하면, Claude Code는 그 파일이 정의한 서버만 로드한다. 사용자는 다른 MCP 서버를 추가·수정·사용할 수 없다 — 플러그인이 제공하는 서버까지 포함해서다. (claude.ai 커넥터도 함께 억제되며, 별도 설정으로만 다시 허용된다.) 이른바 *독점 제어(exclusive control)*다.

파일 형식은 35차시에서 배운 프로젝트 .mcp.json과 완전히 똑같다 — 최상위 mcpServers 객체 아래 “이름 → 엔트리”.

{
  "mcpServers": {
    "github": {
      "type": "http",
      "url": "https://api.githubcopilot.com/mcp/"
    },
    "company-internal": {
      "type": "stdio",
      "command": "/usr/local/bin/company-mcp-server",
      "args": ["--config", "/etc/company/mcp-config.json"],
      "env": {
        "COMPANY_API_URL": "https://internal.example.com"
      }
    }
  }
}

배포 경로는 플랫폼마다 정해져 있다.

플랫폼경로
macOS/Library/Application Support/ClaudeCode/managed-mcp.json
Linux·WSL/etc/claude-code/managed-mcp.json
WindowsC:\Program Files\ClaudeCode\managed-mcp.json

이 파일은 독립 파일이라 서버 관리형 설정으로는 못 보내고, 관리자 권한으로 시스템 경로에 쓸 수 있는 수단 — MDM, 그룹 정책(GPO), Intune, Jamf, 또는 각자의 fleet 관리 도구 — 로 배포한다.

시크릿 주의 — 이 파일은 머신의 누구나 읽을 수 있다. 그래서 env 블록에 API 키 같은 자격증명을 넣으면 안 된다. 35차시의 ${VAR} 확장으로 각 사용자 환경에서 읽거나, 34차시의 OAuth·per-user 헤더, 또는 headersHelper로 연결 시점에 자격증명을 생성한다. 커밋되는 .mcp.json에 평문 시크릿 금지라던 원칙이 여기서도 그대로다.

적용됐는지는 두 가지로 확인한다 — claude mcp list에 managed 서버만 보이면 읽히고 있는 것이고, claude mcp add로 새 서버를 붙여 보면 거부된다.

MCP를 통째로 끄려면 빈 서버 맵을 배포한다.

{
  "mcpServers": {}
}

이러면 /mcp에 서버가 하나도 안 뜨고, 사용자가 이전에 설정해 둔 서버도 다음 세션부터 조용히 로드가 멈춘다(정책 때문이라는 경고는 없다).

allowedMcpServers / deniedMcpServers — 허용과 차단

전부 고정 배포까지는 과하고 “이 목록만 허용” 또는 “이것만 차단”이 필요할 때, 허용/차단 목록을 쓴다. 단 이건 레지스트리가 아니다 — 서버는 사용자·플러그인·managed-mcp.json 중 하나가 먼저 추가해야 하고, 그제서야 목록이 그 서버에 적용된다.

각 엔트리는 키 하나짜리 객체로, 서버를 무엇으로 식별할지 정한다.

키매칭 대상용도
serverUrl원격 서버 URL(정확 또는 * 와일드카드)HTTP·SSE
serverCommandstdio 서버를 띄우는 명령과 인자(정확 일치)stdio
serverName사용자가 붙인 이름(정확 일치만, 와일드카드 불가)둘 다, 단 아래 주의

설정을 비워 두는 것과 빈 배열로 두는 것은 다르다.

설정미설정빈 배열 []채워짐
allowedMcpServers전부 허용전부 차단매칭하는 것만 허용
deniedMcpServers차단 없음차단 없음매칭하는 것 차단
{
  "allowedMcpServers": [
    { "serverUrl": "https://api.githubcopilot.com/*" },
    { "serverCommand": ["npx", "-y", "@modelcontextprotocol/server-filesystem", "."] }
  ],
  "deniedMcpServers": [
    { "serverName": "dangerous-server" },
    { "serverUrl": "https://*.untrusted.example.com/*" }
  ]
}

serverName만으로 만든 allowlist는 보안 통제가 아니다. 이름은 사용자가 claude mcp add 할 때 임의로 붙이는 라벨이라, 누구나 아무 서버에 github라는 이름을 줄 수 있다. 실제로 어떤 서버가 도는지를 강제하려면 serverCommand나 serverUrl로 못 박아야 한다. URL은 * 와일드카드를 지원하고(https://*.internal.example.com/* 식), 명령은 인자 순서까지 정확히 일치해야 한다.

평가 순서 — denylist가 절대 우선

서버 하나를 로드하기 전에(managed-mcp.json의 서버까지 포함해), Claude Code는 세 단계를 순서대로 거친다.

  1. 병합 — 모든 설정 소스(user·project·local·managed)의 allowlist·denylist를 각각 하나로 합친다.
  2. denylist 검사 — URL·명령·이름 중 하나라도 denylist에 걸리면 차단. 어떤 것도 denylist 매칭을 뒤집지 못한다.
  3. allowlist 검사 — allowedMcpServers가 어디에도 없으면, denylist를 통과한 서버는 전부 로드. 설정돼 있으면 원격 서버는 serverUrl을, stdio 서버는 serverCommand를 매칭해야 한다.

여기서 관리자가 알아야 할 핵심이 allowManagedMcpServersOnly다. 이걸 관리형 설정에서 true로 두면 user·project·local의 allowlist는 무시되고 관리형 allowlist만 적용된다(없으면 사용자가 자기 ~/.claude/settings.json에 allowlist를 더해 정책을 넓힐 수 있다). 단 denylist는 늘 모든 소스에서 병합된다 — 그래서 사용자는 언제나 자기 머신에서 특정 서버를 추가로 차단할 수 있다.

사용자에게는 무엇이 보이나

관리자가 정책을 깔면 사용자는 둘 중 하나를 겪는다 — claude mcp add에서 에러를 보거나, 서버가 말없이 사라지거나.

상황사용자가 보는 것
managed-mcp.json이 있는데 claude mcp addCannot add MCP server: enterprise MCP configuration is active and has exclusive control over MCP servers
denylist에 걸린 서버를 claude mcp addCannot add MCP server "<name>": server is explicitly blocked by enterprise policy
allowlist에 없는 서버를 claude mcp addCannot add MCP server "<name>": not allowed by enterprise policy
기존 서버가 새 정책으로 차단됨/mcp·claude mcp list에서 경고 없이 조용히 사라진다

마지막 경우가 함정이다 — 사용자는 왜 서버가 사라졌는지 신호를 받지 못한다. 그래서 관리자는 새 제한을 적용할 때 어떤 서버가 막히는지 미리 알려야 한다. (조직에서 실제로 어떤 서버가 쓰이는지는 OpenTelemetry export를 설정해 둔 경우 OTEL_LOG_TOOL_DETAILS=1을 더해 추적할 수 있다.)

흔한 함정

  1. 리소스 멘션 형식은 @server:protocol://resource/path다. server는 34차시에서 정한 서버 이름이다. @만 치면 자동완성에 파일과 리소스가 같이 뜨니 외워서 칠 필요는 없다.
  2. 프롬프트 명령은 /mcp__server__prompt다 — 구분자가 __(언더스코어 둘)다. 이름의 공백은 언더스코어로 정규화된다. 인자는 명령 뒤에 공백으로 구분해 넘긴다.
  3. Tool Search는 기본으로 켜져 있다 — false로 끄면 모든 도구가 다시 미리 로드된다. 서버를 많이 붙였는데 컨텍스트가 빠르게 차면, 누가 ENABLE_TOOL_SEARCH=false를 박아 두지 않았는지부터 본다.
  4. Haiku에서는 Tool Search가 동작하지 않는다. tool_reference를 지원하는 모델이 필요하다. Vertex AI는 Sonnet 4.5/Opus 4.5 이상, 그리고 커스텀 ANTHROPIC_BASE_URL 프록시에서는 기본으로 꺼진다.
  5. alwaysLoad는 아껴 쓴다. 매 턴 필요한 소수 서버에만. 남발하면 미리 로드되는 도구가 컨텍스트를 먹어 Tool Search를 켠 의미가 없어진다. v2.1.121 이상에서만 동작한다.
  6. alwaysLoad: true인 서버는 startup을 막는다(5초 connect timeout 한도). 연결이 느린 서버에 걸면 세션 시작이 그만큼 지연된다. 다른 서버는 백그라운드로 계속 붙는다.
  7. managed-mcp.json은 독점 제어다. 배포되면 그 파일의 서버만 남고 사용자·플러그인 서버는 전부 막힌다. 빈 { "mcpServers": {} }는 MCP를 통째로 끈다.
  8. 관리형 파일에도 평문 시크릿을 넣지 않는다. 머신의 누구나 읽을 수 있다 — ${VAR}·OAuth·headersHelper로 per-user 자격증명을 쓴다(34·35차시).
  9. denylist가 allowlist를 이긴다, 늘. 평가는 병합 → denylist → allowlist 순서다. 그리고 denylist는 allowManagedMcpServersOnly와 무관하게 모든 소스에서 병합되니, 사용자는 자기 머신에서 언제든 추가 차단할 수 있다.
  10. serverName만으로 만든 allowlist는 보안이 아니다. 사용자가 이름을 마음대로 정하므로, 실제 강제는 serverUrl·serverCommand로 한다.

정리

핵심 요점

  1. MCP 서버는 세 가지를 노출한다 — tools·resources·prompts. 32~35차시가 도구를 다뤘고, 36차시가 나머지 둘을 채웠다. 도구는 Claude가 고르고, 리소스와 프롬프트는 내가 지목한다.
  2. 리소스는 @server:protocol://resource/path로 참조한다. @를 치면 자동완성에 파일과 나란히 뜨고, 참조하면 자동으로 fetch되어 attachment로 붙는다. 한 프롬프트에 여러 개를 섞어 비교시킬 수 있다.
  3. 프롬프트는 /mcp__servername__promptname 슬래시 명령으로 실행한다. 인자는 공백으로 구분해 넘기고, 연결된 서버에서 동적으로 발견된다.
  4. 차시의 심장은 Tool Search다 — 연결된 서버는 매 세션 컨텍스트를 먹는데, Tool Search는 도구 정의를 필요할 때까지 미뤄 그 비용을 낮춘다. 기본으로 켜져 있고, ENABLE_TOOL_SEARCH(미설정·true·auto·auto:N·false)로 제어하며, tool_reference를 지원하는 모델이 필요하다(Haiku 불가).
  5. alwaysLoad는 35차시가 미룬 손잡이다 — 매 턴 필요한 소수 서버만 지연에서 빼 항상 로드한다(모든 타입·v2.1.121+, 개별 도구는 _meta의 anthropic/alwaysLoad). 단 startup을 막으니 아껴 쓴다.
  6. 조직은 관리형 MCP로 통제한다 — managed-mcp.json(시스템 경로)으로 승인 세트를 독점 배포하거나 MCP를 끄고, allowedMcpServers·deniedMcpServers로 허용/차단한다. 평가는 병합 → denylist → allowlist 순서이고, denylist가 절대 우선이다. 차단된 서버는 사용자에게 경고 없이 사라진다.

다음 단계

이로써 섹션 7(MCP 서버 연동)이 끝난다. 32차시의 개념·설치부터 33차시의 인기 서버, 34차시의 관리, 35차시의 .mcp.json 설정, 그리고 이 차시의 리소스·Tool Search·관리형 설정까지 — 외부 도구를 Claude Code에 붙이고, 잘 쓰고, 조직 단위로 통제하는 한 바퀴를 돌았다.

다음 섹션 8(Git 통합 워크플로우), 37차시는 Git 기본 워크플로우다. 지금까지가 Claude Code에 능력을 더하는 이야기였다면, 다음은 그 능력으로 실제 개발 흐름을 돌리는 이야기다 — git status·diff로 변경을 읽고, 변경사항을 분석하고, 안전하게 Git 작업을 진행하는 원칙부터 시작한다.

참고 자료


Previous Post
Claude Code Git 기본 워크플로우 — `git status`·`diff`·`log`로 변경 읽기, 체크포인트(`/rewind`)와 Git의 차이, 그리고 내장 안전 원칙(force push·`--no-verify`·`--amend`·git config)
Next Post
Claude Code .mcp.json 직접 작성과 ${VAR} 환경 변수 확장 — 서버 엔트리 스키마·시크릿 분리·add-json·Claude Desktop 가져오기