📚 엔지니어 · 세션 · LLM · MCP · Core (수정중) · L03

L31 — Notion 내부 integration 연결: 서버가 프롬프트 없이 Notion 을 읽고 쓰기 (수정중)

작성일자 2026-07-18  ·  수정일자 2026-07-18  ·  강의차수 L03  ·  예상소요 30분

목표 이 강의가 끝나면 학습자가 (1) 서버 자동화엔 "내부 integration 토큰(REST)"·대화형 세션엔 "MCP 커넥터"를 구분해 고르고, (2) 연결을 만들어 .env 에 토큰을 저장하고, 접근할 페이지에 권한을 부여해 REST 로 읽고 append 할 수 있다.

1. 두 갈래부터 — 내부 integration 토큰(REST) vs MCP 커넥터

Notion 을 코드/에이전트에서 다루는 길은 두 개다. 둘을 헷갈리면 셋업 내내 헤맨다.

방식 언제 쓰나 인증 누가 실행
내부 integration 토큰 (REST) 서버 cron·에이전트·헤드리스 스크립트가 사람 없이 Notion 을 읽고 쓸 때 프로젝트 .env 의 토큰 서버 프로세스
claude.ai Notion MCP 커넥터 대화형 Claude Code 세션에서 즉석으로 Notion 을 만질 때 사용자 OAuth (/mcp 로 인증) 지금 앉아 있는 나

가르는 기준은 하나다 — 사람이 그 순간 로그인해 있느냐.

  • cron 이 새벽 3시에 도는 백로그 적재, 에이전트가 무인으로 페이지를 업데이트하는 일에는 사람이 없다. OAuth 로그인 창을 띄울 수 없으니 미리 발급해 둔 토큰(내부 integration)이 필요하다.
  • 반대로 내가 Claude Code 세션에서 "이 회의록 Notion 에 정리해줘" 라고 시키는 건 내가 로그인해 있는 상황이라 내 OAuth 로 붙는 MCP 커넥터가 맞다.

서버에서 도는 자동화는 대부분 내부 integration 토큰 쪽이다. 이 강의도 여기에 집중한다. (MCP 커넥터·서브에이전트 연동은 M3 L12 계열에서 다룬다.)


2. 연결(integration) 만들고 토큰을 .env 에 넣기

"내부 integration" 은 워크스페이스 안에 만드는 로봇 사용자라고 보면 된다. 사람 계정이 아니라, 토큰으로 인증하는 프로그램용 신원이다.

① 연결 생성 (워크스페이스 owner 가 1회): 1. https://www.notion.so/my-integrations → New integration 2. 이름(예: myproject-runner) · 워크스페이스(PopupStudio) · Internal 선택 3. 필요한 Capabilities 체크(읽기/쓰기/댓글 등) 4. 생성되면 Internal Integration Token(ntn_…)을 복사

② 토큰을 프로젝트 src/.env 에 저장:

# <용도> (Notion internal integration token)
NOTION_API_KEY=ntn_…          # 실제 값은 .env 에만
NOTION_VERSION=2022-06-28

코드에서는 os.getenv("NOTION_API_KEY") / os.getenv("NOTION_VERSION") 로 읽는다.

토큰 실값은 어떤 문서·코드·커밋에도 넣지 않는다. .gitignore 에 .env/.env.* 가 있는지 확인하고, 값은 .env 에만 둔다. 이건 L30 의 "원본·자격증명은 코드 밖에" 원칙과 같은 결이고, L29 에서 LangSmith 키를 .env 에만 둔 것과 판박이다. .env 를 나중에 고치면, 이미 켜져 있던 dev 서버는 옛 환경변수를 물고 있으니 재시작해야 새 토큰을 읽는다.


3. 가장 잘 막히는 곳 — 페이지 권한 부여

여기서 대부분이 한 번은 막힌다. 토큰을 잘 넣었는데도 API 가 페이지를 못 찾는다.

이유는 내부 integration 의 권한 모델이 직관과 다르기 때문이다:

내부 integration 은 "명시적으로 연결된 페이지와 그 하위"만 접근한다. "워크스페이스 전체 자동 접근" 같은 스코프는 없다.

즉 토큰을 만들었다고 워크스페이스가 통째로 열리는 게 아니다. 접근을 줄 페이지마다 그 연결을 붙여줘야 한다.

부여 방법 — 반드시 그 페이지 안에서 (이 방법만 실제로 추가된다): 1. 접근을 줄 페이지를 연다 2. 우측 상단 ···(더보기) → 연결(Connections) → 연결 추가(Add connections) 3. 연결 이름(예: chris-notion-integration)을 검색·선택 4. → 그 페이지 + 모든 하위 페이지가 열린다

  • 상위(허브) 페이지에 붙이면 하위가 자동 상속된다. 그래서 넓게 열려면 페이지를 낱개로 다 붙이지 말고 최상위 허브 페이지들만 하나씩 붙인다.

⚠️ 함정 — 연결 관리하기 화면의 검색창으로는 새 페이지를 못 추가한다. 연결 상세의 콘텐츠 사용 권한 화면에 검색창과 페이지 목록(팀스페이스·공유된·개인)이 있어서, 여기서 페이지를 검색해 추가하려는 사람이 많다. 그런데 그 화면의 검색/목록은 이미 연결된 항목을 보여주고 필터링만 하는 용도다 — 여기서 새 페이지를 추가할 수는 없다. 새 접근은 반드시 위처럼 그 페이지 안에서 붙여야 한다. 여러 페이지면 각각 반복한다.

  • 팀스페이스 설정에는 Connections 항목 자체가 없다. 워크스페이스 설정 → 연결 은 owner 만 보며, 연결 목록·이미 부여된 권한을 확인하는 용도다(여기서도 새 페이지 추가는 위 per-page 방식).

증상 → 진단: REST search 가 0건이면 코드 버그부터 의심하지 말고, 대개 그 페이지가 연결에 공유 안 됨이다. → 그 페이지에서 연결을 붙인다. (증상만 보고 원인을 단정하지 않는다 — L23 의 "search 0건 = 권한 미부여" 판박이.)


4. REST API 사용 패턴 — 확인 · 읽기 · append

토큰을 넣고 페이지를 연결했으면, 나머지는 평범한 REST 호출이다. 모든 요청에 Authorization: Bearer <토큰> 과 Notion-Version 헤더를 붙인다.

NOTION_TOKEN=$(grep -oP '^NOTION_API_KEY=\K.*' <프로젝트>/src/.env)
NV="Notion-Version: 2022-06-28"

# ① 어느 연결인지 확인 — .name 이 연결 이름
curl -s https://api.notion.com/v1/users/me \
  -H "Authorization: Bearer $NOTION_TOKEN" -H "$NV"

# ② 검색 (연결된 범위 안에서만 나온다)
curl -s -X POST https://api.notion.com/v1/search \
  -H "Authorization: Bearer $NOTION_TOKEN" -H "$NV" \
  -H "Content-Type: application/json" -d '{"query":"검색어"}'

# ③ 페이지 메타 / 블록(본문) 읽기
curl -s https://api.notion.com/v1/pages/<PAGE_ID> \
  -H "Authorization: Bearer $NOTION_TOKEN" -H "$NV"
curl -s "https://api.notion.com/v1/blocks/<PAGE_ID>/children?page_size=100" \
  -H "Authorization: Bearer $NOTION_TOKEN" -H "$NV"

# ④ 블록 추가 (append — 기존 내용 보존하며 덧붙이기)
curl -s -X PATCH "https://api.notion.com/v1/blocks/<PAGE_ID>/children" \
  -H "Authorization: Bearer $NOTION_TOKEN" -H "$NV" \
  -H "Content-Type: application/json" \
  -d '{"children":[ ...block objects... ]}'

두 가지만 몸에 익히면 된다:

  • users/me 로 "내가 어느 연결로 붙었는지" 먼저 확인한다. 토큰이 여러 개일 때 엉뚱한 연결에 대고 삽질하는 걸 막는다.
  • 수정은 통째로 덮어쓰지 말고 append(PATCH blocks/<id>/children) 로 한다. 기존 내용을 보존하며 덧붙이는 게 안전하다 — 이것도 L30 의 "원본 파괴 금지" 와 같은 태도다.
  • 페이지 ID 는 Notion 페이지 URL 끝의 32자리(하이픈 없는) 문자열이다.

실제 코드 예시는 팀 wiki infrastructure/notion-integration.md §7 이 가리키는 agentkim 파일에 있다(자습용, 다른 repo): agentkim/src/agentkim/config.py(토큰 읽기), agentkim/src/agentkim/commands/notion_delete.py(페이지 아카이브/복원). 우리 education 프로젝트 안엔 없으니 링크가 아니라 위치만 적는다.


5. 보안 원칙 — 최소 권한 · 용도별 분리

토큰 하나가 페이지 뭉치의 읽기/쓰기 권한이므로, 새면 그 범위가 통째로 노출된다. 그래서:

  • 토큰 실값은 .env(gitignore)에만. 문서·코드·커밋에 평문 금지.
  • 최소 권한 — 필요한 허브 페이지만 연결하고, 민감 페이지는 연결 범위에서 뺀다. (§3 에서 페이지 단위로 붙이는 구조가 곧 최소 권한 장치다.)
  • 연결을 용도별로 분리 생성한다 — 로그 적재용 / 백로그용을 따로 만든다. 사고가 나도 영향 범위가 그 용도로 국한되고, users/me 로 어느 연결의 소행인지 추적된다. (현재 PopupStudio 에 chris-notion-integration·popax-error-log 두 연결이 이렇게 나뉘어 있다.)
  • 토큰 로테이션 시 해당 프로젝트 .env 만 갈아끼우면 된다. 문서엔 토큰 위치만 있으므로 문서는 안 고쳐도 된다.

이름 주의: 연결 이름이 프로젝트명과 꼭 같지 않다(예: agentkim 이 쓰는 토큰의 연결 이름은 chris-notion-integration). "이 토큰이 무슨 연결이지?" 는 추측하지 말고 users/me 의 .name 으로 확인한다.


오늘 정리 + 다음

  • 정리: Notion 을 코드에서 다루는 길은 둘 — 사람 없이 도는 서버 자동화엔 내부 integration 토큰(REST), 내가 로그인한 대화형 세션엔 MCP 커넥터. 내부 integration 은 ①연결 생성 → ②토큰을 .env 저장 → ③접근할 페이지마다 권한 부여 → ④REST 로 읽기/append 의 순서다. 핵심은 "토큰을 만들었다고 워크스페이스가 열리는 게 아니라, 페이지를 하나씩 연결해야 열린다" 는 권한 모델.
  • 흔한 함정: 연결 관리하기 화면의 검색창으로 새 페이지를 추가하려다 안 되는 것. 그 화면은 이미 연결된 것만 필터링한다 — 새 접근은 그 페이지 안 ··· → 연결 추가로만 붙는다. 그리고 REST search 가 0건이면 코드가 아니라 페이지 미연결을 먼저 의심한다.
  • 다음 시간: 붙인 연결로 실제 자동화 한 사이클 — 에이전트가 Notion 페이지를 읽어 처리하고 결과를 append 로 되쓰는 흐름(L08 백로그 워크플로우와 엮어).
  • 자습 권장: 각자 테스트 페이지 하나를 만들어 §2~§4 를 따라 해본다 — 연결 생성 → .env 저장 → 그 페이지에 연결 부여 → users/me 와 search 로 확인. 절차 원문·현재 등록된 연결 목록은 팀 wiki infrastructure/notion-integration.md.
이 강의를 학습하셨나요?