@yoonion/mimi-seed-mcp 0.3.40 → 0.5.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.
@@ -144,4 +144,18 @@ export function registerFirebaseTools(server) {
144
144
  const services = await firebase.listEnabledServices(auth, projectId);
145
145
  return { content: [{ type: 'text', text: JSON.stringify(services, null, 2) }] };
146
146
  });
147
+ server.tool('firebase_link_analytics', 'Firebase 프로젝트에 Google Analytics(GA4) 링크 → 앱별 measurement 자동 활성화. analyticsAccountId(그 계정에 GA4 property 신규 생성) 또는 analyticsPropertyId(기존 property 링크) 중 하나 필수. 먼저 firebase_enable_common_services 로 firebaseanalytics 활성화 권장. property 이름/web stream 까지 직접 제어하려면 ga4_create_property/ga4_create_data_stream 사용.', {
148
+ projectId: z.string().describe('Firebase 프로젝트 ID'),
149
+ analyticsAccountId: z.string().optional().describe('GA 계정 ID (신규 property 생성 위치) — 예: 123456'),
150
+ analyticsPropertyId: z.string().optional().describe('기존 GA4 property ID 에 링크할 경우'),
151
+ }, async ({ projectId, analyticsAccountId, analyticsPropertyId }) => {
152
+ const auth = await requireAuth();
153
+ const result = await firebase.linkAnalytics(auth, projectId, { analyticsAccountId, analyticsPropertyId });
154
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
155
+ });
156
+ server.tool('firebase_get_analytics_details', '프로젝트의 GA4 링크 상세 — 연결된 analyticsProperty + 앱↔data stream 매핑 조회.', { projectId: z.string().describe('Firebase 프로젝트 ID') }, async ({ projectId }) => {
157
+ const auth = await requireAuth();
158
+ const details = await firebase.getAnalyticsDetails(auth, projectId);
159
+ return { content: [{ type: 'text', text: JSON.stringify(details, null, 2) }] };
160
+ });
147
161
  }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerGa4Tools(server: McpServer): void;
@@ -0,0 +1,90 @@
1
+ import { z } from 'zod';
2
+ import * as ga4Raw from '../ga4/tools.js';
3
+ import { requireAuth } from '../helpers.js';
4
+ import { friendlyGoogleError } from '../lib/google-errors.js';
5
+ // firebase register 와 동일한 프록시 — raw GaxiosError(SERVICE_DISABLED / 403 /
6
+ // ACCESS_TOKEN_SCOPE_INSUFFICIENT)를 "다음에 뭘 할지" 메시지로 변환. 특히 GA4 는
7
+ // analytics.edit 스코프를 새로 추가했으므로 기존 사용자는 재로그인 안내가 필수.
8
+ const ga4 = new Proxy(ga4Raw, {
9
+ get(target, prop, receiver) {
10
+ const orig = Reflect.get(target, prop, receiver);
11
+ if (typeof orig !== 'function')
12
+ return orig;
13
+ return (...args) => {
14
+ try {
15
+ const out = orig(...args);
16
+ if (out && typeof out.then === 'function') {
17
+ return out.catch((err) => {
18
+ throw friendlyGoogleError(err);
19
+ });
20
+ }
21
+ return out;
22
+ }
23
+ catch (err) {
24
+ throw friendlyGoogleError(err);
25
+ }
26
+ };
27
+ },
28
+ });
29
+ const PROPERTY_DESC = "GA4 property ID — '123456789' 또는 'properties/123456789'";
30
+ export function registerGa4Tools(server) {
31
+ server.tool('ga4_list_account_summaries', '접근 가능한 Google Analytics 계정 + 각 계정의 GA4 property 요약. accountId / propertyId 를 확인할 때 먼저 호출. analytics.edit 스코프 필요(없으면 재로그인 안내).', {}, async () => {
32
+ const auth = await requireAuth(ga4Raw.GA4_SCOPE);
33
+ const summaries = await ga4.listAccountSummaries(auth);
34
+ return { content: [{ type: 'text', text: JSON.stringify(summaries, null, 2) }] };
35
+ });
36
+ server.tool('ga4_list_properties', '특정 GA 계정 하위 GA4 property 목록.', { accountId: z.string().describe("GA 계정 ID — '123' 또는 'accounts/123'") }, async ({ accountId }) => {
37
+ const auth = await requireAuth(ga4Raw.GA4_SCOPE);
38
+ const properties = await ga4.listProperties(auth, accountId);
39
+ return { content: [{ type: 'text', text: JSON.stringify(properties, null, 2) }] };
40
+ });
41
+ server.tool('ga4_create_property', '새 GA4 property 생성. 생성 후 ga4_create_data_stream 으로 플랫폼별 stream(웹=measurement ID, 앱=firebaseAppId)을 붙인다.', {
42
+ accountId: z.string().describe("GA 계정 ID — '123' 또는 'accounts/123'"),
43
+ displayName: z.string().describe('property 표시 이름 (예: SpeakReward)'),
44
+ timeZone: z.string().optional().describe("IANA 타임존 (기본 'America/Los_Angeles', 예: 'Asia/Seoul')"),
45
+ currencyCode: z.string().optional().describe("ISO 4217 통화코드 (기본 'USD', 예: 'KRW')"),
46
+ }, async ({ accountId, displayName, timeZone, currencyCode }) => {
47
+ const auth = await requireAuth(ga4Raw.GA4_SCOPE);
48
+ const property = await ga4.createProperty(auth, { accountId, displayName, timeZone, currencyCode });
49
+ return { content: [{ type: 'text', text: JSON.stringify(property, null, 2) }] };
50
+ });
51
+ server.tool('ga4_create_data_stream', 'GA4 property 에 data stream 생성. web → measurementId(G-XXXX) 반환, android/ios → firebaseAppId 반환(Firebase 링크 시). RN 앱은 android/ios 스트림 사용.', {
52
+ propertyId: z.string().describe(PROPERTY_DESC),
53
+ platform: z.enum(['web', 'android', 'ios']).describe('스트림 플랫폼'),
54
+ displayName: z.string().describe('스트림 표시 이름'),
55
+ defaultUri: z.string().optional().describe('web 전용 — 사이트 URL (예: https://example.com)'),
56
+ packageName: z.string().optional().describe('android 전용 — 패키지명 (예: com.example.app)'),
57
+ bundleId: z.string().optional().describe('ios 전용 — Bundle ID (예: com.example.app)'),
58
+ }, async ({ propertyId, platform, displayName, defaultUri, packageName, bundleId }) => {
59
+ const auth = await requireAuth(ga4Raw.GA4_SCOPE);
60
+ const stream = await ga4.createDataStream(auth, propertyId, {
61
+ platform,
62
+ displayName,
63
+ defaultUri,
64
+ packageName,
65
+ bundleId,
66
+ });
67
+ return { content: [{ type: 'text', text: JSON.stringify(stream, null, 2) }] };
68
+ });
69
+ server.tool('ga4_list_data_streams', 'GA4 property 의 data stream 목록 (measurementId / firebaseAppId 평탄화).', { propertyId: z.string().describe(PROPERTY_DESC) }, async ({ propertyId }) => {
70
+ const auth = await requireAuth(ga4Raw.GA4_SCOPE);
71
+ const streams = await ga4.listDataStreams(auth, propertyId);
72
+ return { content: [{ type: 'text', text: JSON.stringify(streams, null, 2) }] };
73
+ });
74
+ server.tool('ga4_run_report', 'GA4 Data API 리포트 (활성 사용자·이벤트 등). dimensions/metrics 는 쉼표 구분 GA4 API 이름.', {
75
+ propertyId: z.string().describe(PROPERTY_DESC),
76
+ startDate: z.string().describe("시작일 (YYYY-MM-DD 또는 'NdaysAgo', 'today')"),
77
+ endDate: z.string().describe("종료일 (YYYY-MM-DD 또는 'today')"),
78
+ dimensions: z.string().optional().describe('쉼표 구분 dimension (예: date,country). 생략 시 전체 합계'),
79
+ metrics: z.string().optional().describe('쉼표 구분 metric (기본 activeUsers,eventCount)'),
80
+ }, async ({ propertyId, startDate, endDate, dimensions, metrics }) => {
81
+ const auth = await requireAuth(ga4Raw.GA4_SCOPE);
82
+ const report = await ga4.runReport(auth, propertyId, {
83
+ startDate,
84
+ endDate,
85
+ dimensions: dimensions ? dimensions.split(',').map((d) => d.trim()).filter(Boolean) : undefined,
86
+ metrics: metrics ? metrics.split(',').map((m) => m.trim()).filter(Boolean) : undefined,
87
+ });
88
+ return { content: [{ type: 'text', text: JSON.stringify(report, null, 2) }] };
89
+ });
90
+ }
@@ -46,7 +46,7 @@ export function registerPlaystoreTools(server) {
46
46
  const listing = await playstore.getListing(auth, packageName, language);
47
47
  return { content: [{ type: 'text', text: JSON.stringify(listing, null, 2) }] };
48
48
  });
49
- server.tool('playstore_update_listing', 'Google Play 스토어 리스팅 수정 (제목, 설명문 변경)', {
49
+ server.tool('playstore_update_listing', 'Google Play 스토어 리스팅 수정 (제목, 설명문 변경). ⚠️ Play API 편집과 Console 동시 편집은 충돌 — Console에서 같은 리스팅을 편집·미게시 중이면 그 변경이 덮어써질 수 있음.', {
50
50
  packageName: z.string().describe('패키지명'),
51
51
  language: z.string().describe('언어 코드 (예: ko-KR, en-US)'),
52
52
  title: z.string().optional().describe('앱 제목 (30자 이내)'),
@@ -115,7 +115,7 @@ export function registerPlaystoreTools(server) {
115
115
  const images = await playstore.listImages(auth, packageName, language, imageType);
116
116
  return { content: [{ type: 'text', text: JSON.stringify(images, null, 2) }] };
117
117
  });
118
- server.tool('playstore_upload_image', 'Google Play 리스팅 이미지 단일 업로드 (기존 이미지 유지). featureGraphic 1024x500 / icon 512x512 / phoneScreenshots 320~3840px', {
118
+ server.tool('playstore_upload_image', 'Google Play 리스팅 이미지 단일 업로드 (기존 이미지 유지). featureGraphic 1024x500 / icon 512x512 / phoneScreenshots 320~3840px. ⚠️ 이 작업은 edit을 commit하므로 Play Console의 미게시(저장 전) 리스팅 변경을 덮어쓸 수 있음 — Console에서 같은 앱을 편집 중이면 먼저 저장·게시하거나 텍스트도 update_listing으로 처리하세요.', {
119
119
  packageName: z.string().describe('패키지명'),
120
120
  language: z.string().describe('언어 코드'),
121
121
  imageType: z.enum(['featureGraphic', 'icon', 'phoneScreenshots', 'promoGraphic', 'sevenInchScreenshots', 'tenInchScreenshots', 'tvBanner', 'tvScreenshots', 'wearScreenshots']),
@@ -125,7 +125,7 @@ export function registerPlaystoreTools(server) {
125
125
  const result = await playstore.uploadImage(auth, packageName, language, imageType, filePath);
126
126
  return { content: [{ type: 'text', text: `✅ 업로드 완료\n${JSON.stringify(result, null, 2)}` }] };
127
127
  });
128
- server.tool('playstore_delete_all_images', 'Google Play 리스팅 특정 imageType의 이미지 전체 삭제 (교체 전 정리)', {
128
+ server.tool('playstore_delete_all_images', 'Google Play 리스팅 특정 imageType의 이미지 전체 삭제 (교체 전 정리). ⚠️ commit이 Play Console의 미게시 리스팅 변경을 덮어쓸 수 있음.', {
129
129
  packageName: z.string().describe('패키지명'),
130
130
  language: z.string().describe('언어 코드'),
131
131
  imageType: z.enum(['featureGraphic', 'icon', 'phoneScreenshots', 'promoGraphic', 'sevenInchScreenshots', 'tenInchScreenshots', 'tvBanner', 'tvScreenshots', 'wearScreenshots']),
@@ -134,7 +134,7 @@ export function registerPlaystoreTools(server) {
134
134
  const result = await playstore.deleteAllImages(auth, packageName, language, imageType);
135
135
  return { content: [{ type: 'text', text: `✅ 전체 삭제\n${JSON.stringify(result, null, 2)}` }] };
136
136
  });
137
- server.tool('playstore_replace_images', 'Google Play 리스팅 이미지 일괄 교체 (한 edit 세션: deleteall → 순서대로 upload → commit). 스크린샷 5~8장 한 번에 교체 시 효율적. 업로드 순서가 스토어 노출 순서', {
137
+ server.tool('playstore_replace_images', 'Google Play 리스팅 이미지 일괄 교체 (한 edit 세션: deleteall → 순서대로 upload → commit). 스크린샷 5~8장 한 번에 교체 시 효율적. 업로드 순서가 스토어 노출 순서. ⚠️ commit이 Play Console의 미게시 리스팅 변경을 덮어쓸 수 있음.', {
138
138
  packageName: z.string().describe('패키지명'),
139
139
  language: z.string().describe('언어 코드'),
140
140
  imageType: z.enum(['featureGraphic', 'icon', 'phoneScreenshots', 'promoGraphic', 'sevenInchScreenshots', 'tenInchScreenshots', 'tvBanner', 'tvScreenshots', 'wearScreenshots']),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoonion/mimi-seed-mcp",
3
- "version": "0.3.40",
3
+ "version": "0.5.0",
4
4
  "description": "Mimi Seed MCP server — Firebase + AdMob + Google Play + App Store management for Claude Code / Codex / Cursor / any MCP client.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,10 @@
8
8
  "mimi-seed-auth": "dist/auth/cli.js",
9
9
  "mimi-seed-appstore-auth": "dist/appstore/setup-cli.js",
10
10
  "mimi-seed-playstore-auth": "dist/auth/playstore-setup-cli.js",
11
- "mimi-seed-bigquery-auth": "dist/auth/bigquery-setup-cli.js"
11
+ "mimi-seed-bigquery-auth": "dist/auth/bigquery-setup-cli.js",
12
+ "mimi-seed-firebase": "dist/firebase/cli.js",
13
+ "mimi-seed-admob": "dist/admob/cli.js",
14
+ "mimi-seed-ga4": "dist/ga4/cli.js"
12
15
  },
13
16
  "files": [
14
17
  "dist",
@@ -47,6 +50,9 @@
47
50
  "engines": {
48
51
  "node": ">=20"
49
52
  },
53
+ "overrides": {
54
+ "gaxios": "7.1.5"
55
+ },
50
56
  "dependencies": {
51
57
  "@anthropic-ai/sdk": "^0.52.0",
52
58
  "@modelcontextprotocol/sdk": "^1.12.1",