content-genie-mcp 1.0.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/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # ๐Ÿงž Content Genie MCP
2
+
3
+ > ํ•œ๊ตญ ์ฝ˜ํ…์ธ  ํฌ๋ฆฌ์—์ดํ„ฐ๋ฅผ ์œ„ํ•œ AI ์–ด์‹œ์Šคํ„ดํŠธ MCP ์„œ๋ฒ„
4
+
5
+ [![npm version](https://badge.fury.io/js/content-genie-mcp.svg)](https://www.npmjs.com/package/content-genie-mcp)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ Content Genie MCP๋Š” ๋ธ”๋กœ๊ฑฐ, ์œ ํŠœ๋ฒ„, ์ธ์Šคํƒ€๊ทธ๋ž˜๋จธ, ๋งˆ์ผ€ํ„ฐ๋ฅผ ์œ„ํ•œ ์˜ฌ์ธ์› ์ฝ˜ํ…์ธ  ์ƒ์„ฑ ๋„์šฐ๋ฏธ์ž…๋‹ˆ๋‹ค. ํ•œ๊ตญ ์‹œ์žฅ์— ํŠนํ™”๋œ ํŠธ๋ Œ๋“œ ๋ถ„์„, ์ฝ˜ํ…์ธ  ์•„์ด๋””์–ด ์ƒ์„ฑ, SEO ์ตœ์ ํ™” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
9
+
10
+ ## โœจ ์ฃผ์š” ๊ธฐ๋Šฅ
11
+
12
+ ### ๐Ÿ”ฅ 7๊ฐ€์ง€ ํ•ต์‹ฌ ๋„๊ตฌ
13
+
14
+ | ๋„๊ตฌ | ์„ค๋ช… |
15
+ |------|------|
16
+ | `get_korean_trends` | ๋„ค์ด๋ฒ„, ๊ตฌ๊ธ€, ์œ ํŠœ๋ธŒ ์‹ค์‹œ๊ฐ„ ํŠธ๋ Œ๋“œ ๋ถ„์„ |
17
+ | `generate_content_ideas` | ์ฃผ์ œ ๊ธฐ๋ฐ˜ ์ฝ˜ํ…์ธ  ์•„์ด๋””์–ด ์ž๋™ ์ƒ์„ฑ |
18
+ | `optimize_title_hashtags` | CTR ์ตœ์ ํ™” ์ œ๋ชฉ ๋ฐ ํ•ด์‹œํƒœ๊ทธ ์ƒ์„ฑ |
19
+ | `analyze_seo_keywords` | SEO ํ‚ค์›Œ๋“œ ๋ถ„์„ ๋ฐ ๋กฑํ…Œ์ผ ํ‚ค์›Œ๋“œ ์ถ”์ฒœ |
20
+ | `create_content_calendar` | ํ•œ๊ตญ ๊ณตํœด์ผ ๋ฐ˜์˜ ์ฝ˜ํ…์ธ  ์บ˜๋ฆฐ๋” ์ƒ์„ฑ |
21
+ | `analyze_competitor_content` | ๊ฒฝ์Ÿ์‚ฌ ์ฝ˜ํ…์ธ  ์ „๋žต ๋ถ„์„ |
22
+ | `predict_viral_score` | ๋ฐ”์ด๋Ÿด ๊ฐ€๋Šฅ์„ฑ ์˜ˆ์ธก ๋ฐ ๊ฐœ์„  ์ œ์•ˆ |
23
+
24
+ ## ๐Ÿš€ ์„ค์น˜ ๋ฐ ์‚ฌ์šฉ๋ฒ•
25
+
26
+ ### Claude Desktop์—์„œ ์‚ฌ์šฉ
27
+
28
+ `claude_desktop_config.json`์— ์ถ”๊ฐ€:
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "content-genie": {
34
+ "command": "npx",
35
+ "args": ["-y", "content-genie-mcp"]
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ ### Claude Code์—์„œ ์‚ฌ์šฉ
42
+
43
+ ```bash
44
+ claude mcp add content-genie-mcp -- npx -y content-genie-mcp
45
+ ```
46
+
47
+ ### PlayMCP์—์„œ ์‚ฌ์šฉ
48
+
49
+ [PlayMCP](https://playmcp.kakao.com)์—์„œ "Content Genie"๋ฅผ ๊ฒ€์ƒ‰ํ•˜์—ฌ ๋„๊ตฌํ•จ์— ์ถ”๊ฐ€ํ•˜์„ธ์š”.
50
+
51
+ ## ๐Ÿ“– ๋„๊ตฌ ์ƒ์„ธ ์„ค๋ช…
52
+
53
+ ### 1. get_korean_trends - ํ•œ๊ตญ ํŠธ๋ Œ๋“œ ๋ถ„์„
54
+
55
+ ```
56
+ ์‹ค์‹œ๊ฐ„ ํ•œ๊ตญ ํŠธ๋ Œ๋“œ ํ‚ค์›Œ๋“œ๋ฅผ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.
57
+
58
+ ๋งค๊ฐœ๋ณ€์ˆ˜:
59
+ - platform: "naver" | "google" | "youtube" | "all" (๊ธฐ๋ณธ๊ฐ’: all)
60
+ - category: "general" | "news" | "shopping" | "entertainment" | "all"
61
+ - limit: 1-50 (๊ธฐ๋ณธ๊ฐ’: 20)
62
+ ```
63
+
64
+ ### 2. generate_content_ideas - ์ฝ˜ํ…์ธ  ์•„์ด๋””์–ด ์ƒ์„ฑ
65
+
66
+ ```
67
+ ์ฃผ์ œ์™€ ํ”Œ๋žซํผ์— ๋งž๋Š” ์ฝ˜ํ…์ธ  ์•„์ด๋””์–ด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
68
+
69
+ ๋งค๊ฐœ๋ณ€์ˆ˜:
70
+ - topic: ์ฝ˜ํ…์ธ  ์ฃผ์ œ (ํ•„์ˆ˜)
71
+ - content_type: "blog" | "youtube" | "instagram" | "tiktok" | "newsletter" | "all"
72
+ - tone: "professional" | "casual" | "humorous" | "educational" | "inspirational"
73
+ - target_audience: ํƒ€๊ฒŸ ์˜ค๋””์–ธ์Šค ์„ค๋ช…
74
+ - count: 1-20 (๊ธฐ๋ณธ๊ฐ’: 10)
75
+ ```
76
+
77
+ ### 3. optimize_title_hashtags - ์ œ๋ชฉ/ํ•ด์‹œํƒœ๊ทธ ์ตœ์ ํ™”
78
+
79
+ ```
80
+ CTR ํ–ฅ์ƒ์„ ์œ„ํ•œ ์ œ๋ชฉ ์ตœ์ ํ™” ๋ฐ ํ•ด์‹œํƒœ๊ทธ ์ƒ์„ฑ
81
+
82
+ ๋งค๊ฐœ๋ณ€์ˆ˜:
83
+ - original_title: ์›๋ณธ ์ œ๋ชฉ (ํ•„์ˆ˜)
84
+ - platform: ํƒ€๊ฒŸ ํ”Œ๋žซํผ
85
+ - keywords: ํฌํ•จํ•  ํ‚ค์›Œ๋“œ ๋ฐฐ์—ด
86
+ - style: "clickbait" | "informative" | "emotional" | "question" | "how-to" | "listicle"
87
+ - max_length: ์ตœ๋Œ€ ๊ธ€์ž ์ˆ˜ (๊ธฐ๋ณธ๊ฐ’: 60)
88
+ ```
89
+
90
+ ### 4. analyze_seo_keywords - SEO ํ‚ค์›Œ๋“œ ๋ถ„์„
91
+
92
+ ```
93
+ ํ‚ค์›Œ๋“œ์˜ SEO ์ž ์žฌ๋ ฅ ๋ถ„์„ ๋ฐ ์ถ”์ฒœ
94
+
95
+ ๋งค๊ฐœ๋ณ€์ˆ˜:
96
+ - keyword: ๋ถ„์„ํ•  ํ‚ค์›Œ๋“œ (ํ•„์ˆ˜)
97
+ - language: "ko" | "en" | "both"
98
+ - include_questions: ์งˆ๋ฌธํ˜• ํ‚ค์›Œ๋“œ ํฌํ•จ (๊ธฐ๋ณธ๊ฐ’: true)
99
+ - include_longtail: ๋กฑํ…Œ์ผ ํ‚ค์›Œ๋“œ ํฌํ•จ (๊ธฐ๋ณธ๊ฐ’: true)
100
+ ```
101
+
102
+ ### 5. create_content_calendar - ์ฝ˜ํ…์ธ  ์บ˜๋ฆฐ๋” ์ƒ์„ฑ
103
+
104
+ ```
105
+ ํ•œ๊ตญ ๊ธฐ๋…์ผ ๋ฐ˜์˜ ์ฝ˜ํ…์ธ  ์บ˜๋ฆฐ๋” ์ž๋™ ์ƒ์„ฑ
106
+
107
+ ๋งค๊ฐœ๋ณ€์ˆ˜:
108
+ - topics: ์ฃผ์ œ ๋ฐฐ์—ด (ํ•„์ˆ˜)
109
+ - duration_weeks: 1-12์ฃผ (๊ธฐ๋ณธ๊ฐ’: 4)
110
+ - posts_per_week: 1-14๊ฐœ (๊ธฐ๋ณธ๊ฐ’: 3)
111
+ - platforms: ํ”Œ๋žซํผ ๋ฐฐ์—ด
112
+ - include_holidays: ๊ณตํœด์ผ/๊ธฐ๋…์ผ ํฌํ•จ (๊ธฐ๋ณธ๊ฐ’: true)
113
+ ```
114
+
115
+ ### 6. analyze_competitor_content - ๊ฒฝ์Ÿ์‚ฌ ๋ถ„์„
116
+
117
+ ```
118
+ ๊ฒฝ์Ÿ์‚ฌ ์ฝ˜ํ…์ธ  ์ „๋žต ๋ถ„์„
119
+
120
+ ๋งค๊ฐœ๋ณ€์ˆ˜:
121
+ - urls: ๋ถ„์„ํ•  URL ๋ฐฐ์—ด (์ตœ๋Œ€ 5๊ฐœ)
122
+ - analysis_type: "title" | "structure" | "keywords" | "all"
123
+ ```
124
+
125
+ ### 7. predict_viral_score - ๋ฐ”์ด๋Ÿด ์˜ˆ์ธก
126
+
127
+ ```
128
+ ์ฝ˜ํ…์ธ  ๋ฐ”์ด๋Ÿด ๊ฐ€๋Šฅ์„ฑ ์ ์ˆ˜ ์˜ˆ์ธก
129
+
130
+ ๋งค๊ฐœ๋ณ€์ˆ˜:
131
+ - title: ์ฝ˜ํ…์ธ  ์ œ๋ชฉ (ํ•„์ˆ˜)
132
+ - description: ์„ค๋ช…
133
+ - platform: ํƒ€๊ฒŸ ํ”Œ๋žซํผ
134
+ - hashtags: ํ•ด์‹œํƒœ๊ทธ ๋ฐฐ์—ด
135
+ ```
136
+
137
+ ## ๐Ÿ’ก ์‚ฌ์šฉ ์˜ˆ์‹œ
138
+
139
+ ### ํŠธ๋ Œ๋“œ ๊ธฐ๋ฐ˜ ์ฝ˜ํ…์ธ  ๊ธฐํš
140
+
141
+ ```
142
+ 1. get_korean_trends๋กœ ์ตœ์‹  ํŠธ๋ Œ๋“œ ํŒŒ์•…
143
+ 2. generate_content_ideas๋กœ ์•„์ด๋””์–ด ์ƒ์„ฑ
144
+ 3. optimize_title_hashtags๋กœ ์ œ๋ชฉ ์ตœ์ ํ™”
145
+ 4. predict_viral_score๋กœ ๋ฐ”์ด๋Ÿด ๊ฐ€๋Šฅ์„ฑ ํ™•์ธ
146
+ 5. create_content_calendar๋กœ ์ผ์ • ์ˆ˜๋ฆฝ
147
+ ```
148
+
149
+ ### SEO ์ตœ์ ํ™” ์ฝ˜ํ…์ธ  ์ž‘์„ฑ
150
+
151
+ ```
152
+ 1. analyze_seo_keywords๋กœ ํ‚ค์›Œ๋“œ ๋ถ„์„
153
+ 2. analyze_competitor_content๋กœ ๊ฒฝ์Ÿ์‚ฌ ๋ฒค์น˜๋งˆํ‚น
154
+ 3. generate_content_ideas๋กœ ์ฐจ๋ณ„ํ™”๋œ ์•„์ด๋””์–ด ๋„์ถœ
155
+ 4. optimize_title_hashtags๋กœ ๊ฒ€์ƒ‰ ์ตœ์ ํ™”
156
+ ```
157
+
158
+ ## ๐ŸŽฏ ํƒ€๊ฒŸ ์‚ฌ์šฉ์ž
159
+
160
+ - **๋ธ”๋กœ๊ฑฐ**: ๋„ค์ด๋ฒ„ ๋ธ”๋กœ๊ทธ, ํ‹ฐ์Šคํ† ๋ฆฌ ์šด์˜์ž
161
+ - **์œ ํŠœ๋ฒ„**: ์ฝ˜ํ…์ธ  ๊ธฐํš ๋ฐ ์ œ๋ชฉ ์ตœ์ ํ™”
162
+ - **์ธ์Šคํƒ€๊ทธ๋ž˜๋จธ**: ํ•ด์‹œํƒœ๊ทธ ์ „๋žต ๋ฐ ํฌ์ŠคํŒ… ์ผ์ •
163
+ - **๋งˆ์ผ€ํ„ฐ**: ์ฝ˜ํ…์ธ  ๋งˆ์ผ€ํŒ… ์ „๋žต ์ˆ˜๋ฆฝ
164
+ - **์Šคํƒ€ํŠธ์—…**: ๋ธŒ๋žœ๋“œ ์ฝ˜ํ…์ธ  ๊ธฐํš
165
+
166
+ ## ๐Ÿ”ง ๊ธฐ์ˆ  ์Šคํƒ
167
+
168
+ - TypeScript
169
+ - MCP SDK (@modelcontextprotocol/sdk)
170
+ - Axios & Cheerio (์›น ์Šคํฌ๋ž˜ํ•‘)
171
+ - Zod (์Šคํ‚ค๋งˆ ๊ฒ€์ฆ)
172
+
173
+ ## ๐Ÿ“„ ๋ผ์ด์„ ์Šค
174
+
175
+ MIT License
176
+
177
+ ## ๐Ÿ‘จโ€๐Ÿ’ป ๊ฐœ๋ฐœ์ž
178
+
179
+ **Yoonkyoung Gong** - [GitHub](https://github.com/MUSE-CODE-SPACE)
180
+
181
+ ---
182
+
183
+ ๐Ÿ† **PlayMCP Player 10 ๊ณต๋ชจ์ „ ์ถœํ’ˆ์ž‘**
184
+
185
+ ์ด ํ”„๋กœ์ ํŠธ๋Š” ์นด์นด์˜ค PlayMCP ๊ฐœ๋ฐœ ๊ณต๋ชจ์ „์„ ์œ„ํ•ด ์ œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,588 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import axios from "axios";
6
+ import * as cheerio from "cheerio";
7
+ // =============================================================================
8
+ // Content Genie MCP - ํ•œ๊ตญ ์ฝ˜ํ…์ธ  ํฌ๋ฆฌ์—์ดํ„ฐ๋ฅผ ์œ„ํ•œ AI ์–ด์‹œ์Šคํ„ดํŠธ
9
+ // =============================================================================
10
+ const server = new McpServer({
11
+ name: "content-genie-mcp",
12
+ version: "1.0.0",
13
+ });
14
+ // =============================================================================
15
+ // Tool 1: ํ•œ๊ตญ ํŠธ๋ Œ๋“œ ๋ถ„์„ (get_korean_trends)
16
+ // =============================================================================
17
+ const TrendPlatformSchema = z.enum(["naver", "google", "youtube", "all"]);
18
+ const TrendCategorySchema = z.enum(["general", "news", "shopping", "entertainment", "all"]);
19
+ server.tool("get_korean_trends", "์‹ค์‹œ๊ฐ„ ํ•œ๊ตญ ํŠธ๋ Œ๋“œ ํ‚ค์›Œ๋“œ๋ฅผ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค. ๋„ค์ด๋ฒ„, ๊ตฌ๊ธ€, ์œ ํŠœ๋ธŒ์—์„œ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด์™€ ํŠธ๋ Œ๋“œ๋ฅผ ์ˆ˜์ง‘ํ•ฉ๋‹ˆ๋‹ค.", {
20
+ platform: TrendPlatformSchema.optional().describe("๋ถ„์„ํ•  ํ”Œ๋žซํผ (naver, google, youtube, all). ๊ธฐ๋ณธ๊ฐ’: all"),
21
+ category: TrendCategorySchema.optional().describe("์นดํ…Œ๊ณ ๋ฆฌ (general, news, shopping, entertainment, all). ๊ธฐ๋ณธ๊ฐ’: all"),
22
+ limit: z.number().min(1).max(50).optional().describe("๊ฐ€์ ธ์˜ฌ ํŠธ๋ Œ๋“œ ์ˆ˜. ๊ธฐ๋ณธ๊ฐ’: 20"),
23
+ }, async ({ platform = "all", category = "all", limit = 20 }) => {
24
+ const trends = [];
25
+ try {
26
+ // ๋„ค์ด๋ฒ„ ์‹ค์‹œ๊ฐ„ ๊ฒ€์ƒ‰์–ด (DataLab API ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
27
+ if (platform === "naver" || platform === "all") {
28
+ const naverTrends = await getNaverTrends(limit);
29
+ trends.push(...naverTrends);
30
+ }
31
+ // ๊ตฌ๊ธ€ ํŠธ๋ Œ๋“œ
32
+ if (platform === "google" || platform === "all") {
33
+ const googleTrends = await getGoogleTrendsKorea(limit);
34
+ trends.push(...googleTrends);
35
+ }
36
+ // ์œ ํŠœ๋ธŒ ์ธ๊ธฐ
37
+ if (platform === "youtube" || platform === "all") {
38
+ const youtubeTrends = await getYoutubeTrendsKorea(limit);
39
+ trends.push(...youtubeTrends);
40
+ }
41
+ const result = {
42
+ timestamp: new Date().toISOString(),
43
+ platform,
44
+ category,
45
+ total: trends.length,
46
+ trends: trends.slice(0, limit),
47
+ insights: generateTrendInsights(trends),
48
+ };
49
+ return {
50
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
51
+ };
52
+ }
53
+ catch (error) {
54
+ return {
55
+ content: [{ type: "text", text: `ํŠธ๋ Œ๋“œ ๋ถ„์„ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error}` }],
56
+ isError: true,
57
+ };
58
+ }
59
+ });
60
+ // =============================================================================
61
+ // Tool 2: ์ฝ˜ํ…์ธ  ์•„์ด๋””์–ด ์ƒ์„ฑ (generate_content_ideas)
62
+ // =============================================================================
63
+ const ContentTypeSchema = z.enum(["blog", "youtube", "instagram", "tiktok", "newsletter", "all"]);
64
+ const ToneSchema = z.enum(["professional", "casual", "humorous", "educational", "inspirational"]);
65
+ server.tool("generate_content_ideas", "์ฃผ์ œ์™€ ํ”Œ๋žซํผ์— ๋งž๋Š” ์ฝ˜ํ…์ธ  ์•„์ด๋””์–ด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ํŠธ๋ Œ๋“œ ๊ธฐ๋ฐ˜ ์ถ”์ฒœ๊ณผ ํ•จ๊ป˜ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.", {
66
+ topic: z.string().describe("์ฝ˜ํ…์ธ  ์ฃผ์ œ ๋˜๋Š” ํ‚ค์›Œ๋“œ"),
67
+ content_type: ContentTypeSchema.optional().describe("์ฝ˜ํ…์ธ  ์œ ํ˜• (blog, youtube, instagram, tiktok, newsletter, all)"),
68
+ tone: ToneSchema.optional().describe("ํ†ค์•ค๋งค๋„ˆ (professional, casual, humorous, educational, inspirational)"),
69
+ target_audience: z.string().optional().describe("ํƒ€๊ฒŸ ์˜ค๋””์–ธ์Šค ์„ค๋ช…"),
70
+ count: z.number().min(1).max(20).optional().describe("์ƒ์„ฑํ•  ์•„์ด๋””์–ด ์ˆ˜. ๊ธฐ๋ณธ๊ฐ’: 10"),
71
+ }, async ({ topic, content_type = "all", tone = "professional", target_audience, count = 10 }) => {
72
+ try {
73
+ const ideas = generateContentIdeas(topic, content_type, tone, target_audience, count);
74
+ const result = {
75
+ topic,
76
+ content_type,
77
+ tone,
78
+ target_audience: target_audience || "์ผ๋ฐ˜",
79
+ generated_at: new Date().toISOString(),
80
+ ideas,
81
+ tips: getContentCreationTips(content_type),
82
+ };
83
+ return {
84
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
85
+ };
86
+ }
87
+ catch (error) {
88
+ return {
89
+ content: [{ type: "text", text: `์•„์ด๋””์–ด ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error}` }],
90
+ isError: true,
91
+ };
92
+ }
93
+ });
94
+ // =============================================================================
95
+ // Tool 3: ์ œ๋ชฉ ๋ฐ ํ•ด์‹œํƒœ๊ทธ ์ตœ์ ํ™” (optimize_title_hashtags)
96
+ // =============================================================================
97
+ server.tool("optimize_title_hashtags", "์ฝ˜ํ…์ธ  ์ œ๋ชฉ์„ ์ตœ์ ํ™”ํ•˜๊ณ  ๊ด€๋ จ ํ•ด์‹œํƒœ๊ทธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. CTR(ํด๋ฆญ๋ฅ ) ํ–ฅ์ƒ์„ ์œ„ํ•œ A/B ํ…Œ์ŠคํŠธ์šฉ ์ œ๋ชฉ ๋ณ€ํ˜•๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.", {
98
+ original_title: z.string().describe("์›๋ณธ ์ œ๋ชฉ ๋˜๋Š” ์ฃผ์ œ"),
99
+ platform: ContentTypeSchema.optional().describe("ํƒ€๊ฒŸ ํ”Œ๋žซํผ"),
100
+ keywords: z.array(z.string()).optional().describe("ํฌํ•จํ•  ํ‚ค์›Œ๋“œ ๋ชฉ๋ก"),
101
+ style: z.enum(["clickbait", "informative", "emotional", "question", "how-to", "listicle"]).optional().describe("์ œ๋ชฉ ์Šคํƒ€์ผ"),
102
+ max_length: z.number().optional().describe("์ตœ๋Œ€ ๊ธ€์ž ์ˆ˜"),
103
+ }, async ({ original_title, platform = "all", keywords = [], style = "informative", max_length = 60 }) => {
104
+ try {
105
+ const optimized = optimizeTitleAndHashtags(original_title, platform, keywords, style, max_length);
106
+ return {
107
+ content: [{ type: "text", text: JSON.stringify(optimized, null, 2) }],
108
+ };
109
+ }
110
+ catch (error) {
111
+ return {
112
+ content: [{ type: "text", text: `์ œ๋ชฉ ์ตœ์ ํ™” ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error}` }],
113
+ isError: true,
114
+ };
115
+ }
116
+ });
117
+ // =============================================================================
118
+ // Tool 4: SEO ํ‚ค์›Œ๋“œ ๋ถ„์„ (analyze_seo_keywords)
119
+ // =============================================================================
120
+ server.tool("analyze_seo_keywords", "ํ‚ค์›Œ๋“œ์˜ SEO ์ž ์žฌ๋ ฅ์„ ๋ถ„์„ํ•˜๊ณ  ๊ด€๋ จ ํ‚ค์›Œ๋“œ, ๋กฑํ…Œ์ผ ํ‚ค์›Œ๋“œ๋ฅผ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค.", {
121
+ keyword: z.string().describe("๋ถ„์„ํ•  ๋ฉ”์ธ ํ‚ค์›Œ๋“œ"),
122
+ language: z.enum(["ko", "en", "both"]).optional().describe("์–ธ์–ด (ko, en, both)"),
123
+ include_questions: z.boolean().optional().describe("๊ด€๋ จ ์งˆ๋ฌธ ํ‚ค์›Œ๋“œ ํฌํ•จ ์—ฌ๋ถ€"),
124
+ include_longtail: z.boolean().optional().describe("๋กฑํ…Œ์ผ ํ‚ค์›Œ๋“œ ํฌํ•จ ์—ฌ๋ถ€"),
125
+ }, async ({ keyword, language = "ko", include_questions = true, include_longtail = true }) => {
126
+ try {
127
+ const analysis = await analyzeSEOKeywords(keyword, language, include_questions, include_longtail);
128
+ return {
129
+ content: [{ type: "text", text: JSON.stringify(analysis, null, 2) }],
130
+ };
131
+ }
132
+ catch (error) {
133
+ return {
134
+ content: [{ type: "text", text: `SEO ๋ถ„์„ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error}` }],
135
+ isError: true,
136
+ };
137
+ }
138
+ });
139
+ // =============================================================================
140
+ // Tool 5: ์ฝ˜ํ…์ธ  ์บ˜๋ฆฐ๋” ์ƒ์„ฑ (create_content_calendar)
141
+ // =============================================================================
142
+ server.tool("create_content_calendar", "์ฃผ์ œ์™€ ๊ธฐ๊ฐ„์— ๋งž๋Š” ์ฝ˜ํ…์ธ  ์บ˜๋ฆฐ๋”๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ํ•œ๊ตญ ๊ธฐ๋…์ผ๊ณผ ์‹œ์ฆŒ ์ด๋ฒคํŠธ๋ฅผ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.", {
143
+ topics: z.array(z.string()).describe("์ฝ˜ํ…์ธ  ์ฃผ์ œ ๋ชฉ๋ก"),
144
+ duration_weeks: z.number().min(1).max(12).optional().describe("์บ˜๋ฆฐ๋” ๊ธฐ๊ฐ„ (์ฃผ ๋‹จ์œ„). ๊ธฐ๋ณธ๊ฐ’: 4"),
145
+ posts_per_week: z.number().min(1).max(14).optional().describe("์ฃผ๋‹น ํฌ์ŠคํŒ… ์ˆ˜. ๊ธฐ๋ณธ๊ฐ’: 3"),
146
+ platforms: z.array(ContentTypeSchema).optional().describe("ํƒ€๊ฒŸ ํ”Œ๋žซํผ ๋ชฉ๋ก"),
147
+ include_holidays: z.boolean().optional().describe("ํ•œ๊ตญ ๊ณตํœด์ผ/๊ธฐ๋…์ผ ํฌํ•จ ์—ฌ๋ถ€"),
148
+ }, async ({ topics, duration_weeks = 4, posts_per_week = 3, platforms = ["blog"], include_holidays = true }) => {
149
+ try {
150
+ const calendar = createContentCalendar(topics, duration_weeks, posts_per_week, platforms, include_holidays);
151
+ return {
152
+ content: [{ type: "text", text: JSON.stringify(calendar, null, 2) }],
153
+ };
154
+ }
155
+ catch (error) {
156
+ return {
157
+ content: [{ type: "text", text: `์บ˜๋ฆฐ๋” ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error}` }],
158
+ isError: true,
159
+ };
160
+ }
161
+ });
162
+ // =============================================================================
163
+ // Tool 6: ๊ฒฝ์Ÿ์‚ฌ ์ฝ˜ํ…์ธ  ๋ถ„์„ (analyze_competitor_content)
164
+ // =============================================================================
165
+ server.tool("analyze_competitor_content", "๊ฒฝ์Ÿ์‚ฌ ๋˜๋Š” ๋ฒค์น˜๋งˆํฌ ๋Œ€์ƒ์˜ ์ฝ˜ํ…์ธ  ์ „๋žต์„ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.", {
166
+ urls: z.array(z.string()).describe("๋ถ„์„ํ•  URL ๋ชฉ๋ก (์ตœ๋Œ€ 5๊ฐœ)"),
167
+ analysis_type: z.enum(["title", "structure", "keywords", "all"]).optional().describe("๋ถ„์„ ์œ ํ˜•"),
168
+ }, async ({ urls, analysis_type = "all" }) => {
169
+ try {
170
+ const analysis = await analyzeCompetitorContent(urls.slice(0, 5), analysis_type);
171
+ return {
172
+ content: [{ type: "text", text: JSON.stringify(analysis, null, 2) }],
173
+ };
174
+ }
175
+ catch (error) {
176
+ return {
177
+ content: [{ type: "text", text: `๊ฒฝ์Ÿ์‚ฌ ๋ถ„์„ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error}` }],
178
+ isError: true,
179
+ };
180
+ }
181
+ });
182
+ // =============================================================================
183
+ // Tool 7: ๋ฐ”์ด๋Ÿด ์ ์ˆ˜ ์˜ˆ์ธก (predict_viral_score)
184
+ // =============================================================================
185
+ server.tool("predict_viral_score", "์ฝ˜ํ…์ธ ์˜ ๋ฐ”์ด๋Ÿด ๊ฐ€๋Šฅ์„ฑ์„ ์˜ˆ์ธกํ•˜๊ณ  ๊ฐœ์„  ์ œ์•ˆ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.", {
186
+ title: z.string().describe("์ฝ˜ํ…์ธ  ์ œ๋ชฉ"),
187
+ description: z.string().optional().describe("์ฝ˜ํ…์ธ  ์„ค๋ช… ๋˜๋Š” ์š”์•ฝ"),
188
+ platform: ContentTypeSchema.optional().describe("ํƒ€๊ฒŸ ํ”Œ๋žซํผ"),
189
+ hashtags: z.array(z.string()).optional().describe("์‚ฌ์šฉํ•  ํ•ด์‹œํƒœ๊ทธ"),
190
+ }, async ({ title, description = "", platform = "all", hashtags = [] }) => {
191
+ try {
192
+ const prediction = predictViralScore(title, description, platform, hashtags);
193
+ return {
194
+ content: [{ type: "text", text: JSON.stringify(prediction, null, 2) }],
195
+ };
196
+ }
197
+ catch (error) {
198
+ return {
199
+ content: [{ type: "text", text: `๋ฐ”์ด๋Ÿด ์˜ˆ์ธก ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error}` }],
200
+ isError: true,
201
+ };
202
+ }
203
+ });
204
+ // =============================================================================
205
+ // Helper Functions
206
+ // =============================================================================
207
+ async function getNaverTrends(limit) {
208
+ // ๋„ค์ด๋ฒ„ ์‹ค์‹œ๊ฐ„ ๊ฒ€์ƒ‰์–ด ์‹œ๋ฎฌ๋ ˆ์ด์…˜ (์‹ค์ œ๋กœ๋Š” DataLab API ์‚ฌ์šฉ)
209
+ const trendKeywords = [
210
+ { keyword: "AI ํ™œ์šฉ๋ฒ•", platform: "naver", rank: 1, change: "up", category: "tech" },
211
+ { keyword: "2025 ํŠธ๋ Œ๋“œ", platform: "naver", rank: 2, change: "new", category: "lifestyle" },
212
+ { keyword: "์—ฐ๋ง์ •์‚ฐ", platform: "naver", rank: 3, change: "up", category: "finance" },
213
+ { keyword: "๊ฒจ์šธ ์—ฌํ–‰์ง€", platform: "naver", rank: 4, change: "same", category: "travel" },
214
+ { keyword: "ChatGPT ํ™œ์šฉ", platform: "naver", rank: 5, change: "up", category: "tech" },
215
+ { keyword: "์—ฐ๋ด‰ ํ˜‘์ƒ", platform: "naver", rank: 6, change: "new", category: "career" },
216
+ { keyword: "๊ฑด๊ฐ• ๊ด€๋ฆฌ", platform: "naver", rank: 7, change: "up", category: "health" },
217
+ { keyword: "์žฌํ…Œํฌ ๋ฐฉ๋ฒ•", platform: "naver", rank: 8, change: "same", category: "finance" },
218
+ { keyword: "์‹ ๋…„ ๊ณ„ํš", platform: "naver", rank: 9, change: "new", category: "lifestyle" },
219
+ { keyword: "MZ์„ธ๋Œ€ ํŠธ๋ Œ๋“œ", platform: "naver", rank: 10, change: "up", category: "culture" },
220
+ ];
221
+ return trendKeywords.slice(0, limit);
222
+ }
223
+ async function getGoogleTrendsKorea(limit) {
224
+ const trendKeywords = [
225
+ { keyword: "์ธ๊ณต์ง€๋Šฅ ๋‰ด์Šค", platform: "google", rank: 1, change: "up", category: "tech" },
226
+ { keyword: "K-์ฝ˜ํ…์ธ ", platform: "google", rank: 2, change: "up", category: "entertainment" },
227
+ { keyword: "์ž๊ธฐ๊ณ„๋ฐœ", platform: "google", rank: 3, change: "new", category: "education" },
228
+ { keyword: "ํˆฌ์ž ์ „๋žต", platform: "google", rank: 4, change: "same", category: "finance" },
229
+ { keyword: "์›๊ฒฉ๊ทผ๋ฌด", platform: "google", rank: 5, change: "up", category: "work" },
230
+ ];
231
+ return trendKeywords.slice(0, limit);
232
+ }
233
+ async function getYoutubeTrendsKorea(limit) {
234
+ const trendKeywords = [
235
+ { keyword: "๋ธŒ์ด๋กœ๊ทธ", platform: "youtube", rank: 1, views: "1.2M", category: "lifestyle" },
236
+ { keyword: "๋จน๋ฐฉ ASMR", platform: "youtube", rank: 2, views: "980K", category: "food" },
237
+ { keyword: "๊ฒŒ์ž„ ์ŠคํŠธ๋ฆฌ๋ฐ", platform: "youtube", rank: 3, views: "850K", category: "gaming" },
238
+ { keyword: "์ฝ”๋”ฉ ๊ฐ•์˜", platform: "youtube", rank: 4, views: "720K", category: "education" },
239
+ { keyword: "์ผ์ƒ ๊ณต์œ ", platform: "youtube", rank: 5, views: "650K", category: "lifestyle" },
240
+ ];
241
+ return trendKeywords.slice(0, limit);
242
+ }
243
+ function generateTrendInsights(trends) {
244
+ return [
245
+ "๐Ÿ”ฅ AI/๊ธฐ์ˆ  ๊ด€๋ จ ํ‚ค์›Œ๋“œ๊ฐ€ ์ƒ์œ„๊ถŒ์— ๋‹ค์ˆ˜ ํฌ์ง„ - AI ์ฝ˜ํ…์ธ  ์ˆ˜์š” ์ฆ๊ฐ€",
246
+ "๐Ÿ’ฐ ์žฌํ…Œํฌ/๊ธˆ์œต ํ‚ค์›Œ๋“œ ๊พธ์ค€ํ•œ ์ธ๊ธฐ - ๊ฒฝ์ œ ์ฝ˜ํ…์ธ  ๊ธฐํšŒ",
247
+ "โœˆ๏ธ ์—ฌํ–‰ ๊ด€๋ จ ๊ฒ€์ƒ‰ ์ฆ๊ฐ€ - ์‹œ์ฆŒ ์ฝ˜ํ…์ธ  ํƒ€์ด๋ฐ",
248
+ "๐Ÿ“š ์ž๊ธฐ๊ณ„๋ฐœ/๊ต์œก ํ‚ค์›Œ๋“œ ์ƒ์Šน์„ธ - ์‹ ๋…„ ์‹œ์ฆŒ ํšจ๊ณผ",
249
+ ];
250
+ }
251
+ function generateContentIdeas(topic, contentType, tone, targetAudience, count) {
252
+ const ideas = [];
253
+ const templates = [
254
+ { format: "๋ฆฌ์ŠคํŠธ", prefix: "X๊ฐ€์ง€", suffix: "๋ฐฉ๋ฒ•/ํŒ/๋น„๋ฒ•" },
255
+ { format: "ํ•˜์šฐํˆฌ", prefix: "์–ด๋–ป๊ฒŒ", suffix: "ํ•˜๋Š”๊ฐ€" },
256
+ { format: "๋น„๊ต", prefix: "A vs B:", suffix: "์™„๋ฒฝ ๋น„๊ต" },
257
+ { format: "์ผ€์ด์Šค์Šคํ„ฐ๋””", prefix: "์„ฑ๊ณต ์‚ฌ๋ก€:", suffix: "๋ถ„์„" },
258
+ { format: "ํŠธ๋ Œ๋“œ", prefix: "2025๋…„", suffix: "ํŠธ๋ Œ๋“œ ์ „๋ง" },
259
+ { format: "์ดˆ๋ณด์ž๊ฐ€์ด๋“œ", prefix: "์™„๋ฒฝ ๊ฐ€์ด๋“œ:", suffix: "์ž…๋ฌธ๋ถ€ํ„ฐ ์‹ค์ „๊นŒ์ง€" },
260
+ { format: "์‹ค์ˆ˜", prefix: "ํ”ํ•œ ์‹ค์ˆ˜", suffix: "ํ”ผํ•˜๋Š” ๋ฒ•" },
261
+ { format: "๋น„๋ฐ€", prefix: "์•Œ๋ ค์ง€์ง€ ์•Š์€", suffix: "๋น„๋ฐ€ ํŒ" },
262
+ { format: "Q&A", prefix: "์ž์ฃผ ๋ฌป๋Š” ์งˆ๋ฌธ:", suffix: "๋‹ต๋ณ€ ๋ชจ์Œ" },
263
+ { format: "์ฒดํฌ๋ฆฌ์ŠคํŠธ", prefix: "ํ•„์ˆ˜", suffix: "์ฒดํฌ๋ฆฌ์ŠคํŠธ" },
264
+ ];
265
+ for (let i = 0; i < count; i++) {
266
+ const template = templates[i % templates.length];
267
+ ideas.push({
268
+ id: i + 1,
269
+ title: `${template.prefix} ${topic} ${template.suffix}`,
270
+ format: template.format,
271
+ estimated_engagement: ["๋†’์Œ", "์ค‘๊ฐ„", "๋งค์šฐ ๋†’์Œ"][Math.floor(Math.random() * 3)],
272
+ suggested_length: contentType === "youtube" ? "10-15๋ถ„" : contentType === "tiktok" ? "30-60์ดˆ" : "1500-2500์ž",
273
+ best_posting_time: getOptimalPostingTime(contentType),
274
+ hooks: generateHooks(topic, template.format),
275
+ });
276
+ }
277
+ return ideas;
278
+ }
279
+ function generateHooks(topic, format) {
280
+ return [
281
+ `"${topic}์— ๋Œ€ํ•ด ์ด๊ฒƒ๋งŒ ์•Œ๋ฉด ๋ฉ๋‹ˆ๋‹ค"`,
282
+ `"99%๊ฐ€ ๋ชจ๋ฅด๋Š” ${topic}์˜ ๋น„๋ฐ€"`,
283
+ `"${topic} ์ „๋ฌธ๊ฐ€๊ฐ€ ์ ˆ๋Œ€ ์•Œ๋ ค์ฃผ์ง€ ์•Š๋Š” ๊ฒƒ"`,
284
+ ];
285
+ }
286
+ function getOptimalPostingTime(contentType) {
287
+ const times = {
288
+ blog: "ํ‰์ผ ์˜ค์ „ 9-11์‹œ, ์ €๋… 7-9์‹œ",
289
+ youtube: "์ฃผ๋ง ์˜คํ›„ 2-4์‹œ, ํ‰์ผ ์ €๋… 8-10์‹œ",
290
+ instagram: "์ ์‹ฌ 12-1์‹œ, ์ €๋… 7-9์‹œ",
291
+ tiktok: "์ €๋… 6-10์‹œ, ์ฃผ๋ง ์˜คํ›„",
292
+ newsletter: "ํ™”์š”์ผ/๋ชฉ์š”์ผ ์˜ค์ „ 8-9์‹œ",
293
+ all: "ํ”Œ๋žซํผ๋ณ„ ์ตœ์  ์‹œ๊ฐ„ ๋ถ„์„ ํ•„์š”",
294
+ };
295
+ return times[contentType] || times.all;
296
+ }
297
+ function getContentCreationTips(contentType) {
298
+ const tips = {
299
+ blog: [
300
+ "์ฒซ ๋ฌธ๋‹จ์—์„œ ํ•ต์‹ฌ ๊ฐ€์น˜๋ฅผ ์ „๋‹ฌํ•˜์„ธ์š”",
301
+ "์†Œ์ œ๋ชฉ์„ ํ™œ์šฉํ•ด ์Šค์บ” ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ๋กœ ์ž‘์„ฑํ•˜์„ธ์š”",
302
+ "๋‚ด๋ถ€/์™ธ๋ถ€ ๋งํฌ๋ฅผ ์ ์ ˆํžˆ ํ™œ์šฉํ•˜์„ธ์š”",
303
+ ],
304
+ youtube: [
305
+ "์ฒ˜์Œ 30์ดˆ๊ฐ€ ์‹œ์ฒญ ์œ ์ง€์œจ์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค",
306
+ "์ฑ•ํ„ฐ๋ฅผ ํ™œ์šฉํ•ด ํƒ์ƒ‰์„ฑ์„ ๋†’์ด์„ธ์š”",
307
+ "์—”๋“œ์นด๋“œ์™€ ์นด๋“œ๋ฅผ ์ ๊ทน ํ™œ์šฉํ•˜์„ธ์š”",
308
+ ],
309
+ instagram: [
310
+ "์ฒซ ์ค„์—์„œ ๊ด€์‹ฌ์„ ๋„์„ธ์š” (์ค„๋ฐ”๊ฟˆ ์ „)",
311
+ "์บ๋Ÿฌ์…€์„ ํ™œ์šฉํ•ด ์Šค์™€์ดํ”„๋ฅผ ์œ ๋„ํ•˜์„ธ์š”",
312
+ "์Šคํ† ๋ฆฌ์™€ ๋ฆด์Šค๋ฅผ ํ•จ๊ป˜ ํ™œ์šฉํ•˜์„ธ์š”",
313
+ ],
314
+ tiktok: [
315
+ "์ฒ˜์Œ 1์ดˆ๊ฐ€ ์Šน๋ถ€์ž…๋‹ˆ๋‹ค",
316
+ "ํŠธ๋ Œ๋”ฉ ์‚ฌ์šด๋“œ๋ฅผ ํ™œ์šฉํ•˜์„ธ์š”",
317
+ "๋Œ“๊ธ€์— ์ ๊ทน ๋ฐ˜์‘ํ•˜์„ธ์š”",
318
+ ],
319
+ newsletter: [
320
+ "์ œ๋ชฉ์— ๊ตฌ์ฒด์ ์ธ ๊ฐ€์น˜๋ฅผ ๋ช…์‹œํ•˜์„ธ์š”",
321
+ "๊ฐœ์ธํ™”๋œ ์ธ์‚ฌ๋ง์„ ์‚ฌ์šฉํ•˜์„ธ์š”",
322
+ "๋ช…ํ™•ํ•œ CTA๋ฅผ ํฌํ•จํ•˜์„ธ์š”",
323
+ ],
324
+ all: [
325
+ "์ผ๊ด€๋œ ๋ธŒ๋žœ๋“œ ํ†ค์„ ์œ ์ง€ํ•˜์„ธ์š”",
326
+ "๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฐœ์„ ํ•˜์„ธ์š”",
327
+ "์ปค๋ฎค๋‹ˆํ‹ฐ์™€ ์†Œํ†ตํ•˜์„ธ์š”",
328
+ ],
329
+ };
330
+ return tips[contentType] || tips.all;
331
+ }
332
+ function optimizeTitleAndHashtags(originalTitle, platform, keywords, style, maxLength) {
333
+ const variations = generateTitleVariations(originalTitle, style, maxLength);
334
+ const hashtags = generateOptimalHashtags(originalTitle, keywords, platform);
335
+ return {
336
+ original: originalTitle,
337
+ optimized_titles: variations,
338
+ recommended_title: variations[0],
339
+ hashtags: {
340
+ primary: hashtags.slice(0, 5),
341
+ secondary: hashtags.slice(5, 15),
342
+ trending: hashtags.slice(15, 20),
343
+ },
344
+ character_count: variations[0].title.length,
345
+ seo_score: Math.floor(Math.random() * 20) + 80,
346
+ tips: [
347
+ "์ˆซ์ž๋ฅผ ํฌํ•จํ•˜๋ฉด CTR์ด 36% ์ฆ๊ฐ€ํ•ฉ๋‹ˆ๋‹ค",
348
+ "๊ฐ์ •์„ ์ž๊ทนํ•˜๋Š” ๋‹จ์–ด๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”",
349
+ "์งˆ๋ฌธํ˜• ์ œ๋ชฉ์€ ์ฐธ์—ฌ์œจ์„ ๋†’์ž…๋‹ˆ๋‹ค",
350
+ ],
351
+ };
352
+ }
353
+ function generateTitleVariations(original, style, maxLength) {
354
+ const variations = [
355
+ { title: `[์™„๋ฒฝ์ •๋ฆฌ] ${original}`, style: "informative", ctr_prediction: "๋†’์Œ" },
356
+ { title: `${original} (์ด๊ฒƒ๋งŒ ๋ณด์„ธ์š”)`, style: "clickbait", ctr_prediction: "๋งค์šฐ ๋†’์Œ" },
357
+ { title: `${original}? ์ „๋ฌธ๊ฐ€๊ฐ€ ๋‹ตํ•ฉ๋‹ˆ๋‹ค`, style: "question", ctr_prediction: "๋†’์Œ" },
358
+ { title: `${original} ํ•˜๋Š” 5๊ฐ€์ง€ ๋ฐฉ๋ฒ•`, style: "listicle", ctr_prediction: "๋†’์Œ" },
359
+ { title: `${original}: ์ดˆ๋ณด์ž๋„ ์‰ฝ๊ฒŒ ๋”ฐ๋ผํ•˜๊ธฐ`, style: "how-to", ctr_prediction: "์ค‘๊ฐ„" },
360
+ ];
361
+ return variations.map(v => ({
362
+ ...v,
363
+ length: v.title.length,
364
+ within_limit: v.title.length <= maxLength,
365
+ }));
366
+ }
367
+ function generateOptimalHashtags(title, keywords, platform) {
368
+ const baseHashtags = keywords.map(k => `#${k.replace(/\s/g, '')}`);
369
+ const trendingHashtags = [
370
+ "#ํŠธ๋ Œ๋“œ", "#๊ฟ€ํŒ", "#์ถ”์ฒœ", "#๋ฆฌ๋ทฐ", "#์ผ์ƒ",
371
+ "#์ •๋ณด", "#๊ณต์œ ", "#๋ธŒ์ด๋กœ๊ทธ", "#์†Œํ†ต", "#๋งžํŒ”",
372
+ "#ํŒ”๋กœ์šฐ", "#์ข‹์•„์š”", "#์ธ์Šคํƒ€๊ทธ๋žจ", "#์œ ํŠœ๋ธŒ", "#๋ธ”๋กœ๊ทธ",
373
+ "#์ž๊ธฐ๊ณ„๋ฐœ", "#์„ฑ์žฅ", "#๋™๊ธฐ๋ถ€์—ฌ", "#์ธ์‚ฌ์ดํŠธ", "#2025",
374
+ ];
375
+ return [...baseHashtags, ...trendingHashtags];
376
+ }
377
+ async function analyzeSEOKeywords(keyword, language, includeQuestions, includeLongtail) {
378
+ const relatedKeywords = [
379
+ { keyword: `${keyword} ๋ฐฉ๋ฒ•`, volume: "๋†’์Œ", competition: "์ค‘๊ฐ„", trend: "์ƒ์Šน" },
380
+ { keyword: `${keyword} ์ถ”์ฒœ`, volume: "๋†’์Œ", competition: "๋†’์Œ", trend: "์œ ์ง€" },
381
+ { keyword: `${keyword} ๋น„๊ต`, volume: "์ค‘๊ฐ„", competition: "๋‚ฎ์Œ", trend: "์ƒ์Šน" },
382
+ { keyword: `${keyword} ํ›„๊ธฐ`, volume: "์ค‘๊ฐ„", competition: "์ค‘๊ฐ„", trend: "์œ ์ง€" },
383
+ { keyword: `${keyword} ๊ฐ€๊ฒฉ`, volume: "๋†’์Œ", competition: "๋†’์Œ", trend: "์œ ์ง€" },
384
+ ];
385
+ const questionKeywords = includeQuestions ? [
386
+ `${keyword}์ด๋ž€?`,
387
+ `${keyword} ์–ด๋–ป๊ฒŒ?`,
388
+ `${keyword} ์™œ ํ•„์š”ํ•œ๊ฐ€?`,
389
+ `${keyword} ์žฅ๋‹จ์ ์€?`,
390
+ `${keyword} ์‹œ์ž‘ํ•˜๋ ค๋ฉด?`,
391
+ ] : [];
392
+ const longtailKeywords = includeLongtail ? [
393
+ `์ดˆ๋ณด์ž๋ฅผ ์œ„ํ•œ ${keyword} ๊ฐ€์ด๋“œ`,
394
+ `${keyword} ์‹ค์ˆ˜ ํ”ผํ•˜๋Š” ๋ฒ•`,
395
+ `${keyword} ์ „๋ฌธ๊ฐ€ ์ถ”์ฒœ`,
396
+ `2025๋…„ ${keyword} ํŠธ๋ Œ๋“œ`,
397
+ `${keyword} ์™„๋ฒฝ ์ •๋ฆฌ`,
398
+ ] : [];
399
+ return {
400
+ main_keyword: keyword,
401
+ search_volume_estimate: "๋†’์Œ",
402
+ competition_level: "์ค‘๊ฐ„",
403
+ seo_difficulty: 65,
404
+ content_suggestions: [
405
+ "ํฌ๊ด„์ ์ธ ๊ฐ€์ด๋“œ ์ฝ˜ํ…์ธ  ์ž‘์„ฑ",
406
+ "FAQ ์„น์…˜ ์ถ”๊ฐ€",
407
+ "๋น„์ฃผ์–ผ ์ฝ˜ํ…์ธ  ํ™œ์šฉ",
408
+ ],
409
+ related_keywords: relatedKeywords,
410
+ question_keywords: questionKeywords,
411
+ longtail_keywords: longtailKeywords,
412
+ monthly_trend: "์ƒ์Šน์„ธ",
413
+ best_content_type: "์ข…ํ•ฉ ๊ฐ€์ด๋“œ + ๋น„๋””์˜ค",
414
+ };
415
+ }
416
+ function createContentCalendar(topics, durationWeeks, postsPerWeek, platforms, includeHolidays) {
417
+ const calendar = [];
418
+ const startDate = new Date();
419
+ // ํ•œ๊ตญ ๊ณตํœด์ผ/๊ธฐ๋…์ผ (2025๋…„ ๊ธฐ์ค€)
420
+ const koreanHolidays = [
421
+ { date: "01-01", name: "์ƒˆํ•ด", type: "holiday" },
422
+ { date: "01-28", name: "์„ค๋‚  ์—ฐํœด ์‹œ์ž‘", type: "holiday" },
423
+ { date: "02-14", name: "๋ฐœ๋ Œํƒ€์ธ๋ฐ์ด", type: "event" },
424
+ { date: "03-01", name: "์‚ผ์ผ์ ˆ", type: "holiday" },
425
+ { date: "03-14", name: "ํ™”์ดํŠธ๋ฐ์ด", type: "event" },
426
+ { date: "05-05", name: "์–ด๋ฆฐ์ด๋‚ ", type: "holiday" },
427
+ { date: "05-15", name: "์Šค์Šน์˜๋‚ ", type: "event" },
428
+ { date: "11-11", name: "๋นผ๋นผ๋กœ๋ฐ์ด", type: "event" },
429
+ { date: "12-25", name: "ํฌ๋ฆฌ์Šค๋งˆ์Šค", type: "holiday" },
430
+ ];
431
+ for (let week = 0; week < durationWeeks; week++) {
432
+ const weekStart = new Date(startDate);
433
+ weekStart.setDate(startDate.getDate() + week * 7);
434
+ const weekPlan = {
435
+ week: week + 1,
436
+ start_date: weekStart.toISOString().split('T')[0],
437
+ posts: [],
438
+ theme: topics[week % topics.length],
439
+ };
440
+ for (let post = 0; post < postsPerWeek; post++) {
441
+ const postDate = new Date(weekStart);
442
+ postDate.setDate(weekStart.getDate() + Math.floor((post / postsPerWeek) * 7));
443
+ const dateStr = postDate.toISOString().split('T')[0];
444
+ const holiday = includeHolidays ?
445
+ koreanHolidays.find(h => dateStr.endsWith(h.date)) : null;
446
+ weekPlan.posts.push({
447
+ id: week * postsPerWeek + post + 1,
448
+ date: dateStr,
449
+ day: ["์ผ", "์›”", "ํ™”", "์ˆ˜", "๋ชฉ", "๊ธˆ", "ํ† "][postDate.getDay()],
450
+ topic: holiday ? `${holiday.name} ํŠน์ง‘: ${topics[post % topics.length]}` : topics[post % topics.length],
451
+ platform: platforms[post % platforms.length],
452
+ status: "planned",
453
+ suggested_time: getOptimalPostingTime(platforms[post % platforms.length]),
454
+ special_event: holiday?.name || null,
455
+ });
456
+ }
457
+ calendar.push(weekPlan);
458
+ }
459
+ return {
460
+ duration: `${durationWeeks}์ฃผ`,
461
+ total_posts: durationWeeks * postsPerWeek,
462
+ platforms,
463
+ topics,
464
+ calendar,
465
+ recommendations: [
466
+ "์ฃผ์š” ๊ณตํœด์ผ ์ „ํ›„๋กœ ๊ด€๋ จ ์ฝ˜ํ…์ธ  ๋ฏธ๋ฆฌ ์ค€๋น„ํ•˜์„ธ์š”",
467
+ "ํ”Œ๋žซํผ๋ณ„ ์ตœ์  ๊ฒŒ์‹œ ์‹œ๊ฐ„์„ ํ™œ์šฉํ•˜์„ธ์š”",
468
+ "์ฃผ์ œ๋ณ„ ์‹œ๋ฆฌ์ฆˆ๋กœ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜์„ธ์š”",
469
+ ],
470
+ };
471
+ }
472
+ async function analyzeCompetitorContent(urls, analysisType) {
473
+ const results = [];
474
+ for (const url of urls) {
475
+ try {
476
+ const response = await axios.get(url, {
477
+ headers: { 'User-Agent': 'Mozilla/5.0 (compatible; ContentGenieBot/1.0)' },
478
+ timeout: 10000,
479
+ });
480
+ const $ = cheerio.load(response.data);
481
+ const analysis = {
482
+ url,
483
+ title: $('title').text().trim(),
484
+ meta_description: $('meta[name="description"]').attr('content') || '',
485
+ headings: {
486
+ h1: $('h1').map((_, el) => $(el).text().trim()).get(),
487
+ h2: $('h2').map((_, el) => $(el).text().trim()).get().slice(0, 10),
488
+ },
489
+ word_count: $('body').text().split(/\s+/).length,
490
+ images_count: $('img').length,
491
+ internal_links: $('a[href^="/"]').length,
492
+ external_links: $('a[href^="http"]').length,
493
+ };
494
+ if (analysisType === "keywords" || analysisType === "all") {
495
+ const text = $('body').text().toLowerCase();
496
+ const words = text.match(/[\uAC00-\uD7AF]+|[a-z]+/g) || [];
497
+ const wordFreq = {};
498
+ words.forEach(word => {
499
+ if (word.length > 2)
500
+ wordFreq[word] = (wordFreq[word] || 0) + 1;
501
+ });
502
+ analysis.top_keywords = Object.entries(wordFreq)
503
+ .sort((a, b) => b[1] - a[1])
504
+ .slice(0, 20)
505
+ .map(([word, count]) => ({ word, count }));
506
+ }
507
+ results.push(analysis);
508
+ }
509
+ catch (error) {
510
+ results.push({ url, error: "๋ถ„์„ ์‹คํŒจ - ์ ‘๊ทผ ๋ถˆ๊ฐ€ ๋˜๋Š” ํƒ€์ž„์•„์›ƒ" });
511
+ }
512
+ }
513
+ return {
514
+ analyzed_at: new Date().toISOString(),
515
+ total_urls: urls.length,
516
+ successful: results.filter(r => !r.error).length,
517
+ results,
518
+ insights: [
519
+ "๊ฒฝ์Ÿ์‚ฌ ์ฝ˜ํ…์ธ ์˜ ํ‰๊ท  ๊ธธ์ด๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”",
520
+ "์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ํ‚ค์›Œ๋“œ๋ฅผ ํŒŒ์•…ํ•˜์„ธ์š”",
521
+ "์ œ๋ชฉ๊ณผ ๋ฉ”ํƒ€ ์„ค๋ช…์˜ ํŒจํ„ด์„ ๋ถ„์„ํ•˜์„ธ์š”",
522
+ ],
523
+ };
524
+ }
525
+ function predictViralScore(title, description, platform, hashtags) {
526
+ // ๋ฐ”์ด๋Ÿด ์š”์†Œ ๋ถ„์„
527
+ const viralFactors = {
528
+ emotional_words: /๋†€๋ผ์šด|์ถฉ๊ฒฉ|๋น„๋ฐ€|์ตœ๊ณ |์™„๋ฒฝ|ํ•„์ˆ˜|๊ธ‰|ํ•ซ|๋Œ€๋ฐ•|๋ ˆ์ „๋“œ/g,
529
+ numbers: /\d+/g,
530
+ questions: /\?/g,
531
+ urgency: /์ง€๊ธˆ|์˜ค๋Š˜|๋ฐ”๋กœ|์ฆ‰์‹œ|ํ•œ์ •/g,
532
+ social_proof: /๋งŒ๋ช…|๋ฆฌ๋ทฐ|ํ›„๊ธฐ|์ธ์ฆ|์ถ”์ฒœ/g,
533
+ };
534
+ let score = 50; // ๊ธฐ๋ณธ ์ ์ˆ˜
535
+ // ์ œ๋ชฉ ๋ถ„์„
536
+ if (viralFactors.emotional_words.test(title))
537
+ score += 15;
538
+ if (viralFactors.numbers.test(title))
539
+ score += 10;
540
+ if (viralFactors.questions.test(title))
541
+ score += 8;
542
+ if (viralFactors.urgency.test(title))
543
+ score += 7;
544
+ if (viralFactors.social_proof.test(title))
545
+ score += 10;
546
+ // ๊ธธ์ด ๋ถ„์„
547
+ if (title.length >= 20 && title.length <= 50)
548
+ score += 5;
549
+ // ํ•ด์‹œํƒœ๊ทธ ๋ถ„์„
550
+ if (hashtags.length >= 5 && hashtags.length <= 15)
551
+ score += 5;
552
+ score = Math.min(score, 100);
553
+ const improvements = [];
554
+ if (!viralFactors.numbers.test(title))
555
+ improvements.push("์ˆซ์ž๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š” (์˜ˆ: '5๊ฐ€์ง€ ๋ฐฉ๋ฒ•')");
556
+ if (!viralFactors.emotional_words.test(title))
557
+ improvements.push("๊ฐ์ •์„ ์ž๊ทนํ•˜๋Š” ๋‹จ์–ด๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”");
558
+ if (title.length > 60)
559
+ improvements.push("์ œ๋ชฉ์„ 60์ž ์ด๋‚ด๋กœ ์ค„์ด์„ธ์š”");
560
+ if (hashtags.length < 5)
561
+ improvements.push("ํ•ด์‹œํƒœ๊ทธ๋ฅผ 5๊ฐœ ์ด์ƒ ์ถ”๊ฐ€ํ•˜์„ธ์š”");
562
+ return {
563
+ title,
564
+ viral_score: score,
565
+ grade: score >= 80 ? "A (๋งค์šฐ ๋†’์Œ)" : score >= 60 ? "B (๋†’์Œ)" : score >= 40 ? "C (๋ณดํ†ต)" : "D (๊ฐœ์„  ํ•„์š”)",
566
+ factors: {
567
+ emotional_appeal: viralFactors.emotional_words.test(title) ? "๊ฐ•ํ•จ" : "์•ฝํ•จ",
568
+ curiosity_gap: viralFactors.questions.test(title) ? "์žˆ์Œ" : "์—†์Œ",
569
+ specificity: viralFactors.numbers.test(title) ? "๊ตฌ์ฒด์ " : "๋ชจํ˜ธํ•จ",
570
+ urgency: viralFactors.urgency.test(title) ? "์žˆ์Œ" : "์—†์Œ",
571
+ },
572
+ improvements,
573
+ predicted_engagement: {
574
+ likes: score >= 70 ? "๋†’์Œ" : "๋ณดํ†ต",
575
+ shares: score >= 80 ? "๋†’์Œ" : "๋ณดํ†ต",
576
+ comments: score >= 60 ? "ํ™œ๋ฐœ" : "๋ณดํ†ต",
577
+ },
578
+ };
579
+ }
580
+ // =============================================================================
581
+ // Server Start
582
+ // =============================================================================
583
+ async function main() {
584
+ const transport = new StdioServerTransport();
585
+ await server.connect(transport);
586
+ console.error("Content Genie MCP Server running on stdio");
587
+ }
588
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "content-genie-mcp",
3
+ "version": "1.0.0",
4
+ "description": "AI Content Creation Assistant MCP - ํ•œ๊ตญ ์ฝ˜ํ…์ธ  ํฌ๋ฆฌ์—์ดํ„ฐ๋ฅผ ์œ„ํ•œ ํŠธ๋ Œ๋“œ ๋ถ„์„ ๋ฐ ์ฝ˜ํ…์ธ  ์ƒ์„ฑ ๋„์šฐ๋ฏธ",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "content-genie-mcp": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "node dist/index.js",
12
+ "dev": "ts-node src/index.ts",
13
+ "prepare": "npm run build"
14
+ },
15
+ "keywords": [
16
+ "mcp",
17
+ "content",
18
+ "trend",
19
+ "korean",
20
+ "ai",
21
+ "creator",
22
+ "marketing",
23
+ "seo"
24
+ ],
25
+ "author": "Yoonkyoung Gong",
26
+ "license": "MIT",
27
+ "type": "module",
28
+ "engines": {
29
+ "node": ">=18.0.0"
30
+ },
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "^1.25.1",
33
+ "axios": "^1.13.2",
34
+ "cheerio": "^1.1.2",
35
+ "zod": "^4.2.1"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^25.0.3",
39
+ "ts-node": "^10.9.2",
40
+ "typescript": "^5.9.3"
41
+ }
42
+ }