3. 프로젝트 아키텍처 및 디렉토리 역할

최상위 디렉토리 구조

mcp-kit/
├── src/
│   ├── index.ts        # 라이브러리 진입점
│   ├── server.ts       # MCP 서버 진입점 (stdio)
│   ├── cli.ts          # CLI 진입점
│   ├── agent.test.ts   # 실제 LLM 테스트 (기본 test에서 제외)
│   ├── tools/          # MCP 도구 정의
│   ├── exchange/       # 환율 스크레이핑 도메인
│   ├── tmdb/           # TMDB 영화 조회 도메인
│   ├── system/         # 기계 상태 도메인 (배터리 / 메모리 / CPU / 디스크)
│   └── common/         # 공유 키트 (별도 배포 없음, 번들에 인라인)
├── docs/               # Rspress 문서 사이트 콘텐츠
├── scripts/            # Rspress 플러그인
├── skills/             # 손으로 쓴 SKILL.md
├── .github/workflows/  # CI/CD 파이프라인
├── package.json        # 단일 패키지 설정 (@julong/mcp-kit)
├── tsconfig.json       # TypeScript 설정 (@/* → src/*)
├── tsup.config.ts      # 번들 설정
├── rstest.config.ts    # 테스트 설정
├── rslint.config.ts    # 린트 설정
├── rspress.config.ts   # 문서 사이트 설정
├── netlify.toml        # Netlify 배포 설정
├── release-please-config.json      # release-please 설정 (단일 패키지)
├── .release-please-manifest.json   # 현재 버전 추적
└── CLAUDE.md           # AI 어시스턴트용 프로젝트 컨텍스트

계층별 책임 분리

src/common/ — 공유 키트 (내부 전용, 별도 배포 없음)

별도 패키지가 아니라 같은 소스 트리의 모듈입니다. tsup이 noExternal로 전부 인라인하므로 빌드 결과물 안에만 존재합니다.

src/common/
├── kit/
│   ├── tool.ts     # 도구 정의: toolDef(), defineTool(), AnyToolDef 타입, text() 헬퍼
│   ├── server.ts   # MCP 서버: createMcpServer(), startServer(), installProcessGuards()
│   ├── cli.ts      # CLI 실행: runCli(), handleCliError()
├── agent/
│   ├── llm.ts      # createChatModel() — 환경 변수로 OpenAI 호환 채팅 모델 생성
│   ├── log.ts      # FileLogCallback, appendLog() — taskId별 .log 파일 기록
│   ├── runner.ts   # runMcpAgent(), nodeMcpServer() — MCP 서버 기동 + deep agent 실행
│   └── index.ts    # 에이전트 키트 진입점 (`@/common`이 아닌 `@/common/agent`로 사용)
├── .log/           # 에이전트 실행 로그, taskId별 파일 (gitignore 대상)
├── types.ts        # 공통 타입 (Nullable, Optional, MaybePromise)
├── constants.ts    # 공통 상수 (VERSION)
└── index.ts        # 공개 진입점 (kit/* 전체 re-export)

agent/는 의도적으로 index.ts에서 재노출하지 않습니다. 빌드가 noExternal로 모든 의존성을 인라인하기 때문에, 재노출하면 langchain·deepagents가 배포되는 MCP 서버 번들에 끌려 들어갑니다. 쓰는 쪽에서 @/common/agent 경로로 직접 가져오며, dist/에는 포함되지 않습니다.

이렇게 해서 계층이 한 방향으로 유지됩니다. 에이전트를 조립하는 곳은 src/common/agent 하나뿐이고, 나머지 소스는 MCP 도구만 제공합니다. 도구를 LLM으로 검증하고 싶으면 테스트에서 @/common/agent에 MCP 서버와 프롬프트를 넘겨 실행합니다 (src/agent.test.ts 참고).

src/tools/ — MCP 도구 정의

src/tools/
├── index.ts        # tools re-export + UTILS_ENV_KEYS (생성된 README의 env 블록)
├── exchange.ts     # exchangeRatesTool, exchangeRateTool 정의
├── exchange.test.ts
├── movie.ts        # nowPlayingMoviesTool, upcomingMoviesTool, movieRecommendationsTool 정의
├── movie.test.ts
├── system.ts       # systemBatteryTool, systemMemoryTool, systemCpuTool, systemDiskTool, systemInfoTool 정의
└── system.test.ts

src/exchange/ — 환율 스크레이핑 도메인

Playwright로 네이버·구글·다음을 직접 열어 환율을 읽어옵니다. playwright는 실행 시점에 브라우저 드라이버를 자기 패키지 경로에서 찾으므로 번들하지 않고 dependencies로 남겨 둡니다 (tsup.config.ts에서 external 처리).

src/exchange/
├── index.ts        # 포털 병렬 수집 오케스트레이션 + 타임아웃 처리
├── types.ts        # Provider / CurrencyCode / ExchangeQuote 정의
├── parse.ts        # 숫자 파싱 · 고시 단위 역산 · ExchangeQuote 생성
├── wait.ts         # 자리표시자가 실제 시세로 바뀔 때까지 대기
├── browser.ts      # Chromium 기동 및 브라우저 컨텍스트 생성
└── providers/      # naver.ts · google.ts · daum.ts 스크레이퍼

src/tmdb/ — TMDB 영화 조회 도메인

TMDB v3 REST API를 fetch로 호출합니다. 브라우저도 추가 의존성도 쓰지 않으므로 나머지 소스와 똑같이 번들에 인라인됩니다.

src/tmdb/
├── index.ts        # 도메인 진입점 (re-export)
├── types.ts        # TmdbMovie / MovieSummary / MovieListResult 정의와 상수
├── client.ts       # 인증 해석 · URL 조립 · 제한 시간을 둔 GET
├── genres.ts       # 장르 id → 이름 표(언어별 캐시), 장르 이름 해석
├── normalize.ts    # TMDB 원본 영화 → MovieSummary
└── movies.ts       # 상영 중 / 개봉 예정 / 추천 오케스트레이션

인증은 환경 변수에서 읽습니다. TMDB_API_KEY(v3 API 키)는 쿼리 문자열로, TMDB_ACCESS_TOKEN(읽기 액세스 토큰)은 Bearer 헤더로 보냅니다. 둘 다 있으면 토큰을 써서 비밀값이 URL에 남지 않게 합니다.

src/system/ — 기계 상태 도메인

서버가 돌고 있는 기계를 systeminformation으로 읽습니다. 네트워크도 자격 증명도 브라우저도 쓰지 않지만, 이 패키지는 플랫폼 명령(ioreg, vm_stat, df 등)을 실행하므로 읽기마다 제한 시간을 걸어 둡니다.

src/system/
├── index.ts        # 도메인 진입점 (재노출)
├── types.ts        # SECTIONS, BatteryInfo / MemoryInfo / CpuLoadInfo / DiskUsage / SystemSnapshot, 기본값
├── normalize.ts    # systeminformation 원본 → 위 타입으로 정리, pickPrimaryDisk()
└── collect.ts      # readBattery / readMemory / readCpu / readDisks / readSnapshot, 제한 시간

이 도메인의 결정 세 가지는 원본 수치가 질문과 다른 답을 주기 때문에 있습니다.

  • CPU 부하는 표본을 두 번 뜹니다. currentLoad()는 앞선 호출과의 차이를 돌려주므로 프로세스의 첫 호출은 부팅 이후 평균입니다. readCpu()는 한 번 눌러 기준점을 잡고 sampleMs(최소 200ms — 그보다 짧으면 systeminformation이 캐시된 값을 돌려줍니다)를 기다린 뒤 다시 읽습니다.
  • 메모리는 used가 아니라 active를 셉니다. macOS·Linux의 used는 캐시와 버퍼를 포함해 한가한 기계도 90%대로 만듭니다. 회수 가능한 몫은 cachedBytes로 따로 담습니다.
  • 디스크는 전부가 아니라 하나를 고릅니다. macOS는 APFS 컨테이너 하나를 여러 볼륨으로 쪼개고 /에 읽기 전용 시스템 스냅샷을 올려 사용률이 3% 남짓으로 나옵니다. pickPrimaryDisk()는 darwin에서 /System/Volumes/Data, Windows에서 작업 디렉터리가 놓인 드라이브, 그 밖에서는 /를 고릅니다. allDisks를 주면 마운트된 것을 모두 돌려줍니다.

readSnapshot()은 요청한 항목을 동시에 읽으므로 네 항목을 다 물어도 CPU 하나를 물을 때와 비용이 비슷합니다. 실패한 항목은 null이 되고 이유가 errors에 담기는데, 이것이 "배터리를 읽지 못했다"와 "배터리가 없는 기계다"를 가릅니다.

docs/ + rspress.config.ts — Rspress 문서 사이트

docs/                        # 정적 마크다운 문서 (01-*.md ~ 10-*.md)
├── index.md                 # 홈 페이지 (Rspress hero layout)
├── 01-project-overview.md
├── ...
├── 10-commands.md
└── ko/                      # 한국어 로케일
scripts/readme-docs-plugin.ts  # README → /api 페이지 변환 플러그인
rspress.config.ts            # Rspress 설정 (sidebar, nav, plugins)
netlify.toml                 # Netlify 배포 설정

readme-docs-plugin이 손으로 쓴 루트 README.md를 /api(및 /ko/api) 라우트에 렌더링합니다.

전체 데이터 흐름

도구 정의 (src/tools/*.ts)
  │
  ├──→ src/index.ts        ─→ tsup build ─→ dist/index.js   (라이브러리)
  ├──→ src/server.ts       ─→ tsup build ─→ dist/server.js  (MCP 서버)
  └──→ src/cli.ts          ─→ tsup build ─→ dist/cli.js     (CLI)

문서는 도구에서 생성되지 않습니다. README.md와 skills/<bin>/SKILL.md는 손으로 쓰며,
src/tools/*.ts를 고칠 때 함께 고칩니다.

tsconfig path alias: @/* → ./src/* (루트 tsconfig.json, rstest.config.ts가 동일 별칭을 미러링)

아키텍처 다이어그램

모듈 및 의존 구조

src/common은 별도 패키지로 배포되지 않고 빌드 시 번들에 인라인됩니다.

소스 내부 구조

하나의 tools 정의가 세 가지로 소비됩니다: MCP 서버(stdio), CLI 실행기, 라이브러리 진입점. 문서는 네 번째 소비처이고 손으로 씁니다.

빌드 흐름

릴리스 파이프라인 (.github/workflows/release.yml)

관심사 분리 원칙

  1. 도구 정의는 src/tools/ 에서 담당 — MCP에 노출할 인터페이스는 여기에만 위치
  2. 도메인 로직은 src/exchange/, src/tmdb/, src/system/ 에서 담당 — 스크레이핑·파싱·타임아웃 처리
  3. MCP 서버/CLI 공통 로직은 src/common/kit/ 에서 담당 — 서버 생성, CLI 파싱, 에러 처리 등
  4. src/server.ts 는 도구 객체를 createMcpServer()에 전달하는 역할만 수행 (매우 얇은 레이어)
  5. src/cli.ts 는 도구 객체를 runCli()에 전달하는 역할만 수행 (매우 얇은 레이어)

신규 기능 추가 위치

  • 새 도구 추가: src/tools/ 하위에 파일 추가 (또는 기존 파일에 추가) 후 src/tools/index.ts에 집계
  • 새 포털/통화 추가: src/exchange/providers/에 스크레이퍼 추가, src/exchange/types.ts에 상수 추가
  • 새 TMDB 엔드포인트 추가: src/tmdb/movies.ts에 호출 추가, src/tmdb/types.ts에 응답 타입 추가
  • 새 기계 상태 항목 추가: src/system/types.ts의 SECTIONS에 항목 이름, src/system/normalize.ts에 정리 함수, src/system/collect.ts에 읽기 함수 추가
  • 공통 기능 추가: src/common/kit/에 모듈 추가
  • 빌드 설정 변경: 루트 tsup.config.ts 수정