Skip to main content
Glama

icloud-mcp

iCloud Calendar와 Claude를 연결하는 MCP(Model Context Protocol) 서버.

Claude(Claude Code / Claude Desktop)에서 자연어로 iCloud 캘린더를 조회·생성·수정·삭제할 수 있게 한다.


아키텍처

Claude (Claude Code / Desktop)
        │  MCP (stdio)
        ▼
  icloud-mcp 서버 (TypeScript, @modelcontextprotocol/sdk)
        │  CalDAV (HTTPS)
        ▼
  iCloud CalDAV 서버 (caldav.icloud.com)
  • 연결 방식: CalDAV + Apple 앱 암호(app-specific password) 인증

    • macOS EventKit 방식 대비 플랫폼 독립적이고, 권한 팝업 없이 동작

    • Apple ID 2FA 환경에서도 앱 암호로 접근 가능

  • 언어/런타임: TypeScript + Node.js 18+, ESM ("type": "module", tsconfig NodeNext)

  • 주요 라이브러리:

    • @modelcontextprotocol/sdk — MCP 서버 프레임워크 (stdio transport)

    • tsdav — CalDAV 클라이언트

    • ical.js — iCalendar(VEVENT) 파싱 생성

    • zod — tool 입력 스키마 검증

  • 인증 정보 관리: 환경변수(APPLE_ID, APPLE_APP_PASSWORD) — 코드/저장소에 절대 커밋 금지. 앱 암호는 macOS 키체인에 두고 실행 래퍼(bin/icloud-mcp.sh)가 주입하는 것을 권장한다(ICMCP-25).

확정된 기술 결정

결정

이유

iCalendar 라이브러리를 ical.js 하나로 통일

update_event는 기존 VEVENT를 파싱 → 수정 → 재직렬화하는 왕복이 필수다. 생성 전용 라이브러리(ical-generator)를 함께 쓰면 왕복 과정에서 필드 유실·포맷 불일치가 발생한다.

ESM + NodeNext

MCP SDK가 ESM 우선이다.

반복 일정은 ICAL.RecurExpansion으로 클라이언트 측 전개

CalDAV 서버측 <C:expand> REPORT는 iCloud 지원이 불균일하다. 조회 구간 내 개별 발생을 라이브러리로 전개하는 편이 안정적이다.

이벤트 식별자는 uid + calendarUrl

CalDAV 리소스 URL은 서버가 바꿀 수 있어 대화 중 재사용에 부적합하다.

모든 로그는 stderr 로만 출력

stdio transport에서 stdout은 JSON-RPC 전용 채널이다. stdout에 한 줄이라도 찍으면 프로토콜이 깨진다.

종일 일정의 end.date포함적(마지막 날)

RFC 5545의 DTEND는 배타적이지만("8/1 하루" → DTEND:20260802), LLM이 매번 +1일을 정확히 계산하기를 기대하는 건 위험하다. "8월 1~3일" → start=08-01, end=08-03으로 직관적으로 매핑되게 하고, ±1일 변환은 ical.ts 한 곳에서만 처리한다.

모듈 구조 및 소유권

bin/
└── icloud-mcp.sh     # 실행 래퍼: 키체인에서 앱 암호를 꺼내 주입 후 서버 exec
scripts/
└── setup.sh          # 새 Mac 셋업: 빌드 + 키체인 + 호스트 등록 + 기동 검증
src/
├── types.ts          # 공용 계약: CalendarEvent, EventDraft, ICloudCalendarClient …
├── errors.ts         # ICloudError + 에러 코드
├── config.ts         # 환경변수 로딩/검증
├── caldav/
│   ├── client.ts     # createICloudClient() — ICloudCalendarClient 구현체
│   └── ical.ts       # VEVENT ↔ CalendarEvent 변환, RRULE 전개
├── tools/            # MCP tool 등록 (7종)
├── schemas.ts        # zod 입력 스키마
└── index.ts          # 서버 엔트리포인트 (stdio)

src/types.ts가 CalDAV 레이어와 MCP 레이어 사이의 고정된 계약이다. 양쪽은 이 파일을 통해서만 통신하며, 계약 변경은 양쪽 동시 수정을 뜻하므로 함부로 바꾸지 않는다.

Related MCP server: Daylite Claude Connector

제공할 MCP Tools

Tool

설명

list_calendars

사용 가능한 캘린더 목록 조회

list_events

기간(시작~종료)으로 이벤트 조회

search_events

키워드로 이벤트 검색

create_event

이벤트 생성 (제목, 시작/종료, 종일 여부, 위치, 메모, 알림, 반복)

update_event

이벤트 수정

delete_event

이벤트 삭제

get_event

단일 이벤트 상세 조회


작업 보드

이 섹션을 Jira처럼 사용한다. 상태: [ ] TODO / [~] IN PROGRESS / [x] DONE 작업 착수 시 [~]로, 완료 시 [x]로 갱신하고 커밋한다.

Phase 1 — 프로젝트 셋업

  • ICMCP-20: 공용 계약 정의 (src/types.ts, src/errors.ts) — Phase 2/3 병렬 구현의 전제

  • ICMCP-1: Node.js + TypeScript 프로젝트 초기화 (package.json, tsconfig.json, .gitignore)

  • ICMCP-2: 의존성 설치 (@modelcontextprotocol/sdk, tsdav, ical.js, zod)

  • ICMCP-3: 빌드/실행 스크립트 구성 (build, dev, start) + src/config.ts

Phase 2 — iCloud CalDAV 연동

  • ICMCP-5: CalDAV 클라이언트 모듈 작성 — 로그인, principal/calendar-home 디스커버리

  • ICMCP-6: 캘린더 목록 조회 구현 (current-user-privilege-set으로 readOnly 판별)

  • ICMCP-7: 이벤트 조회(기간 필터) 구현 — VEVENT 파싱, 타임존 처리

  • ICMCP-8: 이벤트 생성 구현 — VEVENT 생성, UID 관리

  • ICMCP-9: 이벤트 수정/삭제 구현 — etag If-Match 기반 충돌 처리

  • ICMCP-13: 반복 이벤트(RRULE) 지원 — ICAL.RecurExpansion 전개, EXDATE/RECURRENCE-ID 반영, 500회 상한

Phase 3 — MCP 서버

  • ICMCP-10: MCP 서버 뼈대 작성 (stdio transport, 서버 메타데이터)

  • ICMCP-11: Tool 스키마 정의 (zod v4) 및 7개 tool 등록

  • ICMCP-12: 에러 처리 — 인증 실패, 네트워크 오류, 잘못된 입력을 사용자 친화적 메시지로 변환

Phase 4 — 통합 및 검증

  • ICMCP-21: 통합 — 전체 타입체크/빌드 통과, stdout 누수 감사, 자격증명 없는 기동 동작 확인

  • ICMCP-4: 앱 암호 발급 절차 문서화 (appleid.apple.com → 앱 암호)

  • [~] ICMCP-14: Claude Code에 MCP 서버 등록 (claude mcp add) 및 실제 캘린더로 E2E 테스트 — 등록·연결·tools/list(7개)·list_calendars(실 캘린더 8개 조회)까지 확인. 아래 미결 사항 1·3은 미검증

  • ICMCP-15: Claude Desktop 설정 방법 문서화 (claude_desktop_config.json) — GUI 최소 PATH 환경에서 래퍼가 /usr/local/bin/node로 폴백해 기동하는 것까지 검증

  • ICMCP-26: 프로젝트를 TCC 보호 폴더 밖(~/dev/icloud-mcp)으로 이전 — Claude Desktop이 GUI 프로세스라 ~/Documents 내 파일을 실행조차 못 하고 Operation not permitted로 죽었다. 앱에 문서 폴더 접근 권한을 주는 대신 경로를 옮겨 해결. 기존 위치엔 심볼릭 링크를 남겨 폴더 구성을 유지했고, Claude Code/Desktop 양쪽 등록 경로를 갱신했다.

  • ICMCP-27: 다른 Mac에서 한 번에 설치·등록하는 셋업 스크립트 (scripts/setup.sh) — 빌드, 키체인 저장, Claude Code/Desktop 등록, TCC 보호 폴더 검사, GUI 최소 PATH 기동 검증까지

  • ICMCP-16: README 사용법 최종 정리 (설치, 설정, tool 사용 예시)

  • ICMCP-25: 앱 암호를 MCP 호스트 설정(~/.claude.json)에 평문으로 두지 않도록 macOS 키체인 연동 — 실행 래퍼 bin/icloud-mcp.sh가 기동 시 키체인에서 꺼내 환경변수로 주입

Backlog (추후)

  • ICMCP-22: getEvent/updateEvent/deleteEvent의 UID 조회가 캘린더 전체 스캔이다. EventRef에 리소스 URL이 없어 O(n)이며, 이력이 긴 캘린더에서 느리다. UID prop-filter REPORT 또는 uid→url 캐시로 개선.

  • ICMCP-23: 쓰기 시 VTIMEZONE을 고정 오프셋 단일 observance로 합성한다(tzdata 미번들). DST가 있는 지역에서 전환을 걸치는 반복 일정은 전환 이후 발생의 오프셋이 틀어질 수 있다. 기본값 Asia/Seoul은 DST가 없어 영향 없음.

  • ICMCP-24: tsdav의 디스커버리/조회 계열은 상태 코드 없는 평범한 Error를 던져서, 에러 분류가 메시지 정규식 추정에 의존한다. CRUD 계열만 상태 코드 기반으로 정확히 분류됨.

  • ICMCP-28: 기동 시 디스커버리가 간헐적으로 실패한다. 실제로 Claude Desktop에서 principal 조회는 성공하고 calendar-home-set 조회만 cannot find homeUrl로 실패한 뒤, 재시작하니 정상 동작한 사례가 있다. home url은 p60-caldav.icloud.com 같은 별도 샤드 호스트로 리디렉트되는데 이쪽이 일시적으로 실패하는 것으로 보인다. 디스커버리 단계에 짧은 백오프 재시도를 넣어야 한다. 지금은 실패하면 서버가 그대로 죽어서 호스트에는 Server disconnected로만 보인다.

  • ICMCP-17: iCloud Reminders(미리알림) 지원

  • ICMCP-18: 초대/참석자(ATTENDEE) 지원

  • ICMCP-19: 캐싱으로 조회 속도 개선

실제 계정으로 확인이 필요한 미결 사항

구현은 끝났지만 실 iCloud 계정 없이는 검증할 수 없어 가정으로 남아 있는 것들이다. ICMCP-14에서 반드시 확인한다.

  1. 조회 구간 end의 배타성list_events/search_eventsend를 배타적 경계(RFC 4791 time-range 관례)로 문서화하고 tool 설명에도 그렇게 적었다. Apple 서버가 포함적으로 동작한다면 하루치가 어긋난다.

  2. readOnly 판별current-user-privilege-set PROPFIND 응답을 덕타이핑으로 해석한다. 실 계정에서 list_calendars가 8개 캘린더를 모두 "쓰기 가능"으로 반환했으나, 테스트 계정에 읽기 전용 캘린더(구독 캘린더 등)가 없어서 "판별이 동작한다"와 "항상 쓰기 가능을 반환한다"를 구분하지 못했다. 구독 캘린더를 하나 추가해 재확인 필요.

  3. 생성 직후 재조회createEvent는 PUT 후 서버에서 다시 읽어 etag를 채운다. iCloud의 반영 지연이 있다면 재시도가 필요할 수 있다.


빠른 설치 — 다른 Mac에서 (ICMCP-27)

새 Mac에서는 셋업 스크립트 하나로 끝난다. ~/Documents·~/Desktop·~/Downloads 밖에 clone해야 한다 (이유는 Claude Desktop 등록 참고).

git clone https://github.com/jinho-waah/icloud-mcp.git ~/dev/icloud-mcp
cd ~/dev/icloud-mcp
./scripts/setup.sh                 # Apple ID를 대화형으로 물어본다
./scripts/setup.sh you@icloud.com  # 인자로 넘겨도 된다

스크립트가 하는 일:

  1. 프로젝트가 TCC 보호 폴더 안에 있으면 중단하고 옮기는 방법을 안내한다

  2. Node.js 18 이상 확인 → npm installnpm run build

  3. Apple ID의 Latin1 범위 검사 (한글이 섞이면 기동 시점에 btoa()에서 죽으므로 미리 막는다)

  4. 앱 암호를 키체인에 저장 (이미 있으면 유지할지 묻는다). 화면·셸 히스토리 어디에도 남지 않는다

  5. Claude Code 등록 (claude CLI가 있으면)

  6. Claude Desktop 설정 병합 (jq 없이 node로 처리, 기존 키 보존, .bak 생성)

  7. GUI 최소 PATH 환경을 재현해 실제로 iCloud 로그인까지 되는지 검증

끝나면 Claude Code는 재시작, Claude Desktop은 ⌘Q 후 재실행하면 된다.

앱 암호는 기기마다 따로 발급하는 편이 낫다. 한 대를 분실하거나 정리할 때 그 기기 것만 폐기하면 되고, 다른 기기는 영향을 받지 않는다. (Apple 계정당 앱 암호는 여러 개 만들 수 있다.)

개발 환경 설정

npm install
npm run build       # dist/ 생성
npm run typecheck   # 타입만 검사
npm run dev         # watch 모드

수동으로 설정하려면 아래 절들을 따른다.

앱 암호 발급

  1. appleid.apple.com 로그인

  2. 로그인 및 보안 > 앱 전용 암호 > + 로 새 암호 생성 (이름은 icloud-mcp 등 아무거나)

  3. xxxx-xxxx-xxxx-xxxx 형태의 16자 암호가 한 번만 표시된다. 창을 닫으면 다시 볼 수 없으니 바로 복사한다.

Apple ID 본 비밀번호로는 CalDAV 로그인이 되지 않는다. 2단계 인증이 켜져 있어야 앱 암호 메뉴가 나타난다.

Claude Code 등록 — 권장: 키체인 방식 (ICMCP-25)

MCP 호스트 설정 파일(~/.claude.json)에 앱 암호를 평문으로 적지 않는 방식이다. 암호는 macOS 키체인에 두고, 실행 래퍼 bin/icloud-mcp.sh가 기동 시점에 꺼내 환경변수로 주입한다.

# 1) 앱 암호를 키체인에 저장 (한 번만)
#    '-w' 뒤에 값을 쓰지 않으면 대화형으로 입력받는다 → 셸 히스토리에 남지 않는다
security add-generic-password -s icloud-mcp -a "you@icloud.com" -T /usr/bin/security -w

# 2) 래퍼를 command로 등록. env에는 APPLE_ID만 들어간다
claude mcp add icloud --scope user \
  -e APPLE_ID=you@icloud.com \
  -- /절대/경로/icloud-mcp/bin/icloud-mcp.sh
  • -T /usr/bin/security는 키체인 항목의 ACL에 security 명령을 신뢰 앱으로 등록한다. 이게 없으면 서버가 뜰 때마다 키체인 접근 허용 팝업이 뜬다.

  • 키체인 항목의 accountAPPLE_ID 값과 같아야 한다. 래퍼가 -a "$APPLE_ID"로 조회한다.

  • 서비스 이름을 바꾸려면 ICLOUD_MCP_KEYCHAIN_SERVICE 환경변수로 덮어쓴다 (기본 icloud-mcp).

  • APPLE_APP_PASSWORD가 환경변수로 이미 들어와 있으면 래퍼는 키체인을 건너뛴다 (CI·수동 테스트용).

  • GUI 앱(Claude Desktop)은 PATH가 최소한이라 nvm/homebrew의 node를 못 찾을 수 있다. 그런 경우 NODE_BINnode 절대경로를 지정한다.

이 방식이 막아주는 것과 못 막는 것: 평문 유출 경로(설정 파일 백업·클라우드 동기화·스크린샷·실수 커밋)를 막고, 폐기/교체 지점을 한 곳으로 모은다. 반면 키체인은 로그인 시 잠금이 풀려 있으므로 이미 내 계정 권한을 획득한 악성 프로세스는 여전히 꺼낼 수 있다. 로컬 침해에 대한 방어가 아니다.

Claude Code 등록 — 간단(비권장): 환경변수 직접 주입

claude mcp add icloud --scope user \
  -e APPLE_ID=you@icloud.com \
  -e APPLE_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx \
  -- node /절대/경로/icloud-mcp/dist/index.js

앱 암호가 ~/.claude.json평문으로 남는다(파일 권한은 600). 편의상 쓸 수는 있지만 위 키체인 방식을 권한다.

Claude Desktop 등록 (ICMCP-15)

설정 파일: ~/Library/Application Support/Claude/claude_desktop_config.json

Claude Desktop에는 claude mcp add 같은 CLI가 없으므로 이 파일을 직접 편집한다. 기존 키(preferences 등)를 지우지 않도록 병합해서 쓴다.

{
  "mcpServers": {
    "icloud": {
      "command": "/절대/경로/icloud-mcp/bin/icloud-mcp.sh",
      "env": { "APPLE_ID": "you@icloud.com" }
    }
  }
}

명령줄로 안전하게 병합하려면:

CFG=~/Library/Application\ Support/Claude/claude_desktop_config.json
cp "$CFG" "$CFG.bak"
jq '.mcpServers.icloud = {
      "command": "/절대/경로/icloud-mcp/bin/icloud-mcp.sh",
      "env": { "APPLE_ID": "you@icloud.com" }
    }' "$CFG" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG"

Claude Code 설정과 완전히 독립이다. 두 앱은 서로 다른 파일을 보므로 양쪽 다 쓰려면 양쪽에 등록해야 한다. 키체인 항목은 하나를 공유한다.

주의할 점:

  • 프로젝트를 ~/Documents·~/Desktop·~/Downloads 안에 두면 안 된다 (ICMCP-26). 이 세 폴더는 macOS TCC 보호 대상이다. Claude Desktop은 GUI 앱이라 기본적으로 접근 권한이 없어서, 그 안의 스크립트를 실행조차 못 하고 이렇게 죽는다:

    /bin/sh: /Users/이름/Documents/.../bin/icloud-mcp.sh: Operation not permitted
    [icloud] Server transport closed unexpectedly

    파일 권한(chmod +x)이나 quarantine 문제가 아니다. 터미널에서 실행되는 Claude Code는 이미 권한이 있으므로 같은 설정이 Claude Code에선 되고 Desktop에서만 실패한다. 앱에 문서 폴더 접근 권한을 줘도 해결되지만(시스템 설정 > 개인정보 보호 및 보안 > 파일 및 폴더), 서버 하나 돌리려고 GUI 앱에 문서 폴더 전체를 여는 건 과하다. 보호 폴더 밖(~/dev 등)에 두는 편이 낫다.

    기존 폴더 구성을 유지하고 싶으면 원래 위치에 심볼릭 링크를 남긴다. 링크는 사람이 탐색할 때만 쓰이고 MCP 설정에는 실제 경로를 적으므로 TCC에 걸리지 않는다:

    mv ~/Documents/어딘가/icloud-mcp ~/dev/icloud-mcp
    ln -s ~/dev/icloud-mcp ~/Documents/어딘가/icloud-mcp

    dist/index.js를 읽는 node도 같은 TCC 컨텍스트에서 돌기 때문에, 래퍼만 밖으로 빼는 것으로는 부족하고 프로젝트 전체가 밖에 있어야 한다.

  • command는 반드시 절대경로로 쓴다. 상대경로는 앱의 작업 디렉터리 기준이라 맞지 않는다.

  • GUI 앱은 PATH가 최소한(/usr/bin:/bin:/usr/sbin:/sbin)이다. 로그인 셸이 아니라 launchd가 띄우기 때문에 ~/.zshrc가 로드되지 않고, nvm이나 homebrew로 설치한 node는 PATH에 없다. 그래서 node dist/index.js를 직접 command로 지정하면 데스크톱 앱에서만 spawn node ENOENT로 실패한다. 래퍼는 이 상황에서 /usr/local/bin/node/opt/homebrew/bin/node 순으로 폴백한다. 둘 다 없으면 envNODE_BIN으로 절대경로를 지정한다:

    "env": { "APPLE_ID": "you@icloud.com", "NODE_BIN": "/Users/이름/.nvm/versions/node/v22.14.0/bin/node" }
  • 앱을 완전히 종료(⌘Q)한 뒤 다시 실행해야 설정이 반영된다. 창만 닫는 것으로는 부족하다.

  • 로그: ~/Library/Logs/Claude/mcp-server-icloud.log (서버 stderr), ~/Library/Logs/Claude/mcp.log (호스트 측 연결 로그). 연결이 안 되면 여기부터 본다.

  • 키체인 항목을 만들 때 -T /usr/bin/security를 빠뜨리면, 데스크톱 앱이 서버를 띄울 때마다 키체인 접근 허용 팝업이 뜬다.

공통 주의사항

  • --scope project를 쓰지 말 것. 저장소 루트에 .mcp.json을 만들고 거기에 앱 암호를 평문으로 적는다. 이 저장소는 GitHub에 푸시되므로 그대로 유출된다. (.gitignore에 방어선을 넣어 두긴 했다.)

  • --scope user~/.claude.json에 저장되어 어느 디렉터리에서든 쓸 수 있다.

  • 등록 명령은 셸 히스토리에 남는다. 신경 쓰인다면 실행 후 history -d 로 지우거나, 앞에 공백을 한 칸 넣어 히스토리에 남지 않게 한다.

  • APPLE_ID에 한글 등 Latin1 범위 밖 문자가 들어가면 안 된다. CalDAV Basic 인증 헤더를 만드는 btoa() 단계에서 The string to be encoded contains characters outside of the Latin1 range.로 죽고, 호스트에는 Connection closed로만 보인다.

.env 파일은 읽지 않는다. 자격증명은 MCP 호스트가 환경변수로 주입한다.

암호 교체 / 폐기

앱 암호가 새어 나갔다고 판단되면 appleid.apple.com에서 해당 암호만 취소하면 된다. Apple ID 본 비밀번호나 다른 기기 로그인에는 영향이 없다.

키체인 방식이면 교체는 항목 갱신 한 번으로 끝난다:

security delete-generic-password -s icloud-mcp -a "you@icloud.com"
security add-generic-password -s icloud-mcp -a "you@icloud.com" -T /usr/bin/security -w

선택 환경변수: ICLOUD_DEFAULT_CALENDAR_URL(생성 시 기본 캘린더), ICLOUD_DEFAULT_TIMEZONE(기본 Asia/Seoul).

현재 상태

Phase 1~3 구현 완료. 타입체크·빌드 통과, 전 소스에 stdout 출력 없음(프로토콜 안전), 자격증명 누락 시 stderr로 안내 후 exit 1 확인.

실 계정 연동 확인됨 — Claude Code에 키체인 래퍼 방식으로 등록(ICMCP-25), CalDAV 로그인·principal 디스커버리 성공, initialize + tools/list(7개) 정상, list_calendars로 실제 캘린더 8개 조회 성공. 이벤트 CRUD와 조회 구간 경계 동작은 아직 미검증이다(ICMCP-14).

보안 원칙

  • Apple ID 본 비밀번호는 절대 사용하지 않는다. 앱 암호만 사용한다.

  • .env.gitignore에 포함하며, 자격증명은 어떤 파일로도 커밋하지 않는다.

  • 앱 암호는 MCP 호스트 설정 파일에 평문으로 두지 않는다. macOS 키체인 + bin/icloud-mcp.sh 조합을 쓴다(ICMCP-25).

  • 로그는 전부 stderr로 나가며, src/errors.tsredact()가 진단 메시지에서 Apple ID·앱 암호를 마스킹한다. 래퍼 스크립트도 stdout에 아무것도 쓰지 않는다.

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • Calendar API for AI agents: events, availability, Google/Microsoft setup, scheduling, and iCal.

  • Connects ChatGPT to your Apple Calendar via a local Mac agent + Vercel relay

  • Search, read, and write your Apple Notes from ChatGPT/Claude via a local Mac agent + MCP relay.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jinho-waah/icloud-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server