NAVER WORKS CLI + MCP 서버 개발기 (1) — CLI 만들고 npm 배포하기

2026. 3. 20. 23:29·개발기
nworks calendar list

치면 오늘 일정이 나온다.

nworks message send --channel C001 --text "배포 완료"

치면 팀 채널에 메시지가 간다.

NAVER WORKS를 터미널에서 쓸 수 있는 CLI를 만들었다. 이 글은 그 과정을 정리한 거다. 인증 구조에서 터진 것들, scope 의존성 지옥, int64 overflow, npm 배포 시 주의할 것들. 직접 CLI를 만들어보려는 사람한테 도움이 되면 좋겠다.

왜 만들었냐면, Google Workspace에는 gws라는 CLI가 있다. 캘린더 조회, 드라이브 업로드, 메일 전송을 터미널에서 한 줄로 끝내는 도구다. NAVER WORKS에는 이런 게 없었다. API 문서는 있는데 CLI가 없길래 직접 만들었다.


NAVER WORKS API 인증 구조: 두 갈래 길

NAVER WORKS API를 쓰려면 먼저 Developer Console(dev.worksmobile.com)에서 앱을 등록해야 한다. Client ID와 Client Secret을 받는다. 여기까지는 다른 OAuth 앱이랑 같다.

문제는 그 다음이다. NAVER WORKS는 인증 방식이 두 가지다.

Service Account (JWT) 는 봇 전용이다. Private Key로 JWT를 만들어서 토큰을 발급받는다. 메시지 전송, 채널 구성원 조회 같은 봇 동작에 쓴다. 사용자 인터랙션 없이 서버에서 자동으로 돌릴 수 있다.

User OAuth 는 사용자 본인 권한으로 동작한다. 브라우저에서 로그인하고, Authorization Code를 받아서 토큰을 발급받는다. 캘린더, 드라이브, 메일, 할 일, 게시판 — 사용자 데이터에 접근하려면 이쪽이다.

처음에는 Service Account 하나로 다 될 줄 알았다. 캘린더 API를 호출했더니 돌아온 게 이거다.

Error: SERVICE_ACCOUNT_NOT_ALLOWED
Code: FORBIDDEN

캘린더, 드라이브, 메일은 Service Account로 접근이 안 된다. 사용자 본인의 OAuth 토큰이 필요하다. 여기서 CLI 설계가 갈렸다.

CLI에서 User OAuth를 하려면 로컬에 HTTP 서버를 하나 띄워야 한다. http://localhost:9876/callback으로 리다이렉트 받아서 Authorization Code를 캡처하고, 토큰으로 교환하는 방식이다. Developer Console에서 이 Redirect URL을 미리 등록해둬야 한다.

결과적으로 nworks는 두 가지 로그인을 다 지원한다.

# 봇 메시지용 (Service Account)
nworks login

# 캘린더/드라이브/메일용 (User OAuth)
nworks login --user


scope 의존성: 문서에 안 쓰여있는 것

User OAuth 로그인할 때 scope를 지정한다. 캘린더를 쓰려면 calendar, 드라이브를 쓰려면 file 이런 식이다.

그런데 이게 단순하지가 않다.

할 일(task)을 만들려면 할당자(assignorId)가 필수다. 이걸 자동으로 채우려고 /users/me API를 호출하는데, 이 API는 user.read scope가 있어야 한다. 그래서 task scope만 줘서 로그인하면 이런 에러가 난다.

Error: has not permission api scope
Code: FORBIDDEN

task + user.read를 같이 줘야 한다.

캘린더도 비슷하다. 일정을 만들려면 calendar scope가 필요한데, 일정을 만든 뒤 확인하려면 calendar.read도 있어야 한다. calendar만 있으면 만들 수는 있는데 조회가 안 된다.

이런 의존성이 문서에 명시적으로 정리돼있지 않다. 다 직접 부딪혀서 알아낸 거다.

최종적으로 nworks에서 정리한 scope 의존성 맵은 이렇다.

기능 필요한 scope 숨은 의존성
task create/update/delete task user.read (할당자 조회)
calendar create/update/delete calendar calendar.read (생성 후 조회)
drive upload file -
mail send mail -

nworks는 이걸 자동 확장으로 해결했다. 사용자가 task만 요청해도 내부적으로 user.read를 추가한다. 사용자가 scope 의존성을 알 필요가 없다.


boardId int64 overflow: JavaScript의 함정

게시판 API를 붙이다가 이상한 일이 생겼다. 게시판 목록을 조회하면 boardId가 돌아오는데, 이걸 가지고 글 목록을 조회하면 다른 게시판의 글이 나왔다.

원인은 JavaScript의 숫자 정밀도였다.

NAVER WORKS API가 돌려주는 boardId가 7895647055266594817 같은 큰 숫자다. JSON.parse가 이걸 처리하면서 정밀도가 깨졌다.

JSON.parse('{"boardId": 7895647055266594817}')
// boardId: 7895647055266594816 ← 마지막 자리가 바뀜

1이 0으로 바뀌면서 완전히 다른 ID가 됐다. 분명 코드는 맞는데 엉뚱한 게시판이 나오니까 한참 헤맸다.

해결은 JSON 파싱 전에 큰 숫자를 문자열로 변환하는 헬퍼 함수를 만드는 거였다.

function safeParseJson(text) {
  // 14자리 이상 숫자를 문자열로 변환
  const safe = text.replace(/:(\s*)(\d{14,})/g, ':"$2"');
  return JSON.parse(safe);
}

이 방식은 NAVER WORKS API처럼 응답 구조가 단순한 경우에는 충분하다. 다만 JSON 값 안에 큰 숫자 문자열이 섞여있으면 오작동할 수 있다. 엄격한 파싱이 필요하면 json-bigint 같은 라이브러리를 쓰는 게 맞다.

NAVER WORKS API를 쓸 때 ID 값이 큰 숫자로 오는 경우가 있다. boardId가 대표적이다. JavaScript에서 이걸 처리할 거면 JSON.parse를 그냥 쓰면 안 된다.


CLI 구조: commander + tsup

CLI 프레임워크는 commander를 썼다. nworks [도메인] [동작] 패턴에 딱 맞았고, 타입스크립트 호환도 깔끔했다. CLI가 복잡해질수록 유지보수가 어려워서 초반에 단순한 구조를 선택한 거다.

nworks message send --to <userId> --text "메시지"
nworks calendar list
nworks drive upload --file ./report.pdf

빌드는 tsup(내부적으로 esbuild)을 썼다. TypeScript → JavaScript 변환 + 번들링을 한 번에 해준다.

{
  "scripts": {
    "build": "tsup src/index.ts src/mcp.ts --format esm --dts"
  }
}

진입점이 두 개다. index.ts는 CLI용, mcp.ts는 MCP 서버용. 하나의 패키지에서 두 가지 모드를 지원한다. 처음부터 AI를 위한 툴로 개발하기 위해서 MCP 확장을 염두에 두고 진입점을 분리해뒀다.


npm 배포: 배포 전 알아 둘 것

package.json 설정

npm에 CLI로 배포하려면 bin 필드가 필요하다.

{
  "name": "nworks",
  "version": "1.1.1",
  "bin": {
    "nworks": "dist/index.js"
  },
  "type": "module",
  "files": ["dist"]
}

files에 dist만 넣으면 빌드 결과물만 배포된다. 소스 코드가 npm에 올라가지 않는다.

2FA (Two-Factor Authentication)

npm은 publish할 때 2FA를 강제한다. 문제는 기기를 바꾸거나 인증 앱을 초기화하면 2FA 복구가 필요하다는 거다. npm support에 티켓을 넣어야 하고, 며칠 걸린다.

배포 전에 npm 2FA가 정상 동작하는지 먼저 확인하는 게 좋다.

npm whoami
npm profile get

2FA 설정이 안되어있다면 npm의 Account Settings 에서 설정할 수 있다.

npx 지원

npx nworks로 설치 없이 실행하려면 bin 필드만 잘 설정하면 된다. 별도 작업 필요 없다. npm이 bin에 등록된 명령어를 자동으로 npx에서 실행 가능하게 만든다.

버전 관리

README만 바꿔도 npm에 반영하려면 버전을 올려야 한다. npm은 같은 버전으로 재배포가 안 된다.

npm version 1.1.1 --no-git-tag-version
npm run build
npm publish

환경 변수와 .env

CI/CD에서 쓰려면 환경 변수로 인증 정보를 넣을 수 있어야 한다.

NWORKS_CLIENT_ID=...
NWORKS_CLIENT_SECRET=...
NWORKS_BOT_ID=...

로컬 개발할 때는 .env 파일을 자동으로 읽도록 dotenv를 넣었다. nworks login 할 때 .env에 있는 값은 다시 물어보지 않는다.

GitHub Actions에서 배포 알림을 보내는 예시:

nworks message send --channel $CHANNEL_ID --text "v${VERSION} 배포 완료"

지금 다시 한다면

CLI 개발도 결국 UX가 중요하다. 내 경우에는 처음 만들어봐서 만들어 보고 직접 해보면서 수정을 거쳤는데 설계 단계에서 잘 잡혀있으면 더 깔끔한 결과가 나왔을 것 같다.

  • 인증 구조를 처음부터 User OAuth 중심으로 설계했을 것이다. Service Account는 봇 메시지에만 쓰이고, 나머지 전부는 User OAuth다. 처음에 Service Account로 시작한 게 오히려 복잡도를 높였다. 이 과정을 수정하느라 초반에 좀 불필요한 패치가 있었다.
  • scope 의존성도 처음부터 자동 확장으로 가야 한다. 사용자한테 "task 쓰려면 user.read도 추가하세요"라고 요구하는 건 나쁜 UX다.
  • boardId int64 문제는 미리 알 수 없었다. 하지만 외부 API에서 ID 값을 받아서 쓸 때는 항상 정밀도를 의심하는 습관이 필요하다.

배운 것들

  • 국내 API 문서를 너무 믿지 마라. scope 의존성 같은 건 직접 부딪혀야 알 수 있다.
  • 외부 API에서 큰 숫자 ID가 오면 JSON.parse를 의심하라.
  • npm 배포는 2FA가 필수고, 요즘은 대부분 사이트들이 2FA를 강제하는 것 같으니 잘 관리 할 것.
  • CLI에서 User OAuth를 하려면 localhost callback 서버가 필요하다.

다음 글에서는 이 CLI를 MCP 서버로 확장하고, Glama에서 AAA 점수를 받고, awesome-mcp-servers에 등록하는 과정을 정리해볼 예정이다.


GitHub: https://github.com/yjcho9317/nworks
npm: https://www.npmjs.com/package/nworks

'개발기' 카테고리의 다른 글

MCP AI 에이전트 보안 프록시 개발기 (4) — YAML 정책 엔진과 SARIF 감사 로깅  (0) 2026.04.14
MCP AI 에이전트 보안 프록시 개발기 (3) — 유니코드 호모글리프와 ReDoS로 보안 감사하기  (0) 2026.04.14
MCP AI 에이전트 보안 프록시 개발기 (2) — regex로 프롬프트 인젝션 탐지하기  (1) 2026.04.14
MCP AI 에이전트 보안 프록시 개발기 (1) — MCP 서버 응답은 검증 없이 AI에 전달된다  (0) 2026.04.14
NAVER WORKS CLI + MCP 서버 개발기 (2) — MCP 서버 만들고 awesome-mcp-servers 등록하기  (0) 2026.03.20
'개발기' 카테고리의 다른 글
  • MCP AI 에이전트 보안 프록시 개발기 (3) — 유니코드 호모글리프와 ReDoS로 보안 감사하기
  • MCP AI 에이전트 보안 프록시 개발기 (2) — regex로 프롬프트 인젝션 탐지하기
  • MCP AI 에이전트 보안 프록시 개발기 (1) — MCP 서버 응답은 검증 없이 AI에 전달된다
  • NAVER WORKS CLI + MCP 서버 개발기 (2) — MCP 서버 만들고 awesome-mcp-servers 등록하기
João Jin
João Jin
모바일 · 보안 · AI 기록
  • João Jin
    João Jin - 모바일 · 보안 · AI
    João Jin
  • 전체
    오늘
    어제
    • 분류 전체보기 (30)
      • 프로젝트 (3)
      • 개발기 (8)
      • 모바일 (8)
      • 보안 (2)
      • AI (8)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • GitHub
    • X
  • 공지사항

  • 인기 글

  • 태그

    Docker Compose
    mcp-fence
    LLM 보안
    FastAPI
    ndk
    MLOps
    안드로이드 NDK
    AI
    LINE WORKS
    머신러닝
    Android
    온디바이스AI
    model context protocol
    AI 에이전트 보안
    JNI
    MLFlow
    Docker
    MCP
    Native
    MCP 보안
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
João Jin
NAVER WORKS CLI + MCP 서버 개발기 (1) — CLI 만들고 npm 배포하기
상단으로

티스토리툴바