들어가며
33차시에서 서버를 붙이는 법을 세 인증 패턴으로 묶었다 — OAuth 브라우저 로그인(Sentry·Notion·Linear), 헤더에 정적 토큰(GitHub), stdio로 자격증명 싣기(PostgreSQL·Airtable). 그런데 그 차시 내내 두 가지를 의도적으로 미뤘다.
- 붙인 서버는 어디에 저장되나? —
--scope이야기는 마지막에 표 하나로만 예고했다. - 붙인 뒤 어떻게 보고·고치고·지우나? —
claude mcp list를 상태 확인용으로만 잠깐 썼다.
34차시는 이 둘을 답한다. 그리고 둘 다 한 개념으로 수렴한다.
서버 관리의 핵심 질문은 하나다 — 이 서버가 어디에 사는가(스코프).
어디에 사느냐가 — 누가 보는지, 팀과 공유되는지, 같은 이름이 겹칠 때 무엇이 이기는지, 팀원이 승인해야 하는지를 전부 결정한다. list/get/remove는 그 위치를 보고 다루는 도구고, project 스코프의 승인 게이트는 위치가 만드는 결과다. OAuth는 위치와는 별개인 또 하나의 축 — 자격증명의 수명주기다.
사실 이 개념은 33차시 첫 줄에서 이미 스쳤다. claude-code-docs를 붙였을 때 출력이 이랬다.
Added HTTP MCP server claude-code-docs with URL: https://code.claude.com/docs/mcp to local config
끝의 to local config — 저게 스코프다. 아무것도 지정하지 않았으니 local 스코프, 즉 “나에게만, 이 프로젝트에서만”으로 저장됐다는 뜻이다. 이 차시는 저 한 단어를 풀어낸다.
지금 뭐가 붙어 있나 — list · get · /mcp
관리의 출발점은 “현재 상태를 보는 것”이다. 세 도구가 있고, 각자 보여 주는 게 다르다.
claude mcp list — 셸에서 전체 + 상태
claude mcp list
등록된 모든 서버를 연결 상태와 함께 한눈에 보여 준다. 셸에서(세션 밖에서) 돌리는 명령이다. 상태 표시값은 33차시에서 정리한 그대로다 — ✓ Connected, ! Needs authentication, ✗ Failed to connect, ✗ Connection error, 그리고 이 차시에서 중요해지는 ⏸ Pending approval.
list는 “무엇이, 어떤 상태인가”까지다. 각 서버가 어느 스코프에 사는지는 보여 주지 않는다. 그건 get의 몫이다.
claude mcp get <name> — 한 서버의 정체
claude mcp get github
특정 서버 하나의 상세를 보여 준다. 관리에서 get이 결정적인 이유는 이 서버가 어느 스코프에 정의돼 있는지를 알려 주기 때문이다. 전송 방식(http/sse/stdio), URL이나 실행 명령, 그리고 OAuth 자격증명이 설정돼 있는지까지 확인된다. 승인 대기 중인 서버는 ⏸ Pending approval로, 거부한 서버는 ✗ Rejected로 표시된다.
“같은 이름 서버가 여러 군데 있는 것 같은데 지금 쓰이는 건 어느 거지?” 싶을 때 가장 먼저 치는 명령이 get이다.
/mcp — 세션 안의 실시간 패널
> /mcp
list가 셸에서 보는 설정+상태라면, /mcp는 세션 안에서 보는 실시간 패널이자 행동 창구다. 차이가 분명하다.
- 연결된 서버마다 노출하는 도구 수를 옆에 붙여 보여 준다. 도구 기능을 광고하면서 실제 도구가 0개인 서버는 따로 표시해 준다(보통 API 키 같은 필수 환경 변수가 빠진 경우다).
- 서버를 골라 인증(
Authenticate)하거나, 이미 받은 권한을 회수(Clear authentication)하거나, 끊긴 서버를 재연결할 수 있다 — 세션을 나가지 않고.
정리하면, 상태를 빠르게 훑을 땐 list, 한 서버를 파고들 땐 get, 세션 안에서 인증·재연결 같은 행동을 할 땐 /mcp다.
claude mcp remove <name> — 지우기, 그리고 스코프 충돌
claude mcp remove github
서버를 제거한다. 그런데 여기서 스코프가 처음으로 발을 건다. 같은 이름이 두 개 이상의 스코프에 있으면 remove는 exists in multiple scopes라고 알리며 멈춘다 — 어느 사본을 지울지 모르기 때문이다. 이때는 --scope로 짚어 준다.
claude mcp remove claude-code-docs --scope local
안 쓰는 서버는 그때그때 지우는 게 좋다. 33차시에서 봤듯 연결된 서버마다 도구 이름과 서버 instruction이 매 세션 컨텍스트에 로드되기 때문이다. (서버가 많아질 때의 근본 해법인 Tool Search는 36차시에서 다룬다.)
핵심 — 서버는 어디에 사는가 (세 스코프)
이제 이 차시의 심장이다. Claude Code의 MCP 서버는 세 스코프 중 하나에 산다.
| 스코프 | 로드되는 곳 | 팀과 공유 | 저장 위치 |
|---|---|---|---|
| local (기본값) | 현재 프로젝트만 | 안 됨 | ~/.claude.json의 이 프로젝트 항목 아래 |
| project | 현재 프로젝트만 | 됨 (버전 관리로) | 프로젝트 루트의 .mcp.json |
| user | 내 모든 프로젝트 | 안 됨 | ~/.claude.json의 최상위 mcpServers |
세 줄로 요약하면 이렇다.
- local — 나만, 이 프로젝트만.
--scope를 안 주면 이게 된다. 실험용 서버, 나만 쓰는 개인 도구, 그리고 버전 관리에 올리고 싶지 않은 자격증명이 든 서버. - project — 팀 전체, 이 프로젝트.
.mcp.json에 저장되고 git에 커밋해 공유한다. 팀원 모두가 같은 MCP 도구를 갖게 하는 유일한 방법. - user — 나만, 모든 프로젝트. 어느 저장소를 열든 따라오는 개인 도구(예: 내 Sentry, 내 Linear).
local과 user가 같은 파일에 산다
표에서 눈여겨볼 점 — local과 user는 **둘 다 ~/.claude.json**에 저장된다. 갈리는 건 파일 안의 위치다.
-
local은
projects아래 해당 프로젝트 경로에 중첩된다. 그래서 다른 프로젝트를 열면 안 보인다.{ "projects": { "/path/to/your/project": { "mcpServers": { "stripe": { "type": "http", "url": "https://mcp.stripe.com" } } } } } -
user는 파일 최상위의
mcpServers에 놓인다. 프로젝트에 묶이지 않으니 어디서나 로드된다.
project만 별도 파일(.mcp.json)에 사는데, 이건 팀과 공유해야 해서 git에 커밋할 수 있어야 하기 때문이다.
헷갈리기 쉬운 이름 — MCP의 “local 스코프”는 홈 디렉터리의
~/.claude.json에 저장된다. 이름이 비슷한.claude/settings.local.json(프로젝트 디렉터리의 개인 설정 파일)과는 다른 파일이다. 또 예전 버전에서는 지금의 local을project, 지금의 user를global이라 불렀으니 옛 문서를 볼 때 주의한다. (Windows에서~/.claude.json은%USERPROFILE%\.claude.json로,CLAUDE_CONFIG_DIR을 설정했다면 그 디렉터리 안의.claude.json으로 풀린다.)
어느 스코프에 붙일까
33차시 끝에서 실전 기준만 표로 예고했었다. 이제 결정 규칙으로 굳힌다.
- 나만 쓰고, 실험 중이거나 자격증명이 명령에 박혀 있다 → local (기본값 그대로)
- 모든 프로젝트에서 나만 쓰는 개인 도구 → user
- 팀 전원이 공유해야 한다 → project (
.mcp.json커밋)
여기서 33차시의 보안 원칙이 다시 걸린다. GitHub PAT나 DB 비밀번호처럼 평문 자격증명이 명령에 들어가는 서버를 project 스코프에 그대로 넣으면 안 된다 — .mcp.json은 git에 올라가므로 시크릿이 저장소에 노출된다. 팀 공유가 필요하면 자격증명은 환경 변수로 분리하고 .mcp.json엔 변수 이름만 둔다(이 분리 방법은 35차시에서 깊게 다룬다). 그때까지는 — 시크릿이 든 서버는 local 또는 user가 안전한 기본값이다.
이름이 겹치면 뭐가 이기나 — 우선순위
스코프가 셋이면 자연히 따라오는 질문 — 같은 이름의 서버가 두 스코프에 있으면 어떻게 되나?
규칙은 명확하다. Claude Code는 그 서버에 단 한 번만 연결하고, 가장 높은 우선순위의 정의 하나만 쓴다. 그리고 그 정의를 통째로 쓴다 — 스코프 간에 필드를 섞지 않는다.
우선순위는 위에서 아래로 이렇다.
- local 스코프 (가장 높음)
- project 스코프
- user 스코프
- 플러그인이 제공하는 서버
- claude.ai 커넥터 (가장 낮음)
여기서 한 가지 미묘함 — 세 스코프(local/project/user)는 이름으로 중복을 따진다. 반면 플러그인과 claude.ai 커넥터는 엔드포인트(같은 URL이나 명령)로 따진다. 그래서 위 13은 “이름이 같으면 충돌”, 45는 “가리키는 곳이 같으면 충돌”이다.
이 우선순위가 곧 실전 기법이다
local > project라는 순서를 뒤집어 읽으면 유용한 패턴이 나온다 — 내 local 서버가 팀의 project 서버를 덮어쓴다.
예를 들어 팀의 .mcp.json에 공용 DB를 가리키는 db 서버가 project 스코프로 커밋돼 있다고 하자. 나는 잠깐 다른 DSN(내 로컬 복제본)으로 작업하고 싶다. .mcp.json을 건드릴 필요 없다 — 같은 이름 db를 local 스코프로 추가하면 된다. local이 우선이라 내 세션에선 내 정의가 이기고, .mcp.json은 그대로니 다른 팀원에겐 영향이 없다. 작업이 끝나면 claude mcp remove db --scope local로 걷어 내면 팀 정의가 다시 드러난다.
“분명히
.mcp.json에 박아 뒀는데 다른 서버로 연결된다” 싶으면 십중팔구 더 높은 스코프(local 또는 user)에 같은 이름이 있는 것이다.claude mcp get <name>으로 지금 이기고 있는 정의가 어느 스코프인지부터 확인한다.
스코프는 못 바꾼다 — 옮기려면 remove + re-add
한 가지 제약을 분명히 짚는다. 서버의 스코프는 추가하는 시점에 고정된다. “이 서버를 local에서 user로 올려줘” 같은 변경 명령은 없다. 옮기려면 지우고 다시 붙인다.
# local에 있던 걸 걷어 내고
claude mcp remove claude-code-docs --scope local
# user 스코프로 다시 붙인다 (이제 모든 프로젝트에서 로드)
claude mcp add --scope user --transport http claude-code-docs https://code.claude.com/docs/mcp
team 공유로 올릴 때도 같다 — --scope project로 다시 붙이면 .mcp.json이 생성/갱신된다. 그리고 그 순간, project 스코프에만 있는 또 하나의 절차가 작동하기 시작한다 — 승인이다.
공유의 대가 — project 스코프의 승인 게이트
project 스코프는 강력하다. .mcp.json을 커밋하면 저장소를 클론하는 모든 팀원이 같은 서버를 갖는다. 그런데 바로 그 점이 위험하기도 하다 — 클론한 저장소의 .mcp.json이, 내 동의 없이 내 컴퓨터에서 프로세스를 띄우거나 외부 서버에 연결할 수 있다는 뜻이기 때문이다.
그래서 Claude Code는 안전장치를 둔다. project 스코프 서버(.mcp.json 출신)는 처음 쓸 때 승인을 요구한다.
흐름은 이렇다.
.mcp.json이 있는 프로젝트에서 세션을 처음 열면, Claude Code가 project 서버를 발견하고 승인 프롬프트를 띄운다.- 승인하면 연결된다. 그 순간 놓쳤다면 세션 안에서
/mcp로 나중에 승인할 수도 있다. - 승인 전까지 그 서버는
claude mcp list에서⏸ Pending approval로 떠 있다.
거부하면 어떻게 되나? claude mcp get에서 ✗ Rejected로 표시되고, 그 서버는 로드되지 않는다. 그런데 나중에 마음이 바뀌거나, .mcp.json을 고쳤는데 예전에 거부한 탓에 변경이 안 먹는 경우가 있다. 이때 쓰는 명령이 있다.
claude mcp reset-project-choices
이 프로젝트의 승인/거부 기록을 초기화한다. 다음 세션에서 project 서버들을 다시 처음부터 승인할지 묻는다.
이 승인 게이트는 강의 전체를 관통하는 보안 사고의 일부다. 30차시의 PreToolUse Hook(위험한 명령 차단), 33차시의 “project 스코프에 평문 시크릿 금지”와 같은 줄기 — 코드와 설정은 신뢰할 수 있는 출처에서만 실행한다는 원칙이다. 외부 콘텐츠(이슈 본문 등)를 가져오는 서버는 prompt injection 위험이 있으니, 승인할 서버의 출처를 반드시 확인한다.
OAuth 인증 설정 — 자격증명의 수명주기
지금까지가 서버의 위치(스코프)였다면, OAuth는 별개의 축 — 자격증명이다. 33차시에서 OAuth 브라우저 로그인을 “패턴 1”로 다뤘으니, 여기서는 관리 관점에서 빈칸을 채운다.
기본 흐름과 토큰 수명주기
서버가 401 Unauthorized나 403 Forbidden을 반환하면 Claude Code는 그 서버를 “인증 필요”로 표시한다. 그러면 /mcp → 서버 선택 → Authenticate → 브라우저 로그인으로 마무리한다(패턴 1).
여기서 관리에 중요한 두 가지.
- 토큰은 안전하게 저장되고 자동으로 갱신된다. 한 번 로그인하면 매번 다시 로그인할 필요가 없다.
- 권한을 회수하려면
/mcp메뉴의Clear authentication을 쓴다. 토큰을 잘못 받았거나, 다른 계정으로 다시 로그인하고 싶거나, 권한을 끊고 싶을 때 — 여기서 지우고 다시Authenticate하면 된다.
브라우저가 자동으로 안 열리면 터미널에 표시된 URL을 복사해 직접 연다. 인증 후 콜백 단계에서 연결 오류가 나면, 브라우저 주소창의 전체 콜백 URL을 복사해 Claude Code가 띄운 입력 프롬프트에 붙여 넣는다.
그리고 33차시의 함정 하나를 다시 못 박는다 — --header로 Authorization을 직접 박아 둔 경우, 서버가 그 헤더를 거부하면 OAuth로 폴백하지 않고 곧장 failed로 떨어진다. “토큰을 줬는데 거부당했다”는 건 로그인 문제가 아니라 설정 오류이기 때문이다. GitHub 연결이 failed면 토큰을 의심한다.
자동 OAuth가 안 될 때 — 사전 등록 자격증명
대부분의 SaaS 서버는 위 흐름으로 끝난다. 추가로 OAuth 앱을 만들 필요가 없는 건 동적 클라이언트 등록(DCR, Dynamic Client Registration) 덕분이다 — Claude Code가 연결 시점에 클라이언트를 자동 등록한다(33차시에서 Linear가 이 방식이었다).
그런데 일부 서버는 DCR을 지원하지 않는다. 그런 서버에 붙이면 이런 오류가 난다.
Incompatible auth server: does not support dynamic client registration
이때는 내가 직접 OAuth 앱을 등록하고 자격증명을 넘겨야 한다.
- 서버의 개발자 포털에서 OAuth 앱을 만들고 client ID와 client secret을 받는다.
- 자격증명과 함께 서버를 추가한다.
claude mcp add --transport http \
--client-id your-client-id --client-secret --callback-port 8080 \
my-server https://mcp.example.com/mcp
플래그 하나씩 보면.
--client-id— 발급받은 client ID.--client-secret— 값 없이 준다. 그러면 secret을 마스킹된 입력으로 따로 묻는다. 셸 히스토리에 평문으로 남지 않게 하려는 설계다. (CI처럼 대화형 입력이 안 되는 곳에서는MCP_CLIENT_SECRET=your-secret claude mcp add ...로 환경 변수로 넘겨 프롬프트를 건너뛴다.)--callback-port— 아래에서 따로 설명한다.
secret은 어디에 저장되나 — 설정 파일이 아니라 시스템 키체인(macOS)이나 별도 자격증명 파일에 저장된다.
.mcp.json이나~/.claude.json에 평문으로 박히지 않는다는 뜻이다. 또 secret이 없는 공개(public) 클라이언트라면--client-secret없이--client-id만 준다. 이 플래그들은 HTTP·SSE 전송에만 적용되고 stdio 서버엔 효과가 없다. 설정이 제대로 됐는지는claude mcp get <name>으로 확인한다.
콜백 포트 고정 — --callback-port
OAuth 로그인이 끝나면 인증 서버가 http://localhost:PORT/callback으로 브라우저를 되돌려 보낸다. 기본적으로 Claude Code는 이 PORT를 매번 비어 있는 포트 중 무작위로 고른다.
문제는 — 일부 서버가 redirect URI를 사전에 등록하라고 요구한다는 점이다. 포트가 매번 바뀌면 등록해 둔 URI와 안 맞는다. 그래서 포트를 고정하는 플래그가 --callback-port다.
# DCR은 되지만 redirect URI를 미리 등록해야 하는 서버
claude mcp add --transport http \
--callback-port 8080 \
my-server https://mcp.example.com/mcp
--callback-port는 단독으로(DCR과 함께) 쓸 수도, --client-id와 함께(사전 등록 자격증명과 함께) 쓸 수도 있다. 어느 쪽이든 등록한 redirect URI의 포트와 같은 숫자를 주면 된다.
OAuth 스코프를 보안팀이 승인한 최소 범위로 고정하는
oauth.scopes는 33차시(Slack)에서 다뤘다 —.mcp.json에 공백으로 구분한 단일 문자열로 박으면 서버가 광고하는 스코프보다 우선한다. 인증의 범위를 좁히는 이 설정과, 인증의 흐름을 다루는 이번 절을 같이 쓰면 OAuth를 양쪽에서 통제할 수 있다.
흔한 함정
- 스코프는
get으로 본다,list로 안 보인다 —list는 상태까지, 어느 스코프에 사는지는claude mcp get <name>이 알려 준다. “지금 어느 정의가 이기나”가 궁금하면get. - local과 user는 같은
~/.claude.json에 산다 — 갈리는 건 파일 안의 위치다(local은 프로젝트 경로 아래, user는 최상위). project만 별도의.mcp.json. - MCP의 local 스코프 ≠
.claude/settings.local.json— 전자는 홈의~/.claude.json, 후자는 프로젝트의 개인 설정 파일. 이름만 비슷한 다른 파일. - 이름이 겹치면 통째로 하나만 이긴다 — local > project > user > 플러그인 > 커넥터. 필드 병합은 없다. 안 맞는 정의가 잡히면 더 높은 스코프에 같은 이름이 있는지부터 의심.
- 스코프는 추가 시점에 고정 — 옮기려면
remove --scope X후add --scope Y. 변경 명령은 없다. remove가exists in multiple scopes라고 멈추면 —--scope로 어느 사본을 지울지 짚어 준다.- project 서버는 처음에 승인해야 쓴다 —
⏸ Pending approval은 정상 신호다. 거부했거나.mcp.json변경이 안 먹으면claude mcp reset-project-choices. project스코프에 평문 시크릿 금지 —.mcp.json은 git에 올라간다. 시크릿이 든 서버는 local/user에 두거나, 환경 변수로 분리(35차시).--client-secret은 값 없이 준다 — 그러면 마스킹 입력으로 따로 묻는다. 셸 히스토리에 평문이 남지 않는다. CI에선MCP_CLIENT_SECRET환경 변수.- DCR 미지원 서버만
--client-id가 필요하다 —Incompatible auth server: does not support dynamic client registration오류가 그 신호다. 대부분의 서버는/mcp→Authenticate로 끝난다. - redirect URI를 사전 등록하는 서버는
--callback-port로 포트 고정 — 기본은 무작위 포트라 등록된 URI와 안 맞는다. - OAuth는 HTTP(·SSE) 전용 —
--client-id/--client-secret/--callback-port는 stdio 서버에 효과가 없다.
정리
핵심 요점
- 관리의 중심 질문은 “서버가 어디에 사는가(스코프)“다 — 위치가 가시성·공유·우선순위·승인을 전부 결정한다.
- 보는 도구 셋 —
claude mcp list(셸, 전체+상태),claude mcp get(한 서버의 스코프·전송·OAuth 설정), 세션 안/mcp(도구 수·인증·재연결). - 세 스코프 — local(기본,
~/.claude.json프로젝트 항목, 나만/이 프로젝트), project(.mcp.json, git 커밋해 팀 공유), user(~/.claude.json최상위, 나만/모든 프로젝트). - 이름이 겹치면 최상위 하나만 통째로 — local > project > user > 플러그인 > claude.ai 커넥터. 필드 병합 없음. → “local로 팀 서버 임시 덮어쓰기” 기법.
- 스코프는 고정 — 옮기려면 remove 후 re-add.
- project엔 승인 게이트 — 클론한 저장소가 동의 없이 프로세스를 못 띄우게 막는 장치.
⏸ Pending approval→ 승인, 거부는✗ Rejected, 재결정은claude mcp reset-project-choices. - OAuth 수명주기 — 토큰 자동 갱신,
Clear authentication으로 회수. DCR 미지원 서버는--client-id·--client-secret(마스킹 입력)·--callback-port(고정 포트)로 사전 등록 자격증명을 넘긴다. secret은 키체인에 저장.
다음 단계
다음 35차시는 .mcp.json 설정과 환경 변수 확장이다. 이번 차시에서 두 번 미룬 숙제 — “평문 시크릿을 어떻게 분리하나” — 의 해법이다. .mcp.json을 손으로 작성하는 법, ${VAR}·${VAR:-default} 확장으로 자격증명을 코드에서 떼어 내는 법, Claude Desktop 설정을 가져오는 법을 다룬다. 36차시는 MCP 리소스(@ 멘션)와 Tool Search — 서버가 많아질 때 컨텍스트를 지키는 방법과, 조직 단위로 서버를 통제하는 관리형(managed) MCP 설정까지 본다.
참고 자료
- Claude Code MCP 공식 문서 (전체 레퍼런스)
- MCP 설치 스코프와 우선순위
- 원격 MCP 서버 인증 (OAuth·사전 등록 자격증명·콜백 포트)
- MCP 연결 퀵스타트 (상태 표시·승인·트러블슈팅)
- 관리형 MCP 설정 (조직 단위 통제)
- Claude Code 설정 파일 위치 (local 스코프 vs settings.local.json)
- Claude Code 환경 변수 레퍼런스
- Claude Code Security (MCP 위험 모델·prompt injection)
- Anthropic Directory (검토된 커넥터 목록)
- Model Context Protocol 공식 사이트