로컬 MCP 서버를 직접 만들고, Claude Code CLI에 연결하기까지의 삽질 기록
MCP(Model Context Protocol)는 AI 모델이 외부 도구나 데이터에 접근할 수 있도록 하는 표준 프로토콜입니다. Claude Code CLI는 MCP 서버를 통해 다양한 도구를 확장할 수 있는데, 이번 글에서는 NestJS로 직접 MCP 서버를 구현하고 Claude Code에 연결하기까지의 과정을 기록합니다.
npm run build
빌드 결과물은 dist/main.js에 생성됩니다. stdio 방식이므로 별도의 포트나 HTTP 서버가 필요 없고, 표준 입출력으로 JSON-RPC 메시지를 주고받습니다.
아래 명령어로 서버가 정상적으로 initialize 요청에 응답하는지 확인할 수 있습니다.
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' \
| node dist/main.js
응답 예시:
{"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{},"resources":{},"prompts":{}},"serverInfo":{"name":"local-mcp-server","version":"2.0.0"}},"jsonrpc":"2.0","id":1}
Claude Code 공식 문서를 참고해 ~/.claude/settings.json에 아래와 같이 추가했습니다.
{
"mcpServers": {
"local": {
"command": "node",
"args": ["/path/to/Mcp_server/dist/main.js"],
"env": {
"ALLOWED_REPO_ROOTS": "/path/to/project",
"DB_TYPE": "none"
}
}
}
}
Claude Code를 재시작하고 /mcp로 확인했지만 결과는...
2 servers
claude.ai
❯ claude.ai Gmail · △ needs authentication
claude.ai Google Calendar · △ needs authentication
local 서버가 보이지 않았습니다.
서버를 직접 실행해보니 초기화는 성공하지만 이후 에러가 폭발했습니다.
error: Watcher error {"error":"Error: EMFILE: too many open files, watch"}
error: Watcher error {"error":"Error: EMFILE: too many open files, watch"}
... (수십 줄 반복)
원인은 macOS의 launchctl file descriptor 한도였습니다.
launchctl limit maxfiles
# maxfiles 256 unlimited
Claude Code가 node 프로세스를 실행할 때 launchctl의 256 한도를 상속받습니다. 그런데 chokidar(파일 감시 라이브러리)가 프로젝트 전체를 감시하려고 하면 금방 한도를 초과합니다.
해결: DISABLE_WATCHER=true 환경변수로 파일 감시 비활성화
서버 코드(watcher.service.ts)에 다음 조건을 추가했습니다.
onModuleInit(): void {
if (process.env.DISABLE_WATCHER === 'true') {
this.logger.info('WatcherService: disabled via DISABLE_WATCHER env');
return;
}
// ... 기존 watcher 초기화 로직
}
더 근본적인 문제가 있었습니다. Claude Code(v2.1.x)는 MCP 서버 설정을 ~/.claude/settings.json이 아니라 ~/.claude.json 에 저장합니다.
~/.claude.json 내부를 보면 다음과 같은 구조입니다.
{
"projects": {
"/path/to/project": {
"mcpServers": {},
...
}
}
}
각 프로젝트별로 mcpServers가 관리되며, 직접 파일을 수정하는 것보다 claude mcp add 명령어를 사용해야 합니다.
claude mcp add local \
-s user \
-e ALLOWED_REPO_ROOTS=/path/to/project \
-e DB_TYPE=none \
-e DISABLE_WATCHER=true \
-- node /path/to/Mcp_server/dist/main.js
옵션 설명:
| 옵션 | 설명 |
|------|------|
| local | MCP 서버 이름 |
| -s user | 유저 전역 범위로 등록 (~/.claude.json에 저장) |
| -e KEY=VALUE | 환경변수 설정 |
| -- node ... | 실행할 명령어 |
주의:
-s user를 사용하면 모든 프로젝트에서 해당 서버를 사용할 수 있습니다. 특정 프로젝트에만 적용하려면-s project를 사용하세요.
이미 등록된 서버가 있다면 먼저 삭제 후 재등록합니다.
claude mcp remove local -s user
claude mcp add local -s user -e ALLOWED_REPO_ROOTS=/path/to/project -e DB_TYPE=none -e DISABLE_WATCHER=true -- node /path/to/Mcp_server/dist/main.js
Claude Code 재시작 후 /mcp 명령어로 확인합니다.
3 servers
User MCPs (/Users/username/.claude.json)
❯ local · ✔ connected
claude.ai
claude.ai Gmail · △ needs authentication
claude.ai Google Calendar · △ needs authentication
local · ✔ connected — 연결 성공!
| 증상 | 원인 | 해결 |
|---|---|---|
/mcp에 서버가 안 보임 | settings.json은 MCP 설정 파일이 아님 | claude mcp add 명령어 사용 |
| EMFILE 에러 폭발 | macOS launchctl maxfiles 한도(256) | DISABLE_WATCHER=true 환경변수 |
| 서버 등록 실패 | 이미 동일 이름 존재 | claude mcp remove 후 재등록 |
MCP 서버를 Claude Code에 연결할 때 핵심은 두 가지입니다.
settings.json이 아닌 claude mcp add 명령어로 ~/.claude.json에 등록MCP 서버 자체를 구현하는 것도 중요하지만, 연결 환경의 제약을 파악하는 것이 실제 운영에서 더 중요할 수 있습니다.
Claude Code v2.1.81 기준으로 작성되었습니다.