Skip to main content
Glama
H4nnhoi

Notion Task Remote MCP Worker

by H4nnhoi
README.md
# Notion Task Remote MCP Worker

ChatGPT·Claude가 구조화한 할 일을 Notion 데이터 소스에 저장하는 Cloudflare Worker입니다. `/mcp`에서 Streamable HTTP MCP를 제공하며 자연어 파싱은 하지 않습니다. Cloudflare가 새 상태 없는 서버에 권장하는 `createMcpHandler()`와 MCP SDK v2를 사용하고, Notion API `2026-03-11`의 `data_source_id` 부모로 페이지를 생성합니다.

> **보안 경고:** 현재 코드는 로컬 MVP 검증용이며 인증이 없습니다. 인터넷에 공개 배포하면 URL을 아는 누구나 Notion에 항목을 만들 수 있습니다. OAuth를 적용하기 전에는 운영용으로 배포하거나 ChatGPT/Claude에 연결하지 마세요. URL 난독화는 인증이 아닙니다.

## 구조

```text
src/index.ts                 Worker 라우팅과 MCP 핸들러
src/mcp/tools.ts             create_task 도구 정의
src/validation/task.ts       입력 스키마와 검증
src/notion/client.ts         Workers 호환 Notion HTTP 클라이언트
src/notion/createTask.ts     속성·본문 매핑
src/notion/schema.ts         속성명과 API 버전 상수
src/notion/errors.ts         안전한 사용자 오류 변환
test/                        실제 API를 호출하지 않는 단위 테스트
```

## Notion 준비

1. Notion에서 새 데이터베이스를 만들고 다음 속성을 정확히 추가합니다.

   | 이름 | 타입 | 값/설명 |
   | --- | --- | --- |
   | 이름 | Title | 기본 제목 |
   | 프로젝트 | Select | 필요한 옵션을 추가하거나 API 생성 허용 |
   | 분류 | Select | `프로젝트` 옵션 |
   | 마감일 | Date | 선택 |
   | 상태 | Status | 현재 연결된 데이터 소스의 `Not started` 옵션 사용 (`할 일`로 이름을 바꾸면 `src/notion/schema.ts`도 변경) |
   | 알림발송 | Checkbox | 기본 false |

2. [Notion Integrations](https://www.notion.so/profile/integrations)에서 내부 Integration을 만들고 **Read content**, **Insert content** 권한을 부여합니다.
3. 데이터베이스 우측 상단 `…` → Connections에서 Integration을 연결합니다. 연결하지 않으면 API가 해당 데이터 소스를 볼 수 없습니다.
4. 데이터베이스의 `Manage data sources`에서 대상 데이터 소스를 열고 URL을 확인합니다. 최신 API에서는 데이터베이스 컨테이너 ID가 아니라 **Data Source ID**가 필요합니다. 확실하지 않으면 Notion의 `Retrieve a database` 응답에 포함된 `data_sources[].id`를 사용하세요.

속성명을 바꾸려면 [src/notion/schema.ts](src/notion/schema.ts)의 상수만 수정하면 됩니다.

## 로컬 실행과 테스트

```bash
npm install
cp .dev.vars.example .dev.vars
# .dev.vars에 실제 NOTION_TOKEN과 NOTION_DATA_SOURCE_ID 입력
npm run typecheck
npm test
npm run dev
```

기본 테스트는 mock client만 사용하므로 실제 Notion 데이터를 만들지 않습니다. 실제 연동 테스트는 `wrangler dev` 실행 후 아래 Inspector에서 직접 `create_task`를 호출하는 방식으로 명시적으로 수행합니다.

```bash
npx @modelcontextprotocol/inspector@latest
```

Inspector에서 transport를 Streamable HTTP로 선택하고 `http://localhost:8787/mcp`에 연결합니다. `List Tools`에서 `create_task`를 확인한 뒤 다음 입력으로 호출합니다.

```json
{
  "title": "보고서 작성",
  "project": "사포리",
  "category": "프로젝트",
  "dueAt": "2026-08-11T23:59:00+09:00",
  "details": "중간심사 피드백을 반영하고 PDF로 제출"
}
```

Notion에서 여섯 속성과 paragraph 본문을 확인합니다. `details`나 `dueAt`을 빼고 다시 호출해 빈 본문·마감일이 생성되지 않는지도 확인합니다.

## 배포 및 Secret

아래 명령은 **OAuth 적용을 마친 뒤에만** 운영 환경에서 실행하세요.

```bash
npx wrangler secret put NOTION_TOKEN
npx wrangler secret put NOTION_DATA_SOURCE_ID
npm run deploy
```

배포 URL은 `https://<worker-name>.<account-subdomain>.workers.dev/mcp`이며 ChatGPT/Claude의 원격 MCP URL로 사용합니다. 상태 확인은 `/health`입니다. Secret과 `.dev.vars`는 Git에서 제외되며 로그에는 토큰, Authorization 헤더, 요청 본문을 남기지 않습니다.

## 운영 인증 적용

MCP 원격 인증은 OAuth 2.1 흐름이어야 합니다. Cloudflare의 `@cloudflare/workers-oauth-provider`와 다음 중 하나를 구성한 뒤 `/mcp`를 보호하세요.

- Cloudflare Access for SaaS (조직 SSO/일회용 PIN 및 Access 정책)
- GitHub, Google, Auth0, Stytch, WorkOS 같은 외부 IdP

Cloudflare의 [Remote MCP Authorization 문서](https://developers.cloudflare.com/agents/model-context-protocol/protocol/authorization/)와 [Access for SaaS 예제](https://developers.cloudflare.com/agents/model-context-protocol/authorization/access/)를 따라 `/authorize`, `/token`, `/register`를 제공하고, 쓰기 도구 전용 scope를 검사해야 합니다. IdP 선택, 허용 사용자 정책, OAuth client 설정은 배포 계정의 외부 구성이므로 이 MVP에는 임의의 로그인 공급자를 넣지 않았습니다.

## 오류 처리와 제한

- 제목과 본문은 Notion rich-text 한도인 2,000자로 제한합니다.
- `dueAt`은 시간대(`Z` 또는 `+09:00`)가 포함된 ISO 8601만 받습니다.
- 권한, Data Source ID, 속성 스키마, 요청 제한, 일시 장애를 구분해 안전한 메시지를 반환합니다.
- Notion의 내부 응답 메시지나 Secret은 MCP 응답에 노출하지 않고, 로그에는 상태 코드·오류 코드·request ID만 남깁니다.

참고: [Cloudflare Remote MCP](https://developers.cloudflare.com/agents/model-context-protocol/guides/remote-mcp-server/), [Notion Create a page](https://developers.notion.com/reference/post-page), [Notion request limits](https://developers.notion.com/reference/request-limits)