NestJS로 만든 MCP 서버를 Claude Code에 연결하기

밀초·2026년 3월 22일

MCP

목록 보기
1/1

로컬 MCP 서버를 직접 만들고, Claude Code CLI에 연결하기까지의 삽질 기록


배경

MCP(Model Context Protocol)는 AI 모델이 외부 도구나 데이터에 접근할 수 있도록 하는 표준 프로토콜입니다. Claude Code CLI는 MCP 서버를 통해 다양한 도구를 확장할 수 있는데, 이번 글에서는 NestJS로 직접 MCP 서버를 구현하고 Claude Code에 연결하기까지의 과정을 기록합니다.


구현한 MCP 서버 스펙

  • 런타임: Node.js + NestJS + TypeScript
  • 전송 방식: stdio (표준 입출력)
  • 등록 도구: 17개 tool, 5개 resource, 3개 prompt
  • 주요 기능:
    • SQLite 기반 코드 인덱싱 (FTS5 전문 검색)
    • chokidar 파일 감시
    • ts-morph AST 분석
    • Git 히스토리 조회
    • ripgrep 기반 코드 검색

1단계: MCP 서버 빌드

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}

2단계: Claude Code에 연결 시도 (그리고 삽질)

첫 번째 시도: settings.json에 직접 추가

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 서버가 보이지 않았습니다.


3단계: 원인 분석

문제 1: EMFILE (too many open files)

서버를 직접 실행해보니 초기화는 성공하지만 이후 에러가 폭발했습니다.

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 초기화 로직
}

문제 2: settings.json은 MCP 설정 파일이 아니다

더 근본적인 문제가 있었습니다. Claude Code(v2.1.x)는 MCP 서버 설정을 ~/.claude/settings.json이 아니라 ~/.claude.json 에 저장합니다.

~/.claude.json 내부를 보면 다음과 같은 구조입니다.

{
  "projects": {
    "/path/to/project": {
      "mcpServers": {},
      ...
    }
  }
}

각 프로젝트별로 mcpServers가 관리되며, 직접 파일을 수정하는 것보다 claude mcp add 명령어를 사용해야 합니다.


4단계: 올바른 방법으로 등록

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

5단계: 연결 확인

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에 연결할 때 핵심은 두 가지입니다.

  1. 설정 파일 위치: settings.json이 아닌 claude mcp add 명령어로 ~/.claude.json에 등록
  2. macOS 환경: launchctl의 file descriptor 한도를 고려해 파일 감시 같은 기능은 비활성화 옵션을 제공

MCP 서버 자체를 구현하는 것도 중요하지만, 연결 환경의 제약을 파악하는 것이 실제 운영에서 더 중요할 수 있습니다.


Claude Code v2.1.81 기준으로 작성되었습니다.

profile
안녕하세요 :) 성장하는 개발자 밀초입니다 !

0개의 댓글