Skip to main content
Glama

dueday-mcp

개인 할 일을 ChatGPT / Claude 챗에서 MCP로 관리하는 작은 서버입니다. 마감일과 함께 준비 시작일(due - lead_days)을 계산해서, 일 단위 예약 작업이 "지금부터 준비해야 하는 일"을 알려줄 수 있게 설계했습니다.

  • MCP 도구 13개: 할 일 8개(add_todo, list_todos, update_todo, complete_todo, cancel_todo, delete_todo, upcoming, list_tags) + 목표 5개(list_goals, add_goal, update_goal, log_progress, goal_progress)

  • 장기 목표 관리: 인생 목표 → 연간·단기·장기 목표, 목표당 지표 여러 개(누적·측정값·달성 여부), 체크인 기록, 할 일을 목표에 연결(연결 없으면 일상)

  • REST API: 같은 서비스 계층을 /api/*로 노출 (웹 UI용)

  • 저장소: SQLite (node:sqlite 내장, 네이티브 빌드 불필요)

  • 인증: 내장 OAuth 2.1(PKCE, 사전 등록 공개 클라이언트, DCR 없음) 또는 정적 Bearer 토큰, 클라이언트별 요청 제한, 64KB 본문 제한

  • 배포: Docker Compose, 선택적으로 Cloudflare Tunnel 프로필

  • 선택 기능: gbrain 지식 브레인의 프로젝트 페이지에 항목을 선별 동기화

개인용으로 만든 도구라 시간대는 Asia/Seoul 고정이고, 도구 설명과 오류 메시지는 한국어입니다.

빠른 시작 (Docker)

cp .env.example .env
# .env 의 API_TOKEN 을 채웁니다: openssl rand -hex 32
mkdir -p data
docker compose up -d --build
curl localhost:3080/health

컨테이너는 3000번에서 듣고, 호스트에는 HOST_PORT(기본 3080)로 127.0.0.1에만 공개됩니다. Linux에서 bind mount 권한 문제가 나면 chown 1000:1000 data를 실행하세요(컨테이너는 node 사용자로 동작).

외부 공개 (ChatGPT 커넥터용)

ChatGPT 커스텀 커넥터는 공개 HTTPS 주소가 필요합니다. Cloudflare Zero Trust에서 터널을 만들고, 퍼블릭 호스트명을 http://todo:3000으로 연결한 뒤:

# .env 에 TUNNEL_TOKEN 추가
docker compose --profile tunnel up -d

ChatGPT의 커스텀 커넥터는 인증 방식으로 OAuth만 제공하므로(정적 키 옵션 없음) .envPUBLIC_URLOWNER_PASSWORD를 설정해 내장 OAuth 2.1 서버를 켭니다. 그 뒤 ChatGPT → 설정 → Apps & Connectors → 개발자 모드 → Create 에서:

  • 연결: 서버 URL https://<host>/mcp

  • 인증: OAuth. 메타데이터는 /.well-known/oauth-authorization-server에서 자동 발견됩니다. 클라이언트 ID는 OAUTH_CLIENT_ID(기본 chatgpt), 시크릿 없음(PKCE 공개 클라이언트).

  • 연결 버튼을 누르면 /authorize 승인 페이지가 열리고 OWNER_PASSWORD를 입력하면 완료됩니다.

Claude.ai / Claude Code

  • claude.ai 커스텀 커넥터: .envOAUTH_CLIENTS에 confidential 클라이언트를 추가합니다(예: claude|https://claude.ai/api/mcp/auth_callback,https://claude.com/api/mcp/auth_callback|<secret>). claude.ai → 설정 → Connectors → Add custom connector에서 URL https://<host>/mcp, Advanced settings에 OAuth Client ID claude와 Client Secret을 입력합니다. 연결 시 /authorize 승인 페이지에서 OWNER_PASSWORD를 입력합니다.

  • Claude Code: 헤더 방식이 가장 단순합니다.

claude mcp add --transport http -s user dueday https://<host>/mcp --header "Authorization: Bearer <API_TOKEN>"

Claude Code나 스크립트처럼 헤더를 직접 넣을 수 있는 클라이언트는 Authorization: Bearer <API_TOKEN>도 계속 쓸 수 있습니다.

Related MCP server: Personal Task Manager MCP

환경 변수

변수

기본

설명

API_TOKEN

필수

/api, /mcp 보호용 Bearer 토큰, 16자 이상

PORT

3000

컨테이너 내부 포트

HOST_PORT

3080

compose가 호스트에 여는 포트

DB_PATH

./data/todo.db

SQLite 파일 (WAL)

RATE_LIMIT_PER_MIN

240

클라이언트(IP 또는 토큰)별 분당 요청 수 (웹 UI는 한 번 열 때 요청 5~6개)

PUBLIC_URL

비움

터널이 노출하는 공개 origin. OWNER_PASSWORD와 함께 설정하면 OAuth 활성

OWNER_PASSWORD

비움

/authorize 승인 페이지 비밀번호, 12자 이상. WEB_PASSWORD가 없으면 웹 로그인에도 사용

WEB_PASSWORD

비움

웹 UI 로그인 전용 비밀번호(4자 이상, PIN 가능). 실패 5회/15분 잠금, 전체 30회/15분 잠금

OAUTH_CLIENT_ID

chatgpt

사전 등록 공개 클라이언트 ID

OAUTH_REDIRECT_URIS

ChatGPT 기본

허용 리다이렉트 URI(쉼표 구분). https://chatgpt.com/connector/oauth/<id>는 항상 허용

OAUTH_CLIENTS

비움

여러 클라이언트: id|redirect1,redirect2[|secret];.... 설정 시 위 두 값을 대체

GBRAIN_URL

비움

gbrain MCP 엔드포인트. 아래 자격증명 중 하나와 함께 설정하면 동기화 활성

GBRAIN_TOKEN

비움

정적 bearer 토큰

GBRAIN_CLIENT_ID, GBRAIN_CLIENT_SECRET

비움

OAuth client_credentials (권장). 토큰은 자동 발급·캐시

TUNNEL_TOKEN

비움

tunnel 프로필용 cloudflared 토큰

ANTHROPIC_API_KEY

비움

설정하면 새 할 일을 Claude로 자동 분류(아래 참고). 비우면 기능 꺼짐

ENRICH_MODEL

claude-opus-5

분류에 쓰는 모델

ENRICH_DAILY_CAP

200

하루(Asia/Seoul) 최대 분류 호출 수. 넘으면 분류 없이 저장

MCP 도구

모든 응답은 { success, data, meta: { today, total? } } 형태이고 meta.today는 Asia/Seoul 기준 오늘입니다. 상대 날짜("이번주 금요일")는 클라이언트(LLM)가 meta.today로 변환해서 YYYY-MM-DD로 보냅니다. 날짜만 오면 그날 18:00으로 저장됩니다.

도구

역할

add_todo

등록. title, due?, tags?, lead_days?(기본 3), note?, brain_ref?

list_todos

조회. status, tag, due_before, due_after, q, limit, offset

update_todo

부분 수정. 바꿀 필드만 최상위에. due: null이면 마감 제거

complete_todo

완료 / reopen: true로 되돌리기

cancel_todo

취소(목록·알림에서 제외, 기록 유지) / reopen: true로 되살리기

delete_todo

영구 삭제. 명시적 요청 시에만

upcoming

알림용. overdue, start_now, later, no_due 그룹 + 한 줄 summary

list_tags

태그와 미완료 개수

list_goals

인생·연간·단기·장기 목표와 진행률(달성률, 기간 경과율, 상태)

add_goal / update_goal

목표와 지표 정의. 지표 kind: count(권·회 누적) / value(kg·명·BTC 측정값) / boolean

log_progress

체크인. "책 한 권 끝냈어" → reading +1, "몸무게 75.8" → weight 75.8

goal_progress

목표 상세: 지표별 진행, 최근 체크인, 열린 할 일

할 일은 add_todogoal(목표 태그)로 목표에 연결됩니다. 연결되지 않은 할 일은 일상입니다. 진행률은 지표 달성률의 평균(지표가 없으면 할 일 완료율)이고, 연간 목표는 기간 경과율과 비교해 앞섬 / 순항 / 뒤처짐 / 달성으로 표시됩니다.

상세 명세는 docs/mcp-tools.json, 아키텍처는 docs/architecture.html에 있습니다.

ChatGPT 프로젝트 지침 예시

개발자 모드 커넥터는 대화마다 + 도구 메뉴에서 켜야 하고, 명시하지 않으면 ChatGPT가 자체 예약·메모 기능으로 처리해 버립니다. 전용 프로젝트를 만들어 아래 지침을 넣어 두면 안정적으로 동작합니다.

너는 내 할 일 비서다. 할 일 저장소는 dueday 커넥터(MCP) 하나뿐이다.

[절대 규칙]
- 할 일 등록·조회·수정·완료·취소는 반드시 dueday 도구(add_todo, list_todos, update_todo, complete_todo, cancel_todo, upcoming, list_tags)로 처리한다.
- ChatGPT 자체 예약(리마인더)·메모·캔버스 기능으로 대신하지 않는다. 도구를 쓸 수 없으면 "dueday 커넥터가 이 대화에 켜져 있지 않다"고 알리고 멈춘다.
- "안 해도 된다/없던 일로"는 cancel_todo(되돌릴 수 있음). delete_todo는 내가 "삭제"라고 명시했을 때만 쓰고, 실행 전에 제목을 확인받는다.

[날짜]
- 모든 날짜는 Asia/Seoul. 기준일은 도구 응답의 meta.today를 쓴다.
- 상대 표현은 YYYY-MM-DD로 바꿔 넘긴다. "이번주"는 오늘이 속한 월~일, "다음주"는 그 다음 월~일. 요일이 없으면 그 주 금요일로 잡고 답변에 요일을 같이 적어 확인받는다.
- 시각이 없으면 due에 날짜만 넘긴다.

[등록]
- 서버가 비어 있는 태그·준비 기간·목표 연결을 자동으로 채워 응답에 돌려준다. 내가 직접 말한 값(태그, "며칠 전부터", 목표)만 넘기고 나머지는 비운다.
- 한 메시지에 할 일이 여러 개면 각각 따로 등록한다.
- 응답의 suggestion이 null이 아니면 "목표로 올릴까?"를 한 줄 제안하고, 승낙하면 add_goal(같은 tag) 후 update_todo(goal)로 연결한다.

[대화 처리]
- "뭐 남았어": upcoming(days=7) → 마감 지남 / 지금 준비 시작 / 예정 순으로 요약.
- "~했어": list_todos(q=키워드)로 찾아 complete_todo. 후보가 둘 이상이면 고르게 한다.
- "~미뤄줘": update_todo로 due 변경.
- 등록 후 답변은 한 줄: 제목 · 마감(요일) · 준비 시작일 · 태그 · 목표(또는 일상). 응답에 온 값을 그대로 쓴다.

예약 작업 프롬프트 예시 (매일 아침)

dueday 커넥터의 upcoming 도구를 days=7로 호출해라. 결과를 이렇게 정리해서 알려줘:
1) 마감 지남 — 제목, 마감일 (있을 때만, 굵게)
2) 오늘 준비 시작 — 제목, 마감일, 남은 일수. 맨 위에 눈에 띄게.
3) 이번 주 예정 — 제목, 마감일, 준비 시작일
4) 마지막 줄에 도구 응답의 summary를 그대로 인용.
아무것도 없으면 "오늘은 준비 시작할 일이 없음" 한 줄만.

오늘이 월요일이면 마지막에 "주간 목표 리뷰" 블록을 추가한다: list_goals(status=active)를 호출해
연간 목표를 뒤처짐 → 순항 → 앞섬 → 달성 순으로 나열하고, 각 줄에 제목 · 달성률 vs 기간 경과율 ·
지표 현재값/목표값을 적는다. 뒤처짐 목표에는 이번 주에 할 만한 행동 하나를 제안하되 제안임을 밝힌다.
이번 주 체크인이 없는 주기 목표(cadence_met=false)는 "이번 달 아직"으로 따로 표시한다.

개발

pnpm install
API_TOKEN=devtokendevtokendevtoken pnpm dev   # tsx watch
pnpm test            # vitest
pnpm test:coverage   # 80% 이상 강제
pnpm typecheck && pnpm build

구조: src/todos(도메인·저장소·날짜), src/mcp(MCP 어댑터), src/api(REST), src/auth(Bearer·요청 제한), src/oauth(OAuth 2.1 서버), src/brain(gbrain 동기화), src/db(마이그레이션).

자동 분류 (선택, Claude API)

ANTHROPIC_API_KEY를 설정하면 어느 경로(웹·REST·MCP)로 들어오든 새 할 일을 저장 직후 백그라운드에서 한 번 분류합니다.

  • 비어 있는 필드만 채웁니다. 사용자가 준 tags, lead_days, due, goal은 절대 덮어쓰지 않습니다.

  • 태그(기존 태그 우선)·준비 기간(lead_days)·제목 속 날짜(due)는 바로 적용하고, 목표 연결은 확신이 높을 때만 적용합니다.

  • "매주 두 번 달리기"처럼 목표에 가까운 항목은 목표 제안으로만 남깁니다. GET /api/suggestions에서 보고 POST /api/suggestions/:id/accept(인생 목표 아래 목표 생성 + 할 일 연결) 또는 /dismiss로 처리합니다. 웹 UI의 목표 페이지에도 AI 제안 카드로 나옵니다.

  • 자동으로 채운 필드는 todo.enrichment에 기록되고 웹에서 AI 칩으로 표시됩니다. 사용자가 직접 수정하면 표시가 사라집니다.

  • 실패하면 할 일은 그대로 두고 enrichment_log에 남깁니다. 하루 호출 상한(ENRICH_DAILY_CAP)을 넘으면 skipped로 기록합니다.

  • MCP add_todo는 분류를 최대 8초 기다렸다가 채워진 할 일과 suggestion(목표 제안, 없으면 null)을 함께 돌려줍니다. 챗 클라이언트는 사용자가 직접 말한 값만 넘기면 됩니다. 웹·REST 등록은 기다리지 않고 백그라운드로 처리합니다.

  • 분류 호출은 직렬로 처리되며 요청당 약 1KB 입력(시스템 프롬프트는 캐시)이라 비용은 하루 수십 건 기준 매우 낮습니다.

gbrain 동기화 (선택)

brain_ref에 gbrain 프로젝트 페이지 슬러그를 주면 생성·완료 시 그 페이지의 ## 남은 일 표에 todo:<id> 행을 추가·갱신합니다. 없는 페이지는 만들지 않으며 결과는 brain_sync_log에 남습니다. 시간 제한은 요청당 5초입니다.

gbrain 쪽에는 projects/만 쓸 수 있는 client_credentials 클라이언트를 하나 등록하는 것을 권합니다:

gbrain auth register-client dueday --grant-types client_credentials \
  --scopes "read write" --token-endpoint-auth-method client_secret_post \
  --bound-slug-prefixes projects/

라이선스

MIT

Related MCP Connectors

Related MCP Servers