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.jsonenv 필드에 박아 둘 수 있다. 또 검색 동작 자체가 거슬리면 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라는 이름을 줄 수 있다. 실제로 어떤 서버가 도는지를 강제하려면 serverCommandserverUrl로 못 박아야 한다. 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+, 개별 도구는 _metaanthropic/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 작업을 진행하는 원칙부터 시작한다.

참고 자료


Next Post
Claude Code .mcp.json 직접 작성과 ${VAR} 환경 변수 확장 — 서버 엔트리 스키마·시크릿 분리·add-json·Claude Desktop 가져오기