careerly-data-mcp 2.1.1 → 2.2.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 +51 -3
- package/dist/cli/setup.d.ts +10 -2
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +283 -14
- package/dist/cli/setup.js.map +1 -1
- package/dist/gsc/auth.d.ts +37 -0
- package/dist/gsc/auth.d.ts.map +1 -0
- package/dist/gsc/auth.js +99 -0
- package/dist/gsc/auth.js.map +1 -0
- package/dist/gsc/client.d.ts +40 -0
- package/dist/gsc/client.d.ts.map +1 -0
- package/dist/gsc/client.js +177 -0
- package/dist/gsc/client.js.map +1 -0
- package/dist/gsc/index.d.ts +10 -0
- package/dist/gsc/index.d.ts.map +1 -0
- package/dist/gsc/index.js +9 -0
- package/dist/gsc/index.js.map +1 -0
- package/dist/gsc/types.d.ts +79 -0
- package/dist/gsc/types.d.ts.map +1 -0
- package/dist/gsc/types.js +80 -0
- package/dist/gsc/types.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +9 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/tools/gsc-query.d.ts +10 -0
- package/dist/tools/gsc-query.d.ts.map +1 -0
- package/dist/tools/gsc-query.js +293 -0
- package/dist/tools/gsc-query.js.map +1 -0
- package/dist/tools/gsc-sites.d.ts +10 -0
- package/dist/tools/gsc-sites.d.ts.map +1 -0
- package/dist/tools/gsc-sites.js +93 -0
- package/dist/tools/gsc-sites.js.map +1 -0
- package/dist/tools/gsc-status.d.ts +10 -0
- package/dist/tools/gsc-status.d.ts.map +1 -0
- package/dist/tools/gsc-status.js +73 -0
- package/dist/tools/gsc-status.js.map +1 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Careerly Data MCP Server
|
|
2
2
|
|
|
3
|
-
Claude Code에서 **자연어로 GA4
|
|
3
|
+
Claude Code에서 **자연어로 GA4, BigQuery, Search Console 데이터를 분석**하는 MCP(Model Context Protocol) 서버입니다.
|
|
4
4
|
|
|
5
5
|
## 왜 MCP를 쓰나요? (vs API 직접 호출)
|
|
6
6
|
|
|
@@ -16,6 +16,7 @@ BigQuery 분석하고 싶다 → SQL 작성 → UNNEST 문법 검색 → 쿼리
|
|
|
16
16
|
```
|
|
17
17
|
"지난 7일 세션수 보여줘" → 끝!
|
|
18
18
|
"page_view → post_detail_view 전환율 분석해줘" → 끝!
|
|
19
|
+
"검색어별 클릭수 보여줘" → 끝!
|
|
19
20
|
```
|
|
20
21
|
|
|
21
22
|
### 핵심 장점
|
|
@@ -58,7 +59,8 @@ npx careerly-data-mcp setup
|
|
|
58
59
|
1. [Google Cloud Console](https://console.cloud.google.com/)에서 프로젝트 생성
|
|
59
60
|
2. **Google Analytics Data API** 활성화
|
|
60
61
|
3. **BigQuery API** 활성화
|
|
61
|
-
4.
|
|
62
|
+
4. **Search Console API** 활성화
|
|
63
|
+
5. **서비스 계정** 생성 후 JSON 키 다운로드
|
|
62
64
|
|
|
63
65
|
### 2. GA4 권한 설정
|
|
64
66
|
|
|
@@ -69,6 +71,11 @@ npx careerly-data-mcp setup
|
|
|
69
71
|
|
|
70
72
|
- IAM & Admin > 서비스 계정에 **BigQuery 데이터 뷰어** 역할 추가
|
|
71
73
|
|
|
74
|
+
### 4. Search Console 권한 설정 (선택)
|
|
75
|
+
|
|
76
|
+
- [Search Console](https://search.google.com/search-console) > 설정 > 사용자 및 권한
|
|
77
|
+
- 서비스 계정 이메일 추가 (전체 권한 또는 제한된 권한)
|
|
78
|
+
|
|
72
79
|
---
|
|
73
80
|
|
|
74
81
|
## 제공 도구 (Tools)
|
|
@@ -90,7 +97,7 @@ npx careerly-data-mcp setup
|
|
|
90
97
|
| `bq_status` | 연결 상태 확인 | 연결 문제 진단 |
|
|
91
98
|
| `bq_ga4_events` | GA4 Export 이벤트 조회 | event_params 자동 추출 |
|
|
92
99
|
|
|
93
|
-
### BigQuery 고급 도구 (v2.1.0
|
|
100
|
+
### BigQuery 고급 도구 (v2.1.0)
|
|
94
101
|
|
|
95
102
|
| Tool | 설명 | 언제 쓰나요? |
|
|
96
103
|
|------|------|------------|
|
|
@@ -99,6 +106,14 @@ npx careerly-data-mcp setup
|
|
|
99
106
|
| `bq_user_journey` | 사용자 여정 분석 | 특정 사용자가 어떤 행동을 했는지 |
|
|
100
107
|
| `bq_funnel` | 퍼널 분석 | 단계별 전환율 자동 계산 |
|
|
101
108
|
|
|
109
|
+
### Google Search Console 도구 (v2.2.0 신규)
|
|
110
|
+
|
|
111
|
+
| Tool | 설명 | 언제 쓰나요? |
|
|
112
|
+
|------|------|------------|
|
|
113
|
+
| `gsc_query` | 검색 분석 데이터 조회 | 검색어/페이지별 클릭, 노출, CTR, 순위 |
|
|
114
|
+
| `gsc_status` | 연결 상태 확인 | 연결 문제 진단 |
|
|
115
|
+
| `gsc_sites` | 등록된 사이트 목록 | 접근 가능한 사이트 확인 |
|
|
116
|
+
|
|
102
117
|
---
|
|
103
118
|
|
|
104
119
|
## 사용 예시
|
|
@@ -123,6 +138,15 @@ Claude Code에서 자연어로 질문하세요:
|
|
|
123
138
|
"디바이스별 전환율 비교해줘"
|
|
124
139
|
```
|
|
125
140
|
|
|
141
|
+
### Search Console 질의 (v2.2.0)
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
"최근 28일 검색어별 클릭수 보여줘"
|
|
145
|
+
"클릭률이 낮은 페이지 찾아줘"
|
|
146
|
+
"모바일 vs 데스크톱 검색 성과 비교해줘"
|
|
147
|
+
"순위가 높은데 CTR이 낮은 키워드 분석해줘"
|
|
148
|
+
```
|
|
149
|
+
|
|
126
150
|
### 실제 결과 예시 (퍼널 분석)
|
|
127
151
|
|
|
128
152
|
```
|
|
@@ -142,6 +166,28 @@ page_view → post_create_start 전환율: 2.3% | 1,215 → 28명
|
|
|
142
166
|
|
|
143
167
|
---
|
|
144
168
|
|
|
169
|
+
## v2.2.0 주요 업데이트 (최신)
|
|
170
|
+
|
|
171
|
+
### Google Search Console 통합
|
|
172
|
+
|
|
173
|
+
- **3개 새 도구 추가**: `gsc_query`, `gsc_status`, `gsc_sites`
|
|
174
|
+
- 검색어/페이지별 클릭수, 노출수, CTR, 평균 순위 분석
|
|
175
|
+
- 국가별, 디바이스별 필터링 지원
|
|
176
|
+
- 16개월 이전 데이터까지 조회 가능
|
|
177
|
+
|
|
178
|
+
### 새 환경변수
|
|
179
|
+
|
|
180
|
+
| 변수 | 설명 |
|
|
181
|
+
|------|------|
|
|
182
|
+
| `GSC_SITE_URL` | 기본 사이트 URL (예: `https://example.com/` 또는 `sc-domain:example.com`) |
|
|
183
|
+
|
|
184
|
+
### GSC 권한 설정
|
|
185
|
+
|
|
186
|
+
1. Search Console > 설정 > 사용자 및 권한
|
|
187
|
+
2. Service Account 이메일 추가 (전체 권한 또는 제한된 권한)
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
145
191
|
## v2.1.0 주요 업데이트
|
|
146
192
|
|
|
147
193
|
### 버그 수정
|
|
@@ -226,6 +272,7 @@ claude mcp add careerly-ga4 \
|
|
|
226
272
|
-s user \
|
|
227
273
|
-e GA4_PROPERTY_ID=123456789 \
|
|
228
274
|
-e BQ_PROJECT_ID=your-project-id \
|
|
275
|
+
-e GSC_SITE_URL=https://example.com/ \
|
|
229
276
|
-e GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json \
|
|
230
277
|
-- npx -y careerly-data-mcp serve
|
|
231
278
|
```
|
|
@@ -243,6 +290,7 @@ claude mcp add careerly-ga4 \
|
|
|
243
290
|
"env": {
|
|
244
291
|
"GA4_PROPERTY_ID": "123456789",
|
|
245
292
|
"BQ_PROJECT_ID": "your-project-id",
|
|
293
|
+
"GSC_SITE_URL": "https://example.com/",
|
|
246
294
|
"GOOGLE_APPLICATION_CREDENTIALS": "/path/to/key.json"
|
|
247
295
|
}
|
|
248
296
|
}
|
package/dist/cli/setup.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* setup 명령어 - GUI 스타일
|
|
3
|
-
* GA4 + BigQuery 설정 + Claude Code 자동 설정
|
|
3
|
+
* GA4 + BigQuery + Search Console 설정 + Claude Code 자동 설정
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
6
|
* GA4 설정
|
|
@@ -20,9 +20,17 @@ export declare function runBigQuerySetup(): Promise<{
|
|
|
20
20
|
connected: boolean;
|
|
21
21
|
}>;
|
|
22
22
|
/**
|
|
23
|
-
* 통합 설정 (GA4 + BigQuery)
|
|
23
|
+
* 통합 설정 (GA4 + BigQuery + GSC)
|
|
24
24
|
*/
|
|
25
25
|
export declare function runSetup(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Search Console 설정
|
|
28
|
+
*/
|
|
29
|
+
export declare function runGSCSetup(existingCredentialsPath?: string): Promise<{
|
|
30
|
+
siteUrl: string;
|
|
31
|
+
credentialsPath: string;
|
|
32
|
+
connected: boolean;
|
|
33
|
+
}>;
|
|
26
34
|
/**
|
|
27
35
|
* 인터랙티브 메뉴
|
|
28
36
|
*/
|
package/dist/cli/setup.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/cli/setup.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/cli/setup.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAwTH;;GAEG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC,CAgED;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC,CA4FD;AAED;;GAEG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAgL9C;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,uBAAuB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3E,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC,CA2ID;AAED;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CA4JxD"}
|
package/dist/cli/setup.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* setup 명령어 - GUI 스타일
|
|
3
|
-
* GA4 + BigQuery 설정 + Claude Code 자동 설정
|
|
3
|
+
* GA4 + BigQuery + Search Console 설정 + Claude Code 자동 설정
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from "fs/promises";
|
|
6
6
|
import * as path from "path";
|
|
@@ -10,6 +10,7 @@ import ora from "ora";
|
|
|
10
10
|
import { input, confirm, select, checkbox } from "@inquirer/prompts";
|
|
11
11
|
import { GA4Client } from "../ga4/index.js";
|
|
12
12
|
import { BigQueryClient } from "../bigquery/index.js";
|
|
13
|
+
import { GSCClient } from "../gsc/index.js";
|
|
13
14
|
const CLAUDE_SETTINGS_PATH = path.join(process.env.HOME || "~", ".claude", "settings.json");
|
|
14
15
|
const CLAUDE_SETTINGS_LOCAL_PATH = path.join(process.env.HOME || "~", ".claude", "settings.local.json");
|
|
15
16
|
/**
|
|
@@ -82,7 +83,7 @@ async function findServiceAccountKeys() {
|
|
|
82
83
|
function printHeader() {
|
|
83
84
|
console.log("");
|
|
84
85
|
console.log(chalk.cyan.bold(" Careerly Data MCP Server"));
|
|
85
|
-
console.log(chalk.gray(" GA4 + BigQuery 통합 분석"));
|
|
86
|
+
console.log(chalk.gray(" GA4 + BigQuery + Search Console 통합 분석"));
|
|
86
87
|
console.log(chalk.gray(" ─────────────────────────────────────"));
|
|
87
88
|
console.log("");
|
|
88
89
|
}
|
|
@@ -102,6 +103,12 @@ function printStatus(config) {
|
|
|
102
103
|
: config.bqProjectId
|
|
103
104
|
? chalk.yellow("○ configured")
|
|
104
105
|
: chalk.gray("- not set");
|
|
106
|
+
// GSC 상태
|
|
107
|
+
const gscStatus = config.gscConnected
|
|
108
|
+
? chalk.green("✓ connected")
|
|
109
|
+
: config.gscSiteUrl
|
|
110
|
+
? chalk.yellow("○ configured")
|
|
111
|
+
: chalk.gray("- not set");
|
|
105
112
|
console.log(chalk.white.bold("GA4: ") + ga4Status);
|
|
106
113
|
if (config.ga4PropertyId) {
|
|
107
114
|
console.log(chalk.gray(" Property: " + config.ga4PropertyId));
|
|
@@ -110,6 +117,10 @@ function printStatus(config) {
|
|
|
110
117
|
if (config.bqProjectId) {
|
|
111
118
|
console.log(chalk.gray(" Project: " + config.bqProjectId));
|
|
112
119
|
}
|
|
120
|
+
console.log(chalk.white.bold("GSC: ") + gscStatus);
|
|
121
|
+
if (config.gscSiteUrl) {
|
|
122
|
+
console.log(chalk.gray(" Site: " + config.gscSiteUrl));
|
|
123
|
+
}
|
|
113
124
|
if (config.credentialsPath) {
|
|
114
125
|
const shortPath = config.credentialsPath.replace(process.env.HOME || "", "~");
|
|
115
126
|
console.log(chalk.white.bold("Credentials: ") + chalk.gray(shortPath));
|
|
@@ -144,6 +155,9 @@ async function runClaudeMcpAdd(config) {
|
|
|
144
155
|
if (config.bqLocation) {
|
|
145
156
|
envParts.push(`-e BQ_LOCATION=${config.bqLocation}`);
|
|
146
157
|
}
|
|
158
|
+
if (config.gscSiteUrl) {
|
|
159
|
+
envParts.push(`-e GSC_SITE_URL=${config.gscSiteUrl}`);
|
|
160
|
+
}
|
|
147
161
|
envParts.push(`-e GOOGLE_APPLICATION_CREDENTIALS=${config.credentialsPath}`);
|
|
148
162
|
// claude mcp add 실행
|
|
149
163
|
const cmd = [
|
|
@@ -364,7 +378,7 @@ export async function runBigQuerySetup() {
|
|
|
364
378
|
};
|
|
365
379
|
}
|
|
366
380
|
/**
|
|
367
|
-
* 통합 설정 (GA4 + BigQuery)
|
|
381
|
+
* 통합 설정 (GA4 + BigQuery + GSC)
|
|
368
382
|
*/
|
|
369
383
|
export async function runSetup() {
|
|
370
384
|
printHeader();
|
|
@@ -374,6 +388,7 @@ export async function runSetup() {
|
|
|
374
388
|
choices: [
|
|
375
389
|
{ name: "GA4 Data API", value: "ga4", checked: true },
|
|
376
390
|
{ name: "BigQuery", value: "bigquery", checked: true },
|
|
391
|
+
{ name: "Search Console", value: "gsc", checked: true },
|
|
377
392
|
],
|
|
378
393
|
});
|
|
379
394
|
if (services.length === 0) {
|
|
@@ -382,6 +397,7 @@ export async function runSetup() {
|
|
|
382
397
|
}
|
|
383
398
|
let ga4Config = {};
|
|
384
399
|
let bqConfig = {};
|
|
400
|
+
let gscConfig = {};
|
|
385
401
|
let credentialsPath = "";
|
|
386
402
|
// GA4 설정
|
|
387
403
|
if (services.includes("ga4")) {
|
|
@@ -416,8 +432,24 @@ export async function runSetup() {
|
|
|
416
432
|
}
|
|
417
433
|
}
|
|
418
434
|
}
|
|
435
|
+
// Search Console 설정
|
|
436
|
+
if (services.includes("gsc")) {
|
|
437
|
+
try {
|
|
438
|
+
const result = await runGSCSetup(credentialsPath);
|
|
439
|
+
gscConfig = { siteUrl: result.siteUrl, connected: result.connected };
|
|
440
|
+
// credentials가 아직 없으면 사용
|
|
441
|
+
if (!credentialsPath) {
|
|
442
|
+
credentialsPath = result.credentialsPath;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
if (error.message === "설정 취소됨") {
|
|
447
|
+
console.log(chalk.yellow("\nSearch Console 설정이 취소되었습니다."));
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
419
451
|
// 설정할 내용이 있는지 확인
|
|
420
|
-
if (!ga4Config.propertyId && !bqConfig.projectId) {
|
|
452
|
+
if (!ga4Config.propertyId && !bqConfig.projectId && !gscConfig.siteUrl) {
|
|
421
453
|
console.log(chalk.yellow("\n설정된 서비스가 없습니다."));
|
|
422
454
|
return;
|
|
423
455
|
}
|
|
@@ -428,6 +460,7 @@ export async function runSetup() {
|
|
|
428
460
|
ga4PropertyId: ga4Config.propertyId,
|
|
429
461
|
bqProjectId: bqConfig.projectId,
|
|
430
462
|
bqLocation: bqConfig.location,
|
|
463
|
+
gscSiteUrl: gscConfig.siteUrl,
|
|
431
464
|
credentialsPath,
|
|
432
465
|
});
|
|
433
466
|
if (mcpResult.success) {
|
|
@@ -449,6 +482,9 @@ export async function runSetup() {
|
|
|
449
482
|
if (bqConfig.location) {
|
|
450
483
|
env.BQ_LOCATION = bqConfig.location;
|
|
451
484
|
}
|
|
485
|
+
if (gscConfig.siteUrl) {
|
|
486
|
+
env.GSC_SITE_URL = gscConfig.siteUrl;
|
|
487
|
+
}
|
|
452
488
|
const mcpConfig = {
|
|
453
489
|
command: "npx",
|
|
454
490
|
args: ["-y", "careerly-data-mcp", "serve"],
|
|
@@ -480,10 +516,12 @@ export async function runSetup() {
|
|
|
480
516
|
printStatus({
|
|
481
517
|
ga4PropertyId: ga4Config.propertyId,
|
|
482
518
|
bqProjectId: bqConfig.projectId,
|
|
519
|
+
gscSiteUrl: gscConfig.siteUrl,
|
|
483
520
|
credentialsPath,
|
|
484
521
|
ga4Connected: ga4Config.connected,
|
|
485
522
|
bqConnected: bqConfig.connected,
|
|
486
|
-
|
|
523
|
+
gscConnected: gscConfig.connected,
|
|
524
|
+
tools: 14,
|
|
487
525
|
});
|
|
488
526
|
console.log(chalk.cyan(" Claude Code를 재시작하면 사용할 수 있습니다."));
|
|
489
527
|
console.log("");
|
|
@@ -495,6 +533,144 @@ export async function runSetup() {
|
|
|
495
533
|
console.log(chalk.white(' "BigQuery 데이터셋 목록 보여줘"'));
|
|
496
534
|
console.log(chalk.white(' "GA4 이벤트 데이터에서 page_view 조회해줘"'));
|
|
497
535
|
console.log("");
|
|
536
|
+
console.log(chalk.gray(" Search Console 사용 예시:"));
|
|
537
|
+
console.log(chalk.white(' "검색어별 클릭수 보여줘"'));
|
|
538
|
+
console.log(chalk.white(' "페이지별 순위 분석해줘"'));
|
|
539
|
+
console.log("");
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Search Console 설정
|
|
543
|
+
*/
|
|
544
|
+
export async function runGSCSetup(existingCredentialsPath) {
|
|
545
|
+
printHeader();
|
|
546
|
+
console.log(chalk.cyan.bold(" Search Console 설정"));
|
|
547
|
+
console.log("");
|
|
548
|
+
// 1. Service Account 키 파일 선택 (이미 있으면 재사용 제안)
|
|
549
|
+
let absolutePath;
|
|
550
|
+
if (existingCredentialsPath) {
|
|
551
|
+
const reuseCredentials = await confirm({
|
|
552
|
+
message: `기존 키 파일 사용? (${path.basename(existingCredentialsPath)})`,
|
|
553
|
+
default: true,
|
|
554
|
+
});
|
|
555
|
+
if (reuseCredentials) {
|
|
556
|
+
absolutePath = existingCredentialsPath;
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
console.log("");
|
|
560
|
+
absolutePath = await selectServiceAccountKey();
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
absolutePath = await selectServiceAccountKey();
|
|
565
|
+
}
|
|
566
|
+
// 2. 연결 테스트 및 사이트 목록 조회
|
|
567
|
+
console.log("");
|
|
568
|
+
const spinner = ora("Search Console 연결 테스트 중...").start();
|
|
569
|
+
process.env.GOOGLE_APPLICATION_CREDENTIALS = absolutePath;
|
|
570
|
+
let siteUrl = "";
|
|
571
|
+
let connected = false;
|
|
572
|
+
try {
|
|
573
|
+
const client = new GSCClient();
|
|
574
|
+
const result = await client.testConnection();
|
|
575
|
+
if (result.success && result.sites && result.sites.length > 0) {
|
|
576
|
+
spinner.succeed(chalk.green("Search Console 연결 성공!"));
|
|
577
|
+
connected = true;
|
|
578
|
+
// 사이트 선택
|
|
579
|
+
console.log("");
|
|
580
|
+
const choices = [
|
|
581
|
+
...result.sites.map((site) => ({
|
|
582
|
+
name: site,
|
|
583
|
+
value: site,
|
|
584
|
+
})),
|
|
585
|
+
{
|
|
586
|
+
name: chalk.gray("직접 입력..."),
|
|
587
|
+
value: "__manual__",
|
|
588
|
+
},
|
|
589
|
+
];
|
|
590
|
+
const selected = await select({
|
|
591
|
+
message: "분석할 사이트 선택",
|
|
592
|
+
choices,
|
|
593
|
+
});
|
|
594
|
+
if (selected === "__manual__") {
|
|
595
|
+
siteUrl = await input({
|
|
596
|
+
message: "Site URL (예: https://example.com/ 또는 sc-domain:example.com)",
|
|
597
|
+
validate: (value) => {
|
|
598
|
+
if (!value)
|
|
599
|
+
return "Site URL을 입력해주세요";
|
|
600
|
+
if (!value.startsWith("http") && !value.startsWith("sc-domain:")) {
|
|
601
|
+
return "https:// 또는 sc-domain: 형식이어야 합니다";
|
|
602
|
+
}
|
|
603
|
+
return true;
|
|
604
|
+
},
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
siteUrl = selected;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
else if (result.success) {
|
|
612
|
+
spinner.warn(chalk.yellow("연결됐지만 접근 가능한 사이트가 없습니다."));
|
|
613
|
+
// 수동 입력
|
|
614
|
+
siteUrl = await input({
|
|
615
|
+
message: "Site URL (예: https://example.com/ 또는 sc-domain:example.com)",
|
|
616
|
+
validate: (value) => {
|
|
617
|
+
if (!value)
|
|
618
|
+
return "Site URL을 입력해주세요";
|
|
619
|
+
if (!value.startsWith("http") && !value.startsWith("sc-domain:")) {
|
|
620
|
+
return "https:// 또는 sc-domain: 형식이어야 합니다";
|
|
621
|
+
}
|
|
622
|
+
return true;
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
spinner.fail(chalk.red(`Search Console 연결 실패: ${result.message}`));
|
|
628
|
+
const continueSetup = await confirm({
|
|
629
|
+
message: "연결에 실패했습니다. 계속 진행하시겠습니까?",
|
|
630
|
+
default: false,
|
|
631
|
+
});
|
|
632
|
+
if (!continueSetup) {
|
|
633
|
+
throw new Error("설정 취소됨");
|
|
634
|
+
}
|
|
635
|
+
// 수동 입력
|
|
636
|
+
siteUrl = await input({
|
|
637
|
+
message: "Site URL (예: https://example.com/ 또는 sc-domain:example.com)",
|
|
638
|
+
validate: (value) => {
|
|
639
|
+
if (!value)
|
|
640
|
+
return "Site URL을 입력해주세요";
|
|
641
|
+
if (!value.startsWith("http") && !value.startsWith("sc-domain:")) {
|
|
642
|
+
return "https:// 또는 sc-domain: 형식이어야 합니다";
|
|
643
|
+
}
|
|
644
|
+
return true;
|
|
645
|
+
},
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
catch (error) {
|
|
650
|
+
if (error.message === "설정 취소됨")
|
|
651
|
+
throw error;
|
|
652
|
+
spinner.fail(chalk.red(`Search Console 연결 실패: ${error instanceof Error ? error.message : error}`));
|
|
653
|
+
const continueSetup = await confirm({
|
|
654
|
+
message: "계속 진행하시겠습니까?",
|
|
655
|
+
default: false,
|
|
656
|
+
});
|
|
657
|
+
if (!continueSetup) {
|
|
658
|
+
throw new Error("설정 취소됨");
|
|
659
|
+
}
|
|
660
|
+
// 수동 입력
|
|
661
|
+
siteUrl = await input({
|
|
662
|
+
message: "Site URL (예: https://example.com/ 또는 sc-domain:example.com)",
|
|
663
|
+
validate: (value) => {
|
|
664
|
+
if (!value)
|
|
665
|
+
return "Site URL을 입력해주세요";
|
|
666
|
+
if (!value.startsWith("http") && !value.startsWith("sc-domain:")) {
|
|
667
|
+
return "https:// 또는 sc-domain: 형식이어야 합니다";
|
|
668
|
+
}
|
|
669
|
+
return true;
|
|
670
|
+
},
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
return { siteUrl, credentialsPath: absolutePath, connected };
|
|
498
674
|
}
|
|
499
675
|
/**
|
|
500
676
|
* 인터랙티브 메뉴
|
|
@@ -502,7 +678,7 @@ export async function runSetup() {
|
|
|
502
678
|
export async function runInteractiveMenu() {
|
|
503
679
|
printHeader();
|
|
504
680
|
// 현재 설정 상태 확인
|
|
505
|
-
let currentConfig = { ga4Connected: false, bqConnected: false };
|
|
681
|
+
let currentConfig = { ga4Connected: false, bqConnected: false, gscConnected: false };
|
|
506
682
|
// settings 파일에서 현재 설정 읽기
|
|
507
683
|
const settingsPath = (await fileExists(CLAUDE_SETTINGS_LOCAL_PATH))
|
|
508
684
|
? CLAUDE_SETTINGS_LOCAL_PATH
|
|
@@ -515,6 +691,7 @@ export async function runInteractiveMenu() {
|
|
|
515
691
|
if (mcpConfig?.env) {
|
|
516
692
|
currentConfig.ga4PropertyId = mcpConfig.env.GA4_PROPERTY_ID;
|
|
517
693
|
currentConfig.bqProjectId = mcpConfig.env.BQ_PROJECT_ID;
|
|
694
|
+
currentConfig.gscSiteUrl = mcpConfig.env.GSC_SITE_URL;
|
|
518
695
|
currentConfig.credentialsPath =
|
|
519
696
|
mcpConfig.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
520
697
|
// GA4 연결 테스트
|
|
@@ -547,13 +724,26 @@ export async function runInteractiveMenu() {
|
|
|
547
724
|
currentConfig.bqConnected = false;
|
|
548
725
|
}
|
|
549
726
|
}
|
|
727
|
+
// GSC 연결 테스트
|
|
728
|
+
if (currentConfig.gscSiteUrl && currentConfig.credentialsPath) {
|
|
729
|
+
process.env.GSC_SITE_URL = currentConfig.gscSiteUrl;
|
|
730
|
+
process.env.GOOGLE_APPLICATION_CREDENTIALS = currentConfig.credentialsPath;
|
|
731
|
+
try {
|
|
732
|
+
const client = new GSCClient();
|
|
733
|
+
const result = await client.testConnection();
|
|
734
|
+
currentConfig.gscConnected = result.success;
|
|
735
|
+
}
|
|
736
|
+
catch {
|
|
737
|
+
currentConfig.gscConnected = false;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
550
740
|
}
|
|
551
741
|
}
|
|
552
742
|
printStatus({
|
|
553
743
|
...currentConfig,
|
|
554
|
-
tools:
|
|
744
|
+
tools: 14,
|
|
555
745
|
});
|
|
556
|
-
const isConfigured = currentConfig.ga4Connected || currentConfig.bqConnected;
|
|
746
|
+
const isConfigured = currentConfig.ga4Connected || currentConfig.bqConnected || currentConfig.gscConnected;
|
|
557
747
|
// 메뉴 선택
|
|
558
748
|
const action = await select({
|
|
559
749
|
message: "선택하세요",
|
|
@@ -566,7 +756,7 @@ export async function runInteractiveMenu() {
|
|
|
566
756
|
{
|
|
567
757
|
name: isConfigured ? "Reconfigure" : "Setup",
|
|
568
758
|
value: "setup",
|
|
569
|
-
description: "GA4 + BigQuery 설정",
|
|
759
|
+
description: "GA4 + BigQuery + Search Console 설정",
|
|
570
760
|
},
|
|
571
761
|
{
|
|
572
762
|
name: "Setup GA4 only",
|
|
@@ -578,6 +768,11 @@ export async function runInteractiveMenu() {
|
|
|
578
768
|
value: "setup-bq",
|
|
579
769
|
description: "BigQuery만 설정",
|
|
580
770
|
},
|
|
771
|
+
{
|
|
772
|
+
name: "Setup Search Console only",
|
|
773
|
+
value: "setup-gsc",
|
|
774
|
+
description: "Search Console만 설정",
|
|
775
|
+
},
|
|
581
776
|
{
|
|
582
777
|
name: "Test connections",
|
|
583
778
|
value: "test",
|
|
@@ -603,6 +798,9 @@ export async function runInteractiveMenu() {
|
|
|
603
798
|
case "setup-bq":
|
|
604
799
|
await runBigQuerySetupOnly();
|
|
605
800
|
break;
|
|
801
|
+
case "setup-gsc":
|
|
802
|
+
await runGSCSetupOnly();
|
|
803
|
+
break;
|
|
606
804
|
case "test":
|
|
607
805
|
await testConnections();
|
|
608
806
|
break;
|
|
@@ -614,6 +812,39 @@ export async function runInteractiveMenu() {
|
|
|
614
812
|
break;
|
|
615
813
|
}
|
|
616
814
|
}
|
|
815
|
+
/**
|
|
816
|
+
* Search Console만 설정
|
|
817
|
+
*/
|
|
818
|
+
async function runGSCSetupOnly() {
|
|
819
|
+
try {
|
|
820
|
+
const result = await runGSCSetup();
|
|
821
|
+
// Claude Code MCP 설정
|
|
822
|
+
console.log("");
|
|
823
|
+
const settingsSpinner = ora("Claude Code MCP 설정 중...").start();
|
|
824
|
+
const mcpResult = await runClaudeMcpAdd({
|
|
825
|
+
gscSiteUrl: result.siteUrl,
|
|
826
|
+
credentialsPath: result.credentialsPath,
|
|
827
|
+
});
|
|
828
|
+
if (mcpResult.success) {
|
|
829
|
+
settingsSpinner.succeed(chalk.green("Claude Code MCP 서버 등록 완료"));
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
await saveSettingsManually({
|
|
833
|
+
gscSiteUrl: result.siteUrl,
|
|
834
|
+
credentialsPath: result.credentialsPath,
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
console.log("");
|
|
838
|
+
console.log(chalk.green.bold(" ✓ Search Console 설정 완료!"));
|
|
839
|
+
console.log(chalk.cyan(" Claude Code를 재시작하면 사용할 수 있습니다."));
|
|
840
|
+
console.log("");
|
|
841
|
+
}
|
|
842
|
+
catch (error) {
|
|
843
|
+
if (error.message === "설정 취소됨") {
|
|
844
|
+
console.log(chalk.yellow("\n설정이 취소되었습니다."));
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
617
848
|
/**
|
|
618
849
|
* GA4만 설정
|
|
619
850
|
*/
|
|
@@ -699,6 +930,9 @@ async function saveSettingsManually(config) {
|
|
|
699
930
|
if (config.bqLocation) {
|
|
700
931
|
env.BQ_LOCATION = config.bqLocation;
|
|
701
932
|
}
|
|
933
|
+
if (config.gscSiteUrl) {
|
|
934
|
+
env.GSC_SITE_URL = config.gscSiteUrl;
|
|
935
|
+
}
|
|
702
936
|
const mcpConfig = {
|
|
703
937
|
command: "npx",
|
|
704
938
|
args: ["-y", "careerly-data-mcp", "serve"],
|
|
@@ -727,11 +961,10 @@ async function saveSettingsManually(config) {
|
|
|
727
961
|
*/
|
|
728
962
|
async function showTools() {
|
|
729
963
|
console.log("");
|
|
730
|
-
console.log(chalk.white.bold(" GA4 Data API Tools:"));
|
|
964
|
+
console.log(chalk.white.bold(" GA4 Data API Tools (3):"));
|
|
731
965
|
console.log("");
|
|
732
966
|
console.log(chalk.cyan(" ga4_query"));
|
|
733
967
|
console.log(chalk.gray(" GA4 데이터 조회 및 인사이트 생성"));
|
|
734
|
-
console.log(chalk.gray(" 예: 지난 7일 세션수, 채널별 전환수"));
|
|
735
968
|
console.log("");
|
|
736
969
|
console.log(chalk.cyan(" ga4_metadata"));
|
|
737
970
|
console.log(chalk.gray(" 사용 가능한 지표/차원 목록 확인"));
|
|
@@ -739,15 +972,13 @@ async function showTools() {
|
|
|
739
972
|
console.log(chalk.cyan(" ga4_status"));
|
|
740
973
|
console.log(chalk.gray(" GA4 연결 상태 확인"));
|
|
741
974
|
console.log("");
|
|
742
|
-
console.log(chalk.white.bold(" BigQuery Tools:"));
|
|
975
|
+
console.log(chalk.white.bold(" BigQuery Tools (8):"));
|
|
743
976
|
console.log("");
|
|
744
977
|
console.log(chalk.cyan(" bq_query"));
|
|
745
978
|
console.log(chalk.gray(" BigQuery 테이블 파라미터 기반 조회"));
|
|
746
|
-
console.log(chalk.gray(" 예: 특정 테이블에서 조건별 데이터 조회"));
|
|
747
979
|
console.log("");
|
|
748
980
|
console.log(chalk.cyan(" bq_ga4_events"));
|
|
749
981
|
console.log(chalk.gray(" GA4 Export 이벤트 데이터 조회"));
|
|
750
|
-
console.log(chalk.gray(" 예: page_view, purchase 이벤트 분석"));
|
|
751
982
|
console.log("");
|
|
752
983
|
console.log(chalk.cyan(" bq_metadata"));
|
|
753
984
|
console.log(chalk.gray(" 데이터셋/테이블 목록 및 스키마 조회"));
|
|
@@ -755,6 +986,29 @@ async function showTools() {
|
|
|
755
986
|
console.log(chalk.cyan(" bq_status"));
|
|
756
987
|
console.log(chalk.gray(" BigQuery 연결 상태 확인"));
|
|
757
988
|
console.log("");
|
|
989
|
+
console.log(chalk.cyan(" bq_raw_sql"));
|
|
990
|
+
console.log(chalk.gray(" SQL 직접 실행 (SELECT/WITH만 허용)"));
|
|
991
|
+
console.log("");
|
|
992
|
+
console.log(chalk.cyan(" bq_event_params"));
|
|
993
|
+
console.log(chalk.gray(" event_params 쉽게 추출"));
|
|
994
|
+
console.log("");
|
|
995
|
+
console.log(chalk.cyan(" bq_user_journey"));
|
|
996
|
+
console.log(chalk.gray(" 사용자별 이벤트 시퀀스 분석"));
|
|
997
|
+
console.log("");
|
|
998
|
+
console.log(chalk.cyan(" bq_funnel"));
|
|
999
|
+
console.log(chalk.gray(" 전환 퍼널 자동 계산"));
|
|
1000
|
+
console.log("");
|
|
1001
|
+
console.log(chalk.white.bold(" Search Console Tools (3):"));
|
|
1002
|
+
console.log("");
|
|
1003
|
+
console.log(chalk.cyan(" gsc_query"));
|
|
1004
|
+
console.log(chalk.gray(" 검색어/페이지별 클릭, 노출, CTR, 순위 분석"));
|
|
1005
|
+
console.log("");
|
|
1006
|
+
console.log(chalk.cyan(" gsc_sites"));
|
|
1007
|
+
console.log(chalk.gray(" 접근 가능한 사이트 목록"));
|
|
1008
|
+
console.log("");
|
|
1009
|
+
console.log(chalk.cyan(" gsc_status"));
|
|
1010
|
+
console.log(chalk.gray(" Search Console 연결 상태 확인"));
|
|
1011
|
+
console.log("");
|
|
758
1012
|
await select({
|
|
759
1013
|
message: "",
|
|
760
1014
|
choices: [{ name: "← Back", value: "back" }],
|
|
@@ -796,6 +1050,21 @@ async function testConnections() {
|
|
|
796
1050
|
catch (error) {
|
|
797
1051
|
bqSpinner.fail(chalk.red(`BigQuery: ${error instanceof Error ? error.message : error}`));
|
|
798
1052
|
}
|
|
1053
|
+
// Search Console 테스트
|
|
1054
|
+
const gscSpinner = ora("Search Console 연결 테스트 중...").start();
|
|
1055
|
+
try {
|
|
1056
|
+
const client = new GSCClient();
|
|
1057
|
+
const result = await client.testConnection();
|
|
1058
|
+
if (result.success) {
|
|
1059
|
+
gscSpinner.succeed(chalk.green(`Search Console: ${result.message}`));
|
|
1060
|
+
}
|
|
1061
|
+
else {
|
|
1062
|
+
gscSpinner.fail(chalk.red(`Search Console: ${result.message}`));
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
catch (error) {
|
|
1066
|
+
gscSpinner.fail(chalk.red(`Search Console: ${error instanceof Error ? error.message : error}`));
|
|
1067
|
+
}
|
|
799
1068
|
console.log("");
|
|
800
1069
|
await select({
|
|
801
1070
|
message: "",
|