Claude Code에 MCP 서버를 연결하면 사내 DB 조회나 사내 API처럼 기본 도구로 할 수 없던 일을 도구로 추가할 수 있습니다. 서버는 claude mcp add 명령 한 줄로 등록하며, 설정을 어디에 저장할지(scope)에 따라 나만 쓰는 서버가 될 수도 있고 git으로 팀과 공유하는 서버가 될 수도 있습니다. 연결된 서버의 도구는 mcp__서버__도구 형식의 이름을 가지며, 이 이름으로 권한 규칙과 hook을 걸어 실행 범위를 제한하고 호출 기록을 남깁니다. 이 글에서는 서버 등록 방법과 scope, 권한 규칙을 정리하고, AI 에이전트와 MCP 개념 정리에서 만든 읽기 전용 디스크 조회 서버를 Claude Code에 연결해 권한과 감사 로그가 어떻게 동작하는지 확인합니다.
1. Claude Code에서 MCP를 쓰는 이유
Claude Code는 파일 읽기와 수정, 셸 명령, 웹 검색 같은 기본 도구를 갖고 있어서 로컬에서 할 수 있는 일은 대부분 직접 처리합니다. 반면 사내 시스템의 데이터나 인증이 필요한 외부 서비스는 기본 도구로 다루기 어렵고, 셸 명령으로 우회하면 토큰을 명령줄에 넣거나 필요 이상의 권한을 줘야 합니다. MCP 서버는 이런 기능을 정해진 입력과 출력을 가진 도구로 감싸서, 연결과 인증은 서버가 맡고 Claude는 도구를 호출만 하게 만듭니다.
| 방식 | 특징 |
| 기본 도구(Bash 등) | 무엇이든 실행할 수 있지만 범위를 좁히기 어려움, 명령 문자열 단위로만 통제 |
| MCP 도구 | 서버 코드가 허용한 기능만 노출, 입력 스키마가 정해져 있고 도구 단위로 권한과 hook을 걸 수 있음 |
MCP의 구조와 명세는 AI 에이전트와 MCP 개념 정리에서, 권한과 hook으로 에이전트의 실행 환경을 설계하는 방법은 하네스 엔지니어링 개념 정리에서 다루었습니다. 이 글은 두 글의 내용을 Claude Code에서 실제로 적용하는 실습편입니다.
2. 서버 추가하기
서버는 claude mcp add로 추가합니다. Claude Code의 옵션은 서버 이름 앞에 쓰고, 로컬에서 실행하는 stdio 서버는 -- 뒤에 실행 명령을 씁니다. -- 를 빼면 서버 명령의 옵션을 Claude Code가 자기 옵션으로 해석하므로 꼭 넣어야 합니다.
# 로컬 프로세스로 실행하는 stdio 서버
claude mcp add [옵션] <이름> -- <명령> [인자...]
claude mcp add --env API_KEY=xxx --transport stdio myserver -- python server.py --port 8080
# 원격 서버는 HTTP로 연결
claude mcp add --transport http notion https://mcp.notion.com/mcp
claude mcp add --transport http secure-api https://api.example.com/mcp \
--header "Authorization: Bearer your-token"
등록한 서버는 다음 명령으로 관리합니다. 대화 중에는 /mcp를 입력하면 서버별 연결 상태와 도구 개수를 보고, 서버를 끄거나 다시 연결할 수 있습니다.
claude mcp list # 서버 목록과 연결 상태
claude mcp get <이름> # 서버 하나의 상세 정보
claude mcp remove <이름> # 서버 삭제
연결된 서버의 도구는 세션을 시작할 때 이름만 먼저 불러오고, 전체 스키마는 Claude가 그 도구를 쓸 때 불러옵니다. 그래서 서버를 여러 개 연결해도 쓰지 않는 도구가 컨텍스트를 크게 차지하지 않습니다.
3. scope와 설정 파일
--scope 옵션은 서버 설정을 어디에 저장할지 정합니다. 같은 이름의 서버가 여러 scope에 있으면 local, project, user 순서로 우선하며, 설정 값을 합치지 않고 우선하는 쪽의 설정을 통째로 씁니다.

같은 이름의 서버가 여러 scope에 있으면 local, project, user 순서로 우선합니다
| scope | 저장 위치와 쓰임새 |
| local (기본값) | ~/.claude.json의 해당 프로젝트 경로 아래, 현재 프로젝트에서 나만 사용 |
| project | 프로젝트 루트의 .mcp.json, git으로 팀과 공유, 처음 사용할 때 승인 필요 |
| user | ~/.claude.json 최상위, 내 모든 프로젝트에서 사용 |
.mcp.json에는 ${VAR} 또는 ${VAR:-기본값} 형식으로 환경 변수를 쓸 수 있어서, 토큰 값은 파일에 적지 않고 각자의 환경 변수로 넘기는 것이 좋습니다. 또 원격 서버의 URL과 헤더에서는 ANTHROPIC_API_KEY, AWS_BEARER_TOKEN_BEDROCK, NPM_TOKEN 같은 자격 증명 변수를 빈 값으로 읽습니다. 다른 사람이 만든 .mcp.json이 내 자격 증명을 자기 서버로 보내지 못하게 막는 장치입니다.
project scope 서버는 대화형 세션에서 처음 쓸 때 승인을 묻습니다. 다만 claude -p 같은 비대화형 실행에서는 승인 화면을 띄울 수 없어 묻지 않고 불러온다고 문서에 적혀 있습니다. CI 파이프라인처럼 -p로 돌리는 환경에서 저장소의 .mcp.json을 그대로 믿고 싶지 않다면, --strict-mcp-config와 --mcp-config로 쓸 서버를 직접 지정하거나 설정의 disabledMcpjsonServers로 특정 서버를 막습니다.
4. 실습: 디스크 조회 서버를 mcp 2.x로 옮기기
AI 에이전트와 MCP 개념 정리에서 만든 서버는 허용된 경로(/, /var, /home)의 디스크 사용률만 조회하는 읽기 전용 서버입니다. 같은 서버를 다시 쓰려고 공식 Python SDK를 새로 설치했더니 버전이 2.3.0으로 올라가 있었고, 기존 코드는 실행되지 않았습니다.
python3 -m venv .venv
.venv/bin/pip install mcp
.venv/bin/python -c "from mcp.server.fastmcp import FastMCP"
No module named 'mcp.server.fastmcp'. This is mcp 2.x, where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import MCPServer) and other APIs changed; see the migration guide at https://py.sdk.modelcontextprotocol.io/v2/migration/#fastmcp-renamed-to-mcpserver or pin 'mcp<2' to keep running v1 code.
마이그레이션 가이드에 따르면 2.x에서 바뀐 점 중 이 서버에 해당하는 것은 세 가지입니다.
| 항목 | 1.x → 2.x |
| 서버 클래스 | mcp.server.fastmcp.FastMCP → mcp.server.mcpserver.MCPServer |
| 실행 | mcp.run() → mcp.run(transport="stdio")처럼 전송 방식을 명시 |
| 도구 오류 | 모델이 보고 대응해야 하는 실패는 ToolError로 반환, 클라이언트의 isError 필드는 is_error로 바뀜 |
바뀐 내용을 반영한 서버 코드입니다. @mcp.tool() 데코레이터와 docstring이 도구 설명이 되는 방식은 그대로이고, 거부 사유에 허용 경로를 함께 적어 모델이 다음에 무엇을 요청해야 하는지 알 수 있게 했습니다.
# disk_server.py: 허용된 경로의 디스크 사용량만 조회하는 읽기 전용 MCP 서버 (mcp 2.x)
import shutil
from mcp.server.mcpserver import MCPServer
from mcp.server.mcpserver.exceptions import ToolError
ALLOWED = {"/", "/var", "/home"} # 조회를 허용할 경로만 명시
mcp = MCPServer("disk-check")
@mcp.tool()
def disk_usage(path: str) -> str:
"""지정한 경로가 속한 파일 시스템의 사용률과 남은 용량을 반환합니다."""
if path not in ALLOWED:
raise ToolError(f"허용되지 않은 경로입니다: {path}. 허용 경로: {sorted(ALLOWED)}")
u = shutil.disk_usage(path)
pct = u.used / (u.used + u.free) * 100 # df의 Use%와 같은 방식
return f"{path}: {pct:.0f}% used, {u.free / 1024**3:.1f}GB free"
if __name__ == "__main__":
mcp.run(transport="stdio")
5. 실습: Claude Code에 등록하고 호출하기
팀과 공유한다고 가정하고 project scope로 등록했습니다. 프로젝트 루트에 .mcp.json이 생기고, 승인 전이라 상태는 Pending approval로 나옵니다.
claude mcp add --scope project disk-check -- .venv/bin/python disk_server.py
cat .mcp.json
claude mcp get disk-check
Added stdio MCP server disk-check with command: .venv/bin/python disk_server.py to project config
{
"mcpServers": {
"disk-check": {
"type": "stdio",
"command": ".venv/bin/python",
"args": [
"disk_server.py"
],
"env": {}
}
}
}
disk-check:
Scope: Project config (shared via .mcp.json)
Status: ⏸ Pending approval (run `claude` to approve)
Type: stdio
Command: .venv/bin/python
Args: disk_server.py
처음에는 도구만 허용하고 바로 "/var 디스크 사용률 알려줘"라고 물었는데, Claude는 MCP 도구를 쓰지 않고 내장 Bash 도구로 df와 du를 실행해 답했습니다. 같은 일을 할 수 있는 도구가 여러 개면 모델은 그중 더 알맞다고 판단한 쪽을 고르기 때문입니다. MCP 도구를 반드시 거치게 하려면 우회 경로를 하네스에서 막아야 하므로, 이후 실습은 --disallowedTools "Bash"로 Bash를 빼고 진행했습니다. 내 PC의 다른 MCP 서버가 섞이지 않도록 --strict-mcp-config로 이 서버만 불러왔습니다.
claude --strict-mcp-config --mcp-config .mcp.json --model haiku --disallowedTools "Bash" \
--allowedTools "mcp__disk-check__disk_usage" -p "/var 디스크 사용률 알려줘"
`/var`의 디스크 사용률은 86%이고, 남은 용량은 132.1GB입니다.
이 값은 `/var`가 속한 파일 시스템 전체 기준입니다. ...
허용되지 않은 /etc를 물으면 서버가 ToolError로 거부하고, 모델은 거부 사유에 적힌 허용 경로를 보고 / 로 다시 조회했습니다. 서버의 허용 목록 덕분에 모델이 어떤 경로를 요청하든 서버 코드가 허용한 범위를 넘지 못합니다.
`/etc` 디스크 사용률은 86% 사용 중, 남은 용량 132.1GB입니다.
다만 이 수치는 `/etc` 전용이 아니라 `/etc`가 속한 루트 파일시스템 전체 값입니다. ... 이 도구는 `/`, `/home`, `/var`만 허용해서 `/etc` 경로를 직접 조회할 수 없었습니다.
6. 권한 규칙과 감사 로그
MCP 도구 호출은 실제로 실행되기까지 여러 단계를 거칩니다. hook이 가장 먼저 실행되고, 그다음 권한 규칙과 권한 모드가 허용 여부를 정하며, 허용되면 서버 코드와 서버 프로세스의 권한이 실제로 할 수 있는 일을 제한합니다.

hook, 권한 규칙, 서버 코드, 서버 계정의 권한을 모두 통과해야 실제로 실행됩니다
권한 규칙에는 도구의 전체 이름을 씁니다. 서버의 모든 도구는 mcp__서버__* 로, 도구 하나는 mcp__서버__도구 로 지정하고, 규칙은 deny, ask, allow 순서로 평가되어 deny에 걸리면 allow가 있어도 막힙니다.
| 규칙 | 의미 |
| mcp__disk-check__disk_usage | disk-check 서버의 disk_usage 도구 하나 |
| mcp__disk-check__* | disk-check 서버의 모든 도구 |
| mcp__github__get_* | github 서버에서 get_으로 시작하는 도구, 조회 도구만 허용할 때 사용 |
| mcp__* (deny) | 모든 MCP 도구 차단, allow 규칙에서는 서버 이름 없는 와일드카드가 무시됨 |
허용 규칙 없이 manual 권한 모드로 실행하면 도구 호출이 거부됩니다. 처음에는 같은 명령이 허용 규칙 없이도 실행되었는데, 제 설정의 기본 권한 모드가 auto여서 분류기가 읽기 전용 호출을 자동으로 승인했기 때문이었습니다. 권한 모드에 따라 결과가 달라지므로 실습 결과를 볼 때는 어떤 모드였는지 함께 확인해야 합니다.
claude --strict-mcp-config --mcp-config .mcp.json --model haiku --disallowedTools "Bash" \
--permission-mode manual -p "/var 디스크 사용률 알려줘"
`/var` 디스크 사용률은 아직 확인하지 못했습니다. `mcp__disk-check__disk_usage` 도구를 호출했는데 권한이 아직 부여되지 않았다는 응답이 왔습니다.
허용 규칙을 매번 옵션으로 넘기지 않으려면 .claude/settings.json의 permissions.allow에 적습니다. 다만 이 파일을 넣고 다시 실행하자 다음 메시지와 함께 규칙이 무시되었습니다. 한 번도 신뢰(trust)하지 않은 작업 폴더의 프로젝트 설정은 허용 규칙을 적용하지 않기 때문에, 저장소를 클론하는 것만으로 권한이 열리지 않습니다. 대화형으로 claude를 한 번 실행해 신뢰를 승인하면 적용됩니다.
Ignoring 1 permissions.allow entry from .claude/settings.json: this workspace has not been trusted. Run Claude Code interactively here once and accept the trust dialog, ...
누가 어떤 인자로 도구를 호출했는지 남기기 위해 PreToolUse hook을 MCP 도구에 걸었습니다. hook의 matcher에도 도구 이름 형식을 그대로 씁니다.
#!/bin/bash
# .claude/hooks/audit-mcp.sh: MCP 도구 호출을 실행 전에 감사 로그로 남기는 PreToolUse hook
jq -c '{time: (now | todate), tool: .tool_name, input: .tool_input}' >> "${CLAUDE_PROJECT_DIR}/mcp-audit.log"
exit 0
{
"permissions": {
"allow": ["mcp__disk-check__disk_usage"]
},
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__disk-check__.*",
"hooks": [
{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/audit-mcp.sh", "args": [] }
]
}
]
}
}
앞의 /var, /etc 실습을 실행한 뒤의 로그입니다. /etc가 거부되고 / 로 다시 조회한 과정이 그대로 남았습니다.
{"time":"2026-10-27T09:27:43Z","tool":"mcp__disk-check__disk_usage","input":{"path":"/var"}}
{"time":"2026-10-27T09:27:54Z","tool":"mcp__disk-check__disk_usage","input":{"path":"/etc"}}
{"time":"2026-10-27T09:27:56Z","tool":"mcp__disk-check__disk_usage","input":{"path":"/"}}
manual 모드에서 권한이 거부된 호출도 로그에 남았습니다. PreToolUse hook이 권한 확인보다 먼저 실행되기 때문입니다. 감사 로그 관점에서는 실행된 호출뿐 아니라 시도했다가 막힌 호출도 기록된다는 점이 오히려 유용합니다. 반대로 실행 결과까지 남기려면 PostToolUse hook을 함께 걸어야 합니다.
7. 운영할 때 확인할 것
| 항목 | 확인할 내용 |
| scope | 팀 공용 서버는 project(.mcp.json), 개인 토큰이 필요한 서버는 local이나 user |
| 비밀 값 | .mcp.json에는 ${VAR}만 적고 실제 토큰은 각자의 환경 변수나 비밀 저장소에서 주입 |
| 비대화형 실행 | -p와 Agent SDK는 project 서버를 묻지 않고 불러오므로 --strict-mcp-config로 서버를 명시 |
| 우회 경로 | 같은 일을 Bash로도 할 수 있으면 모델이 그쪽을 고를 수 있음, 필요하면 Bash 규칙으로 막음 |
| 권한 | allow는 서버 이름을 명시한 도구 단위로, 쓰기 도구는 ask나 deny로 |
| 출력 크기 | 도구 출력이 1만 토큰을 넘으면 경고, 기본 한도는 2만 5천 토큰이며 MAX_MCP_OUTPUT_TOKENS로 조정 |
| 감사 | PreToolUse hook으로 시도를, PostToolUse hook으로 결과를 기록 |
외부에서 받은 MCP 서버는 실행 명령과 패키지를 직접 확인한 뒤 등록하고, 외부 콘텐츠를 읽어 오는 서버는 프롬프트 인젝션의 통로가 될 수 있다는 점도 염두에 둬야 합니다. 서버 자체를 운영할 때의 보안 항목은 AI 에이전트와 MCP 개념 정리의 마지막 절에 정리해 두었습니다.
8. 정리
- claude mcp add로 서버를 등록하며, stdio 서버는 -- 뒤에 실행 명령을, 원격 서버는 --transport http와 URL을 씁니다.
- scope는 local, project, user 순서로 우선하고, 팀 공유는 .mcp.json의 project scope로 합니다.
- Python SDK 2.x는 FastMCP가 MCPServer로 바뀌었고, 모델에 보여 줄 실패는 ToolError로 반환합니다.
- MCP 도구는 mcp__서버__도구 이름으로 권한 규칙과 hook을 걸며, 신뢰하지 않은 작업 폴더의 허용 규칙은 적용되지 않습니다.
- PreToolUse hook은 권한 확인보다 먼저 실행되어 거부된 시도까지 감사 로그에 남깁니다.
참고
'AI > 활용' 카테고리의 다른 글
| [AI] 생성형 AI 업무 활용 시 보안 주의점 정리 (0) | 2026.09.26 |
|---|---|
| [AI] 생성형 AI로 에러 로그 분석하는 프롬프트 정리 (0) | 2026.09.26 |
| [AI] ChatGPT, Gemini, Claude 비교 (0) | 2026.09.25 |