content-genie-mcp 2.9.1 → 2.9.2
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/Dockerfile +10 -4
- package/dist/index.js +191 -41
- package/package.json +4 -1
package/Dockerfile
CHANGED
|
@@ -2,12 +2,18 @@ FROM node:20-alpine
|
|
|
2
2
|
|
|
3
3
|
WORKDIR /app
|
|
4
4
|
|
|
5
|
-
#
|
|
6
|
-
COPY package*.json
|
|
5
|
+
# package.json과 tsconfig.json 복사
|
|
6
|
+
COPY package*.json ./
|
|
7
|
+
COPY tsconfig.json ./
|
|
8
|
+
|
|
9
|
+
# 의존성 설치 (prepare 스크립트 무시)
|
|
10
|
+
RUN npm ci --ignore-scripts
|
|
11
|
+
|
|
12
|
+
# 소스 코드 복사
|
|
7
13
|
COPY src/ ./src/
|
|
8
14
|
|
|
9
|
-
#
|
|
10
|
-
RUN npm
|
|
15
|
+
# 빌드 실행
|
|
16
|
+
RUN npm run build
|
|
11
17
|
|
|
12
18
|
# 프로덕션 의존성만 남기기
|
|
13
19
|
RUN npm prune --production
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import {
|
|
4
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import axios from "axios";
|
|
7
7
|
import * as cheerio from "cheerio";
|
|
8
|
-
import
|
|
8
|
+
import express from "express";
|
|
9
|
+
import cors from "cors";
|
|
10
|
+
import crypto from "crypto";
|
|
9
11
|
// =============================================================================
|
|
10
12
|
// Content Genie MCP v2.5 - 한국 콘텐츠 크리에이터를 위한 AI 어시스턴트 (프로 버전)
|
|
11
13
|
// =============================================================================
|
|
12
14
|
const server = new McpServer({
|
|
13
15
|
name: "content-genie-mcp",
|
|
14
|
-
version: "2.
|
|
16
|
+
version: "2.9.2",
|
|
15
17
|
});
|
|
16
18
|
// =============================================================================
|
|
17
19
|
// 공통 스키마
|
|
@@ -354,13 +356,13 @@ server.tool("optimize_title_hashtags", "AI 기반으로 콘텐츠 제목을 최
|
|
|
354
356
|
// =============================================================================
|
|
355
357
|
// Tool 4: SEO 키워드 분석 (analyze_seo_keywords) - 고도화
|
|
356
358
|
// =============================================================================
|
|
357
|
-
server.tool("analyze_seo_keywords", "키워드의 SEO 잠재력을 심층 분석하고
|
|
359
|
+
server.tool("analyze_seo_keywords", "키워드의 SEO 잠재력을 심층 분석하고 다음/네이버/구글 최적화 전략을 제공합니다.", {
|
|
358
360
|
keyword: z.string().describe("분석할 메인 키워드"),
|
|
359
|
-
search_engine: z.enum(["naver", "google", "
|
|
361
|
+
search_engine: z.enum(["daum", "naver", "google", "all"]).optional().describe("검색엔진 (daum, naver, google, all)"),
|
|
360
362
|
include_questions: z.boolean().optional().describe("관련 질문 키워드 포함"),
|
|
361
363
|
include_longtail: z.boolean().optional().describe("롱테일 키워드 포함"),
|
|
362
364
|
competitor_analysis: z.boolean().optional().describe("경쟁 분석 포함"),
|
|
363
|
-
}, async ({ keyword, search_engine = "
|
|
365
|
+
}, async ({ keyword, search_engine = "all", include_questions = true, include_longtail = true, competitor_analysis = true }) => {
|
|
364
366
|
try {
|
|
365
367
|
const analysis = await analyzeAdvancedSEOKeywords(keyword, search_engine, include_questions, include_longtail, competitor_analysis);
|
|
366
368
|
return {
|
|
@@ -618,6 +620,31 @@ async function getGoogleAutocomplete(keyword) {
|
|
|
618
620
|
return [];
|
|
619
621
|
}
|
|
620
622
|
}
|
|
623
|
+
// 다음 자동완성 키워드
|
|
624
|
+
async function getDaumAutocomplete(keyword) {
|
|
625
|
+
try {
|
|
626
|
+
const response = await axios.get(`https://suggest.search.daum.net/sushi/ns`, {
|
|
627
|
+
params: {
|
|
628
|
+
w: 'tot',
|
|
629
|
+
mod: 'json',
|
|
630
|
+
code: 'utf_in_out',
|
|
631
|
+
q: keyword,
|
|
632
|
+
},
|
|
633
|
+
headers: {
|
|
634
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
635
|
+
},
|
|
636
|
+
timeout: 5000,
|
|
637
|
+
});
|
|
638
|
+
// 응답 형식: {"q":"keyword","items":["suggestion1", "suggestion2", ...]}
|
|
639
|
+
if (response.data && Array.isArray(response.data.items)) {
|
|
640
|
+
return response.data.items.slice(0, 10);
|
|
641
|
+
}
|
|
642
|
+
return [];
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
return [];
|
|
646
|
+
}
|
|
647
|
+
}
|
|
621
648
|
// 네이버 연관검색어 스크래핑
|
|
622
649
|
async function getNaverRelatedKeywords(keyword) {
|
|
623
650
|
try {
|
|
@@ -705,6 +732,34 @@ async function getGoogleSearchResultCount(keyword) {
|
|
|
705
732
|
return 100000;
|
|
706
733
|
}
|
|
707
734
|
}
|
|
735
|
+
// 다음 검색 결과 수 추정
|
|
736
|
+
async function getDaumSearchResultCount(keyword) {
|
|
737
|
+
try {
|
|
738
|
+
const response = await axios.get(`https://search.daum.net/search`, {
|
|
739
|
+
params: {
|
|
740
|
+
w: 'blog',
|
|
741
|
+
q: keyword,
|
|
742
|
+
},
|
|
743
|
+
headers: {
|
|
744
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
745
|
+
},
|
|
746
|
+
timeout: 5000,
|
|
747
|
+
});
|
|
748
|
+
const $ = cheerio.load(response.data);
|
|
749
|
+
// 검색 결과 수 추출 시도
|
|
750
|
+
const countText = $('.sub_expander .txt_info, .cont_result .txt_info, [class*="count"]').first().text();
|
|
751
|
+
const match = countText.match(/[\d,]+/);
|
|
752
|
+
if (match) {
|
|
753
|
+
return parseInt(match[0].replace(/,/g, ''), 10);
|
|
754
|
+
}
|
|
755
|
+
// 대략적 추정: 검색 결과 아이템 수 기반
|
|
756
|
+
const itemCount = $('.wrap_cont.blog, .cont_blog').length;
|
|
757
|
+
return itemCount > 0 ? itemCount * 10000 : 50000;
|
|
758
|
+
}
|
|
759
|
+
catch {
|
|
760
|
+
return 50000; // 기본값
|
|
761
|
+
}
|
|
762
|
+
}
|
|
708
763
|
// 검색량 레벨 추정 (자동완성 순위 기반)
|
|
709
764
|
function estimateSearchVolume(autocompleteRank, resultCount) {
|
|
710
765
|
// 자동완성 상위 + 결과 수 많음 = 검색량 높음
|
|
@@ -1984,20 +2039,26 @@ function calculateReadabilityScore(text) {
|
|
|
1984
2039
|
// 고급 SEO 키워드 분석 - 실시간 데이터 기반
|
|
1985
2040
|
async function analyzeAdvancedSEOKeywords(keyword, searchEngine, includeQuestions, includeLongtail, competitorAnalysis) {
|
|
1986
2041
|
// 병렬로 실시간 데이터 수집
|
|
1987
|
-
const
|
|
1988
|
-
|
|
1989
|
-
|
|
2042
|
+
const includeNaver = searchEngine === 'naver' || searchEngine === 'all';
|
|
2043
|
+
const includeGoogle = searchEngine === 'google' || searchEngine === 'all';
|
|
2044
|
+
const includeDaum = searchEngine === 'daum' || searchEngine === 'all';
|
|
2045
|
+
const [naverAutocomplete, googleAutocomplete, daumAutocomplete, naverRelated, naverResultCount, googleResultCount, daumResultCount] = await Promise.all([
|
|
2046
|
+
includeNaver ? getNaverAutocomplete(keyword) : Promise.resolve([]),
|
|
2047
|
+
includeGoogle ? getGoogleAutocomplete(keyword) : Promise.resolve([]),
|
|
2048
|
+
includeDaum ? getDaumAutocomplete(keyword) : Promise.resolve([]),
|
|
1990
2049
|
getNaverRelatedKeywords(keyword),
|
|
1991
|
-
|
|
1992
|
-
|
|
2050
|
+
includeNaver ? getNaverSearchResultCount(keyword) : Promise.resolve(0),
|
|
2051
|
+
includeGoogle ? getGoogleSearchResultCount(keyword) : Promise.resolve(0),
|
|
2052
|
+
includeDaum ? getDaumSearchResultCount(keyword) : Promise.resolve(0),
|
|
1993
2053
|
]);
|
|
1994
2054
|
// 주요 검색 엔진 결과 수 선택
|
|
1995
2055
|
const primaryResultCount = searchEngine === 'naver' ? naverResultCount :
|
|
1996
2056
|
searchEngine === 'google' ? googleResultCount :
|
|
1997
|
-
|
|
2057
|
+
searchEngine === 'daum' ? daumResultCount :
|
|
2058
|
+
Math.max(naverResultCount, googleResultCount, daumResultCount);
|
|
1998
2059
|
// 경쟁도 및 검색량 추정
|
|
1999
2060
|
const competition = estimateCompetition(primaryResultCount);
|
|
2000
|
-
const autocompleteKeywords = [...new Set([...naverAutocomplete, ...googleAutocomplete])];
|
|
2061
|
+
const autocompleteKeywords = [...new Set([...daumAutocomplete, ...naverAutocomplete, ...googleAutocomplete])];
|
|
2001
2062
|
const keywordRank = autocompleteKeywords.findIndex(k => k.includes(keyword)) + 1 || 10;
|
|
2002
2063
|
const searchVolume = estimateSearchVolume(keywordRank, primaryResultCount);
|
|
2003
2064
|
// SEO 난이도 계산
|
|
@@ -2140,6 +2201,18 @@ async function analyzeAdvancedSEOKeywords(keyword, searchEngine, includeQuestion
|
|
|
2140
2201
|
],
|
|
2141
2202
|
content_types: ["웹사이트", "유튜브", "뉴스"],
|
|
2142
2203
|
},
|
|
2204
|
+
daum: {
|
|
2205
|
+
result_count: daumResultCount.toLocaleString(),
|
|
2206
|
+
competition: estimateCompetition(daumResultCount).level,
|
|
2207
|
+
tips: [
|
|
2208
|
+
"다음 블로그/카페에 발행하세요",
|
|
2209
|
+
"카카오 채널과 연동 고려",
|
|
2210
|
+
"티스토리 블로그 활용 추천",
|
|
2211
|
+
"다음 뉴스 검색 노출 전략",
|
|
2212
|
+
"카카오톡 공유 최적화",
|
|
2213
|
+
],
|
|
2214
|
+
content_types: ["티스토리", "다음카페", "브런치"],
|
|
2215
|
+
},
|
|
2143
2216
|
};
|
|
2144
2217
|
// 추천 액션 생성
|
|
2145
2218
|
const recommendedAction = seoDifficulty > 70
|
|
@@ -2150,9 +2223,11 @@ async function analyzeAdvancedSEOKeywords(keyword, searchEngine, includeQuestion
|
|
|
2150
2223
|
return {
|
|
2151
2224
|
main_keyword: keyword,
|
|
2152
2225
|
data_source: {
|
|
2226
|
+
daum_autocomplete: daumAutocomplete.length,
|
|
2153
2227
|
naver_autocomplete: naverAutocomplete.length,
|
|
2154
2228
|
google_autocomplete: googleAutocomplete.length,
|
|
2155
2229
|
naver_related: naverRelated.length,
|
|
2230
|
+
daum_results: daumResultCount.toLocaleString(),
|
|
2156
2231
|
naver_results: naverResultCount.toLocaleString(),
|
|
2157
2232
|
google_results: googleResultCount.toLocaleString(),
|
|
2158
2233
|
},
|
|
@@ -2169,7 +2244,7 @@ async function analyzeAdvancedSEOKeywords(keyword, searchEngine, includeQuestion
|
|
|
2169
2244
|
related_keywords: relatedKeywords.slice(0, 15),
|
|
2170
2245
|
question_keywords: questionKeywords.slice(0, 8),
|
|
2171
2246
|
longtail_keywords: longtailKeywords.slice(0, 8),
|
|
2172
|
-
search_engine_strategy: searchEngine === "
|
|
2247
|
+
search_engine_strategy: searchEngine === "all" ? searchEngineStrategy : searchEngineStrategy[searchEngine],
|
|
2173
2248
|
content_recommendations: {
|
|
2174
2249
|
ideal_length: seoDifficulty > 60 ? "4000-6000자 (경쟁 대응)" : "2500-4000자",
|
|
2175
2250
|
must_include: ["정의", "방법", "예시", "FAQ", "비교"],
|
|
@@ -3910,44 +3985,119 @@ async function main() {
|
|
|
3910
3985
|
if (isHttpMode) {
|
|
3911
3986
|
// HTTP/SSE 모드 (PlayMCP, 웹 클라이언트용)
|
|
3912
3987
|
console.log(`Starting Content Genie MCP Server v2.9.0 in HTTP mode on port ${port}...`);
|
|
3913
|
-
const
|
|
3914
|
-
|
|
3988
|
+
const app = express();
|
|
3989
|
+
app.use(cors());
|
|
3990
|
+
app.use(express.json());
|
|
3991
|
+
// 도구 목록 (PlayMCP 연결 확인용)
|
|
3992
|
+
const toolsList = [
|
|
3993
|
+
{ name: "get_korean_trends", description: "다음/네이버 실시간 트렌드 조회" },
|
|
3994
|
+
{ name: "analyze_news_trends", description: "뉴스 트렌드 분석" },
|
|
3995
|
+
{ name: "get_seasonal_content_guide", description: "시즌 콘텐츠 가이드" },
|
|
3996
|
+
{ name: "analyze_seo_keywords", description: "SEO 키워드 심층 분석" },
|
|
3997
|
+
{ name: "generate_hashtag_strategy", description: "해시태그 전략 생성" },
|
|
3998
|
+
{ name: "analyze_competitor_content", description: "경쟁사 콘텐츠 분석" },
|
|
3999
|
+
{ name: "generate_content_ideas", description: "콘텐츠 아이디어 생성" },
|
|
4000
|
+
{ name: "optimize_title_hashtags", description: "제목/해시태그 최적화" },
|
|
4001
|
+
{ name: "create_content_calendar", description: "콘텐츠 캘린더 생성" },
|
|
4002
|
+
{ name: "generate_script_outline", description: "스크립트 아웃라인 생성" },
|
|
4003
|
+
{ name: "repurpose_content", description: "콘텐츠 리퍼포징" },
|
|
4004
|
+
{ name: "predict_viral_score", description: "바이럴 점수 예측" },
|
|
4005
|
+
{ name: "benchmark_content_performance", description: "성과 벤치마크" },
|
|
4006
|
+
{ name: "predict_content_performance", description: "콘텐츠 성과 예측" },
|
|
4007
|
+
{ name: "analyze_thumbnail", description: "썸네일 분석" },
|
|
4008
|
+
{ name: "generate_ab_test_variants", description: "A/B 테스트 변형 생성" },
|
|
4009
|
+
{ name: "analyze_influencer_collab", description: "인플루언서 협업 분석" },
|
|
4010
|
+
];
|
|
4011
|
+
// Health check
|
|
4012
|
+
app.get('/', (_req, res) => {
|
|
4013
|
+
res.json({
|
|
4014
|
+
status: 'ok',
|
|
4015
|
+
server: 'content-genie-mcp',
|
|
4016
|
+
version: '2.9.2',
|
|
4017
|
+
tools: 17,
|
|
4018
|
+
timestamp: new Date().toISOString()
|
|
4019
|
+
});
|
|
3915
4020
|
});
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
4021
|
+
app.get('/health', (_req, res) => {
|
|
4022
|
+
res.json({
|
|
4023
|
+
status: 'ok',
|
|
4024
|
+
server: 'content-genie-mcp',
|
|
4025
|
+
version: '2.9.2',
|
|
4026
|
+
tools: 17,
|
|
4027
|
+
timestamp: new Date().toISOString()
|
|
4028
|
+
});
|
|
4029
|
+
});
|
|
4030
|
+
// MCP 엔드포인트 - PlayMCP 연결 확인용 (간단한 응답)
|
|
4031
|
+
app.post('/mcp', (req, res) => {
|
|
4032
|
+
const { method, id } = req.body;
|
|
4033
|
+
// initialize 요청에 대한 응답
|
|
4034
|
+
if (method === 'initialize') {
|
|
4035
|
+
res.json({
|
|
4036
|
+
jsonrpc: '2.0',
|
|
4037
|
+
id,
|
|
4038
|
+
result: {
|
|
4039
|
+
protocolVersion: '2024-11-05',
|
|
4040
|
+
capabilities: { tools: { listChanged: true } },
|
|
4041
|
+
serverInfo: { name: 'content-genie-mcp', version: '2.9.2' }
|
|
4042
|
+
}
|
|
4043
|
+
});
|
|
3924
4044
|
return;
|
|
3925
4045
|
}
|
|
3926
|
-
//
|
|
3927
|
-
if (
|
|
3928
|
-
res.
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
tools: 17,
|
|
3934
|
-
timestamp: new Date().toISOString()
|
|
3935
|
-
}));
|
|
4046
|
+
// tools/list 요청에 대한 응답
|
|
4047
|
+
if (method === 'tools/list') {
|
|
4048
|
+
res.json({
|
|
4049
|
+
jsonrpc: '2.0',
|
|
4050
|
+
id,
|
|
4051
|
+
result: { tools: toolsList }
|
|
4052
|
+
});
|
|
3936
4053
|
return;
|
|
3937
4054
|
}
|
|
3938
|
-
//
|
|
3939
|
-
|
|
3940
|
-
|
|
4055
|
+
// 기타 요청
|
|
4056
|
+
res.json({
|
|
4057
|
+
jsonrpc: '2.0',
|
|
4058
|
+
id,
|
|
4059
|
+
error: { code: -32601, message: 'Method not found' }
|
|
4060
|
+
});
|
|
4061
|
+
});
|
|
4062
|
+
// SSE endpoint for MCP connection (MCP Inspector용)
|
|
4063
|
+
const transports = new Map();
|
|
4064
|
+
app.get('/sse', async (_req, res) => {
|
|
4065
|
+
console.log('New SSE connection established');
|
|
4066
|
+
const transport = new SSEServerTransport('/message', res);
|
|
4067
|
+
const sessionId = crypto.randomUUID();
|
|
4068
|
+
transports.set(sessionId, transport);
|
|
4069
|
+
res.on('close', () => {
|
|
4070
|
+
console.log('SSE connection closed');
|
|
4071
|
+
transports.delete(sessionId);
|
|
4072
|
+
});
|
|
4073
|
+
await server.connect(transport);
|
|
4074
|
+
});
|
|
4075
|
+
// Message endpoint for MCP communication
|
|
4076
|
+
app.post('/message', async (req, res) => {
|
|
4077
|
+
const sessionId = req.query.sessionId;
|
|
4078
|
+
if (!sessionId) {
|
|
4079
|
+
const transport = Array.from(transports.values())[0];
|
|
4080
|
+
if (transport) {
|
|
4081
|
+
await transport.handlePostMessage(req, res);
|
|
4082
|
+
}
|
|
4083
|
+
else {
|
|
4084
|
+
res.status(400).json({ error: 'No active session' });
|
|
4085
|
+
}
|
|
3941
4086
|
return;
|
|
3942
4087
|
}
|
|
3943
|
-
|
|
3944
|
-
|
|
4088
|
+
const transport = transports.get(sessionId);
|
|
4089
|
+
if (transport) {
|
|
4090
|
+
await transport.handlePostMessage(req, res);
|
|
4091
|
+
}
|
|
4092
|
+
else {
|
|
4093
|
+
res.status(404).json({ error: 'Session not found' });
|
|
4094
|
+
}
|
|
3945
4095
|
});
|
|
3946
|
-
|
|
3947
|
-
httpServer.listen(port, () => {
|
|
4096
|
+
app.listen(port, () => {
|
|
3948
4097
|
console.log(`Content Genie MCP Server v2.9.0 running on HTTP port ${port}`);
|
|
3949
4098
|
console.log(`Health check: http://localhost:${port}/health`);
|
|
3950
4099
|
console.log(`MCP endpoint: http://localhost:${port}/mcp`);
|
|
4100
|
+
console.log(`SSE endpoint: http://localhost:${port}/sse`);
|
|
3951
4101
|
});
|
|
3952
4102
|
}
|
|
3953
4103
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "content-genie-mcp",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.2",
|
|
4
4
|
"description": "AI Content Creation Assistant MCP - 한국 콘텐츠 크리에이터를 위한 트렌드 분석 및 콘텐츠 생성 도우미",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": "dist/index.js",
|
|
@@ -36,9 +36,12 @@
|
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
37
37
|
"axios": "^1.13.2",
|
|
38
38
|
"cheerio": "^1.1.2",
|
|
39
|
+
"cors": "^2.8.5",
|
|
39
40
|
"zod": "^4.2.1"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
43
|
+
"@types/cors": "^2.8.19",
|
|
44
|
+
"@types/express": "^5.0.6",
|
|
42
45
|
"@types/node": "^25.0.3",
|
|
43
46
|
"ts-node": "^10.9.2",
|
|
44
47
|
"typescript": "^5.9.3"
|