@roottale/cms-mcp 0.44.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @roottale/cms-mcp
2
2
 
3
+ ## 0.45.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 23aa6ae: 카테고리 소속을 콘텐츠 유형 설정과 분리하고, 공개 collections 응답의 categories를 분류 원장에서 조립합니다. 사이트 내부 경로 검증을 강화하고 taxonomy.updated 집계 웹훅 계약과 연동 예제를 추가합니다.
8
+
9
+ ## 0.44.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 4b09885: CMS 자동화 문서의 `read` 권한 범위, R2 미디어 키 경로, CLI 하위 명령 도움말을
14
+ 실제 동작과 맞게 바로잡았습니다.
15
+
3
16
  ## 0.44.0
4
17
 
5
18
  ### Minor Changes
package/README.md CHANGED
@@ -59,7 +59,10 @@ npx -y @roottale/cms-mcp cli posts create \
59
59
  npx -y @roottale/cms-mcp cli posts publish "<post-id>"
60
60
  ```
61
61
 
62
- 전체 명령은 `npx -y @roottale/cms-mcp cli --help`에서 확인할 수 있습니다.
62
+ 상위 `cli --help`는 명령 그룹만 보여줍니다. 글의 `list`, `create`, `update`,
63
+ `publish`, `unpublish`, `set-terms`는 `cli posts --help`, 미디어의 `list`,
64
+ `upload`, `update`, `delete`는 `cli media --help`에서 확인하세요. 각 명령의
65
+ 옵션은 `cli posts create --help`처럼 하위 명령 뒤에 `--help`를 붙이면 됩니다.
63
66
 
64
67
  ## Tools
65
68
 
package/dist/index.js CHANGED
@@ -801,7 +801,7 @@ function registerTools(server) {
801
801
  }
802
802
 
803
803
  // src/server.ts
804
- var VERSION = true ? "0.44.0" : "dev";
804
+ var VERSION = true ? "0.45.0" : "dev";
805
805
  var SERVER_INSTRUCTIONS = `
806
806
  roottale-cms-mcp\uB294 RootTale CMS\uB97C \uC678\uBD80 \uC0AC\uC774\uD2B8(\uC8FC\uB85C Next.js)\uC5D0 \uC5F0\uB3D9\uD558\uACE0
807
807
  \uAE00\xB7\uC378\uB124\uC77C\xB7\uBCF8\uBB38 \uC774\uBBF8\uC9C0\uB97C \uC790\uB3D9\uD654\uD558\uAE30 \uC704\uD55C \uBB38\uC11C\xB7\uC608\uC2DC \uCF54\uB4DC\xB7API tool\uC744 \uC81C\uACF5\uD569\uB2C8\uB2E4.
@@ -12,8 +12,9 @@ description: 공개 API raw 엔드포인트 — JS 외 스택이나 저수준
12
12
  `@roottale/cms-client`를 사용하세요. 글쓰기·미디어 자동화는 아래 HTTP API,
13
13
  MCP tool 또는 공개 CLI를 사용합니다.
14
14
 
15
- 글쓰기·미디어 자동화는 `read_write` API 키가 필요합니다. 공개 콘텐츠 조회만
16
- 필요하면 `read` 키를 사용하세요.
15
+ 글쓰기·미디어 자동화는 `read_write` API 키가 필요합니다. `read` 키는
16
+ 읽기 전용이며 공개 콘텐츠뿐 아니라 관리 API의 초안·예약·비공개 글과
17
+ 미디어 목록도 조회할 수 있습니다.
17
18
 
18
19
  ## 관리 API 빠른 흐름
19
20
 
@@ -109,7 +110,7 @@ curl -X PUT "$UPLOAD_URL" \
109
110
 
110
111
  ```json
111
112
  {
112
- "r2_key": "tenant-id/site-id/...",
113
+ "r2_key": "tenants/<tenant-id>/sites/<site-id>/media/...",
113
114
  "original_name": "hero.webp",
114
115
  "content_type": "image/webp",
115
116
  "size_bytes": 482031,
@@ -118,7 +119,8 @@ curl -X PUT "$UPLOAD_URL" \
118
119
  }
119
120
  ```
120
121
 
121
- 서버가 R2 객체의 tenant 경로, 크기, 형식을 검증한 뒤 미디어를 등록합니다.
122
+ `r2_key`는 1단계 응답값을 바꾸지 말고 그대로 보냅니다. 서버가 R2 객체의
123
+ tenant/site 경로, 크기, 형식을 검증한 뒤 미디어를 등록합니다.
122
124
  응답의 `id`는 글의 `featured_media_id`에, `url`은 Tiptap image 노드의
123
125
  `attrs.src`에 사용합니다.
124
126
 
@@ -80,12 +80,13 @@ export const COLLECTIONS: RouteCollection[] = [
80
80
  |---|---|---|
81
81
  | 무엇 | 글이 사는 곳 (공지 / 블로그) | 섹션 안의 세부 분류 (칼럼 / 소식) |
82
82
  | 글당 | **딱 하나** (배타적) | 0개 이상 (선택·복수) |
83
- | 정하는 곳 | 글쓰기 "어디에 올릴까요?" | 글쓰기 주제 칩 / 설정 > 분류 |
83
+ | 정하는 곳 | 글쓰기 "어디에 올릴까요?" | 글 > 분류 > 카테고리 / 글쓰기 주제 칩 |
84
84
  | 저장 | `post.collection_key` | 글의 category terms |
85
85
  | 라우팅 | basePath 결정 (`/notice`) | 아카이브만 (`/blog/categories/칼럼`) |
86
86
 
87
87
  > 이전 버전은 *카테고리로 섹션을 추론*했지만, 지금은 섹션이 글에 **명시**됩니다.
88
- > 카테고리는 더 이상 어느 섹션에 속하는지를 결정하지 않습니다(주제 아카이브 전용).
88
+ > 카테고리는 글의 섹션을 결정하지 않습니다. 카테고리 자체에는 사용할 섹션을
89
+ > 연결할 수 있고, 글에서는 그 섹션에 연결된 카테고리만 주제로 고릅니다.
89
90
 
90
91
  ## 어드민에서 설정 (`admin.roottale.com`)
91
92
 
@@ -97,16 +98,20 @@ export const COLLECTIONS: RouteCollection[] = [
97
98
  | key | 안정 식별자 (`notice`, `blog`) — 사이트 코드와 맞물리므로 보통 고정 |
98
99
  | 라벨 | 메뉴·작성 화면 표시 이름 (공지/블로그) |
99
100
  | basePath | URL 앞부분 (`/notice`, `/blog`) |
100
- | 주제 | 이 섹션이 제공하는 카테고리(아카이브 범위). 비우면 주제 없음 |
101
101
  | feed / archives / og | RSS 포함 / 주제 아카이브 / 동적 OG |
102
102
  | 글별 상세 페이지 (`detail`) | 기본 켜짐. 끄면 목록 전용(상세 URL 없음) — 아래 "목록 전용 유형" 참고 |
103
103
  | 순서 | 메뉴·표시 순서 |
104
104
 
105
105
  **섹션을 하나도 안 만들면 단일 블로그(`/blog`)** 로 동작합니다(설정 전 기본값).
106
106
 
107
+ 카테고리 소속은 **글 > 분류 > 카테고리**에서 정합니다. 카테고리를 만들거나
108
+ 편집할 때 콘텐츠 유형을 하나 고르면, 공개 `collections[].categories`에도 그
109
+ 유형의 주제로 나타납니다. 콘텐츠 유형 설정과 카테고리 소속을 서로 다른 화면에서
110
+ 동시에 바꿔도 JSON 배열을 덮어쓰지 않도록 별도 데이터로 저장됩니다.
111
+
107
112
  ### ⚠️ basePath는 "라우트가 있어야" 동작합니다 (데이터=DB, 라우트=코드)
108
113
 
109
- basePath·라벨·주제·플래그는 어드민에서 바꾸면 sitemap·feed·라우팅이 즉시 따라갑니다.
114
+ basePath·라벨·플래그는 어드민에서 바꾸면 sitemap·feed·라우팅이 즉시 따라갑니다.
110
115
  **단 basePath에 해당하는 페이지 파일이 사이트에 있어야** 실제로 열립니다:
111
116
 
112
117
  - `/notice`·`/blog`처럼 **이미 라우트가 있는 경로**는 어드민만으로 자유롭게 편집 → 동작.
@@ -118,7 +123,8 @@ basePath·라벨·주제·플래그는 어드민에서 바꾸면 sitemap·feed·
118
123
 
119
124
  글쓰기 화면 맨 위 **"어디에 올릴까요?"**에서 공지/블로그 카드를 고릅니다 — 이게 글의
120
125
  섹션(`collection_key`)이 됩니다. 그 아래 **주제**는 고른 섹션의 카테고리만 보입니다
121
- (블로그면 칼럼·소식 등). 주제는 선택이며, **설정 > 분류**(taxonomy)에서 미리 만들어 둡니다.
126
+ (블로그면 칼럼·소식 등). 주제는 선택이며, **글 > 분류 > 카테고리**에서 미리 만들어
127
+ 콘텐츠 유형에 연결합니다.
122
128
 
123
129
  ## 사이트 연동 코드
124
130
 
@@ -192,6 +198,9 @@ export const GET = createFeedRoute({ apiKey, siteUrl, title, collections: getCol
192
198
  공개 엔드포인트: `GET /v1/cms/public/collections` (블로그 조회와 같은 API 키).
193
199
  응답은 `RouteCollection`과 구조 호환이라 그대로 넘길 수 있습니다. 매 요청 fetch를 피하려면
194
200
  사이트 경계에서 캐시하세요(예: Next `fetch(url, { next: { revalidate: 300 } })`).
201
+ `categories`는 콘텐츠 유형 설정 JSON이 아니라 카테고리의 현재 소속을 기준으로
202
+ 서버가 조립하므로, 카테고리 이름 변경·삭제·이동이 즉시 반영됩니다. 빈 배열은
203
+ catch-all이 아니라 **그 유형에 연결된 카테고리가 없음**을 뜻합니다.
195
204
 
196
205
  ### 섹션 목록 페이지 (공지/블로그 분리 렌더)
197
206
 
@@ -10,7 +10,8 @@ description: API 키 발급, 환경변수 설정, 패키지 설치, 첫 콘텐
10
10
  1. 어드민(`admin.roottale.com`) 로그인
11
11
  2. **설정 > 사이트 연결 키** 메뉴로 이동
12
12
  3. 새 키 발급 — 권한 선택:
13
- - **read** (기본): 발행된 콘텐츠 조회만. 외부 사이트 연동은 이걸로 충분
13
+ - **read** (기본): 읽기 전용. 공개 콘텐츠와 관리 API의
14
+ 초안·예약·비공개 글, 미디어 목록을 조회할 수 있음
14
15
  - **read_write**: 글 작성·수정·발행, 카테고리/태그, 미디어 업로드 포함
15
16
  4. 발급된 키(`rtlk_cust_` + 24자)는 **발급 직후 1회만 평문 표시**됩니다. 바로
16
17
  복사해 환경변수에 저장하세요.
@@ -127,6 +128,21 @@ npx -y @roottale/cms-mcp cli posts create \
127
128
  npx -y @roottale/cms-mcp cli posts publish "<글 id>"
128
129
  ```
129
130
 
131
+ 상위 `cli --help`는 `posts`, `media` 그룹만 보여줍니다. 전체 하위 명령은
132
+ 다음 도움말에서 확인하세요.
133
+
134
+ ```bash
135
+ npx -y @roottale/cms-mcp cli posts --help
136
+ npx -y @roottale/cms-mcp cli media --help
137
+
138
+ # 특정 명령의 모든 옵션
139
+ npx -y @roottale/cms-mcp cli posts create --help
140
+ npx -y @roottale/cms-mcp cli media upload --help
141
+ ```
142
+
143
+ 글 명령은 `list`, `create`, `update`, `publish`, `unpublish`, `set-terms`,
144
+ 미디어 명령은 `list`, `upload`, `update`, `delete`를 제공합니다.
145
+
130
146
  내부 운영 저장소에서는 같은 기능을 `rt cms posts ...`,
131
147
  `rt cms media ...` 명령으로도 실행할 수 있습니다.
132
148
 
@@ -60,7 +60,7 @@ URL을 비우고 저장하면 웹훅이 비활성화됩니다.
60
60
 
61
61
  - 게시물 생성/발행/수정/삭제/발행 취소
62
62
  - 게시물의 카테고리·태그 변경 (블로그 카드의 카테고리 라벨이 바뀌므로)
63
- - 분류(taxonomy) 용어 삭제 (해당 용어를 참조하던 발행 글 전부)
63
+ - 분류(taxonomy) 용어 생성/수정/순서 변경/삭제
64
64
  - 블로그 표시 설정 변경 (TOC, 작성자/발행일, 작성자 카드)
65
65
  - 디자인 토큰 변경
66
66
  - 수동 revalidation API 호출
@@ -74,6 +74,11 @@ URL을 비우고 저장하면 웹훅이 비활성화됩니다.
74
74
  - 상세 페이지는 현재 slug + payload의 `paths` 힌트 경로 모두 revalidate
75
75
  - 홈에 최신 글 섹션이 있으면 `alsoRevalidate`에 `/` 포함
76
76
 
77
+ 분류 변경은 `taxonomy.updated` 이벤트 **한 번**으로 전달됩니다. payload의
78
+ `paths`에는 영향을 받는 글 상세 경로와 카테고리 모음 경로가 중복 없이 들어갑니다.
79
+ 연결된 글마다 웹훅을 따로 보내지 않으므로, 카테고리 하나를 바꿔도 수십 번 재검증되는
80
+ 문제가 없습니다.
81
+
77
82
  ## 저수준 검증 — verifyRootTaleWebhook
78
83
 
79
84
  `createRevalidateRoute`를 못 쓰는 환경(다른 프레임워크 등)은
@@ -92,8 +97,12 @@ export async function POST(request: Request) {
92
97
  if (!result.ok) {
93
98
  return Response.json({ reason: result.reason }, { status: 401 });
94
99
  }
95
- // result.event: "post.published" | "post.updated" | "post.deleted"
96
- // result.payload.paths: 갱신할 root-relative 경로 배열
100
+ const payload = JSON.parse(rawBody) as { paths?: unknown };
101
+ const paths = Array.isArray(payload.paths)
102
+ ? payload.paths.filter((path): path is string => typeof path === "string")
103
+ : [];
104
+ // result.event: "post.published" | "post.updated" | "post.deleted" | "taxonomy.updated"
105
+ for (const path of paths) revalidatePath(path);
97
106
  return Response.json({ ok: true });
98
107
  }
99
108
  ```
@@ -5,8 +5,9 @@ import { createRevalidateRoute } from "@roottale/cms-renderer-next/routes";
5
5
 
6
6
  function revalidateBlogPath(path: string): void {
7
7
  revalidatePath(path);
8
- // 카테고리/태그 인덱스 같은 동적 경로가 있으면 함께 무효화
9
- // if (path === "/blog/categories") revalidatePath("/blog/categories/[category]", "page");
8
+ if (path === "/blog/categories") {
9
+ revalidatePath("/blog/categories/[category]", "page");
10
+ }
10
11
  }
11
12
 
12
13
  export const POST = createRevalidateRoute({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roottale/cms-mcp",
3
- "version": "0.44.0",
3
+ "version": "0.45.0",
4
4
  "type": "module",
5
5
  "description": "RootTale CMS MCP server and CLI for post publishing, media uploads, integration docs, and public API access.",
6
6
  "bin": {