@yoonion/mimi-seed-mcp 0.3.29 → 0.3.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/google-auth.js +2 -1
- package/dist/googleads/config.js +10 -1
- package/dist/googleads/tools.js +4 -1
- package/dist/helpers.d.ts +17 -2
- package/dist/helpers.js +50 -18
- package/dist/lib/google-errors.d.ts +10 -0
- package/dist/lib/google-errors.js +71 -0
- package/dist/playstore/errors.d.ts +1 -0
- package/dist/playstore/errors.js +33 -0
- package/dist/playstore/tools.js +13 -4
- package/dist/registers/admob.js +7 -7
- package/dist/registers/checks.js +1 -1
- package/dist/registers/firebase.js +43 -18
- package/dist/registers/googleads.js +4 -4
- package/dist/registers/iam.js +5 -5
- package/dist/registers/playstore.js +31 -2
- package/package.json +2 -2
package/dist/auth/google-auth.js
CHANGED
|
@@ -12,6 +12,7 @@ const SCOPES = [
|
|
|
12
12
|
'https://www.googleapis.com/auth/admob.readonly',
|
|
13
13
|
'https://www.googleapis.com/auth/admob.monetization',
|
|
14
14
|
'https://www.googleapis.com/auth/androidpublisher',
|
|
15
|
+
'https://www.googleapis.com/auth/adwords', // Google Ads API (googleads_* tools)
|
|
15
16
|
];
|
|
16
17
|
// Primary config dir. Legacy `~/.preseed` is read as a fallback during the
|
|
17
18
|
// rebrand so existing auth sessions don't force a re-login; new writes go to
|
|
@@ -214,7 +215,7 @@ export function startAuth(clientId, clientSecret, options = {}) {
|
|
|
214
215
|
<html><body style="font-family:system-ui;display:flex;justify-content:center;align-items:center;height:100vh;margin:0">
|
|
215
216
|
<div style="text-align:center">
|
|
216
217
|
<h1>✅ Mimi Seed 인증 완료!</h1>
|
|
217
|
-
<p>이 창을 닫고 Claude Code 또는 Codex로 돌아가세요.</p>
|
|
218
|
+
<p>이 창을 닫고 Claude Code 또는 Codex로 돌아가세요.</p>
|
|
218
219
|
</div>
|
|
219
220
|
</body></html>
|
|
220
221
|
`);
|
package/dist/googleads/config.js
CHANGED
|
@@ -24,7 +24,16 @@ export function loadConfig() {
|
|
|
24
24
|
if (!fs.existsSync(CONFIG_PATH))
|
|
25
25
|
return null;
|
|
26
26
|
try {
|
|
27
|
-
|
|
27
|
+
const cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
|
|
28
|
+
// 읽기 시점에도 정규화 — 하이픈 포함 값이 손으로 적히거나 구버전이 쓴 경우에도
|
|
29
|
+
// URL/login-customer-id 헤더가 항상 숫자만 포함하도록 보장 (write 경로 의존 제거).
|
|
30
|
+
return {
|
|
31
|
+
...cfg,
|
|
32
|
+
customerId: normalizeCustomerId(cfg.customerId),
|
|
33
|
+
loginCustomerId: cfg.loginCustomerId
|
|
34
|
+
? normalizeCustomerId(cfg.loginCustomerId)
|
|
35
|
+
: undefined,
|
|
36
|
+
};
|
|
28
37
|
}
|
|
29
38
|
catch {
|
|
30
39
|
return null;
|
package/dist/googleads/tools.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
// Google Ads API는 ~13개월 주기로 sunset (항상 최신 3개 major만 유지).
|
|
2
|
+
// v24 = 2026-06 기준 현행 major. 새 major 출시 시 갱신 필요.
|
|
3
|
+
// https://developers.google.com/google-ads/api/docs/sunset-dates
|
|
4
|
+
const API_VERSION = 'v24';
|
|
2
5
|
const BASE = `https://googleads.googleapis.com/${API_VERSION}`;
|
|
3
6
|
// ─── 내부 헬퍼 ───────────────────────────────────────────────
|
|
4
7
|
async function getAccessToken(auth) {
|
package/dist/helpers.d.ts
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
import { ensureFreshAccessToken } from './auth/google-auth.js';
|
|
2
2
|
export { ensureFreshAccessToken };
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* OAuth 클라이언트 확보 — 호출 전에 access_token 을 사전 갱신하고,
|
|
5
|
+
* refresh_token 이 만료/누락이면 raw googleapis 에러(invalid_grant 등) 대신
|
|
6
|
+
* 친절한 재로그인 안내를 던진다.
|
|
7
|
+
*
|
|
8
|
+
* 기존엔 만료 시 각 OAuth 도구(firebase/admob/iam/googleads/checks)가
|
|
9
|
+
* googleapis GaxiosError 를 그대로 노출 → 사용자는 "재로그인하라"는 안내를 못 받았다.
|
|
10
|
+
*/
|
|
11
|
+
export declare function requireAuth(): Promise<import("google-auth-library").OAuth2Client>;
|
|
4
12
|
export declare const PLAY_AUTH_HINT: string;
|
|
5
13
|
export declare const APPSTORE_AUTH_HINT: string;
|
|
6
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Google Play 인증 — 서비스 계정 JSON 우선, 없으면 OAuth 클라이언트로 폴백.
|
|
16
|
+
*
|
|
17
|
+
* 로그인(mimi-seed-auth) 시 androidpublisher scope 를 이미 부여받으므로,
|
|
18
|
+
* 별도 서비스 계정 JSON 을 받지 않아도 대부분의 Play 작업이 가능하다.
|
|
19
|
+
* (서비스 계정은 서버/헤드리스 — onesub 영수증 검증 등 — 용도로 계속 우선 적용.)
|
|
20
|
+
*/
|
|
21
|
+
export declare function requirePlayStoreAuth(packageName?: string): import("google-auth-library").OAuth2Client;
|
|
7
22
|
export declare function requireServiceAccountJson(packageName?: string): string;
|
|
8
23
|
export declare function requireAppStoreCreds(): import("./appstore/auth.js").AppStoreCredentials;
|
package/dist/helpers.js
CHANGED
|
@@ -2,30 +2,52 @@ import { getAuthenticatedClient, ensureFreshAccessToken } from './auth/google-au
|
|
|
2
2
|
import { getServiceAccountClient, getServiceAccountJson } from './auth/playstore-auth.js';
|
|
3
3
|
import { getAppStoreCredentials } from './appstore/auth.js';
|
|
4
4
|
export { ensureFreshAccessToken };
|
|
5
|
-
|
|
5
|
+
const REAUTH_CMD = ' npx -y @yoonion/mimi-seed-mcp mimi-seed-auth';
|
|
6
|
+
function formatAuthError(p) {
|
|
7
|
+
return [
|
|
8
|
+
`❌ [${p.code}] ${p.message}`,
|
|
9
|
+
p.hint ? `→ ${p.hint}` : '',
|
|
10
|
+
'',
|
|
11
|
+
'터미널에서 재로그인:',
|
|
12
|
+
REAUTH_CMD,
|
|
13
|
+
]
|
|
14
|
+
.filter((l) => l !== '')
|
|
15
|
+
.join('\n');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* OAuth 클라이언트 확보 — 호출 전에 access_token 을 사전 갱신하고,
|
|
19
|
+
* refresh_token 이 만료/누락이면 raw googleapis 에러(invalid_grant 등) 대신
|
|
20
|
+
* 친절한 재로그인 안내를 던진다.
|
|
21
|
+
*
|
|
22
|
+
* 기존엔 만료 시 각 OAuth 도구(firebase/admob/iam/googleads/checks)가
|
|
23
|
+
* googleapis GaxiosError 를 그대로 노출 → 사용자는 "재로그인하라"는 안내를 못 받았다.
|
|
24
|
+
*/
|
|
25
|
+
export async function requireAuth() {
|
|
26
|
+
const result = await ensureFreshAccessToken();
|
|
27
|
+
if (result.status === 'unauthenticated' || result.status === 'expired_refresh_failed') {
|
|
28
|
+
throw new Error(formatAuthError(result.error));
|
|
29
|
+
}
|
|
6
30
|
const client = getAuthenticatedClient();
|
|
7
31
|
if (!client) {
|
|
8
|
-
throw new Error(
|
|
9
|
-
|
|
10
|
-
'',
|
|
11
|
-
'
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
'브라우저가 열리면 Google 로그인 → 끝.',
|
|
16
|
-
'그 다음에 다시 물어봐줘.',
|
|
17
|
-
].join('\n'));
|
|
32
|
+
throw new Error(formatAuthError({
|
|
33
|
+
code: 'UNAUTHENTICATED',
|
|
34
|
+
message: 'Google 계정이 연결되지 않았어.',
|
|
35
|
+
hint: 'mimi-seed-auth 로 로그인하세요.',
|
|
36
|
+
retriable: false,
|
|
37
|
+
needsReauth: true,
|
|
38
|
+
}));
|
|
18
39
|
}
|
|
19
40
|
return client;
|
|
20
41
|
}
|
|
21
42
|
export const PLAY_AUTH_HINT = [
|
|
22
|
-
'❌ Google Play
|
|
43
|
+
'❌ Google Play 인증이 없어.',
|
|
23
44
|
'',
|
|
24
|
-
'
|
|
45
|
+
'가장 쉬운 방법 — Google 로그인 (androidpublisher 권한 포함):',
|
|
46
|
+
REAUTH_CMD,
|
|
25
47
|
'',
|
|
48
|
+
'또는 서버/헤드리스 환경이면 서비스 계정 JSON 등록:',
|
|
26
49
|
' npx -y @yoonion/mimi-seed-mcp mimi-seed-playstore-auth',
|
|
27
50
|
'',
|
|
28
|
-
'서비스 계정 JSON 파일 경로를 입력하면 저장 완료.',
|
|
29
51
|
'그 다음에 다시 물어봐줘.',
|
|
30
52
|
].join('\n');
|
|
31
53
|
export const APPSTORE_AUTH_HINT = [
|
|
@@ -38,11 +60,21 @@ export const APPSTORE_AUTH_HINT = [
|
|
|
38
60
|
'Issuer ID, Key ID, .p8 파일 경로를 입력하면 저장 완료.',
|
|
39
61
|
'그 다음에 다시 물어봐줘.',
|
|
40
62
|
].join('\n');
|
|
63
|
+
/**
|
|
64
|
+
* Google Play 인증 — 서비스 계정 JSON 우선, 없으면 OAuth 클라이언트로 폴백.
|
|
65
|
+
*
|
|
66
|
+
* 로그인(mimi-seed-auth) 시 androidpublisher scope 를 이미 부여받으므로,
|
|
67
|
+
* 별도 서비스 계정 JSON 을 받지 않아도 대부분의 Play 작업이 가능하다.
|
|
68
|
+
* (서비스 계정은 서버/헤드리스 — onesub 영수증 검증 등 — 용도로 계속 우선 적용.)
|
|
69
|
+
*/
|
|
41
70
|
export function requirePlayStoreAuth(packageName) {
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
71
|
+
const sa = getServiceAccountClient(packageName);
|
|
72
|
+
if (sa)
|
|
73
|
+
return sa;
|
|
74
|
+
const oauth = getAuthenticatedClient();
|
|
75
|
+
if (oauth)
|
|
76
|
+
return oauth;
|
|
77
|
+
throw new Error(PLAY_AUTH_HINT);
|
|
46
78
|
}
|
|
47
79
|
export function requireServiceAccountJson(packageName) {
|
|
48
80
|
const json = getServiceAccountJson(packageName);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function extractHttpStatus(e: unknown): number | undefined;
|
|
2
|
+
export declare function rawMessage(e: unknown): string;
|
|
3
|
+
/** invalid_grant / 만료 refresh_token / 부족 scope → 재로그인 안내 메시지. 아니면 null. */
|
|
4
|
+
export declare function authReauthMessage(text: string): string | null;
|
|
5
|
+
export declare function withCause(err: Error, cause: unknown): Error;
|
|
6
|
+
/**
|
|
7
|
+
* Firebase / GCP 에러를 친절 메시지로 변환. 신규 사용자가 가장 자주 만나는
|
|
8
|
+
* 'API 미활성화 / 프로젝트 없음 / billing / 권한' 케이스에 다음 행동을 붙인다.
|
|
9
|
+
*/
|
|
10
|
+
export declare function friendlyGoogleError(e: unknown): Error;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Google API 에러 친절화 (Firebase / Cloud Resource Manager / 공용 OAuth).
|
|
2
|
+
// raw GaxiosError dump 대신 "무엇을 어떻게 고칠지"를 즉시 보여준다.
|
|
3
|
+
// 인식 못 한 에러는 원본을 그대로 보존한다.
|
|
4
|
+
const REAUTH = '터미널에서 재로그인:\n npx -y @yoonion/mimi-seed-mcp mimi-seed-auth';
|
|
5
|
+
export function extractHttpStatus(e) {
|
|
6
|
+
if (e && typeof e === 'object') {
|
|
7
|
+
const any = e;
|
|
8
|
+
if (typeof any.code === 'number')
|
|
9
|
+
return any.code;
|
|
10
|
+
if (typeof any.status === 'number')
|
|
11
|
+
return any.status;
|
|
12
|
+
if (any.response && typeof any.response.status === 'number')
|
|
13
|
+
return any.response.status;
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
export function rawMessage(e) {
|
|
18
|
+
if (e instanceof Error)
|
|
19
|
+
return e.message;
|
|
20
|
+
if (typeof e === 'string')
|
|
21
|
+
return e;
|
|
22
|
+
try {
|
|
23
|
+
return JSON.stringify(e);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return String(e);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** invalid_grant / 만료 refresh_token / 부족 scope → 재로그인 안내 메시지. 아니면 null. */
|
|
30
|
+
export function authReauthMessage(text) {
|
|
31
|
+
if (/invalid_grant|invalid_rapt|rapt_required|unauthorized_client|ACCESS_TOKEN_SCOPE_INSUFFICIENT|insufficient.*scope/i.test(text)) {
|
|
32
|
+
return ['❌ Google 인증이 만료됐거나 권한이 부족해요.', '', REAUTH].join('\n');
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function extractActivationUrl(text) {
|
|
37
|
+
const m = text.match(/https?:\/\/console\.(?:developers|cloud)\.google\.com\/\S+/);
|
|
38
|
+
return m?.[0]?.replace(/[).,'"]+$/, '');
|
|
39
|
+
}
|
|
40
|
+
export function withCause(err, cause) {
|
|
41
|
+
err.cause = cause;
|
|
42
|
+
return err;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Firebase / GCP 에러를 친절 메시지로 변환. 신규 사용자가 가장 자주 만나는
|
|
46
|
+
* 'API 미활성화 / 프로젝트 없음 / billing / 권한' 케이스에 다음 행동을 붙인다.
|
|
47
|
+
*/
|
|
48
|
+
export function friendlyGoogleError(e) {
|
|
49
|
+
const text = rawMessage(e);
|
|
50
|
+
const status = extractHttpStatus(e);
|
|
51
|
+
const reauth = authReauthMessage(text);
|
|
52
|
+
if (reauth)
|
|
53
|
+
return withCause(new Error(reauth), e);
|
|
54
|
+
if (/SERVICE_DISABLED|has not been used in project|is disabled|accessNotConfigured/i.test(text)) {
|
|
55
|
+
const url = extractActivationUrl(text);
|
|
56
|
+
return withCause(new Error([
|
|
57
|
+
'❌ 필요한 Google API가 비활성화돼 있어요.',
|
|
58
|
+
url ? `→ 활성화: ${url}` : '→ Google Cloud Console에서 해당 API를 활성화한 뒤 다시 시도하세요.',
|
|
59
|
+
].join('\n')), e);
|
|
60
|
+
}
|
|
61
|
+
if (/BILLING_DISABLED|billing.*(disabled|not enabled|required)/i.test(text)) {
|
|
62
|
+
return withCause(new Error('❌ 결제(billing)가 비활성화된 프로젝트예요.\n→ Google Cloud Console → 결제 에서 결제 계정을 연결하세요.'), e);
|
|
63
|
+
}
|
|
64
|
+
if (status === 404 || /NOT_FOUND|not found/i.test(text)) {
|
|
65
|
+
return withCause(new Error('❌ 프로젝트/리소스를 찾을 수 없어요.\n→ firebase_list_projects 로 유효한 projectId를 확인하세요.'), e);
|
|
66
|
+
}
|
|
67
|
+
if (status === 403 || /PERMISSION_DENIED|forbidden|permission/i.test(text)) {
|
|
68
|
+
return withCause(new Error('❌ 권한 부족 — 이 Google 계정이 해당 프로젝트에 접근 권한이 있는지 확인하세요.'), e);
|
|
69
|
+
}
|
|
70
|
+
return e instanceof Error ? e : new Error(text);
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function friendlyPlayError(e: unknown, packageName?: string): Error;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Google Play (Android Publisher / Developer Reporting) 에러 친절화.
|
|
2
|
+
// App Store 의 friendlyAppStoreError 와 대칭 — Play 쪽엔 없던 친절화 레이어.
|
|
3
|
+
// 신규 사용자가 가장 자주 막히는 두 케이스(서비스 계정 권한 403, edit 세션 충돌)에
|
|
4
|
+
// 구체적 복구 단계를 붙인다. 인식 못 한 에러(도메인 에러 포함)는 원본 보존.
|
|
5
|
+
import { extractHttpStatus, authReauthMessage, withCause } from '../lib/google-errors.js';
|
|
6
|
+
// playstore_verify_service_account 의 403 안내와 동일한 체크리스트.
|
|
7
|
+
const PERMISSION_403 = [
|
|
8
|
+
'❌ Google Play 권한 부족 (403).',
|
|
9
|
+
'원인: 인증은 됐지만 Play Console에서 이 계정/서비스 계정에 권한이 없어요.',
|
|
10
|
+
'',
|
|
11
|
+
'1. Play Console → 사용자 및 권한(Users and permissions)',
|
|
12
|
+
'2. 이 Google 계정(또는 서비스 계정 이메일)을 초대 / 권한 부여',
|
|
13
|
+
'3. 앱 권한에 "앱 정보 관리" (재무 데이터 필요 시 "재무 데이터 보기") 부여',
|
|
14
|
+
'4. 권한 적용까지 ~5분 대기 후 재시도 (너무 빨리 시도하면 계속 403)',
|
|
15
|
+
].join('\n');
|
|
16
|
+
export function friendlyPlayError(e, packageName) {
|
|
17
|
+
const text = e instanceof Error ? e.message : String(e);
|
|
18
|
+
const status = extractHttpStatus(e);
|
|
19
|
+
const reauth = authReauthMessage(text);
|
|
20
|
+
if (reauth)
|
|
21
|
+
return withCause(new Error(reauth), e);
|
|
22
|
+
if (status === 403 || /PERMISSION_DENIED|insufficient permission|forbidden/i.test(text)) {
|
|
23
|
+
return withCause(new Error(PERMISSION_403), e);
|
|
24
|
+
}
|
|
25
|
+
if (status === 404 || /NOT_FOUND|not found/i.test(text)) {
|
|
26
|
+
return withCause(new Error(`❌ 패키지${packageName ? ` (${packageName})` : ''}를 이 개발자 계정에서 찾을 수 없어요.\n→ packageName 철자와 계정 권한을 확인하세요.`), e);
|
|
27
|
+
}
|
|
28
|
+
if (/This Edit has been deleted|editAlreadyCommitted|edit.*(deleted|expired|not found)/i.test(text)) {
|
|
29
|
+
return withCause(new Error('❌ Play edit 세션 충돌 — 같은 요청에서 edit 세션을 여러 번 열었을 수 있어요.\n→ 작업을 하나의 withEdit 트랜잭션으로 묶고, 잠시 후 재시도하세요.'), e);
|
|
30
|
+
}
|
|
31
|
+
// 인식 못 한 에러(이미 친절한 도메인 에러 포함)는 원본 보존
|
|
32
|
+
return e instanceof Error ? e : new Error(text);
|
|
33
|
+
}
|
package/dist/playstore/tools.js
CHANGED
|
@@ -62,23 +62,32 @@ export async function withEdit(auth, packageName, fn, commit = false) {
|
|
|
62
62
|
export async function getStatistics(auth, packageName, query) {
|
|
63
63
|
const metricSet = query.metricSet ?? 'anrRate';
|
|
64
64
|
const resource = METRIC_SET_RESOURCE[metricSet];
|
|
65
|
-
const
|
|
65
|
+
const period = query.aggregationPeriod ?? 'DAILY';
|
|
66
|
+
// Reporting API는 집계 단위별 지원 timezone이 고정: HOURLY=UTC, DAILY=America/Los_Angeles.
|
|
67
|
+
// 단일 default를 모든 period에 쓰면 HOURLY가 INVALID_ARGUMENT로 실패한다.
|
|
68
|
+
const timeZone = query.timeZone ?? (period === 'HOURLY' ? 'UTC' : 'America/Los_Angeles');
|
|
69
|
+
// errorCountMetricSet은 reportType dimension이 필수 — default에 포함하지 않으면 실패.
|
|
70
|
+
const dimensions = query.dimensions ??
|
|
71
|
+
(metricSet === 'errorCount' ? ['reportType', 'versionCode'] : ['versionCode']);
|
|
66
72
|
const url = `${REPORTING_API_BASE}/apps/${encodeURIComponent(packageName)}/${resource}:query`;
|
|
67
73
|
const res = await auth.request({
|
|
68
74
|
url,
|
|
69
75
|
method: 'POST',
|
|
70
76
|
data: {
|
|
71
77
|
timelineSpec: {
|
|
72
|
-
aggregationPeriod:
|
|
78
|
+
aggregationPeriod: period,
|
|
73
79
|
startTime: dateToReportingDateTime(query.startDate, timeZone),
|
|
74
80
|
endTime: dateToReportingDateTime(query.endDate, timeZone),
|
|
75
81
|
},
|
|
76
|
-
dimensions
|
|
82
|
+
dimensions,
|
|
77
83
|
metrics: query.metrics ?? DEFAULT_METRICS[metricSet],
|
|
78
84
|
...(query.filter ? { filter: query.filter } : {}),
|
|
79
85
|
...(query.pageSize ? { pageSize: query.pageSize } : {}),
|
|
80
86
|
...(query.pageToken ? { pageToken: query.pageToken } : {}),
|
|
81
|
-
|
|
87
|
+
// userCohort는 anrRate/crashRate만 지원 (errorCount에 보내면 INVALID_ARGUMENT).
|
|
88
|
+
...(query.userCohort && metricSet !== 'errorCount'
|
|
89
|
+
? { userCohort: query.userCohort }
|
|
90
|
+
: {}),
|
|
82
91
|
},
|
|
83
92
|
});
|
|
84
93
|
return res.data;
|
package/dist/registers/admob.js
CHANGED
|
@@ -3,22 +3,22 @@ import * as admob from '../admob/tools.js';
|
|
|
3
3
|
import { requireAuth } from '../helpers.js';
|
|
4
4
|
export function registerAdmobTools(server) {
|
|
5
5
|
server.tool('admob_list_accounts', 'AdMob 계정 목록 조회', {}, async () => {
|
|
6
|
-
const auth = requireAuth();
|
|
6
|
+
const auth = await requireAuth();
|
|
7
7
|
const accounts = await admob.listAccounts(auth);
|
|
8
8
|
return { content: [{ type: 'text', text: JSON.stringify(accounts, null, 2) }] };
|
|
9
9
|
});
|
|
10
10
|
server.tool('admob_list_apps', 'AdMob에 등록된 앱 목록', { accountId: z.string().describe('AdMob 계정 ID (예: accounts/pub-XXXX)') }, async ({ accountId }) => {
|
|
11
|
-
const auth = requireAuth();
|
|
11
|
+
const auth = await requireAuth();
|
|
12
12
|
const apps = await admob.listApps(auth, accountId);
|
|
13
13
|
return { content: [{ type: 'text', text: JSON.stringify(apps, null, 2) }] };
|
|
14
14
|
});
|
|
15
15
|
server.tool('admob_list_ad_units', 'AdMob 광고 단위 목록', { accountId: z.string().describe('AdMob 계정 ID') }, async ({ accountId }) => {
|
|
16
|
-
const auth = requireAuth();
|
|
16
|
+
const auth = await requireAuth();
|
|
17
17
|
const units = await admob.listAdUnits(auth, accountId);
|
|
18
18
|
return { content: [{ type: 'text', text: JSON.stringify(units, null, 2) }] };
|
|
19
19
|
});
|
|
20
20
|
server.tool('admob_get_today_earnings', '오늘 AdMob 수익 요약', { accountId: z.string().describe('AdMob 계정 ID') }, async ({ accountId }) => {
|
|
21
|
-
const auth = requireAuth();
|
|
21
|
+
const auth = await requireAuth();
|
|
22
22
|
const report = await admob.getTodayEarnings(auth, accountId);
|
|
23
23
|
return { content: [{ type: 'text', text: JSON.stringify(report, null, 2) }] };
|
|
24
24
|
});
|
|
@@ -31,7 +31,7 @@ export function registerAdmobTools(server) {
|
|
|
31
31
|
endMonth: z.number().describe('종료 월 (1-12)'),
|
|
32
32
|
endDay: z.number().describe('종료 일'),
|
|
33
33
|
}, async ({ accountId, startYear, startMonth, startDay, endYear, endMonth, endDay }) => {
|
|
34
|
-
const auth = requireAuth();
|
|
34
|
+
const auth = await requireAuth();
|
|
35
35
|
const report = await admob.getNetworkReport(auth, accountId, { year: startYear, month: startMonth, day: startDay }, { year: endYear, month: endMonth, day: endDay });
|
|
36
36
|
return { content: [{ type: 'text', text: JSON.stringify(report, null, 2) }] };
|
|
37
37
|
});
|
|
@@ -40,7 +40,7 @@ export function registerAdmobTools(server) {
|
|
|
40
40
|
platform: z.enum(['ANDROID', 'IOS']).describe('플랫폼'),
|
|
41
41
|
displayName: z.string().describe('앱 이름'),
|
|
42
42
|
}, async ({ accountId, platform, displayName }) => {
|
|
43
|
-
const auth = requireAuth();
|
|
43
|
+
const auth = await requireAuth();
|
|
44
44
|
try {
|
|
45
45
|
const result = await admob.createApp(auth, accountId, platform, displayName);
|
|
46
46
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
@@ -71,7 +71,7 @@ export function registerAdmobTools(server) {
|
|
|
71
71
|
displayName: z.string().describe('광고 단위 이름'),
|
|
72
72
|
adFormat: z.enum(['BANNER', 'INTERSTITIAL', 'REWARDED', 'REWARDED_INTERSTITIAL', 'APP_OPEN', 'NATIVE']).describe('광고 형식'),
|
|
73
73
|
}, async ({ accountId, appId, displayName, adFormat }) => {
|
|
74
|
-
const auth = requireAuth();
|
|
74
|
+
const auth = await requireAuth();
|
|
75
75
|
try {
|
|
76
76
|
const result = await admob.createAdUnit(auth, accountId, appId, displayName, adFormat);
|
|
77
77
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
package/dist/registers/checks.js
CHANGED
|
@@ -13,7 +13,7 @@ export function registerChecksTools(server) {
|
|
|
13
13
|
packageName: z.string().describe('Android 패키지명 (예: com.example.myapp)'),
|
|
14
14
|
language: z.string().optional().describe('언어 코드 (기본: ko-KR)'),
|
|
15
15
|
}, async ({ packageName, language }) => {
|
|
16
|
-
const auth = requireAuth();
|
|
16
|
+
const auth = await requireAuth();
|
|
17
17
|
const risks = await checkPlayStoreRisks(auth, packageName, language ?? 'ko-KR');
|
|
18
18
|
const text = formatRisks(risks, 'Google Play');
|
|
19
19
|
return { content: [{ type: 'text', text }] };
|
|
@@ -1,19 +1,44 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import * as
|
|
2
|
+
import * as firebaseRaw from '../firebase/tools.js';
|
|
3
3
|
import { requireAuth } from '../helpers.js';
|
|
4
|
+
import { friendlyGoogleError } from '../lib/google-errors.js';
|
|
5
|
+
// 모든 firebase tools 호출을 친절 에러로 감싸는 프록시 — 17개 핸들러에 개별
|
|
6
|
+
// try/catch 없이 raw GaxiosError(API 미활성화/프로젝트 없음/billing/권한)를
|
|
7
|
+
// "다음에 뭘 할지" 메시지로 변환. 비-Promise 반환은 그대로 통과.
|
|
8
|
+
const firebase = new Proxy(firebaseRaw, {
|
|
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
|
+
});
|
|
4
29
|
export function registerFirebaseTools(server) {
|
|
5
30
|
server.tool('firebase_list_projects', '내 Firebase 프로젝트 목록 조회', {}, async () => {
|
|
6
|
-
const auth = requireAuth();
|
|
31
|
+
const auth = await requireAuth();
|
|
7
32
|
const projects = await firebase.listProjects(auth);
|
|
8
33
|
return { content: [{ type: 'text', text: JSON.stringify(projects, null, 2) }] };
|
|
9
34
|
});
|
|
10
35
|
server.tool('firebase_get_project', 'Firebase 프로젝트 상세 정보 조회', { projectId: z.string().describe('Firebase 프로젝트 ID') }, async ({ projectId }) => {
|
|
11
|
-
const auth = requireAuth();
|
|
36
|
+
const auth = await requireAuth();
|
|
12
37
|
const project = await firebase.getProject(auth, projectId);
|
|
13
38
|
return { content: [{ type: 'text', text: JSON.stringify(project, null, 2) }] };
|
|
14
39
|
});
|
|
15
40
|
server.tool('firebase_list_android_apps', 'Firebase 프로젝트의 Android 앱 목록', { projectId: z.string().describe('Firebase 프로젝트 ID') }, async ({ projectId }) => {
|
|
16
|
-
const auth = requireAuth();
|
|
41
|
+
const auth = await requireAuth();
|
|
17
42
|
const apps = await firebase.listAndroidApps(auth, projectId);
|
|
18
43
|
return { content: [{ type: 'text', text: JSON.stringify(apps, null, 2) }] };
|
|
19
44
|
});
|
|
@@ -22,7 +47,7 @@ export function registerFirebaseTools(server) {
|
|
|
22
47
|
packageName: z.string().describe('Android 패키지명 (예: com.example.myapp)'),
|
|
23
48
|
displayName: z.string().describe('앱 표시 이름'),
|
|
24
49
|
}, async ({ projectId, packageName, displayName }) => {
|
|
25
|
-
const auth = requireAuth();
|
|
50
|
+
const auth = await requireAuth();
|
|
26
51
|
const result = await firebase.createAndroidApp(auth, projectId, packageName, displayName);
|
|
27
52
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
28
53
|
});
|
|
@@ -30,7 +55,7 @@ export function registerFirebaseTools(server) {
|
|
|
30
55
|
projectId: z.string().describe('Firebase 프로젝트 ID'),
|
|
31
56
|
appId: z.string().describe('Firebase 앱 ID'),
|
|
32
57
|
}, async ({ projectId, appId }) => {
|
|
33
|
-
const auth = requireAuth();
|
|
58
|
+
const auth = await requireAuth();
|
|
34
59
|
const config = await firebase.getAndroidConfig(auth, projectId, appId);
|
|
35
60
|
return { content: [{ type: 'text', text: JSON.stringify(config, null, 2) }] };
|
|
36
61
|
});
|
|
@@ -38,12 +63,12 @@ export function registerFirebaseTools(server) {
|
|
|
38
63
|
projectId: z.string().describe('Firebase 프로젝트 ID'),
|
|
39
64
|
appId: z.string().describe('삭제할 Firebase 앱 ID'),
|
|
40
65
|
}, async ({ projectId, appId }) => {
|
|
41
|
-
const auth = requireAuth();
|
|
66
|
+
const auth = await requireAuth();
|
|
42
67
|
const result = await firebase.deleteAndroidApp(auth, projectId, appId);
|
|
43
68
|
return { content: [{ type: 'text', text: `삭제 완료: ${JSON.stringify(result)}` }] };
|
|
44
69
|
});
|
|
45
70
|
server.tool('firebase_list_ios_apps', 'Firebase 프로젝트의 iOS 앱 목록', { projectId: z.string().describe('Firebase 프로젝트 ID') }, async ({ projectId }) => {
|
|
46
|
-
const auth = requireAuth();
|
|
71
|
+
const auth = await requireAuth();
|
|
47
72
|
const apps = await firebase.listIosApps(auth, projectId);
|
|
48
73
|
return { content: [{ type: 'text', text: JSON.stringify(apps, null, 2) }] };
|
|
49
74
|
});
|
|
@@ -52,7 +77,7 @@ export function registerFirebaseTools(server) {
|
|
|
52
77
|
bundleId: z.string().describe('iOS Bundle ID (예: com.example.myapp)'),
|
|
53
78
|
displayName: z.string().describe('앱 표시 이름'),
|
|
54
79
|
}, async ({ projectId, bundleId, displayName }) => {
|
|
55
|
-
const auth = requireAuth();
|
|
80
|
+
const auth = await requireAuth();
|
|
56
81
|
const result = await firebase.createIosApp(auth, projectId, bundleId, displayName);
|
|
57
82
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
58
83
|
});
|
|
@@ -60,7 +85,7 @@ export function registerFirebaseTools(server) {
|
|
|
60
85
|
projectId: z.string().describe('Firebase 프로젝트 ID'),
|
|
61
86
|
appId: z.string().describe('Firebase 앱 ID'),
|
|
62
87
|
}, async ({ projectId, appId }) => {
|
|
63
|
-
const auth = requireAuth();
|
|
88
|
+
const auth = await requireAuth();
|
|
64
89
|
const config = await firebase.getIosConfig(auth, projectId, appId);
|
|
65
90
|
return { content: [{ type: 'text', text: JSON.stringify(config, null, 2) }] };
|
|
66
91
|
});
|
|
@@ -68,12 +93,12 @@ export function registerFirebaseTools(server) {
|
|
|
68
93
|
projectId: z.string().describe('Firebase 프로젝트 ID'),
|
|
69
94
|
appId: z.string().describe('삭제할 Firebase 앱 ID'),
|
|
70
95
|
}, async ({ projectId, appId }) => {
|
|
71
|
-
const auth = requireAuth();
|
|
96
|
+
const auth = await requireAuth();
|
|
72
97
|
const result = await firebase.deleteIosApp(auth, projectId, appId);
|
|
73
98
|
return { content: [{ type: 'text', text: `삭제 완료: ${JSON.stringify(result)}` }] };
|
|
74
99
|
});
|
|
75
100
|
server.tool('firebase_list_web_apps', 'Firebase 프로젝트의 Web 앱 목록', { projectId: z.string().describe('Firebase 프로젝트 ID') }, async ({ projectId }) => {
|
|
76
|
-
const auth = requireAuth();
|
|
101
|
+
const auth = await requireAuth();
|
|
77
102
|
const apps = await firebase.listWebApps(auth, projectId);
|
|
78
103
|
return { content: [{ type: 'text', text: JSON.stringify(apps, null, 2) }] };
|
|
79
104
|
});
|
|
@@ -81,7 +106,7 @@ export function registerFirebaseTools(server) {
|
|
|
81
106
|
projectId: z.string().describe('Firebase 프로젝트 ID'),
|
|
82
107
|
displayName: z.string().describe('앱 표시 이름'),
|
|
83
108
|
}, async ({ projectId, displayName }) => {
|
|
84
|
-
const auth = requireAuth();
|
|
109
|
+
const auth = await requireAuth();
|
|
85
110
|
const result = await firebase.createWebApp(auth, projectId, displayName);
|
|
86
111
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
87
112
|
});
|
|
@@ -89,7 +114,7 @@ export function registerFirebaseTools(server) {
|
|
|
89
114
|
projectId: z.string().describe('Firebase 프로젝트 ID'),
|
|
90
115
|
appId: z.string().describe('Firebase 앱 ID'),
|
|
91
116
|
}, async ({ projectId, appId }) => {
|
|
92
|
-
const auth = requireAuth();
|
|
117
|
+
const auth = await requireAuth();
|
|
93
118
|
const config = await firebase.getWebConfig(auth, projectId, appId);
|
|
94
119
|
return { content: [{ type: 'text', text: JSON.stringify(config, null, 2) }] };
|
|
95
120
|
});
|
|
@@ -97,7 +122,7 @@ export function registerFirebaseTools(server) {
|
|
|
97
122
|
projectId: z.string().describe('Firebase 프로젝트 ID'),
|
|
98
123
|
appId: z.string().describe('삭제할 Firebase 앱 ID'),
|
|
99
124
|
}, async ({ projectId, appId }) => {
|
|
100
|
-
const auth = requireAuth();
|
|
125
|
+
const auth = await requireAuth();
|
|
101
126
|
const result = await firebase.deleteWebApp(auth, projectId, appId);
|
|
102
127
|
return { content: [{ type: 'text', text: `삭제 완료: ${JSON.stringify(result)}` }] };
|
|
103
128
|
});
|
|
@@ -105,17 +130,17 @@ export function registerFirebaseTools(server) {
|
|
|
105
130
|
projectId: z.string().describe('프로젝트 ID'),
|
|
106
131
|
serviceId: z.string().describe('서비스 ID (예: firestore.googleapis.com)'),
|
|
107
132
|
}, async ({ projectId, serviceId }) => {
|
|
108
|
-
const auth = requireAuth();
|
|
133
|
+
const auth = await requireAuth();
|
|
109
134
|
const result = await firebase.enableService(auth, projectId, serviceId);
|
|
110
135
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
111
136
|
});
|
|
112
137
|
server.tool('firebase_enable_common_services', 'Firebase 기본 서비스 일괄 활성화 (Firestore, Auth, Storage, FCM 등)', { projectId: z.string().describe('프로젝트 ID') }, async ({ projectId }) => {
|
|
113
|
-
const auth = requireAuth();
|
|
138
|
+
const auth = await requireAuth();
|
|
114
139
|
const results = await firebase.enableCommonServices(auth, projectId);
|
|
115
140
|
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
|
|
116
141
|
});
|
|
117
142
|
server.tool('firebase_list_enabled_services', '프로젝트에서 활성화된 GCP 서비스 목록', { projectId: z.string().describe('프로젝트 ID') }, async ({ projectId }) => {
|
|
118
|
-
const auth = requireAuth();
|
|
143
|
+
const auth = await requireAuth();
|
|
119
144
|
const services = await firebase.listEnabledServices(auth, projectId);
|
|
120
145
|
return { content: [{ type: 'text', text: JSON.stringify(services, null, 2) }] };
|
|
121
146
|
});
|
|
@@ -26,7 +26,7 @@ export function registerGoogleAdsTools(server) {
|
|
|
26
26
|
};
|
|
27
27
|
});
|
|
28
28
|
server.tool('googleads_list_campaigns', 'Google Ads 캠페인 목록 조회 (상태, 채널 타입, 일일 예산 포함)', {}, async () => {
|
|
29
|
-
const auth = requireAuth();
|
|
29
|
+
const auth = await requireAuth();
|
|
30
30
|
const cfg = requireConfig();
|
|
31
31
|
const campaigns = await googleads.listCampaigns(auth, cfg);
|
|
32
32
|
return {
|
|
@@ -40,7 +40,7 @@ export function registerGoogleAdsTools(server) {
|
|
|
40
40
|
startDate: z.string().describe('시작일 (YYYY-MM-DD)'),
|
|
41
41
|
endDate: z.string().describe('종료일 (YYYY-MM-DD)'),
|
|
42
42
|
}, async ({ startDate, endDate }) => {
|
|
43
|
-
const auth = requireAuth();
|
|
43
|
+
const auth = await requireAuth();
|
|
44
44
|
const cfg = requireConfig();
|
|
45
45
|
const report = await googleads.getCampaignReport(auth, cfg, { startDate, endDate });
|
|
46
46
|
const totalCost = report.reduce((s, r) => s + r.cost, 0);
|
|
@@ -68,7 +68,7 @@ export function registerGoogleAdsTools(server) {
|
|
|
68
68
|
startDate: z.string().describe('시작일 (YYYY-MM-DD)'),
|
|
69
69
|
endDate: z.string().describe('종료일 (YYYY-MM-DD)'),
|
|
70
70
|
}, async ({ startDate, endDate }) => {
|
|
71
|
-
const auth = requireAuth();
|
|
71
|
+
const auth = await requireAuth();
|
|
72
72
|
const cfg = requireConfig();
|
|
73
73
|
const report = await googleads.getUacReport(auth, cfg, { startDate, endDate });
|
|
74
74
|
const totalCost = report.reduce((s, r) => s + r.cost, 0);
|
|
@@ -90,7 +90,7 @@ export function registerGoogleAdsTools(server) {
|
|
|
90
90
|
};
|
|
91
91
|
});
|
|
92
92
|
server.tool('googleads_list_accessible_customers', 'OAuth 토큰으로 접근 가능한 Google Ads 계정 목록 (API 연결 확인용)', {}, async () => {
|
|
93
|
-
const auth = requireAuth();
|
|
93
|
+
const auth = await requireAuth();
|
|
94
94
|
const cfg = requireConfig();
|
|
95
95
|
const result = await googleads.listAccessibleCustomers(auth, cfg);
|
|
96
96
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
package/dist/registers/iam.js
CHANGED
|
@@ -5,7 +5,7 @@ export function registerIamTools(server) {
|
|
|
5
5
|
server.tool('iam_list_service_accounts', '주어진 projectId의 서비스 계정 목록. 이메일 / displayName / disabled 상태 반환.', {
|
|
6
6
|
projectId: z.string().describe('Google Cloud 프로젝트 ID'),
|
|
7
7
|
}, async ({ projectId }) => {
|
|
8
|
-
const auth = requireAuth();
|
|
8
|
+
const auth = await requireAuth();
|
|
9
9
|
const accounts = await iam.listServiceAccounts(auth, projectId);
|
|
10
10
|
return { content: [{ type: 'text', text: JSON.stringify(accounts, null, 2) }] };
|
|
11
11
|
});
|
|
@@ -17,7 +17,7 @@ export function registerIamTools(server) {
|
|
|
17
17
|
.describe('서비스 계정 ID (이메일 로컬 파트)'),
|
|
18
18
|
displayName: z.string().describe('사람이 읽을 표시 이름 (예: "onesub Play verifier")'),
|
|
19
19
|
}, async ({ projectId, accountId, displayName }) => {
|
|
20
|
-
const auth = requireAuth();
|
|
20
|
+
const auth = await requireAuth();
|
|
21
21
|
const account = await iam.createServiceAccount(auth, projectId, accountId, displayName);
|
|
22
22
|
return {
|
|
23
23
|
content: [
|
|
@@ -41,7 +41,7 @@ export function registerIamTools(server) {
|
|
|
41
41
|
server.tool('iam_list_keys', '주어진 서비스 계정의 기존 키 목록. keyId / keyType / 만료시간 반환. 회수할 키 찾을 때 사용.', {
|
|
42
42
|
serviceAccount: z.string().describe('서비스 계정 이메일'),
|
|
43
43
|
}, async ({ serviceAccount }) => {
|
|
44
|
-
const auth = requireAuth();
|
|
44
|
+
const auth = await requireAuth();
|
|
45
45
|
const keys = await iam.listServiceAccountKeys(auth, serviceAccount);
|
|
46
46
|
return { content: [{ type: 'text', text: JSON.stringify(keys, null, 2) }] };
|
|
47
47
|
});
|
|
@@ -51,7 +51,7 @@ export function registerIamTools(server) {
|
|
|
51
51
|
].join(' '), {
|
|
52
52
|
serviceAccount: z.string().describe('서비스 계정 이메일'),
|
|
53
53
|
}, async ({ serviceAccount }) => {
|
|
54
|
-
const auth = requireAuth();
|
|
54
|
+
const auth = await requireAuth();
|
|
55
55
|
const key = await iam.createServiceAccountKey(auth, serviceAccount);
|
|
56
56
|
return {
|
|
57
57
|
content: [
|
|
@@ -89,7 +89,7 @@ export function registerIamTools(server) {
|
|
|
89
89
|
.describe('권한 받을 주체 (예: serviceAccount:my-sa@my-project.iam.gserviceaccount.com)'),
|
|
90
90
|
role: z.string().describe('IAM 역할 (예: roles/iam.serviceAccountTokenCreator)'),
|
|
91
91
|
}, async ({ projectId, member, role }) => {
|
|
92
|
-
const auth = requireAuth();
|
|
92
|
+
const auth = await requireAuth();
|
|
93
93
|
const result = await iam.addProjectIamPolicyBinding(auth, projectId, member, role);
|
|
94
94
|
return {
|
|
95
95
|
content: [
|
|
@@ -1,10 +1,36 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import * as
|
|
2
|
+
import * as playstoreRaw from '../playstore/tools.js';
|
|
3
|
+
import { friendlyPlayError } from '../playstore/errors.js';
|
|
3
4
|
import { saveServiceAccountJsonForPackage, listRegisteredServiceAccounts, deleteServiceAccountJsonForPackage, } from '../auth/playstore-auth.js';
|
|
4
5
|
import { createGoogleOneTimePurchase, createGoogleSubscription, updateGoogleProduct, deleteGoogleProduct, listGoogleProducts, } from '@onesub/providers';
|
|
5
6
|
import { requirePlayStoreAuth, requireServiceAccountJson } from '../helpers.js';
|
|
6
7
|
import { buildPlayStoreReleasePlan } from '../checks/plan.js';
|
|
7
8
|
import { validatePlayReleaseNotes, formatIssuesForUser } from '../lib/text-validators.js';
|
|
9
|
+
// 모든 playstore tools 호출을 친절 에러로 감싸는 프록시 — 403(권한)/404/edit 충돌/
|
|
10
|
+
// invalid_grant 를 raw dump 대신 구체적 복구 안내로 변환. args[1] 이 packageName 규약.
|
|
11
|
+
// 비-Promise 반환(publisher 등 sync factory)은 그대로 통과, 도메인 에러는 원본 보존.
|
|
12
|
+
const playstore = new Proxy(playstoreRaw, {
|
|
13
|
+
get(target, prop, receiver) {
|
|
14
|
+
const orig = Reflect.get(target, prop, receiver);
|
|
15
|
+
if (typeof orig !== 'function')
|
|
16
|
+
return orig;
|
|
17
|
+
return (...args) => {
|
|
18
|
+
const pkg = typeof args[1] === 'string' ? args[1] : undefined;
|
|
19
|
+
try {
|
|
20
|
+
const out = orig(...args);
|
|
21
|
+
if (out && typeof out.then === 'function') {
|
|
22
|
+
return out.catch((err) => {
|
|
23
|
+
throw friendlyPlayError(err, pkg);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
throw friendlyPlayError(err, pkg);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
});
|
|
8
34
|
export function registerPlaystoreTools(server) {
|
|
9
35
|
server.tool('playstore_get_app', 'Google Play 앱 상세 정보 조회', { packageName: z.string().describe('패키지명 (예: com.findthem.app)') }, async ({ packageName }) => {
|
|
10
36
|
const auth = requirePlayStoreAuth(packageName);
|
|
@@ -46,6 +72,9 @@ export function registerPlaystoreTools(server) {
|
|
|
46
72
|
endDate: z.string().describe('종료일 YYYY-MM-DD (보통 exclusive처럼 다음날 지정 권장)'),
|
|
47
73
|
aggregationPeriod: z.enum(['DAILY', 'HOURLY']).default('DAILY').describe('집계 단위'),
|
|
48
74
|
dimensions: z.array(z.enum([
|
|
75
|
+
'reportType', // errorCount metricSet 필수
|
|
76
|
+
'issueId', // errorCount 전용
|
|
77
|
+
'osBuild', // OS_BETA cohort 전용
|
|
49
78
|
'apiLevel',
|
|
50
79
|
'versionCode',
|
|
51
80
|
'deviceModel',
|
|
@@ -64,7 +93,7 @@ export function registerPlaystoreTools(server) {
|
|
|
64
93
|
'deviceGlEsVersion',
|
|
65
94
|
'deviceScreenSize',
|
|
66
95
|
'deviceScreenDpi',
|
|
67
|
-
])).optional().describe('분해 차원. 기본: ["versionCode"]'),
|
|
96
|
+
])).optional().describe('분해 차원. 기본: ["versionCode"] (errorCount는 ["reportType","versionCode"]). reportType/issueId는 errorCount 전용, countryCode/deviceBrand는 anrRate/crashRate 전용'),
|
|
68
97
|
metrics: z.array(z.string()).optional().describe('조회 metric. 기본은 metricSet별 핵심 metric + distinctUsers'),
|
|
69
98
|
filter: z.string().optional().describe('AIP-160 필터. 예: versionCode = 42'),
|
|
70
99
|
pageSize: z.number().int().min(1).max(100000).optional().describe('최대 행 수'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yoonion/mimi-seed-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.31",
|
|
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": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"homepage": "https://mimi-seed.pryzm.gg",
|
|
38
38
|
"repository": {
|
|
39
39
|
"type": "git",
|
|
40
|
-
"url": "git+https://github.com/jeonghwanko/mimi-seed.git",
|
|
40
|
+
"url": "git+https://github.com/jeonghwanko/mimi-seed-sdk.git",
|
|
41
41
|
"directory": "packages/mcp-server"
|
|
42
42
|
},
|
|
43
43
|
"publishConfig": {
|