@roottale/cms-mcp 0.42.0 → 0.44.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/dist/index.js CHANGED
@@ -3,6 +3,535 @@
3
3
  // src/index.ts
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
5
 
6
+ // src/cli.ts
7
+ import { readFile as readFile2 } from "fs/promises";
8
+ import { Command, InvalidArgumentError } from "commander";
9
+
10
+ // src/api.ts
11
+ import ky from "ky";
12
+ var DEFAULT_API_BASE = "https://api.roottale.com";
13
+ var CmsAutomationError = class extends Error {
14
+ name = "CmsAutomationError";
15
+ };
16
+ function apiConfig() {
17
+ const apiKey = process.env.ROOTTALE_API_KEY;
18
+ if (!apiKey) {
19
+ throw new CmsAutomationError(
20
+ "ROOTTALE_API_KEY \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uC5B4\uB4DC\uBBFC \uC124\uC815 > \uC0AC\uC774\uD2B8 \uC5F0\uACB0 \uD0A4\uC5D0\uC11C read_write \uD0A4\uB97C \uBC1C\uAE09\uD574 MCP env\uC5D0 \uCD94\uAC00\uD558\uC138\uC694."
21
+ );
22
+ }
23
+ return {
24
+ apiKey,
25
+ baseUrl: (process.env.ROOTTALE_API_BASE ?? DEFAULT_API_BASE).replace(
26
+ /\/+$/,
27
+ ""
28
+ )
29
+ };
30
+ }
31
+ async function requestRootTaleApi(request) {
32
+ const { apiKey, baseUrl } = apiConfig();
33
+ const url = new URL(`${baseUrl}${request.pathname}`);
34
+ for (const [key, value] of Object.entries(request.query ?? {})) {
35
+ if (value !== void 0) url.searchParams.set(key, value);
36
+ }
37
+ return ky(url, {
38
+ method: request.method,
39
+ headers: { authorization: `Bearer ${apiKey}` },
40
+ json: request.body,
41
+ retry: 0,
42
+ timeout: 3e4
43
+ }).json();
44
+ }
45
+ async function putUpload(uploadUrl, bytes, contentType) {
46
+ const body = new Uint8Array(bytes.byteLength);
47
+ body.set(bytes);
48
+ await ky.put(uploadUrl, {
49
+ body: body.buffer,
50
+ headers: { "content-type": contentType },
51
+ retry: 0,
52
+ timeout: 12e4
53
+ });
54
+ }
55
+
56
+ // src/automation-definitions.ts
57
+ var AUTOMATION_TOOL_DEFINITIONS = [
58
+ tool("listManagedCmsPosts", "\uCD08\uC548\xB7\uC608\uC57D\xB7\uBC1C\uD589 \uAE00\uC744 \uD3EC\uD568\uD55C \uAD00\uB9AC\uC6A9 \uAE00 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4.", {
59
+ siteId: stringProperty("\uC0AC\uC774\uD2B8 ID"),
60
+ limit: numberProperty("1-100"),
61
+ type: enumProperty(["post", "page"]),
62
+ status: enumProperty(["draft", "published", "scheduled", "private"]),
63
+ cursor: stringProperty("\uC774\uC804 \uC751\uB2F5\uC758 next_cursor")
64
+ }),
65
+ tool(
66
+ "createCmsPost",
67
+ "Tiptap bodyJson\uC73C\uB85C \uAE00 \uB610\uB294 \uD398\uC774\uC9C0\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4. \uAE30\uBCF8\uC740 \uCD08\uC548\uC774\uBA70 publish=true\uBA74 \uC989\uC2DC \uBC1C\uD589\uD569\uB2C8\uB2E4.",
68
+ {
69
+ siteId: stringProperty("\uC0AC\uC774\uD2B8 ID"),
70
+ type: enumProperty(["post", "page"]),
71
+ title: stringProperty("\uC81C\uBAA9"),
72
+ slug: stringProperty("\uC2AC\uB7EC\uADF8"),
73
+ bodyJson: { type: "object", description: "Tiptap \uBB38\uC11C JSON" },
74
+ excerpt: nullableStringProperty("\uC694\uC57D"),
75
+ featuredMediaId: nullableStringProperty("\uB300\uD45C \uC774\uBBF8\uC9C0 media id"),
76
+ publish: { type: "boolean" },
77
+ scheduledAt: stringProperty("\uBBF8\uB798 ISO 8601 \uBC1C\uD589 \uC2DC\uAC01"),
78
+ categoryIds: arrayProperty("\uCE74\uD14C\uACE0\uB9AC term id"),
79
+ tagIds: arrayProperty("\uD0DC\uADF8 term id")
80
+ },
81
+ ["title", "slug", "bodyJson"]
82
+ ),
83
+ tool("updateCmsPost", "\uAE30\uC874 CMS \uAE00\uC744 \uBD80\uBD84 \uC218\uC815\uD569\uB2C8\uB2E4.", {
84
+ postId: stringProperty("\uAE00 ID"),
85
+ siteId: stringProperty("\uC0AC\uC774\uD2B8 ID"),
86
+ title: stringProperty("\uC81C\uBAA9"),
87
+ slug: stringProperty("\uC2AC\uB7EC\uADF8"),
88
+ bodyJson: { type: "object", description: "Tiptap \uBB38\uC11C JSON" },
89
+ excerpt: nullableStringProperty("\uC694\uC57D"),
90
+ featuredMediaId: nullableStringProperty("\uB300\uD45C \uC774\uBBF8\uC9C0 media id"),
91
+ scheduledAt: nullableStringProperty("\uC608\uC57D \uC2DC\uAC01")
92
+ }, ["postId"]),
93
+ tool("publishCmsPost", "\uCD08\uC548 \uB610\uB294 \uC608\uC57D \uAE00\uC744 \uC989\uC2DC \uBC1C\uD589\uD558\uACE0 \uC6F9\uD6C5\uC744 \uC804\uC1A1\uD569\uB2C8\uB2E4.", {
94
+ postId: stringProperty("\uAE00 ID"),
95
+ siteId: stringProperty("\uC0AC\uC774\uD2B8 ID")
96
+ }, ["postId"]),
97
+ tool("unpublishCmsPost", "\uBC1C\uD589 \uAE00\uC744 \uCD08\uC548\uC73C\uB85C \uB418\uB3CC\uB9BD\uB2C8\uB2E4.", {
98
+ postId: stringProperty("\uAE00 ID"),
99
+ siteId: stringProperty("\uC0AC\uC774\uD2B8 ID")
100
+ }, ["postId"]),
101
+ tool("setCmsPostTerms", "\uAE00\uC758 \uCE74\uD14C\uACE0\uB9AC \uB610\uB294 \uD0DC\uADF8 \uC5F0\uACB0\uC744 \uAD50\uCCB4\uD569\uB2C8\uB2E4.", {
102
+ postId: stringProperty("\uAE00 ID"),
103
+ siteId: stringProperty("\uC0AC\uC774\uD2B8 ID"),
104
+ taxonomy: enumProperty(["category", "tag"]),
105
+ termIds: arrayProperty("term id")
106
+ }, ["postId", "taxonomy", "termIds"]),
107
+ tool("listCmsMedia", "CMS \uBBF8\uB514\uC5B4 \uB77C\uC774\uBE0C\uB7EC\uB9AC\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.", {
108
+ siteId: stringProperty("\uC0AC\uC774\uD2B8 ID"),
109
+ limit: numberProperty("1-100"),
110
+ offset: numberProperty("0 \uC774\uC0C1")
111
+ }),
112
+ tool(
113
+ "uploadCmsMedia",
114
+ "\uB85C\uCEEC \uC774\uBBF8\uC9C0/PDF \uD30C\uC77C\uC744 \uC5C5\uB85C\uB4DC\uD558\uACE0 CMS media id\uC640 \uACF5\uAC1C URL\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4.",
115
+ {
116
+ filePath: stringProperty("\uB85C\uCEEC \uD30C\uC77C \uC808\uB300/\uC0C1\uB300 \uACBD\uB85C"),
117
+ siteId: stringProperty("\uC0AC\uC774\uD2B8 ID"),
118
+ contentType: enumProperty([
119
+ "image/jpeg",
120
+ "image/png",
121
+ "image/webp",
122
+ "image/gif",
123
+ "application/pdf"
124
+ ]),
125
+ alt: nullableStringProperty("\uB300\uCCB4 \uD14D\uC2A4\uD2B8"),
126
+ caption: nullableStringProperty("\uCEA1\uC158")
127
+ },
128
+ ["filePath"]
129
+ ),
130
+ tool("updateCmsMedia", "CMS \uBBF8\uB514\uC5B4\uC758 \uB300\uCCB4 \uD14D\uC2A4\uD2B8 \uB610\uB294 \uCEA1\uC158\uC744 \uC218\uC815\uD569\uB2C8\uB2E4.", {
131
+ mediaId: stringProperty("media id"),
132
+ siteId: stringProperty("\uC0AC\uC774\uD2B8 ID"),
133
+ alt: nullableStringProperty("\uB300\uCCB4 \uD14D\uC2A4\uD2B8"),
134
+ caption: nullableStringProperty("\uCEA1\uC158")
135
+ }, ["mediaId"]),
136
+ tool("deleteCmsMedia", "CMS \uBBF8\uB514\uC5B4\uB97C R2\uC640 \uB77C\uC774\uBE0C\uB7EC\uB9AC\uC5D0\uC11C \uC0AD\uC81C\uD569\uB2C8\uB2E4.", {
137
+ mediaId: stringProperty("media id"),
138
+ siteId: stringProperty("\uC0AC\uC774\uD2B8 ID")
139
+ }, ["mediaId"])
140
+ ];
141
+ function isAutomationToolName(name) {
142
+ return AUTOMATION_TOOL_DEFINITIONS.some(
143
+ (toolDefinition) => toolDefinition.name === name
144
+ );
145
+ }
146
+ function tool(name, description, properties, required = []) {
147
+ return {
148
+ name,
149
+ description,
150
+ inputSchema: {
151
+ type: "object",
152
+ properties,
153
+ ...required.length > 0 ? { required } : {},
154
+ additionalProperties: false
155
+ }
156
+ };
157
+ }
158
+ function stringProperty(description) {
159
+ return { type: "string", description };
160
+ }
161
+ function nullableStringProperty(description) {
162
+ return { type: ["string", "null"], description };
163
+ }
164
+ function numberProperty(description) {
165
+ return { type: "number", description };
166
+ }
167
+ function enumProperty(values) {
168
+ return { type: "string", enum: values };
169
+ }
170
+ function arrayProperty(description) {
171
+ return { type: "array", items: { type: "string" }, description };
172
+ }
173
+
174
+ // src/automation-media.ts
175
+ import { readFile } from "fs/promises";
176
+ import { basename, extname, resolve } from "path";
177
+ import { z as z2 } from "zod";
178
+
179
+ // src/automation-schemas.ts
180
+ import { z } from "zod";
181
+ var SiteId = z.string().min(1).optional();
182
+ var PostId = z.string().min(1);
183
+ var NullableText = z.string().nullable().optional();
184
+ var CreatePostInput = z.object({
185
+ siteId: SiteId,
186
+ type: z.enum(["post", "page"]).optional(),
187
+ title: z.string().min(1).max(500),
188
+ slug: z.string().min(1).max(120),
189
+ bodyJson: z.record(z.string(), z.unknown()),
190
+ excerpt: z.string().max(1e3).nullable().optional(),
191
+ featuredMediaId: z.string().nullable().optional(),
192
+ publish: z.boolean().optional(),
193
+ scheduledAt: z.string().datetime({ offset: true }).optional(),
194
+ categoryIds: z.array(z.string()).optional(),
195
+ tagIds: z.array(z.string()).optional()
196
+ }).strict();
197
+ var UpdatePostInput = z.object({
198
+ postId: PostId,
199
+ siteId: SiteId,
200
+ title: z.string().min(1).max(500).optional(),
201
+ slug: z.string().min(1).max(120).optional(),
202
+ bodyJson: z.record(z.string(), z.unknown()).optional(),
203
+ excerpt: NullableText,
204
+ featuredMediaId: z.string().nullable().optional(),
205
+ scheduledAt: z.string().datetime({ offset: true }).nullable().optional()
206
+ }).strict();
207
+ var PostLifecycleInput = z.object({ postId: PostId, siteId: SiteId }).strict();
208
+ var ListManagedPostsInput = z.object({
209
+ siteId: SiteId,
210
+ limit: z.number().int().min(1).max(100).optional(),
211
+ type: z.enum(["post", "page"]).optional(),
212
+ status: z.enum(["draft", "published", "scheduled", "private"]).optional(),
213
+ cursor: z.string().optional()
214
+ }).strict();
215
+ var SetTermsInput = z.object({
216
+ postId: PostId,
217
+ siteId: SiteId,
218
+ taxonomy: z.enum(["category", "tag"]),
219
+ termIds: z.array(z.string())
220
+ }).strict();
221
+ var UploadMediaInput = z.object({
222
+ filePath: z.string().min(1),
223
+ siteId: SiteId,
224
+ contentType: z.enum([
225
+ "image/jpeg",
226
+ "image/png",
227
+ "image/webp",
228
+ "image/gif",
229
+ "application/pdf"
230
+ ]).optional(),
231
+ alt: NullableText,
232
+ caption: NullableText
233
+ }).strict();
234
+ var ListMediaInput = z.object({
235
+ siteId: SiteId,
236
+ limit: z.number().int().min(1).max(100).optional(),
237
+ offset: z.number().int().nonnegative().optional()
238
+ }).strict();
239
+ var UpdateMediaInput = z.object({
240
+ mediaId: z.string().min(1),
241
+ siteId: SiteId,
242
+ alt: NullableText,
243
+ caption: NullableText
244
+ }).strict().refine((input) => input.alt !== void 0 || input.caption !== void 0, {
245
+ message: "alt \uB610\uB294 caption \uC911 \uD558\uB098 \uC774\uC0C1\uC774 \uD544\uC694\uD569\uB2C8\uB2E4."
246
+ });
247
+ var DeleteMediaInput = z.object({ mediaId: z.string().min(1), siteId: SiteId }).strict();
248
+
249
+ // src/automation-media.ts
250
+ async function uploadCmsMedia(args) {
251
+ const input = UploadMediaInput.parse(args);
252
+ const filePath = resolve(input.filePath);
253
+ const bytes = await readFile(filePath);
254
+ const contentType = input.contentType ?? inferContentType(filePath);
255
+ if (!contentType) {
256
+ throw new CmsAutomationError(
257
+ "contentType\uC744 \uCD94\uB860\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. jpg/png/webp/gif/pdf \uD30C\uC77C\uC774 \uC544\uB2C8\uBA74 contentType\uC744 \uC9C0\uC815\uD558\uC138\uC694."
258
+ );
259
+ }
260
+ const requested = z2.object({
261
+ upload_url: z2.string().url(),
262
+ r2_key: z2.string().min(1)
263
+ }).parse(
264
+ await requestRootTaleApi({
265
+ method: "POST",
266
+ pathname: "/v1/cms/media/uploads",
267
+ body: {
268
+ site_id: input.siteId,
269
+ original_name: basename(filePath),
270
+ content_type: contentType,
271
+ size_bytes: bytes.byteLength
272
+ }
273
+ })
274
+ );
275
+ await putUpload(requested.upload_url, bytes, contentType);
276
+ return requestRootTaleApi({
277
+ method: "POST",
278
+ pathname: "/v1/cms/media/uploads/complete",
279
+ body: {
280
+ site_id: input.siteId,
281
+ r2_key: requested.r2_key,
282
+ original_name: basename(filePath),
283
+ content_type: contentType,
284
+ size_bytes: bytes.byteLength,
285
+ alt: input.alt,
286
+ caption: input.caption
287
+ }
288
+ });
289
+ }
290
+ function inferContentType(filePath) {
291
+ switch (extname(filePath).toLowerCase()) {
292
+ case ".jpg":
293
+ case ".jpeg":
294
+ return "image/jpeg";
295
+ case ".png":
296
+ return "image/png";
297
+ case ".webp":
298
+ return "image/webp";
299
+ case ".gif":
300
+ return "image/gif";
301
+ case ".pdf":
302
+ return "application/pdf";
303
+ default:
304
+ return void 0;
305
+ }
306
+ }
307
+
308
+ // src/automation-tools.ts
309
+ async function callAutomationTool(name, args) {
310
+ switch (name) {
311
+ case "listManagedCmsPosts": {
312
+ const input = ListManagedPostsInput.parse(args);
313
+ return requestRootTaleApi({
314
+ method: "GET",
315
+ pathname: "/v1/cms/posts",
316
+ query: optionalQuery({
317
+ site_id: input.siteId,
318
+ limit: input.limit,
319
+ type: input.type,
320
+ status: input.status,
321
+ cursor: input.cursor
322
+ })
323
+ });
324
+ }
325
+ case "createCmsPost": {
326
+ const input = CreatePostInput.parse(args);
327
+ return requestRootTaleApi({
328
+ method: "POST",
329
+ pathname: "/v1/cms/posts",
330
+ body: {
331
+ site_id: input.siteId,
332
+ type: input.type,
333
+ title: input.title,
334
+ slug: input.slug,
335
+ body_json: input.bodyJson,
336
+ excerpt: input.excerpt,
337
+ featured_media_id: input.featuredMediaId,
338
+ publish: input.publish,
339
+ scheduled_at: input.scheduledAt,
340
+ category_ids: input.categoryIds,
341
+ tag_ids: input.tagIds
342
+ }
343
+ });
344
+ }
345
+ case "updateCmsPost": {
346
+ const input = UpdatePostInput.parse(args);
347
+ return requestRootTaleApi({
348
+ method: "PATCH",
349
+ pathname: `/v1/cms/posts/${encodeURIComponent(input.postId)}`,
350
+ body: {
351
+ site_id: input.siteId,
352
+ title: input.title,
353
+ slug: input.slug,
354
+ body_json: input.bodyJson,
355
+ excerpt: input.excerpt,
356
+ featured_media_id: input.featuredMediaId,
357
+ scheduled_at: input.scheduledAt
358
+ }
359
+ });
360
+ }
361
+ case "publishCmsPost":
362
+ case "unpublishCmsPost": {
363
+ const input = PostLifecycleInput.parse(args);
364
+ const action = name === "publishCmsPost" ? "publish" : "unpublish";
365
+ return requestRootTaleApi({
366
+ method: "POST",
367
+ pathname: `/v1/cms/posts/${encodeURIComponent(input.postId)}/${action}`,
368
+ body: { site_id: input.siteId }
369
+ });
370
+ }
371
+ case "setCmsPostTerms": {
372
+ const input = SetTermsInput.parse(args);
373
+ return requestRootTaleApi({
374
+ method: "PATCH",
375
+ pathname: `/v1/cms/posts/${encodeURIComponent(input.postId)}/terms`,
376
+ body: {
377
+ site_id: input.siteId,
378
+ taxonomy: input.taxonomy,
379
+ term_ids: input.termIds
380
+ }
381
+ });
382
+ }
383
+ case "listCmsMedia": {
384
+ const input = ListMediaInput.parse(args);
385
+ return requestRootTaleApi({
386
+ method: "GET",
387
+ pathname: "/v1/cms/media",
388
+ query: optionalQuery({
389
+ site_id: input.siteId,
390
+ limit: input.limit,
391
+ offset: input.offset
392
+ })
393
+ });
394
+ }
395
+ case "uploadCmsMedia":
396
+ return uploadCmsMedia(args);
397
+ case "updateCmsMedia": {
398
+ const input = UpdateMediaInput.parse(args);
399
+ return requestRootTaleApi({
400
+ method: "PATCH",
401
+ pathname: `/v1/cms/media/${encodeURIComponent(input.mediaId)}`,
402
+ body: {
403
+ site_id: input.siteId,
404
+ alt: input.alt,
405
+ caption: input.caption
406
+ }
407
+ });
408
+ }
409
+ case "deleteCmsMedia": {
410
+ const input = DeleteMediaInput.parse(args);
411
+ return requestRootTaleApi({
412
+ method: "DELETE",
413
+ pathname: `/v1/cms/media/${encodeURIComponent(input.mediaId)}`,
414
+ query: optionalQuery({ site_id: input.siteId })
415
+ });
416
+ }
417
+ default:
418
+ return assertNever(name);
419
+ }
420
+ }
421
+ function optionalQuery(values) {
422
+ return Object.fromEntries(
423
+ Object.entries(values).filter((entry) => entry[1] !== void 0).map(([key, value]) => [key, String(value)])
424
+ );
425
+ }
426
+ function assertNever(value) {
427
+ throw new CmsAutomationError(`Unknown automation tool: ${String(value)}`);
428
+ }
429
+
430
+ // src/cli.ts
431
+ function createCmsCli() {
432
+ const cli = new Command().name("roottale-cms").description("RootTale CMS \uAE00\xB7\uC378\uB124\uC77C\xB7\uBCF8\uBB38 \uC774\uBBF8\uC9C0 \uC790\uB3D9\uD654 CLI");
433
+ registerPostCommands(cli);
434
+ registerMediaCommands(cli);
435
+ return cli;
436
+ }
437
+ async function runCmsCli(argv) {
438
+ try {
439
+ await createCmsCli().parseAsync([
440
+ argv[0] ?? "node",
441
+ "roottale-cms",
442
+ ...argv.slice(3)
443
+ ]);
444
+ } catch (error) {
445
+ const message = error instanceof Error ? error.message : String(error);
446
+ process.stderr.write(`${message}
447
+ `);
448
+ process.exitCode = 1;
449
+ }
450
+ }
451
+ function registerPostCommands(cli) {
452
+ const posts = cli.command("posts").description("CMS \uAE00 \uC790\uB3D9\uD654");
453
+ posts.command("list").description("\uCD08\uC548\xB7\uC608\uC57D\xB7\uBC1C\uD589 \uAE00 \uBAA9\uB85D \uC870\uD68C").option("--site-id <id>", "\uC0AC\uC774\uD2B8 ID").option("--limit <number>", "\uC870\uD68C \uAC1C\uC218", integer, 20).option("--cursor <cursor>", "\uB2E4\uC74C \uD398\uC774\uC9C0 \uCEE4\uC11C").option("--type <type>", "post \uB610\uB294 page").option("--status <status>", "draft, published, scheduled, private").action(async (options) => {
454
+ await run("listManagedCmsPosts", options);
455
+ });
456
+ posts.command("create").description("Tiptap JSON \uBCF8\uBB38\uC73C\uB85C \uC0C8 \uAE00 \uC0DD\uC131").requiredOption("--title <title>", "\uAE00 \uC81C\uBAA9").requiredOption("--slug <slug>", "URL \uC2AC\uB7EC\uADF8").requiredOption("--body-file <path>", "Tiptap \uBCF8\uBB38 JSON \uD30C\uC77C").option("--site-id <id>", "\uC0AC\uC774\uD2B8 ID").option("--type <type>", "post \uB610\uB294 page", "post").option("--excerpt <text>", "\uC694\uC57D").option("--featured-media-id <id>", "\uC378\uB124\uC77C \uBBF8\uB514\uC5B4 ID").option("--publish", "\uC989\uC2DC \uBC1C\uD589").option("--scheduled-at <iso>", "\uC608\uC57D \uBC1C\uD589 ISO \uC2DC\uAC01").option("--category-ids <ids>", "\uC27C\uD45C\uB85C \uAD6C\uBD84\uD55C \uCE74\uD14C\uACE0\uB9AC ID").option("--tag-ids <ids>", "\uC27C\uD45C\uB85C \uAD6C\uBD84\uD55C \uD0DC\uADF8 ID").action(async (options) => {
457
+ await run("createCmsPost", {
458
+ ...options,
459
+ bodyJson: await readJsonObject(options.bodyFile),
460
+ categoryIds: commaSeparated(options.categoryIds),
461
+ tagIds: commaSeparated(options.tagIds),
462
+ bodyFile: void 0
463
+ });
464
+ });
465
+ posts.command("update").description("\uAE30\uC874 \uAE00\uC758 \uC77C\uBD80 \uD544\uB4DC \uC218\uC815").argument("<post-id>", "\uAE00 ID").option("--site-id <id>", "\uC0AC\uC774\uD2B8 ID").option("--title <title>", "\uAE00 \uC81C\uBAA9").option("--slug <slug>", "URL \uC2AC\uB7EC\uADF8").option("--body-file <path>", "Tiptap \uBCF8\uBB38 JSON \uD30C\uC77C").option("--excerpt <text>", "\uC694\uC57D").option("--featured-media-id <id>", "\uC378\uB124\uC77C \uBBF8\uB514\uC5B4 ID").option("--clear-featured-media", "\uC378\uB124\uC77C \uC5F0\uACB0 \uD574\uC81C").option("--scheduled-at <iso>", "\uC608\uC57D \uBC1C\uD589 ISO \uC2DC\uAC01").option("--clear-schedule", "\uC608\uC57D \uD574\uC81C").action(async (postId, options) => {
466
+ await run("updateCmsPost", {
467
+ postId,
468
+ siteId: options.siteId,
469
+ title: options.title,
470
+ slug: options.slug,
471
+ bodyJson: options.bodyFile ? await readJsonObject(options.bodyFile) : void 0,
472
+ excerpt: options.excerpt,
473
+ featuredMediaId: options.clearFeaturedMedia ? null : options.featuredMediaId,
474
+ scheduledAt: options.clearSchedule ? null : options.scheduledAt
475
+ });
476
+ });
477
+ lifecycle(posts, "publish", "\uAE00\uC744 \uC989\uC2DC \uBC1C\uD589", "publishCmsPost");
478
+ lifecycle(posts, "unpublish", "\uBC1C\uD589 \uAE00\uC744 \uCD08\uC548\uC73C\uB85C \uC804\uD658", "unpublishCmsPost");
479
+ posts.command("set-terms").description("\uAE00\uC758 \uCE74\uD14C\uACE0\uB9AC \uB610\uB294 \uD0DC\uADF8 \uC804\uCCB4 \uAD50\uCCB4").argument("<post-id>", "\uAE00 ID").requiredOption("--taxonomy <type>", "category \uB610\uB294 tag").requiredOption("--term-ids <ids>", "\uC27C\uD45C\uB85C \uAD6C\uBD84\uD55C term ID").option("--site-id <id>", "\uC0AC\uC774\uD2B8 ID").action(async (postId, options) => {
480
+ await run("setCmsPostTerms", {
481
+ postId,
482
+ siteId: options.siteId,
483
+ taxonomy: options.taxonomy,
484
+ termIds: commaSeparated(options.termIds) ?? []
485
+ });
486
+ });
487
+ }
488
+ function registerMediaCommands(cli) {
489
+ const media = cli.command("media").description("CMS \uBBF8\uB514\uC5B4 \uC790\uB3D9\uD654");
490
+ media.command("list").description("\uBBF8\uB514\uC5B4 \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC870\uD68C").option("--site-id <id>", "\uC0AC\uC774\uD2B8 ID").option("--limit <number>", "\uC870\uD68C \uAC1C\uC218", integer, 20).option("--offset <number>", "\uC870\uD68C \uC2DC\uC791 \uC704\uCE58", integer, 0).action(async (options) => {
491
+ await run("listCmsMedia", options);
492
+ });
493
+ media.command("upload").description("\uB85C\uCEEC \uD30C\uC77C\uC744 \uC5C5\uB85C\uB4DC\uD558\uACE0 CMS \uBBF8\uB514\uC5B4\uB85C \uB4F1\uB85D").argument("<file>", "jpg, png, webp, gif \uB610\uB294 pdf \uD30C\uC77C").option("--site-id <id>", "\uC0AC\uC774\uD2B8 ID").option("--content-type <type>", "MIME type \uC9C1\uC811 \uC9C0\uC815").option("--alt <text>", "\uB300\uCCB4 \uD14D\uC2A4\uD2B8").option("--caption <text>", "\uCEA1\uC158").action(async (filePath, options) => {
494
+ await run("uploadCmsMedia", { filePath, ...options });
495
+ });
496
+ media.command("update").description("\uBBF8\uB514\uC5B4 \uB300\uCCB4 \uD14D\uC2A4\uD2B8 \uB610\uB294 \uCEA1\uC158 \uC218\uC815").argument("<media-id>", "\uBBF8\uB514\uC5B4 ID").option("--site-id <id>", "\uC0AC\uC774\uD2B8 ID").option("--alt <text>", "\uB300\uCCB4 \uD14D\uC2A4\uD2B8").option("--caption <text>", "\uCEA1\uC158").action(async (mediaId, options) => {
497
+ await run("updateCmsMedia", { mediaId, ...options });
498
+ });
499
+ media.command("delete").description("\uBBF8\uB514\uC5B4 \uD30C\uC77C\uACFC \uB77C\uC774\uBE0C\uB7EC\uB9AC \uD56D\uBAA9 \uC0AD\uC81C").argument("<media-id>", "\uBBF8\uB514\uC5B4 ID").option("--site-id <id>", "\uC0AC\uC774\uD2B8 ID").action(async (mediaId, options) => {
500
+ await run("deleteCmsMedia", { mediaId, ...options });
501
+ });
502
+ }
503
+ function lifecycle(posts, commandName, description, toolName) {
504
+ posts.command(commandName).description(description).argument("<post-id>", "\uAE00 ID").option("--site-id <id>", "\uC0AC\uC774\uD2B8 ID").action(async (postId, options) => {
505
+ await run(toolName, { postId, ...options });
506
+ });
507
+ }
508
+ async function run(name, args) {
509
+ const definedArgs = Object.fromEntries(
510
+ Object.entries(args).filter(([, value]) => value !== void 0)
511
+ );
512
+ const result = await callAutomationTool(name, definedArgs);
513
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
514
+ `);
515
+ }
516
+ async function readJsonObject(filePath) {
517
+ const parsed = JSON.parse(await readFile2(filePath, "utf8"));
518
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
519
+ throw new InvalidArgumentError("\uBCF8\uBB38 \uD30C\uC77C\uC740 Tiptap JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
520
+ }
521
+ return parsed;
522
+ }
523
+ function commaSeparated(value) {
524
+ if (value === void 0) return void 0;
525
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
526
+ }
527
+ function integer(value) {
528
+ const parsed = Number.parseInt(value, 10);
529
+ if (!Number.isInteger(parsed) || parsed < 0) {
530
+ throw new InvalidArgumentError("0 \uC774\uC0C1\uC758 \uC815\uC218\uB97C \uC785\uB825\uD558\uC138\uC694.");
531
+ }
532
+ return parsed;
533
+ }
534
+
6
535
  // src/server.ts
7
536
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8
537
 
@@ -13,10 +542,10 @@ import {
13
542
  ListToolsRequestSchema,
14
543
  McpError
15
544
  } from "@modelcontextprotocol/sdk/types.js";
16
- import { z } from "zod";
545
+ import { z as z3 } from "zod";
17
546
 
18
547
  // src/docs.ts
19
- import { readdir, readFile } from "fs/promises";
548
+ import { readdir, readFile as readFile3 } from "fs/promises";
20
549
  import path from "path";
21
550
  import { fileURLToPath } from "url";
22
551
  var PKG_ROOT = fileURLToPath(new URL("..", import.meta.url));
@@ -53,7 +582,7 @@ async function listDocs() {
53
582
  const files = (await walkFiles(DOCS_ROOT)).filter((f) => f.endsWith(".md"));
54
583
  return Promise.all(
55
584
  files.map(async (rel) => {
56
- const content = await readFile(path.join(DOCS_ROOT, rel), "utf8");
585
+ const content = await readFile3(path.join(DOCS_ROOT, rel), "utf8");
57
586
  const fm = parseFrontmatter(content);
58
587
  return { path: rel, title: fm.title || rel, description: fm.description };
59
588
  })
@@ -63,7 +592,7 @@ async function readDoc(relPath) {
63
592
  const resolved = path.resolve(DOCS_ROOT, relPath);
64
593
  if (!resolved.startsWith(DOCS_ROOT + path.sep)) return null;
65
594
  try {
66
- return await readFile(resolved, "utf8");
595
+ return await readFile3(resolved, "utf8");
67
596
  } catch {
68
597
  return null;
69
598
  }
@@ -87,7 +616,7 @@ async function readNextjsExampleCode() {
87
616
  const files = await walkFiles(root);
88
617
  const sections = await Promise.all(
89
618
  files.map(async (rel) => {
90
- const content = await readFile(path.join(root, rel), "utf8");
619
+ const content = await readFile3(path.join(root, rel), "utf8");
91
620
  return `--- FILE: ${rel} ---
92
621
  ${content}`;
93
622
  })
@@ -96,17 +625,16 @@ ${content}`;
96
625
  }
97
626
 
98
627
  // src/tools.ts
99
- var DEFAULT_API_BASE = "https://api.roottale.com";
100
- var ReadDocInput = z.object({ path: z.string().min(1) }).strict();
101
- var SearchDocsInput = z.object({ query: z.string().min(1) }).strict();
102
- var ListPostsInput = z.object({
103
- limit: z.number().int().min(1).max(100).optional(),
104
- type: z.enum(["post", "page"]).optional(),
105
- cursor: z.string().optional()
628
+ var ReadDocInput = z3.object({ path: z3.string().min(1) }).strict();
629
+ var SearchDocsInput = z3.object({ query: z3.string().min(1) }).strict();
630
+ var ListPostsInput = z3.object({
631
+ limit: z3.number().int().min(1).max(100).optional(),
632
+ type: z3.enum(["post", "page"]).optional(),
633
+ cursor: z3.string().optional()
106
634
  }).strict();
107
- var GetPostInput = z.object({ slugOrId: z.string().min(1) }).strict();
108
- var GetSiteKnowledgeInput = z.object({ siteId: z.string().optional() }).strict();
109
- var TOOL_DEFINITIONS = [
635
+ var GetPostInput = z3.object({ slugOrId: z3.string().min(1) }).strict();
636
+ var GetSiteKnowledgeInput = z3.object({ siteId: z3.string().optional() }).strict();
637
+ var DOCUMENTATION_TOOL_DEFINITIONS = [
110
638
  {
111
639
  name: "listRootTaleDocs",
112
640
  description: "RootTale CMS \uD1B5\uD569 \uBB38\uC11C \uC804\uCCB4 \uBAA9\uB85D(\uACBD\uB85C\xB7\uC81C\uBAA9\xB7\uC124\uBA85)\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4. RootTale \uC5F0\uB3D9 \uC791\uC5C5\uC744 \uC2DC\uC791\uD558\uAE30 \uC804 \uAC00\uC7A5 \uBA3C\uC800 \uD638\uCD9C\uD558\uC138\uC694.",
@@ -181,45 +709,28 @@ var TOOL_DEFINITIONS = [
181
709
  }
182
710
  }
183
711
  ];
184
- function requireApiKey() {
185
- const apiKey = process.env.ROOTTALE_API_KEY;
186
- if (!apiKey) {
187
- throw new McpError(
188
- ErrorCode.InvalidRequest,
189
- "ROOTTALE_API_KEY \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. MCP \uC11C\uBC84 \uC124\uC815\uC758 env\uC5D0 rtlk_cust_* \uD0A4\uB97C \uCD94\uAC00\uD558\uC138\uC694 (\uBC1C\uAE09: \uC5B4\uB4DC\uBBFC \uC124\uC815 > \uC0AC\uC774\uD2B8 \uC5F0\uACB0 \uD0A4)."
190
- );
191
- }
192
- const baseUrl = (process.env.ROOTTALE_API_BASE ?? DEFAULT_API_BASE).replace(/\/+$/, "");
193
- return { apiKey, baseUrl };
194
- }
195
- async function fetchPublicApi(pathname, params) {
196
- const { apiKey, baseUrl } = requireApiKey();
197
- const url = new URL(`${baseUrl}${pathname}`);
198
- for (const [key, value] of Object.entries(params ?? {})) {
199
- url.searchParams.set(key, value);
200
- }
201
- const response = await fetch(url, {
202
- headers: { authorization: `Bearer ${apiKey}` }
203
- });
204
- const body = await response.text();
205
- if (!response.ok) {
206
- throw new Error(`RootTale API ${response.status}: ${body.slice(0, 500)}`);
207
- }
208
- return JSON.parse(body);
209
- }
712
+ var TOOL_DEFINITIONS = [
713
+ ...DOCUMENTATION_TOOL_DEFINITIONS,
714
+ ...AUTOMATION_TOOL_DEFINITIONS
715
+ ];
210
716
  function textResponse(text) {
211
717
  return { content: [{ type: "text", text }] };
212
718
  }
213
719
  function jsonResponse(value) {
214
720
  return textResponse(JSON.stringify(value, null, 2));
215
721
  }
216
- function registerTools(server2) {
217
- server2.setRequestHandler(ListToolsRequestSchema, async () => ({
722
+ function registerTools(server) {
723
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
218
724
  tools: TOOL_DEFINITIONS.map((t) => ({ ...t }))
219
725
  }));
220
- server2.setRequestHandler(CallToolRequestSchema, async (request) => {
726
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
221
727
  const args = request.params.arguments ?? {};
222
728
  try {
729
+ if (isAutomationToolName(request.params.name)) {
730
+ return jsonResponse(
731
+ await callAutomationTool(request.params.name, args)
732
+ );
733
+ }
223
734
  switch (request.params.name) {
224
735
  case "listRootTaleDocs":
225
736
  return jsonResponse(await listDocs());
@@ -246,14 +757,21 @@ function registerTools(server2) {
246
757
  if (input.limit) params.limit = String(input.limit);
247
758
  if (input.type) params.type = input.type;
248
759
  if (input.cursor) params.cursor = input.cursor;
249
- return jsonResponse(await fetchPublicApi("/v1/cms/public/posts", params));
760
+ return jsonResponse(
761
+ await requestRootTaleApi({
762
+ method: "GET",
763
+ pathname: "/v1/cms/public/posts",
764
+ query: params
765
+ })
766
+ );
250
767
  }
251
768
  case "getPublishedPost": {
252
769
  const input = GetPostInput.parse(args);
253
770
  return jsonResponse(
254
- await fetchPublicApi(
255
- `/v1/cms/public/posts/${encodeURIComponent(input.slugOrId)}`
256
- )
771
+ await requestRootTaleApi({
772
+ method: "GET",
773
+ pathname: `/v1/cms/public/posts/${encodeURIComponent(input.slugOrId)}`
774
+ })
257
775
  );
258
776
  }
259
777
  case "getSiteKnowledge": {
@@ -261,7 +779,11 @@ function registerTools(server2) {
261
779
  const params = {};
262
780
  if (input.siteId) params.site_id = input.siteId;
263
781
  return jsonResponse(
264
- await fetchPublicApi("/v1/cms/public/site-knowledge", params)
782
+ await requestRootTaleApi({
783
+ method: "GET",
784
+ pathname: "/v1/cms/public/site-knowledge",
785
+ query: params
786
+ })
265
787
  );
266
788
  }
267
789
  default:
@@ -279,10 +801,10 @@ function registerTools(server2) {
279
801
  }
280
802
 
281
803
  // src/server.ts
282
- var VERSION = true ? "0.42.0" : "dev";
804
+ var VERSION = true ? "0.44.0" : "dev";
283
805
  var SERVER_INSTRUCTIONS = `
284
- roottale-cms-mcp\uB294 RootTale CMS\uB97C \uC678\uBD80 \uC0AC\uC774\uD2B8(\uC8FC\uB85C Next.js)\uC5D0 \uC5F0\uB3D9\uD558\uAE30 \uC704\uD55C
285
- \uD1B5\uD569 \uBB38\uC11C\xB7\uC608\uC2DC \uCF54\uB4DC\xB7\uACF5\uAC1C API \uC870\uD68C tool\uC744 \uC81C\uACF5\uD569\uB2C8\uB2E4.
806
+ roottale-cms-mcp\uB294 RootTale CMS\uB97C \uC678\uBD80 \uC0AC\uC774\uD2B8(\uC8FC\uB85C Next.js)\uC5D0 \uC5F0\uB3D9\uD558\uACE0
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.
286
808
 
287
809
  \uC9C0\uCF1C\uC57C \uD560 \uADDC\uCE59:
288
810
  - RootTale \uC5F0\uB3D9 \uAD00\uB828 \uC791\uC5C5\uC740 \uAC00\uC7A5 \uBA3C\uC800 listRootTaleDocs\uB97C \uD638\uCD9C\uD574 \uBB38\uC11C \uBAA9\uB85D\uC744 \uD30C\uC545\uD558\uC138\uC694.
@@ -290,19 +812,28 @@ roottale-cms-mcp\uB294 RootTale CMS\uB97C \uC678\uBD80 \uC0AC\uC774\uD2B8(\uC8FC
290
812
  \uCC38\uACE0\uD55C \uD6C4 \uADF8 \uD328\uD134\uC744 \uB530\uB77C \uC791\uC131\uD558\uC138\uC694.
291
813
  - API \uD0A4(rtlk_cust_*)\uB294 \uC11C\uBC84 \uC804\uC6A9\uC785\uB2C8\uB2E4. NEXT_PUBLIC_* \uB4F1 \uBE0C\uB77C\uC6B0\uC800\uB85C \uB178\uCD9C\uB418\uB294
292
814
  \uD658\uACBD\uBCC0\uC218\uC5D0 \uC808\uB300 \uB123\uC9C0 \uB9C8\uC138\uC694.
815
+ - \uAE00\uC4F0\uAE30 \uC804\uC5D0 getSiteKnowledge\uB85C \uBE0C\uB79C\uB4DC \uBCF4\uC774\uC2A4\uC640 \uC6A9\uC5B4 \uADDC\uCE59\uC744 \uD655\uC778\uD558\uC138\uC694.
816
+ - \uC0C8 \uAE00\uC740 \uAE30\uBCF8\uC801\uC73C\uB85C draft\uB85C \uB9CC\uB4E4\uACE0, \uC0AC\uC6A9\uC790\uAC00 \uC989\uC2DC \uBC1C\uD589\uC744 \uBA85\uC2DC\uD588\uC744 \uB54C\uB9CC
817
+ publish=true \uB610\uB294 publishCmsPost\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.
818
+ - \uC774\uBBF8\uC9C0\uB294 uploadCmsMedia\uB85C \uBA3C\uC800 \uB4F1\uB85D\uD55C \uB4A4 \uBC18\uD658\uB41C id\uB97C featuredMediaId\uC5D0,
819
+ \uBC18\uD658\uB41C URL\uC744 Tiptap image \uB178\uB4DC\uC758 attrs.src\uC5D0 \uC0AC\uC6A9\uD558\uC138\uC694.
293
820
  - \uC774\uBBF8 \uD559\uC2B5\uB41C \uB0B4\uC6A9\uC774\uB354\uB77C\uB3C4 \uBCF8 \uC11C\uBC84\uC758 \uBB38\uC11C\uB85C \uB354\uBE14\uCCB4\uD06C \uD6C4 \uC791\uC5C5\uD558\uC138\uC694.
294
821
  `.trim();
295
822
  function createServer() {
296
- const server2 = new Server(
823
+ const server = new Server(
297
824
  { name: "roottale-cms", version: VERSION },
298
825
  { capabilities: { tools: {} }, instructions: SERVER_INSTRUCTIONS }
299
826
  );
300
- registerTools(server2);
301
- return server2;
827
+ registerTools(server);
828
+ return server;
302
829
  }
303
830
 
304
831
  // src/index.ts
305
- var server = createServer();
306
- var transport = new StdioServerTransport();
307
- await server.connect(transport);
832
+ if (process.argv[2] === "cli") {
833
+ await runCmsCli(process.argv);
834
+ } else {
835
+ const server = createServer();
836
+ const transport = new StdioServerTransport();
837
+ await server.connect(transport);
838
+ }
308
839
  //# sourceMappingURL=index.js.map